chore:新增保存文件到本地和读取本地文件功能

This commit is contained in:
zhaoxuhui 2018-11-05 17:57:47 +08:00
parent a036667426
commit 468559bf7c
30 changed files with 992 additions and 613 deletions

View File

@ -1,22 +1,14 @@
# vue-Markdown编辑器
[在线示例地址](http://47.99.49.57/markdown/)
### 1.简介
**一款使用marked和highlight.js开发的一款markdown编辑器目前只支持在vue项目中使用。
编辑器涵盖了常用的markdown编辑器功能工具栏可自定义配置也可进行二次开发。**
#### 提供的常用功能
![image](https://noticejs.oss-cn-hangzhou.aliyuncs.com/gongneng.jpg)
#### 多种主题,分别支持 Light、DarkvsCode、OneDark、GitHub四种主题风格
![image](https://noticejs.oss-cn-hangzhou.aliyuncs.com/theme.jpg)
#### 一键打印
![image](https://noticejs.oss-cn-hangzhou.aliyuncs.com/print.jpg)
**效果**
![image](http://smalleyes.oss-cn-shanghai.aliyuncs.com/WechatIMG586.png)
### 2.安装
@ -86,15 +78,15 @@ table |表格|是
checked|已完成列表|是
notChecked |未完成列表|是
shift|预览|是
print |打印|
print |打印|
theme|主题切换|是
fullscreen |全屏|是
exportmd|导出为*.md文件|是
importmd|倒入本地*.md文件|是
### 7.其他说明
**关于保存时返回值**
```
markdownValue // 编辑器输入的原始内容
htmlValue // 右侧现实的问转义后的内容
@ -141,3 +133,8 @@ v0.7.0
1.修复主题无法更新的问题
2.修复文档初始化值无法动态切换的问题
v0.8.0
1.新增md文件导出和读取功能
2.修改预览部分样式
3.修改头部菜单样式

7
dist/build.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/build.js.map vendored Normal file

File diff suppressed because one or more lines are too long

BIN
dist/checked.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
dist/iconfont.eot vendored

Binary file not shown.

27
dist/iconfont.svg vendored

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 36 KiB

After

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

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

BIN
dist/notChecked.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,7 +1,7 @@
{
"name": "vue-meditor",
"description": "一款使用marked和highlight.js开发的一款markdown编辑器",
"version": "0.7.0",
"version": "0.8.0",
"author": "zhaoxuhui<1258835133@qq.com>",
"license": "MIT",
"main": "dist/index.js",

View File

@ -7,7 +7,7 @@
</template>
<script>
//import MarkDown from './markdown/index' //
// import MarkDown from './markdown/index' //
import MarkDown from '../dist' //
export default {
name: 'app',

View File

@ -3,186 +3,229 @@
*Author zhaoxuhui
*/
.markdown-preview {
max-width: 960px;
margin: 0 auto!important;
ul {
list-style: none;
padding: 0 20px;
li {
position: relative;
&:after {
display: block;
content: "";
width: 6px;
height: 6px;
border-radius: 50%;
position: absolute;
z-index: 99;
top: 7px;
left: -20px;
background: @content;
}
}
}
ol,
ul {
margin: 20px 0;
padding: 0 40px;
li {
font-size: 14px !important;
line-height: @line-height;
color: @title;
margin-bottom: 8px;
input[type="checkbox"] {
position: relative;
// transform: translateX(-40px);
&:after {
display: block;
content: "";
width: @line-height;
height: @line-height;
position: absolute;
z-index: 999;
background: #fff;
top: 0;
left: -30px;
}
}
}
}
hr {
color: @border;
height: 1px;
border: 0;
border-top: 1px solid @border;
margin: 20px 0;
padding: 0;
}
del,
em,
strong {
display: inline-block;
margin: @margin;
}
blockquote {
position: relative;
background: @background;
padding: 6px 12px;
border-left: 5px solid @divider;
border-radius: 2px;
margin: @margin;
}
/*基本样式*/
h1,
h2,
h3,
h4,
h5,
h6 {
color: @title;
}
h1 {
font-size: 28px;
border-bottom: 1px solid @border;
//text-align: center;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 18px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
font-size: 12px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
/* border-bottom: 1px solid @border; */
padding: 8px 0;
}
p {
font-size: 14px !important;
color: @content;
margin: @margin;
line-height: @line-height;
}
img {
display: block;
max-width: 90%;
margin: 20px auto;
}
table {
width: 100%;
border: 1px solid @border;
border-bottom: 0;
max-width: 960px;
margin: 0 auto !important;
flex: 1;
overflow: hidden;
overflow-y: scroll;
background: #fff;
border-spacing: 0;
border-collapse: collapse;
margin: 20px 0;
tr {
-webkit-transition: background 0.1s;
transition: background 0.1s;
text-align: nav;
}
tr td,
tr th {
padding: 0 8px;
font-size: 14px;
line-height: 39px;
color: #333;
border-bottom: 1px solid @border;
cursor: default;
}
th {
background: #f8f8f9;
text-align: left;
font-weight: bold;
}
tr:nth-of-type(even) {
td {
background: #f8f8f9;
}
}
tr{
&:hover{
td{
background: #eaf5f6;
>div{
padding: 10px 12px !important;
background: #fff;
&::-webkit-scrollbar {
display: none;
}
}
}
td,
th {
border: 1px solid @border;
&::-webkit-scrollbar {
display: none;
}
ul {
list-style: none;
padding: 0 20px;
li {
position: relative;
&:after {
display: block;
content: "";
width: 8px;
height: 8px;
border-radius: 50%;
position: absolute;
z-index: 99;
top: 7px;
left: -20px;
background: @content;
}
}
}
ol,
ul {
margin: 20px 0;
padding: 0 40px;
li {
font-size: 14px !important;
color: @content;
margin-bottom: 10px;
line-height: 24px;
padding-left: 12px;
input[type="checkbox"] {
position: relative;
cursor: pointer;
overflow: visible;
position: absolute;
left: 0;
top: 0;
&:after {
display: block;
content: "";
width: 16px;
height: 16px;
position: absolute;
z-index:99999;
background: #fff;
top: 0;
right: 0;
}
&:before {
display: block;
width: 18px;
height: 18px;
position: absolute;
content: '';
top: 2px;
left: -25px;
z-index: 999999;
background: url("../img/notChecked.jpg") no-repeat;
background-size: contain;
}
}
input[type="checkbox"]:checked {
&:before {
background: url("../img/checked.jpg") no-repeat;
background-size: contain;
}
}
}
}
ol {
list-style-type: decimal;
}
hr {
color: @border;
height: 1px;
border: 0;
border-top: 1px solid @border;
margin: 20px 0;
padding: 0;
}
del,
em,
strong {
display: inline-block;
margin: @margin;
}
blockquote {
position: relative;
background: @background;
padding: 6px 12px;
border-left: 5px solid @divider;
border-radius: 2px;
margin: @margin;
}
/*基本样式*/
h1,
h2,
h3,
h4,
h5,
h6 {
color: @title;
}
h1 {
font-size: 28px;
border-bottom: 1px solid @border;
//text-align: center;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 18px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
font-size: 12px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
/* border-bottom: 1px solid @border; */
padding: 8px 0;
font-weight: 600;
}
p {
font-size: 14px !important;
color: @content;
margin: @margin;
line-height: @line-height;
}
img {
display: block;
width: 90%;
margin: 20px auto;
}
table {
width: 100%;
border: 1px solid @border;
border-bottom: 0;
background: #fff;
border-spacing: 0;
border-collapse: collapse;
margin: 20px 0;
tr {
-webkit-transition: background 0.1s;
transition: background 0.1s;
text-align: nav;
}
tr td,
tr th {
padding: 0 8px;
font-size: 14px;
line-height: 39px;
color: #333;
border-bottom: 1px solid @border;
cursor: pointer;
}
th {
background: #f8f8f9;
text-align: left;
font-weight: bold;
}
tr:nth-of-type(even) {
td {
background: #f8f8f9;
}
}
tr {
&:hover {
td {
background: #eaf5f6;
}
}
}
td,
th {
border: 1px solid @border;
}
}
input[type="checkbox"] {
display: inline-block;
border-radius: 0;
margin-right: 8px;
}
a {
text-decoration: none;
color: @info;
font-size: 14px;
line-height: @line-height;
}
}
input[type="checkbox"] {
display: inline-block;
border-radius: 0;
margin-right: 8px;
}
a {
text-decoration: none;
color: @primary;
font-size: 14px;
line-height: @line-height;
}
}
@media only screen and (min-width: 1600px ) {
.markdown-preview {
max-width: 60%;
margin: 0 auto !important;
}
.markdown-preview {
max-width: 60%;
margin: 0 auto !important;
}
}

View File

@ -13,7 +13,7 @@
background: #1e1e1e;
color: #DCDCDC;
overflow-y: hidden !important;
overflow-x: scroll !important;
overflow-x: auto !important;
font-family: Menlo, Consolas, "Courier New", Courier, FreeMono, monospace !important;
* {
line-height: 1.6 !important;

View File

@ -14,7 +14,7 @@
line-height: 20px;
border-radius: 4px;
margin: 20px 0 !important;
overflow-x: scroll !important;
overflow-x: auto !important;
* {
font-family: Consolas !important;

View File

@ -1,219 +1,501 @@
@margin: 8px 0;
@line-height: 22px;
.markdown {
* {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
&.fullscreen {
position: fixed;
z-index: 999999;
top: 0;
left: 0;
right: 0;
bottom: 0;
.markdown-content {
padding: 0;
padding-top: 10px;
}
}
margin: 0;
padding: 0;
box-sizing: border-box;
}
&.fullscreen {
position: fixed;
z-index: 999;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
margin: 0;
padding: 0;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: column;
background: #eaeaea;
min-height: 400px;
.markdown-toolbars {
width: 100%;
display: flex;
align-items: center;
list-style: none;
background: #fff;
color: #464c5b;
height: 46px;
cursor: pointer;
box-shadow: 0 2px 3px #ddd;
padding-left: 4px;
border-bottom: 1px solid @border;
>li {
position: relative;
cursor: default;
&:after {
display: block;
content: attr(name);
position: absolute;
z-index: 999;
top: 34px;
left: 30%;
background: #e6e6e6;
color: #333;
white-space: nowrap;
font-size: 12px;
line-height: 20px;
padding: 0 6px;
border: 1px solid @border;
transition: all 0.3s;
transform: scale(0);
opacity: 0;
transform-origin: top;
}
&:hover {
&:after {
transform: scale(1);
opacity: 1;
}
}
&:last-child{
&:after{
right: 20%;
left: auto;
}
}
}
.empty {
flex: 1;
}
span {
padding: 0 8px;
transition: all 0.3s;
font-size: 14px;
display: inline-block;
line-height: 32px;
&:hover {
color: @primary;
background: @background;
border-radius: 3px;
}
}
.title {
padding-left: 4px;
padding-right: 10px;
}
li:last-child {
span {
font-size: 20px !important;
}
}
.shift-theme {
height: 46px;
//width: 80px;
position: relative;
display: flex;
justify-content: center;
align-items: center;
span {
padding: 0 8px;
transition: all 0.3s;
font-size: 18px;
display: inline-block;
line-height: 32px;
&:hover {
color: @primary;
background: @background;
border-radius: 3px;
}
}
ul {
position: absolute;
z-index: 9999999;
top: 46px;
left: 50%;
margin-left: -41px;
background: #fff;
list-style: none;
font-size: 12px;
opacity: 0;
transition: all 0.3s;
transform-origin: top left;
transform: scaleY(0);
border: 1px solid @border;
border-top: 0;
&.active {
opacity: 1;
transform: scaleY(1);
}
li {
transition: all 0.3s;
padding: 0 15px;
width: 82px;
line-height: 30px;
border-bottom: 1px dashed @border;
&:last-child {
border-bottom: none;
}
&:hover {
background: @background;
color: @primary;
}
}
}
}
}
.markdown-content {
flex: 1;
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
position: relative;
overflow: hidden;
padding-top: 12px;
.markdown-editor {
flex: 1;
min-height: 100%;
position: relative;
margin: 0 !important;
overflow: hidden;
overflow-y: scroll;
display: flex;
justify-content: space-between;
&::-webkit-scrollbar {
display: none;
}
.index {
background: #272727;
min-height: 100%;
width: 36px;
line-height: @line-height;
padding: 12px 0;
li {
background: #272727;
color: #ccc;
font-size: 14px;
text-align: center;
font-family: Consolas;
}
}
textarea {
align-items: center;
flex-direction: column;
background: @background;
min-height: 400px;
.markdown-toolbars {
width: 100%;
min-height: 100%;
outline: none;
border: 0;
background: #2d2d2d;
line-height: @line-height;
caret-color: #ccc;
color: #669acc;
font-size: 14px;
font-family: Consolas;
resize: none;
padding: 12px 8px;
overflow: hidden;
&::selection {
background: #999;
color: @primary;
display: flex;
align-items: center;
list-style: none;
background: #fff;
color: #464c5b;
height: 40px;
cursor: pointer;
//box-shadow: 0 2px 3px #ddd;
padding-left: 4px;
border-bottom: 1px solid @border;
> li {
position: relative;
cursor: pointer;
&:after {
display: block;
content: attr(name);
position: absolute;
z-index: 999999999999;
top: 32px;
left: 20px;
background: #e6e6e6;
color: #333;
white-space: nowrap;
font-size: 12px;
line-height: 20px;
padding: 0 6px;
border: 1px solid @border;
transition: all 0.3s 0.3s;
transform: scale(0);
opacity: 0;
transform-origin: top;
}
&:hover {
&:after {
transform: scale(1);
opacity: 1;
}
}
&:last-child {
&:after {
right: 20%;
left: auto;
}
}
.title{
font-size: 16px!important;
}
}
.empty {
flex: 1;
}
span {
font-size: 18px;
color: #999;
cursor: pointer;
display: block;
width: 30px;
height: 30px;
border-radius: 3px;
line-height: 30px;
text-align: center;
&:hover {
background: @background;
color: #0084ff;
}
}
.title {
padding-left: 4px;
padding-right: 10px;
}
li:last-child {
span {
font-size: 20px !important;
margin-right: 10px;
}
}
.shift-theme,.export-file {
height: 46px;
//width: 80px;
position: relative;
display: flex;
justify-content: center;
align-items: center;
span {
padding: 0 8px;
transition: all 0.3s;
font-size: 18px;
display: inline-block;
line-height: 32px;
&:hover {
color: #0084ff;
background: @background;
border-radius: 3px;
}
}
ul {
position: absolute;
transform: scale(0);
transition: all 0.3s;
left: -50%;
top: 40px;
width: 160px;
transform-origin: top center;
list-style: none;
margin: 0;
padding:6px 0;
box-sizing: border-box;
border: 1px solid @border;
background: #fff;
border-radius:4px;
position: absolute;
z-index: 9999999;
box-shadow: 0 2px 8px 0 rgba(0,0,0,0.1);
font-family: "Monospaced Number", "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
&.active {
opacity: 1;
transform: scaleY(1);
}
li {
line-height: 30px;
padding: 0 12px;
padding-left: 12px;
font-size: 13px;
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
color: @content;
.iconfont{
font-size: 14px;
display: block;
height: 30px;
width: 30px;
line-height: 30px;
overflow: hidden;
&:hover{
color: @content;
}
}
i{
font-size: 13px;
display: block;
font-style: normal;
flex: 1;
white-space: normal;
}
&:last-child {
border-bottom: 0;
.iconfont{
font-size: 14px!important;
margin: 0!important;
}
}
&:hover {
background: @background;
}
&.disabled {
cursor: not-allowed;
color: @disabled;
&:hover {
background: transparent;
}
}
}
}
}
.import-file{
position: relative;
overflow: hidden;
input{
position: absolute;
z-index: 9999;
left: 0;
top: 0;
display: block;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
}
}
}
.markdown-preview {
min-height: 100%;
flex: 1;
padding: 20px 12px;
background: #fff;
overflow: hidden;
overflow-y: scroll;
&::-webkit-scrollbar {
display: none;
}
.markdown-content {
flex: 1;
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
position: relative;
overflow: hidden;
//padding: 10px;
padding-bottom: 0;
.markdown-editor {
flex: 1;
min-height: 100%;
position: relative;
margin: 0 !important;
overflow: hidden;
overflow-y: scroll;
display: flex;
justify-content: space-between;
background: #2d2d2d;
&::-webkit-scrollbar {
display: none;
}
.index {
background: #272727;
min-height: 100%;
width: 36px;
line-height: @line-height;
padding: 12px 0;
li {
background: #272727;
color: #ccc;
font-size: 14px;
text-align: center;
font-family: Consolas;
}
}
textarea {
width: 100%;
min-height: 100%;
outline: none;
border: 0;
background: #2d2d2d;
line-height: @line-height;
caret-color: #ccc;
color: #669acc;
font-size: 14px;
font-family: Consolas;
resize: none;
padding: 12px 8px;
overflow: hidden;
&::selection {
background: #999;
color: #0084ff;
}
}
}
.markdown-preview {
min-height: 100%;
}
}
}
.insert-img-model {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 99999;
background: @mask;
padding-top: 12%;
transition: all 0.3s;
opacity: 0;
display: none;
.model-container {
background: #fff;
width: 480px;
margin: 0 auto;
border-radius: 4px;
transition: all 0.3s;
transform: scale(0);
transform-origin: center;
.model-head {
line-height: 32px;
padding: 0 12px;
background: @background;
border-radius: 4px 4px 0 0;
box-shadow: 0 1px 2px @border;
display: flex;
justify-content: space-between;
span:nth-of-type(2) {
font-size: 14px;
padding-left: 12px;
cursor: pointer;
&:hover {
color: @error;
}
}
}
.model-content {
padding: 20px 12px;
padding-top: 0;
min-height: 180px;
.insert-url {
padding: 42px 0;
display: flex;
justify-content: space-between;
align-items: center;
input {
display: block;
border: 1px solid #ccc;
font-size: 14px;
padding: 4px 8px;
line-height: 24px;
color: #333;
background: #fff;
border-radius: 4px;
writing-mode: horizontal-tb;
text-rendering: auto;
transition: box-shadow 2s;
flex: 1;
&:focus {
border-color: @info;
}
&::placeholder{
color: @tip;
}
}
a {
display: block;
background: @info;
color: #fff;
line-height: 32px;
height: 32px;
font-size: 13px;
padding: 0 12px;
border-radius: 3px;
margin-left: 20px;
border: 1px solid @border;
transition: all 0.3s;
&:hover {
background: @dark-info;
}
}
}
.insert-local {
height: 120px;
border: 1px dashed @border;
border-radius: 4px;
transition: all 0.3s;
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
cursor: pointer;
span {
font-size: 40px;
color: @border;
line-height: 50px;
transition: all 0.3s;
}
p {
font-size: 14px;
color: @content;
transition: all 0.3s;
}
&:hover {
border-color: @info;
span, p {
color: @info;
}
}
input {
display: block;
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
}
}
}
.model-foot {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 10px 12px;
display: none;
a {
display: block;
background: @background;
color: @title;
line-height: 26px;
height: 26px;
font-size: 13px;
padding: 0 12px;
border-radius: 3px;
margin-left: 12px;
border: 1px solid @border;
transition: all 0.3s;
&:hover {
background: @divider;
}
&.ok {
background: @info;
color: #fff;
border-color: @info;
&:hover {
background: @dark-info;
}
}
}
}
}
&.active{
opacity: 1;
display: block;
.model-container{
transform: scale(1);
}
}
}
ul.shift {
padding: 6px 12px;
display: flex;
align-items: center;
span {
font-size: 12px;
cursor: pointer;
user-select: none;
&.iconfont {
font-size: 14px;
}
}
label {
font-size: 12px;
padding-right: 10px;
position: relative;
cursor: pointer;
user-select: none;
}
input[type='radio'],
label {
transition: all 0.6s ease;
box-sizing: border-box;
}
input[type="radio"] + label::before {
content: "\a0";
display: inline-block;
vertical-align: middle;
margin-right: 4px;
width: 8px;
height: 8px;
border-radius: 50%;
border: 1px solid @primary;
padding: 2px;
}
input[type="radio"]:checked + label::before {
background-color: @primary;
background-clip: content-box;
padding: 2px;
}
input[type="radio"] {
position: absolute;
clip: rect(0, 0, 0, 0);
}
input[type="radio"]:checked + label {
color: @primary;
}
}
}

View File

@ -15,7 +15,7 @@
padding: 20px !important;
border-radius: 4px !important;
overflow-y: hidden !important;
overflow-x: scroll !important;
overflow-x: auto !important;
margin: 20px 0 !important;
code {
line-height: @line-height !important;

View File

@ -11,7 +11,7 @@
background: #292c34;
border-radius: 4px;
overflow-y: hidden !important;
overflow-x: scroll !important;
overflow-x: auto !important;
margin: 20px 0 !important;
* {
line-height: 1.6 !important;

View File

@ -2,20 +2,26 @@
/*
*Author zhaoxuhui
*/
@primary: #1890ff;
@line-height: 22px; //主体颜色
@primary: #2d8cf0;
@lightPrimary: #5cadff;
@darkPrimary: #2b85e4; //
@info: #2d8cf0;
//主体颜色
@primary: #292d35;
@light-primary: #323741;
@dark-primary: #1c1e24;
//
@info: #007acc;
@success: #19be6b;
@warning: #ff9900;
@error: #ed3f14; //
@title: #1c2438;
@content: #333;
@subColor: #80848f;
@error: #ed3f14;
//
@title: #252525;
@content: #555;
@sub-color: #80848f;
@disabled: #bbbec4;
@border: #dddee1;
@divider: #e9eaec;
@background: #f8f8f9;
@margin: 8px 0;
@background: #f7f7f7;
@tip:#c1c1c1;
@primary-rgba:rgba(49,204,102,0.2);
@mask:rgba(0, 0, 0, 0.3);
@blue-rgba:rgba(0,122,204,0.2);
@dark-info:#0169af;

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: 36 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,11 +1,5 @@
<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="{height:`${editorHeight}px`}" @mouseover="addListener" @mouseout="removeListener">
<!-- 头部工具栏 -->
<ul class="markdown-toolbars">
<li class="title" v-if="title" :style="{titleStyle}">{{title}}</li>
@ -18,23 +12,23 @@
<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)">h1</span>
<li v-if="tools.h1" name="标题1" >
<span @click="insertTitle(1)"class="title">h1</span>
</li>
<li v-if="tools.h2" name="标题2">
<span @click="insertTitle(2)">h2</span>
<span @click="insertTitle(2)"class="title">h2</span>
</li>
<li v-if="tools.h3" name="标题3">
<span @click="insertTitle(3)">h3</span>
<li v-if="tools.h3" name="标题3" >
<span @click="insertTitle(3)"class="title">h3</span>
</li>
<li v-if="tools.h4" name="标题4">
<span @click="insertTitle(4)">h4</span>
<li v-if="tools.h4" name="标题4" >
<span @click="insertTitle(4)"class="title">h4</span>
</li>
<li v-if="tools.h5" name="标题5">
<span @click="insertTitle(5)">h5</span>
<li v-if="tools.h5" name="标题5" >
<span @click="insertTitle(5)"class="title">h5</span>
</li>
<li v-if="tools.h6" name="标题6">
<span @click="insertTitle(6)">h6</span>
<span @click="insertTitle(6)"class="title">h6</span>
</li>
<li v-if="tools.hr" name="分割线">
<span @click="insertLine" class="iconfont icon-horizontal"></span>
@ -64,24 +58,29 @@
<span @click="insertImage" class="iconfont icon-img"></span>
</li>
<li v-if="tools.table" name="表格">
<span
@click="insertTable"
class="iconfont icon-table"></span>
<span @click="insertTable" class="iconfont icon-table"></span>
</li>
<li v-if="tools.print" name="打印">
<span class="iconfont icon-dayin" @click="print"></span>
</li>
<li v-if="tools.theme" class="shift-theme" name="代码块主题">
<div>
<span class="iconfont icon-theme" @click="toggleSlideDown"></span>
<ul :class="{active:slideDown}">
<li @click="setThemes('Light')">Light</li>
<li @click="setThemes('Dark')">Dark</li>
<li @click="setThemes('OneDark')">OneDark</li>
<li @click="setThemes('GitHub')">GitHub</li>
<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>
</ul>
</div>
</li>
<li name="导入本地文件" class="import-file" v-show="tools.importmd">
<span class="iconfont icon-daoru"></span>
<input type="file" @change="importFile($event)">
</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>
@ -102,28 +101,14 @@
</ul>
<!-- 编辑器 -->
<div class="markdown-content">
<div v-show="preview===1||preview===3" class="markdown-editor" ref="markdownContent" @scroll="markdownScroll"
@mouseenter="mousescrollSide('markdown')">
<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}`" v-html="previewMarkdown" ref="preview" @scroll="previewScroll" @mouseenter="mousescrollSide('preview')">
</div>
</div>
</div>
@ -133,6 +118,7 @@
import markdown from './markdown';
export default markdown;
</script>
<style lang="less">
@ -143,5 +129,6 @@
@import "css/gitHub";
@import "css/common";
@import "css/index";
@import "font/iconfont.css";
@import "./font/iconfont.css";
</style>

View File

@ -6,110 +6,112 @@ import Print from './js/print';
hljs.initHighlightingOnLoad();
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
highlight: function (code) {
renderer : new marked.Renderer(),
gfm : true,
tables : true,
breaks : false,
pedantic : false,
sanitize : true,
smartLists : true,
highlight : function (code) {
return hljs.highlightAuto(code).value;
}
});
export default {
name: 'markdown',
props: {
title: { // 标题
type: String,
default: ''
name : 'markdown',
props : {
title : { // 标题
type : String,
default : ''
},
titleStyle: { // 标题样式
type: Object,
default () {
titleStyle : {// 标题样式
type : Object,
default() {
return {}
}
},
theme: { // 默认主题
type: String,
default: 'Light'
theme : { // 默认主题
type : String,
default : 'Light'
},
width: { // 宽度
type: [Number, String],
default: 'auto'
width : {// 宽度
type : [Number, String],
default : 'auto'
},
height: { // 高度
type: Number,
default: 600
height : { // 高度
type : Number,
default : 600
}, // 宽度
toolbars: { // 工具栏
type: Object,
default () {
toolbars : { // 工具栏
type : Object,
default() {
return {};
}
},
autoSave: { // 是否自动保存
type: Boolean,
default: true
autoSave : {// 是否自动保存
type : Boolean,
default : true
},
interval: { // 自动保存频率 单位:毫秒
type: Number,
default: 10000
interval : { // 自动保存频率 单位:毫秒
type : Number,
default : 10000
},
initialValue: { // 初始化值
type: String,
default: ''
initialValue : { // 初始化值
type : String,
default : ''
},
mode: { // 模式 1 分屏显示 2 预览详情 3 全屏编辑
type: [Number, String],
default: 1
mode : { // 模式 1 分屏显示 2 预览详情 3 全屏编辑
type : [Number, String],
default : 1
}
},
data() {
return {
value: '', // 输入框内容
timeoutId: null,
hljsInit: null,
indexLenth: 1,
previewMarkdown: '',
preview: 1, // 是否是预览状态
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: true,
theme: true
value : '', // 输入框内容
timeoutId : null,
hljsInit : null,
indexLenth : 1,
previewMarkdown : '',
preview : 1, // 是否是预览状态
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
slideDown : false,
themeName : 'Light',// 主题名称
lastInsert : '',
timerId : null,// 定时器id
};
},
computed: {
computed : {
editorHeight() {
if (this.isFullscreen) {
return window.innerHeight;
@ -118,34 +120,26 @@ export default {
}
},
tools() {
const {
allTools,
toolbars
} = this;
const {allTools, toolbars} = this;
return Object.assign(allTools, toolbars)
}
},
mounted() {
this.$nextTick(() => {
this.$refs.textarea.focus();
})
this.init();
//this.addListener();
},
methods: {
methods : {
init() {
this.themeName = this.theme;
const {
autoSave,
interval,
theme,
initialValue,
mode
} = this;
const {autoSave, interval, theme, initialValue, mode} = this;
this.value = initialValue;
this.preview = mode;
this.previewMarkdown = marked(initialValue, {
sanitize: true
sanitize : true
});
if (autoSave) {
this.timerId = setInterval(() => {
@ -174,13 +168,11 @@ export default {
markdownContent.scrollTop = parseInt((previewScrollTop / previewScrollHeight) * markdownScrollHeight);
}
},
mousescrollSide(side) { // 设置究竟是哪个半边在主动滑动
mousescrollSide(side) {// 设置究竟是哪个半边在主动滑动
this.scroll = side;
},
insertContent(str) { // 插入文本
const {
preview
} = this;
const {preview} = this;
if (preview === 2) {
return;
}
@ -202,7 +194,7 @@ export default {
if (document.selection) {
textDom.focus();
let selectRange = document.selection.createRange();
selectRange.moveStart('character', -this.value.length);
selectRange.moveStart('character', - this.value.length);
cursorPos = selectRange.text.length;
} else if (textDom.selectionStart || textDom.selectionStart == '0') {
cursorPos = textDom.selectionStart;
@ -250,12 +242,12 @@ export default {
},
insertTitle(level) { // 插入标题
const titleLevel = {
1: '\n# ',
2: '\n## ',
3: '\n### ',
4: '\n#### ',
5: '\n##### ',
6: '\n###### '
1 : '\n# ',
2 : '\n## ',
3 : '\n### ',
4 : '\n#### ',
5 : '\n##### ',
6 : '\n###### '
};
this.insertContent(titleLevel[level]);
},
@ -351,12 +343,12 @@ export default {
},
insertTitle(level) { // 插入标题
const titleLevel = {
1: '# ',
2: '## ',
3: '### ',
4: '#### ',
5: '##### ',
6: '###### '
1 : '# ',
2 : '## ',
3 : '### ',
4 : '#### ',
5 : '##### ',
6 : '###### '
};
this.insertContent(titleLevel[level]);
},
@ -374,25 +366,23 @@ export default {
},
handleSave() { // 保存操作
this.$emit('on-save', {
markdownValue: this.value,
htmlValue: this.previewMarkdown,
theme: this.themeName
markdownValue : this.value,
htmlValue : this.previewMarkdown,
theme : this.theme
});
},
insertLine() { // 插入分割线
this.insertContent(`\n----\n`);
},
toggleSlideDown() { // 显示主题选项
this.slideDown = !this.slideDown;
this.slideDown = ! this.slideDown;
},
setThemes(name) { // 设置主题
this.themeName = name;
this.slideDown = false;
},
enter(e) { // 回车事件
const {
lastInsert
} = this;
const {lastInsert} = this;
const list = ['- ', '1. ', '- [ ] ', '- [x] ']
if (list.includes(lastInsert)) {
this.insertContent(lastInsert);
@ -410,26 +400,57 @@ export default {
},
addListener() { // 事件监听,阻止保存
this.removeListener();
document.addEventListener('keydown', this.listener)
document.addEventListener('keydown',this.listener)
},
removeListener() {
document.removeEventListener('keydown', this.listener)
removeListener(){
document.removeEventListener('keydown',this.listener)
},
listener(e) {
listener(e){
if (e.keyCode === 83) {
if (e.metaKey || e.ctrlKey) {
if( e.metaKey||e.ctrlKey){
e.preventDefault();
this.handleSave();
}
}
},
importFile(e) { // 导入文件
const file = e.target.files[0];
if (! file) {
return;
}
const {type} = file;
if (type !== 'text/markdown') {
this.$Notice.error('文件格式有误!');
return;
}
const reader = new FileReader();
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();
}
}
},
watch: {
watch : {
value() {
clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => {
this.previewMarkdown = marked(this.value, {
sanitize: true
sanitize : true
});
}, 30)
this.indexLenth = this.value.split('\n').length;
@ -437,12 +458,6 @@ export default {
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;
},
theme() {
this.themeName = this.theme;
}
},
destroyed() { // 销毁时清除定时器

View File

@ -2,11 +2,11 @@ var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: './src/main.js',
entry: './src/main.js', // 打包为npm包时将此处修改为 ./src/index.js
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js',
filename: 'build.js',// 打包为npm包时将此处修改为 index.js
libraryTarget: 'umd',
library: 'markdown-vue',
umdNamedDefine: true