diff --git a/.babelrc b/.babelrc old mode 100644 new mode 100755 index e812394..1918387 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,10 @@ { - "presets": [ - ["env", { "modules": false }], - "stage-3" + presets: [ + [ + '@babel/preset-env', + { + modules: false + } + ] ] -} +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig old mode 100755 new mode 100644 index 6d9db03..e291365 --- a/.editorconfig +++ b/.editorconfig @@ -1,16 +1,9 @@ -# http://editorconfig.org root = true [*] +charset = utf-8 indent_style = space indent_size = 4 end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false - -[Makefile] -indent_style = tab +trim_trailing_whitespace = true diff --git a/.eslintignore b/.eslintignore index f1c3e28..0afc03d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,3 @@ # Created by .ignore support plugin (hsz.mobi) -src/markdown/font/*.js -src/doc.js -src/markdown/js/hljs.js +/config/ +/src/assets/ diff --git a/.eslintrc.json b/.eslintrc.json index 2e085f7..b397cd7 100755 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,22 +1,470 @@ { - "rules": {}, - "env": { - "es6": true, - "browser": true - }, - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module" - }, - "extends": [ - "eslint:recommended", - "plugin:vue/essential" - ], - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "plugins": [ - "vue" - ] -} \ No newline at end of file + "rules": {}, + "env": { + "es6": true, + "browser": true + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "extends": [ + "eslint:recommended", + "plugin:vue/essential" + ], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "plugins": [ + "vue" + ], + "rules": { + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + 2, + "single" + ], + "generator-star-spacing": 0, + "consistent-return": 0, + "no-alert": 2, + "no-caller": 2, + "no-debugger": 2, + "no-else-return": [ + 0 + ], + "no-nested-ternary": 0, + //禁止使用嵌套的三目运算 + "no-cond-assign": 2, + //禁止在条件表达式中使用赋值语句 + "arrow-parens": 0, + "no-class-assign": 2, + //禁止给类赋值 + "no-const-assign": 2, + //禁止修改const声明的变量 + "no-constant-condition": 2, + //禁止在条件中使用常量表达式 if(true) if(1) + "no-control-regex": 2, + //禁止在正则表达式中使用控制字符 + "no-debugger": 2, + //禁止使用debugger + "no-delete-var": 1, + //不能对var声明的变量使用delete操作符 + "no-div-regex": 1, + //不能使用看起来像除法的正则表达式/=foo/ + "no-dupe-keys": 2, + //在创建对象字面量时不允许键重复 {a:1,a:1} + "no-dupe-args": 2, + //函数参数不能重复 + "no-duplicate-case": 2, + //switch中的case标签不能重复 + "no-empty": 2, + //块语句中的内容不能为空 + "no-empty-character-class": 2, + //正则表达式中的[]内容不能为空 + "no-empty-label": 0, + //禁止使用空label + "no-eq-null": 2, + //禁止对null使用==或!=运算符 + "no-eval": 1, + //禁止使用eval + "no-ex-assign": 2, + //禁止给catch语句中的异常参数赋值 + "no-extra-parens": 0, + "no-extend-native": 2, + //禁止扩展native对象 + "no-extra-bind": 2, + //禁止不必要的函数绑定 + "no-extra-boolean-cast": 2, + //禁止不必要的bool转换 + "no-extra-semi": 2, + //禁止多余的冒号 + "no-fallthrough": 1, + //禁止switch穿透 + "no-floating-decimal": 2, + //禁止省略浮点数中的0 .5 3. + "no-func-assign": 2, + //禁止重复的函数声明 + "no-implicit-coercion": 1, + //禁止隐式转换 + "no-implied-eval": 2, + //禁止使用隐式eval + "no-inline-comments": 0, + //禁止行内备注 + "no-inner-declarations": [ + 2, + "functions" + ], + //禁止在块语句中使用声明(变量或函数) + "no-invalid-regexp": 2, + //禁止无效的正则表达式 + "no-invalid-this": 2, + //禁止无效的this,只能用在构造器,类,对象字面量 + "no-irregular-whitespace": 2, + //不能有不规则的空格 + "no-iterator": 2, + //禁止使用__iterator__ 属性 + "no-label-var": 2, + //label名不能与var声明的变量名相同 + "no-labels": 2, + //禁止标签声明 + "no-lone-blocks": 2, + //禁止不必要的嵌套块 + "no-lonely-if": 0, + //禁止else语句内只有if语句 + "no-loop-func": 1, + //禁止在循环中使用函数(如果没有引用外部变量不形成闭包就可以) + "no-mixed-requires": [ + 0, + false + ], + //声明时不能混用声明类型 + "no-mixed-spaces-and-tabs": [ + 0, + false + ], + //禁止混用tab和空格 + "no-multi-spaces": 1, + //不能用多余的空格 + "no-multi-str": 2, + //字符串不能用\换行 + "no-multiple-empty-lines": [ + 1, + { + "max": 2 + } + ], + //空行最多不能超过2行 + "no-native-reassign": 2, + //不能重写native对象 + "no-negated-in-lhs": 2, + //in 操作符的左边不能有! + "no-new": 0, + //禁止在使用new构造一个实例后不赋值 + "no-new-func": 1, + //禁止使用new Function + "no-new-object": 1, + //禁止使用new Object() + "no-new-require": 2, + //禁止使用new require + "no-new-wrappers": 1, + //禁止使用new创建包装实例,new String new Boolean new Number + "no-obj-calls": 2, + //不能调用内置的全局对象,比如Math() JSON() + "no-octal": 2, + //禁止使用八进制数字 + "no-octal-escape": 2, + //禁止使用八进制转义序列 + "no-param-reassign": 2, + //禁止给参数重新赋值 + "no-proto": 2, + //禁止使用__proto__属性 + "no-redeclare": 2, + //禁止重复声明变量 + "no-regex-spaces": 2, + //禁止在正则表达式字面量中使用多个空格 /foo bar/ + "no-return-assign": 1, + //return 语句中不能有赋值表达式 + "no-script-url": 0, + //禁止使用javascript:void(0) + "no-self-compare": 2, + //不能比较自身 + "no-sequences": 0, + //禁止使用逗号运算符 + "no-shadow": 2, + //外部作用域中的变量不能与它所包含的作用域中的变量或参数同名 + "no-shadow-restricted-names": 2, + //严格模式中规定的限制标识符不能作为声明时的变量名使用 + "no-spaced-func": 2, + //函数调用时 函数名与()之间不能有空格 + "no-sparse-arrays": 2, + //禁止稀疏数组, [1,,2] + "no-trailing-spaces": 1, + //一行结束后面不要有空格 + "no-throw-literal": 2, + //禁止抛出字面量错误 throw "error"; + "no-undef": 1, + //不能有未定义的变量 + "no-undef-init": 2, + //变量初始化时不能直接给它赋值为undefined + "no-undefined": 2, + //不能使用undefined + "no-unexpected-multiline": 2, + //避免多行表达式 + "no-underscore-dangle": 1, + //标识符不能以_开头或结尾 + "no-unneeded-ternary": 2, + //禁止不必要的嵌套 var isYes = answer === 1 ? true : false; + "no-unreachable": 2, + //不能有无法执行的代码 + "no-unused-expressions": 2, + //禁止无用的表达式 + "no-unused-vars": [ + 2, + { + "vars": "all", + "args": "after-used" + } + ], + //不能有声明后未被使用的变量或参数 + "no-use-before-define": 2, + //未定义前不能使用 + "no-useless-call": 2, + //禁止不必要的call和apply + "no-void": 2, + //禁用void操作符 + "no-var": 0, + //禁用var,用let和const代替 + "no-warning-comments": [ + 1, + { + "terms": [ + "todo", + "fixme", + "xxx" + ], + "location": "start" + } + ], + //不能有警告备注 + "no-with": 2, + //禁用with + "array-bracket-spacing": [ + 2, + "never" + ], + //是否允许非空数组里面有多余的空格 + "brace-style": [ + 1, + "1tbs" + ], + //大括号风格 + "callback-return": 0, + //避免多次调用回调什么的 + // "camelcase": 2,//强制驼峰法命名 + "comma-style": [ + 2, + "last" + ], + //逗号风格,换行时在行首还是行尾 + "complexity": [ + 0, + 11 + ], + //循环复杂度 + "computed-property-spacing": [ + 0, + "never" + ], + //是否允许计算后的键名什么的 + "consistent-this": [ + 2, + "that" + ], + //this别名 + "constructor-super": 0, + //非派生类不能调用super,派生类必须调用super + "curly": [ + 2, + "all" + ], + //必须使用 if(){} 中的{} + "default-case": 2, + //switch语句最后必须有default + "dot-location": 0, + //对象访问符的位置,换行的时候在行首还是行尾 + "dot-notation": [ + 0, + { + "allowKeywords": true + } + ], + //避免不必要的方括号 + "eol-last": 0, + //文件以单一的换行符结束 + "eqeqeq": 2, + //必须使用全等 + "func-names": 0, + //函数表达式必须有名字 + "func-style": [ + 0, + "declaration" + ], + //函数风格,规定只能使用函数声明/函数表达式 + "id-length": 0, + //变量名长度 + "indent": [ + 2, + 4 + ], + //缩进风格 + "init-declarations": 0, + //声明时必须赋初值 + "key-spacing": [ + 0, + { + "beforeColon": false, + "afterColon": false + } + ], + //对象字面量中冒号的前后空格 + "lines-around-comment": 0, + //行前/行后备注 + "max-depth": [ + 0, + 4 + ], + //嵌套块深度 + "max-len": [ + 0, + 80, + 4 + ], + //字符串最大长度 + "max-nested-callbacks": [ + 0, + 2 + ], + //回调嵌套深度 + "max-params": [ + 0, + 3 + ], + //函数最多只能有3个参数 + "max-statements": [ + 0, + 10 + ], + //函数内最多有几个声明 + "new-cap": 2, + //函数名首行大写必须使用new方式调用,首行小写必须用不带new方式调用 + "new-parens": 2, + //new时必须加小括号 + "newline-after-var": 0, + //变量声明后是否需要空一行 + "object-curly-spacing": [ + 0, + "never" + ], + //大括号内是否允许不必要的空格 + "object-shorthand": 0, + //强制对象字面量缩写语法 + "one-var": 0, + //连续声明 + "operator-assignment": [ + 0, + "always" + ], + //赋值运算符 += -=什么的 + "operator-linebreak": [ + 2, + "after" + ], + //换行时运算符在行尾还是行首 + "padded-blocks": 0, + //块语句内行首行尾是否要空行 + "prefer-const": 0, + //首选const + "prefer-spread": 0, + //首选展开运算 + "prefer-reflect": 0, + //首选Reflect的方法 + "quote-props": [ + 0, + "always" + ], + //对象字面量中的属性名是否强制双引号 + "radix": 2, + //parseInt必须指定第二个参数 + "id-match": 2, + //命名检测 + "require-yield": 0, + //生成器函数必须有yield + "semi": [ + 0, + "always" + ], + //语句强制分号结尾 + "semi-spacing": [ + 0, + { + "before": false, + "after": true + } + ], + //分号前后空格 + "sort-vars": 0, + //变量声明时排序 + "space-after-keywords": [ + 0, + "always" + ], + //关键字后面是否要空一格 + "space-before-blocks": [ + 0, + "always" + ], + //不以新行开始的块{前面要不要有空格 + "space-before-function-paren": [ + 0, + "always" + ], + //函数定义时括号前面要不要有空格 + "space-in-parens": [ + 0, + "never" + ], + //小括号里面要不要有空格 + "space-infix-ops": 0, + //中缀操作符周围要不要有空格 + "space-return-throw-case": 0, + //return throw case后面要不要加空格 + "space-unary-ops": [ + 0, + { + "words": true, + "nonwords": false + } + ], + //一元运算符的前/后要不要加空格 + "spaced-comment": 0, + //注释风格要不要有空格什么的 + "strict": 2, + //使用严格模式 + "use-isnan": 2, + //禁止比较时使用NaN,只能用isNaN() + "valid-jsdoc": 0, + //jsdoc规则 + "valid-typeof": 2, + //必须使用合法的typeof的值 + "vars-on-top": 2, + //var必须放在作用域顶部 + "wrap-iife": [ + 2, + "inside" + ], + //立即执行函数表达式的小括号风格 + "wrap-regex": 0, + //正则表达式字面量用小括号包起来 + "vue/name-property-casing": "off", + "vue/html-self-closing": "off", + "indent": "off", + "vue/script-indent": [ + "error", + 4, + { + "baseIndent": 1 + } + ], + "vue/html-indent": [ + "error", + 4, + { + "baseIndent": 1 + } + ] + } +} diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 88c0dd2..f3c5475 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,20 @@ -.DS_Store -node_modules/ -npm-debug.log -yarn-error.log -# Editor directories and files -.idea -*.suo -*.ntvs* -*.njsproj -*.sln -/build/ -.vscode/ +.cache/ +coverage/ +dist/ +!dist/index.html +node_modules/ +*.log + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + .vscode +.idea +/docs/.vuepress/dist/ diff --git a/.project b/.project deleted file mode 100644 index 430e89e..0000000 --- a/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - markdown-vue - - - - - - com.aptana.ide.core.unifiedBuilder - - - - - - com.aptana.projects.webnature - - - - 1529661132631 - - 26 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-false-false-node_modules - - - - diff --git a/App.vue b/App.vue new file mode 100755 index 0000000..b470046 --- /dev/null +++ b/App.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 11d705f..113becc --- a/README.md +++ b/README.md @@ -1,68 +1,127 @@ -# vue-Markdown编辑器 +## 简介 +一款使用marked和highlight.js开发的一款markdown编辑器,除常见markdown语法外,支持快捷输入、图片粘贴、代码复制、全屏编辑、预览等功能。 -[在线示例地址](https://zhaoxuhui1122.github.io/vue-markdown/) +使用起来简单方便,只需几行代码,即可在你的页面上引入一个markdown编辑器,编辑区支持像专业编辑器那样。 -GitHub :[https://github.com/zhaoxuhui1122/vue-markdown]( https://github.com/zhaoxuhui1122/vue-markdown) +编辑器涵盖了常用的markdown编辑器功能,可通过已有属性进行配置,对编辑器功能和样式进行基本的配置,也可根据需求进行深度定制。 +#### [项目地址](https://github.com/zhaoxuhui1122/vue-markdown) +#### [文档地址]([https://zhaoxuhui1122.github.io/vue-markdown-docs/) +**示例** +![image.png](https://upload-images.jianshu.io/upload_images/9390764-f6b0840eca057d61.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) -### 1.简介 +## 特点 +- 使用简单,只需要安装npm包,引入项目即可使用,不需要繁琐的初始化配置。 +- 方便扩展,根据实际需求,支持常见的功能配置,也可根据实际需求进行深度定制。 +- 体积小,加载速度快,npm包删除了highlight.js和codemirror里的依赖。 +- 灵活的主题,默认支持四种代码块风格,也可根据实际需求定制自己的主题样式 +- 功能强大,支持专业版的编辑器,使用codemirror实现编辑窗口,可识别markdown语法 +- 键盘事件监听,如保存、粘贴、回车时上次输入语法判断等 +- 可扩展性强,除了提供的属性配置编辑器,也可直接在原有组件基础上进行二次开发 -**一款使用marked和highlight.js开发的一款markdown编辑器,目前只支持在vue项目中使用。** +## 实现思路 -**编辑器涵盖了常用的markdown编辑器功能,工具栏可自定义配置,也可进行二次开发。** +通过监听文本输入区域内内容的变化,实时将输入的markdown语法进行编译,并渲染到预览区域。 +编辑器大致分为头部菜单栏、左侧内容输入区域、右侧预览区域三个部分。 +头部菜单主要为定自定义标题区域和菜单按钮,菜单按钮可通过配置文件进行显示和隐藏;左侧编辑区域,简单版使用textarea开发,满足基本需求, +专业版使用codemirror开发,编辑区域支持手动输入文本和通过头部菜单插入;右侧预览区域可实时预览输入文本,并可通过菜单按钮,进行编辑区域和预览区域的切换。 -**效果** -![image](http://smalleyes.oss-cn-shanghai.aliyuncs.com/WechatIMG586.png) - -### 2.安装 +## 安装方式 +### 使用npm安装 +1. 安装依赖 ``` npm i -S vue-meditor - -或 - -直接复制对应的组件到项目目录内 (推荐) ``` -### 3.在项目中使用 - - +### 将组件复制到项目内 +1. 将git仓库代码拉到本地 +``` +git clone https://github.com/zhaoxuhui1122/vue-markdown.git ``` -import MarkDown from 'vue-meditor' -... -components:{ - MarkDown -} -... +2. 复制src文件夹下内容至components文件夹下 +## 在项目使用 +#### npm包安装时 +简单版 +```js +import Markdown from 'vue-meditor' +``` +专业版 +```js +import { MarkdownPro } from 'vue-meditor' +``` +预览组件 +```js +import { MarkdownPreview } from 'vue-meditor' +``` +#### 复制组件到本地时(推荐) +简单版 +```js +import Markdown from '@/components/markdown/...'; +``` +专业版 +```js +import MarkdownPro from '@/components/markdown/pro'; +``` +预览组件 +```js +import MarkdownPreview from '@/components/markdown/preview'; +``` + +#### 在页面内使用 +```vue + + ``` +## API +### 编辑器基本属性 +#### value +- Type: `String/Number` +- Default: `''` -### 4.props +编辑器输入的文本,支持通过`v-dodel`数据双向绑定设置编辑器内容和获取编辑器的值。 -名称 | 类型|说明|默认值 ----|---|---|--- -initialValue|String|编辑器初始化内容 -width|Number|编辑器宽度| -height|Number|编辑器高度,单位 px|600 -theme|String|代码块主题配置,共有四个值,分别为Light、Dark、OneDark、GitHub|Light -autoSave|Boolean|是否自动保存|true -interval|Number|自动保存频率,单位毫秒|10000 -toolbars|Object|工具栏配置,具体功能详见工具栏功能配置表 -exportFileName|String|导出文件的名称|未命名文件 +#### width +- Type: `String/Number` +- Default: `auto` -### 5.events +编辑器的初始化宽度。 -名称 | 说明 ----|--- -on-save|自动保存或者手动保存时触发,返回当前编辑器内原始输入内容和转以后的内容 -on-paste-image|粘贴图片,返回当前粘贴的file文件 -### 6.工具栏配置 +#### height +- Type: `Number` +- Default: `600` -名称 | 说明 | 默认显示 +编辑器的初始化高度。 + +#### bordered +- Type: `Boolean` +- Default: `true` + +编辑器是否含有边框。 + +#### toolbars +- Type: `Object` +- Default: `参见下表` + +头部菜单按钮,通过设置true or false控制决定是否显示,目前配置支持持控制按钮显示隐藏,后续将支持根据配置显示排列顺序。 + +名称 | 说明 | 默认是否显示 ---|---|--- strong|粗体|是 italic|斜体|是 @@ -83,74 +142,286 @@ image|image|是 table |表格|是 checked|已完成列表|是 notChecked |未完成列表|是 -shift|预览|是 +preview|预览|是 +split|分屏模式切换|是 print |打印|否 theme|主题切换|是 fullscreen |全屏|是 exportmd|导出为*.md文件|是 importmd|导入本地*.md文件|是 +save|保存按钮|否 +clear|清空内容|否 -### 7.其他说明 -**关于保存时返回值** +#### theme +- Type: `String` +- Default: `light` + +编辑器代码块主题,目前支持`light`、`dark`、`oneDark`、`gitHub`四种代码块风格,可通过自定义theme并修改样式文件进行主题定制。 + +自定义theme时,预览区域的会增加一个为`markdown-theme-[theme]`的`class`。 + + +#### autoSave +- Type: `Boolean` +- Default: `false` + +是否开启自动保存,设置为开启时可通过绑定`on-save`事件获取编辑器内的值和代码块主题。 +```vue + ``` - value // 编辑器输入的原始内容 - html // 右侧现实的问转义后的内容 - theme // 保存时的主题名字 +```js + handleOnSave({value, theme}){ + console.log(value, theme); + } ``` -**标题配置** +#### interval +- Type: `Number` +- Default: `10000` +自动保存间隔时间,单位:`mm`,默认10000mm,需要`autoSave = true`时才有效。 + +#### exportFileName +- Type: `String` +- Default: `unnamed` + +导出的md文件名称,默认unnamed.md。 + +#### markedOptions +- Type: `Object` +- Default: `{}` + +marked配置项,可以根据需求自定义。 + +```vue + ``` -支持配置编辑器名称,提供了name=title的slot插槽 -``` +#### isPreview +- Type: `Boolean` +- Default: `false` + +是否是预览模式,开启时可作为一个预览组件使用,与预览组件功能一致。 + +#### copyCode +- Type: `Boolean` +- Default: `true` + +是否支持复制代码块内的内容。 + +#### copyBtnText +- Type: `String` +- Default: `复制代码` + +复制代码按钮显示文字。 -**工具栏配置** +### 预览组件基本属性 +#### initialValue +- Type: `String/Number` +- Default: `''` -``` -// 例: -const config = { - print:false // 隐藏掉打印功能 +预览组件初始化内容,支持动态更新。 + +#### theme +- Type: `String` +- Default: `light` + +代码块主题,与编辑器编辑器代码块主题一致。 + +#### markedOptions +- Type: `Object` +- Default: `{}` + +marked配置项,与编辑器内该配置一致。 + +#### copyCode +- Type: `Boolean` +- Default: `true` + +是否支持复制代码块内的内容。 + + +#### copyBtnText +- Type: `String` +- Default: `复制代码` + +复制代码按钮显示文字。 +### on-ready +编辑器初始化完成时触发,返回值为`Object`,包含组件本身和`insertContent`方法。 + +#### on-save +编辑器保存事件,自动保存或者手动保存时触发,支持`ctrl+s`或`command+s`触发保存,返回值类型为`Object`,包含当前输入值`value`和选择的代码块主题`theme`。 + + +#### on-paste-image + +监听编辑器粘贴图片事件,在编辑区域内手动粘贴图片时触发,可用于支持粘贴插入图片文件,返回`file`文件,上传文件后可结合`on-ready`事件内返回的`insertContent`插入图片。 + +#### on-copy +复制代码块内容,触发时返回当前代码块的text,copyCode开启时才有效。 + +## 二次开发 +### 粘贴插入图片 + +`on-paste-image`虽然可以支持图片粘贴事件的监听,但不会处理图片上传至服务器并将链接插入编辑器这段逻辑。 + +目前如果想要支持粘贴插入图片,需要在`on-paste-image`方法里上传图片文件,拿到图片地址后,使用`on-ready`方法里返回的insertContent方法插入图片。 + +上述操作显得过于复杂,可以直接在源码里扩展mixins里的`handlePaste`方法,图片上传完成后,直接调用`this.insertContent`方法插入图片。 + +修改`/markdown/mixins/common.js` + +```js +handlePaste(_, e) {// 粘贴图片 + const { clipboardData = {} } = e; + const { types = [], items } = clipboardData; + let item = null; + for (let i = 0; i < types.length; i++) { + if (types[i] === 'Files') { + item = items[i]; + break; + } + } + if (item) { + const file = item.getAsFile(); + if (/image/gi.test(file.type)) { + e.preventDefault(); + // 1.上传操作 + // 2.插入图片 this.insertContent(`![image](imgUrl)`) + } + } } - ``` -**优化代码体积** +### 支持流程图、甘特图等语法 +目前编辑器只支持常见code语法,如果需要实现如流程图等功能,需要进一步扩展,以实现一个简单的流程图为例,具体实现思路如下: -``` -项目中为了达到代码高亮显示,需要用到highlight.js, -由于highlight.js体积过于庞大,项目中按需加载了部分常用的程序语言, -可根据需求自行配置,配置目录位于/markdown/js/hljs内 +默认情况下,markedjs会使用renderer.code方法对输入的代码块进行解析,并会借助`highlight.js`支持语法高亮。 +可以将流程图语法输入到代码块内,并标明语言,重写marked.Renderer的code解析方法,结合结合`flowchart.js`路程图代码进行解析,返回文本内容。 + +修改`/markdown/libs/js/simple.js + +```js +import hljs from './hljs'; +import index from 'index'; +import {parse} from 'flowchart.js' + +hljs.initHighlightingOnLoad(); + +const renderer = new index.Renderer(); +renderer.code = (code, language) => { + if (language === 'flow') {// 流程图 + const dom = document.createElement('div'); + const flowchart = parse(code); + flowchart.drawSVG(dom, {/*相关配置*/}); + return dom.innerHTML; + } else {// 默认解析 + return `
${hljs.highlightAuto(code).value}
` + } +} +export default index.setOptions({ + renderer, + gfm: true, + tables: true, + breaks: false, + pedantic: false, + sanitize: false, + smartLists: true, + highlight: function (code) { + return hljs.highlightAuto(code).value; + } +}) ``` +### 自定义markdown语法转换 -### 更新日志 -**1.3.0** -- 支持配置marked的markedOptions,感谢[dkvirus](https://github.com/dkvirus)提出的[issues#12](https://github.com/zhaoxuhui1122/vue-markdown/issues/12)和具体的解决办法 +项目内使用的`index.js均为其默认配置功能,如需要特殊转换,可重写其内部的解析方法,即重写其renderer相关方法 +[参考文档](https://github.com/markedjs/marked/blob/master/docs/USING_PRO.md)。 -**1.2.1** -- 支持theme、width、heigh动态切换 +## 自动生成文档目录 -**v1.2.0** -- 优化代码体积,按需加载highlight.js,较少了三分之二的代码体积 -- 新增图片粘贴功能 -- 增加图片预览功能 -- 修复部分bug +预览区域和文档预览组件暂不支持自动生成目录,实现自动生成目录思路目前想到的大致有 +- 重写`renderer.heading` 方法,为生成的标题添加id,输入特定快捷键,如`[TOC]`时,查找预览区域内的的所有标题标签,分析等级关系,生成目录标签 -**v0.9.3** +### icon替换 +项目内所有的icon和命名参考`/assets/font/index.html`,替换时需注意,预览区域的checkbox为icon,注意一并替换, +修改`/assets/css/index.less`内的`input[type="checkbox"]`的`:after`样式。 -- 解决初始化值initialValue无法动态改变的问题 -- 修改了打包配置 - -**v0.8.0** - -- 新增md文件导出和读取功能 -- 修改预览部分样式 -- 修改头部菜单样式 - -**v0.7.0** - -- 修复主题无法更新的问题 -- 修复文档初始化值无法动态切换的问题 +## 代码体积优化 +### 公共代码提取 +npm包构建时,三个组件完全独立,没有抽离公共文件,所以,当同一个项目内引入其中的两个或三个组件都引入时,存在一定的重复代码, +主要为`highlight.js`、`marked`、`iconfont`、css样式几个部分。 + +解决方式:将组件复制到本地项目,打包时将这些文件作为公共文件抽离出来。 + +**注意**:三个组件中使用的iconfont为同一套,如果只是单纯的使用`preview`组件, +将会引入整个项目所使用的iconfont,可删除iconfont的引入, +重写`input[type="checkbox"]`的样式,preview组件体积将会减少一半,样式文件位于`markdown/assets/css/index.less`。 + +### codemirror体积优化 +codemirror主要分为主文件、mode相关文件和样式文件,主文件体积异常的大,mode文件目前只选用了css/jsvascript/markdown/meta/xml五个文件, +其中markdown.js和meta.js为必须引用的,项目中已将常见的编程语言代码风格定义为css/js/xml之一,例如less/sass/scss按照css规则解析,html/vue按照xml规则解析。 +优化可从一下方面入手 +- 减少codemirror主文件体积 +- 减少引用的mode依赖 + +### highlight.js体积优化 + +highlight.js原本体积也是较大的,主要原因为,编译时为支持各种代码语言,引入了相应的解析文件, +项目内已根据常见的代码语言进行了一次筛选,进行按需引入,可根据自身需求,再次对引用文件进行删减 + +参见`src/markdown/libs/js/hljs.js`,目前支持的语言有 +```js +import hljs from 'highlight.js/lib/highlight' + +import javascript from 'highlight.js/lib/languages/javascript' +import java from 'highlight.js/lib/languages/java'; +import css from 'highlight.js/lib/languages/css'; +import less from 'highlight.js/lib/languages/less'; +import go from 'highlight.js/lib/languages/go'; +import markdown from src; +import php from 'highlight.js/lib/languages/php'; +import python from 'highlight.js/lib/languages/python'; +import ruby from 'highlight.js/lib/languages/ruby'; +import stylus from 'highlight.js/lib/languages/stylus'; +import typescript from 'highlight.js/lib/languages/typescript'; +import xml from 'highlight.js/lib/languages/xml'; + +const languages = { + javascript, + java, + css, + less, + markdown, + go, + php, + python, + ruby, + stylus, + typescript, + xml +} +Object.keys(languages).forEach(key => { + hljs.registerLanguage(key, languages[key]) +}) + +export default hljs; +``` + +### 专业版编辑器codemirror/simple.js +优化思路:无 +#### iconfont 体积优化 +只需要preview组件时,避免引入所有icon,参考功能扩展里icon替换方法。 + +## 升级路线 +- 普通版编辑器对选中文本进行操作功能 +- 文档目录功能 +- 优化专业版编辑器体积 +- react版开发 +- ... + +## 问题反馈 + +对于功能上的缺陷、使用方法和希望扩展的功能,可以提 [Issues](https://github.com/zhaoxuhui1122/vue-markdown/issues)。 diff --git a/config/webpack.base.config.js b/config/webpack.base.config.js new file mode 100644 index 0000000..3e097f3 --- /dev/null +++ b/config/webpack.base.config.js @@ -0,0 +1,67 @@ +const path = require('path'); +const VueLoaderPlugin = require('vue-loader/lib/plugin'); +const resolve = dir => path.resolve(__dirname, dir) + +const config = { + entry: resolve('../main.js'), + output: { + path: resolve('../dist/'), + filename: 'simple.js' + }, + module: { + rules: [{ + test: /\.(js|vue)$/, + loader: 'eslint-loader', + enforce: 'pre', + include: [resolve('src'), resolve('test')] + }, { + test: /\.vue$/, + loader: 'vue-loader' + }, + { + test: /\.js$/, + use: 'babel-loader', + exclude: /node_modules/ + }, + { + test: /\.(css|less)$/, + use: [ + 'vue-style-loader', + 'css-loader', + 'less-loader' + ] + }, + { + test: /\.(jpg|png|jpeg)$/, + use: { + loader: 'url-loader', + options: { + // placeholder + name: '[name]_[hash].[ext]', + outputPath: 'img/', + limit: 4096 + } + } + }, + { + test: /\.(woff2?|eot|ttf|otf|dtd|svg)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10240, + name: 'fonts/[name].[hash:7].[ext]' + } + } + ] + }, + resolve: { + alias: { + 'vue$': 'vue/dist/vue.esm.js' + }, + extensions: ['*', '.js', '.vue', '.json'] + }, + plugins: [ + new VueLoaderPlugin() + ] +}; + +module.exports = config; diff --git a/config/webpack.config.js b/config/webpack.config.js new file mode 100644 index 0000000..f141a00 --- /dev/null +++ b/config/webpack.config.js @@ -0,0 +1,45 @@ +const {CleanWebpackPlugin} = require('clean-webpack-plugin'); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const ProgressBarPlugin = require('progress-bar-webpack-plugin'); +const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer'); +const CopyPlugin = require('copy-webpack-plugin'); +const path = require('path'); +const merge = require('webpack-merge'); +const baseConfig = require('./webpack.base.config'); + +const resolve = dir => path.resolve(__dirname, dir); +const analyzerPlugins = process.env.analyzer==='1'?[new BundleAnalyzerPlugin({analyzerPort:5555})]:[]; + +const devConfig = { + mode: 'production', + entry: { + simple: resolve('../src/simple.js'), + pro: resolve('../src/pro.js'), + preview: resolve('../src/preview.js') + }, + output: { + path: resolve('../dist/'), + filename: '[name].js', + libraryTarget: 'umd', + libraryExport: 'default', + library: 'makdown', + umdNamedDefine: true, + globalObject: 'typeof self !== \'undefined\' ? self : this' + }, + plugins: [ + ...analyzerPlugins, + new CleanWebpackPlugin(), + new ProgressBarPlugin({ + width: 100, + clear: false + }), + new CopyPlugin([ + {from: resolve('../index.js'), to: resolve('../dist/')} + ]), + ], + performance: { + hints: false + } +}; + +module.exports = merge(baseConfig, devConfig); diff --git a/config/webpack.dev.config.js b/config/webpack.dev.config.js new file mode 100644 index 0000000..3134670 --- /dev/null +++ b/config/webpack.dev.config.js @@ -0,0 +1,29 @@ +const webpack = require('webpack'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +const path = require('path'); +const merge = require('webpack-merge'); +const baseConfig = require('./webpack.base.config'); +const resolve = dir => path.resolve(__dirname, dir) + +const devConfig = { + mode: 'development', + devtool: 'cheap-module-eval-source-map', + devServer: { + // open: true, + hot: true, + hotOnly: true + }, + resolve: { + extensions: ['.js', '.vue', '.json'], + alias: { + 'vue$': 'vue/dist/vue.esm.js' + } + }, + plugins: [ + new HtmlWebpackPlugin({ + template: resolve('../index.dev.html') + }), + new webpack.HotModuleReplacementPlugin() + ] +}; +module.exports = merge(baseConfig, devConfig); diff --git a/dist/build.js b/dist/build.js deleted file mode 100644 index 3a0b4a8..0000000 --- a/dist/build.js +++ /dev/null @@ -1,6 +0,0 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("markdown-vue",[],t):"object"==typeof exports?exports["markdown-vue"]=t():e["markdown-vue"]=t()}("undefined"!=typeof self?self:this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="/dist/",t(t.s=8)}([function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t){function n(e,t){var n=e[1]||"",i=e[3];if(!i)return n;if(t&&"function"==typeof btoa){var o=r(i);return[n].concat(i.sources.map(function(e){return"/*# sourceURL="+i.sourceRoot+e+" */"})).concat([o]).join("\n")}return[n].join("\n")}function r(e){return"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(e))))+" */"}e.exports=function(e){var t=[];return t.toString=function(){return this.map(function(t){var r=n(t,e);return t[2]?"@media "+t[2]+"{"+r+"}":r}).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},i=0;i1)for(var n=1;nn.parts.length&&(r.parts.length=n.parts.length)}else{for(var a=[],i=0;i=0&&Math.floor(t)===t&&isFinite(e)}function h(e){return o(e)&&"function"==typeof e.then&&"function"==typeof e.catch}function m(e){return null==e?"":Array.isArray(e)||d(e)&&e.toString===qo?JSON.stringify(e,null,2):String(e)}function v(e){var t=parseFloat(e);return isNaN(t)?e:t}function g(e,t){for(var n=Object.create(null),r=e.split(","),i=0;i-1)return e.splice(n,1)}}function y(e,t){return Qo.call(e,t)}function _(e){var t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}function w(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n}function x(e,t){return e.bind(t)}function k(e,t){t=t||0;for(var n=e.length-t,r=new Array(n);n--;)r[n]=e[n+t];return r}function E(e,t){for(var n in t)e[n]=t[n];return e}function N(e){for(var t={},n=0;n-1)if(a&&!y(o,"default"))s=!1;else if(""===s||s===ea(t)){var c=de(String,o.type);(c<0||l0&&(r=Te(r,(t||"")+"_"+n),Me(r[0])&&Me(c)&&(u[s]=I(c.text+r[0].text),r.shift()),u.push.apply(u,r)):l(r)?Me(c)?u[s]=I(c.text+r):""!==r&&u.push(I(r)):Me(r)&&Me(c)?u[s]=I(c.text+r.text):(a(e._isVList)&&o(r.tag)&&i(r.key)&&o(t)&&(r.key="__vlist"+t+"_"+n+"__"),u.push(r)));return u}function $e(e){var t=e.$options.provide;t&&(e._provided="function"==typeof t?t.call(e):t)}function je(t){var n=Re(t.$options.inject,t);n&&(V(!1),Object.keys(n).forEach(function(r){"production"!==e.env.NODE_ENV?H(t,r,n[r],function(){Aa('Avoid mutating an injected value directly since the changes will be overwritten whenever the provided component re-renders. injection being mutated: "'+r+'"',t)}):H(t,r,n[r])}),V(!0))}function Re(t,n){if(t){for(var r=Object.create(null),i=Ca?Reflect.ownKeys(t):Object.keys(t),o=0;o0,o=e?!!e.$stable:!i,a=e&&e.$key;if(e){if(e._normalized)return e._normalized;if(o&&n&&n!==Ko&&a===n.$key&&!i&&!n.$hasNormal)return n;r={};for(var s in e)e[s]&&"$"!==s[0]&&(r[s]=ze(t,s,e[s]))}else r={};for(var l in t)l in r||(r[l]=Pe(t,l));return e&&Object.isExtensible(e)&&(e._normalized=r),M(r,"$stable",o),M(r,"$key",a),M(r,"$hasNormal",i),r}function ze(e,t,n){var r=function(){var e=arguments.length?n.apply(null,arguments):n({});return e=e&&"object"==typeof e&&!Array.isArray(e)?[e]:Se(e),e&&(0===e.length||1===e.length&&e[0].isComment)?void 0:e};return n.proxy&&Object.defineProperty(e,t,{get:r,enumerable:!0,configurable:!0}),r}function Pe(e,t){return function(){return e[t]}}function Be(e,t){var n,r,i,a,s;if(Array.isArray(e)||"string"==typeof e)for(n=new Array(e.length),r=0,i=e.length;rNs)){Aa("You may have an infinite update loop "+(t.user?'in watcher with expression "'+t.expression+'"':"in a component render function."),t.vm);break}var r=Cs.slice(),i=Os.slice();jt(),Vt(r),It(i),Oa&&sa.devtools&&Oa.emit("flush")}function It(e){for(var t=e.length;t--;){var n=e[t],r=n.vm;r._watcher===n&&r._isMounted&&!r._isDestroyed&&$t(r,"updated")}}function Lt(e){e._inactive=!1,Cs.push(e)}function Vt(e){for(var t=0;tTs&&Os[r].id>t.id;)r--;Os.splice(r+1,0,t)}else Os.push(t);if(!Ss){if(Ss=!0,"production"!==e.env.NODE_ENV&&!sa.async)return void Rt();we(Rt)}}}function Pt(e,t,n){Vs.get=function(){return this[t][n]},Vs.set=function(e){this[t][n]=e},Object.defineProperty(e,n,Vs)}function Bt(e){e._watchers=[];var t=e.$options;t.props&&Ht(e,t.props),t.methods&&Qt(e,t.methods),t.data?Ut(e):B(e._data={},!0),t.computed&&Kt(e,t.computed),t.watch&&t.watch!==_a&&Wt(e,t.watch)}function Ht(t,n){var r=t.$options.propsData||{},i=t._props={},o=t.$options._propKeys=[],a=!t.$parent;a||V(!1);for(var s in n)!function(s){o.push(s);var l=oe(s,n,r,t);if("production"!==e.env.NODE_ENV){var c=ea(s);(Zo(c)||sa.isReservedAttr(c))&&Aa('"'+c+'" is a reserved attribute and cannot be used as component prop.',t),H(i,s,l,function(){a||Es||Aa("Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: \""+s+'"',t)})}else H(i,s,l);s in t||Pt(t,"_props",s)}(s);V(!0)}function Ut(t){var n=t.$options.data;n=t._data="function"==typeof n?Ft(n,t):n||{},d(n)||(n={},"production"!==e.env.NODE_ENV&&Aa("data functions should return an object:\nhttps://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function",t));for(var r=Object.keys(n),i=t.$options.props,o=t.$options.methods,a=r.length;a--;){var s=r[a];"production"!==e.env.NODE_ENV&&o&&y(o,s)&&Aa('Method "'+s+'" has already been defined as a data property.',t),i&&y(i,s)?"production"!==e.env.NODE_ENV&&Aa('The data property "'+s+'" is already declared as a prop. Use prop default value instead.',t):S(s)||Pt(t,"_data",s)}B(n,!0)}function Ft(e,t){j();try{return e.call(t,t)}catch(e){return ve(e,t,"data()"),{}}finally{R()}}function Kt(t,n){var r=t._computedWatchers=Object.create(null),i=Na();for(var o in n){var a=n[o],s="function"==typeof a?a:a.get;"production"!==e.env.NODE_ENV&&null==s&&Aa('Getter is missing for computed property "'+o+'".',t),i||(r[o]=new Ls(t,s||O,O,zs)),o in t?"production"!==e.env.NODE_ENV&&(o in t.$data?Aa('The computed property "'+o+'" is already defined in data.',t):t.$options.props&&o in t.$options.props&&Aa('The computed property "'+o+'" is already defined as a prop.',t)):qt(t,o,a)}}function qt(t,n,r){var i=!Na();"function"==typeof r?(Vs.get=i?Gt(n):Zt(r),Vs.set=O):(Vs.get=r.get?i&&!1!==r.cache?Gt(n):Zt(r.get):O,Vs.set=r.set||O),"production"!==e.env.NODE_ENV&&Vs.set===O&&(Vs.set=function(){Aa('Computed property "'+n+'" was assigned to but it has no setter.',this)}),Object.defineProperty(t,n,Vs)}function Gt(e){return function(){var t=this._computedWatchers&&this._computedWatchers[e];if(t)return t.dirty&&t.evaluate(),La.target&&t.depend(),t.value}}function Zt(e){return function(){return e.call(this,this)}}function Qt(t,n){var r=t.$options.props;for(var i in n)"production"!==e.env.NODE_ENV&&("function"!=typeof n[i]&&Aa('Method "'+i+'" has type "'+typeof n[i]+'" in the component definition. Did you reference the function correctly?',t),r&&y(r,i)&&Aa('Method "'+i+'" has already been defined as a prop.',t),i in t&&S(i)&&Aa('Method "'+i+'" conflicts with an existing Vue instance method. Avoid defining component methods that start with _ or $.')),t[i]="function"!=typeof n[i]?O:ta(n[i],t)}function Wt(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var i=0;i-1)return this;var n=k(arguments,1);return n.unshift(this),"function"==typeof e.install?e.install.apply(e,n):"function"==typeof e&&e.apply(null,n),t.push(e),this}}function rn(e){e.mixin=function(e){return this.options=re(this.options,e),this}}function on(t){t.cid=0;var n=1;t.extend=function(t){t=t||{};var r=this,i=r.cid,o=t._Ctor||(t._Ctor={});if(o[i])return o[i];var a=t.name||r.options.name;"production"!==e.env.NODE_ENV&&a&&J(a);var s=function(e){this._init(e)};return s.prototype=Object.create(r.prototype),s.prototype.constructor=s,s.cid=n++,s.options=re(r.options,t),s.super=r,s.options.props&&an(s),s.options.computed&&sn(s),s.extend=r.extend,s.mixin=r.mixin,s.use=r.use,oa.forEach(function(e){s[e]=r[e]}),a&&(s.options.components[a]=s),s.superOptions=r.options,s.extendOptions=t,s.sealedOptions=E({},s.options),o[i]=s,s}}function an(e){var t=e.options.props;for(var n in t)Pt(e.prototype,"_props",n)}function sn(e){var t=e.options.computed;for(var n in t)qt(e.prototype,n,t[n])}function ln(t){oa.forEach(function(n){t[n]=function(t,r){return r?("production"!==e.env.NODE_ENV&&"component"===n&&J(t),"component"===n&&d(r)&&(r.name=r.name||t,r=this.options._base.extend(r)),"directive"===n&&"function"==typeof r&&(r={bind:r,update:r}),this.options[n+"s"][t]=r,r):this.options[n+"s"][t]}})}function cn(e){return e&&(e.Ctor.options.name||e.tag)}function un(e,t){return Array.isArray(e)?e.indexOf(t)>-1:"string"==typeof e?e.split(",").indexOf(t)>-1:!!p(e)&&e.test(t)}function dn(e,t){var n=e.cache,r=e.keys,i=e._vnode;for(var o in n){var a=n[o];if(a){var s=cn(a.componentOptions);s&&!t(s)&&pn(n,o,r,i)}}}function pn(e,t,n,r){var i=e[t];!i||r&&i.tag===r.tag||i.componentInstance.$destroy(),e[t]=null,b(n,t)}function fn(e){for(var t=e.data,n=e,r=e;o(r.componentInstance);)(r=r.componentInstance._vnode)&&r.data&&(t=hn(r.data,t));for(;o(n=n.parent);)n&&n.data&&(t=hn(t,n.data));return mn(t.staticClass,t.class)}function hn(e,t){return{staticClass:vn(e.staticClass,t.staticClass),class:o(e.class)?[e.class,t.class]:t.class}}function mn(e,t){return o(e)||o(t)?vn(e,gn(t)):""}function vn(e,t){return e?t?e+" "+t:e:t||""}function gn(e){return Array.isArray(e)?bn(e):c(e)?yn(e):"string"==typeof e?e:""}function bn(e){for(var t,n="",r=0,i=e.length;r-1?vl[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:vl[e]=/HTMLUnknownElement/.test(t.toString())}function xn(t){if("string"==typeof t){var n=document.querySelector(t);return n||("production"!==e.env.NODE_ENV&&Aa("Cannot find element: "+t),document.createElement("div"))}return t}function kn(e,t){var n=document.createElement(e);return"select"!==e?n:(t.data&&t.data.attrs&&void 0!==t.data.attrs.multiple&&n.setAttribute("multiple","multiple"),n)}function En(e,t){return document.createElementNS(dl[e],t)}function Nn(e){return document.createTextNode(e)}function On(e){return document.createComment(e)}function Cn(e,t,n){e.insertBefore(t,n)}function An(e,t){e.removeChild(t)}function Dn(e,t){e.appendChild(t)}function Sn(e){return e.parentNode}function Mn(e){return e.nextSibling}function Tn(e){return e.tagName}function $n(e,t){e.textContent=t}function jn(e,t){e.setAttribute(t,"")}function Rn(e,t){var n=e.data.ref;if(o(n)){var r=e.context,i=e.componentInstance||e.elm,a=r.$refs;t?Array.isArray(a[n])?b(a[n],i):a[n]===i&&(a[n]=void 0):e.data.refInFor?Array.isArray(a[n])?a[n].indexOf(i)<0&&a[n].push(i):a[n]=[i]:a[n]=i}}function In(e,t){return e.key===t.key&&(e.tag===t.tag&&e.isComment===t.isComment&&o(e.data)===o(t.data)&&Ln(e,t)||a(e.isAsyncPlaceholder)&&e.asyncFactory===t.asyncFactory&&i(t.asyncFactory.error))}function Ln(e,t){if("input"!==e.tag)return!0;var n,r=o(n=e.data)&&o(n=n.attrs)&&n.type,i=o(n=t.data)&&o(n=n.attrs)&&n.type;return r===i||gl(r)&&gl(i)}function Vn(e,t,n){var r,i,a={};for(r=t;r<=n;++r)i=e[r].key,o(i)&&(a[i]=r);return a}function zn(e,t){(e.data.directives||t.data.directives)&&Pn(e,t)}function Pn(e,t){var n,r,i,o=e===_l,a=t===_l,s=Bn(e.data.directives,e.context),l=Bn(t.data.directives,t.context),c=[],u=[];for(n in l)r=s[n],i=l[n],r?(i.oldValue=r.value,i.oldArg=r.arg,Un(i,"update",t,e),i.def&&i.def.componentUpdated&&u.push(i)):(Un(i,"bind",t,e),i.def&&i.def.inserted&&c.push(i));if(c.length){var d=function(){for(var n=0;n-1?qn(e,t,n):al(t)?ul(n)?e.removeAttribute(t):(n="allowfullscreen"===t&&"EMBED"===e.tagName?"true":t,e.setAttribute(t,n)):rl(t)?e.setAttribute(t,ol(t,n)):ll(t)?ul(n)?e.removeAttributeNS(sl,cl(t)):e.setAttributeNS(sl,t,n):qn(e,t,n)}function qn(e,t,n){if(ul(n))e.removeAttribute(t);else{if(ma&&!va&&"TEXTAREA"===e.tagName&&"placeholder"===t&&""!==n&&!e.__ieph){var r=function(t){t.stopImmediatePropagation(),e.removeEventListener("input",r)};e.addEventListener("input",r),e.__ieph=!0}e.setAttribute(t,n)}}function Gn(e,t){var n=t.elm,r=t.data,a=e.data;if(!(i(r.staticClass)&&i(r.class)&&(i(a)||i(a.staticClass)&&i(a.class)))){var s=fn(t),l=n._transitionClasses;o(l)&&(s=vn(s,gn(l))),s!==n._prevClass&&(n.setAttribute("class",s),n._prevClass=s)}}function Zn(e){function t(){(a||(a=[])).push(e.slice(h,i).trim()),h=i+1}var n,r,i,o,a,s=!1,l=!1,c=!1,u=!1,d=0,p=0,f=0,h=0;for(i=0;i=0&&" "===(v=e.charAt(m));m--);v&&Cl.test(v)||(u=!0)}}else void 0===o?(h=i+1,o=e.slice(0,i).trim()):t();if(void 0===o?o=e.slice(0,i).trim():0!==h&&t(),a)for(i=0;i-1?{exp:e.slice(0,Gs),key:'"'+e.slice(Gs+1)+'"'}:{exp:e,key:null};for(Ks=e,Gs=Zs=Qs=0;!fr();)qs=pr(),hr(qs)?vr(qs):91===qs&&mr(qs);return{exp:e.slice(0,Zs),key:e.slice(Zs+1,Qs)}}function pr(){return Ks.charCodeAt(++Gs)}function fr(){return Gs>=Fs}function hr(e){return 34===e||39===e}function mr(e){var t=1;for(Zs=Gs;!fr();)if(e=pr(),hr(e))vr(e);else if(91===e&&t++,93===e&&t--,0===t){Qs=Gs;break}}function vr(e){for(var t=e;!fr()&&(e=pr())!==t;);}function gr(t,n,r){Ws=r;var i=n.value,o=n.modifiers,a=t.tag,s=t.attrsMap.type;if("production"!==e.env.NODE_ENV&&"input"===a&&"file"===s&&Ws("<"+t.tag+' v-model="'+i+'" type="file">:\nFile inputs are read only. Use a v-on:change listener instead.',t.rawAttrsMap["v-model"]),t.component)return cr(t,i,o),!1;if("select"===a)_r(t,i,o);else if("input"===a&&"checkbox"===s)br(t,i,o);else if("input"===a&&"radio"===s)yr(t,i,o);else if("input"===a||"textarea"===a)wr(t,i,o);else{if(!sa.isReservedTag(a))return cr(t,i,o),!1;"production"!==e.env.NODE_ENV&&Ws("<"+t.tag+' v-model="'+i+"\">: v-model is not supported on this element type. If you are working with contenteditable, it's recommended to wrap a library dedicated for that purpose inside a custom component.",t.rawAttrsMap["v-model"])}return!0}function br(e,t,n){var r=n&&n.number,i=or(e,"value")||"null",o=or(e,"true-value")||"true",a=or(e,"false-value")||"false";Jn(e,"checked","Array.isArray("+t+")?_i("+t+","+i+")>-1"+("true"===o?":("+t+")":":_q("+t+","+o+")")),rr(e,"change","var $$a="+t+",$$el=$event.target,$$c=$$el.checked?("+o+"):("+a+");if(Array.isArray($$a)){var $$v="+(r?"_n("+i+")":i)+",$$i=_i($$a,$$v);if($$el.checked){$$i<0&&("+ur(t,"$$a.concat([$$v])")+")}else{$$i>-1&&("+ur(t,"$$a.slice(0,$$i).concat($$a.slice($$i+1))")+")}}else{"+ur(t,"$$c")+"}",null,!0)}function yr(e,t,n){var r=n&&n.number,i=or(e,"value")||"null";i=r?"_n("+i+")":i,Jn(e,"checked","_q("+t+","+i+")"),rr(e,"change",ur(t,i),null,!0)}function _r(e,t,n){var r=n&&n.number,i='Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){var val = "_value" in o ? o._value : o.value;return '+(r?"_n(val)":"val")+"})",o="var $$selectedVal = "+i+";";o=o+" "+ur(t,"$event.target.multiple ? $$selectedVal : $$selectedVal[0]"),rr(e,"change",o,null,!0)}function wr(t,n,r){var i=t.attrsMap.type;if("production"!==e.env.NODE_ENV){var o=t.attrsMap["v-bind:value"]||t.attrsMap[":value"],a=t.attrsMap["v-bind:type"]||t.attrsMap[":type"];if(o&&!a){var s=t.attrsMap["v-bind:value"]?"v-bind:value":":value";Ws(s+'="'+o+'" conflicts with v-model on the same element because the latter already expands to a value binding internally',t.rawAttrsMap[s])}}var l=r||{},c=l.lazy,u=l.number,d=l.trim,p=!c&&"range"!==i,f=c?"change":"range"===i?Al:"input",h="$event.target.value";d&&(h="$event.target.value.trim()"),u&&(h="_n("+h+")");var m=ur(n,h);p&&(m="if($event.target.composing)return;"+m),Jn(t,"value","("+n+")"),rr(t,f,m,null,!0),(d||u)&&rr(t,"blur","$forceUpdate()")}function xr(e){if(o(e[Al])){var t=ma?"change":"input";e[t]=[].concat(e[Al],e[t]||[]),delete e[Al]}o(e[Dl])&&(e.change=[].concat(e[Dl],e.change||[]),delete e[Dl])}function kr(e,t,n){var r=Ys;return function i(){null!==t.apply(null,arguments)&&Nr(e,i,n,r)}}function Er(e,t,n,r){if(Sl){var i=$s,o=t;t=o._wrapper=function(e){if(e.target===e.currentTarget||e.timeStamp>=i||e.timeStamp<=0||e.target.ownerDocument!==document)return o.apply(this,arguments)}}Ys.addEventListener(e,t,wa?{capture:n,passive:r}:n)}function Nr(e,t,n,r){(r||Ys).removeEventListener(e,t._wrapper||t,n)}function Or(e,t){if(!i(e.data.on)||!i(t.data.on)){var n=t.data.on||{},r=e.data.on||{};Ys=t.elm,xr(n),Ne(n,r,Er,Nr,kr,t.context),Ys=void 0}}function Cr(e,t){if(!i(e.data.domProps)||!i(t.data.domProps)){var n,r,a=t.elm,s=e.data.domProps||{},l=t.data.domProps||{};o(l.__ob__)&&(l=t.data.domProps=E({},l));for(n in s)n in l||(a[n]="");for(n in l){if(r=l[n],"textContent"===n||"innerHTML"===n){if(t.children&&(t.children.length=0),r===s[n])continue;1===a.childNodes.length&&a.removeChild(a.childNodes[0])}if("value"===n&&"PROGRESS"!==a.tagName){a._value=r;var c=i(r)?"":String(r);Ar(a,c)&&(a.value=c)}else if("innerHTML"===n&&fl(a.tagName)&&i(a.innerHTML)){Js=Js||document.createElement("div"),Js.innerHTML=""+r+"";for(var u=Js.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;u.firstChild;)a.appendChild(u.firstChild)}else if(r!==s[n])try{a[n]=r}catch(e){}}}}function Ar(e,t){return!e.composing&&("OPTION"===e.tagName||Dr(e,t)||Sr(e,t))}function Dr(e,t){var n=!0;try{n=document.activeElement!==e}catch(e){}return n&&e.value!==t}function Sr(e,t){var n=e.value,r=e._vModifiers;if(o(r)){if(r.number)return v(n)!==v(t);if(r.trim)return n.trim()!==t.trim()}return n!==t}function Mr(e){var t=Tr(e.style);return e.staticStyle?E(e.staticStyle,t):t}function Tr(e){return Array.isArray(e)?N(e):"string"==typeof e?$l(e):e}function $r(e,t){var n,r={};if(t)for(var i=e;i.componentInstance;)(i=i.componentInstance._vnode)&&i.data&&(n=Mr(i.data))&&E(r,n);(n=Mr(e.data))&&E(r,n);for(var o=e;o=o.parent;)o.data&&(n=Mr(o.data))&&E(r,n);return r}function jr(e,t){var n=t.data,r=e.data;if(!(i(n.staticStyle)&&i(n.style)&&i(r.staticStyle)&&i(r.style))){var a,s,l=t.elm,c=r.staticStyle,u=r.normalizedStyle||r.style||{},d=c||u,p=Tr(t.data.style)||{};t.data.normalizedStyle=o(p.__ob__)?E({},p):p;var f=$r(t,!0);for(s in d)i(f[s])&&Il(l,s,"");for(s in f)(a=f[s])!==d[s]&&Il(l,s,null==a?"":a)}}function Rr(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(Pl).forEach(function(t){return e.classList.add(t)}):e.classList.add(t);else{var n=" "+(e.getAttribute("class")||"")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function Ir(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(Pl).forEach(function(t){return e.classList.remove(t)}):e.classList.remove(t),e.classList.length||e.removeAttribute("class");else{for(var n=" "+(e.getAttribute("class")||"")+" ",r=" "+t+" ";n.indexOf(r)>=0;)n=n.replace(r," ");n=n.trim(),n?e.setAttribute("class",n):e.removeAttribute("class")}}function Lr(e){if(e){if("object"==typeof e){var t={};return!1!==e.css&&E(t,Bl(e.name||"v")),E(t,e),t}return"string"==typeof e?Bl(e):void 0}}function Vr(e){Ql(function(){Ql(e)})}function zr(e,t){var n=e._transitionClasses||(e._transitionClasses=[]);n.indexOf(t)<0&&(n.push(t),Rr(e,t))}function Pr(e,t){e._transitionClasses&&b(e._transitionClasses,t),Ir(e,t)}function Br(e,t,n){var r=Hr(e,t),i=r.type,o=r.timeout,a=r.propCount;if(!i)return n();var s=i===Ul?ql:Zl,l=0,c=function(){e.removeEventListener(s,u),n()},u=function(t){t.target===e&&++l>=a&&c()};setTimeout(function(){l0&&(n=Ul,u=a,d=o.length):t===Fl?c>0&&(n=Fl,u=c,d=l.length):(u=Math.max(a,c),n=u>0?a>c?Ul:Fl:null,d=n?n===Ul?o.length:l.length:0),{type:n,timeout:u,propCount:d,hasTransform:n===Ul&&Wl.test(r[Kl+"Property"])}}function Ur(e,t){for(;e.length explicit "+t+" duration is not a valid number - got "+JSON.stringify(e)+".",n.context):isNaN(e)&&Aa(" explicit "+t+" duration is NaN - the duration expression might be incorrect.",n.context)}function Zr(e){return"number"==typeof e&&!isNaN(e)}function Qr(e){if(i(e))return!1;var t=e.fns;return o(t)?Qr(Array.isArray(t)?t[0]:t):(e._length||e.length)>1}function Wr(e,t){!0!==t.data.show&&Kr(t)}function Yr(e,t,n){Jr(e,t,n),(ma||ga)&&setTimeout(function(){Jr(e,t,n)},0)}function Jr(t,n,r){var i=n.value,o=t.multiple;if(o&&!Array.isArray(i))return void("production"!==e.env.NODE_ENV&&Aa('