chore:优化代码,减少代码体积,增加部分功能

This commit is contained in:
zhaoxuhui 2019-05-09 16:17:47 +08:00
parent 214bae06f6
commit d007de2fae
44 changed files with 1518 additions and 2756 deletions

View File

@ -1,14 +1,16 @@
# vue-Markdown编辑器
[在线示例地址](http://47.99.49.57/markdown/)
[在线示例地址](https://zhaoxuhui1122.github.io/vue-markdown/)
GitHub :[ https://github.com/coinsuper/vue-markdown]( https://github.com/coinsuper/vue-markdown)
GitHub :[https://github.com/zhaoxuhui1122/vue-markdown]( https://github.com/zhaoxuhui1122/vue-markdown)
### 1.简介
**一款使用marked和highlight.js开发的一款markdown编辑器目前只支持在vue项目中使用。
编辑器涵盖了常用的markdown编辑器功能工具栏可自定义配置也可进行二次开发。**
**一款使用marked和highlight.js开发的一款markdown编辑器目前只支持在vue项目中使用。**
**编辑器涵盖了常用的markdown编辑器功能工具栏可自定义配置也可进行二次开发。**
**效果**
![image](http://smalleyes.oss-cn-shanghai.aliyuncs.com/WechatIMG586.png)
@ -17,6 +19,10 @@ GitHub :[ https://github.com/coinsuper/vue-markdown]( https://github.com/coinsup
```
npm i -S vue-meditor
直接复制对应的组件到项目目录内 (推荐)
```
### 3.在项目中使用
@ -40,8 +46,6 @@ components:{
名称 | 类型|说明|默认值
---|---|---|---
title|String|编辑器标题,默认为空,不显示
titleStyle|Object|标题样式,如果自定义标题时可自行编写样式
initialValue|String|编辑器初始化内容
width|Number|编辑器宽度|
height|Number|编辑器高度,单位 px|600
@ -49,14 +53,14 @@ theme|String|代码块主题配置共有四个值分别为Light、Dark、O
autoSave|Boolean|是否自动保存|true
interval|Number|自动保存频率,单位毫秒|10000
toolbars|Object|工具栏配置,具体功能详见工具栏功能配置表
mode|Number|初始化显示模式 1 分屏显示 2 预览详情 3 全屏编辑
exportFileName|String|导出文件的名称|未命名文件
### 5.events
名称 | 说明
---|---
on-save|自动保存或者手动保存时触发,返回当前编辑器内原始输入内容和转以后的内容
on-paste-image|粘贴图片返回当前粘贴的file文件
### 6.工具栏配置
名称 | 说明 | 默认显示
@ -91,10 +95,16 @@ importmd|导入本地*.md文件|是
**关于保存时返回值**
```
markdownValue // 编辑器输入的原始内容
htmlValue // 右侧现实的问转义后的内容
value // 编辑器输入的原始内容
html // 右侧现实的问转义后的内容
theme // 保存时的主题名字
```
**标题配置**
```
支持配置编辑器名称提供了name=title的slot插槽
```
**工具栏配置**
@ -105,43 +115,38 @@ const config = {
}
<MarkDown :toolbars="config"/>
```
**优化代码体积**
**关于二次开发**
原始文件在src/markdown下可在其基础上自定义开发也可将markdown文件夹
复制到新项目中,安装对应依赖 highlight.js和marked即可
**关于二次开发后打包**
```
// 修改webpack.config.js
entry: './src/main.js', // main.js改为index.js
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js', // 输出文件名改为index.js或其他
libraryTarget: 'umd',
library: 'markdown-vue',
umdNamedDefine: true
},
项目中为了达到代码高亮显示需要用到highlight.js,
由于highlight.js体积过于庞大项目中按需加载了部分常用的程序语言
可根据需求自行配置,配置目录位于/markdown/js/hljs内
```
### 更新日志
v0.7.0
1.修复主题无法更新的问题
2.修复文档初始化值无法动态切换的问题
**v1.2.0**
1. 优化代码体积按需加载highlight.js较少了三分之二的代码体积
2. 新增图片粘贴功能
3. 增加图片预览功能
4. 修复部分bug
**v0.9.3**
1. 解决初始化值initialValue无法动态改变的问题
2. 修改了打包配置
**v0.8.0**
1. 新增md文件导出和读取功能
2. 修改预览部分样式
3. 修改头部菜单样式
**v0.7.0**
1. 修复主题无法更新的问题
2. 修复文档初始化值无法动态切换的问题
v0.8.0
1.新增md文件导出和读取功能
2.修改预览部分样式
3.修改头部菜单样式
v0.9.3
1.解决初始化值initialValue无法动态改变的问题
2.修改了打包配置

Binary file not shown.

BIN
build/iconfont.eot.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 30 KiB

BIN
build/iconfont.svg.gz Normal file

Binary file not shown.

Binary file not shown.

BIN
build/iconfont.ttf.gz Normal file

Binary file not shown.

BIN
build/iconfont.woff Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
build/index.js.gz Normal file

Binary file not shown.

1
build/index.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
build/index.js.map.gz Normal file

Binary file not shown.

8
dist/build.js vendored

File diff suppressed because one or more lines are too long

BIN
dist/build.js.gz vendored Normal file

Binary file not shown.

2
dist/build.js.map vendored

File diff suppressed because one or more lines are too long

BIN
dist/build.js.map.gz vendored Normal file

Binary file not shown.

BIN
dist/iconfont.eot vendored

Binary file not shown.

116
dist/iconfont.svg vendored

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 49 KiB

BIN
dist/iconfont.ttf vendored

Binary file not shown.

2
dist/index.js vendored

File diff suppressed because one or more lines are too long

1
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>template</title>
</head>
<body>
<div id="app"></div>
<script src="./dist/build.js"></script>
</body>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>markdown编辑器组件</title>
</head>
<body>
<div id="app"></div>
<script src="./dist/build.js"></script>
</body>
</html>

View File

@ -1,10 +1,10 @@
{
"name": "vue-meditor",
"description": "一款使用marked和highlight.js开发的一款markdown编辑器",
"version": "0.9.3",
"version": "1.2.0",
"author": "zhaoxuhui<1258835133@qq.com>",
"license": "MIT",
"main": "dist/index.js",
"main": "build/index.js",
"keywords": [
"markdown编辑器",
"vue markdown编辑器",
@ -18,7 +18,8 @@
"dependencies": {
"highlight.js": "^9.12.0",
"marked": "^0.4.0",
"vue": "^2.5.11"
"vue": "^2.5.11",
"vue-meditor": "^1.1.0"
},
"browserslist": [
"> 1%",
@ -30,8 +31,9 @@
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.0",
"babel-preset-stage-3": "^6.24.1",
"cross-env": "^5.0.5",
"cross-env": "^5.2.0",
"css-loader": "^0.28.11",
"compression-webpack-plugin": "^1.1.11",
"file-loader": "^1.1.4",
"less": "^3.0.4",
"less-loader": "^4.1.0",

View File

@ -1,40 +1,86 @@
<template>
<div id="app">
<div class="container">
<mark-down @on-save="save" theme="OneDark" :initialValue="initialValue"></mark-down>
<div id="app">
<div class="container">
<h1>vue-markdown编辑器组件</h1>
<a target="_blank" href="https://zhaoxuhui1122.github.io/vue-markdown/">使用文档</a>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
<div class="content">
<mark-down @on-save="save" theme="OneDark" :initialValue="initialValue"></mark-down>
</div>
</div>
</div>
</div>
</template>
<script>
// import MarkDown from './markdown/index' //
import MarkDown from "../build"; //
export default {
// import MarkDown from './markdown/index' //
// import MarkDown from "../build"; //
import MarkDown from 'vue-meditor';
import doc from './doc';
export default {
name: "app",
components: {
MarkDown
MarkDown
},
data() {
return {
initialValue: ""
};
return {
initialValue: ""
};
},
methods: {
save(res) {
console.log(res);
}
save(res) {
console.log(res);
}
},
mounted() {
setTimeout(() => {
this.initialValue = "# vue-markdown";
}, 1000);
setTimeout(() => {
this.initialValue = doc;
}, 1000);
}
};
};
</script>
<style>
.container {
margin: 20px auto;
border: 1px solid #ccc;
}
<style scoped lang="less">
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 1200px;
margin: 0 auto;
h1 {
font-size: 26px;
font-weight: 400;
margin: 12px 0;
}
a {
display: block;
background-color: #f8f8f9;
border-radius: 4px;
padding: 8px 16px;
border: 1px solid #edeff0;
border-radius: 4px;
margin-bottom: 20px;
text-decoration: none;
color: #2d8cf0;
}
ul {
list-style: none;
}
.content {
border: 1px solid #ccc;
}
}
</style>

153
src/doc.js Normal file
View File

@ -0,0 +1,153 @@
export default `# vue-Markdown编辑器
[在线示例地址](https://zhaoxuhui1122.github.io/vue-markdown/)
GitHub :[https://github.com/zhaoxuhui1122/vue-markdown]( https://github.com/zhaoxuhui1122/vue-markdown)
### 1.简介
**一款使用marked和highlight.js开发的一款markdown编辑器目前只支持在vue项目中使用**
**编辑器涵盖了常用的markdown编辑器功能工具栏可自定义配置也可进行二次开发**
**效果**
![image](http://smalleyes.oss-cn-shanghai.aliyuncs.com/WechatIMG586.png)
### 2.安装
\`\`\`
npm i -S vue-meditor
直接复制对应的组件到项目目录内 推荐
\`\`\`
### 3.在项目中使用
\`\`\`
import MarkDown from 'vue-meditor'
...
components:{
MarkDown
}
...
<template>
<mark-down/>
</template>
\`\`\`
### 4.props
名称 | 类型|说明|默认值
---|---|---|---
initialValue|String|编辑器初始化内容
width|Number|编辑器宽度|
height|Number|编辑器高度单位 px|600
theme|String|代码块主题配置共有四个值分别为LightDarkOneDarkGitHub|Light
autoSave|Boolean|是否自动保存|true
interval|Number|自动保存频率单位毫秒|10000
toolbars|Object|工具栏配置具体功能详见工具栏功能配置表
exportFileName|String|导出文件的名称|未命名文件
### 5.events
名称 | 说明
---|---
on-save|自动保存或者手动保存时触发返回当前编辑器内原始输入内容和转以后的内容
on-paste-image|粘贴图片返回当前粘贴的file文件
### 6.工具栏配置
名称 | 说明 | 默认显示
---|---|---
strong|粗体|
italic|斜体|
overline |删除线|
h1 |标题1|
h2 |标题2|
h3 |标题3|
h4|标题4|
h5 |标题5|
h6 |标题6|
hr |分割线|
quote|引用|
ul |无序列表|
ol|有序列表|
code |代码块|
link |链接|
image|image|
table |表格|
checked|已完成列表|
notChecked |未完成列表|
shift|预览|
print |打印|
theme|主题切换|
fullscreen |全屏|
exportmd|导出为*.md文件|
importmd|导入本地*.md文件|
### 7.其他说明
**关于保存时返回值**
\`\`\`
value // 编辑器输入的原始内容
html // 右侧现实的问转义后的内容
theme // 保存时的主题名字
\`\`\`
**标题配置**
\`\`\`
支持配置编辑器名称提供了name=title的slot插槽
\`\`\`
**工具栏配置**
\`\`\`
// 例:
const config = {
print:false // 隐藏掉打印功能
}
<MarkDown :toolbars="config"/>
\`\`\`
**优化代码体积**
\`\`\`
项目中为了达到代码高亮显示需要用到highlight.js,
由于highlight.js体积过于庞大项目中按需加载了部分常用的程序语言
可根据需求自行配置配置目录位于/markdown/js/hljs内
\`\`\`
### 更新日志
**v1.0.0**
1. 优化代码体积按需加载highlight.js较少了三分之二的代码体积
2. 新增图片粘贴功能
3. 增加图片预览功能
4. 修复部分bug
**v0.9.3**
1. 解决初始化值initialValue无法动态改变的问题
2. 修改了打包配置
**v0.8.0**
1. 新增md文件导出和读取功能
2. 修改预览部分样式
3. 修改头部菜单样式
**v0.7.0**
1. 修复主题无法更新的问题
2. 修复文档初始化值无法动态切换的问题
`

File diff suppressed because one or more lines are too long

View File

@ -15,11 +15,9 @@
border-radius: 4px;
margin: 20px 0 !important;
overflow-x: auto !important;
* {
font-family: Consolas !important;
}
}
.hljs-comment,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,8 +1,12 @@
<template lang="html">
<div :class="isFullscreen?'markdown fullscreen':'markdown' " ref="markdown" :style="{height:`${editorHeight}px`}" @mouseover="addListener" @mouseout="removeListener">
<div
:class="isFullscreen?'markdown fullscreen':'markdown' "
ref="markdown"
:style="{width:editorWidth+'px',height:editorHeight+'px'}"
>
<!-- 头部工具栏 -->
<ul class="markdown-toolbars">
<li class="title" v-if="title" :style="{titleStyle}">{{title}}</li>
<li><slot name="title"/></li>
<li v-if="tools.strong" name="粗体">
<span @click="insertStrong" class="iconfont icon-strong"></span>
</li>
@ -12,29 +16,29 @@
<li v-if="tools.overline" name="删除线">
<span @click="insertOverline" class="iconfont icon-overline"></span>
</li>
<li v-if="tools.h1" name="标题1" >
<span @click="insertTitle(1)"class="title">h1</span>
<li v-if="tools.h1" name="标题1">
<span style="font-size: 16px;" @click="insertTitle(1)">h1</span>
</li>
<li v-if="tools.h2" name="标题2">
<span @click="insertTitle(2)"class="title">h2</span>
<span style="font-size: 16px;" @click="insertTitle(2)">h2</span>
</li>
<li v-if="tools.h3" name="标题3" >
<span @click="insertTitle(3)"class="title">h3</span>
<li v-if="tools.h3" name="标题3">
<span style="font-size: 16px;" @click="insertTitle(3)">h3</span>
</li>
<li v-if="tools.h4" name="标题4" >
<span @click="insertTitle(4)"class="title">h4</span>
<li v-if="tools.h4" name="标题4">
<span style="font-size: 16px;" @click="insertTitle(4)">h4</span>
</li>
<li v-if="tools.h5" name="标题5" >
<span @click="insertTitle(5)"class="title">h5</span>
<li v-if="tools.h5" name="标题5">
<span style="font-size: 16px;" @click="insertTitle(5)">h5</span>
</li>
<li v-if="tools.h6" name="标题6">
<span @click="insertTitle(6)"class="title">h6</span>
<span style="font-size: 16px;" @click="insertTitle(6)">h6</span>
</li>
<li v-if="tools.hr" name="分割线">
<span @click="insertLine" class="iconfont icon-horizontal"></span>
</li>
<li v-if="tools.quote" name="引用">
<span @click="insertQuote" class="iconfont icon-quote"></span>
<span style="font-size: 16px;" @click="insertQuote" class="iconfont icon-quote"></span>
</li>
<li v-if="tools.ul" name="无序列表">
<span @click="insertUl" class="iconfont icon-ul"></span>
@ -65,31 +69,35 @@
</li>
<li v-if="tools.theme" class="shift-theme" name="代码块主题">
<div>
<span class="iconfont icon-yanse" @click="toggleSlideDown"></span>
<ul :class="{active:slideDown}" @mouseleave="slideDown=false">
<li @click="setThemes('Light')"> <span class="iconfont icon-theme"></span><i>Light</i></li>
<li @click="setThemes('Dark')"><span class="iconfont icon-vip"></span><i>VS Code</i></li>
<li @click="setThemes('OneDark')"><span class="iconfont icon-atom"></span><i>Atom OneDark</i></li>
<li @click="setThemes('GitHub')"><span class="iconfont icon-github51"></span><i>GitHub</i></li>
<span class="iconfont icon-yanse" @click="themeSlideDown=!themeSlideDown"></span>
<ul :class="{active:themeSlideDown}" @mouseleave="themeSlideDown=false">
<li @click="setThemes('Light')">Light</li>
<li @click="setThemes('Dark')">VS Code</li>
<li @click="setThemes('OneDark')">Atom OneDark</li>
<li @click="setThemes('GitHub')">GitHub</li>
</ul>
</div>
</li>
<li name="导入本地文件" class="import-file" v-show="tools.importmd">
<span class="iconfont icon-daoru"></span>
<span class="iconfont icon-daoru" @click="importFile"></span>
<input type="file" @change="importFile($event)" accept="text/markdown">
</li>
<li name="保存到本地" v-show="tools.exportmd">
<span class="iconfont icon-download" @click="exportMd"></span>
</li>
<li v-if="tools.shift&&preview==1" name="预览">
<span @click="preview=2" class="iconfont icon-preview"></span>
</li>
<li v-if="tools.shift&&preview==2" name="编辑">
<li v-if="tools.shift&&preview==2" name="全屏编辑">
<span @click="preview=3" class="iconfont icon-md"></span>
</li>
<li v-if="tools.shift&&preview==3" name="分屏显示">
<span @click="preview=1" class="iconfont icon-group"></span>
</li>
<li v-if="tools.shift&&preview==1" name="预览">
<span @click="preview=2" class="iconfont icon-preview"></span>
</li>
<li :name="scrolling?'同步滚动:开':'同步滚动:关'">
<span @click="scrolling=!scrolling" v-show="scrolling" class="iconfont icon-on"></span>
<span @click="scrolling=!scrolling" v-show="!scrolling" class="iconfont icon-off"></span>
</li>
<li class="empty"></li>
<li v-if="tools.fullscreen&&!isFullscreen" name="全屏">
@ -100,17 +108,38 @@
</li>
</ul>
<!-- 编辑器 -->
<div class="markdown-content">
<div v-show="preview===1||preview===3" class="markdown-editor" ref="markdownContent" @scroll="markdownScroll" @mouseenter="mousescrollSide('markdown')">
<div class="markdown-content" :style="{background:preview==2?'#fff':''}">
<div v-show="preview===1||preview===3" class="markdown-editor" ref="markdownContent"
@scroll="markdownScroll"
@mouseenter="mousescrollSide('markdown')">
<ul class="index" ref="index" :style="{height:scrollHeight?`${scrollHeight}px`:'100%'}">
<li v-for="(item,index) in indexLenth">{{index+1}}</li>
</ul>
<textarea v-model="value" @keydown.tab="tab" @keyup.enter="enter" @keyup.delete="onDelete" ref="textarea" :style="{height:scrollHeight?`${scrollHeight}px`:'100%'}"></textarea>
<textarea
v-model="value"
@keydown.tab="tab"
@keyup.enter="enter"
@keyup.delete="onDelete"
ref="textarea"
:style="{height:scrollHeight?`${scrollHeight}px`:'100%'}"
></textarea>
</div>
<div v-show="preview==1" class="empty" style="width:12px;"></div>
<div v-show="preview===1||preview===2" :class="`markdown-preview ${themeName}`" v-html="previewMarkdown" ref="preview" @scroll="previewScroll" @mouseenter="mousescrollSide('preview')">
<div
v-show="preview===1||preview===2"
:class="`markdown-preview ${themeName}`"
ref="preview"
@scroll="previewScroll"
@mouseenter="mousescrollSide('preview')">
<div v-html="html"
ref="previewInner" ></div>
</div>
</div>
<!-- 预览图片-->
<div :class="['preview-img',previewImgModal?'active':'']">
<span class="close" @click="previewImgModal=false">关闭</span>
<img :src="previewImgSrc" :class="[previewImgMode]" alt="">
</div>
</div>
</template>
@ -118,17 +147,14 @@
import markdown from './markdown';
export default markdown;
</script>
<style lang="less">
@import "css/theme";
@import "font/iconfont.css";
@import "./css/theme";
@import "css/light";
@import "css/dark";
@import "css/oneDark";
@import "css/one-dark";
@import "css/gitHub";
@import "css/common";
@import "css/index";
@import "./font/iconfont.css";
</style>

40
src/markdown/js/hljs.js Normal file
View File

@ -0,0 +1,40 @@
//hljs体积过大多数为解决代码高亮显示的问题,所以只引入部分语言,如果需要可自行加载
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 json from 'highlight.js/lib/languages/json';
import go from 'highlight.js/lib/languages/go';
import markdown from 'highlight.js/lib/languages/markdown';
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 rust from 'highlight.js/lib/languages/rust';
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 languagesMap = {
javascript,
java,
css,
less,
json,
markdown,
go,
php,
python,
ruby,
rust,
stylus,
typescript,
xml
}
Object.keys(languagesMap).forEach(language => {
hljs.registerLanguage(language,languagesMap[language])
})
export default hljs;

View File

@ -1,119 +0,0 @@
var Print = function (dom, options) {
if (! (this instanceof Print)) return new Print(dom, options);
this.options = this.extend({
'noPrint' : '.no-print'
}, options);
if ((typeof dom) === 'string') {
this.dom = document.querySelector(dom);
} else {
this.dom = dom;
}
this.init();
};
Print.prototype = {
init : function () {
var content = this.getStyle() + this.getHtml();
this.writeIframe(content);
},
extend : function (obj, obj2) {
for (var k in obj2) {
obj[k] = obj2[k];
}
return obj;
},
getStyle : function () {
var str = '',
styles = document.querySelectorAll('style,link');
for (var i = 0 ; i<styles.length ; i ++) {
str += styles[i].outerHTML;
}
str += '<style>' + (this.options.noPrint ? this.options.noPrint: '.no-print') + '{display:none;}</style>';
return str;
},
getHtml : function () {
var inputs = document.querySelectorAll('input');
var textareas = document.querySelectorAll('textarea');
var selects = document.querySelectorAll('select');
for (var k in inputs) {
if (inputs[k].type == 'checkbox' || inputs[k].type == 'radio') {
if (inputs[k].checked == true) {
inputs[k].setAttribute('checked', 'checked')
} else {
inputs[k].removeAttribute('checked')
}
} else if (inputs[k].type == 'text') {
inputs[k].setAttribute('value', inputs[k].value)
}
}
for (var k2 in textareas) {
if (textareas[k2].type == 'textarea') {
textareas[k2].innerHTML = textareas[k2].value
}
}
for (var k3 in selects) {
if (selects[k3].type == 'select-one') {
var child = selects[k3].children;
for (var i in child) {
if (child[i].tagName == 'OPTION') {
if (child[i].selected == true) {
child[i].setAttribute('selected', 'selected')
} else {
child[i].removeAttribute('selected')
}
}
}
}
}
return this.dom.outerHTML;
},
writeIframe : function (content) {
var w, doc, iframe = document.createElement('iframe'),
f = document.body.appendChild(iframe);
iframe.id = 'myIframe';
iframe.style = 'position:absolute;width:0;height:0;top:-10px;left:-10px;';
w = f.contentWindow || f.contentDocument;
doc = f.contentDocument || f.contentWindow.document;
doc.open();
doc.write(content);
doc.close();
this.toPrint(w);
setTimeout(function () {
document.body.removeChild(iframe)
}, 100)
},
toPrint : function (frameWindow) {
try {
setTimeout(function () {
frameWindow.focus();
try {
if (! frameWindow.document.execCommand('print', false, null)) {
frameWindow.print();
}
} catch (e) {
frameWindow.print();
}
frameWindow.close();
}, 10);
} catch (err) {
console.log('err', err);
}
}
};
export default function _Print(dom) {
Print(dom);
}

View File

@ -1,131 +0,0 @@
// 获取光标位置
function getCursortPosition(textDom) {
var cursorPos = 0;
if (document.selection) {
// IE Support
textDom.focus();
var selectRange = document.selection.createRange();
selectRange.moveStart('character', -textDom.value.length);
cursorPos = selectRange.text.length;
} else if (textDom.selectionStart || textDom.selectionStart == '0') {
// Firefox support
cursorPos = textDom.selectionStart;
}
return cursorPos;
}
// 设置光标位置
function setCaretPosition(textDom, pos) {
if (textDom.setSelectionRange) {
// IE Support
textDom.focus();
textDom.setSelectionRange(pos, pos);
} else if (textDom.createTextRange) {
// Firefox support
var range = textDom.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
}
// 获取选中文字
function getSelectText() {
var userSelection,
text;
if (window.getSelection) {
// Firefox support
userSelection = window.getSelection();
} else if (document.selection) {
// IE Support
userSelection = document.selection.createRange();
}
if (!(text = userSelection.text)) {
text = userSelection;
}
return text;
}
/**
* 选中特定范围的文本
* 参数
* textDom [JavaScript DOM String] 当前对象
* startPos [Int] 起始位置
* endPos [Int] 终点位置
*/
function setSelectText(textDom, startPos, endPos) {
var startPos = parseInt(startPos),
endPos = parseInt(endPos),
textLength = textDom.value.length;
if (textLength) {
if (!startPos) {
startPos = 0;
}
if (!endPos) {
endPos = textLength;
}
if (startPos > textLength) {
startPos = textLength;
}
if (endPos > textLength) {
endPos = textLength;
}
if (startPos < 0) {
startPos = textLength + startPos;
}
if (endPos < 0) {
endPos = textLength + endPos;
}
if (textDom.createTextRange) {
// IE Support
var range = textDom.createTextRange();
range.moveStart("character", -textLength);
range.moveEnd("character", -textLength);
range.moveStart("character", startPos);
range.moveEnd("character", endPos);
range.select();
} else {
// Firefox support
textDom.setSelectionRange(startPos, endPos);
textDom.focus();
}
}
}
/**
* 在光标后插入文本
* 参数
* textDom [JavaScript DOM String] 当前对象
* value [String] 要插入的文本
*/
function insertAfterText(textDom, value) {
var selectRange;
if (document.selection) {
// IE Support
textDom.focus();
selectRange = document.selection.createRange();
selectRange.text = value;
textDom.focus();
} else if (textDom.selectionStart || textDom.selectionStart == '0') {
// Firefox support
var startPos = textDom.selectionStart;
var endPos = textDom.selectionEnd;
var scrollTop = textDom.scrollTop;
textDom.value = textDom.value.substring(0, startPos) + value + textDom.value.substring(endPos, textDom.value.length);
textDom.focus();
textDom.selectionStart = startPos + value.length;
textDom.selectionEnd = startPos + value.length;
textDom.scrollTop = scrollTop;
} else {
textDom.value += value;
textDom.focus();
}
}
module.exports = {
getCursortPosition,
setCaretPosition,
getSelectText,
setSelectText,
insertAfterText
};

30
src/markdown/js/tools.js Normal file
View File

@ -0,0 +1,30 @@
/*
* 默认头部菜单配置
* */
export default {
strong : true,
italic : true,
overline : true,
h1 : true,
h2 : true,
h3 : true,
h4 : false,
h5 : false,
h6 : false,
hr : true,
quote : true,
ul : true,
ol : true,
code : true,
link : true,
image : true,
table : true,
checked : true,
notChecked : true,
shift : true,
fullscreen : true,
print : false,
theme : true,
exportmd : true,
importmd : true
}

18
src/markdown/js/utils.js Normal file
View File

@ -0,0 +1,18 @@
/*
* 保存文件到本地
* */
export function saveFile(fileData, name) {
var pom = document.createElement('a');
pom.setAttribute('href', 'data:text/plain;charset=UTF-8,' + encodeURIComponent(fileData));
pom.setAttribute('download', name);
pom.style.display = 'none';
if (document.createEvent) {
const event = document.createEvent('MouseEvents');
event.initEvent('click', true, true);
pom.dispatchEvent(event);
} else {
pom.click();
}
}

View File

@ -1,17 +1,18 @@
import hljs from 'highlight.js';
import hljs from './js/hljs';
import marked from 'marked';
import range from './js/range';
import Print from './js/print';
import {saveFile} from "./js/utils";
import defaultTools from './js/tools';
hljs.initHighlightingOnLoad();
const renderer = new marked.Renderer();
marked.setOptions({
renderer: new marked.Renderer(),
renderer,
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
sanitize: false,
smartLists: true,
highlight: function (code) {
return hljs.highlightAuto(code).value;
@ -21,182 +22,155 @@ marked.setOptions({
export default {
name: 'markdown',
props: {
title: { // 标题
type: String,
default: ''
},
titleStyle: { // 标题样式
type: Object,
default () {
return {}
}
},
initialValue: String,// 初始化内容
theme: { // 默认主题
type: String,
default: 'Light'
},
width: { // 宽度
width: { // 初始化宽度
type: [Number, String],
default: 'auto'
},
height: { // 高度
height: { // 初始化高度
type: Number,
default: 600
}, // 宽度
toolbars: { // 工具栏
type: Object,
default () {
default() {
return {};
}
},
autoSave: { // 是否自动保存
autoSave: {// 是否自动保存
type: Boolean,
default: true
},
interval: { // 自动保存频率 单位:毫秒
interval: {// 自动保存间隔 mm
type: Number,
default: 10000
default: 100000
},
initialValue: { // 初始化值
exportFileName: {// 默认导出文件名称
type: String,
default: ''
},
mode: { // 模式 1 分屏显示 2 预览详情 3 全屏编辑
type: [Number, String],
default: 1
default: '未命名文件'
}
},
data() {
return {
value: '', // 输入框内容
timeoutId: null,
hljsInit: null,
indexLenth: 1,
previewMarkdown: '',
indexLenth: 100,
html: '',
preview: 1, // 是否是预览状态
isFullscreen: false,
isFullscreen: false,// 是否是全屏
scrollHeight: null,
scroll: 'markdown', // 哪个半栏在滑动
allTools: { // 显示隐藏的工具栏
strong: true,
italic: true,
overline: true,
h1: true,
h2: true,
h3: true,
h4: false,
h5: false,
h6: false,
hr: true,
quote: true,
ul: true,
ol: true,
code: true,
link: true,
image: true,
table: true,
checked: true,
notChecked: true,
shift: true,
fullscreen: true,
print: false,
theme: true,
exportmd: true,
importmd: true
},
slideDown: false,
themeName: 'Light', // 主题名称
lastInsert: '',
timerId: null, // 定时器id
themeSlideDown: false,
imgs: [],
scrolling: true,// 同步滚动
editorHeight: this.height,
editorWidth: this.width,
previewImgModal: false,
previewImgSrc: '',
previewImgMode: ''
};
},
computed: {
editorHeight() {
if (this.isFullscreen) {
return window.innerHeight;
} else {
return this.height;
}
},
tools() {
const {
allTools,
toolbars
} = this;
return Object.assign(allTools, toolbars)
const {toolbars = {}} = this;
return {
...defaultTools,
...toolbars
}
}
},
mounted() {
this.$nextTick(() => {
this.$refs.textarea.focus();
})
this.init();
//this.addListener();
},
methods: {
init() {
this.themeName = this.theme;
const {
autoSave,
interval,
theme,
initialValue,
mode
} = this;
this.value = initialValue;
this.preview = mode;
this.previewMarkdown = marked(initialValue, {
sanitize: true
});
if (autoSave) {
setTimeout(() => {
this.value = this.initialValue;
const textarea = this.$refs.textarea;
textarea.focus();
textarea.addEventListener('keydown', e => {
if (e.keyCode === 83) {
if (e.metaKey || e.ctrlKey) {
e.preventDefault();
this.handleSave();
}
}
})
textarea.addEventListener('paste', this.handlePaste);
if (this.autoSave) {
this.timerId = setInterval(() => {
this.handleSave();
}, interval)
}, this.interval);
}
}, 20)
},
methods: {
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/ig.test(file.type)) {
this.$emit('on-paste-image', file);
e.preventDefault();
}
}
},
markdownScroll() {
const {scrolling} = this;
if (!scrolling) {
return;
}
if (this.scroll === 'markdown') {
const markdownContent = this.$refs.markdownContent;
const preview = this.$refs.preview;
const markdownScrollHeight = markdownContent.scrollHeight;
const markdownScrollTop = markdownContent.scrollTop;
const previewScrollHeight = preview.scrollHeight;
preview.scrollTop = parseInt((markdownScrollTop / markdownScrollHeight) * previewScrollHeight);
preview.scrollTop = parseInt(markdownScrollTop / markdownScrollHeight * previewScrollHeight, 0);
}
},
previewScroll() {
const {scrolling} = this;
if (!scrolling) {
return;
}
if (this.scroll === 'preview') {
const markdownContent = this.$refs.markdownContent;
const preview = this.$refs.preview;
const markdownScrollHeight = markdownContent.scrollHeight;
const previewScrollHeight = preview.scrollHeight;
const previewScrollTop = preview.scrollTop;
markdownContent.scrollTop = parseInt((previewScrollTop / previewScrollHeight) * markdownScrollHeight);
markdownContent.scrollTop = parseInt(previewScrollTop / previewScrollHeight * markdownScrollHeight, 0);
}
},
mousescrollSide(side) { // 设置究竟是哪个半边在主动滑动
this.scroll = side;
},
insertContent(str) { // 插入文本
const {
preview
} = this;
insertContent(initStr) { // 插入文本
const {preview} = this;
if (preview === 2) {
return;
}
this.lastInsert = str;
const textareaDom = this.$refs.textarea;
this.lastInsert = initStr;
const point = this.getCursortPosition();
const lastChart = this.value.substring(point - 1, point);
const lastFourCharts = this.value.substring(point - 4, point);
if (lastChart != '\n' && this.value != '' && lastFourCharts != ' ') {
str = '\n' + str;
if (lastChart !== '\n' && this.value !== '' && lastFourCharts !== ' ') {
const str = '\n' + initStr;
this.insertAfterText(str);
} else {
this.insertAfterText(str);
this.insertAfterText(initStr);
}
},
getCursortPosition() { // 获取光标位置
@ -207,7 +181,7 @@ export default {
let selectRange = document.selection.createRange();
selectRange.moveStart('character', -this.value.length);
cursorPos = selectRange.text.length;
} else if (textDom.selectionStart || textDom.selectionStart == '0') {
} else if (textDom.selectionStart || parseInt(textDom.selectionStart, 0) === 0) {
cursorPos = textDom.selectionStart;
}
return cursorPos;
@ -220,7 +194,7 @@ export default {
selectRange = document.selection.createRange();
selectRange.text = value;
textDom.focus();
} else if (textDom.selectionStart || textDom.selectionStart == '0') {
} else if (textDom.selectionStart || parseInt(textDom.selectionStart, 0) === 0) {
const startPos = textDom.selectionStart;
const endPos = textDom.selectionEnd;
const scrollTop = textDom.scrollTop;
@ -241,27 +215,13 @@ export default {
textDom.focus();
textDom.setSelectionRange(position, position);
} else if (textDom.createTextRange) {
var range = textDom.createTextRange();
let range = textDom.createTextRange();
range.collapse(true);
range.moveEnd('character', position);
range.moveStart('character', position);
range.select();
}
},
insertLine() { // 插入分割线
this.insertContent(`\n----\n`);
},
insertTitle(level) { // 插入标题
const titleLevel = {
1: '\n# ',
2: '\n## ',
3: '\n### ',
4: '\n#### ',
5: '\n##### ',
6: '\n###### '
};
this.insertContent(titleLevel[level]);
},
insertQuote() { // 引用
this.insertContent('\n> ');
},
@ -278,75 +238,69 @@ export default {
this.insertContent('- [ ] ');
},
insertLink() { // 插入链接
this.insertContent('\n[插入链接](https://github.com/coinsuper)');
this.insertContent('\n[插入链接](href)');
},
insertImage() { // 插入图片
this.insertContent('\n![image](https://noticejs.oss-cn-hangzhou.aliyuncs.com/%E6%9C%AA%E6%A0%87%E9%A2%98-3.jpg)');
this.insertContent('\n![image](imgUrl)');
},
insertTable() { // 插入表格
this.insertContent(`\nheader 1 | header 2\n---|---\nrow 1 col 1 | row 1 col 2\nrow 2 col 1 | row 2 col 2\n\n`);
this.insertContent('\nheader 1 | header 2\n---|---\nrow 1 col 1 | row 1 col 2\nrow 2 col 1 | row 2 col 2\n\n');
},
insertCode() { // 插入code
const textareaDom = this.$refs.textarea;
const point = this.getCursortPosition();
const lastChart = this.value.substring(point - 1, point);
this.insertContent('\n```\n\n```');
if (lastChart != '\n' && this.value != '') {
if (lastChart !== '\n' && this.value !== '') {
this.setCaretPosition(point + 5);
} else {
this.setCaretPosition(point + 5);
}
},
insertStrong() { // 粗体
const textareaDom = this.$refs.textarea;
const point = this.getCursortPosition();
const lastChart = this.value.substring(point - 1, point);
this.insertContent('****');
if (lastChart != '\n' && this.value != '') {
if (lastChart !== '\n' && this.value !== '') {
this.setCaretPosition(point + 2);
} else {
this.setCaretPosition(point + 2);
}
},
insertItalic() { // 斜体
const textareaDom = this.$refs.textarea;
const point = this.getCursortPosition();
const lastChart = this.value.substring(point - 1, point);
this.insertContent('**');
if (lastChart != '\n' && this.value != '') {
if (lastChart !== '\n' && this.value !== '') {
this.setCaretPosition(point + 1);
} else {
this.setCaretPosition(point + 1);
}
},
insertBg() { // 背景色
const textareaDom = this.$refs.textarea;
const point = this.getCursortPosition();
const lastChart = this.value.substring(point - 1, point);
this.insertContent('====');
if (lastChart != '\n' && this.value != '') {
if (lastChart !== '\n' && this.value !== '') {
this.setCaretPosition(point + 5);
} else {
this.setCaretPosition(point + 5);
}
},
insertUnderline() { // 下划线
const textareaDom = this.$refs.textarea;
const point = this.getCursortPosition();
const lastChart = this.value.substring(point - 1, point);
this.insertContent('<u></u>');
if (lastChart != '\n' && this.value != '') {
if (lastChart !== '\n' && this.value !== '') {
this.setCaretPosition(point + 3);
} else {
this.setCaretPosition(point + 5);
}
},
insertOverline() { // overline
const textareaDom = this.$refs.textarea;
const point = this.getCursortPosition();
const lastChart = this.value.substring(point - 1, point);
this.insertContent('~~~~');
if (lastChart != '\n' && this.value != '') {
if (lastChart !== '\n' && this.value !== '') {
this.setCaretPosition(point + 2);
} else {
this.setCaretPosition(point + 2);
@ -363,10 +317,6 @@ export default {
};
this.insertContent(titleLevel[level]);
},
save(e) { // ctrl+s 保存
e.preventDefault();
this.handleSave();
},
tab(e) { // 屏蔽teatarea tab默认事件
this.insertContent(' ', this);
if (e.preventDefault) {
@ -376,26 +326,25 @@ export default {
}
},
handleSave() { // 保存操作
const {value, html, themeName} = this;
this.$emit('on-save', {
markdownValue: this.value,
htmlValue: this.previewMarkdown,
theme: this.theme
theme: themeName,
value,
html
});
},
insertLine() { // 插入分割线
this.insertContent(`\n----\n`);
this.insertContent('\n----\n');
},
toggleSlideDown() { // 显示主题选项
this.slideDown = !this.slideDown;
},
setThemes(name) { // 设置主题
this.themeName = name;
this.slideDown = false;
this.themeSlideDown = false;
},
enter(e) { // 回车事件
const {
lastInsert
} = this;
enter() { // 回车事件
const {lastInsert} = this;
const list = ['- ', '1. ', '- [ ] ', '- [x] ']
if (list.includes(lastInsert)) {
this.insertContent(lastInsert);
@ -403,84 +352,81 @@ export default {
},
onDelete() { // 删除时,以回车为界分割,如果数组最后一个元素为''时,将行一次插入的共嗯那个置为空,避免回车时再次插入
const lines = this.value.split('\n');
if (lines[lines.length - 1] == '') {
if (lines[lines.length - 1] === '') {
this.lastInsert = '';
}
},
print() { // 打印文件
const dom = this.$refs.preview;
Print(dom);
exportMd() { // 导出为.md格式
saveFile(this.value, this.exportFileName + '.md');
},
addListener() { // 事件监听,阻止保存
this.removeListener();
document.addEventListener('keydown', this.listener)
},
removeListener() {
document.removeEventListener('keydown', this.listener)
},
listener(e) {
if (e.keyCode === 83) {
if (e.metaKey || e.ctrlKey) {
e.preventDefault();
this.handleSave();
}
}
},
importFile(e) { // 导入文件
importFile(e) {// 导入本地文件
const file = e.target.files[0];
if (!file) {
return;
}
const {
type
} = file;
const {type} = file;
if (type !== 'text/markdown') {
alert('文件格式有误');
this.$Notice.error('文件格式有误!');
return;
}
const reader = new FileReader();
reader.readAsText(file, {
encoding: 'utf-8'
});
reader.readAsText(file, {encoding: 'utf-8'});
reader.onload = () => {
this.value = reader.result;
e.target.value = '';
}
},
exportMd() { // 导出文件到本地
const {
value
} = this;
var pom = document.createElement('a');
pom.setAttribute('href', 'data:text/plain;charset=UTF-8,' + encodeURIComponent(value));
pom.setAttribute('download', '未命名.md');
pom.style.display = 'none';
if (document.createEvent) {
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true);
pom.dispatchEvent(event);
} else {
pom.click();
addImageClickLintener() { // 监听查看大图
const {imgs} = this;
if (imgs.length > 0) {
for (let i = 0, len = imgs.length; i < len; i++) {
imgs[i].onclick = null;
}
}
setTimeout(() => {
this.imgs = this.$refs.preview.querySelectorAll('img');
for (let i = 0, len = this.imgs.length; i < len; i++) {
this.imgs[i].onclick = () => {
const src = this.imgs[i].getAttribute('src');
this.previewImage(src);
}
}
}, 600);
},
previewImage(src) {// 预览图片
const img = new Image();
img.src = src;
img.onload = () => {
const width = img.naturalWidth;
const height = img.naturalHeight;
if ((height / width) > 1.4) {
this.previewImgMode = 'horizontal';
} else {
this.previewImgMode = 'vertical';
}
this.previewImgSrc = src;
this.previewImgModal = true;
}
}
},
watch: {
initialValue() {
this.value = this.initialValue;
},
value() {
clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => {
this.previewMarkdown = marked(this.value, {
sanitize: true
this.html = marked(this.value, {
sanitize: false
});
}, 30)
this.indexLenth = this.value.split('\n').length;
const height_1 = this.indexLenth * 22;
const height_2 = this.$refs.textarea.scrollHeight;
const height_3 = this.$refs.preview.scrollHeight;
this.scrollHeight = Math.max(height_1, height_2, height_3);
},
initialValue() {
this.value = this.initialValue;
const height1 = this.indexLenth * 22;
const height2 = this.$refs.textarea.scrollHeight;
const height3 = this.$refs.preview.scrollHeight;
this.scrollHeight = Math.max(height1, height2, height3);
this.indexLenth = parseInt(this.scrollHeight / 22, 0) - 1;
this.addImageClickLintener();
}
},
destroyed() { // 销毁时清除定时器

View File

@ -1,6 +1,7 @@
var path = require('path')
var webpack = require('webpack')
const NODE_ENV = process.env.NODE_ENV;
const CompressionWebpackPlugin = require('compression-webpack-plugin')
module.exports = {
entry: NODE_ENV==='npm'?'./src/index.js':'./src/main.js',
@ -12,6 +13,7 @@ module.exports = {
library: 'markdown-vue',
umdNamedDefine: true
},
devtool: NODE_ENV==='develop'?'cheap-module-eval-source-map':'cheap-module-source-map',
module: {
rules: [
{
@ -60,7 +62,7 @@ module.exports = {
devtool: '#eval-source-map'
}
if (process.env.NODE_ENV === 'production') {
if (NODE_ENV === 'production'||NODE_ENV==='npm') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
@ -77,6 +79,9 @@ if (process.env.NODE_ENV === 'production') {
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
}),
new CompressionWebpackPlugin({
algorithm: 'gzip'
})
])
}