diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2017-07-27 21:24:05 +0100 |
---|---|---|
committer | Filipa Lacerda <filipa@gitlab.com> | 2017-07-27 21:25:02 +0100 |
commit | d34c620fae23597e0130474fe20883f0718ded58 (patch) | |
tree | da89ba9fc2dcbc731fb3fd1e98dc25ad063455f8 | |
parent | 487ed06f444892abce47a6e21aaea79c9ca9c800 (diff) | |
download | gitlab-ce-d34c620fae23597e0130474fe20883f0718ded58.tar.gz |
[ci skip] Add issue data and notes data provided through haml to the store to stop querying the DOM everywhere
-rw-r--r-- | app/assets/javascripts/notes/components/issue_comment_form.vue | 16 | ||||
-rw-r--r-- | app/assets/javascripts/notes/components/issue_notes_app.vue (renamed from app/assets/javascripts/notes/components/issue_notes.vue) | 54 | ||||
-rw-r--r-- | app/assets/javascripts/notes/components/issue_system_note.vue | 5 | ||||
-rw-r--r-- | app/assets/javascripts/notes/constants.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/notes/index.js | 58 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/actions.js | 91 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/getters.js | 6 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/index.js | 5 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/mutation_types.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/mutations.js | 12 | ||||
-rw-r--r-- | app/views/projects/issues/_discussion.html.haml | 12 |
11 files changed, 154 insertions, 110 deletions
diff --git a/app/assets/javascripts/notes/components/issue_comment_form.vue b/app/assets/javascripts/notes/components/issue_comment_form.vue index a8d8a22be76..b39dd2febe9 100644 --- a/app/assets/javascripts/notes/components/issue_comment_form.vue +++ b/app/assets/javascripts/notes/components/issue_comment_form.vue @@ -1,7 +1,7 @@ <script> /* global Flash */ - import { mapActions } from 'vuex'; + import { mapActions, mapGetters } from 'vuex'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import markdownField from '../../vue_shared/components/markdown/field.vue'; import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue'; @@ -30,6 +30,10 @@ issueNoteSignedOutWidget, }, computed: { + ...mapGetters([ + 'getNotesDataByProp', + 'getIssueDataByProp', + ]), isLoggedIn() { return window.gon.current_user_id; }, @@ -57,8 +61,7 @@ }; }, canUpdateIssue() { - const { issueData } = window.gl; - return issueData && issueData.current_user.can_update; + return this.getIssueDataByProp(current_user).can_update; }, }, methods: { @@ -146,11 +149,8 @@ }, }, mounted() { - const issuableDataEl = document.getElementById('js-issuable-app-initial-data'); - const issueData = JSON.parse(issuableDataEl.innerHTML.replace(/"/g, '"')); - - this.markdownDocsUrl = issueData.markdownDocs; - this.quickActionsDocsUrl = issueData.quickActionsDocs; + this.markdownDocsUrl = this.getIssueDataByProp(markdownDocs); + this.quickActionsDocsUrl = this.getIssueDataByProp(quickActionsDocs); eventHub.$on('issueStateChanged', (isClosed) => { this.issueState = isClosed ? constants.CLOSED : constants.REOPENED; diff --git a/app/assets/javascripts/notes/components/issue_notes.vue b/app/assets/javascripts/notes/components/issue_notes_app.vue index b1c8f495eb9..a3b6d928a51 100644 --- a/app/assets/javascripts/notes/components/issue_notes.vue +++ b/app/assets/javascripts/notes/components/issue_notes_app.vue @@ -5,7 +5,6 @@ import { mapGetters, mapActions, mapMutations } from 'vuex'; import store from '../stores/'; import * as constants from '../constants' - import * as types from '../stores/mutation_types'; import eventHub from '../event_hub'; import issueNote from './issue_note.vue'; import issueDiscussion from './issue_discussion.vue'; @@ -17,6 +16,16 @@ export default { name: 'IssueNotes', + props: { + issueData: { + type: Object, + required: true, + }, + notesData: { + type: Object, + required: true, + }, + }, store, data() { return { @@ -36,20 +45,19 @@ ...mapGetters([ 'notes', 'notesById', + 'getNotesData', + 'getNotesDataByProp', + 'setLastFetchedAt', + 'setTargetNoteHash', ]), }, methods: { ...mapActions({ actionFetchNotes: 'fetchNotes', - }), - ...mapActions([ - 'poll', - 'toggleAward', - 'scrollToNoteIfNeeded', - ]), - ...mapMutations({ - setLastFetchedAt: types.SET_LAST_FETCHED_AT, - setTargetNoteHash: types.SET_TARGET_NOTE_HASH, + poll: 'poll', + toggleAward: 'toggleAward', + scrollToNoteIfNeeded: 'scrollToNoteIfNeeded', + setNotesData: 'setNotesData' }), getComponentName(note) { if (note.isPlaceholderNote) { @@ -67,9 +75,7 @@ return note.individual_note ? note.notes[0] : note; }, fetchNotes() { - const { discussionsPath } = this.$el.parentNode.dataset; - - this.actionFetchNotes(discussionsPath) + this.actionFetchNotes(his.getNotesDataByProp('discussionsPath')) .then(() => { this.isLoading = false; @@ -78,23 +84,19 @@ this.checkLocationHash(); }); }) - .catch(() => { - Flash('Something went wrong while fetching issue comments. Please try again.'); - }); + .catch(() => Flash('Something went wrong while fetching issue comments. Please try again.')); }, initPolling() { - const { lastFetchedAt } = $('.js-notes-wrapper')[0].dataset; - this.setLastFetchedAt(lastFetchedAt); + this.setLastFetchedAt(this.getNotesDataByProp('lastFetchedAt')); // FIXME: @fatihacet Implement real polling mechanism + // TODO: FILIPA: DEAL WITH THIS setInterval(() => { this.poll() .then((res) => { this.setLastFetchedAt(res.lastFetchedAt); }) - .catch(() => { - Flash('Something went wrong while fetching latest comments.'); - }); + .catch(() => Flash('Something went wrong while fetching latest comments.')); }, 15000); }, bindEventHubListeners() { @@ -106,6 +108,7 @@ .catch(() => Flash('Something went wrong on our end.')); }); + //TODO: FILIPA: REMOVE JQUERY $(document).on('issuable:change', (e, isClosed) => { eventHub.$emit('issueStateChanged', isClosed); }); @@ -120,6 +123,10 @@ } }, }, + created() { + this.setNotesData(this.notesData); + this.setIssueData(this.issueData); + }, mounted() { this.fetchNotes(); this.initPolling(); @@ -135,10 +142,12 @@ class="loading"> <loading-icon /> </div> + <ul v-if="!isLoading" id="notes-list" class="notes main-notes-list timeline"> + <component v-for="note in notes" :is="getComponentName(note)" @@ -146,6 +155,7 @@ :key="note.id" /> </ul> - <issue-comment-form v-if="!isLoading" /> + + <issue-comment-form /> </div> </template> diff --git a/app/assets/javascripts/notes/components/issue_system_note.vue b/app/assets/javascripts/notes/components/issue_system_note.vue index 91e1c62bba0..492ba58c6a6 100644 --- a/app/assets/javascripts/notes/components/issue_system_note.vue +++ b/app/assets/javascripts/notes/components/issue_system_note.vue @@ -38,8 +38,9 @@ :class="{ target: isTargetNote }" class="note system-note timeline-entry"> <div class="timeline-entry-inner"> - <div class="timeline-icon"> - <span v-html="svg"></span> + <div + class="timeline-icon" + v-html="svg"> </div> <div class="timeline-content"> <div class="note-header"> diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js index 53fb03bab41..0ebde2dccb8 100644 --- a/app/assets/javascripts/notes/constants.js +++ b/app/assets/javascripts/notes/constants.js @@ -6,3 +6,5 @@ export const COMMENT = 'comment'; export const OPENED = 'opened'; export const REOPENED = 'reopened'; export const CLOSED = 'closed'; +export const EMOJI_THUMBSUP = 'thumbsup'; +export const EMOJI_THUMBSDOWN = 'thumbsdown'; diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index 914e08ff112..0bf52b551f4 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -1,25 +1,39 @@ import Vue from 'vue'; -import issueNotes from './components/issue_notes.vue'; -import '../vue_shared/vue_resource_interceptor'; +import issueNotesApp from './components/issue_notes_app.vue'; -document.addEventListener('DOMContentLoaded', () => { - const vm = new Vue({ - el: '#js-notes', - components: { - issueNotes, - }, - render(createElement) { - return createElement('issue-notes', { - attrs: { - ref: 'notes', - }, - }); - }, - }); +document.addEventListener('DOMContentLoaded', () => new Vue({ + el: '#js-vue-notes', + components: { + issueNotesApp, + }, + data() { + const notesDataset = document.getElementById('js-vue-notes').dataset; - window.issueNotes = { - refresh() { - vm.$refs.notes.$store.dispatch('poll'); - }, - }; -}); + return { + issueData: JSON.parse(notesDataset.issueData), + currentUserData: JSON.parse(notesDataset.currentUserData), + notesData: { + lastFetchedAt: notesDataset.lastFetchedAt, + discussionsPath: notesDataset.discussionsPath, + }, + }; + }, + render(createElement) { + return createElement('issue-notes-app', { + attrs: { + ref: 'notes', + }, + props: { + issueData: this.issueData, + notesData: this.notesData, + }, + }); + }, +})); + + // // TODO: FILIPA: FIX THIS + // window.issueNotes = { + // refresh() { + // vm.$refs.notes.$store.dispatch('poll'); + // }, + // }; diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 092049cb377..7f806fcd675 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -7,6 +7,13 @@ import service from '../services/issue_notes_service'; import loadAwardsHandler from '../../awards_handler'; import sidebarTimeTrackingEventHub from '../../sidebar/event_hub'; +export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data); +export const setIssueData = ({ commit }, data) => commit(types.SET_ISSUE_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_INITAL_NOTES, data); +export const setTargetNoteHash = ({ commit }, data) => commit(types.SET_TARGET_NOTE_HASH, data); + export const fetchNotes = ({ commit }, path) => service .fetchNotes(path) .then(res => res.json()) @@ -20,43 +27,31 @@ export const deleteNote = ({ commit }, note) => service 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); +export const updateNote = ({ commit }, { endpoint, note }) => service + .updateNote(endpoint, note) + .then(res => res.json()) + .then((res) => { + commit(types.UPDATE_NOTE, res); + }); - return 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 createNewNote = ({ commit }, note) => { - const { endpoint, data } = note; + return res; + }); - return 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 saveNote = ({ commit, dispatch }, noteData) => { const { note } = noteData.data.note; @@ -91,6 +86,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { if (hasQuickActions && Object.keys(errors).length) { dispatch('poll'); + $('.js-gfm-input').trigger('clear-commands-cache.atwho'); Flash('Commands applied', 'notice', $(noteData.flashContainer)); } @@ -136,9 +132,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { }; export const poll = ({ commit, state, getters }) => { - const { notesPath } = $('.js-notes-wrapper')[0].dataset; - - return service.poll(`${notesPath}?full_data=1`, state.lastFetchedAt) + return service.poll(state.notesData.notesPath, state.lastFetchedAt) .then(res => res.json()) .then((res) => { if (res.notes.length) { @@ -160,7 +154,6 @@ export const poll = ({ commit, state, getters }) => { } }); } - return res; }); }; @@ -175,20 +168,24 @@ export const toggleAward = ({ commit, getters, dispatch }, data) => { .then(() => { commit(types.TOGGLE_AWARD, { awardName, note }); - if (!skipMutalityCheck && (awardName === 'thumbsup' || awardName === 'thumbsdown')) { - const counterAward = awardName === 'thumbsup' ? 'thumbsdown' : 'thumbsup'; + if (!skipMutalityCheck && + (awardName === constants.EMOJI_THUMBSUP || awardName === constants.EMOJI_THUMBSDOWN)) { + const counterAward = awardName === constants.EMOJI_THUMBSUP ? + constants.EMOJI_THUMBSDOWN : + constants.EMOJI_THUMBSUP; + const targetNote = getters.notesById[noteId]; - let amIAwarded = false; + let noteHasAward = false; targetNote.award_emoji.forEach((a) => { if (a.name === counterAward && a.user.id === window.gon.current_user_id) { - amIAwarded = true; + noteHasAward = true; } }); - if (amIAwarded) { - data.awardName = counterAward; - data.skipMutalityCheck = true; + if (noteHasAward) { + Object.assign(data, { awardName: counterAward }); + Object.assign(data, { kipMutalityCheck: true }); dispatch(types.TOGGLE_AWARD, data); } @@ -197,9 +194,7 @@ export const toggleAward = ({ commit, getters, dispatch }, data) => { }; export const scrollToNoteIfNeeded = (context, el) => { - const isInViewport = gl.utils.isInViewport(el[0]); - - if (!isInViewport) { + if (!gl.utils.isInViewport(el[0])) { gl.utils.scrollToElement(el); } }; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index c3a9f0a5e89..2db3c3f02af 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -1,10 +1,12 @@ export const notes = state => state.notes; - export const targetNoteHash = state => state.targetNoteHash; +export const getNotesDataByProp = state => prop => state.notesData[prop]; +export const getIssueDataByProp = state => prop => state.notesData[prop]; +export const getUserDataByProp = state => prop => state.notesData[prop]; export const notesById = (state) => { const notesByIdObject = {}; - + // TODO: FILIPA: TRANSFORM INTO A REDUCE state.notes.forEach((note) => { note.notes.forEach((n) => { notesByIdObject[n.id] = n; diff --git a/app/assets/javascripts/notes/stores/index.js b/app/assets/javascripts/notes/stores/index.js index edca63fae67..8e0c8531bbc 100644 --- a/app/assets/javascripts/notes/stores/index.js +++ b/app/assets/javascripts/notes/stores/index.js @@ -11,6 +11,11 @@ export default new Vuex.Store({ notes: [], targetNoteHash: null, lastFetchedAt: null, + + // holds endpoints and permissions provided through haml + notesData: {}, + userData: {}, + issueData: {}, }, actions, getters, diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index f84f26684ca..4eccc2af56e 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -2,6 +2,9 @@ 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_NOTES_DATA = 'SET_NOTES_DATA'; +export const SET_ISSUE_DATA = 'SET_ISSUE_DATA'; +export const SET_USER_DATA = 'SET_USER_DATA'; 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'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index bb2ed91e570..4af8b0e6b9d 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -58,6 +58,18 @@ export default { } }, + [types.SET_NOTES_DATA](state, data) { + state.notesData = data; + }, + + [types.SET_ISSUE_DATA](state, data) { + state.issueData = data; + }, + + [types.SET_USER_DATA](state, data) { + state.userData = data; + }, + [types.SET_INITAL_NOTES](state, notes) { state.notes = notes; }, diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 90b97d36d2c..2b4fb6be327 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -3,16 +3,16 @@ = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, format: 'json'), data: {original_text: "Reopen issue", alternative_text: "Comment & reopen issue"}, class: "btn btn-nr btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, format: 'json'), data: {original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' -%section.js-notes-wrapper{ data: { discussions_path: discussions_namespace_project_issue_path(@project.namespace, @project, @issue, format: :json), new_session_path: new_session_path(:user, redirect_to_referer: 'yes'), notes_path: notes_url, last_fetched_at: Time.now.to_i } } - #js-notes +%section + #js-vue-notes{ data: { discussions_path: discussions_namespace_project_issue_path(@project.namespace, @project, @issue, format: :json), + new_session_path: new_session_path(:user, redirect_to_referer: 'yes'), + notes_path: '#{notes_url}?full_data=1', last_fetched_at: Time.now.to_i, + issue_data: serialize_issuable(@issue), + current_user_data: UserSerializer.new.represent(current_user).to_json }} - content_for :page_specific_javascripts do = webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'notes' - -/ #notes{style: "margin-top: 150px"} -/ = render 'shared/notes/notes_with_form', :autocomplete => true - = render "layouts/init_auto_complete" :javascript |