Follow vuex docs to divide store into: actions, getters and mutations
Use constants for mutation types as per vuex docs
This commit is contained in:
parent
fbdc02adc1
commit
cf5cc6a9cc
13 changed files with 446 additions and 408 deletions
|
@ -209,12 +209,13 @@ export default {
|
|||
aria-hidden="true"
|
||||
class="fa fa-caret-down toggle-icon"></i>
|
||||
</button>
|
||||
<ul
|
||||
class="note-type-dropdown dropdown-open-top dropdown-menu">
|
||||
<ul class="note-type-dropdown dropdown-open-top dropdown-menu">
|
||||
<li
|
||||
:class="{ 'droplab-item-selected': noteType === 'comment' }"
|
||||
@click.prevent="setNoteType('comment')">
|
||||
<button class="btn btn-transparent">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-transparent">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-check icon"></i>
|
||||
|
@ -230,10 +231,13 @@ export default {
|
|||
<li
|
||||
:class="{ 'droplab-item-selected': noteType === 'discussion' }"
|
||||
@click.prevent="setNoteType('discussion')">
|
||||
<button class="btn btn-transparent">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-transparent">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-check icon"></i>
|
||||
class="fa fa-check icon">
|
||||
</i>
|
||||
<div class="description">
|
||||
<strong>Start discussion</strong>
|
||||
<p>
|
||||
|
@ -244,21 +248,21 @@ export default {
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a
|
||||
<button
|
||||
type="button"
|
||||
@click="handleSave(true)"
|
||||
v-if="canUpdateIssue"
|
||||
:class="actionButtonClassNames"
|
||||
class="btn btn-nr btn-comment btn-comment-and-close"
|
||||
role="button">
|
||||
class="btn btn-nr btn-comment btn-comment-and-close">
|
||||
{{issueActionButtonTitle}}
|
||||
</a>
|
||||
<a
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
v-if="note.length"
|
||||
@click="discard"
|
||||
class="btn btn-cancel js-note-discard"
|
||||
role="button">
|
||||
class="btn btn-cancel js-note-discard">
|
||||
Discard draft
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -71,7 +71,8 @@ export default {
|
|||
cancelReplyForm(shouldConfirm) {
|
||||
if (shouldConfirm && this.$refs.noteForm.isDirty) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
@ -112,7 +113,8 @@ export default {
|
|||
:link-href="author.path"
|
||||
:img-src="author.avatar_url"
|
||||
:img-alt="author.name"
|
||||
:img-size="40" />
|
||||
:img-size="40"
|
||||
/>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="discussion">
|
||||
|
@ -123,13 +125,15 @@ export default {
|
|||
:note-id="discussion.id"
|
||||
:include-toggle="true"
|
||||
:toggle-handler="toggleDiscussion"
|
||||
actionText="started a discussion" />
|
||||
actionText="started a discussion"
|
||||
/>
|
||||
<issue-note-edited-text
|
||||
v-if="note.last_updated_by"
|
||||
:edited-at="note.last_updated_at"
|
||||
:edited-by="note.last_updated_by"
|
||||
actionText="Last updated"
|
||||
className="discussion-headline-light js-discussion-headline" />
|
||||
className="discussion-headline-light js-discussion-headline"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -142,7 +146,8 @@ export default {
|
|||
v-for="note in note.notes"
|
||||
:is="componentName(note)"
|
||||
:note="componentData(note)"
|
||||
key="note.id" />
|
||||
key="note.id"
|
||||
/>
|
||||
</ul>
|
||||
<div class="flash-container"></div>
|
||||
<div class="discussion-reply-holder">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
|
||||
import emojiSmile from 'icons/_emoji_smile.svg';
|
||||
import emojiSmiley from 'icons/_emoji_smiley.svg';
|
||||
import loadingIcon from '../../vue_shared/components/loadingIcon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
@ -78,9 +79,7 @@ export default {
|
|||
data-position="right"
|
||||
href="#"
|
||||
title="Add reaction">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="fa fa-spinner fa-spin"></i>
|
||||
<loading-icon />
|
||||
<span
|
||||
v-html="emojiSmiling"
|
||||
class="link-highlight award-control-icon-neutral"></span>
|
||||
|
@ -122,15 +121,14 @@ export default {
|
|||
</a>
|
||||
</li>
|
||||
<li v-if="canEdit">
|
||||
<a
|
||||
<button
|
||||
@click.prevent="deleteHandler"
|
||||
class="btn btn-transparent js-note-delete js-note-delete"
|
||||
href="#"
|
||||
type="button">
|
||||
<span class="text-danger">
|
||||
Delete comment
|
||||
</span>
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -12,10 +12,7 @@ import issueSystemNote from './issue_system_note.vue';
|
|||
import issueCommentForm from './issue_comment_form.vue';
|
||||
import placeholderNote from './issue_placeholder_note.vue';
|
||||
import placeholderSystemNote from './issue_placeholder_system_note.vue';
|
||||
|
||||
Vue.use(Vuex);
|
||||
Vue.use(VueResource);
|
||||
const store = new Vuex.Store(storeOptions);
|
||||
import store from './store';
|
||||
|
||||
export default {
|
||||
name: 'IssueNotes',
|
||||
|
|
|
@ -8,9 +8,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
components: {
|
||||
issueNotes,
|
||||
},
|
||||
template: `
|
||||
<issue-notes ref="notes" />
|
||||
`,
|
||||
render(createElement) {
|
||||
return createElement('issue-notes', {
|
||||
attrs: {
|
||||
ref: 'notes',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
window.issueNotes = {
|
||||
|
|
205
app/assets/javascripts/notes/stores/actions.js
Normal file
205
app/assets/javascripts/notes/stores/actions.js
Normal file
|
@ -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);
|
||||
}
|
||||
};
|
15
app/assets/javascripts/notes/stores/getters.js
Normal file
15
app/assets/javascripts/notes/stores/getters.js
Normal file
|
@ -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;
|
||||
};
|
18
app/assets/javascripts/notes/stores/index.js
Normal file
18
app/assets/javascripts/notes/stores/index.js
Normal file
|
@ -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,
|
||||
});
|
|
@ -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,
|
||||
};
|
|
@ -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();
|
||||
},
|
||||
};
|
11
app/assets/javascripts/notes/stores/mutation_types.js
Normal file
11
app/assets/javascripts/notes/stores/mutation_types.js
Normal file
|
@ -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';
|
127
app/assets/javascripts/notes/stores/mutations.js
Normal file
127
app/assets/javascripts/notes/stores/mutations.js
Normal file
|
@ -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);
|
||||
}
|
||||
},
|
||||
};
|
31
app/assets/javascripts/notes/stores/utils.js
Normal file
31
app/assets/javascripts/notes/stores/utils.js
Normal file
|
@ -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();
|
||||
|
Loading…
Reference in a new issue