Skip to content

Feat Mentions #1790

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 4, 2020
4 changes: 4 additions & 0 deletions components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ import { default as message } from './message';

import { default as Menu } from './menu';

import { default as Mentions } from './mentions';

import { default as Modal } from './modal';

import { default as notification } from './notification';
Expand Down Expand Up @@ -171,6 +173,7 @@ const components = [
List,
LocaleProvider,
Menu,
Mentions,
Modal,
Pagination,
Popconfirm,
Expand Down Expand Up @@ -258,6 +261,7 @@ export {
List,
LocaleProvider,
Menu,
Mentions,
Modal,
Pagination,
Popconfirm,
Expand Down
46 changes: 46 additions & 0 deletions components/mentions/__test__/__snapshots__/demo.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders ./components/mentions/demo/async.md correctly 1`] = `<div class="ant-mentions"><textarea rows="1"></textarea></div>`;

exports[`renders ./components/mentions/demo/basic.md correctly 1`] = `<div class="ant-mentions"><textarea rows="1"></textarea></div>`;

exports[`renders ./components/mentions/demo/form.md correctly 1`] = `
<form class="ant-form ant-form-horizontal">
<div class="ant-row ant-form-item">
<div class="ant-col-5 ant-form-item-label"><label for="mentions_coders" title="Top coders" class="">Top coders</label></div>
<div class="ant-col-12 ant-form-item-control-wrapper">
<div class="ant-form-item-control"><span class="ant-form-item-children"><div class="ant-mentions"><textarea rows="1" data-__meta="[object Object]" data-__field="[object Object]" id="mentions_coders"></textarea></div></span>
<!---->
</div>
</div>
</div>
<div class="ant-row ant-form-item">
<div class="ant-col-5 ant-form-item-label"><label for="mentions_bio" title="Bio" class="ant-form-item-required">Bio</label></div>
<div class="ant-col-12 ant-form-item-control-wrapper">
<div class="ant-form-item-control"><span class="ant-form-item-children"><div class="ant-mentions"><textarea rows="3" placeholder="You can use @ to ref user here" data-__meta="[object Object]" data-__field="[object Object]" id="mentions_bio"></textarea></div></span>
<!---->
</div>
</div>
</div>
<div class="ant-row ant-form-item">
<div class="ant-col-12 ant-col-offset-5 ant-form-item-control-wrapper">
<div class="ant-form-item-control"><span class="ant-form-item-children"><button type="button" class="ant-btn ant-btn-primary"><span>Submit</span></button><button type="button" class="ant-btn" style="margin-left: 8px;"><span>Reset</span></button></span>
<!---->
</div>
</div>
</div>
</form>
`;

exports[`renders ./components/mentions/demo/placement.md correctly 1`] = `<div class="ant-mentions"><textarea rows="1"></textarea></div>`;

exports[`renders ./components/mentions/demo/prefix.md correctly 1`] = `<div class="ant-mentions"><textarea rows="1" placeholder="input @ to mention people, # to mention tag"></textarea></div>`;

exports[`renders ./components/mentions/demo/readonly.md correctly 1`] = `
<div>
<div style="margin-bottom: 10px;">
<div class="ant-mentions ant-mentions-disabled"><textarea disabled="disabled" rows="1" placeholder="this is disabled Mentions"></textarea></div>
</div>
<div class="ant-mentions"><textarea rows="1" placeholder="this is readOnly a-mentions" readonly=""></textarea></div>
</div>
`;
3 changes: 3 additions & 0 deletions components/mentions/__test__/demo.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import demoTest from '../../../tests/shared/demoTest';

demoTest('mentions');
91 changes: 91 additions & 0 deletions components/mentions/__test__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import Mentions from '..';
import { asyncExpect } from '@/tests/utils';
import focusTest from '../../../tests/shared/focusTest';

const { getMentions } = Mentions;

function $$(className) {
return document.body.querySelectorAll(className);
}

function triggerInput(wrapper, text = '') {
wrapper.find('textarea').element.value = text;
wrapper.find('textarea').element.selectionStart = text.length;
wrapper.find('textarea').trigger('keydown');
wrapper.find('textarea').trigger('change');
wrapper.find('textarea').trigger('keyup');
}

describe('Mentions', () => {
beforeAll(() => {
jest.useFakeTimers();
});

afterAll(() => {
jest.useRealTimers();
});

it('getMentions', () => {
const mentions = getMentions('@light #bamboo cat', { prefix: ['@', '#'] });
expect(mentions).toEqual([
{
prefix: '@',
value: 'light',
},
{
prefix: '#',
value: 'bamboo',
},
]);
});

it('focus', () => {
const onFocus = jest.fn();
const onBlur = jest.fn();

const wrapper = mount({
render() {
return <Mentions onFocus={onFocus} onBlur={onBlur} />;
},
});
wrapper.find('textarea').trigger('focus');
expect(wrapper.find('.ant-mentions').classes('ant-mentions-focused')).toBeTruthy();
expect(onFocus).toHaveBeenCalled();

wrapper.find('textarea').trigger('blur');
jest.runAllTimers();
expect(wrapper.classes()).not.toContain('ant-mentions-focused');
expect(onBlur).toHaveBeenCalled();
});

it('loading', done => {
const wrapper = mount(
{
render() {
return <Mentions loading />;
},
},
{ sync: false },
);
triggerInput(wrapper, '@');
Vue.nextTick(() => {
mount(
{
render() {
return wrapper.find({ name: 'Trigger' }).vm.getComponent();
},
},
{ sync: false },
);
Vue.nextTick(() => {
expect($$('.ant-mentions-dropdown-menu-item').length).toBeTruthy();
expect($$('.ant-spin')).toBeTruthy();
done();
});
});
});

focusTest(Mentions);
});
65 changes: 65 additions & 0 deletions components/mentions/demo/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<cn>
#### 异步加载
匹配内容列表为异步返回时。
</cn>

<us>
#### Asynchronous loading
async.
</us>

```tpl
<template>
<a-mentions @search="onSearch" :loading="loading">
<a-mentions-option
v-for="({ login, avatar_url: avatar }) in users"
:key="login"
:value="login"
>
<img :src="avatar" :alt="login" style="width: 20px; margin-right: 8px;">
<span>{{login}}</span>
</a-mentions-option>
</a-mentions>
</template>

<script>
import debounce from 'lodash/debounce';
export default {
data() {
return {
loading: false,
users: []
}
},
mounted() {
this.loadGithubUsers = debounce(this.loadGithubUsers, 800);
},
methods: {
onSearch(search) {
this.search = search;
this.loading = !!search;
console.log(!!search)
this.users = [];
console.log('Search:', search);
this.loadGithubUsers(search);
},
loadGithubUsers(key) {
if (!key) {
this.users = [];
return;
}
fetch(`https://api.github.com/search/users?q=${key}`)
.then(res => res.json())
.then(({ items = [] }) => {
const { search } = this;
if (search !== key) return;

this.users = items.slice(0, 10);
this.loading = false;
});
}
}
}
</script>
<style>
```
35 changes: 35 additions & 0 deletions components/mentions/demo/basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<cn>
#### 基础列表
基本使用。
</cn>

<us>
#### Basic usage
Basic usage.
</us>

```tpl
<template>
<a-mentions
defaultValue="@afc163"
@change="onChange"
@select="onSelect"
>
<a-mentions-option value="afc163">afc163</a-mentions-option>
<a-mentions-option value="zombieJ">zombieJ</a-mentions-option>
<a-mentions-option value="yesmeck">yesmeck</a-mentions-option>
</a-mentions>
</template>
<script>
export default {
methods: {
onSelect(option) {
console.log('select', option);
},
onChange(value) {
console.log('Change:', value);
}
}
}
</script>
```
89 changes: 89 additions & 0 deletions components/mentions/demo/form.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<cn>
#### 配合 Form 使用
受控模式,例如配合 Form 使用。
</cn>

<us>
#### With Form
Controlled mode, for example, to work with `Form`.
</us>

```tpl
<template>
<a-form :form="form" layout="horizontal">
<a-form-item label="Top coders" :label-col="{ span: 5 }" :wrapper-col="{ span: 12 }">
<a-mentions
rows="1"
v-decorator="[
'coders',
{
rules: [{ validator: checkMention }],
},
]"
>
<a-mentions-option value="afc163">afc163</a-mentions-option>
<a-mentions-option value="zombieJ">zombieJ</a-mentions-option>
<a-mentions-option value="yesmeck">yesmeck</a-mentions-option>
</a-mentions>
</a-form-item>
<a-form-item label="Bio" :label-col="{ span: 5 }" :wrapper-col="{ span: 12 }">
<a-mentions
rows="3"
placeholder="You can use @ to ref user here"
v-decorator="[
'bio',
{
rules: [{ required: true }],
},
]"
>
<a-mentions-option value="afc163">afc163</a-mentions-option>
<a-mentions-option value="zombieJ">zombieJ</a-mentions-option>
<a-mentions-option value="yesmeck">yesmeck</a-mentions-option>
</a-mentions>
</a-form-item>
<a-form-item :wrapper-col="{ span: 12, offset: 5 }">
<a-button type="primary" @click="handleSubmit">
Submit
</a-button>
<a-button style="margin-left: 8px;" @click="handleReset">Reset</a-button>
</a-form-item>
</a-form>
</template>
<script>
import { Mentions } from 'ant-design-vue';
const { getMentions } = Mentions;
export default {
data() {
return {
form: this.$form.createForm(this, { name: 'mentions' })
}
},
methods: {
handleReset(e) {
e.preventDefault();
this.form.resetFields();
},
handleSubmit(e) {
e.preventDefault();
this.form.validateFields((errors, values) => {
if (errors) {
console.log('Errors in the form!!!');
return;
}
console.log('Submit!!!');
console.log(values);
});
},
checkMention(rule, value, callback) {
const mentions = getMentions(value);
if (mentions.length < 2) {
callback(new Error('More than one must be selected!'));
} else {
callback();
}
}
}
}
</script>
```
Loading