Use mapActions, mapGetters and mapMutations for components
This commit is contained in:
parent
4e81ad2ab9
commit
ffef16690c
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
/* global Flash */
|
/* global Flash */
|
||||||
|
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||||
import markdownField from '../../vue_shared/components/markdown/field.vue';
|
import markdownField from '../../vue_shared/components/markdown/field.vue';
|
||||||
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
|
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
|
||||||
|
@ -60,6 +61,9 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions([
|
||||||
|
'saveNote'
|
||||||
|
]),
|
||||||
handleSave(withIssueAction) {
|
handleSave(withIssueAction) {
|
||||||
if (this.note.length) {
|
if (this.note.length) {
|
||||||
const noteData = {
|
const noteData = {
|
||||||
|
@ -79,7 +83,7 @@
|
||||||
noteData.data.note.type = constants.DISCUSSION_NOTE;
|
noteData.data.note.type = constants.DISCUSSION_NOTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.dispatch('saveNote', noteData)
|
this.saveNote(noteData)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.errors) {
|
if (res.errors) {
|
||||||
if (res.errors.commands_only) {
|
if (res.errors.commands_only) {
|
||||||
|
|
|
@ -1,108 +1,112 @@
|
||||||
<script>
|
<script>
|
||||||
/* global Flash */
|
/* global Flash */
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
|
import { TOGGLE_DISCUSSION } from '../stores/mutation_types';
|
||||||
|
import { SYSTEM_NOTE } from '../constants';
|
||||||
|
import issueNote from './issue_note.vue';
|
||||||
|
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||||
|
import issueNoteHeader from './issue_note_header.vue';
|
||||||
|
import issueNoteActions from './issue_note_actions.vue';
|
||||||
|
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
|
||||||
|
import issueNoteEditedText from './issue_note_edited_text.vue';
|
||||||
|
import issueNoteForm from './issue_note_form.vue';
|
||||||
|
import placeholderNote from './issue_placeholder_note.vue';
|
||||||
|
import placeholderSystemNote from './issue_placeholder_system_note.vue';
|
||||||
|
|
||||||
import issueNote from './issue_note.vue';
|
export default {
|
||||||
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
props: {
|
||||||
import issueNoteHeader from './issue_note_header.vue';
|
note: {
|
||||||
import issueNoteActions from './issue_note_actions.vue';
|
type: Object,
|
||||||
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
|
required: true,
|
||||||
import issueNoteEditedText from './issue_note_edited_text.vue';
|
},
|
||||||
import issueNoteForm from './issue_note_form.vue';
|
|
||||||
import placeholderNote from './issue_placeholder_note.vue';
|
|
||||||
import placeholderSystemNote from './issue_placeholder_system_note.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
note: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
},
|
||||||
},
|
data() {
|
||||||
data() {
|
return {
|
||||||
return {
|
newNotePath: window.gl.issueData.create_note_path,
|
||||||
newNotePath: window.gl.issueData.create_note_path,
|
isReplying: false,
|
||||||
isReplying: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
issueNote,
|
|
||||||
userAvatarLink,
|
|
||||||
issueNoteHeader,
|
|
||||||
issueNoteActions,
|
|
||||||
issueNoteSignedOutWidget,
|
|
||||||
issueNoteEditedText,
|
|
||||||
issueNoteForm,
|
|
||||||
placeholderNote,
|
|
||||||
placeholderSystemNote,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
discussion() {
|
|
||||||
return this.note.notes[0];
|
|
||||||
},
|
|
||||||
author() {
|
|
||||||
return this.discussion.author;
|
|
||||||
},
|
|
||||||
canReply() {
|
|
||||||
return window.gl.issueData.current_user.can_create_note;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
componentName(note) {
|
|
||||||
if (note.isPlaceholderNote) {
|
|
||||||
if (note.placeholderType === 'systemNote') {
|
|
||||||
return placeholderSystemNote;
|
|
||||||
}
|
|
||||||
return placeholderNote;
|
|
||||||
}
|
|
||||||
|
|
||||||
return issueNote;
|
|
||||||
},
|
|
||||||
componentData(note) {
|
|
||||||
return note.isPlaceholderNote ? note.notes[0] : note;
|
|
||||||
},
|
|
||||||
toggleDiscussion() {
|
|
||||||
this.$store.commit('toggleDiscussion', {
|
|
||||||
discussionId: this.note.id,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
showReplyForm() {
|
|
||||||
this.isReplying = true;
|
|
||||||
},
|
|
||||||
cancelReplyForm(shouldConfirm) {
|
|
||||||
if (shouldConfirm && this.$refs.noteForm.isDirty) {
|
|
||||||
const msg = 'Are you sure you want to cancel creating this comment?';
|
|
||||||
// eslint-disable-next-line no-alert
|
|
||||||
const isConfirmed = confirm(msg);
|
|
||||||
if (!isConfirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isReplying = false;
|
|
||||||
},
|
|
||||||
saveReply({ note }) {
|
|
||||||
const replyData = {
|
|
||||||
endpoint: this.newNotePath,
|
|
||||||
flashContainer: this.$el,
|
|
||||||
data: {
|
|
||||||
in_reply_to_discussion_id: this.note.reply_id,
|
|
||||||
target_type: 'issue',
|
|
||||||
target_id: this.discussion.noteable_id,
|
|
||||||
note: { note },
|
|
||||||
full_data: true,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$store.dispatch('saveNote', replyData)
|
|
||||||
.then(() => {
|
|
||||||
this.isReplying = false;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
Flash('Something went wrong while adding your reply. Please try again.');
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
components: {
|
||||||
};
|
issueNote,
|
||||||
|
userAvatarLink,
|
||||||
|
issueNoteHeader,
|
||||||
|
issueNoteActions,
|
||||||
|
issueNoteSignedOutWidget,
|
||||||
|
issueNoteEditedText,
|
||||||
|
issueNoteForm,
|
||||||
|
placeholderNote,
|
||||||
|
placeholderSystemNote,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
discussion() {
|
||||||
|
return this.note.notes[0];
|
||||||
|
},
|
||||||
|
author() {
|
||||||
|
return this.discussion.author;
|
||||||
|
},
|
||||||
|
canReply() {
|
||||||
|
return window.gl.issueData.current_user.can_create_note;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions([
|
||||||
|
'saveNote',
|
||||||
|
]),
|
||||||
|
...mapMutations({
|
||||||
|
toggleDiscussion: TOGGLE_DISCUSSION,
|
||||||
|
}),
|
||||||
|
componentName(note) {
|
||||||
|
if (note.isPlaceholderNote) {
|
||||||
|
if (note.placeholderType === SYSTEM_NOTE) {
|
||||||
|
return placeholderSystemNote;
|
||||||
|
}
|
||||||
|
return placeholderNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
return issueNote;
|
||||||
|
},
|
||||||
|
componentData(note) {
|
||||||
|
return note.isPlaceholderNote ? note.notes[0] : note;
|
||||||
|
},
|
||||||
|
toggleDiscussion() {
|
||||||
|
this.toggleDiscussion({ discussionId: this.note.id });
|
||||||
|
},
|
||||||
|
showReplyForm() {
|
||||||
|
this.isReplying = true;
|
||||||
|
},
|
||||||
|
cancelReplyForm(shouldConfirm) {
|
||||||
|
if (shouldConfirm && this.$refs.noteForm.isDirty) {
|
||||||
|
const msg = 'Are you sure you want to cancel creating this comment?';
|
||||||
|
// eslint-disable-next-line no-alert
|
||||||
|
const isConfirmed = confirm(msg);
|
||||||
|
if (!isConfirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isReplying = false;
|
||||||
|
},
|
||||||
|
saveReply({ note }) {
|
||||||
|
const replyData = {
|
||||||
|
endpoint: this.newNotePath,
|
||||||
|
flashContainer: this.$el,
|
||||||
|
data: {
|
||||||
|
in_reply_to_discussion_id: this.note.reply_id,
|
||||||
|
target_type: 'issue',
|
||||||
|
target_id: this.discussion.noteable_id,
|
||||||
|
note: { note },
|
||||||
|
full_data: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.saveNote(replyData)
|
||||||
|
.then(() => {
|
||||||
|
this.isReplying = false;
|
||||||
|
})
|
||||||
|
.catch(() => Flash('Something went wrong while adding your reply. Please try again.'));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -132,8 +136,7 @@ export default {
|
||||||
: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
|
||||||
|
@ -162,7 +165,8 @@ export default {
|
||||||
saveButtonTitle="Comment"
|
saveButtonTitle="Comment"
|
||||||
:update-handler="saveReply"
|
:update-handler="saveReply"
|
||||||
:cancel-handler="cancelReplyForm"
|
:cancel-handler="cancelReplyForm"
|
||||||
ref="noteForm" />
|
ref="noteForm"
|
||||||
|
/>
|
||||||
<issue-note-signed-out-widget v-if="!canReply" />
|
<issue-note-signed-out-widget v-if="!canReply" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,116 +1,118 @@
|
||||||
<script>
|
<script>
|
||||||
/* global Flash */
|
/* global Flash */
|
||||||
|
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||||
import issueNoteHeader from './issue_note_header.vue';
|
import issueNoteHeader from './issue_note_header.vue';
|
||||||
import issueNoteActions from './issue_note_actions.vue';
|
import issueNoteActions from './issue_note_actions.vue';
|
||||||
import issueNoteBody from './issue_note_body.vue';
|
import issueNoteBody from './issue_note_body.vue';
|
||||||
import eventHub from '../event_hub';
|
import eventHub from '../event_hub';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
note: {
|
note: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
data() {
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isEditing: false,
|
|
||||||
isDeleting: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
userAvatarLink,
|
|
||||||
issueNoteHeader,
|
|
||||||
issueNoteActions,
|
|
||||||
issueNoteBody,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapGetters([
|
|
||||||
'targetNoteHash',
|
|
||||||
]),
|
|
||||||
author() {
|
|
||||||
return this.note.author;
|
|
||||||
},
|
|
||||||
classNameBindings() {
|
|
||||||
return {
|
return {
|
||||||
'is-editing': this.isEditing,
|
isEditing: false,
|
||||||
'disabled-content': this.isDeleting,
|
isDeleting: false,
|
||||||
'js-my-note': this.author.id === window.gon.current_user_id,
|
|
||||||
target: this.targetNoteHash === this.noteAnchorId,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
canReportAsAbuse() {
|
components: {
|
||||||
return this.note.report_abuse_path && this.author.id !== window.gon.current_user_id;
|
userAvatarLink,
|
||||||
|
issueNoteHeader,
|
||||||
|
issueNoteActions,
|
||||||
|
issueNoteBody,
|
||||||
},
|
},
|
||||||
noteAnchorId() {
|
computed: {
|
||||||
return `note_${this.note.id}`;
|
...mapGetters([
|
||||||
|
'targetNoteHash',
|
||||||
|
]),
|
||||||
|
author() {
|
||||||
|
return this.note.author;
|
||||||
|
},
|
||||||
|
classNameBindings() {
|
||||||
|
return {
|
||||||
|
'is-editing': this.isEditing,
|
||||||
|
'disabled-content': this.isDeleting,
|
||||||
|
'js-my-note': this.author.id === window.gon.current_user_id,
|
||||||
|
target: this.targetNoteHash === this.noteAnchorId,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
canReportAsAbuse() {
|
||||||
|
return this.note.report_abuse_path && this.author.id !== window.gon.current_user_id;
|
||||||
|
},
|
||||||
|
noteAnchorId() {
|
||||||
|
return `note_${this.note.id}`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
methods: {
|
||||||
methods: {
|
...mapActions([
|
||||||
editHandler() {
|
'deleteNote',
|
||||||
this.isEditing = true;
|
'updateNote',
|
||||||
},
|
'scrollToNoteIfNeeded',
|
||||||
deleteHandler() {
|
]),
|
||||||
const msg = 'Are you sure you want to delete this list?';
|
editHandler() {
|
||||||
const isConfirmed = confirm(msg); // eslint-disable-line
|
|
||||||
|
|
||||||
if (isConfirmed) {
|
|
||||||
this.isDeleting = true;
|
|
||||||
this.$store
|
|
||||||
.dispatch('deleteNote', this.note)
|
|
||||||
.then(() => {
|
|
||||||
this.isDeleting = false;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
new Flash('Something went wrong while deleting your note. Please try again.'); // eslint-disable-line
|
|
||||||
this.isDeleting = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
formUpdateHandler(note) {
|
|
||||||
const data = {
|
|
||||||
endpoint: this.note.path,
|
|
||||||
note: {
|
|
||||||
full_data: true,
|
|
||||||
target_type: 'issue',
|
|
||||||
target_id: this.note.noteable_id,
|
|
||||||
note,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.$store.dispatch('updateNote', data)
|
|
||||||
.then(() => {
|
|
||||||
this.isEditing = false;
|
|
||||||
$(this.$refs.noteBody.$el).renderGFM();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
Flash('Something went wrong while editing your comment. Please try again.');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
formCancelHandler(shouldConfirm) {
|
|
||||||
if (shouldConfirm && this.$refs.noteBody.$refs.noteForm.isDirty) {
|
|
||||||
const msg = 'Are you sure you want to cancel editing this comment?';
|
|
||||||
const isConfirmed = confirm(msg); // eslint-disable-line
|
|
||||||
if (!isConfirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isEditing = false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
eventHub.$on('enterEditMode', ({ noteId }) => {
|
|
||||||
if (noteId === this.note.id) {
|
|
||||||
this.isEditing = true;
|
this.isEditing = true;
|
||||||
this.$store.dispatch('scrollToNoteIfNeeded', $(this.$el));
|
},
|
||||||
}
|
deleteHandler() {
|
||||||
});
|
const msg = 'Are you sure you want to delete this list?';
|
||||||
},
|
const isConfirmed = confirm(msg); // eslint-disable-line
|
||||||
};
|
|
||||||
|
if (isConfirmed) {
|
||||||
|
this.isDeleting = true;
|
||||||
|
this.deleteNote(this.note)
|
||||||
|
.then(() => {
|
||||||
|
this.isDeleting = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
Flash('Something went wrong while deleting your note. Please try again.');
|
||||||
|
this.isDeleting = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formUpdateHandler(note) {
|
||||||
|
const data = {
|
||||||
|
endpoint: this.note.path,
|
||||||
|
note: {
|
||||||
|
full_data: true,
|
||||||
|
target_type: 'issue',
|
||||||
|
target_id: this.note.noteable_id,
|
||||||
|
note,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateNote(data)
|
||||||
|
.then(() => {
|
||||||
|
this.isEditing = false;
|
||||||
|
$(this.$refs.noteBody.$el).renderGFM();
|
||||||
|
})
|
||||||
|
.catch(() => Flash('Something went wrong while editing your comment. Please try again.'));
|
||||||
|
},
|
||||||
|
formCancelHandler(shouldConfirm) {
|
||||||
|
if (shouldConfirm && this.$refs.noteBody.$refs.noteForm.isDirty) {
|
||||||
|
const msg = 'Are you sure you want to cancel editing this comment?';
|
||||||
|
const isConfirmed = confirm(msg); // eslint-disable-line
|
||||||
|
if (!isConfirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isEditing = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
eventHub.$on('enterEditMode', ({ noteId }) => {
|
||||||
|
if (noteId === this.note.id) {
|
||||||
|
this.isEditing = true;
|
||||||
|
this.scrollToNoteIfNeeded($(this.$el));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -124,7 +126,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="note-header">
|
<div class="note-header">
|
||||||
|
@ -132,7 +135,8 @@ export default {
|
||||||
:author="author"
|
:author="author"
|
||||||
:created-at="note.created_at"
|
:created-at="note.created_at"
|
||||||
:note-id="note.id"
|
:note-id="note.id"
|
||||||
actionText="commented" />
|
actionText="commented"
|
||||||
|
/>
|
||||||
<issue-note-actions
|
<issue-note-actions
|
||||||
:author-id="author.id"
|
:author-id="author.id"
|
||||||
:note-id="note.id"
|
:note-id="note.id"
|
||||||
|
@ -142,7 +146,8 @@ export default {
|
||||||
:can-report-as-abuse="canReportAsAbuse"
|
:can-report-as-abuse="canReportAsAbuse"
|
||||||
:report-abuse-path="note.report_abuse_path"
|
:report-abuse-path="note.report_abuse_path"
|
||||||
:edit-handler="editHandler"
|
:edit-handler="editHandler"
|
||||||
:delete-handler="deleteHandler" />
|
:delete-handler="deleteHandler"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<issue-note-body
|
<issue-note-body
|
||||||
:note="note"
|
:note="note"
|
||||||
|
@ -150,7 +155,8 @@ export default {
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
:form-update-handler="formUpdateHandler"
|
:form-update-handler="formUpdateHandler"
|
||||||
:form-cancel-handler="formCancelHandler"
|
:form-cancel-handler="formCancelHandler"
|
||||||
ref="noteBody" />
|
ref="noteBody"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,68 +1,71 @@
|
||||||
<script>
|
<script>
|
||||||
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';
|
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
authorId: {
|
authorId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
|
noteId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
accessLevel: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
reportAbusePath: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
canEdit: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
canDelete: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
canReportAsAbuse: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
editHandler: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
deleteHandler: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
noteId: {
|
data() {
|
||||||
type: Number,
|
return {
|
||||||
required: true,
|
emojiSmiling,
|
||||||
|
emojiSmile,
|
||||||
|
emojiSmiley,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
accessLevel: {
|
components: {
|
||||||
type: String,
|
loadingIcon,
|
||||||
required: false,
|
|
||||||
default: '',
|
|
||||||
},
|
},
|
||||||
reportAbusePath: {
|
computed: {
|
||||||
type: String,
|
shouldShowActionsDropdown() {
|
||||||
required: true,
|
return window.gon.current_user_id && (this.canEdit || this.canReportAsAbuse);
|
||||||
|
},
|
||||||
|
canAddAwardEmoji() {
|
||||||
|
return window.gon.current_user_id;
|
||||||
|
},
|
||||||
|
isAuthoredByMe() {
|
||||||
|
return this.authorId === window.gon.current_user_id;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
canEdit: {
|
};
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
canDelete: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
canReportAsAbuse: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
editHandler: {
|
|
||||||
type: Function,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
deleteHandler: {
|
|
||||||
type: Function,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
emojiSmiling,
|
|
||||||
emojiSmile,
|
|
||||||
emojiSmiley,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
shouldShowActionsDropdown() {
|
|
||||||
return window.gon.current_user_id && (this.canEdit || this.canReportAsAbuse);
|
|
||||||
},
|
|
||||||
canAddAwardEmoji() {
|
|
||||||
return window.gon.current_user_id;
|
|
||||||
},
|
|
||||||
isAuthoredByMe() {
|
|
||||||
return this.authorId === window.gon.current_user_id;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -82,13 +85,16 @@ export default {
|
||||||
<loading-icon />
|
<loading-icon />
|
||||||
<span
|
<span
|
||||||
v-html="emojiSmiling"
|
v-html="emojiSmiling"
|
||||||
class="link-highlight award-control-icon-neutral"></span>
|
class="link-highlight award-control-icon-neutral">
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
v-html="emojiSmiley"
|
v-html="emojiSmiley"
|
||||||
class="link-highlight award-control-icon-positive"></span>
|
class="link-highlight award-control-icon-positive">
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
v-html="emojiSmile"
|
v-html="emojiSmile"
|
||||||
class="link-highlight award-control-icon-super-positive"></span>
|
class="link-highlight award-control-icon-super-positive">
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<div
|
<div
|
||||||
v-if="shouldShowActionsDropdown"
|
v-if="shouldShowActionsDropdown"
|
||||||
|
@ -101,7 +107,8 @@ export default {
|
||||||
data-container="body">
|
data-container="body">
|
||||||
<i
|
<i
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="fa fa-ellipsis-v icon"></i>
|
class="fa fa-ellipsis-v icon">
|
||||||
|
</i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
|
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
|
||||||
<template v-if="canEdit">
|
<template v-if="canEdit">
|
||||||
|
|
|
@ -1,164 +1,166 @@
|
||||||
<script>
|
<script>
|
||||||
/* global Flash */
|
/* global Flash */
|
||||||
|
|
||||||
import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
|
import { mapActions } from 'vuex';
|
||||||
import emojiSmile from 'icons/_emoji_smile.svg';
|
import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
|
||||||
import emojiSmiley from 'icons/_emoji_smiley.svg';
|
import emojiSmile from 'icons/_emoji_smile.svg';
|
||||||
import * as Emoji from '../../emoji';
|
import emojiSmiley from 'icons/_emoji_smiley.svg';
|
||||||
|
import * as Emoji from '../../emoji';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
awards: {
|
awards: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
|
toggleAwardPath: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
noteAuthorId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
noteId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
toggleAwardPath: {
|
data() {
|
||||||
type: String,
|
const userId = window.gon.current_user_id;
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
noteAuthorId: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
noteId: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
const userId = window.gon.current_user_id;
|
|
||||||
|
|
||||||
return {
|
|
||||||
emojiSmiling,
|
|
||||||
emojiSmile,
|
|
||||||
emojiSmiley,
|
|
||||||
canAward: !!userId,
|
|
||||||
myUserId: userId,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// `this.awards` is an array with emojis but they are not grouped by emoji name. See below.
|
|
||||||
// [ { name: foo, user: user1 }, { name: bar, user: user1 }, { name: foo, user: user2 } ]
|
|
||||||
// This method will group emojis by their name as an Object. See below.
|
|
||||||
// {
|
|
||||||
// foo: [ { name: foo, user: user1 }, { name: foo, user: user2 } ],
|
|
||||||
// bar: [ { name: bar, user: user1 } ]
|
|
||||||
// }
|
|
||||||
// We need to do this otherwise we will render the same emoji over and over again.
|
|
||||||
groupedAwards() {
|
|
||||||
const awards = {};
|
|
||||||
const orderedAwards = {};
|
|
||||||
|
|
||||||
this.awards.forEach((award) => {
|
|
||||||
awards[award.name] = awards[award.name] || [];
|
|
||||||
awards[award.name].push(award);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Always show thumbsup and thumbsdown first
|
|
||||||
const { thumbsup, thumbsdown } = awards;
|
|
||||||
if (thumbsup) {
|
|
||||||
orderedAwards.thumbsup = thumbsup;
|
|
||||||
delete awards.thumbsup;
|
|
||||||
}
|
|
||||||
if (thumbsdown) {
|
|
||||||
orderedAwards.thumbsdown = thumbsdown;
|
|
||||||
delete awards.thumbsdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Because for-in forbidden
|
|
||||||
const keys = Object.keys(awards);
|
|
||||||
keys.forEach((key) => {
|
|
||||||
orderedAwards[key] = awards[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
return orderedAwards;
|
|
||||||
},
|
|
||||||
isAuthoredByMe() {
|
|
||||||
return this.noteAuthorId === window.gon.current_user_id;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getAwardHTML(name) {
|
|
||||||
return Emoji.glEmojiTag(name);
|
|
||||||
},
|
|
||||||
getAwardClassBindings(awardList, awardName) {
|
|
||||||
return {
|
return {
|
||||||
active: this.amIAwarded(awardList),
|
emojiSmiling,
|
||||||
disabled: !this.canInteractWithEmoji(awardList, awardName),
|
emojiSmile,
|
||||||
|
emojiSmiley,
|
||||||
|
canAward: !!userId,
|
||||||
|
myUserId: userId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
canInteractWithEmoji(awardList, awardName) {
|
computed: {
|
||||||
let isAllowed = true;
|
// `this.awards` is an array with emojis but they are not grouped by emoji name. See below.
|
||||||
const restrictedEmojis = ['thumbsup', 'thumbsdown'];
|
// [ { name: foo, user: user1 }, { name: bar, user: user1 }, { name: foo, user: user2 } ]
|
||||||
const { myUserId, noteAuthorId } = this;
|
// This method will group emojis by their name as an Object. See below.
|
||||||
|
// {
|
||||||
|
// foo: [ { name: foo, user: user1 }, { name: foo, user: user2 } ],
|
||||||
|
// bar: [ { name: bar, user: user1 } ]
|
||||||
|
// }
|
||||||
|
// We need to do this otherwise we will render the same emoji over and over again.
|
||||||
|
groupedAwards() {
|
||||||
|
const awards = {};
|
||||||
|
const orderedAwards = {};
|
||||||
|
|
||||||
// Users can not add :+1: and :-1: to their notes
|
this.awards.forEach((award) => {
|
||||||
if (myUserId === noteAuthorId && restrictedEmojis.indexOf(awardName) > -1) {
|
awards[award.name] = awards[award.name] || [];
|
||||||
isAllowed = false;
|
awards[award.name].push(award);
|
||||||
}
|
|
||||||
|
|
||||||
return this.canAward && isAllowed;
|
|
||||||
},
|
|
||||||
amIAwarded(awardList) {
|
|
||||||
const isAwarded = awardList.filter(award => award.user.id === this.myUserId);
|
|
||||||
|
|
||||||
return isAwarded.length;
|
|
||||||
},
|
|
||||||
awardTitle(awardsList) {
|
|
||||||
const amIAwarded = this.amIAwarded(awardsList);
|
|
||||||
const TOOLTIP_NAME_COUNT = amIAwarded ? 9 : 10;
|
|
||||||
let awardList = awardsList;
|
|
||||||
|
|
||||||
// Filter myself from list if I am awarded.
|
|
||||||
if (amIAwarded) {
|
|
||||||
awardList = awardList.filter(award => award.user.id !== this.myUserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get only 9-10 usernames to show in tooltip text.
|
|
||||||
const namesToShow = awardList.slice(0, TOOLTIP_NAME_COUNT).map(award => award.user.name);
|
|
||||||
|
|
||||||
// Get the remaining list to use in `and x more` text.
|
|
||||||
const remainingAwardList = awardList.slice(TOOLTIP_NAME_COUNT, awardList.length);
|
|
||||||
|
|
||||||
// Add myself to the begining of the list so title will start with You.
|
|
||||||
if (amIAwarded) {
|
|
||||||
namesToShow.unshift('You');
|
|
||||||
}
|
|
||||||
|
|
||||||
let title = '';
|
|
||||||
|
|
||||||
// We have 10+ awarded user, join them with comma and add `and x more`.
|
|
||||||
if (remainingAwardList.length) {
|
|
||||||
title = `${namesToShow.join(', ')}, and ${remainingAwardList.length} more.`;
|
|
||||||
} else if (namesToShow.length > 1) {
|
|
||||||
// Join all names with comma but not the last one, it will be added with and text.
|
|
||||||
title = namesToShow.slice(0, namesToShow.length - 1).join(', ');
|
|
||||||
// If we have more than 2 users we need an extra comma before and text.
|
|
||||||
title += namesToShow.length > 2 ? ',' : '';
|
|
||||||
title += ` and ${namesToShow.slice(-1)}`; // Append and text
|
|
||||||
} else { // We have only 2 users so join them with and.
|
|
||||||
title = namesToShow.join(' and ');
|
|
||||||
}
|
|
||||||
|
|
||||||
return title;
|
|
||||||
},
|
|
||||||
handleAward(awardName) {
|
|
||||||
const data = {
|
|
||||||
endpoint: this.toggleAwardPath,
|
|
||||||
noteId: this.noteId,
|
|
||||||
awardName,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.$store.dispatch('toggleAward', data)
|
|
||||||
.then(() => {
|
|
||||||
$(this.$el).find('.award-control').tooltip('fixTitle');
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
Flash('Something went wrong on our end.');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Always show thumbsup and thumbsdown first
|
||||||
|
const { thumbsup, thumbsdown } = awards;
|
||||||
|
if (thumbsup) {
|
||||||
|
orderedAwards.thumbsup = thumbsup;
|
||||||
|
delete awards.thumbsup;
|
||||||
|
}
|
||||||
|
if (thumbsdown) {
|
||||||
|
orderedAwards.thumbsdown = thumbsdown;
|
||||||
|
delete awards.thumbsdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because for-in forbidden
|
||||||
|
const keys = Object.keys(awards);
|
||||||
|
keys.forEach((key) => {
|
||||||
|
orderedAwards[key] = awards[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
return orderedAwards;
|
||||||
|
},
|
||||||
|
isAuthoredByMe() {
|
||||||
|
return this.noteAuthorId === window.gon.current_user_id;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
methods: {
|
||||||
};
|
...mapActions([
|
||||||
|
'toggleAward',
|
||||||
|
]),
|
||||||
|
getAwardHTML(name) {
|
||||||
|
return Emoji.glEmojiTag(name);
|
||||||
|
},
|
||||||
|
getAwardClassBindings(awardList, awardName) {
|
||||||
|
return {
|
||||||
|
active: this.amIAwarded(awardList),
|
||||||
|
disabled: !this.canInteractWithEmoji(awardList, awardName),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
canInteractWithEmoji(awardList, awardName) {
|
||||||
|
let isAllowed = true;
|
||||||
|
const restrictedEmojis = ['thumbsup', 'thumbsdown'];
|
||||||
|
const { myUserId, noteAuthorId } = this;
|
||||||
|
|
||||||
|
// Users can not add :+1: and :-1: to their own notes
|
||||||
|
if (myUserId === noteAuthorId && restrictedEmojis.indexOf(awardName) > -1) {
|
||||||
|
isAllowed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.canAward && isAllowed;
|
||||||
|
},
|
||||||
|
amIAwarded(awardList) {
|
||||||
|
const isAwarded = awardList.filter(award => award.user.id === this.myUserId);
|
||||||
|
|
||||||
|
return isAwarded.length;
|
||||||
|
},
|
||||||
|
awardTitle(awardsList) {
|
||||||
|
const amIAwarded = this.amIAwarded(awardsList);
|
||||||
|
const TOOLTIP_NAME_COUNT = amIAwarded ? 9 : 10;
|
||||||
|
let awardList = awardsList;
|
||||||
|
|
||||||
|
// Filter myself from list if I am awarded.
|
||||||
|
if (amIAwarded) {
|
||||||
|
awardList = awardList.filter(award => award.user.id !== this.myUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get only 9-10 usernames to show in tooltip text.
|
||||||
|
const namesToShow = awardList.slice(0, TOOLTIP_NAME_COUNT).map(award => award.user.name);
|
||||||
|
|
||||||
|
// Get the remaining list to use in `and x more` text.
|
||||||
|
const remainingAwardList = awardList.slice(TOOLTIP_NAME_COUNT, awardList.length);
|
||||||
|
|
||||||
|
// Add myself to the begining of the list so title will start with You.
|
||||||
|
if (amIAwarded) {
|
||||||
|
namesToShow.unshift('You');
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = '';
|
||||||
|
|
||||||
|
// We have 10+ awarded user, join them with comma and add `and x more`.
|
||||||
|
if (remainingAwardList.length) {
|
||||||
|
title = `${namesToShow.join(', ')}, and ${remainingAwardList.length} more.`;
|
||||||
|
} else if (namesToShow.length > 1) {
|
||||||
|
// Join all names with comma but not the last one, it will be added with and text.
|
||||||
|
title = namesToShow.slice(0, namesToShow.length - 1).join(', ');
|
||||||
|
// If we have more than 2 users we need an extra comma before and text.
|
||||||
|
title += namesToShow.length > 2 ? ',' : '';
|
||||||
|
title += ` and ${namesToShow.slice(-1)}`; // Append and text
|
||||||
|
} else { // We have only 2 users so join them with and.
|
||||||
|
title = namesToShow.join(' and ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return title;
|
||||||
|
},
|
||||||
|
handleAward(awardName) {
|
||||||
|
const data = {
|
||||||
|
endpoint: this.toggleAwardPath,
|
||||||
|
noteId: this.noteId,
|
||||||
|
awardName,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.toggleAward(data)
|
||||||
|
.then(() => {
|
||||||
|
$(this.$el).find('.award-control').tooltip('fixTitle');
|
||||||
|
})
|
||||||
|
.catch(() => Flash('Something went wrong on our end.'));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -189,13 +191,16 @@ export default {
|
||||||
type="button">
|
type="button">
|
||||||
<span
|
<span
|
||||||
v-html="emojiSmiling"
|
v-html="emojiSmiling"
|
||||||
class="award-control-icon award-control-icon-neutral"></span>
|
class="award-control-icon award-control-icon-neutral">
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
v-html="emojiSmiley"
|
v-html="emojiSmiley"
|
||||||
class="award-control-icon award-control-icon-positive"></span>
|
class="award-control-icon award-control-icon-positive">
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
v-html="emojiSmile"
|
v-html="emojiSmile"
|
||||||
class="award-control-icon award-control-icon-super-positive"></span>
|
class="award-control-icon award-control-icon-super-positive">
|
||||||
|
</span>
|
||||||
<i
|
<i
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>
|
class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>
|
||||||
|
|
|
@ -1,70 +1,70 @@
|
||||||
<script>
|
<script>
|
||||||
import issueNoteEditedText from './issue_note_edited_text.vue';
|
import issueNoteEditedText from './issue_note_edited_text.vue';
|
||||||
import issueNoteAwardsList from './issue_note_awards_list.vue';
|
import issueNoteAwardsList from './issue_note_awards_list.vue';
|
||||||
import issueNoteForm from './issue_note_form.vue';
|
import issueNoteForm from './issue_note_form.vue';
|
||||||
import TaskList from '../../task_list';
|
import TaskList from '../../task_list';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
note: {
|
note: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
|
canEdit: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isEditing: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
formUpdateHandler: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
formCancelHandler: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
canEdit: {
|
components: {
|
||||||
type: Boolean,
|
issueNoteEditedText,
|
||||||
required: true,
|
issueNoteAwardsList,
|
||||||
|
issueNoteForm,
|
||||||
},
|
},
|
||||||
isEditing: {
|
computed: {
|
||||||
type: Boolean,
|
noteBody() {
|
||||||
required: false,
|
return this.note.note;
|
||||||
default: false,
|
},
|
||||||
},
|
},
|
||||||
formUpdateHandler: {
|
methods: {
|
||||||
type: Function,
|
renderGFM() {
|
||||||
required: true,
|
$(this.$refs['note-body']).renderGFM();
|
||||||
},
|
},
|
||||||
formCancelHandler: {
|
initTaskList() {
|
||||||
type: Function,
|
if (this.canEdit) {
|
||||||
required: true,
|
this.taskList = new TaskList({
|
||||||
},
|
dataType: 'note',
|
||||||
},
|
fieldName: 'note',
|
||||||
components: {
|
selector: '.notes',
|
||||||
issueNoteEditedText,
|
});
|
||||||
issueNoteAwardsList,
|
}
|
||||||
issueNoteForm,
|
},
|
||||||
},
|
handleFormUpdate() {
|
||||||
computed: {
|
this.formUpdateHandler({
|
||||||
noteBody() {
|
note: this.$refs.noteForm.note,
|
||||||
return this.note.note;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
renderGFM() {
|
|
||||||
$(this.$refs['note-body']).renderGFM();
|
|
||||||
},
|
|
||||||
initTaskList() {
|
|
||||||
if (this.canEdit) {
|
|
||||||
this.taskList = new TaskList({
|
|
||||||
dataType: 'note',
|
|
||||||
fieldName: 'note',
|
|
||||||
selector: '.notes',
|
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
handleFormUpdate() {
|
mounted() {
|
||||||
this.formUpdateHandler({
|
this.renderGFM();
|
||||||
note: this.$refs.noteForm.note,
|
this.initTaskList();
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
updated() {
|
||||||
mounted() {
|
this.initTaskList();
|
||||||
this.renderGFM();
|
},
|
||||||
this.initTaskList();
|
};
|
||||||
},
|
|
||||||
updated() {
|
|
||||||
this.initTaskList();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
<script>
|
<script>
|
||||||
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
|
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
actionText: {
|
actionText: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
|
editedAt: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
editedBy: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'edited-text',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
editedAt: {
|
components: {
|
||||||
type: String,
|
timeAgoTooltip,
|
||||||
required: true,
|
|
||||||
},
|
},
|
||||||
editedBy: {
|
};
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
className: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: 'edited-text',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
timeAgoTooltip,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -38,6 +38,7 @@ export default {
|
||||||
</a>
|
</a>
|
||||||
<time-ago-tooltip
|
<time-ago-tooltip
|
||||||
:time="editedAt"
|
:time="editedAt"
|
||||||
tooltip-placement="bottom" />
|
tooltip-placement="bottom"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,88 +1,88 @@
|
||||||
<script>
|
<script>
|
||||||
import markdownField from '../../vue_shared/components/markdown/field.vue';
|
import markdownField from '../../vue_shared/components/markdown/field.vue';
|
||||||
import eventHub from '../event_hub';
|
import eventHub from '../event_hub';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
noteBody: {
|
noteBody: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: '',
|
default: '',
|
||||||
|
},
|
||||||
|
noteId: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
updateHandler: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
cancelHandler: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
saveButtonTitle: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'Save comment',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
noteId: {
|
data() {
|
||||||
type: Number,
|
return {
|
||||||
required: false,
|
initialNote: this.noteBody,
|
||||||
|
note: this.noteBody,
|
||||||
|
markdownPreviewUrl: gl.issueData.preview_note_path,
|
||||||
|
markdownDocsUrl: '',
|
||||||
|
conflictWhileEditing: false,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
updateHandler: {
|
components: {
|
||||||
type: Function,
|
markdownField,
|
||||||
required: true,
|
|
||||||
},
|
},
|
||||||
cancelHandler: {
|
computed: {
|
||||||
type: Function,
|
isDirty() {
|
||||||
required: true,
|
return this.initialNote !== this.note;
|
||||||
|
},
|
||||||
|
noteHash() {
|
||||||
|
return `#note_${this.noteId}`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
saveButtonTitle: {
|
methods: {
|
||||||
type: String,
|
handleUpdate() {
|
||||||
required: false,
|
this.updateHandler({
|
||||||
default: 'Save comment',
|
note: this.note,
|
||||||
},
|
});
|
||||||
},
|
},
|
||||||
data() {
|
editMyLastNote() {
|
||||||
return {
|
if (this.note === '') {
|
||||||
initialNote: this.noteBody,
|
const discussion = $(this.$el).closest('.discussion-notes');
|
||||||
note: this.noteBody,
|
const myLastNoteId = discussion.find('.js-my-note').last().attr('id');
|
||||||
markdownPreviewUrl: gl.issueData.preview_note_path,
|
|
||||||
markdownDocsUrl: '',
|
|
||||||
conflictWhileEditing: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
markdownField,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isDirty() {
|
|
||||||
return this.initialNote !== this.note;
|
|
||||||
},
|
|
||||||
noteHash() {
|
|
||||||
return `#note_${this.noteId}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleUpdate() {
|
|
||||||
this.updateHandler({
|
|
||||||
note: this.note,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
editMyLastNote() {
|
|
||||||
if (this.note === '') {
|
|
||||||
const discussion = $(this.$el).closest('.discussion-notes');
|
|
||||||
const myLastNoteId = discussion.find('.js-my-note').last().attr('id');
|
|
||||||
|
|
||||||
if (myLastNoteId) {
|
if (myLastNoteId) {
|
||||||
eventHub.$emit('enterEditMode', {
|
eventHub.$emit('enterEditMode', {
|
||||||
noteId: parseInt(myLastNoteId.replace('note_', ''), 10),
|
noteId: parseInt(myLastNoteId.replace('note_', ''), 10),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
mounted() {
|
||||||
mounted() {
|
const issuableDataEl = document.getElementById('js-issuable-app-initial-data');
|
||||||
const issuableDataEl = document.getElementById('js-issuable-app-initial-data');
|
const issueData = JSON.parse(issuableDataEl.innerHTML.replace(/"/g, '"'));
|
||||||
const issueData = JSON.parse(issuableDataEl.innerHTML.replace(/"/g, '"'));
|
|
||||||
|
|
||||||
this.markdownDocsUrl = issueData.markdownDocs;
|
this.markdownDocsUrl = issueData.markdownDocs;
|
||||||
this.$refs.textarea.focus();
|
this.$refs.textarea.focus();
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
noteBody() {
|
|
||||||
if (this.note === this.initialNote) {
|
|
||||||
this.note = this.noteBody;
|
|
||||||
} else {
|
|
||||||
this.conflictWhileEditing = true;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
watch: {
|
||||||
};
|
noteBody() {
|
||||||
|
if (this.note === this.initialNote) {
|
||||||
|
this.note = this.noteBody;
|
||||||
|
} else {
|
||||||
|
this.conflictWhileEditing = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,66 +1,71 @@
|
||||||
<script>
|
<script>
|
||||||
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
|
import { mapMutations } from 'vuex';
|
||||||
|
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
|
||||||
|
import * as types from '../stores/mutation_types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
author: {
|
author: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
actionText: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
actionTextHtml: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
noteId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
includeToggle: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
toggleHandler: {
|
||||||
|
type: Function,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
createdAt: {
|
data() {
|
||||||
type: String,
|
return {
|
||||||
required: true,
|
isExpanded: true,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
actionText: {
|
components: {
|
||||||
type: String,
|
timeAgoTooltip,
|
||||||
required: false,
|
|
||||||
default: '',
|
|
||||||
},
|
},
|
||||||
actionTextHtml: {
|
computed: {
|
||||||
type: String,
|
toggleChevronClass() {
|
||||||
required: false,
|
return this.isExpanded ? 'fa-chevron-up' : 'fa-chevron-down';
|
||||||
default: '',
|
},
|
||||||
|
noteTimestampLink() {
|
||||||
|
return `#note_${this.noteId}`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
noteId: {
|
methods: {
|
||||||
type: Number,
|
...mapMutations({
|
||||||
required: true,
|
setTargetNoteHash: types.SET_TARGET_NOTE_HASH,
|
||||||
|
}),
|
||||||
|
handleToggle() {
|
||||||
|
this.isExpanded = !this.isExpanded;
|
||||||
|
this.toggleHandler();
|
||||||
|
},
|
||||||
|
updateTargetNoteHash() {
|
||||||
|
this.setTargetNoteHash(this.noteTimestampLink);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
includeToggle: {
|
};
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
toggleHandler: {
|
|
||||||
type: Function,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isExpanded: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
timeAgoTooltip,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
toggleChevronClass() {
|
|
||||||
return this.isExpanded ? 'fa-chevron-up' : 'fa-chevron-down';
|
|
||||||
},
|
|
||||||
noteTimestampLink() {
|
|
||||||
return `#note_${this.noteId}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleToggle() {
|
|
||||||
this.isExpanded = !this.isExpanded;
|
|
||||||
this.toggleHandler();
|
|
||||||
},
|
|
||||||
updateTargetNoteHash() {
|
|
||||||
this.$store.commit('setTargetNoteHash', this.noteTimestampLink);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -81,13 +86,15 @@ export default {
|
||||||
<span
|
<span
|
||||||
v-if="actionTextHtml"
|
v-if="actionTextHtml"
|
||||||
v-html="actionTextHtml"
|
v-html="actionTextHtml"
|
||||||
class="system-note-message"></span>
|
class="system-note-message">
|
||||||
|
</span>
|
||||||
<a
|
<a
|
||||||
:href="noteTimestampLink"
|
:href="noteTimestampLink"
|
||||||
@click="updateTargetNoteHash">
|
@click="updateTargetNoteHash">
|
||||||
<time-ago-tooltip
|
<time-ago-tooltip
|
||||||
:time="createdAt"
|
:time="createdAt"
|
||||||
tooltipPlacement="bottom" />
|
tooltipPlacement="bottom"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -101,7 +108,8 @@ export default {
|
||||||
<i
|
<i
|
||||||
:class="toggleChevronClass"
|
:class="toggleChevronClass"
|
||||||
class="fa"
|
class="fa"
|
||||||
aria-hidden="true"></i>
|
aria-hidden="true">
|
||||||
|
</i>
|
||||||
Toggle discussion
|
Toggle discussion
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
signInLink: '#',
|
signInLink: '#',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
const wrapper = document.querySelector('.js-notes-wrapper');
|
const wrapper = document.querySelector('.js-notes-wrapper');
|
||||||
|
|
||||||
if (wrapper) {
|
if (wrapper) {
|
||||||
this.signInLink = wrapper.dataset.newSessionPath;
|
this.signInLink = wrapper.dataset.newSessionPath;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,119 +1,131 @@
|
||||||
<script>
|
<script>
|
||||||
/* global Flash */
|
/* global Flash */
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Vuex from 'vuex';
|
import { mapGetters, mapActions, mapMutations } from 'vuex';
|
||||||
import VueResource from 'vue-resource';
|
import store from '../stores/';
|
||||||
import storeOptions from '../stores/issue_notes_store';
|
import * as constants from '../constants'
|
||||||
import eventHub from '../event_hub';
|
import * as types from '../stores/mutation_types';
|
||||||
import issueNote from './issue_note.vue';
|
import eventHub from '../event_hub';
|
||||||
import issueDiscussion from './issue_discussion.vue';
|
import issueNote from './issue_note.vue';
|
||||||
import issueSystemNote from './issue_system_note.vue';
|
import issueDiscussion from './issue_discussion.vue';
|
||||||
import issueCommentForm from './issue_comment_form.vue';
|
import issueSystemNote from './issue_system_note.vue';
|
||||||
import placeholderNote from './issue_placeholder_note.vue';
|
import issueCommentForm from './issue_comment_form.vue';
|
||||||
import placeholderSystemNote from './issue_placeholder_system_note.vue';
|
import placeholderNote from './issue_placeholder_note.vue';
|
||||||
import store from './store';
|
import placeholderSystemNote from './issue_placeholder_system_note.vue';
|
||||||
|
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'IssueNotes',
|
name: 'IssueNotes',
|
||||||
store,
|
store,
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
issueNote,
|
issueNote,
|
||||||
issueDiscussion,
|
issueDiscussion,
|
||||||
issueSystemNote,
|
issueSystemNote,
|
||||||
issueCommentForm,
|
issueCommentForm,
|
||||||
placeholderNote,
|
loadingIcon,
|
||||||
placeholderSystemNote,
|
placeholderNote,
|
||||||
},
|
placeholderSystemNote,
|
||||||
computed: {
|
},
|
||||||
...Vuex.mapGetters([
|
computed: {
|
||||||
'notes',
|
...mapGetters([
|
||||||
'notesById',
|
'notes',
|
||||||
]),
|
'notesById',
|
||||||
},
|
]),
|
||||||
methods: {
|
},
|
||||||
componentName(note) {
|
methods: {
|
||||||
if (note.isPlaceholderNote) {
|
...mapActions({
|
||||||
if (note.placeholderType === 'systemNote') {
|
actionFetchNotes: 'fetchNotes',
|
||||||
return placeholderSystemNote;
|
}),
|
||||||
|
...mapActions([
|
||||||
|
'poll',
|
||||||
|
'toggleAward',
|
||||||
|
'scrollToNoteIfNeeded',
|
||||||
|
]),
|
||||||
|
...mapMutations({
|
||||||
|
setLastFetchedAt: types.SET_LAST_FETCHED_AT,
|
||||||
|
setTargetNoteHash: types.SET_TARGET_NOTE_HASH,
|
||||||
|
}),
|
||||||
|
getComponentName(note) {
|
||||||
|
if (note.isPlaceholderNote) {
|
||||||
|
if (note.placeholderType === constants.SYSTEM_NOTE) {
|
||||||
|
return placeholderSystemNote;
|
||||||
|
}
|
||||||
|
return placeholderNote;
|
||||||
|
} else if (note.individual_note) {
|
||||||
|
return note.notes[0].system ? issueSystemNote : issueNote;
|
||||||
}
|
}
|
||||||
return placeholderNote;
|
|
||||||
} else if (note.individual_note) {
|
|
||||||
return note.notes[0].system ? issueSystemNote : issueNote;
|
|
||||||
}
|
|
||||||
|
|
||||||
return issueDiscussion;
|
return issueDiscussion;
|
||||||
},
|
},
|
||||||
componentData(note) {
|
getComponentData(note) {
|
||||||
return note.individual_note ? note.notes[0] : note;
|
return note.individual_note ? note.notes[0] : note;
|
||||||
},
|
},
|
||||||
fetchNotes() {
|
fetchNotes() {
|
||||||
const { discussionsPath } = this.$el.parentNode.dataset;
|
const { discussionsPath } = this.$el.parentNode.dataset;
|
||||||
|
|
||||||
this.$store.dispatch('fetchNotes', discussionsPath)
|
this.actionFetchNotes(discussionsPath)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
|
||||||
// Scroll to note if we have hash fragment in the page URL
|
// Scroll to note if we have hash fragment in the page URL
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
this.checkLocationHash();
|
this.checkLocationHash();
|
||||||
});
|
});
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
Flash('Something went wrong while fetching issue comments. Please try again.');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
initPolling() {
|
|
||||||
const { lastFetchedAt } = $('.js-notes-wrapper')[0].dataset;
|
|
||||||
this.$store.commit('setLastFetchedAt', lastFetchedAt);
|
|
||||||
|
|
||||||
// FIXME: @fatihacet Implement real polling mechanism
|
|
||||||
setInterval(() => {
|
|
||||||
this.$store.dispatch('poll')
|
|
||||||
.then((res) => {
|
|
||||||
this.$store.commit('setLastFetchedAt', res.lastFetchedAt);
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
Flash('Something went wrong while fetching latest comments.');
|
Flash('Something went wrong while fetching issue comments. Please try again.');
|
||||||
});
|
});
|
||||||
}, 15000);
|
},
|
||||||
},
|
initPolling() {
|
||||||
bindEventHubListeners() {
|
const { lastFetchedAt } = $('.js-notes-wrapper')[0].dataset;
|
||||||
eventHub.$on('toggleAward', (data) => {
|
this.setLastFetchedAt(lastFetchedAt);
|
||||||
const { awardName, noteId } = data;
|
|
||||||
const endpoint = this.notesById[noteId].toggle_award_path;
|
|
||||||
|
|
||||||
this.$store.dispatch('toggleAward', { endpoint, awardName, noteId })
|
// FIXME: @fatihacet Implement real polling mechanism
|
||||||
.catch(() => {
|
setInterval(() => {
|
||||||
Flash('Something went wrong on our end.');
|
this.poll()
|
||||||
});
|
.then((res) => {
|
||||||
});
|
this.setLastFetchedAt(res.lastFetchedAt);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
Flash('Something went wrong while fetching latest comments.');
|
||||||
|
});
|
||||||
|
}, 15000);
|
||||||
|
},
|
||||||
|
bindEventHubListeners() {
|
||||||
|
eventHub.$on('toggleAward', (data) => {
|
||||||
|
const { awardName, noteId } = data;
|
||||||
|
const endpoint = this.notesById[noteId].toggle_award_path;
|
||||||
|
|
||||||
$(document).on('issuable:change', (e, isClosed) => {
|
this.toggleAward({ endpoint, awardName, noteId })
|
||||||
eventHub.$emit('issueStateChanged', isClosed);
|
.catch(() => {new Flash('Something went wrong on our end.')});
|
||||||
});
|
});
|
||||||
},
|
|
||||||
checkLocationHash() {
|
|
||||||
const hash = gl.utils.getLocationHash();
|
|
||||||
const $el = $(`#${hash}`);
|
|
||||||
|
|
||||||
if (hash && $el) {
|
$(document).on('issuable:change', (e, isClosed) => {
|
||||||
this.$store.commit('setTargetNoteHash', hash);
|
eventHub.$emit('issueStateChanged', isClosed);
|
||||||
this.$store.dispatch('scrollToNoteIfNeeded', $el);
|
});
|
||||||
}
|
},
|
||||||
|
checkLocationHash() {
|
||||||
|
const hash = gl.utils.getLocationHash();
|
||||||
|
const $el = $(`#${hash}`);
|
||||||
|
|
||||||
|
if (hash && $el) {
|
||||||
|
this.setTargetNoteHash(hash);
|
||||||
|
this.scrollToNoteIfNeeded($el);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
mounted() {
|
||||||
mounted() {
|
this.fetchNotes();
|
||||||
this.fetchNotes();
|
this.initPolling();
|
||||||
this.initPolling();
|
this.bindEventHubListeners();
|
||||||
this.bindEventHubListeners();
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -121,9 +133,7 @@ export default {
|
||||||
<div
|
<div
|
||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
class="loading">
|
class="loading">
|
||||||
<i
|
<loading-icon />
|
||||||
class="fa fa-spinner fa-spin"
|
|
||||||
aria-hidden="true"></i>
|
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
v-if="!isLoading"
|
v-if="!isLoading"
|
||||||
|
@ -131,9 +141,10 @@ export default {
|
||||||
class="notes main-notes-list timeline">
|
class="notes main-notes-list timeline">
|
||||||
<component
|
<component
|
||||||
v-for="note in notes"
|
v-for="note in notes"
|
||||||
:is="componentName(note)"
|
:is="getComponentName(note)"
|
||||||
:note="componentData(note)"
|
:note="getComponentData(note)"
|
||||||
:key="note.id" />
|
:key="note.id"
|
||||||
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
<issue-comment-form v-if="!isLoading" />
|
<issue-comment-form v-if="!isLoading" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
note: {
|
note: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
data() {
|
||||||
data() {
|
return {
|
||||||
return {
|
currentUser: window.gl.currentUserData,
|
||||||
currentUser: window.gl.currentUserData,
|
};
|
||||||
};
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -21,7 +21,8 @@ export default {
|
||||||
<a :href="currentUser.path">
|
<a :href="currentUser.path">
|
||||||
<img
|
<img
|
||||||
:src="currentUser.avatar_url"
|
:src="currentUser.avatar_url"
|
||||||
class="avatar s40" />
|
class="avatar s40"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
note: {
|
note: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import iconsMap from './issue_note_icons';
|
import iconsMap from './issue_note_icons';
|
||||||
import issueNoteHeader from './issue_note_header.vue';
|
import issueNoteHeader from './issue_note_header.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
note: {
|
note: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
data() {
|
||||||
data() {
|
return {
|
||||||
return {
|
svg: iconsMap[this.note.system_note_icon_name],
|
||||||
svg: iconsMap[this.note.system_note_icon_name],
|
};
|
||||||
};
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
issueNoteHeader,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapGetters([
|
|
||||||
'targetNoteHash',
|
|
||||||
]),
|
|
||||||
noteAnchorId() {
|
|
||||||
return `note_${this.note.id}`;
|
|
||||||
},
|
},
|
||||||
isTargetNote() {
|
components: {
|
||||||
return this.targetNoteHash === this.noteAnchorId;
|
issueNoteHeader,
|
||||||
},
|
},
|
||||||
},
|
computed: {
|
||||||
};
|
...mapGetters([
|
||||||
|
'targetNoteHash',
|
||||||
|
]),
|
||||||
|
noteAnchorId() {
|
||||||
|
return `note_${this.note.id}`;
|
||||||
|
},
|
||||||
|
isTargetNote() {
|
||||||
|
return this.targetNoteHash === this.noteAnchorId;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -5,4 +5,4 @@ export const SYSTEM_NOTE = 'systemNote';
|
||||||
export const COMMENT = 'comment';
|
export const COMMENT = 'comment';
|
||||||
export const OPENED = 'opened';
|
export const OPENED = 'opened';
|
||||||
export const REOPENED = 'reopened';
|
export const REOPENED = 'reopened';
|
||||||
export const CLOSED = 'closed';
|
export const CLOSED = 'closed';
|
||||||
|
|
|
@ -138,8 +138,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
|
||||||
export const poll = ({ commit, state, getters }) => {
|
export const poll = ({ commit, state, getters }) => {
|
||||||
const { notesPath } = $('.js-notes-wrapper')[0].dataset;
|
const { notesPath } = $('.js-notes-wrapper')[0].dataset;
|
||||||
|
|
||||||
return service
|
return service.poll(`${notesPath}?full_data=1`, state.lastFetchedAt)
|
||||||
.poll(`${notesPath}?full_data=1`, state.lastFetchedAt)
|
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.notes.length) {
|
if (res.notes.length) {
|
||||||
|
@ -188,8 +187,8 @@ export const toggleAward = ({ commit, getters, dispatch }, data) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (amIAwarded) {
|
if (amIAwarded) {
|
||||||
Object.assign(data, { awardName: counterAward });
|
data.awardName = counterAward;
|
||||||
Object.assign(data, { skipMutalityCheck: true });
|
data.skipMutalityCheck = true;
|
||||||
|
|
||||||
dispatch(types.TOGGLE_AWARD, data);
|
dispatch(types.TOGGLE_AWARD, data);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue