├── packages
├── test-e2e
│ ├── .eslintignore
│ ├── .browserslistrc
│ ├── cypress.json
│ ├── src
│ │ ├── views
│ │ │ ├── Home.vue
│ │ │ ├── Limit.vue
│ │ │ ├── OmitKey.vue
│ │ │ ├── InsertSpace.vue
│ │ │ ├── MapInsert.vue
│ │ │ ├── ItemSlot.vue
│ │ │ ├── Defaults.vue
│ │ │ ├── WrappedInput.vue
│ │ │ ├── ContentEditable.vue
│ │ │ ├── Events.vue
│ │ │ └── MultipleCollections.vue
│ │ ├── App.vue
│ │ ├── main.js
│ │ └── router.js
│ ├── babel.config.js
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── .editorconfig
│ ├── tests
│ │ └── e2e
│ │ │ ├── .eslintrc.js
│ │ │ ├── specs
│ │ │ ├── item-slot.js
│ │ │ ├── insert-options.js
│ │ │ ├── wrapped-input.js
│ │ │ ├── limit.js
│ │ │ ├── multiple-collections.js
│ │ │ ├── events.js
│ │ │ ├── content-editable.js
│ │ │ └── defaults.js
│ │ │ ├── support
│ │ │ ├── index.js
│ │ │ └── commands.js
│ │ │ └── plugins
│ │ │ └── index.js
│ ├── .gitignore
│ ├── README.md
│ ├── .eslintrc.js
│ └── package.json
├── vue-mention
│ ├── .npmignore
│ ├── src
│ │ ├── global-shim.d.ts
│ │ ├── vue-shim.d.ts
│ │ ├── index.ts
│ │ └── Mentionable.vue
│ ├── babel.config.js
│ ├── tsconfig.json
│ ├── vite.config.ts
│ ├── .eslintrc.js
│ ├── README.md
│ └── package.json
└── docs
│ ├── package.json
│ └── src
│ ├── .vuepress
│ ├── config.js
│ ├── components
│ │ ├── GettingStartedDemo.vue
│ │ ├── AsyncItemsDemo.vue
│ │ └── Demo.vue
│ ├── styles
│ │ └── index.styl
│ └── public
│ │ └── vue-mention.svg
│ ├── guide
│ ├── README.md
│ └── async-items.md
│ └── README.md
├── .github
├── FUNDING.yml
└── workflows
│ ├── changelog.yml
│ └── ci.yml
├── lerna.json
├── package.json
├── LICENSE
├── .gitignore
└── README.md
/packages/test-e2e/.eslintignore:
--------------------------------------------------------------------------------
1 | vue-mention
2 |
--------------------------------------------------------------------------------
/packages/vue-mention/.npmignore:
--------------------------------------------------------------------------------
1 | docs/
2 | docs-src/
3 | .babelrc
4 |
--------------------------------------------------------------------------------
/packages/test-e2e/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/packages/vue-mention/src/global-shim.d.ts:
--------------------------------------------------------------------------------
1 | declare const VERSION: string
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: Akryum
4 |
--------------------------------------------------------------------------------
/packages/test-e2e/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "pluginsFile": "tests/e2e/plugins/index.js"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home
4 |
5 |
6 |
--------------------------------------------------------------------------------
/packages/test-e2e/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset',
4 | ],
5 | }
6 |
--------------------------------------------------------------------------------
/packages/test-e2e/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akryum/vue-mention/HEAD/packages/test-e2e/public/favicon.ico
--------------------------------------------------------------------------------
/packages/vue-mention/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [require('@babel/preset-env'), { modules: false }],
4 | ],
5 | }
6 |
--------------------------------------------------------------------------------
/packages/vue-mention/src/vue-shim.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | declare module '*.vue' {
3 | const component: any
4 | export default component
5 | }
6 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "npmClient": "yarn",
3 | "useWorkspaces": true,
4 | "version": "2.0.0-alpha.3",
5 | "packages": [
6 | "packages/*"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/test-e2e/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/packages/test-e2e/tests/e2e/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | 'cypress',
4 | ],
5 | env: {
6 | mocha: true,
7 | 'cypress/globals': true,
8 | },
9 | rules: {
10 | strict: 'off',
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import VueMention from 'vue-mention'
3 | import App from './App.vue'
4 | import router from './router'
5 | import 'floating-vue/dist/style.css'
6 |
7 | const app = createApp(App)
8 |
9 | app.use(VueMention)
10 | app.use(router)
11 |
12 | app.mount('#app')
13 |
--------------------------------------------------------------------------------
/packages/test-e2e/tests/e2e/specs/item-slot.js:
--------------------------------------------------------------------------------
1 | describe('default item slot', () => {
2 | it('display custom item content', () => {
3 | cy.visit('/item-slot')
4 | cy.get('.input').type('@')
5 | cy.get('.mention-item')
6 | .should('contain', 'Guillaume')
7 | .should('contain', 'Eduardo')
8 | .should('contain', 'Sébastien')
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/packages/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-mention-docs",
3 | "version": "0.1.7",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vuepress dev src",
7 | "build": "vuepress build src"
8 | },
9 | "dependencies": {
10 | "v-tooltip": "^2.0.3",
11 | "vue-mention": "^1.0.0"
12 | },
13 | "devDependencies": {
14 | "vuepress": "^1.4.1"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/test-e2e/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | /tests/e2e/videos/
6 | /tests/e2e/screenshots/
7 |
8 | # local env files
9 | .env.local
10 | .env.*.local
11 |
12 | # Log files
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 |
17 | # Editor directories and files
18 | .idea
19 | .vscode
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-mention-monorepo",
3 | "version": "2.0.0-alpha.3",
4 | "private": true,
5 | "workspaces": [
6 | "packages/*"
7 | ],
8 | "scripts": {
9 | "build": "lerna run build",
10 | "lint": "lerna run lint",
11 | "release": "yarn run lint && lerna publish --dist-tag next"
12 | },
13 | "devDependencies": {
14 | "lerna": "^3.20.2"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/test-e2e/README.md:
--------------------------------------------------------------------------------
1 | # test-e2e
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn build
16 | ```
17 |
18 | ### Run your end-to-end tests
19 | ```
20 | yarn test:e2e
21 | ```
22 |
23 | ### Lints and fixes files
24 | ```
25 | yarn lint
26 | ```
27 |
28 | ### Customize configuration
29 | See [Configuration Reference](https://cli.vuejs.org/config/).
30 |
--------------------------------------------------------------------------------
/packages/vue-mention/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | "module": "ESNext",
5 | "strict": true,
6 | "declaration": true,
7 | "lib": [
8 | "ESNext",
9 | "DOM",
10 | "DOM.Iterable"
11 | ],
12 | "jsx": "preserve",
13 | "outDir": "dist",
14 | "moduleResolution": "node"
15 | },
16 | "include": [
17 | "src/**/*.ts",
18 | "src/**/*.vue"
19 | ],
20 | "exclude": [
21 | "node_modules",
22 | "dist",
23 | "**/*.js"
24 | ]
25 | }
--------------------------------------------------------------------------------
/packages/test-e2e/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | extends: [
7 | 'plugin:vue/essential',
8 | '@vue/standard',
9 | ],
10 | parserOptions: {
11 | parser: 'babel-eslint',
12 | },
13 | rules: {
14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
16 | // trailing comma
17 | 'comma-dangle': ['error', 'always-multiline'],
18 | 'vue/multi-word-component-names': 'off',
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/packages/test-e2e/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/views/Limit.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
24 |
29 |
34 |
35 |
36 |
{{ text }}
37 |
38 |
39 |
--------------------------------------------------------------------------------
/packages/test-e2e/tests/e2e/specs/insert-options.js:
--------------------------------------------------------------------------------
1 | describe('insert space', () => {
2 | it('inserts a space after mention', () => {
3 | cy.visit('/insert-space')
4 | cy.get('.input').type('@{enter}abc')
5 | cy.get('.preview').should('contain', '@akryum abc')
6 | })
7 | })
8 |
9 | describe('omit key', () => {
10 | it('does not insert the key before mention', () => {
11 | cy.visit('/omit-key')
12 | cy.get('.input').type('@{enter}')
13 | cy.get('.preview').should('contain', 'akryum')
14 | })
15 | })
16 |
17 | describe('map insert', () => {
18 | it('maps the inserted value', () => {
19 | cy.visit('/map-insert')
20 | cy.get('.input').type('@{enter}')
21 | cy.get('.preview').should('contain', '@AKRYUM')
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/packages/test-e2e/tests/e2e/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/packages/vue-mention/src/index.ts:
--------------------------------------------------------------------------------
1 | import { App } from 'vue'
2 | import Mentionable from './Mentionable.vue'
3 |
4 | export {
5 | Mentionable,
6 | }
7 |
8 | function registerComponents (app: App, prefix: string) {
9 | app.component(`${prefix}mentionable`, Mentionable)
10 | app.component(`${prefix}Mentionable`, Mentionable)
11 | }
12 |
13 | export function install (app: App, options: string) {
14 | const finalOptions = Object.assign({}, {
15 | installComponents: true,
16 | componentsPrefix: '',
17 | }, options)
18 |
19 | if (finalOptions.installComponents) {
20 | registerComponents(app, finalOptions.componentsPrefix)
21 | }
22 | }
23 |
24 | const plugin = {
25 | // eslint-disable-next-line no-undef
26 | version: VERSION,
27 | install,
28 | }
29 |
30 | export default plugin
31 |
--------------------------------------------------------------------------------
/packages/vue-mention/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path'
2 | import { defineConfig } from 'vite'
3 | import vue from '@vitejs/plugin-vue'
4 |
5 | export default defineConfig({
6 | plugins: [
7 | vue(),
8 | ],
9 | build: {
10 | lib: {
11 | entry: resolve(__dirname, './src/index.ts'),
12 | name: 'VueMention',
13 | },
14 | rollupOptions: {
15 | external: [
16 | 'vue',
17 | 'floating-vue',
18 | ],
19 | output: {
20 | globals: {
21 | vue: 'Vue',
22 | 'floating-vue': 'FloatingVue',
23 | },
24 | },
25 | },
26 | },
27 | define: {
28 | // eslint-disable-next-line @typescript-eslint/no-var-requires
29 | VERSION: JSON.stringify(require('./package.json').version),
30 | },
31 | })
32 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/views/OmitKey.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
29 |
34 |
39 |
40 |
41 |
{{ text }}
42 |
43 |
44 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/views/InsertSpace.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
29 |
34 |
39 |
40 |
41 |
{{ text }}
42 |
43 |
44 |
--------------------------------------------------------------------------------
/packages/test-e2e/tests/e2e/specs/wrapped-input.js:
--------------------------------------------------------------------------------
1 | describe('wrapped input support', () => {
2 | it('finds the textarea', () => {
3 | cy.visit('/wrapped-input')
4 | cy.get('.input').type('abc')
5 | cy.get('.v-popper__popper').should('not.exist')
6 | cy.get('.input').type(' @')
7 | cy.get('.v-popper__popper').should('be.visible')
8 | .should('contain', 'akryum')
9 | .should('contain', 'posva')
10 | .should('contain', 'atinux')
11 | })
12 |
13 | it('finds the input', () => {
14 | cy.visit('/wrapped-input')
15 | cy.get('.toggle').click()
16 | cy.get('.input').type('abc')
17 | cy.get('.v-popper__popper').should('not.exist')
18 | cy.get('.input').type(' @')
19 | cy.get('.v-popper__popper').should('be.visible')
20 | .should('contain', 'akryum')
21 | .should('contain', 'posva')
22 | .should('contain', 'atinux')
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/views/MapInsert.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
29 |
34 |
39 |
40 |
41 |
{{ text }}
42 |
43 |
44 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/views/ItemSlot.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
29 |
33 |
38 |
39 |
40 | {{ item.firstName }}
41 |
42 |
43 |
44 |
{{ text }}
45 |
46 |
47 |
--------------------------------------------------------------------------------
/packages/test-e2e/tests/e2e/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/views/Defaults.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
29 |
34 |
39 |
40 |
41 |
{{ text }}
42 |
43 |
44 |
45 |
50 |
--------------------------------------------------------------------------------
/packages/test-e2e/tests/e2e/plugins/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable arrow-body-style */
2 | // https://docs.cypress.io/guides/guides/plugins-guide.html
3 |
4 | // if you need a custom webpack configuration you can uncomment the following import
5 | // and then use the `file:preprocessor` event
6 | // as explained in the cypress docs
7 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples
8 |
9 | // /* eslint-disable import/no-extraneous-dependencies, global-require */
10 | // const webpack = require('@cypress/webpack-preprocessor')
11 |
12 | module.exports = (on, config) => {
13 | // on('file:preprocessor', webpack({
14 | // webpackOptions: require('@vue/cli-service/webpack.config'),
15 | // watchOptions: {}
16 | // }))
17 |
18 | return Object.assign({}, config, {
19 | fixturesFolder: 'tests/e2e/fixtures',
20 | integrationFolder: 'tests/e2e/specs',
21 | screenshotsFolder: 'tests/e2e/screenshots',
22 | videosFolder: 'tests/e2e/videos',
23 | supportFile: 'tests/e2e/support/index.js',
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/packages/test-e2e/tests/e2e/specs/limit.js:
--------------------------------------------------------------------------------
1 | describe('limit', () => {
2 | it('displays the first 10 items', () => {
3 | cy.visit('/limit')
4 | cy.get('.input').type('@')
5 | cy.get('.mention-item').should('have.length', 10)
6 | for (let i = 0; i < 10; i++) {
7 | cy.get('.v-popper__popper').should('contain', `item-${String(i).padStart(3, '0')}`)
8 | }
9 | // Loops
10 | cy.get('.input').type('{uparrow}{enter}')
11 | cy.get('.preview').should('contain', 'item-009')
12 | })
13 |
14 | it('searches and displays the first 10 matched items', () => {
15 | cy.visit('/limit')
16 | cy.get('.input').type('@1')
17 | cy.get('.mention-item').should('have.length', 10)
18 | cy.get('.v-popper__popper').should('contain', 'item-001')
19 | for (let i = 0; i < 9; i++) {
20 | cy.get('.v-popper__popper').should('contain', `item-${String(10 + i).padStart(3, '0')}`)
21 | }
22 | // Loops
23 | cy.get('.input').type('{uparrow}{enter}')
24 | cy.get('.preview').should('contain', 'item-018')
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/packages/test-e2e/tests/e2e/specs/multiple-collections.js:
--------------------------------------------------------------------------------
1 | describe('with multiple collections and @open', () => {
2 | it('shows user suggestions with @open', () => {
3 | cy.visit('/multiple-collections')
4 | cy.get('.input').type('@')
5 | cy.get('.v-popper__popper').should('be.visible')
6 | .should('contain', 'akryum')
7 | .should('contain', 'posva')
8 | .should('contain', 'atinux')
9 | cy.get('.user').should('have.length', 3)
10 | })
11 |
12 | it('shows issue suggestions with @open', () => {
13 | cy.visit('/multiple-collections')
14 | cy.get('.input').type('#')
15 | cy.get('.v-popper__popper').should('be.visible')
16 | .should('contain', '#123')
17 | .should('contain', '#42')
18 | .should('contain', '#77')
19 | cy.get('.issue').should('have.length', 3)
20 | })
21 |
22 | it('shows custom no-result content', () => {
23 | cy.visit('/multiple-collections')
24 | cy.get('.input').type('@zzz')
25 | cy.get('.v-popper__popper').should('be.visible')
26 | cy.get('.custom-no-result').should('be.visible')
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Guillaume Chau
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/test-e2e/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-e2e",
3 | "version": "2.0.0-alpha.3",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "test:e2e": "vue-cli-service test:e2e",
9 | "lint": "vue-cli-service lint",
10 | "test:e2e:run": "yarn test:e2e --headless"
11 | },
12 | "dependencies": {
13 | "core-js": "^3.6.4",
14 | "floating-vue": "^2.0.0-beta.1",
15 | "vue": "^3.2.26",
16 | "vue-mention": "^2.0.0-alpha.1",
17 | "vue-router": "^4.0.12"
18 | },
19 | "devDependencies": {
20 | "@vue/cli-plugin-babel": "~4.5.15",
21 | "@vue/cli-plugin-e2e-cypress": "~4.5.15",
22 | "@vue/cli-plugin-eslint": "~4.5.15",
23 | "@vue/cli-plugin-router": "~4.5.15",
24 | "@vue/cli-service": "~4.5.15",
25 | "@vue/eslint-config-standard": "^6.1.0",
26 | "babel-eslint": "^10.1.0",
27 | "eslint": "^7.24.0",
28 | "eslint-plugin-import": "^2.20.2",
29 | "eslint-plugin-node": "^11.1.0",
30 | "eslint-plugin-promise": "^6.0.0",
31 | "eslint-plugin-standard": "^5.0.0",
32 | "eslint-plugin-vue": "^8.2.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/test-e2e/tests/e2e/specs/events.js:
--------------------------------------------------------------------------------
1 | describe('events', () => {
2 | it('emits search event', () => {
3 | cy.visit('/search-event')
4 | cy.get('.input').type('@pos')
5 | cy.get('.v-popper__popper').should('be.visible')
6 | cy.get('.search').should('contain', 'pos')
7 | })
8 |
9 | it('emits open and close events', () => {
10 | cy.visit('/search-event')
11 | cy.get('.input').type('@pos')
12 | cy.get('.v-popper__popper').should('be.visible')
13 | cy.get('.open').should('contain', '@')
14 | cy.get('.close').should('not.contain', '@')
15 | cy.get('.input').clear()
16 | cy.get('.v-popper__popper').should('not.be.visible')
17 | cy.get('.close').should('contain', '@')
18 | })
19 |
20 | it('emits apply event', () => {
21 | cy.visit('/search-event')
22 | cy.get('.input').type('@pos')
23 | cy.get('.v-popper__popper').should('be.visible')
24 | cy.get('.apply').should('not.contain', '"item":')
25 | cy.get('.input').type('{downArrow}{enter}')
26 | cy.get('.apply').should('contain', 'value: posva')
27 | .should('contain', 'key: @')
28 | .should('contain', 'replacedWith: @posva')
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/views/WrappedInput.vue:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 |
47 |
51 |
52 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/router.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from 'vue-router'
2 |
3 | const routes = [
4 | {
5 | path: '/',
6 | component: () => import('./views/Home.vue'),
7 | },
8 | {
9 | path: '/defaults',
10 | component: () => import('./views/Defaults.vue'),
11 | },
12 | {
13 | path: '/multiple-collections',
14 | component: () => import('./views/MultipleCollections.vue'),
15 | },
16 | {
17 | path: '/search-event',
18 | component: () => import('./views/Events.vue'),
19 | },
20 | {
21 | path: '/insert-space',
22 | component: () => import('./views/InsertSpace.vue'),
23 | },
24 | {
25 | path: '/omit-key',
26 | component: () => import('./views/OmitKey.vue'),
27 | },
28 | {
29 | path: '/map-insert',
30 | component: () => import('./views/MapInsert.vue'),
31 | },
32 | {
33 | path: '/limit',
34 | component: () => import('./views/Limit.vue'),
35 | },
36 | {
37 | path: '/item-slot',
38 | component: () => import('./views/ItemSlot.vue'),
39 | },
40 | {
41 | path: '/wrapped-input',
42 | component: () => import('./views/WrappedInput.vue'),
43 | },
44 | {
45 | path: '/content-editable',
46 | component: () => import('./views/ContentEditable.vue'),
47 | },
48 | ]
49 |
50 | const router = createRouter({
51 | history: createWebHistory(process.env.BASE_URL),
52 | routes,
53 | })
54 |
55 | export default router
56 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/views/ContentEditable.vue:
--------------------------------------------------------------------------------
1 |
41 |
42 |
43 |
46 |
Content editable
47 |
48 |
53 |
59 |
60 |
61 |
{{ text }}
62 |
63 |
64 |
65 |
70 |
--------------------------------------------------------------------------------
/packages/vue-mention/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | },
6 | extends: [
7 | 'plugin:vue/recommended',
8 | '@vue/standard',
9 | '@vue/typescript/recommended',
10 | ],
11 | parserOptions: {
12 | ecmaVersion: 2020,
13 | },
14 | globals: {
15 | name: 'off',
16 | },
17 | rules: {
18 | 'arrow-parens': 0,
19 | 'generator-star-spacing': 0,
20 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
21 | 'comma-dangle': ['error', 'always-multiline'],
22 | 'vue/multi-word-component-names': 'off',
23 | 'vue/html-closing-bracket-newline': [
24 | 'error',
25 | {
26 | singleline: 'never',
27 | multiline: 'always',
28 | },
29 | ],
30 | 'no-var': ['error'],
31 | '@typescript-eslint/member-delimiter-style': [
32 | 'error',
33 | {
34 | multiline: {
35 | delimiter: 'none',
36 | },
37 | singleline: {
38 | delimiter: 'comma',
39 | },
40 | },
41 | ],
42 | '@typescript-eslint/ban-ts-comment': 'warn',
43 | '@typescript-eslint/no-use-before-define': 'off',
44 | 'no-use-before-define': 'off',
45 | '@typescript-eslint/indent': ['error', 2],
46 | quotes: ['error', 'single', { allowTemplateLiterals: true }],
47 | },
48 | ignorePatterns: [
49 | 'node_modules/',
50 | 'dist/',
51 | '!.*',
52 | ],
53 | }
54 |
--------------------------------------------------------------------------------
/packages/test-e2e/src/views/Events.vue:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
33 |
applyEvent = { item, key, replacedWith }"
40 | >
41 |
46 |
47 |
48 |
{{ searchText }}
49 |
{{ openKey }}
50 |
{{ closeKey }}
51 |
52 | value: {{ applyEvent ? applyEvent.item.value : null }}
53 | key: {{ applyEvent ? applyEvent.key : null }}
54 | replacedWith: {{ applyEvent ? applyEvent.replacedWith : null }}
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/packages/docs/src/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: 'vue-mention',
3 | head: [
4 | ['script', {
5 | src: 'https://polyfill.io/v3/polyfill.min.js?features=Object.assign%2CPromise%2CArray.from%2CEvent',
6 | }],
7 | ['script', {
8 | src: 'https://unpkg.com/shim-keyboard-event-key',
9 | async: true,
10 | }],
11 | ['link', { rel: 'icon', href: '/vue-mention.svg' }],
12 | ],
13 | themeConfig: {
14 | logo: '/vue-mention.svg',
15 | repo: 'Akryum/vue-mention',
16 | docsDir: 'packages/docs',
17 | locales: {
18 | '/': {
19 | lang: 'en-US',
20 | title: 'vue-mention',
21 | description: 'Mention for Vue.js',
22 | },
23 | },
24 | editLinks: true,
25 | lastUpdated: true,
26 | smoothScroll: true,
27 | sidebarDepth: 3,
28 | locales: {
29 | '/': {
30 | label: 'English',
31 | selectText: 'Languages',
32 | lastUpdated: 'Last Updated',
33 | editLinkText: 'Edit this page on GitHub',
34 | nav: [
35 | { text: 'Home', link: '/' },
36 | { text: 'Guide', link: '/guide/' },
37 | ],
38 | sidebar: {
39 | '/guide/': [
40 | {
41 | title: 'Guide',
42 | collapsable: false,
43 | children: [
44 | '/guide/',
45 | '/guide/async-items',
46 | ],
47 | },
48 | ],
49 | },
50 | },
51 | },
52 | },
53 | }
54 |
--------------------------------------------------------------------------------
/packages/test-e2e/tests/e2e/specs/content-editable.js:
--------------------------------------------------------------------------------
1 | describe('contenteditable support', () => {
2 | it('shows suggestions', () => {
3 | cy.visit('/content-editable')
4 | cy.get('.input').type('abc')
5 | cy.get('.v-popper__popper').should('not.exist')
6 | cy.get('.input').type(' @')
7 | cy.get('.v-popper__popper').should('be.visible')
8 | .should('contain', 'akryum')
9 | .should('contain', 'posva')
10 | .should('contain', 'atinux')
11 | })
12 |
13 | it('hides menu on space character', () => {
14 | cy.visit('/content-editable')
15 | cy.get('.input').type(' @')
16 | cy.get('.v-popper__popper').should('be.visible')
17 | cy.get('.input').type(' ')
18 | cy.get('.v-popper__popper').should('not.be.visible')
19 | })
20 |
21 | it('inserts suggestion on enter', () => {
22 | cy.visit('/content-editable')
23 | cy.get('.input').type(' @')
24 | cy.get('.v-popper__popper').should('be.visible')
25 | cy.get('.input').type('{enter}')
26 | cy.get('.preview').should('contain', '@akryum')
27 | })
28 |
29 | it('searches through suggestions', () => {
30 | cy.visit('/content-editable')
31 | cy.get('.input').type(' @pos')
32 | cy.get('.v-popper__popper').should('be.visible')
33 | cy.get('.mention-item').should('have.length', 1)
34 | .should('contain', 'posva')
35 | cy.get('.input').type('{enter}')
36 | cy.get('.preview').should('contain', '@posva')
37 | })
38 |
39 | it('does not insert a space after mention', () => {
40 | cy.visit('/content-editable')
41 | cy.get('.input').type(' @{enter}abc')
42 | cy.get('.preview').should('contain', '@akryumabc')
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/.github/workflows/changelog.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: Release Changelog
4 |
5 | # Controls when the action will run.
6 | on:
7 | # Triggers the workflow on push or pull request events but only for the master branch
8 | push:
9 | branches: [ master ]
10 | pull_request:
11 | branches: [ master ]
12 |
13 | # Allows you to run this workflow manually from the Actions tab
14 | workflow_dispatch:
15 |
16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
17 | jobs:
18 | # This workflow contains a single job called "build"
19 | changelog:
20 | # The type of runner that the job will run on
21 | runs-on: ubuntu-latest
22 |
23 | # Steps represent a sequence of tasks that will be executed as part of the job
24 | steps:
25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
26 | - uses: actions/checkout@v2
27 |
28 | - name: Conventional Changelog Action
29 | id: changelog
30 | uses: TriPSs/conventional-changelog-action@v3
31 | with:
32 | github-token: ${{ secrets.github_token }}
33 | output-file: "false"
34 | skip-commit: "true"
35 | version-file: "./packages/vue-mention/package.json"
36 |
37 | - name: Create Release
38 | uses: actions/create-release@v1
39 | if: ${{ steps.changelog.outputs.skipped == 'false' }}
40 | env:
41 | GITHUB_TOKEN: ${{ secrets.github_token }}
42 | with:
43 | tag_name: ${{ steps.changelog.outputs.tag }}
44 | release_name: ${{ steps.changelog.outputs.tag }}
45 | body: ${{ steps.changelog.outputs.clean_changelog }}
46 |
--------------------------------------------------------------------------------
/packages/docs/src/.vuepress/components/GettingStartedDemo.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
31 |
36 |
42 |
43 |
44 |
45 | {{ item.label }}
46 |
47 |
48 |
49 |
50 |
{{ text }}
51 |
52 |
53 |
54 |
89 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/packages/docs/src/guide/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ## Installation
10 |
11 | vue-mention requires [v-tooltip](https://github.com/Akryum/v-tooltip).
12 |
13 | Install the `vue-mention` package:
14 |
15 | ```sh
16 | npm i vue-mention
17 | ```
18 |
19 | Or
20 |
21 | ```sh
22 | yarn add vue-mention
23 | ```
24 |
25 | ## Quick start
26 |
27 | Import the `Mentionable` component from `vue-mention`:
28 |
29 | ```vue{2,6}
30 |
45 | ```
46 |
47 | Add some items for the suggestions:
48 |
49 | ```vue{12-21}
50 |
75 | ```
76 |
77 | The `value` properties will be used as the text replacement.
78 |
79 | Wrap your `