├── .editorconfig ├── .eslintignore ├── .github ├── dependabot.yml └── workflows │ └── nodejs.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── appveyor.yml ├── babel.config.js ├── build ├── rollup.config.base.js ├── rollup.config.es.js └── rollup.config.unpkg.js ├── dist ├── demo.html ├── vue-trix.common.js ├── vue-trix.common.js.map ├── vue-trix.css ├── vue-trix.esm.js ├── vue-trix.esm.js.map ├── vue-trix.min.js ├── vue-trix.min.js.map ├── vue-trix.umd.js ├── vue-trix.umd.js.map ├── vue-trix.umd.min.js └── vue-trix.umd.min.js.map ├── example ├── public │ ├── favicon.ico │ └── index.html └── src │ ├── App.vue │ ├── assets │ ├── logo.png │ ├── vue-trix-editor.png │ ├── vue-trix-form.png │ ├── vue-trix-in-prod.png │ └── vue-trix-simple.png │ ├── components │ └── Editor.vue │ └── main.js ├── jest.config.js ├── jest └── htmlSnapshotBeautifier.js ├── package-lock.json ├── package.json ├── src ├── components │ └── VueTrix.vue ├── index.js └── mixins │ ├── EmitAttachmentAdd.js │ ├── EmitAttachmentRemove.js │ ├── EmitBeforeInitialize.js │ ├── EmitDroppedFile.js │ ├── EmitFileAccept.js │ ├── EmitInitialize.js │ ├── EmitSelectionChange.js │ └── ProcessEditorFocusAndBlur.js └── tests └── unit ├── .eslintrc.js └── VueTrix.spec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_style = space 4 | indent_size = 2 5 | end_of_line = lf 6 | insert_final_newline = true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "20:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: y18n 11 | versions: 12 | - 4.0.1 13 | - dependency-name: "@vue/cli-plugin-babel" 14 | versions: 15 | - 4.5.11 16 | - dependency-name: "@vue/test-utils" 17 | versions: 18 | - 1.1.3 19 | - dependency-name: js-beautify 20 | versions: 21 | - 1.13.4 22 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [16.x, 18.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm install, build, and test 21 | run: | 22 | npm install 23 | npm run build --if-present 24 | npm run test:unit 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /coverage 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at tranduchanh.ms@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Hanh D. TRAN 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue-Trix Text Editor 2 | 3 | [![npm](https://img.shields.io/npm/v/vue-trix.svg?style=flat)](https://www.npmjs.com/package/vue-trix) [![Build status](https://ci.appveyor.com/api/projects/status/nffmo893v52evpgm/branch/master?svg=true)](https://ci.appveyor.com/project/tranduchanh/vue-trix/branch/master) npm 4 | 5 | Simple and lightweight [Trix](https://www.npmjs.com/package/trix) rich-text editor Vue.js component for writing daily 6 | 7 | ## Table of Contents 8 | 9 | - [Vue-Trix Text Editor](#vue-trix-text-editor) 10 | - [Table of Contents](#table-of-contents) 11 | - [Getting started](#getting-started) 12 | - [Demo page](#demo-page) 13 | - [Integrate into the form](#integrate-into-the-form) 14 | - [Features](#features) 15 | - [Installation](#installation) 16 | - [NPM](#npm) 17 | - [YARN](#yarn) 18 | - [Or directly from latest Github repo](#or-directly-from-latest-github-repo) 19 | - [Mount](#mount) 20 | - [Mount with global](#mount-with-global) 21 | - [Mount with component](#mount-with-component) 22 | - [Setup with Nuxt.js (SSR)](#setup-with-nuxtjs-ssr) 23 | - [Component Usages](#component-usages) 24 | - [Create a simple editor in your single component file](#create-a-simple-editor-in-your-single-component-file) 25 | - [Integrating with Forms](#integrating-with-forms) 26 | - [Props descriptions](#props-descriptions) 27 | - [Populating editor content](#populating-editor-content) 28 | - [Init loading content into the editor](#init-loading-content-into-the-editor) 29 | - [Track data changes](#track-data-changes) 30 | - [Binding attachment events](#binding-attachment-events) 31 | - [Process uploading attachment to remote server](#process-uploading-attachment-to-remote-server) 32 | - [Trix document](#trix-document) 33 | - [Contributing](#contributing) 34 | 35 | ## Getting started 36 | 37 | ### [Demo page](/example) 38 | 39 | ![vue-trix editor](/example/src/assets/vue-trix-editor.png) 40 | 41 | ### Integrate into the form 42 | 43 | ![vue-trix in production](/example/src/assets/vue-trix-in-prod.png) 44 | 45 | ## Features 46 | 47 | - A simple and lightweight rich-text editor for writing daily. 48 | - Two-ways binding with `v-model` easily. 49 | - Auto-save editor data temporally what you has typed into the form input in case something goes wrong (for example, the browser could crash or you could accidentally refresh the page without submit saving). 50 | 51 | ## Installation 52 | 53 | ### NPM 54 | 55 | ```Shell 56 | npm install --save vue-trix 57 | ``` 58 | 59 | ### YARN 60 | 61 | ```Shell 62 | yarn add vue-trix 63 | ``` 64 | 65 | ### Or directly from latest Github repo 66 | 67 | ```Shell 68 | npm install --save hanhdt/vue-trix 69 | ``` 70 | 71 | ## Mount 72 | 73 | ### Mount with global 74 | 75 | in the `main.js`, import the package as a global component. 76 | 77 | ```javascript 78 | import "vue-trix"; 79 | ``` 80 | 81 | ### Mount with component 82 | 83 | ```javascript 84 | import VueTrix from "vue-trix"; 85 | 86 | export default { 87 | // ... 88 | components: { 89 | VueTrix 90 | } 91 | }; 92 | ``` 93 | 94 | ### Setup with Nuxt.js (SSR) 95 | 96 | Create mounting plugin file 97 | 98 | ```javascript 99 | // plugins/vue_trix.js 100 | import Vue from "vue"; 101 | import VueTrix from "vue-trix"; 102 | 103 | Vue.use(VueTrix); 104 | ``` 105 | 106 | Update Nuxt.js config file 107 | 108 | ```javascript 109 | // nuxt.config.js 110 | plugins: [{ src: "~/plugins/vue_trix", mode: "client" }]; 111 | ``` 112 | 113 | ## Component Usages 114 | 115 | ### Create a simple editor in your single component file 116 | 117 | Add `VueTrix` component into `*.vue` template 118 | 119 | ```XML 120 | 125 | ``` 126 | 127 | ### Integrating with Forms 128 | 129 | ```XML 130 |
131 | 132 | 133 | ``` 134 | 135 | ### Props descriptions 136 | 137 | - `inputId`: This is referenced `id` of the hidden input field defined, it is optional. 138 | - `inputName`: This is referenced `name` of the hidden input field defined, default value is `content`. 139 | - `placeholder`: The placeholder option attribute specifies a short hint that describes the expected value of a editor. 140 | - `autofocus`: Automatically focus the editor when it loads 141 | - `disabledEditor`: This prop will put the editor in read-only mode. 142 | - `localStorage`: The boolean attribute allows saving editor state into browser's localStorage (optional, default is `false`). 143 | 144 | ### Populating editor content 145 | 146 | #### Init loading content into the editor 147 | 148 | In case, you want to load initial data from database then display into the editor. you can use `v-model` directive with local component's state. 149 | 150 | ```javascript 151 | // Declare local component's state is loaded from database 152 | export default { 153 | // ... 154 | data() { 155 | return { 156 | editorContent: "

Editor contents

" 157 | }; 158 | } 159 | // ... 160 | }; 161 | ``` 162 | 163 | ```HTML 164 | // Assign to v-model directive 165 | 170 | ``` 171 | 172 | #### Track data changes 173 | 174 | The local component's state will be changed reactively when you modified contents inside the trix editor UI. Therefore, you need to `watch` the local state for updating content back to database 175 | 176 | ```javascript 177 | export default { 178 | // ... 179 | methods: { 180 | updateEditorContent(value) { 181 | // Update new content into the database via state mutations. 182 | } 183 | }, 184 | watch: { 185 | editorContent: { 186 | handler: "updateEditorContent" 187 | } 188 | } 189 | // ... 190 | }; 191 | ``` 192 | 193 | ### Binding attachment events 194 | 195 | The `` element emits several events which you can use to observe and respond to changes in editor state. 196 | 197 | - `@trix-file-accept` fires before an attachment is added to the document. If you don’t want to accept dropped or pasted files, call preventDefault() on this event. 198 | 199 | - `@trix-attachment-add` fires after an attachment is added to the document. You can access the Trix attachment object through the attachment property on the event. If the attachment object has a file property, you should store this file remotely and set the attachment’s URL attribute. 200 | 201 | - `@trix-attachment-remove` fires when an attachment is removed from the document. You can access the Trix attachment object through the attachment property on the event. You may wish to use this event to clean up remotely stored files. 202 | 203 | ### Process uploading attachment to remote server 204 | 205 | Add binding event listener to `trix-attachment-add` 206 | 207 | ```HTML 208 | 213 | ``` 214 | 215 | In Javascript 216 | 217 | ```Javascript 218 | const remoteHost = 'your remote host'; 219 | 220 | function handleAttachmentChanges(event) { 221 | // 1. get file object 222 | let file = event.attachment.file; 223 | 224 | // 2. upload file to remote server with FormData 225 | // ... 226 | 227 | // 3. if upload success, set back the attachment's URL attribute 228 | // @param object data from remote server response data after upload. 229 | let attributes = { 230 | url: remoteHost + data.path, 231 | href: remoteHost + data.path 232 | }; 233 | event.attachment.setAttributes(attributes); 234 | } 235 | ``` 236 | 237 | ## Trix document 238 | 239 | [Full documentation](https://github.com/basecamp/trix#readme) 240 | 241 | ## Contributing 242 | 243 | If you're interested in contributing to Vue-Trix or share your opinions, please consider to submitting a [**pull request**](https://github.com/hanhdt/vue-trix/pulls) or post [**issues**](https://github.com/hanhdt/vue-trix/issues). Also, I will try to code-self documentation. 244 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | nodejs_version: "10" 3 | 4 | install: 5 | - ps: Install-Product node $env:nodejs_version 6 | - npm install 7 | 8 | test_script: 9 | - node --version 10 | - npm --version 11 | - npm run test:unit 12 | 13 | cache: 14 | - node_modules -> package-lock.json 15 | 16 | build: off -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /build/rollup.config.base.js: -------------------------------------------------------------------------------- 1 | import vue from 'rollup-plugin-vue' 2 | import replace from 'rollup-plugin-replace' 3 | import buble from 'rollup-plugin-buble' 4 | import commonjs from 'rollup-plugin-commonjs' 5 | 6 | const config = require('../package.json') 7 | 8 | export default { 9 | input: 'src/index.js', 10 | name: 'VueTrix', 11 | plugins: [ 12 | commonjs(), 13 | vue({ 14 | compileTemplate: true, 15 | css: true 16 | }), 17 | replace({ 18 | VERSION: JSON.stringify(config.version) 19 | }), 20 | buble() 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /build/rollup.config.es.js: -------------------------------------------------------------------------------- 1 | import base from './rollup.config.base' 2 | const globals = { vue: 'Vue' } 3 | 4 | const config = Object.assign({}, base, { 5 | output: { 6 | globals, 7 | file: 'dist/vue-trix.esm.js', 8 | format: 'esm', 9 | sourcemap: true 10 | } 11 | }) 12 | 13 | export default config 14 | -------------------------------------------------------------------------------- /build/rollup.config.unpkg.js: -------------------------------------------------------------------------------- 1 | import base from './rollup.config.base' 2 | import uglify from 'rollup-plugin-uglify' 3 | import { minify } from 'uglify-es' 4 | const globals = { vue: 'Vue' } 5 | 6 | const config = Object.assign({}, base, { 7 | output: { 8 | globals, 9 | name: 'VueTrix', 10 | file: 'dist/vue-trix.min.js', 11 | format: 'iife', 12 | sourcemap: true 13 | } 14 | }) 15 | 16 | config.plugins.push(uglify.uglify({}, minify)) 17 | 18 | export default config 19 | -------------------------------------------------------------------------------- /dist/demo.html: -------------------------------------------------------------------------------- 1 | 2 | vue-trix demo 3 | 4 | 5 | 6 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /dist/vue-trix.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";trix-editor{border:1px solid #bbb;border-radius:3px;margin:0;padding:.4em .6em;min-height:5em;outline:none}trix-toolbar *{-webkit-box-sizing:border-box;box-sizing:border-box}trix-toolbar .trix-button-row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;overflow-x:auto}trix-toolbar .trix-button-group{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px;border:1px solid #bbb;border-top-color:#ccc;border-bottom-color:#888;border-radius:3px}trix-toolbar .trix-button-group:not(:first-child){margin-left:1.5vw}@media (max-device-width:768px){trix-toolbar .trix-button-group:not(:first-child){margin-left:0}}trix-toolbar .trix-button-group-spacer{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}@media (max-device-width:768px){trix-toolbar .trix-button-group-spacer{display:none}}trix-toolbar .trix-button{position:relative;float:left;color:rgba(0,0,0,.6);font-size:.75em;font-weight:600;white-space:nowrap;padding:0 .5em;margin:0;outline:none;border:none;border-bottom:1px solid #ddd;border-radius:0;background:transparent}trix-toolbar .trix-button:not(:first-child){border-left:1px solid #ccc}trix-toolbar .trix-button.trix-active{background:#cbeefa;color:#000}trix-toolbar .trix-button:not(:disabled){cursor:pointer}trix-toolbar .trix-button:disabled{color:rgba(0,0,0,.125)}@media (max-device-width:768px){trix-toolbar .trix-button{letter-spacing:-.01em;padding:0 .3em}}trix-toolbar .trix-button--icon{font-size:inherit;width:2.6em;height:1.6em;max-width:calc(.8em + 4vw);text-indent:-9999px}@media (max-device-width:768px){trix-toolbar .trix-button--icon{height:2em;max-width:calc(.8em + 3.5vw)}}trix-toolbar .trix-button--icon:before{display:inline-block;position:absolute;top:0;right:0;bottom:0;left:0;opacity:.6;content:"";background-position:50%;background-repeat:no-repeat;background-size:contain}@media (max-device-width:768px){trix-toolbar .trix-button--icon:before{right:6%;left:6%}}trix-toolbar .trix-button--icon.trix-active:before{opacity:1}trix-toolbar .trix-button--icon:disabled:before{opacity:.125}trix-toolbar .trix-button--icon-attach:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M16.5 6v11.5a4 4 0 11-8 0V5a2.5 2.5 0 015 0v10.5a1 1 0 11-2 0V6H10v9.5a2.5 2.5 0 005 0V5a4 4 0 10-8 0v12.5a5.5 5.5 0 0011 0V6h-1.5z'/%3E%3C/svg%3E");top:8%;bottom:4%}trix-toolbar .trix-button--icon-bold:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M15.6 11.8c1-.7 1.6-1.8 1.6-2.8a4 4 0 00-4-4H7v14h7c2.1 0 3.7-1.7 3.7-3.8 0-1.5-.8-2.8-2.1-3.4zM10 7.5h3a1.5 1.5 0 110 3h-3v-3zm3.5 9H10v-3h3.5a1.5 1.5 0 110 3z'/%3E%3C/svg%3E")}trix-toolbar .trix-button--icon-italic:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M10 5v3h2.2l-3.4 8H6v3h8v-3h-2.2l3.4-8H18V5h-8z'/%3E%3C/svg%3E")}trix-toolbar .trix-button--icon-link:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M9.88 13.7a4.3 4.3 0 010-6.07l3.37-3.37a4.26 4.26 0 016.07 0 4.3 4.3 0 010 6.06l-1.96 1.72a.91.91 0 11-1.3-1.3l1.97-1.71a2.46 2.46 0 00-3.48-3.48l-3.38 3.37a2.46 2.46 0 000 3.48.91.91 0 11-1.3 1.3z'/%3E%3Cpath d='M4.25 19.46a4.3 4.3 0 010-6.07l1.93-1.9a.91.91 0 111.3 1.3l-1.93 1.9a2.46 2.46 0 003.48 3.48l3.37-3.38c.96-.96.96-2.52 0-3.48a.91.91 0 111.3-1.3 4.3 4.3 0 010 6.07l-3.38 3.38a4.26 4.26 0 01-6.07 0z'/%3E%3C/svg%3E")}trix-toolbar .trix-button--icon-strike:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M12.73 14l.28.14c.26.15.45.3.57.44.12.14.18.3.18.5 0 .3-.15.56-.44.75-.3.2-.76.3-1.39.3A13.52 13.52 0 017 14.95v3.37a10.64 10.64 0 004.84.88c1.26 0 2.35-.19 3.28-.56.93-.37 1.64-.9 2.14-1.57s.74-1.45.74-2.32c0-.26-.02-.51-.06-.75h-5.21zm-5.5-4c-.08-.34-.12-.7-.12-1.1 0-1.29.52-2.3 1.58-3.02 1.05-.72 2.5-1.08 4.34-1.08 1.62 0 3.28.34 4.97 1l-1.3 2.93c-1.47-.6-2.73-.9-3.8-.9-.55 0-.96.08-1.2.26-.26.17-.38.38-.38.64 0 .27.16.52.48.74.17.12.53.3 1.05.53H7.23zM3 13h18v-2H3v2z'/%3E%3C/svg%3E")}trix-toolbar .trix-button--icon-quote:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg version='1' xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z'/%3E%3C/svg%3E")}trix-toolbar .trix-button--icon-heading-1:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg version='1' xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M12 9v3H9v7H6v-7H3V9h9zM8 4h14v3h-6v12h-3V7H8V4z'/%3E%3C/svg%3E")}trix-toolbar .trix-button--icon-code:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M18.2 12L15 15.2l1.4 1.4L21 12l-4.6-4.6L15 8.8l3.2 3.2zM5.8 12L9 8.8 7.6 7.4 3 12l4.6 4.6L9 15.2 5.8 12z'/%3E%3C/svg%3E")}trix-toolbar .trix-button--icon-bullet-list:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg version='1' xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M4 4a2 2 0 100 4 2 2 0 000-4zm0 6a2 2 0 100 4 2 2 0 000-4zm0 6a2 2 0 100 4 2 2 0 000-4zm4 3h14v-2H8v2zm0-6h14v-2H8v2zm0-8v2h14V5H8z'/%3E%3C/svg%3E")}trix-toolbar .trix-button--icon-number-list:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z'/%3E%3C/svg%3E")}trix-toolbar .trix-button--icon-undo:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M12.5 8c-2.6 0-5 1-6.9 2.6L2 7v9h9l-3.6-3.6A8 8 0 0120 16l2.4-.8a10.5 10.5 0 00-10-7.2z'/%3E%3C/svg%3E")}trix-toolbar .trix-button--icon-redo:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M18.4 10.6a10.5 10.5 0 00-16.9 4.6L4 16a8 8 0 0112.7-3.6L13 16h9V7l-3.6 3.6z'/%3E%3C/svg%3E")}trix-toolbar .trix-button--icon-decrease-nesting-level:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M3 19h19v-2H3v2zm7-6h12v-2H10v2zm-8.3-.3l2.8 2.9L6 14.2 4 12l2-2-1.4-1.5L1 12l.7.7zM3 5v2h19V5H3z'/%3E%3C/svg%3E")}trix-toolbar .trix-button--icon-increase-nesting-level:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M3 19h19v-2H3v2zm7-6h12v-2H10v2zm-6.9-1L1 14.2l1.4 1.4L6 12l-.7-.7-2.8-2.8L1 9.9 3.1 12zM3 5v2h19V5H3z'/%3E%3C/svg%3E")}trix-toolbar .trix-dialogs{position:relative}trix-toolbar .trix-dialog{position:absolute;top:0;left:0;right:0;font-size:.75em;padding:15px 10px;background:#fff;-webkit-box-shadow:0 .3em 1em #ccc;box-shadow:0 .3em 1em #ccc;border-top:2px solid #888;border-radius:5px;z-index:5}trix-toolbar .trix-input--dialog{font-size:inherit;font-weight:400;padding:.5em .8em;margin:0 10px 0 0;border-radius:3px;border:1px solid #bbb;background-color:#fff;-webkit-box-shadow:none;box-shadow:none;outline:none;-webkit-appearance:none;-moz-appearance:none}trix-toolbar .trix-input--dialog.validate:invalid{-webkit-box-shadow:red 0 0 1.5px 1px;box-shadow:0 0 1.5px 1px red}trix-toolbar .trix-button--dialog{font-size:inherit;padding:.5em;border-bottom:none}trix-toolbar .trix-dialog--link{max-width:600px}trix-toolbar .trix-dialog__link-fields{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}trix-toolbar .trix-dialog__link-fields .trix-input{-webkit-box-flex:1;-ms-flex:1;flex:1}trix-toolbar .trix-dialog__link-fields .trix-button-group{-webkit-box-flex:0;-ms-flex:0 0 content;flex:0 0 content;margin:0}trix-editor [data-trix-mutable]:not(.attachment__caption-editor){-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}trix-editor [data-trix-cursor-target]::-moz-selection,trix-editor [data-trix-mutable]::-moz-selection,trix-editor [data-trix-mutable] ::-moz-selection{background:none}trix-editor [data-trix-cursor-target]::selection,trix-editor [data-trix-mutable]::selection,trix-editor [data-trix-mutable] ::selection{background:none}trix-editor [data-trix-mutable].attachment__caption-editor:focus::-moz-selection{background:highlight}trix-editor [data-trix-mutable].attachment__caption-editor:focus::selection{background:highlight}trix-editor [data-trix-mutable].attachment.attachment--file{border-color:transparent}trix-editor [data-trix-mutable].attachment.attachment--file,trix-editor [data-trix-mutable].attachment img{-webkit-box-shadow:0 0 0 2px highlight;box-shadow:0 0 0 2px highlight}trix-editor .attachment{position:relative}trix-editor .attachment:hover{cursor:default}trix-editor .attachment--preview .attachment__caption:hover{cursor:text}trix-editor .attachment__progress{position:absolute;z-index:1;height:20px;top:calc(50% - 10px);left:5%;width:90%;opacity:.9;-webkit-transition:opacity .2s ease-in;transition:opacity .2s ease-in}trix-editor .attachment__progress[value="100"]{opacity:0}trix-editor .attachment__caption-editor{display:inline-block;width:100%;margin:0;padding:0;font-size:inherit;font-family:inherit;line-height:inherit;color:inherit;text-align:center;vertical-align:top;border:none;outline:none;-webkit-appearance:none;-moz-appearance:none}trix-editor .attachment__toolbar{position:absolute;z-index:1;top:-.9em;left:0;width:100%;text-align:center}trix-editor .trix-button-group{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}trix-editor .trix-button{position:relative;float:left;color:#666;white-space:nowrap;font-size:80%;padding:0 .8em;margin:0;outline:none;border:none;border-radius:0;background:transparent}trix-editor .trix-button:not(:first-child){border-left:1px solid #ccc}trix-editor .trix-button.trix-active{background:#cbeefa}trix-editor .trix-button:not(:disabled){cursor:pointer}trix-editor .trix-button--remove{text-indent:-9999px;display:inline-block;padding:0;outline:none;width:1.8em;height:1.8em;line-height:1.8em;border-radius:50%;background-color:#fff;border:2px solid highlight;-webkit-box-shadow:1px 1px 6px rgba(0,0,0,.25);box-shadow:1px 1px 6px rgba(0,0,0,.25)}trix-editor .trix-button--remove:before{display:inline-block;position:absolute;top:0;right:0;bottom:0;left:0;opacity:.7;content:"";background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg height='24' width='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M19 6.4L17.6 5 12 10.6 6.4 5 5 6.4l5.6 5.6L5 17.6 6.4 19l5.6-5.6 5.6 5.6 1.4-1.4-5.6-5.6z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:90%}trix-editor .trix-button--remove:hover{border-color:#333}trix-editor .trix-button--remove:hover:before{opacity:1}trix-editor .attachment__metadata-container{position:relative}trix-editor .attachment__metadata{position:absolute;left:50%;top:2em;-webkit-transform:translate(-50%);transform:translate(-50%);max-width:90%;padding:.1em .6em;font-size:.8em;color:#fff;background-color:rgba(0,0,0,.7);border-radius:3px}trix-editor .attachment__metadata .attachment__name{display:inline-block;max-width:100%;vertical-align:bottom;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}trix-editor .attachment__metadata .attachment__size{margin-left:.2em;white-space:nowrap}.trix-content{line-height:1.5}.trix-content *{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0}.trix-content h1{font-size:1.2em;line-height:1.2}.trix-content blockquote{border:0 solid #ccc;border-left-width:.3em;margin-left:.3em;padding-left:.6em}.trix-content [dir=rtl] blockquote,.trix-content blockquote[dir=rtl]{border-width:0;border-right-width:.3em;margin-right:.3em;padding-right:.6em}.trix-content li{margin-left:1em}.trix-content [dir=rtl] li{margin-right:1em}.trix-content pre{display:inline-block;width:100%;vertical-align:top;font-family:monospace;font-size:.9em;padding:.5em;white-space:pre;background-color:#eee;overflow-x:auto}.trix-content img{max-width:100%;height:auto}.trix-content .attachment{display:inline-block;position:relative;max-width:100%}.trix-content .attachment a{color:inherit;text-decoration:none}.trix-content .attachment a:hover,.trix-content .attachment a:visited:hover{color:inherit}.trix-content .attachment__caption{text-align:center}.trix-content .attachment__caption .attachment__name+.attachment__size:before{content:" · "}.trix-content .attachment--preview{width:100%;text-align:center}.trix-content .attachment--preview .attachment__caption{color:#666;font-size:.9em;line-height:1.2}.trix-content .attachment--file{color:#333;line-height:1;margin:0 2px 2px 2px;padding:.4em 1em;border:1px solid #bbb;border-radius:5px}.trix-content .attachment-gallery{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;position:relative}.trix-content .attachment-gallery .attachment{-webkit-box-flex:1;-ms-flex:1 0 33%;flex:1 0 33%;padding:0 .5em;max-width:33%}.trix-content .attachment-gallery.attachment-gallery--2 .attachment,.trix-content .attachment-gallery.attachment-gallery--4 .attachment{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.VueTrix_trix_container_5Bcya{max-width:100%;height:auto}.VueTrix_trix_container_5Bcya .VueTrix_trix-button-group_2D-Jd,.VueTrix_trix_container_5Bcya .VueTrix_trix-content_1TD_D{background-color:#fff} -------------------------------------------------------------------------------- /dist/vue-trix.esm.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Trix from 'trix'; 3 | import 'trix/dist/trix.css'; 4 | 5 | /** 6 | * 7 | * @param {*} component 8 | */ 9 | function EmitFileAccept (component) { 10 | return { 11 | methods: { 12 | emitFileAccept: function emitFileAccept (file) { 13 | this.$emit('trix-file-accept', file); 14 | } 15 | } 16 | } 17 | } 18 | 19 | /** 20 | * 21 | * @param {*} component 22 | */ 23 | function EmitInitialize (component) { 24 | return { 25 | methods: { 26 | emitInitialize: function emitInitialize (editor) { 27 | this.$emit('trix-initialize', this.$refs.trix.editor, event); 28 | } 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * 35 | * @param {*} component 36 | */ 37 | function EmitAttachmentAdd (component) { 38 | return { 39 | methods: { 40 | emitAttachmentAdd: function emitAttachmentAdd (file) { 41 | this.$emit('trix-attachment-add', file); 42 | } 43 | } 44 | } 45 | } 46 | 47 | /** 48 | * 49 | * @param {*} component 50 | */ 51 | function EmitSelectionChange (component) { 52 | return { 53 | methods: { 54 | emitSelectionChange: function emitSelectionChange (event) { 55 | this.$emit('trix-selection-change', this.$refs.trix.editor, event); 56 | } 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * 63 | * @param {*} component 64 | */ 65 | function EmitAttachmentRemove (component) { 66 | return { 67 | methods: { 68 | emitAttachmentRemove: function emitAttachmentRemove (file) { 69 | this.$emit('trix-attachment-remove', file); 70 | } 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * 77 | * @param {*} component 78 | */ 79 | function EmitBeforeInitialize (component) { 80 | return { 81 | methods: { 82 | emitBeforeInitialize: function emitBeforeInitialize (event) { 83 | this.$emit('trix-before-initialize', this.$refs.trix.editor, event); 84 | } 85 | } 86 | } 87 | } 88 | 89 | function ProcessEditorFocusAndBlur (component) { 90 | return { 91 | methods: { 92 | processTrixFocus: function processTrixFocus (event) { 93 | if (this.$refs.trix) { 94 | this.isActived = true; 95 | this.$emit('trix-focus', this.$refs.trix.editor, event); 96 | } 97 | }, 98 | processTrixBlur: function processTrixBlur (event) { 99 | if (this.$refs.trix) { 100 | this.isActived = false; 101 | this.$emit('trix-blur', this.$refs.trix.editor, event); 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | // 109 | 110 | var script = { 111 | name: 'vue-trix', 112 | mixins: [ 113 | EmitFileAccept(), 114 | EmitInitialize(), 115 | EmitAttachmentAdd(), 116 | EmitSelectionChange(), 117 | EmitAttachmentRemove(), 118 | EmitBeforeInitialize(), 119 | ProcessEditorFocusAndBlur() 120 | ], 121 | model: { 122 | prop: 'srcContent', 123 | event: 'update' 124 | }, 125 | props: { 126 | /** 127 | * This prop will put the editor in read-only mode 128 | */ 129 | disabledEditor: { 130 | type: Boolean, 131 | required: false, 132 | default: function default$1 () { 133 | return false 134 | } 135 | }, 136 | /** 137 | * This is referenced `id` of the hidden input field defined. 138 | * It is optional and will be a random string by default. 139 | */ 140 | inputId: { 141 | type: String, 142 | required: false, 143 | default: function default$2 () { 144 | return '' 145 | } 146 | }, 147 | /** 148 | * This is referenced `name` of the hidden input field defined, 149 | * default value is `content`. 150 | */ 151 | inputName: { 152 | type: String, 153 | required: false, 154 | default: function default$3 () { 155 | return 'content' 156 | } 157 | }, 158 | /** 159 | * The placeholder attribute specifies a short hint 160 | * that describes the expected value of a editor. 161 | */ 162 | placeholder: { 163 | type: String, 164 | required: false, 165 | default: function default$4 () { 166 | return '' 167 | } 168 | }, 169 | /** 170 | * The source content is associcated to v-model directive. 171 | */ 172 | srcContent: { 173 | type: String, 174 | required: false, 175 | default: function default$5 () { 176 | return '' 177 | } 178 | }, 179 | /** 180 | * The boolean attribute allows saving editor state into browser's localStorage 181 | * (optional, default is `false`). 182 | */ 183 | localStorage: { 184 | type: Boolean, 185 | required: false, 186 | default: function default$6 () { 187 | return false 188 | } 189 | }, 190 | /** 191 | * Focuses cursor in the editor when attached to the DOM 192 | * (optional, default is `false`). 193 | */ 194 | autofocus: { 195 | type: Boolean, 196 | required: false, 197 | default: function default$7 () { 198 | return false 199 | } 200 | }, 201 | /** 202 | * Object to override default editor configuration 203 | */ 204 | config: { 205 | type: Object, 206 | required: false, 207 | default: function default$8 () { 208 | return {} 209 | } 210 | } 211 | }, 212 | mounted: function mounted () { 213 | var this$1 = this; 214 | 215 | /** Override editor configuration */ 216 | this.overrideConfig(this.config); 217 | /** Check if editor read-only mode is required */ 218 | this.decorateDisabledEditor(this.disabledEditor); 219 | this.$nextTick(function () { 220 | /** 221 | * If localStorage is enabled, 222 | * then load editor's content from the beginning. 223 | */ 224 | if (this$1.localStorage) { 225 | var savedValue = localStorage.getItem(this$1.storageId('VueTrix')); 226 | if (savedValue && !this$1.srcContent) { 227 | this$1.$refs.trix.editor.loadJSON(JSON.parse(savedValue)); 228 | } 229 | } 230 | }); 231 | }, 232 | data: function data () { 233 | return { 234 | editorContent: this.srcContent, 235 | isActived: null 236 | } 237 | }, 238 | methods: { 239 | handleContentChange: function handleContentChange (event) { 240 | this.editorContent = event.srcElement ? event.srcElement.value : event.target.value; 241 | this.$emit('input', this.editorContent); 242 | }, 243 | handleInitialize: function handleInitialize (event) { 244 | /** 245 | * If autofocus is true, manually set focus to 246 | * beginning of content (consistent with Trix behavior) 247 | */ 248 | if (this.autofocus) { 249 | this.$refs.trix.editor.setSelectedRange(0); 250 | } 251 | 252 | this.$emit('trix-initialize', this.emitInitialize); 253 | }, 254 | handleInitialContentChange: function handleInitialContentChange (newContent, oldContent) { 255 | newContent = newContent === undefined ? '' : newContent; 256 | 257 | if (this.$refs.trix.editor && this.$refs.trix.editor.innerHTML !== newContent) { 258 | /* Update editor's content when initial content changed */ 259 | this.editorContent = newContent; 260 | 261 | /** 262 | * If user are typing, then don't reload the editor, 263 | * hence keep cursor's position after typing. 264 | */ 265 | if (!this.isActived) { 266 | this.reloadEditorContent(this.editorContent); 267 | } 268 | } 269 | }, 270 | emitEditorState: function emitEditorState (value) { 271 | /** 272 | * If localStorage is enabled, 273 | * then save editor's content into storage 274 | */ 275 | if (this.localStorage) { 276 | localStorage.setItem( 277 | this.storageId('VueTrix'), 278 | JSON.stringify(this.$refs.trix.editor) 279 | ); 280 | } 281 | this.$emit('update', this.editorContent); 282 | }, 283 | storageId: function storageId (component) { 284 | if (this.inputId) { 285 | return (component + "." + (this.inputId) + ".content") 286 | } else { 287 | return (component + ".content") 288 | } 289 | }, 290 | reloadEditorContent: function reloadEditorContent (newContent) { 291 | // Reload HTML content 292 | this.$refs.trix.editor.loadHTML(newContent); 293 | 294 | // Move cursor to end of new content updated 295 | this.$refs.trix.editor.setSelectedRange(this.getContentEndPosition()); 296 | }, 297 | getContentEndPosition: function getContentEndPosition () { 298 | return this.$refs.trix.editor.getDocument().toString().length - 1 299 | }, 300 | decorateDisabledEditor: function decorateDisabledEditor (editorState) { 301 | /** Disable toolbar and editor by pointer events styling */ 302 | if (editorState) { 303 | this.$refs.trix.toolbarElement.style['pointer-events'] = 'none'; 304 | this.$refs.trix.contentEditable = false; 305 | this.$refs.trix.style['background'] = '#e9ecef'; 306 | } else { 307 | this.$refs.trix.toolbarElement.style['pointer-events'] = 'unset'; 308 | this.$refs.trix.style['pointer-events'] = 'unset'; 309 | this.$refs.trix.style['background'] = 'transparent'; 310 | } 311 | }, 312 | overrideConfig: function overrideConfig (config) { 313 | Trix.config = this.deepMerge(Trix.config, config); 314 | }, 315 | deepMerge: function deepMerge (target, override) { 316 | // deep merge the object into the target object 317 | for (var prop in override) { 318 | if (override.hasOwnProperty(prop)) { 319 | if (Object.prototype.toString.call(override[prop]) === '[object Object]') { 320 | // if the property is a nested object 321 | target[prop] = this.deepMerge(target[prop], override[prop]); 322 | } else { 323 | // for regular property 324 | target[prop] = override[prop]; 325 | } 326 | } 327 | } 328 | 329 | return target 330 | } 331 | }, 332 | computed: { 333 | /** 334 | * Compute a random id of hidden input 335 | * when it haven't been specified. 336 | */ 337 | generateId: function generateId () { 338 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 339 | var r = Math.random() * 16 | 0; 340 | var v = c === 'x' ? r : (r & 0x3 | 0x8); 341 | return v.toString(16) 342 | }) 343 | }, 344 | computedId: function computedId () { 345 | return this.inputId || this.generateId 346 | }, 347 | initialContent: function initialContent () { 348 | return this.srcContent 349 | }, 350 | isDisabled: function isDisabled () { 351 | return this.disabledEditor 352 | } 353 | }, 354 | watch: { 355 | editorContent: { 356 | handler: 'emitEditorState' 357 | }, 358 | initialContent: { 359 | handler: 'handleInitialContentChange' 360 | }, 361 | isDisabled: { 362 | handler: 'decorateDisabledEditor' 363 | }, 364 | config: { 365 | handler: 'overrideConfig', 366 | deep: true 367 | } 368 | } 369 | }; 370 | 371 | function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { 372 | if (typeof shadowMode !== 'boolean') { 373 | createInjectorSSR = createInjector; 374 | createInjector = shadowMode; 375 | shadowMode = false; 376 | } 377 | // Vue.extend constructor export interop. 378 | var options = typeof script === 'function' ? script.options : script; 379 | // render functions 380 | if (template && template.render) { 381 | options.render = template.render; 382 | options.staticRenderFns = template.staticRenderFns; 383 | options._compiled = true; 384 | // functional template 385 | if (isFunctionalTemplate) { 386 | options.functional = true; 387 | } 388 | } 389 | // scopedId 390 | if (scopeId) { 391 | options._scopeId = scopeId; 392 | } 393 | var hook; 394 | if (moduleIdentifier) { 395 | // server build 396 | hook = function (context) { 397 | // 2.3 injection 398 | context = 399 | context || // cached call 400 | (this.$vnode && this.$vnode.ssrContext) || // stateful 401 | (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional 402 | // 2.2 with runInNewContext: true 403 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { 404 | context = __VUE_SSR_CONTEXT__; 405 | } 406 | // inject component styles 407 | if (style) { 408 | style.call(this, createInjectorSSR(context)); 409 | } 410 | // register component module identifier for async chunk inference 411 | if (context && context._registeredComponents) { 412 | context._registeredComponents.add(moduleIdentifier); 413 | } 414 | }; 415 | // used by ssr in case component is cached and beforeCreate 416 | // never gets called 417 | options._ssrRegister = hook; 418 | } 419 | else if (style) { 420 | hook = shadowMode 421 | ? function (context) { 422 | style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot)); 423 | } 424 | : function (context) { 425 | style.call(this, createInjector(context)); 426 | }; 427 | } 428 | if (hook) { 429 | if (options.functional) { 430 | // register for functional component in vue file 431 | var originalRender = options.render; 432 | options.render = function renderWithStyleInjection(h, context) { 433 | hook.call(context); 434 | return originalRender(h, context); 435 | }; 436 | } 437 | else { 438 | // inject component registration as beforeCreate hook 439 | var existing = options.beforeCreate; 440 | options.beforeCreate = existing ? [].concat(existing, hook) : [hook]; 441 | } 442 | } 443 | return script; 444 | } 445 | 446 | var isOldIE = typeof navigator !== 'undefined' && 447 | /msie [6-9]\\b/.test(navigator.userAgent.toLowerCase()); 448 | function createInjector(context) { 449 | return function (id, style) { return addStyle(id, style); }; 450 | } 451 | var HEAD; 452 | var styles = {}; 453 | function addStyle(id, css) { 454 | var group = isOldIE ? css.media || 'default' : id; 455 | var style = styles[group] || (styles[group] = { ids: new Set(), styles: [] }); 456 | if (!style.ids.has(id)) { 457 | style.ids.add(id); 458 | var code = css.source; 459 | if (css.map) { 460 | // https://developer.chrome.com/devtools/docs/javascript-debugging 461 | // this makes source maps inside style tags work properly in Chrome 462 | code += '\n/*# sourceURL=' + css.map.sources[0] + ' */'; 463 | // http://stackoverflow.com/a/26603875 464 | code += 465 | '\n/*# sourceMappingURL=data:application/json;base64,' + 466 | btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) + 467 | ' */'; 468 | } 469 | if (!style.element) { 470 | style.element = document.createElement('style'); 471 | style.element.type = 'text/css'; 472 | if (css.media) 473 | { style.element.setAttribute('media', css.media); } 474 | if (HEAD === undefined) { 475 | HEAD = document.head || document.getElementsByTagName('head')[0]; 476 | } 477 | HEAD.appendChild(style.element); 478 | } 479 | if ('styleSheet' in style.element) { 480 | style.styles.push(code); 481 | style.element.styleSheet.cssText = style.styles 482 | .filter(Boolean) 483 | .join('\n'); 484 | } 485 | else { 486 | var index = style.ids.size - 1; 487 | var textNode = document.createTextNode(code); 488 | var nodes = style.element.childNodes; 489 | if (nodes[index]) 490 | { style.element.removeChild(nodes[index]); } 491 | if (nodes.length) 492 | { style.element.insertBefore(textNode, nodes[index]); } 493 | else 494 | { style.element.appendChild(textNode); } 495 | } 496 | } 497 | } 498 | 499 | /* script */ 500 | var __vue_script__ = script; 501 | 502 | /* template */ 503 | var __vue_render__ = function() { 504 | var _vm = this; 505 | var _h = _vm.$createElement; 506 | var _c = _vm._self._c || _h; 507 | return _c( 508 | "div", 509 | { class: [_vm.$style.trix_container] }, 510 | [ 511 | _c("trix-editor", { 512 | ref: "trix", 513 | class: ["trix-content"], 514 | attrs: { 515 | contenteditable: !_vm.disabledEditor, 516 | input: _vm.computedId, 517 | placeholder: _vm.placeholder 518 | }, 519 | on: { 520 | "trix-change": _vm.handleContentChange, 521 | "trix-file-accept": _vm.emitFileAccept, 522 | "trix-attachment-add": _vm.emitAttachmentAdd, 523 | "trix-attachment-remove": _vm.emitAttachmentRemove, 524 | "trix-selection-change": _vm.emitSelectionChange, 525 | "trix-initialize": _vm.handleInitialize, 526 | "trix-before-initialize": _vm.emitBeforeInitialize, 527 | "trix-focus": _vm.processTrixFocus, 528 | "trix-blur": _vm.processTrixBlur 529 | } 530 | }), 531 | _vm._v(" "), 532 | _c("input", { 533 | attrs: { type: "hidden", name: _vm.inputName, id: _vm.computedId }, 534 | domProps: { value: _vm.editorContent } 535 | }) 536 | ], 537 | 1 538 | ) 539 | }; 540 | var __vue_staticRenderFns__ = []; 541 | __vue_render__._withStripped = true; 542 | 543 | /* style */ 544 | var __vue_inject_styles__ = function (inject) { 545 | if (!inject) { return } 546 | inject("data-v-501b9774_0", { source: "\n.src-components-trix_container-5Bcy {\n max-width: 100%;\n height: auto;\n}\n.src-components-trix_container-5Bcy .src-components-trix-button-group-2D-J {\n background-color: white;\n}\n.src-components-trix_container-5Bcy .src-components-trix-content-1TD_ {\n background-color: white;\n}\n", map: {"version":3,"sources":["/home/hanh/side-projects/vue-trix/src/components/VueTrix.vue"],"names":[],"mappings":";AA2SA;EACA,eAAA;EACA,YAAA;AACA;AACA;EACA,uBAAA;AACA;AACA;EACA,uBAAA;AACA","file":"VueTrix.vue","sourcesContent":["\n\n\n\n\n"]}, media: undefined }); 547 | Object.defineProperty(this, "$style", { value: {"trix_container":"src-components-trix_container-5Bcy","trix-button-group":"src-components-trix-button-group-2D-J","trix-content":"src-components-trix-content-1TD_"} }); 548 | 549 | }; 550 | /* scoped */ 551 | var __vue_scope_id__ = undefined; 552 | /* module identifier */ 553 | var __vue_module_identifier__ = undefined; 554 | /* functional template */ 555 | var __vue_is_functional_template__ = false; 556 | /* style inject SSR */ 557 | 558 | /* style inject shadow dom */ 559 | 560 | 561 | 562 | var __vue_component__ = /*#__PURE__*/normalizeComponent( 563 | { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ }, 564 | __vue_inject_styles__, 565 | __vue_script__, 566 | __vue_scope_id__, 567 | __vue_is_functional_template__, 568 | __vue_module_identifier__, 569 | false, 570 | createInjector, 571 | undefined, 572 | undefined 573 | ); 574 | 575 | /* 576 | * Vue-Trix index.js 577 | * Author: tranduchanh.ms@gmail.com 578 | * Github: https://github.com/hanhdt/vue-trix 579 | */ 580 | Vue.config.ignoredElements = ['trix-editor']; 581 | 582 | Vue.component(__vue_component__.name, __vue_component__); 583 | 584 | export default __vue_component__; 585 | //# sourceMappingURL=vue-trix.esm.js.map 586 | -------------------------------------------------------------------------------- /dist/vue-trix.esm.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"vue-trix.esm.js","sources":["../src/mixins/EmitFileAccept.js","../src/mixins/EmitInitialize.js","../src/mixins/EmitAttachmentAdd.js","../src/mixins/EmitSelectionChange.js","../src/mixins/EmitAttachmentRemove.js","../src/mixins/EmitBeforeInitialize.js","../src/mixins/ProcessEditorFocusAndBlur.js","../src/components/VueTrix.vue","../node_modules/vue-runtime-helpers/dist/normalize-component.mjs","../node_modules/vue-runtime-helpers/dist/inject-style/browser.mjs","../src/index.js"],"sourcesContent":["/**\n *\n * @param {*} component\n */\nexport default function (component) {\n return {\n methods: {\n emitFileAccept (file) {\n this.$emit('trix-file-accept', file)\n }\n }\n }\n}\n","/**\n *\n * @param {*} component\n */\nexport default function (component) {\n return {\n methods: {\n emitInitialize (editor) {\n this.$emit('trix-initialize', this.$refs.trix.editor, event)\n }\n }\n }\n}\n","/**\n *\n * @param {*} component\n */\nexport default function (component) {\n return {\n methods: {\n emitAttachmentAdd (file) {\n this.$emit('trix-attachment-add', file)\n }\n }\n }\n}\n","/**\n *\n * @param {*} component\n */\nexport default function (component) {\n return {\n methods: {\n emitSelectionChange (event) {\n this.$emit('trix-selection-change', this.$refs.trix.editor, event)\n }\n }\n }\n}\n","/**\n *\n * @param {*} component\n */\nexport default function (component) {\n return {\n methods: {\n emitAttachmentRemove (file) {\n this.$emit('trix-attachment-remove', file)\n }\n }\n }\n}\n","/**\n *\n * @param {*} component\n */\nexport default function (component) {\n return {\n methods: {\n emitBeforeInitialize (event) {\n this.$emit('trix-before-initialize', this.$refs.trix.editor, event)\n }\n }\n }\n}\n","export default function (component) {\n return {\n methods: {\n processTrixFocus (event) {\n if (this.$refs.trix) {\n this.isActived = true\n this.$emit('trix-focus', this.$refs.trix.editor, event)\n }\n },\n processTrixBlur (event) {\n if (this.$refs.trix) {\n this.isActived = false\n this.$emit('trix-blur', this.$refs.trix.editor, event)\n }\n }\n }\n }\n}\n","\n\n\n\n\n","function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {\r\n if (typeof shadowMode !== 'boolean') {\r\n createInjectorSSR = createInjector;\r\n createInjector = shadowMode;\r\n shadowMode = false;\r\n }\r\n // Vue.extend constructor export interop.\r\n const options = typeof script === 'function' ? script.options : script;\r\n // render functions\r\n if (template && template.render) {\r\n options.render = template.render;\r\n options.staticRenderFns = template.staticRenderFns;\r\n options._compiled = true;\r\n // functional template\r\n if (isFunctionalTemplate) {\r\n options.functional = true;\r\n }\r\n }\r\n // scopedId\r\n if (scopeId) {\r\n options._scopeId = scopeId;\r\n }\r\n let hook;\r\n if (moduleIdentifier) {\r\n // server build\r\n hook = function (context) {\r\n // 2.3 injection\r\n context =\r\n context || // cached call\r\n (this.$vnode && this.$vnode.ssrContext) || // stateful\r\n (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional\r\n // 2.2 with runInNewContext: true\r\n if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {\r\n context = __VUE_SSR_CONTEXT__;\r\n }\r\n // inject component styles\r\n if (style) {\r\n style.call(this, createInjectorSSR(context));\r\n }\r\n // register component module identifier for async chunk inference\r\n if (context && context._registeredComponents) {\r\n context._registeredComponents.add(moduleIdentifier);\r\n }\r\n };\r\n // used by ssr in case component is cached and beforeCreate\r\n // never gets called\r\n options._ssrRegister = hook;\r\n }\r\n else if (style) {\r\n hook = shadowMode\r\n ? function (context) {\r\n style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot));\r\n }\r\n : function (context) {\r\n style.call(this, createInjector(context));\r\n };\r\n }\r\n if (hook) {\r\n if (options.functional) {\r\n // register for functional component in vue file\r\n const originalRender = options.render;\r\n options.render = function renderWithStyleInjection(h, context) {\r\n hook.call(context);\r\n return originalRender(h, context);\r\n };\r\n }\r\n else {\r\n // inject component registration as beforeCreate hook\r\n const existing = options.beforeCreate;\r\n options.beforeCreate = existing ? [].concat(existing, hook) : [hook];\r\n }\r\n }\r\n return script;\r\n}\n\nexport default normalizeComponent;\n//# sourceMappingURL=normalize-component.mjs.map\n","const isOldIE = typeof navigator !== 'undefined' &&\r\n /msie [6-9]\\\\b/.test(navigator.userAgent.toLowerCase());\r\nfunction createInjector(context) {\r\n return (id, style) => addStyle(id, style);\r\n}\r\nlet HEAD;\r\nconst styles = {};\r\nfunction addStyle(id, css) {\r\n const group = isOldIE ? css.media || 'default' : id;\r\n const style = styles[group] || (styles[group] = { ids: new Set(), styles: [] });\r\n if (!style.ids.has(id)) {\r\n style.ids.add(id);\r\n let code = css.source;\r\n if (css.map) {\r\n // https://developer.chrome.com/devtools/docs/javascript-debugging\r\n // this makes source maps inside style tags work properly in Chrome\r\n code += '\\n/*# sourceURL=' + css.map.sources[0] + ' */';\r\n // http://stackoverflow.com/a/26603875\r\n code +=\r\n '\\n/*# sourceMappingURL=data:application/json;base64,' +\r\n btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +\r\n ' */';\r\n }\r\n if (!style.element) {\r\n style.element = document.createElement('style');\r\n style.element.type = 'text/css';\r\n if (css.media)\r\n style.element.setAttribute('media', css.media);\r\n if (HEAD === undefined) {\r\n HEAD = document.head || document.getElementsByTagName('head')[0];\r\n }\r\n HEAD.appendChild(style.element);\r\n }\r\n if ('styleSheet' in style.element) {\r\n style.styles.push(code);\r\n style.element.styleSheet.cssText = style.styles\r\n .filter(Boolean)\r\n .join('\\n');\r\n }\r\n else {\r\n const index = style.ids.size - 1;\r\n const textNode = document.createTextNode(code);\r\n const nodes = style.element.childNodes;\r\n if (nodes[index])\r\n style.element.removeChild(nodes[index]);\r\n if (nodes.length)\r\n style.element.insertBefore(textNode, nodes[index]);\r\n else\r\n style.element.appendChild(textNode);\r\n }\r\n }\r\n}\n\nexport default createInjector;\n//# sourceMappingURL=browser.mjs.map\n","/*\n * Vue-Trix index.js\n * Author: tranduchanh.ms@gmail.com\n * Github: https://github.com/hanhdt/vue-trix\n */\n\nimport Vue from 'vue'\nimport VueTrix from './components/VueTrix.vue'\nVue.config.ignoredElements = ['trix-editor']\n\nVue.component(VueTrix.name, VueTrix)\n\nexport default VueTrix\n"],"names":["const","let","VueTrix"],"mappings":"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"} -------------------------------------------------------------------------------- /dist/vue-trix.min.js: -------------------------------------------------------------------------------- 1 | var VueTrix=function(e,t){"use strict";e=e&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e,t=t&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t;var n={name:"vue-trix",mixins:[{methods:{emitFileAccept:function(e){this.$emit("trix-file-accept",e)}}},{methods:{emitInitialize:function(){this.$emit("trix-initialize",this.$refs.trix.editor,event)}}},{methods:{emitAttachmentAdd:function(e){this.$emit("trix-attachment-add",e)}}},{methods:{emitSelectionChange:function(e){this.$emit("trix-selection-change",this.$refs.trix.editor,e)}}},{methods:{emitAttachmentRemove:function(e){this.$emit("trix-attachment-remove",e)}}},{methods:{emitBeforeInitialize:function(e){this.$emit("trix-before-initialize",this.$refs.trix.editor,e)}}},{methods:{processTrixFocus:function(e){this.$refs.trix&&(this.isActived=!0,this.$emit("trix-focus",this.$refs.trix.editor,e))},processTrixBlur:function(e){this.$refs.trix&&(this.isActived=!1,this.$emit("trix-blur",this.$refs.trix.editor,e))}}}],model:{prop:"srcContent",event:"update"},props:{disabledEditor:{type:Boolean,required:!1,default:function(){return!1}},inputId:{type:String,required:!1,default:function(){return""}},inputName:{type:String,required:!1,default:function(){return"content"}},placeholder:{type:String,required:!1,default:function(){return""}},srcContent:{type:String,required:!1,default:function(){return""}},localStorage:{type:Boolean,required:!1,default:function(){return!1}},autofocus:{type:Boolean,required:!1,default:function(){return!1}},config:{type:Object,required:!1,default:function(){return{}}}},mounted:function(){var t=this;this.overrideConfig(this.config),this.decorateDisabledEditor(this.disabledEditor),this.$nextTick(function(){if(t.localStorage){var e=localStorage.getItem(t.storageId("VueTrix"));e&&!t.srcContent&&t.$refs.trix.editor.loadJSON(JSON.parse(e))}})},data:function(){return{editorContent:this.srcContent,isActived:null}},methods:{handleContentChange:function(e){this.editorContent=e.srcElement?e.srcElement.value:e.target.value,this.$emit("input",this.editorContent)},handleInitialize:function(){this.autofocus&&this.$refs.trix.editor.setSelectedRange(0),this.$emit("trix-initialize",this.emitInitialize)},handleInitialContentChange:function(e){e=void 0===e?"":e,this.$refs.trix.editor&&this.$refs.trix.editor.innerHTML!==e&&(this.editorContent=e,this.isActived||this.reloadEditorContent(this.editorContent))},emitEditorState:function(){this.localStorage&&localStorage.setItem(this.storageId("VueTrix"),JSON.stringify(this.$refs.trix.editor)),this.$emit("update",this.editorContent)},storageId:function(e){return this.inputId?e+"."+this.inputId+".content":e+".content"},reloadEditorContent:function(e){this.$refs.trix.editor.loadHTML(e),this.$refs.trix.editor.setSelectedRange(this.getContentEndPosition())},getContentEndPosition:function(){return this.$refs.trix.editor.getDocument().toString().length-1},decorateDisabledEditor:function(e){e?(this.$refs.trix.toolbarElement.style["pointer-events"]="none",this.$refs.trix.contentEditable=!1,this.$refs.trix.style.background="#e9ecef"):(this.$refs.trix.toolbarElement.style["pointer-events"]="unset",this.$refs.trix.style["pointer-events"]="unset",this.$refs.trix.style.background="transparent")},overrideConfig:function(e){t.config=this.deepMerge(t.config,e)},deepMerge:function(e,t){for(var n in t)t.hasOwnProperty(n)&&("[object Object]"===Object.prototype.toString.call(t[n])?e[n]=this.deepMerge(e[n],t[n]):e[n]=t[n]);return e}},computed:{generateId:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})},computedId:function(){return this.inputId||this.generateId},initialContent:function(){return this.srcContent},isDisabled:function(){return this.disabledEditor}},watch:{editorContent:{handler:"emitEditorState"},initialContent:{handler:"handleInitialContentChange"},isDisabled:{handler:"decorateDisabledEditor"},config:{handler:"overrideConfig",deep:!0}}};function i(e,t,n,i,r,o,s,a,d,c){"boolean"!=typeof s&&(d=a,a=s,s=!1);var l,u="function"==typeof n?n.options:n;if(e&&e.render&&(u.render=e.render,u.staticRenderFns=e.staticRenderFns,u._compiled=!0,r&&(u.functional=!0)),i&&(u._scopeId=i),o?(l=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),t&&t.call(this,d(e)),e&&e._registeredComponents&&e._registeredComponents.add(o)},u._ssrRegister=l):t&&(l=s?function(e){t.call(this,c(e,this.$root.$options.shadowRoot))}:function(e){t.call(this,a(e))}),l)if(u.functional){var h=u.render;u.render=function(e,t){return l.call(t),h(e,t)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,l):[l]}return n}var d,c="undefined"!=typeof navigator&&/msie [6-9]\\b/.test(navigator.userAgent.toLowerCase());function r(e){return function(e,t){return function(e,t){var n=c?t.media||"default":e,i=l[n]||(l[n]={ids:new Set,styles:[]});if(!i.ids.has(e)){i.ids.add(e);var r=t.source;if(t.map&&(r+="\n/*# sourceURL="+t.map.sources[0]+" */",r+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(t.map))))+" */"),i.element||(i.element=document.createElement("style"),i.element.type="text/css",t.media&&i.element.setAttribute("media",t.media),void 0===d&&(d=document.head||document.getElementsByTagName("head")[0]),d.appendChild(i.element)),"styleSheet"in i.element)i.styles.push(r),i.element.styleSheet.cssText=i.styles.filter(Boolean).join("\n");else{var o=i.ids.size-1,s=document.createTextNode(r),a=i.element.childNodes;a[o]&&i.element.removeChild(a[o]),a.length?i.element.insertBefore(s,a[o]):i.element.appendChild(s)}}}(e,t)}}var l={};function o(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{class:[e.$style.trix_container]},[n("trix-editor",{ref:"trix",class:["trix-content"],attrs:{contenteditable:!e.disabledEditor,input:e.computedId,placeholder:e.placeholder},on:{"trix-change":e.handleContentChange,"trix-file-accept":e.emitFileAccept,"trix-attachment-add":e.emitAttachmentAdd,"trix-attachment-remove":e.emitAttachmentRemove,"trix-selection-change":e.emitSelectionChange,"trix-initialize":e.handleInitialize,"trix-before-initialize":e.emitBeforeInitialize,"trix-focus":e.processTrixFocus,"trix-blur":e.processTrixBlur}}),e._v(" "),n("input",{attrs:{type:"hidden",name:e.inputName,id:e.computedId},domProps:{value:e.editorContent}})],1)}var s=n,a=(o._withStripped=!0,i({render:o,staticRenderFns:[]},function(e){e&&(e("data-v-501b9774_0",{source:"\n.src-components-trix_container-5Bcy {\n max-width: 100%;\n height: auto;\n}\n.src-components-trix_container-5Bcy .src-components-trix-button-group-2D-J {\n background-color: white;\n}\n.src-components-trix_container-5Bcy .src-components-trix-content-1TD_ {\n background-color: white;\n}\n",map:{version:3,sources:["/home/hanh/side-projects/vue-trix/src/components/VueTrix.vue"],names:[],mappings:";AA2SA;EACA,eAAA;EACA,YAAA;AACA;AACA;EACA,uBAAA;AACA;AACA;EACA,uBAAA;AACA",file:"VueTrix.vue",sourcesContent:["\n\n\n\n\n","/*\n * Vue-Trix index.js\n * Author: tranduchanh.ms@gmail.com\n * Github: https://github.com/hanhdt/vue-trix\n */\n\nimport Vue from 'vue'\nimport VueTrix from './components/VueTrix.vue'\nVue.config.ignoredElements = ['trix-editor']\n\nVue.component(VueTrix.name, VueTrix)\n\nexport default VueTrix\n"],"names":["methods","emitFileAccept","file","this","$emit","emitInitialize","$refs","trix","editor","event","emitAttachmentAdd","emitSelectionChange","emitAttachmentRemove","emitBeforeInitialize","processTrixFocus","isActived","processTrixBlur","normalizeComponent","template","style","script","scopeId","isFunctionalTemplate","moduleIdentifier","shadowMode","createInjector","createInjectorSSR","createInjectorShadow","const","hook","options","render","staticRenderFns","_compiled","functional","_scopeId","context","$vnode","ssrContext","parent","__VUE_SSR_CONTEXT__","call","_registeredComponents","add","_ssrRegister","$root","$options","shadowRoot","originalRender","h","existing","beforeCreate","concat","HEAD","isOldIE","navigator","test","userAgent","toLowerCase","id","css","group","media","styles","ids","Set","has","let","code","source","map","sources","btoa","unescape","encodeURIComponent","JSON","stringify","element","document","createElement","type","setAttribute","undefined","head","getElementsByTagName","appendChild","push","styleSheet","cssText","filter","Boolean","join","index","size","textNode","createTextNode","nodes","childNodes","removeChild","length","insertBefore","addStyle","Vue","config","ignoredElements","component","VueTrix","name"],"mappings":"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"} -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanhdt/vue-trix/b0ab9a4504f56c0a15aa169746ad76090a7fd96c/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-trix 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | 28 | -------------------------------------------------------------------------------- /example/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanhdt/vue-trix/b0ab9a4504f56c0a15aa169746ad76090a7fd96c/example/src/assets/logo.png -------------------------------------------------------------------------------- /example/src/assets/vue-trix-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanhdt/vue-trix/b0ab9a4504f56c0a15aa169746ad76090a7fd96c/example/src/assets/vue-trix-editor.png -------------------------------------------------------------------------------- /example/src/assets/vue-trix-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanhdt/vue-trix/b0ab9a4504f56c0a15aa169746ad76090a7fd96c/example/src/assets/vue-trix-form.png -------------------------------------------------------------------------------- /example/src/assets/vue-trix-in-prod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanhdt/vue-trix/b0ab9a4504f56c0a15aa169746ad76090a7fd96c/example/src/assets/vue-trix-in-prod.png -------------------------------------------------------------------------------- /example/src/assets/vue-trix-simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanhdt/vue-trix/b0ab9a4504f56c0a15aa169746ad76090a7fd96c/example/src/assets/vue-trix-simple.png -------------------------------------------------------------------------------- /example/src/components/Editor.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 120 | 121 | 122 | 147 | -------------------------------------------------------------------------------- /example/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | 4 | const app = createApp(App); 5 | app.mount('#app') 6 | 7 | export default app; 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'json', 5 | 'vue' 6 | ], 7 | testEnvironment: 'jsdom', 8 | transform: { 9 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 10 | "^.+\\.vue$": ["vue-jest", { 11 | compilerOptions: { 12 | isCustomElement: tag => tag === 'trix-editor' 13 | } 14 | }], 15 | "^.+\\js$": "babel-jest" 16 | }, 17 | transformIgnorePatterns: [ 18 | '/node_modules/.*' 19 | ], 20 | moduleNameMapper: { 21 | '^@/(.*)$': '/src/$1', 22 | '\\.(css|less|scss)$': 'identity-obj-proxy' 23 | }, 24 | snapshotSerializers: [ 25 | 'jest-serializer-vue', 26 | '/jest/htmlSnapshotBeautifier.js' 27 | ], 28 | testMatch: [ 29 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 30 | ], 31 | collectCoverage: true, 32 | coverageReporters: ['html', 'text-summary'], 33 | collectCoverageFrom: [ 34 | 'src/**/*.{js,vue}', 35 | '!**/node_modules/**', 36 | '!src/index.js', 37 | '!src/mixins/**' 38 | ], 39 | globals: {}, 40 | } 41 | -------------------------------------------------------------------------------- /jest/htmlSnapshotBeautifier.js: -------------------------------------------------------------------------------- 1 | const snapshot = require('jest-snapshot') 2 | const beautify = require('js-beautify').html 3 | 4 | module.exports = { 5 | test (object) { 6 | return typeof object == 'string' && object[0] === '<' 7 | }, 8 | print (val, print, opts, colors) { 9 | return beautify(val, {}) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-trix", 3 | "version": "1.3.0", 4 | "description": "Lightweight and simple Trix rich-text editor component for Vue.js", 5 | "main": "dist/vue-trix.umd.js", 6 | "module": "dist/vue-trix.esm.js", 7 | "unpkg": "dist/vue-trix.min.js", 8 | "browser": { 9 | "./sfc": "src/components/VueTrix.vue" 10 | }, 11 | "files": [ 12 | "example/*", 13 | "dist/*", 14 | "src/*", 15 | "*.json", 16 | "*.js" 17 | ], 18 | "homepage": "https://github.com/hanhdt/vue-trix", 19 | "bugs": "https://github.com/hanhdt/vue-trix/issues", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/hanhdt/vue-trix.git" 23 | }, 24 | "author": { 25 | "name": "Hanh D. TRAN", 26 | "email": "tranduchanh.ms@gmail.com", 27 | "url": "https://hanhdt.github.io" 28 | }, 29 | "license": "MIT", 30 | "keywords": [ 31 | "vue", 32 | "trix", 33 | "text editor", 34 | "vue text editor", 35 | "js", 36 | "javascript" 37 | ], 38 | "scripts": { 39 | "build": "vue-cli-service build --target lib --name vue-trix ./src/index.js", 40 | "build:es": "rollup --config build/rollup.config.es.js", 41 | "build:unpkg": "rollup --config build/rollup.config.unpkg.js", 42 | "build:example": "vue-cli-service build ./example/src/main.js", 43 | "dev": "vue-cli-service serve ./example/src/main.js --open", 44 | "lint": "vue-cli-service lint --fix", 45 | "test:unit": "vue-cli-service test:unit" 46 | }, 47 | "dependencies": { 48 | "trix": "^1.3.1" 49 | }, 50 | "devDependencies": { 51 | "@vue/cli-plugin-babel": "^3.12.1", 52 | "@vue/cli-plugin-eslint": "^3.12.1", 53 | "@vue/cli-plugin-unit-jest": "^4.5.6", 54 | "@vue/cli-service": "^4.5.8", 55 | "@vue/compiler-dom": "^3.2.30", 56 | "@vue/test-utils": "^2.0.0-rc.18", 57 | "babel-core": "~7.0.0-bridge.0", 58 | "babel-eslint": "^10.0.1", 59 | "babel-jest": "^25.2.6", 60 | "identity-obj-proxy": "^3.0.0", 61 | "jest-serializer-vue": "^2.0.2", 62 | "jest-transform-stub": "^2.0.0", 63 | "js-beautify": "^1.13.0", 64 | "rollup": "^1.32.1", 65 | "rollup-plugin-alias": "^2.2.0", 66 | "rollup-plugin-babel": "^4.4.0", 67 | "rollup-plugin-buble": "^0.19.6", 68 | "rollup-plugin-commonjs": "^10.0.1", 69 | "rollup-plugin-node-resolve": "^5.2.0", 70 | "rollup-plugin-replace": "^2.2.0", 71 | "rollup-plugin-sass": "^1.2.2", 72 | "rollup-plugin-uglify": "^6.0.2", 73 | "rollup-plugin-vue": "^5.1.9", 74 | "sass-loader": "^10.0.3", 75 | "uglify-es": "^3.3.9", 76 | "vue": "^3.2.30", 77 | "vue-jest": "^5.0.0-alpha.10", 78 | "vue-template-compiler": "^2.6.12" 79 | }, 80 | "eslintConfig": { 81 | "root": true, 82 | "env": { 83 | "node": true 84 | }, 85 | "extends": [ 86 | "plugin:vue/essential" 87 | ], 88 | "rules": {}, 89 | "parserOptions": { 90 | "parser": "babel-eslint" 91 | } 92 | }, 93 | "postcss": { 94 | "plugins": { 95 | "autoprefixer": {} 96 | } 97 | }, 98 | "browserslist": [ 99 | "> 1%", 100 | "last 2 versions", 101 | "not ie <= 8" 102 | ] 103 | } 104 | -------------------------------------------------------------------------------- /src/components/VueTrix.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 298 | 299 | 311 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Vue-Trix index.js 3 | * Author: tranduchanh.ms@gmail.com 4 | * Github: https://github.com/hanhdt/vue-trix 5 | */ 6 | 7 | import VueTrix from './components/VueTrix.vue' 8 | 9 | const VueTrixPlugin = { 10 | install (app, options) { 11 | if (!options) { 12 | options = {}; 13 | } 14 | app.config.compilerOptions.isCustomElement = tag => tag === 'trix-editor' 15 | app.config.ignoredElements = ['trix-editor'] 16 | 17 | app.component('vue-trix', VueTrix); 18 | } 19 | }; 20 | 21 | export default VueTrixPlugin; 22 | -------------------------------------------------------------------------------- /src/mixins/EmitAttachmentAdd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {*} component 4 | */ 5 | export default function (component) { 6 | return { 7 | methods: { 8 | emitAttachmentAdd (file) { 9 | this.$emit('trix-attachment-add', file) 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/mixins/EmitAttachmentRemove.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {*} component 4 | */ 5 | export default function (component) { 6 | return { 7 | methods: { 8 | emitAttachmentRemove (file) { 9 | this.$emit('trix-attachment-remove', file) 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/mixins/EmitBeforeInitialize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {*} component 4 | */ 5 | export default function (component) { 6 | return { 7 | methods: { 8 | emitBeforeInitialize (event) { 9 | // this.$emit('trix-before-initialize', this.$refs.trix.editor, event) 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/mixins/EmitDroppedFile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {*} component 4 | */ 5 | export default function (component) { 6 | return { 7 | methods: { 8 | emitHandleFile (file) { 9 | this.$emit('trix-attachment-add', file) 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/mixins/EmitFileAccept.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {*} component 4 | */ 5 | export default function (component) { 6 | return { 7 | methods: { 8 | emitFileAccept (file) { 9 | this.$emit('trix-file-accept', file) 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/mixins/EmitInitialize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {*} component 4 | */ 5 | export default function (component) { 6 | return { 7 | methods: { 8 | emitInitialize (editor) { 9 | this.$emit('trix-initialize', this.$refs.trix.editor, event) 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/mixins/EmitSelectionChange.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {*} component 4 | */ 5 | export default function (component) { 6 | return { 7 | methods: { 8 | emitSelectionChange (event) { 9 | this.$emit('trix-selection-change', this.$refs.trix.editor, event) 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/mixins/ProcessEditorFocusAndBlur.js: -------------------------------------------------------------------------------- 1 | export default function (component) { 2 | return { 3 | methods: { 4 | processTrixFocus (event) { 5 | if (this.$refs.trix) { 6 | this.isActivated = true 7 | this.$emit('trix-focus', this.$refs.trix.editor, event) 8 | } 9 | }, 10 | processTrixBlur (event) { 11 | if (this.$refs.trix) { 12 | this.isActivated = false 13 | this.$emit('trix-blur', this.$refs.trix.editor, event) 14 | } 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | }, 5 | rules: { 6 | 'import/no-extraneous-dependencies': 'off' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/unit/VueTrix.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | shallowMount, 3 | mount 4 | } from '@vue/test-utils' 5 | import VueTrix from '../../src/components/VueTrix.vue' 6 | 7 | describe('VueTrix.vue', () => { 8 | it('renders valid elements', () => { 9 | const wrapper = mount(VueTrix) 10 | 11 | // assert the component is rendered 12 | expect(wrapper.getComponent(VueTrix).exists()).toBe(true) 13 | 14 | // assert the trix-editor is rendered 15 | expect(wrapper.find('trix-editor').exists()).toBe(true) 16 | expect(wrapper.find('trix-editor').attributes().input).toBeDefined() 17 | 18 | // assert the hidden input is rendered 19 | expect(wrapper.find('input').exists()).toBe(true) 20 | // expect(wrapper.find('input').attributes('id')).toBeDefined() 21 | }) 22 | 23 | it('has initial props', () => { 24 | const props = { 25 | inputId: 'inputId', 26 | inputName: 'content', 27 | placeholder: 'placeholder', 28 | srcContent: 'srcContent', 29 | localStorage: true, 30 | autofocus: true 31 | } 32 | 33 | const wrapper = shallowMount(VueTrix, { props }) 34 | 35 | // assert component props correctly 36 | Object.keys(props).forEach(key => { 37 | expect(wrapper.props()[key]).toBe(props[key]) 38 | }) 39 | }) 40 | 41 | it('has valid hidden input', () => { 42 | const wrapper = mount(VueTrix, { 43 | props: { 44 | inputId: 'inputId', 45 | inputName: 'content', 46 | srcContent: 'srcContent', 47 | placeholder: 'placeholder' 48 | } 49 | }) 50 | 51 | // get hidden input element 52 | const inputWrapper = wrapper.find('input') 53 | const inputEl = inputWrapper.element 54 | 55 | // assert hidden input attributes 56 | expect(inputEl.value).toEqual('srcContent') 57 | expect(inputEl.id).toEqual('inputId') 58 | expect(inputEl.name).toEqual('content') 59 | }) 60 | 61 | it('has valid trix-editor attributes', () => { 62 | const wrapper = mount(VueTrix, { 63 | props: { 64 | inputId: 'inputId', 65 | inputName: 'content', 66 | initContent: 'initContent', 67 | placeholder: 'placeholder' 68 | } 69 | }) 70 | 71 | // get trix-editor element 72 | const trixWrapper = wrapper.find('trix-editor') 73 | 74 | // assert attributes 75 | expect(trixWrapper.attributes().class).toBe('trix-content') 76 | expect(trixWrapper.attributes().role).toBe('textbox') 77 | expect(trixWrapper.attributes().placeholder).toBe('placeholder') 78 | }) 79 | 80 | it('works with v-model directive', () => { 81 | const wrapper = mount(VueTrix, { 82 | props: { 83 | srcContent: 'init content' 84 | } 85 | }) 86 | 87 | const inputWrapper = wrapper.find('input[type="hidden"]') 88 | 89 | // Has the connect starting value 90 | expect(wrapper.props().srcContent).toEqual('init content') 91 | expect(inputWrapper.element.value).toEqual('init content') 92 | 93 | // Sets the input to the correct value when props change 94 | wrapper.setProps({ srcContent: 'new content' }) 95 | expect(wrapper.vm.initialContent).toEqual('new content') 96 | }) 97 | 98 | it('overrides config', () => { 99 | const propsData = { 100 | config: { 101 | blockAttributes: { 102 | heading2: { 103 | tagName: 'h2', 104 | terminal: true, 105 | breakOnReturn: true, 106 | group: false 107 | }}}} 108 | 109 | 110 | const wrapper = shallowMount(VueTrix, { propsData }) 111 | 112 | // assert component props correctly 113 | expect(wrapper.props().config.blockAttributes.heading2.tagName).toBe('h2') 114 | }) 115 | 116 | }) 117 | --------------------------------------------------------------------------------