Commit 1dba0133 authored by Felipe Artur's avatar Felipe Artur Committed by Tim Zallmann
Browse files

Changes tab VUE refactoring

parent 5fcaac8780a2
......@@ -31,7 +31,9 @@ export default class Autosave {
// https://github.com/vuejs/vue/issues/2804#issuecomment-216968137
const event = new Event('change', { bubbles: true, cancelable: false });
const field = this.field.get(0);
field.dispatchEvent(event);
if (field) {
field.dispatchEvent(event);
}
}
save() {
......
......@@ -11,7 +11,8 @@ import axios from './lib/utils/axios_utils';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
const requestAnimationFrame = window.requestAnimationFrame ||
const requestAnimationFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.setTimeout;
......@@ -37,21 +38,28 @@ class AwardsHandler {
this.emoji = emoji;
this.eventListeners = [];
// If the user shows intent let's pre-build the menu
this.registerEventListener('one', $(document), 'mouseenter focus', '.js-add-award', 'mouseenter focus', () => {
const $menu = $('.emoji-menu');
if ($menu.length === 0) {
requestAnimationFrame(() => {
this.createEmojiMenu();
});
}
});
this.registerEventListener('on', $(document), 'click', '.js-add-award', (e) => {
this.registerEventListener(
'one',
$(document),
'mouseenter focus',
'.js-add-award',
'mouseenter focus',
() => {
const $menu = $('.emoji-menu');
if ($menu.length === 0) {
requestAnimationFrame(() => {
this.createEmojiMenu();
});
}
},
);
this.registerEventListener('on', $(document), 'click', '.js-add-award', e => {
e.stopPropagation();
e.preventDefault();
this.showEmojiMenu($(e.currentTarget));
});
this.registerEventListener('on', $('html'), 'click', (e) => {
this.registerEventListener('on', $('html'), 'click', e => {
const $target = $(e.target);
if (!$target.closest('.emoji-menu').length) {
$('.js-awards-block.current').removeClass('current');
......@@ -61,12 +69,14 @@ class AwardsHandler {
}
}
});
this.registerEventListener('on', $(document), 'click', '.js-emoji-btn', (e) => {
this.registerEventListener('on', $(document), 'click', '.js-emoji-btn', e => {
e.preventDefault();
const $target = $(e.currentTarget);
const $glEmojiElement = $target.find('gl-emoji');
const $spriteIconElement = $target.find('.icon');
const emojiName = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
const emojiName = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data(
'name',
);
$target.closest('.js-awards-block').addClass('current');
this.addAward(this.getVotesBlock(), this.getAwardUrl(), emojiName);
......@@ -83,7 +93,10 @@ class AwardsHandler {
showEmojiMenu($addBtn) {
if ($addBtn.hasClass('js-note-emoji')) {
$addBtn.closest('.note').find('.js-awards-block').addClass('current');
$addBtn
.closest('.note')
.find('.js-awards-block')
.addClass('current');
} else {
$addBtn.closest('.js-awards-block').addClass('current');
}
......@@ -177,32 +190,38 @@ class AwardsHandler {
const remainingCategories = Object.keys(categoryMap).slice(1);
const allCategoriesAddedPromise = remainingCategories.reduce(
(promiseChain, categoryNameKey) =>
promiseChain.then(() =>
new Promise((resolve) => {
const emojisInCategory = categoryMap[categoryNameKey];
const categoryMarkup = this.renderCategory(
categoryLabelMap[categoryNameKey],
emojisInCategory,
);
requestAnimationFrame(() => {
emojiContentElement.insertAdjacentHTML('beforeend', categoryMarkup);
resolve();
});
}),
),
promiseChain.then(
() =>
new Promise(resolve => {
const emojisInCategory = categoryMap[categoryNameKey];
const categoryMarkup = this.renderCategory(
categoryLabelMap[categoryNameKey],
emojisInCategory,
);
requestAnimationFrame(() => {
emojiContentElement.insertAdjacentHTML('beforeend', categoryMarkup);
resolve();
});
}),
),
Promise.resolve(),
);
allCategoriesAddedPromise.then(() => {
// Used for tests
// We check for the menu in case it was destroyed in the meantime
if (menu) {
menu.dispatchEvent(new CustomEvent('build-emoji-menu-finish'));
}
}).catch((err) => {
emojiContentElement.insertAdjacentHTML('beforeend', '<p>We encountered an error while adding the remaining categories</p>');
throw new Error(`Error occurred in addRemainingEmojiMenuCategories: ${err.message}`);
});
allCategoriesAddedPromise
.then(() => {
// Used for tests
// We check for the menu in case it was destroyed in the meantime
if (menu) {
menu.dispatchEvent(new CustomEvent('build-emoji-menu-finish'));
}
})
.catch(err => {
emojiContentElement.insertAdjacentHTML(
'beforeend',
'<p>We encountered an error while adding the remaining categories</p>',
);
throw new Error(`Error occurred in addRemainingEmojiMenuCategories: ${err.message}`);
});
}
renderCategory(name, emojiList, opts = {}) {
......@@ -211,7 +230,9 @@ class AwardsHandler {
${name}
</h5>
<ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
${emojiList.map(emojiName => `
${emojiList
.map(
emojiName => `
<li class="emoji-menu-list-item">
<button class="emoji-menu-btn text-center js-emoji-btn" type="button">
${this.emoji.glEmojiTag(emojiName, {
......@@ -219,7 +240,9 @@ class AwardsHandler {
})}
</button>
</li>
`).join('\n')}
`,
)
.join('\n')}
</ul>
`;
}
......@@ -232,7 +255,7 @@ class AwardsHandler {
top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`,
};
if (position === 'right') {
css.left = `${($addBtn.offset().left - $menu.outerWidth()) + 20}px`;
css.left = `${$addBtn.offset().left - $menu.outerWidth() + 20}px`;
$menu.addClass('is-aligned-right');
} else {
css.left = `${$addBtn.offset().left}px`;
......@@ -416,7 +439,10 @@ class AwardsHandler {
</button>
`;
const $emojiButton = $(buttonHtml);
$emojiButton.insertBefore(votesBlock.find('.js-award-holder')).find('.emoji-icon').data('name', emojiName);
$emojiButton
.insertBefore(votesBlock.find('.js-award-holder'))
.find('.emoji-icon')
.data('name', emojiName);
this.animateEmoji($emojiButton);
$('.award-control').tooltip();
votesBlock.removeClass('current');
......@@ -426,7 +452,7 @@ class AwardsHandler {
const className = 'pulse animated once short';
$emoji.addClass(className);
this.registerEventListener('on', $emoji, animationEndEventString, (e) => {
this.registerEventListener('on', $emoji, animationEndEventString, e => {
$(e.currentTarget).removeClass(className);
});
}
......@@ -444,15 +470,16 @@ class AwardsHandler {
if (this.isUserAuthored($emojiButton)) {
this.userAuthored($emojiButton);
} else {
axios.post(awardUrl, {
name: emoji,
})
.then(({ data }) => {
if (data.ok) {
callback();
}
})
.catch(() => flash(__('Something went wrong on our end.')));
axios
.post(awardUrl, {
name: emoji,
})
.then(({ data }) => {
if (data.ok) {
callback();
}
})
.catch(() => flash(__('Something went wrong on our end.')));
}
}
......@@ -486,26 +513,33 @@ class AwardsHandler {
}
getFrequentlyUsedEmojis() {
return this.frequentlyUsedEmojis || (() => {
const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(','));
this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter(
inputName => this.emoji.isEmojiNameValid(inputName),
);
return this.frequentlyUsedEmojis;
})();
return (
this.frequentlyUsedEmojis ||
(() => {
const frequentlyUsedEmojis = _.uniq(
(Cookies.get('frequently_used_emojis') || '').split(','),
);
this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter(inputName =>
this.emoji.isEmojiNameValid(inputName),
);
return this.frequentlyUsedEmojis;
})()
);
}
setupSearch() {
const $search = $('.js-emoji-menu-search');
this.registerEventListener('on', $search, 'input', (e) => {
const term = $(e.target).val().trim();
this.registerEventListener('on', $search, 'input', e => {
const term = $(e.target)
.val()
.trim();
this.searchEmojis(term);
});
const $menu = $('.emoji-menu');
this.registerEventListener('on', $menu, transitionEndEventString, (e) => {
this.registerEventListener('on', $menu, transitionEndEventString, e => {
if (e.target === e.currentTarget) {
// Clear the search
this.searchEmojis('');
......@@ -523,19 +557,26 @@ class AwardsHandler {
// Generate a search result block
const h5 = $('<h5 class="emoji-search-title"/>').text('Search results');
const foundEmojis = this.findMatchingEmojiElements(term).show();
const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis);
const ul = $('<ul>')
.addClass('emoji-menu-list emoji-menu-search')
.append(foundEmojis);
$('.emoji-menu-content ul, .emoji-menu-content h5').hide();
$('.emoji-menu-content').append(h5).append(ul);
$('.emoji-menu-content')
.append(h5)
.append(ul);
} else {
$('.emoji-menu-content').children().show();
$('.emoji-menu-content')
.children()
.show();
}
}
findMatchingEmojiElements(query) {
const emojiMatches = this.emoji.filterEmojiNamesByAlias(query);
const $emojiElements = $('.emoji-menu-list:not(.frequent-emojis) [data-name]');
const $matchingElements = $emojiElements
.filter((i, elm) => emojiMatches.indexOf(elm.dataset.name) >= 0);
const $matchingElements = $emojiElements.filter(
(i, elm) => emojiMatches.indexOf(elm.dataset.name) >= 0,
);
return $matchingElements.closest('li').clone();
}
......@@ -550,16 +591,13 @@ class AwardsHandler {
$emojiMenu.addClass(IS_RENDERED);
// enqueues animation as a microtask, so it begins ASAP once IS_RENDERED added
return Promise.resolve()
.then(() => $emojiMenu.addClass(IS_VISIBLE));
return Promise.resolve().then(() => $emojiMenu.addClass(IS_VISIBLE));
}
hideMenuElement($emojiMenu) {
$emojiMenu.on(transitionEndEventString, (e) => {
$emojiMenu.on(transitionEndEventString, e => {
if (e.currentTarget === e.target) {
$emojiMenu
.removeClass(IS_RENDERED)
.off(transitionEndEventString);
$emojiMenu.removeClass(IS_RENDERED).off(transitionEndEventString);
}
});
......@@ -567,7 +605,7 @@ class AwardsHandler {
}
destroy() {
this.eventListeners.forEach((entry) => {
this.eventListeners.forEach(entry => {
entry.element.off.call(entry.element, ...entry.args);
});
$('.emoji-menu').remove();
......@@ -577,8 +615,9 @@ class AwardsHandler {
let awardsHandlerPromise = null;
export default function loadAwardsHandler(reload = false) {
if (!awardsHandlerPromise || reload) {
awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji')
.then(Emoji => new AwardsHandler(Emoji));
awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then(
Emoji => new AwardsHandler(Emoji),
);
}
return awardsHandlerPromise;
}
/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, max-len */
/* global CommentsStore */
/* global ResolveService */
......@@ -41,54 +40,54 @@ const ResolveBtn = Vue.extend({
required: true,
},
},
data: function () {
data() {
return {
discussions: CommentsStore.state,
loading: false
loading: false,
};
},
computed: {
discussion: function () {
discussion() {
return this.discussions[this.discussionId];
},
note: function () {
note() {
return this.discussion ? this.discussion.getNote(this.noteId) : {};
},
buttonText: function () {
buttonText() {
if (this.isResolved) {
return `Resolved by ${this.resolvedByName}`;
} else if (this.canResolve) {
return 'Mark as resolved';
} else {
return 'Unable to resolve';
}
return 'Unable to resolve';
},
isResolved: function () {
isResolved() {
if (this.note) {
return this.note.resolved;
} else {
return false;
}
return false;
},
resolvedByName: function () {
resolvedByName() {
return this.note.resolved_by;
},
},
watch: {
'discussions': {
discussions: {
handler: 'updateTooltip',
deep: true
}
deep: true,
},
},
mounted: function () {
mounted() {
$(this.$refs.button).tooltip({
container: 'body'
container: 'body',
});
},
beforeDestroy: function () {
beforeDestroy() {
CommentsStore.delete(this.discussionId, this.noteId);
},
created: function () {
created() {
CommentsStore.create({
discussionId: this.discussionId,
noteId: this.noteId,
......@@ -101,43 +100,41 @@ const ResolveBtn = Vue.extend({
});
},
methods: {
updateTooltip: function () {
updateTooltip() {
this.$nextTick(() => {
$(this.$refs.button)
.tooltip('hide')
.tooltip('_fixTitle');
});
},
resolve: function () {
resolve() {
if (!this.canResolve) return;
let promise;
this.loading = true;
if (this.isResolved) {
promise = ResolveService
.unresolve(this.noteId);
promise = ResolveService.unresolve(this.noteId);
} else {
promise = ResolveService
.resolve(this.noteId);
promise = ResolveService.resolve(this.noteId);
}
promise
.then(resp => resp.json())
.then((data) => {
.then(data => {
this.loading = false;
const resolved_by = data ? data.resolved_by : null;
const resolvedBy = data ? data.resolved_by : null;
CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by);
CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolvedBy);
this.discussion.updateHeadline(data);
gl.mrWidget.checkStatus();
document.dispatchEvent(new CustomEvent('refreshVueNotes'));
this.updateTooltip();
})
.catch(() => new Flash('An error occurred when trying to resolve a comment. Please try again.'));
}
.catch(
() => new Flash('An error occurred when trying to resolve a comment. Please try again.'),
);
},
},
});
......
/* eslint-disable func-names, comma-dangle, new-cap, no-new */
/* global ResolveCount */
/* eslint-disable func-names, new-cap */
import $ from 'jquery';
import Vue from 'vue';
......@@ -15,12 +14,13 @@ import './components/resolve_count';
import './components/resolve_discussion_btn';
import './components/diff_note_avatars';
import './components/new_issue_for_discussion';
import { hasVueMRDiscussionsCookie } from '../lib/utils/common_utils';
export default () => {
const projectPathHolder = document.querySelector('.merge-request') || document.querySelector('.commit-box');
const projectPathHolder =
document.querySelector('.merge-request') || document.querySelector('.commit-box');
const projectPath = projectPathHolder.dataset.projectPath;
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn';
const COMPONENT_SELECTOR =
'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn';
window.gl = window.gl || {};
window.gl.diffNoteApps = {};
......@@ -28,9 +28,9 @@ export default () => {
window.ResolveService = new gl.DiffNotesResolveServiceClass(projectPath);
gl.diffNotesCompileComponents = () => {
$('diff-note-avatars').each(function () {
$('diff-note-avatars').each(function() {
const tmp = Vue.extend({
template: $(this).get(0).outerHTML
template: $(this).get(0).outerHTML,
});
const tmpApp = new tmp().$mount();
......@@ -41,12 +41,12 @@ export default () => {
});
});
const $components = $(COMPONENT_SELECTOR).filter(function () {
const $components = $(COMPONENT_SELECTOR).filter(function() {
return $(this).closest('resolve-count').length !== 1;
});
if ($components) {
$components.each(function () {
$components.each(function() {
const $this = $(this);
const noteId = $this.attr(':note-id');
const discussionId = $this.attr(':discussion-id');
......@@ -54,7 +54,7 @@ export default () => {
if ($this.is('comment-and-resolve-btn') && !discussionId) return;
const tmp = Vue.extend({
template: $this.get(0).outerHTML
template: $this.get(0).outerHTML,
});
const tmpApp = new tmp().$mount();
......@@ -69,15 +69,5 @@ export default () => {
gl.diffNotesCompileComponents();
const resolveCountAppEl = document.querySelector('#resolve-count-app');
if (!hasVueMRDiscussionsCookie() && resolveCountAppEl) {
new Vue({
el: resolveCountAppEl,
components: {
'resolve-count': ResolveCount
},
});
}
$(window).trigger('resize.nav');
};
......@@ -8,8 +8,12 @@ window.gl = window.gl || {};
class ResolveServiceClass {
constructor(root) {
this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve?html=true`);
this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve?html=true`);
this.noteResource = Vue.resource(
`${root}/notes{/noteId}/resolve?html=true`,
);
this.discussionResource = Vue.resource(
`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve?html=true`,
);
}
resolve(noteId) {
......@@ -33,7 +37,7 @@ class ResolveServiceClass {
promise
.then(resp => resp.json())
.then((data) => {
.then(data => {
discussion.loading = false;
const resolvedBy = data ? data.resolved_by : null;
......@@ -45,9 +49,13 @@ class ResolveServiceClass {
if (gl.mrWidget) gl.mrWidget.checkStatus();
discussion.updateHeadline(data);
document.dispatchEvent(new CustomEvent('refreshVueNotes'));
})
.catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.'));
.catch(
() =>
new Flash(
'An error occurred when trying to resolve a discussion. Please try again.',
),
);
}
resolveAll(mergeRequestId, discussionId) {
......@@ -55,10 +63,13 @@ class ResolveServiceClass {
discussion.loading = true;
return this.discussionResource.save({