Skip to content
Snippets Groups Projects
Commit 88bd9bac authored by Mike Greiling's avatar Mike Greiling
Browse files

Merge branch 'acet-notes-prettified' into 'master'

Prettify notes app

See merge request gitlab-org/gitlab-ce!17812
parents bcc04515 fdc9ae2e
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -12,7 +12,8 @@ export default {
discussionResolved() {
const { notes, resolved } = this.note;
 
if (notes) { // Decide resolved state using store. Only valid for discussions.
if (notes) {
// Decide resolved state using store. Only valid for discussions.
return notes.every(note => note.resolved && !note.system);
}
 
Loading
Loading
@@ -26,7 +27,9 @@ export default {
 
return __('Comment and resolve discussion');
}
return this.discussionResolved ? __('Unresolve discussion') : __('Resolve discussion');
return this.discussionResolved
? __('Unresolve discussion')
: __('Resolve discussion');
},
},
methods: {
Loading
Loading
@@ -42,7 +45,9 @@ export default {
})
.catch(() => {
this.isResolving = false;
const msg = __('Something went wrong while resolving this discussion. Please try again.');
const msg = __(
'Something went wrong while resolving this discussion. Please try again.',
);
Flash(msg, 'alert', this.$el);
});
},
Loading
Loading
Loading
Loading
@@ -22,7 +22,9 @@ export default {
},
toggleResolveNote(endpoint, isResolved) {
const { RESOLVE_NOTE_METHOD_NAME, UNRESOLVE_NOTE_METHOD_NAME } = constants;
const method = isResolved ? UNRESOLVE_NOTE_METHOD_NAME : RESOLVE_NOTE_METHOD_NAME;
const method = isResolved
? UNRESOLVE_NOTE_METHOD_NAME
: RESOLVE_NOTE_METHOD_NAME;
 
return Vue.http[method](endpoint);
},
Loading
Loading
Loading
Loading
@@ -12,97 +12,115 @@ import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
 
let eTagPoll;
 
export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data);
export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data);
export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
export const setInitialNotes = ({ commit }, data) => commit(types.SET_INITIAL_NOTES, data);
export const setTargetNoteHash = ({ commit }, data) => commit(types.SET_TARGET_NOTE_HASH, data);
export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data);
export const fetchNotes = ({ commit }, path) => service
.fetchNotes(path)
.then(res => res.json())
.then((res) => {
commit(types.SET_INITIAL_NOTES, res);
});
export const setNotesData = ({ commit }, data) =>
commit(types.SET_NOTES_DATA, data);
export const setNoteableData = ({ commit }, data) =>
commit(types.SET_NOTEABLE_DATA, data);
export const setUserData = ({ commit }, data) =>
commit(types.SET_USER_DATA, data);
export const setLastFetchedAt = ({ commit }, data) =>
commit(types.SET_LAST_FETCHED_AT, data);
export const setInitialNotes = ({ commit }, data) =>
commit(types.SET_INITIAL_NOTES, data);
export const setTargetNoteHash = ({ commit }, data) =>
commit(types.SET_TARGET_NOTE_HASH, data);
export const toggleDiscussion = ({ commit }, data) =>
commit(types.TOGGLE_DISCUSSION, data);
export const fetchNotes = ({ commit }, path) =>
service
.fetchNotes(path)
.then(res => res.json())
.then(res => {
commit(types.SET_INITIAL_NOTES, res);
});
 
export const deleteNote = ({ commit }, note) => service
.deleteNote(note.path)
.then(() => {
export const deleteNote = ({ commit }, note) =>
service.deleteNote(note.path).then(() => {
commit(types.DELETE_NOTE, note);
});
 
export const updateNote = ({ commit }, { endpoint, note }) => service
.updateNote(endpoint, note)
.then(res => res.json())
.then((res) => {
commit(types.UPDATE_NOTE, res);
});
export const updateNote = ({ commit }, { endpoint, note }) =>
service
.updateNote(endpoint, note)
.then(res => res.json())
.then(res => {
commit(types.UPDATE_NOTE, res);
});
 
export const replyToDiscussion = ({ commit }, { endpoint, data }) => service
.replyToDiscussion(endpoint, data)
.then(res => res.json())
.then((res) => {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
export const replyToDiscussion = ({ commit }, { endpoint, data }) =>
service
.replyToDiscussion(endpoint, data)
.then(res => res.json())
.then(res => {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
 
return res;
});
return res;
});
 
export const createNewNote = ({ commit }, { endpoint, data }) => service
.createNewNote(endpoint, data)
.then(res => res.json())
.then((res) => {
if (!res.errors) {
commit(types.ADD_NEW_NOTE, res);
}
return res;
});
export const createNewNote = ({ commit }, { endpoint, data }) =>
service
.createNewNote(endpoint, data)
.then(res => res.json())
.then(res => {
if (!res.errors) {
commit(types.ADD_NEW_NOTE, res);
}
return res;
});
 
export const removePlaceholderNotes = ({ commit }) =>
commit(types.REMOVE_PLACEHOLDER_NOTES);
 
export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion }) => service
.toggleResolveNote(endpoint, isResolved)
.then(res => res.json())
.then((res) => {
const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE;
export const toggleResolveNote = (
{ commit },
{ endpoint, isResolved, discussion },
) =>
service
.toggleResolveNote(endpoint, isResolved)
.then(res => res.json())
.then(res => {
const mutationType = discussion
? types.UPDATE_DISCUSSION
: types.UPDATE_NOTE;
 
commit(mutationType, res);
});
commit(mutationType, res);
});
 
export const closeIssue = ({ commit, dispatch, state }) => {
dispatch('toggleStateButtonLoading', true);
return service
.toggleIssueState(state.notesData.closePath)
.then(res => res.json())
.then((data) => {
commit(types.CLOSE_ISSUE);
dispatch('emitStateChangedEvent', data);
dispatch('toggleStateButtonLoading', false);
});
.toggleIssueState(state.notesData.closePath)
.then(res => res.json())
.then(data => {
commit(types.CLOSE_ISSUE);
dispatch('emitStateChangedEvent', data);
dispatch('toggleStateButtonLoading', false);
});
};
 
export const reopenIssue = ({ commit, dispatch, state }) => {
dispatch('toggleStateButtonLoading', true);
return service
.toggleIssueState(state.notesData.reopenPath)
.then(res => res.json())
.then((data) => {
commit(types.REOPEN_ISSUE);
dispatch('emitStateChangedEvent', data);
dispatch('toggleStateButtonLoading', false);
});
.toggleIssueState(state.notesData.reopenPath)
.then(res => res.json())
.then(data => {
commit(types.REOPEN_ISSUE);
dispatch('emitStateChangedEvent', data);
dispatch('toggleStateButtonLoading', false);
});
};
 
export const toggleStateButtonLoading = ({ commit }, value) =>
commit(types.TOGGLE_STATE_BUTTON_LOADING, value);
 
export const emitStateChangedEvent = ({ commit, getters }, data) => {
const event = new CustomEvent('issuable_vue_app:change', { detail: {
data,
isClosed: getters.openState === constants.CLOSED,
} });
const event = new CustomEvent('issuable_vue_app:change', {
detail: {
data,
isClosed: getters.openState === constants.CLOSED,
},
});
 
document.dispatchEvent(event);
};
Loading
Loading
@@ -144,59 +162,70 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
});
}
 
return dispatch(methodToDispatch, noteData)
.then((res) => {
const { errors } = res;
const commandsChanges = res.commands_changes;
return dispatch(methodToDispatch, noteData).then(res => {
const { errors } = res;
const commandsChanges = res.commands_changes;
 
if (hasQuickActions && errors && Object.keys(errors).length) {
eTagPoll.makeRequest();
$('.js-gfm-input').trigger('clear-commands-cache.atwho');
Flash('Commands applied', 'notice', noteData.flashContainer);
}
if (hasQuickActions && errors && Object.keys(errors).length) {
eTagPoll.makeRequest();
 
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.',
'alert',
noteData.flashContainer,
);
});
}
$('.js-gfm-input').trigger('clear-commands-cache.atwho');
Flash('Commands applied', 'notice', noteData.flashContainer);
}
 
if (commandsChanges.spend_time != null || commandsChanges.time_estimate != null) {
sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res);
}
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.',
'alert',
noteData.flashContainer,
);
});
}
 
if (errors && errors.commands_only) {
Flash(errors.commands_only, 'notice', noteData.flashContainer);
if (
commandsChanges.spend_time != null ||
commandsChanges.time_estimate != null
) {
sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res);
}
commit(types.REMOVE_PLACEHOLDER_NOTES);
}
 
return res;
});
if (errors && errors.commands_only) {
Flash(errors.commands_only, 'notice', noteData.flashContainer);
}
commit(types.REMOVE_PLACEHOLDER_NOTES);
return res;
});
};
 
const pollSuccessCallBack = (resp, commit, state, getters) => {
if (resp.notes && resp.notes.length) {
const { notesById } = getters;
 
resp.notes.forEach((note) => {
resp.notes.forEach(note => {
if (notesById[note.id]) {
commit(types.UPDATE_NOTE, note);
} else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) {
const discussion = utils.findNoteObjectById(state.notes, note.discussion_id);
} else if (
note.type === constants.DISCUSSION_NOTE ||
note.type === constants.DIFF_NOTE
) {
const discussion = utils.findNoteObjectById(
state.notes,
note.discussion_id,
);
 
if (discussion) {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
Loading
Loading
@@ -219,9 +248,12 @@ export const poll = ({ commit, state, getters }) => {
resource: service,
method: 'poll',
data: state,
successCallback: resp => resp.json()
.then(data => pollSuccessCallBack(data, commit, state, getters)),
errorCallback: () => Flash('Something went wrong while fetching latest comments.'),
successCallback: resp =>
resp
.json()
.then(data => pollSuccessCallBack(data, commit, state, getters)),
errorCallback: () =>
Flash('Something went wrong while fetching latest comments.'),
});
 
if (!Visibility.hidden()) {
Loading
Loading
@@ -248,15 +280,22 @@ export const restartPolling = () => {
};
 
export const fetchData = ({ commit, state, getters }) => {
const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt };
const requestData = {
endpoint: state.notesData.notesPath,
lastFetchedAt: state.lastFetchedAt,
};
 
service.poll(requestData)
service
.poll(requestData)
.then(resp => resp.json)
.then(data => pollSuccessCallBack(data, commit, state, getters))
.catch(() => Flash('Something went wrong while fetching latest comments.'));
};
 
export const toggleAward = ({ commit, state, getters, dispatch }, { awardName, noteId }) => {
export const toggleAward = (
{ commit, state, getters, dispatch },
{ awardName, noteId },
) => {
commit(types.TOGGLE_AWARD, { awardName, note: getters.notesById[noteId] });
};
 
Loading
Loading
Loading
Loading
@@ -11,27 +11,31 @@ export const getNoteableDataByProp = state => prop => state.noteableData[prop];
export const openState = state => state.noteableData.state;
 
export const getUserData = state => state.userData || {};
export const getUserDataByProp = state => prop => state.userData && state.userData[prop];
export const getUserDataByProp = state => prop =>
state.userData && state.userData[prop];
 
export const notesById = state => state.notes.reduce((acc, note) => {
note.notes.every(n => Object.assign(acc, { [n.id]: n }));
return acc;
}, {});
export const notesById = state =>
state.notes.reduce((acc, note) => {
note.notes.every(n => Object.assign(acc, { [n.id]: n }));
return acc;
}, {});
 
const reverseNotes = array => array.slice(0).reverse();
const isLastNote = (note, state) => !note.system &&
state.userData && note.author &&
const isLastNote = (note, state) =>
!note.system &&
state.userData &&
note.author &&
note.author.id === state.userData.id;
 
export const getCurrentUserLastNote = state => _.flatten(
reverseNotes(state.notes)
.map(note => reverseNotes(note.notes)),
export const getCurrentUserLastNote = state =>
_.flatten(
reverseNotes(state.notes).map(note => reverseNotes(note.notes)),
).find(el => isLastNote(el, state));
 
export const getDiscussionLastNote = state => discussion => reverseNotes(discussion.notes)
.find(el => isLastNote(el, state));
export const getDiscussionLastNote = state => discussion =>
reverseNotes(discussion.notes).find(el => isLastNote(el, state));
 
export const discussionCount = (state) => {
export const discussionCount = state => {
const discussions = state.notes.filter(n => !n.individual_note);
 
return discussions.length;
Loading
Loading
@@ -43,10 +47,10 @@ export const unresolvedDiscussions = (state, getters) => {
return state.notes.filter(n => !n.individual_note && !resolvedMap[n.id]);
};
 
export const resolvedDiscussionsById = (state) => {
export const resolvedDiscussionsById = state => {
const map = {};
 
state.notes.forEach((n) => {
state.notes.forEach(n => {
if (n.notes) {
const resolved = n.notes.every(note => note.resolved && !note.system);
 
Loading
Loading
Loading
Loading
@@ -7,7 +7,7 @@ export default {
[types.ADD_NEW_NOTE](state, note) {
const { discussion_id, type } = note;
const [exists] = state.notes.filter(n => n.id === note.discussion_id);
const isDiscussion = (type === constants.DISCUSSION_NOTE);
const isDiscussion = type === constants.DISCUSSION_NOTE;
 
if (!exists) {
const noteData = {
Loading
Loading
@@ -63,13 +63,15 @@ export default {
const note = notes[i];
const children = note.notes;
 
if (children.length && !note.individual_note) { // remove placeholder from discussions
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
} else if (note.isPlaceholderNote) {
// remove placeholders from state root
notes.splice(i, 1);
}
}
Loading
Loading
@@ -89,10 +91,10 @@ export default {
[types.SET_INITIAL_NOTES](state, notesData) {
const notes = [];
 
notesData.forEach((note) => {
notesData.forEach(note => {
// To support legacy notes, should be very rare case.
if (note.individual_note && note.notes.length > 1) {
note.notes.forEach((n) => {
note.notes.forEach(n => {
notes.push({
...note,
notes: [n], // override notes array to only have one item to mimick individual_note
Loading
Loading
@@ -103,7 +105,7 @@ export default {
 
notes.push({
...note,
expanded: (oldNote ? oldNote.expanded : note.expanded),
expanded: oldNote ? oldNote.expanded : note.expanded,
});
}
});
Loading
Loading
@@ -128,7 +130,9 @@ export default {
notesArr.push({
individual_note: true,
isPlaceholderNote: true,
placeholderType: data.isSystemNote ? constants.SYSTEM_NOTE : constants.NOTE,
placeholderType: data.isSystemNote
? constants.SYSTEM_NOTE
: constants.NOTE,
notes: [
{
body: data.noteBody,
Loading
Loading
@@ -141,12 +145,16 @@ export default {
const { awardName, note } = data;
const { id, name, username } = state.userData;
 
const hasEmojiAwardedByCurrentUser = note.award_emoji
.filter(emoji => emoji.name === data.awardName && emoji.user.id === id);
const hasEmojiAwardedByCurrentUser = note.award_emoji.filter(
emoji => emoji.name === data.awardName && emoji.user.id === id,
);
 
if (hasEmojiAwardedByCurrentUser.length) {
// If current user has awarded this emoji, remove it.
note.award_emoji.splice(note.award_emoji.indexOf(hasEmojiAwardedByCurrentUser[0]), 1);
note.award_emoji.splice(
note.award_emoji.indexOf(hasEmojiAwardedByCurrentUser[0]),
1,
);
} else {
note.award_emoji.push({
name: awardName,
Loading
Loading
Loading
Loading
@@ -2,13 +2,15 @@ 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 findNoteObjectById = (notes, id) =>
notes.filter(n => n.id === id)[0];
 
export const getQuickActionText = (note) => {
export const getQuickActionText = note => {
let text = 'Applying command';
const quickActions = AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
const quickActions =
AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
 
const executedCommands = quickActions.filter((command) => {
const executedCommands = quickActions.filter(command => {
const commandRegex = new RegExp(`/${command.name}`);
return commandRegex.test(note);
});
Loading
Loading
@@ -27,4 +29,5 @@ export const getQuickActionText = (note) => {
 
export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note);
 
export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim();
export const stripQuickActions = note =>
note.replace(REGEX_QUICK_ACTIONS, '').trim();
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment