Commit cf5cc6a9 authored by Filipa Lacerda's avatar Filipa Lacerda

Follow vuex docs to divide store into: actions, getters and mutations

Use constants for mutation types as per vuex docs
parent fbdc02ad
...@@ -209,12 +209,13 @@ export default { ...@@ -209,12 +209,13 @@ export default {
aria-hidden="true" aria-hidden="true"
class="fa fa-caret-down toggle-icon"></i> class="fa fa-caret-down toggle-icon"></i>
</button> </button>
<ul <ul class="note-type-dropdown dropdown-open-top dropdown-menu">
class="note-type-dropdown dropdown-open-top dropdown-menu">
<li <li
:class="{ 'droplab-item-selected': noteType === 'comment' }" :class="{ 'droplab-item-selected': noteType === 'comment' }"
@click.prevent="setNoteType('comment')"> @click.prevent="setNoteType('comment')">
<button class="btn btn-transparent"> <button
type="button"
class="btn btn-transparent">
<i <i
aria-hidden="true" aria-hidden="true"
class="fa fa-check icon"></i> class="fa fa-check icon"></i>
...@@ -230,10 +231,13 @@ export default { ...@@ -230,10 +231,13 @@ export default {
<li <li
:class="{ 'droplab-item-selected': noteType === 'discussion' }" :class="{ 'droplab-item-selected': noteType === 'discussion' }"
@click.prevent="setNoteType('discussion')"> @click.prevent="setNoteType('discussion')">
<button class="btn btn-transparent"> <button
type="button"
class="btn btn-transparent">
<i <i
aria-hidden="true" aria-hidden="true"
class="fa fa-check icon"></i> class="fa fa-check icon">
</i>
<div class="description"> <div class="description">
<strong>Start discussion</strong> <strong>Start discussion</strong>
<p> <p>
...@@ -244,21 +248,21 @@ export default { ...@@ -244,21 +248,21 @@ export default {
</li> </li>
</ul> </ul>
</div> </div>
<a <button
type="button"
@click="handleSave(true)" @click="handleSave(true)"
v-if="canUpdateIssue" v-if="canUpdateIssue"
:class="actionButtonClassNames" :class="actionButtonClassNames"
class="btn btn-nr btn-comment btn-comment-and-close" class="btn btn-nr btn-comment btn-comment-and-close">
role="button">
{{issueActionButtonTitle}} {{issueActionButtonTitle}}
</a> </button>
<a <button
type="button"
v-if="note.length" v-if="note.length"
@click="discard" @click="discard"
class="btn btn-cancel js-note-discard" class="btn btn-cancel js-note-discard">
role="button">
Discard draft Discard draft
</a> </button>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -71,7 +71,8 @@ export default { ...@@ -71,7 +71,8 @@ export default {
cancelReplyForm(shouldConfirm) { cancelReplyForm(shouldConfirm) {
if (shouldConfirm && this.$refs.noteForm.isDirty) { if (shouldConfirm && this.$refs.noteForm.isDirty) {
const msg = 'Are you sure you want to cancel creating this comment?'; const msg = 'Are you sure you want to cancel creating this comment?';
const isConfirmed = confirm(msg); // eslint-disable-line // eslint-disable-next-line no-alert
const isConfirmed = confirm(msg);
if (!isConfirmed) { if (!isConfirmed) {
return; return;
} }
...@@ -112,7 +113,8 @@ export default { ...@@ -112,7 +113,8 @@ export default {
:link-href="author.path" :link-href="author.path"
:img-src="author.avatar_url" :img-src="author.avatar_url"
:img-alt="author.name" :img-alt="author.name"
:img-size="40" /> :img-size="40"
/>
</div> </div>
<div class="timeline-content"> <div class="timeline-content">
<div class="discussion"> <div class="discussion">
...@@ -123,13 +125,15 @@ export default { ...@@ -123,13 +125,15 @@ export default {
:note-id="discussion.id" :note-id="discussion.id"
:include-toggle="true" :include-toggle="true"
:toggle-handler="toggleDiscussion" :toggle-handler="toggleDiscussion"
actionText="started a discussion" /> actionText="started a discussion"
/>
<issue-note-edited-text <issue-note-edited-text
v-if="note.last_updated_by" v-if="note.last_updated_by"
:edited-at="note.last_updated_at" :edited-at="note.last_updated_at"
:edited-by="note.last_updated_by" :edited-by="note.last_updated_by"
actionText="Last updated" actionText="Last updated"
className="discussion-headline-light js-discussion-headline" /> className="discussion-headline-light js-discussion-headline"
/>
</div> </div>
</div> </div>
<div <div
...@@ -142,7 +146,8 @@ export default { ...@@ -142,7 +146,8 @@ export default {
v-for="note in note.notes" v-for="note in note.notes"
:is="componentName(note)" :is="componentName(note)"
:note="componentData(note)" :note="componentData(note)"
key="note.id" /> key="note.id"
/>
</ul> </ul>
<div class="flash-container"></div> <div class="flash-container"></div>
<div class="discussion-reply-holder"> <div class="discussion-reply-holder">
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg'; import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
import emojiSmile from 'icons/_emoji_smile.svg'; import emojiSmile from 'icons/_emoji_smile.svg';
import emojiSmiley from 'icons/_emoji_smiley.svg'; import emojiSmiley from 'icons/_emoji_smiley.svg';
import loadingIcon from '../../vue_shared/components/loadingIcon.vue';
export default { export default {
props: { props: {
...@@ -78,9 +79,7 @@ export default { ...@@ -78,9 +79,7 @@ export default {
data-position="right" data-position="right"
href="#" href="#"
title="Add reaction"> title="Add reaction">
<i <loading-icon />
aria-hidden="true"
class="fa fa-spinner fa-spin"></i>
<span <span
v-html="emojiSmiling" v-html="emojiSmiling"
class="link-highlight award-control-icon-neutral"></span> class="link-highlight award-control-icon-neutral"></span>
...@@ -122,15 +121,14 @@ export default { ...@@ -122,15 +121,14 @@ export default {
</a> </a>
</li> </li>
<li v-if="canEdit"> <li v-if="canEdit">
<a <button
@click.prevent="deleteHandler" @click.prevent="deleteHandler"
class="btn btn-transparent js-note-delete js-note-delete" class="btn btn-transparent js-note-delete js-note-delete"
href="#"
type="button"> type="button">
<span class="text-danger"> <span class="text-danger">
Delete comment Delete comment
</span> </span>
</a> </button>
</li> </li>
</ul> </ul>
</div> </div>
......
...@@ -12,10 +12,7 @@ import issueSystemNote from './issue_system_note.vue'; ...@@ -12,10 +12,7 @@ import issueSystemNote from './issue_system_note.vue';
import issueCommentForm from './issue_comment_form.vue'; import issueCommentForm from './issue_comment_form.vue';
import placeholderNote from './issue_placeholder_note.vue'; import placeholderNote from './issue_placeholder_note.vue';
import placeholderSystemNote from './issue_placeholder_system_note.vue'; import placeholderSystemNote from './issue_placeholder_system_note.vue';
import store from './store';
Vue.use(Vuex);
Vue.use(VueResource);
const store = new Vuex.Store(storeOptions);
export default { export default {
name: 'IssueNotes', name: 'IssueNotes',
......
...@@ -8,9 +8,13 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -8,9 +8,13 @@ document.addEventListener('DOMContentLoaded', () => {
components: { components: {
issueNotes, issueNotes,
}, },
template: ` render(createElement) {
<issue-notes ref="notes" /> return createElement('issue-notes', {
`, attrs: {
ref: 'notes',
},
});
},
}); });
window.issueNotes = { window.issueNotes = {
......
/* global Flash */
import * as types from './mutation_types';
import * as utils from './issue_notes_utils';
import service from '../services/issue_notes_service';
import loadAwardsHandler from '../../awards_handler';
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
export const fetchNotes = ({ commit }, path) => service
.fetchNotes(path)
.then(res => res.json())
.then((res) => {
commit(types.SET_INITAL_NOTES, res);
});
export const deleteNote = ({ commit }, note) => service
.deleteNote(note.path)
.then(() => {
commit(types.DELETE_NOTE, note);
});
export const updateNote = ({ commit }, data) => {
const { endpoint, note } = data;
return service
.updateNote(endpoint, note)
.then(res => res.json())
.then((res) => {
commit(types.UPDATE_NOTE, res);
});
};
export const replyToDiscussion = ({ commit }, note) => {
const { endpoint, data } = note;
return service
.replyToDiscussion(endpoint, data)
.then(res => res.json())
.then((res) => {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
return res;
});
};
export const createNewNote = ({ commit }, note) => {
const { endpoint, data } = note;
return service
.createNewNote(endpoint, data)
.then(res => res.json())
.then((res) => {
if (!res.errors) {
commit(types.ADD_NEW_NOTE, res);
}
return res;
});
};
export const saveNote = ({ commit, dispatch }, noteData) => {
const { note } = noteData.data.note;
let placeholderText = note;
const hasQuickActions = utils.hasQuickActions(placeholderText);
const replyId = noteData.data.in_reply_to_discussion_id;
const methodToDispatch = replyId ? 'replyToDiscussion' : 'createNewNote';
if (hasQuickActions) {
placeholderText = utils.stripQuickActions(placeholderText);
}
if (placeholderText.length) {
commit(types.SHOW_PLACEHOLDER_NOTE, {
noteBody: placeholderText,
replyId,
});
}
if (hasQuickActions) {
commit(types.SHOW_PLACEHOLDER_NOTE, {
isSystemNote: true,
noteBody: utils.getQuickActionText(note),
replyId,
});
}
return dispatch(methodToDispatch, noteData)
.then((res) => {
const { errors } = res;
const commandsChanges = res.commands_changes;
if (hasQuickActions && Object.keys(errors).length) {
dispatch('poll');
$('.js-gfm-input').trigger('clear-commands-cache.atwho');
Flash('Commands applied', 'notice', $(noteData.flashContainer));
}
if (commandsChanges) {
if (commandsChanges.emoji_award) {
const votesBlock = $('.js-awards-block').eq(0);
loadAwardsHandler()
.then((awardsHandler) => {
awardsHandler.addAwardToEmojiBar(votesBlock, commandsChanges.emoji_award);
awardsHandler.scrollToAwards();
})
.catch(() => {
Flash(
'Something went wrong while adding your award. Please try again.',
null,
$(noteData.flashContainer),
);
});
}
if (commandsChanges.spend_time != null || commandsChanges.time_estimate != null) {
sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res);
}
}
if (errors && errors.commands_only) {
Flash(errors.commands_only, 'notice', $(noteData.flashContainer));
}
commit(types.REMOVE_PLACEHOLDER_NOTES);
return res;
})
.catch(() => {
Flash(
'Your comment could not be submitted! Please check your network connection and try again.',
'alert',
$(noteData.flashContainer),
);
commit(types.REMOVE_PLACEHOLDER_NOTES);
});
};
export const poll = ({ commit, state, getters }) => {
const { notesPath } = $('.js-notes-wrapper')[0].dataset;
return service
.poll(`${notesPath}?full_data=1`, state.lastFetchedAt)
.then(res => res.json())
.then((res) => {
if (res.notes.length) {
const { notesById } = getters;
res.notes.forEach((note) => {
if (notesById[note.id]) {
commit(types.UPDATE_NOTE, note);
} else if (note.type === 'DiscussionNote') {
const discussion = utils.findNoteObjectById(state.notes, note.discussion_id);
if (discussion) {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
} else {
commit(types.ADD_NEW_NOTE, note);
}
} else {
commit(types.ADD_NEW_NOTE, note);
}
});
}
return res;
});
};
export const toggleAward = ({ commit, getters, dispatch }, data) => {
const { endpoint, awardName, noteId, skipMutalityCheck } = data;
const note = getters.notesById[noteId];
return service
.toggleAward(endpoint, { name: awardName })
.then(res => res.json())
.then(() => {
commit(types.TOGGLE_AWARD, { awardName, note });
if (!skipMutalityCheck && (awardName === 'thumbsup' || awardName === 'thumbsdown')) {
const counterAward = awardName === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
const targetNote = getters.notesById[noteId];
let amIAwarded = false;
targetNote.award_emoji.forEach((a) => {
if (a.name === counterAward && a.user.id === window.gon.current_user_id) {
amIAwarded = true;
}
});
if (amIAwarded) {
Object.assign(data, { awardName: counterAward });
Object.assign(data, { skipMutalityCheck: true });
dispatch(types.TOGGLE_AWARD, data);
}
}
});
};
export const scrollToNoteIfNeeded = (context, el) => {
const isInViewport = gl.utils.isInViewport(el[0]);
if (!isInViewport) {
gl.utils.scrollToElement(el);
}
};
export const notes = state => state.notes;
export const targetNoteHash = state => state.targetNoteHash;
export const notesById = (state) => {
const notesByIdObject = {};
state.notes.forEach((note) => {
note.notes.forEach((n) => {
notesByIdObject[n.id] = n;
});
});
return notesByIdObject;
};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
notes: [],
targetNoteHash: null,
lastFetchedAt: null,
},
actions,
getters,
mutations,
});
/* eslint-disable no-param-reassign */
/* global Flash */
import service from '../services/issue_notes_service';
import utils from './issue_notes_utils';
import loadAwardsHandler from '../../awards_handler';
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
const state = {
notes: [],
targetNoteHash: null,
lastFetchedAt: null,
};
const getters = {
notes(storeState) {
return storeState.notes;
},
targetNoteHash(storeState) {
return storeState.targetNoteHash;
},
notesById(storeState) {
const notesById = {};
storeState.notes.forEach((note) => {
note.notes.forEach((n) => {
notesById[n.id] = n;
});
});
return notesById;
},
};
const mutations = {
setInitialNotes(storeState, notes) {
storeState.notes = notes;
},
setTargetNoteHash(storeState, hash) {
storeState.targetNoteHash = hash;
},
toggleDiscussion(storeState, { discussionId }) {
const discussion = utils.findNoteObjectById(storeState.notes, discussionId);
discussion.expanded = !discussion.expanded;
},
deleteNote(storeState, note) {
const noteObj = utils.findNoteObjectById(storeState.notes, note.discussion_id);
if (noteObj.individual_note) {
storeState.notes.splice(storeState.notes.indexOf(noteObj), 1);
} else {
const comment = utils.findNoteObjectById(noteObj.notes, note.id);
noteObj.notes.splice(noteObj.notes.indexOf(comment), 1);
if (!noteObj.notes.length) {
storeState.notes.splice(storeState.notes.indexOf(noteObj), 1);
}
}
},
addNewReplyToDiscussion(storeState, note) {
const noteObj = utils.findNoteObjectById(storeState.notes, note.discussion_id);
if (noteObj) {
noteObj.notes.push(note);
}
},
updateNote(storeState, note) {
const noteObj = utils.findNoteObjectById(storeState.notes, note.discussion_id);
if (noteObj.individual_note) {
noteObj.notes.splice(0, 1, note);
} else {
const comment = utils.findNoteObjectById(noteObj.notes, note.id);
noteObj.notes.splice(noteObj.notes.indexOf(comment), 1, note);
}
},
addNewNote(storeState, note) {
const { discussion_id, type } = note;
const noteData = {
expanded: true,
id: discussion_id,
individual_note: !(type === 'DiscussionNote'),
notes: [note],
reply_id: discussion_id,
};
storeState.notes.push(noteData);
},
toggleAward(storeState, data) {
const { awardName, note } = data;
const { id, name, username } = window.gl.currentUserData;
let index = -1;
note.award_emoji.forEach((a, i) => {
if (a.name === awardName && a.user.id === id) {
index = i;
}
});
if (index > -1) { // if I am awarded, remove my award
note.award_emoji.splice(index, 1);
} else {
note.award_emoji.push({
name: awardName,
user: { id, name, username },
});
}
},
setLastFetchedAt(storeState, fetchedAt) {
storeState.lastFetchedAt = fetchedAt;
},
showPlaceholderNote(storeState, data) {
let notesArr = storeState.notes;
if (data.replyId) {
notesArr = utils.findNoteObjectById(notesArr, data.replyId).notes;
}
notesArr.push({
individual_note: true,
isPlaceholderNote: true,
placeholderType: data.isSystemNote ? 'systemNote' : 'note',
notes: [
{
body: data.noteBody,
},
],
});
},
removePlaceholderNotes(storeState) {
const { notes } = storeState;
for (let i = notes.length - 1; i >= 0; i -= 1) {
const note = notes[i];
const children = note.notes;
if (children.length && !note.individual_note) { // remove placeholder from discussions
for (let j = children.length - 1; j >= 0; j -= 1) {
if (children[j].isPlaceholderNote) {
children.splice(j, 1);
}
}
} else if (note.isPlaceholderNote) { // remove placeholders from state root
notes.splice(i, 1);
}
}
},
};
const actions = {
fetchNotes(context, path) {
return service
.fetchNotes(path)
.then(res => res.json())
.then((res) => {
context.commit('setInitialNotes', res);
});
},
deleteNote(context, note) {
return service
.deleteNote(note.path)
.then(() => {
context.commit('deleteNote', note);
});
},
updateNote(context, data) {
const { endpoint, note } = data;
return service
.updateNote(endpoint, note)
.then(res => res.json())
.then((res) => {
context.commit('updateNote', res);
});
},
replyToDiscussion(context, noteData) {
const { endpoint, data } = noteData;
return service
.replyToDiscussion(endpoint, data)
.then(res => res.json())
.then((res) => {
context.commit('addNewReplyToDiscussion', res);
return res;
});
},
createNewNote(context, noteData) {
const { endpoint, data } = noteData;
return service
.createNewNote(endpoint, data)
.then(res => res.json())
.then((res) => {
if (!res.errors) {
context.commit('addNewNote', res);
}
return res;
});
},
saveNote(context, noteData) {
const { note } = noteData.data.note;
let placeholderText = note;
const hasQuickActions = utils.hasQuickActions(placeholderText);
const replyId = noteData.data.in_reply_to_discussion_id;
const methodToDispatch = replyId ? 'replyToDiscussion' : 'createNewNote';
if (hasQuickActions) {
placeholderText = utils.stripQuickActions(placeholderText);
}
if (placeholderText.length) {
context.commit('showPlaceholderNote', {
noteBody: placeholderText,
replyId,
});
}
if (hasQuickActions) {
context.commit('showPlaceholderNote', {
isSystemNote: true,
noteBody: utils.getQuickActionText(note),
replyId,
});
}
return context.dispatch(methodToDispatch, noteData)
.then((res) => {
const { errors } = res;
const commandsChanges = res.commands_changes;
if (hasQuickActions && Object.keys(errors).length) {
context.dispatch('poll');
$('.js-gfm-input').trigger('clear-commands-cache.atwho');
Flash('Commands applied', 'notice', $(noteData.flashContainer));
}
if (commandsChanges) {
if (commandsChanges.emoji_award) {
const votesBlock = $('.js-awards-block').eq(0);
loadAwardsHandler().then((awardsHandler) => {
awardsHandler.addAwardToEmojiBar(votesBlock, commandsChanges.emoji_award);
awardsHandler.scrollToAwards();
}).catch(() => {
const msg = 'Something went wrong while adding your award. Please try again.';
Flash(msg, $(noteData.flashContainer));
});
}
if (commandsChanges.spend_time != null || commandsChanges.time_estimate != null) {
sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res);
}
}
if (errors && errors.commands_only) {
Flash(errors.commands_only, 'notice', $(noteData.flashContainer));
}
context.commit('removePlaceholderNotes');
return res;
})
.catch(() => {
const msg = 'Your comment could not be submitted! Please check your network connection and try again.';
Flash(msg, 'alert', $(noteData.flashContainer));
context.commit('removePlaceholderNotes');
});
},
poll(context) {
const { notesPath } = $('.js-notes-wrapper')[0].dataset;
return service
.poll(`${notesPath}?full_data=1`, context.state.lastFetchedAt)
.then(res => res.json())
.then((res) => {
if (res.notes.length) {
const { notesById } = context.getters;
res.notes.forEach((note) => {
if (notesById[note.id]) {
context.commit('updateNote', note);
} else if (note.type === 'DiscussionNote') {
const discussion = utils.findNoteObjectById(context.state.notes, note.discussion_id);
if (discussion) {
context.commit('addNewReplyToDiscussion', note);
} else {
context.commit('addNewNote', note);
}
} else {
context.commit('addNewNote', note);
}
});
}
return res;
});
},
toggleAward(context, data) {
const { endpoint, awardName, noteId, skipMutalityCheck } = data;
const note = context.getters.notesById[noteId];
return service
.toggleAward(endpoint, { name: awardName })
.then(res => res.json())
.then(() => {
context.commit('toggleAward', { awardName, note });
if (!skipMutalityCheck && (awardName === 'thumbsup' || awardName === 'thumbsdown')) {
const counterAward = awardName === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
const targetNote = context.getters.notesById[noteId];
let amIAwarded = false;
targetNote.award_emoji.forEach((a) => {
if (a.name === counterAward && a.user.id === window.gon.current_user_id) {
amIAwarded = true;
}
});
if (amIAwarded) {
data.awardName = counterAward;
data.skipMutalityCheck = true;
context.dispatch('toggleAward', data);
}
}
});
},
scrollToNoteIfNeeded(context, el) {
const isInViewport = gl.utils.isInViewport(el[0]);
if (!isInViewport) {
gl.utils.scrollToElement(el);
}
},
};
export default {
state,
getters,
mutations,
actions,
};
import AjaxCache from '~/lib/utils/ajax_cache';
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
export default {
findNoteObjectById(notes, id) {
return notes.filter(n => n.id === id)[0];
},
getQuickActionText(note) {
let text = 'Applying command';
const quickActions = AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
const executedCommands = quickActions.filter((command) => {
const commandRegex = new RegExp(`/${command.name}`);
return commandRegex.test(note);
});
if (executedCommands && executedCommands.length) {
if (executedCommands.length > 1) {
text = 'Applying multiple commands';
} else {
const commandDescription = executedCommands[0].description.toLowerCase();
text = `Applying command to ${commandDescription}`;
}
}
return text;
},
hasQuickActions(note) {
return REGEX_QUICK_ACTIONS.test(note);
},
stripQuickActions(note) {
return note.replace(REGEX_QUICK_ACTIONS, '').trim();
},
};
export const ADD_NEW_NOTE = 'ADD_NEW_NOTE';
export const ADD_NEW_REPLY_TO_DISCUSSION = 'ADD_NEW_REPLY_TO_DISCUSSION';
export const DELETE_NOTE = 'DELETE_NOTE';
export const REMOVE_PLACEHOLDER_NOTES = 'REMOVE_PLACEHOLDER_NOTES';
export const SET_INITAL_NOTES = 'SET_INITIAL_NOTES';
export const SET_LAST_FETCHED_AT = 'SET_LAST_FETCHED_AT';
export const SET_TARGET_NOTE_HASH = 'SET_TARGET_NOTE_HASH';
export const SHOW_PLACEHOLDER_NOTE = 'SHOW_PLACEHOLDER_NOTE';
export const TOGGLE_AWARD = 'TOGGLE_AWARD';
export const TOGGLE_DISCUSSION = 'TOGGLE_DISCUSSION';
export const UPDATE_NOTE = 'UPDATE_NOTE';
import * as utils from './utils';
import * as types from './mutation_types';
export default {
[types.ADD_NEW_NOTE](state, note) {
const { discussion_id, type } = note;
const noteData = {
expanded: true,
id: discussion_id,
individual_note: !(type === 'DiscussionNote'),
notes: [note],
reply_id: discussion_id,
};
state.notes.push(noteData);
},
[types.ADD_NEW_REPLY_TO_DISCUSSION](state, note) {
const noteObj = utils.findNoteObjectById(state.notes, note.discussion_id);
if (noteObj) {
noteObj.notes.push(note);
}
},
[types.DELETE_NOTE](state, note) {
const noteObj = utils.findNoteObjectById(state.notes, note.discussion_id);
if (noteObj.individual_note) {
state.notes.splice(state.notes.indexOf(noteObj), 1);
} else {
const comment = utils.findNoteObjectById(noteObj.notes, note.id);
noteObj.notes.splice(noteObj.notes.indexOf(comment), 1);
if (!noteObj.notes.length) {
state.notes.splice(state.notes.indexOf(noteObj), 1);
}
}
},
[types.REMOVE_PLACEHOLDER_NOTES](state) {
const { notes } = state;
for (let i = notes.length - 1; i >= 0; i -= 1) {
const note = notes[i];
const children = note.notes;
if (children.length && !note.individual_note) { // remove placeholder from discussions
for (let j = children.length - 1; j >= 0; j -= 1) {
if (children[j].isPlaceholderNote) {
children.splice(j, 1);
}
}
} else if (note.isPlaceholderNote) { // remove placeholders from state root
notes.splice(i, 1);
}
}
},
[types.SET_INITAL_NOTES](state, notes) {
state.notes = notes;
},
[types.SET_LAST_FETCHED_AT](state, fetchedAt) {
state.lastFetchedAt = fetchedAt;
},
[types.SET_TARGET_NOTE_HASH](state, hash) {
state.targetNoteHash = hash;
},
[types.SHOW_PLACEHOLDER_NOTE](state, data) {
let notesArr = state.notes;
if (data.replyId) {
notesArr = utils.findNoteObjectById(notesArr, data.replyId).notes;
}
notesArr.push({
individual_note: true,
isPlaceholderNote: true,
placeholderType: data.isSystemNote ? 'systemNote' : 'note',
notes: [
{
body: data.noteBody,
},
],
});
},
[types.TOGGLE_AWARD](state, data) {
const { awardName, note } = data;
const { id, name, username } = window.gl.currentUserData;
let index = -1;
note.award_emoji.forEach((a, i) => {
if (a.name === awardName && a.user.id === id) {
index = i;
}
});
if (index > -1) { // if I am awarded, remove my award
note.award_emoji.splice(index, 1);
} else {
note.award_emoji.push({
name: awardName,
user: { id, name, username },
});
}
},
[types.TOGGLE_DISCUSSION](state, { discussionId }) {
const discussion = utils.findNoteObjectById(state.notes, discussionId);
discussion.expanded = !discussion.expanded;
},
[types.UPDATE_NOTE](state, note) {
const noteObj = utils.findNoteObjectById(state.notes, note.discussion_id);
if (noteObj.individual_note) {
noteObj.notes.splice(0, 1, note);
} else {
const comment = utils.findNoteObjectById(noteObj.notes, note.id);
noteObj.notes.splice(noteObj.notes.indexOf(comment), 1, note);
}
},
};
import AjaxCache from '~/lib/utils/ajax_cache';
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
export const findNoteObjectById = (notes, id) => notes.filter(n => n.id === id)[0];
export const getQuickActionText = (note) => {
let text = 'Applying command';
const quickActions = AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
const executedCommands = quickActions.filter((command) => {
const commandRegex = new RegExp(`/${command.name}`);
return commandRegex.test(note);
});
if (executedCommands && executedCommands.length) {
if (executedCommands.length > 1) {
text = 'Applying multiple commands';
} else {
const commandDescription = executedCommands[0].description.toLowerCase();
text = `Applying command to ${commandDescription}`;
}
}
return text;
};
export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note);
export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim();
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment