Commit 75292299 authored by Coung Ngo's avatar Coung Ngo

Add new mentions component

Added new GlMentions component to eventually replace at.js
mention functionality since at.js is no longer maintained.
parent 2dc80baf
<script>
import escape from 'lodash/escape';
import Tribute from 'tributejs';
import axios from '~/lib/utils/axios_utils';
import { spriteIcon } from '~/lib/utils/common_utils';
/**
* Creates the HTML template for each row of the mentions dropdown.
*
* @param original An object from the array returned from the `autocomplete_sources/members` API
* @returns {string} An HTML template
*/
function createMenuItemTemplate({ original }) {
const rectAvatarClass = original.type === 'Group' ? 'rect-avatar' : '';
const avatarTag = original.avatar_url
? `<img
src="${original.avatar_url}"
alt="${original.username} avatar"
class="avatar ${rectAvatarClass} avatar-inline center s26"/>`
: `<div class="avatar ${rectAvatarClass} avatar-inline center s26">
${original.username.charAt(0).toUpperCase()}</div>`;
const name = escape(this.sanitize(original.name));
const count = original.count && !original.mentionsDisabled ? ` (${original.count})` : '';
const icon = original.mentionsDisabled
? spriteIcon('notifications-off', 's16 vertical-align-middle prepend-left-5')
: '';
return `${avatarTag}
${original.username}
<small class="small font-weight-normal gl-color-inherit">${name}${count}</small>
${icon}`;
}
/**
* Creates the list of users to show in the mentions dropdown.
*
* @param inputText The text entered by the user in the mentions input field
* @param processValues Callback function to set the list of users to show in the mentions dropdown
*/
function getMembers(inputText, processValues) {
if (this.members) {
processValues(this.members);
} else if (this.dataSources.members) {
axios
.get(this.dataSources.members)
.then(response => {
this.members = response.data;
processValues(response.data);
})
.catch(() => {});
} else {
processValues([]);
}
}
export default {
name: 'GlMentions',
props: {
dataSources: {
type: Object,
required: false,
default: () => gl.GfmAutoComplete?.dataSources || {},
},
},
data() {
return {
members: undefined,
options: {
trigger: '@',
fillAttr: 'username',
lookup(value) {
return value.name + value.username;
},
menuItemTemplate: createMenuItemTemplate.bind(this),
values: getMembers.bind(this),
},
};
},
mounted() {
const input = this.$slots.default[0].elm;
this.tribute = new Tribute(this.options);
this.tribute.attach(input);
},
beforeDestroy() {
const input = this.$slots.default[0].elm;
if (this.tribute) {
this.tribute.detach(input);
}
},
methods: {
sanitize(str) {
return str.replace(/<(?:.|\n)*?>/gm, '');
},
},
render(h) {
return h('div', this.$slots.default);
},
};
</script>
.tribute-container {
background: $white-light;
border: 1px solid $gl-gray-100;
border-radius: $border-radius-base;
box-shadow: 0 0 5px $issue-boards-card-shadow;
color: $black;
margin-top: 12px;
max-height: 200px;
min-width: 120px;
overflow-y: auto;
z-index: 11110 !important;
ul {
list-style: none;
margin-bottom: 0;
padding: 8px 1px;
}
li {
cursor: pointer;
padding: 8px 16px;
white-space: nowrap;
div.avatar {
align-items: center;
display: inline-flex;
justify-content: center;
}
small {
color: $gl-gray-500;
}
&.highlight {
background-color: $gray-darker;
color: $gl-text-color;
.avatar {
@include disable-all-animation;
border: 1px solid $white-light;
}
small {
color: inherit;
}
}
}
}
......@@ -161,7 +161,9 @@ module.exports = {
},
{
test: /\.js$/,
exclude: path => /node_modules|vendor[\\/]assets/.test(path) && !/\.vue\.js/.test(path),
exclude: path =>
/node_modules\/(?!tributejs)|node_modules|vendor[\\/]assets/.test(path) &&
!/\.vue\.js/.test(path),
loader: 'babel-loader',
options: {
cacheDirectory: path.join(CACHE_PATH, 'babel-loader'),
......
import { shallowMount } from '@vue/test-utils';
import Tribute from 'tributejs';
import GlMentions from '~/vue_shared/components/gl_mentions.vue';
describe('GlMentions', () => {
let wrapper;
describe('Tribute', () => {
const mentions = '/gitlab-org/gitlab-test/-/autocomplete_sources/members?type=Issue&type_id=1';
beforeEach(() => {
wrapper = shallowMount(GlMentions, {
propsData: {
dataSources: {
mentions,
},
},
slots: {
default: ['<input/>'],
},
});
});
it('is set to tribute instance variable', () => {
expect(wrapper.vm.tribute instanceof Tribute).toBe(true);
});
it('contains the slot input element', () => {
wrapper.find('input').setValue('@');
expect(wrapper.vm.tribute.current.element).toBe(wrapper.find('input').element);
});
});
});
......@@ -11273,6 +11273,11 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
tributejs@4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-4.1.3.tgz#2e1be7d9a1e403ed4c394f91d859812267e4691c"
integrity sha512-+VUqyi8p7tCdaqCINCWHf95E2hJFMIML180BhplTpXNooz3E2r96AONXI9qO2Ru6Ugp7MsMPJjB+rnBq+hAmzA==
trim-newlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment