Commit 1afbba31 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '207134-frontend-replace-at-mentions' into 'master'

Add new basic mentions component

See merge request gitlab-org/gitlab!26160
parents 331fc541 2d843f22
<script>
import escape from 'lodash/escape';
import sanitize from 'sanitize-html';
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 avatarClasses = `avatar avatar-inline center s26 ${rectAvatarClass}
align-items-center d-inline-flex justify-content-center`;
const avatarTag = original.avatar_url
? `<img
src="${original.avatar_url}"
alt="${original.username}'s avatar"
class="${avatarClasses}"/>`
: `<div class="${avatarClasses}">${original.username.charAt(0).toUpperCase()}</div>`;
const name = escape(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);
}
},
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: $gl-padding-12;
max-height: 200px;
min-width: 120px;
overflow-y: auto;
z-index: 11110 !important;
ul {
list-style: none;
margin-bottom: 0;
padding: $gl-padding-8 1px;
}
li {
cursor: pointer;
padding: $gl-padding-8 $gl-padding;
white-space: nowrap;
small {
color: $gl-gray-500;
}
&.highlight {
background-color: $gray-darker;
.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