summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/notes/stores
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-07-25 20:01:53 +0100
committerFilipa Lacerda <filipa@gitlab.com>2017-07-25 20:01:53 +0100
commitcf5cc6a9cc0ac1a8fe2ae9dff260fd66e316e483 (patch)
tree1d9f8e8ed576763a45253302fa8ccdead4b4c455 /app/assets/javascripts/notes/stores
parentfbdc02adc1682fe40c68e850496d94b89491a6f9 (diff)
downloadgitlab-ce-cf5cc6a9cc0ac1a8fe2ae9dff260fd66e316e483.tar.gz
Follow vuex docs to divide store into: actions, getters and mutations
Use constants for mutation types as per vuex docs
Diffstat (limited to 'app/assets/javascripts/notes/stores')
-rw-r--r--app/assets/javascripts/notes/stores/actions.js205
-rw-r--r--app/assets/javascripts/notes/stores/getters.js15
-rw-r--r--app/assets/javascripts/notes/stores/index.js18
-rw-r--r--app/assets/javascripts/notes/stores/issue_notes_store.js342
-rw-r--r--app/assets/javascripts/notes/stores/issue_notes_utils.js35
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js11
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js127
-rw-r--r--app/assets/javascripts/notes/stores/utils.js31
8 files changed, 407 insertions, 377 deletions
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
new file mode 100644
index 00000000000..3cfdcd68e0d
--- /dev/null
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -0,0 +1,205 @@
+/* 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);
+ }
+};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
new file mode 100644
index 00000000000..c3a9f0a5e89
--- /dev/null
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -0,0 +1,15 @@
+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;
+};
diff --git a/app/assets/javascripts/notes/stores/index.js b/app/assets/javascripts/notes/stores/index.js
new file mode 100644
index 00000000000..edca63fae67
--- /dev/null
+++ b/app/assets/javascripts/notes/stores/index.js
@@ -0,0 +1,18 @@
+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,
+});
diff --git a/app/assets/javascripts/notes/stores/issue_notes_store.js b/app/assets/javascripts/notes/stores/issue_notes_store.js
deleted file mode 100644
index abe5edee03b..00000000000
--- a/app/assets/javascripts/notes/stores/issue_notes_store.js
+++ /dev/null
@@ -1,342 +0,0 @@
-/* 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,
-};
diff --git a/app/assets/javascripts/notes/stores/issue_notes_utils.js b/app/assets/javascripts/notes/stores/issue_notes_utils.js
deleted file mode 100644
index eac80c9f9c2..00000000000
--- a/app/assets/javascripts/notes/stores/issue_notes_utils.js
+++ /dev/null
@@ -1,35 +0,0 @@
-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();
- },
-};
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
new file mode 100644
index 00000000000..f84f26684ca
--- /dev/null
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -0,0 +1,11 @@
+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';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
new file mode 100644
index 00000000000..61be4aa1864
--- /dev/null
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -0,0 +1,127 @@
+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);
+ }
+ },
+};
diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js
new file mode 100644
index 00000000000..6074115e855
--- /dev/null
+++ b/app/assets/javascripts/notes/stores/utils.js
@@ -0,0 +1,31 @@
+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();
+