mirror of
https://github.com/doocs/md.git
synced 2024-11-24 19:10:34 +08:00
feat:remove codemirror js/css in html
This commit is contained in:
parent
1740ac1cc8
commit
26238e6241
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.js
|
||||||
|
*.vue
|
@ -10,7 +10,8 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
'camelcase': 'off'
|
'camelcase': 'off',
|
||||||
|
'eqeqeq': 'off'
|
||||||
},
|
},
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
parser: 'babel-eslint'
|
parser: 'babel-eslint'
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
// 左右栏同步滚动
|
// 左右栏同步滚动
|
||||||
$(document).ready(() => {
|
$(document).ready(() => {
|
||||||
let timeout;
|
let timeout
|
||||||
$('div.CodeMirror-scroll, #preview').on("scroll", function callback() {
|
$('div.CodeMirror-scroll, #preview').on('scroll', function callback () {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout)
|
||||||
|
|
||||||
let source = $(this),
|
let source = $(this)
|
||||||
target = $(source.is("#preview") ? 'div.CodeMirror-scroll' : '#preview');
|
let target = $(source.is('#preview') ? 'div.CodeMirror-scroll' : '#preview')
|
||||||
|
|
||||||
target.off("scroll");
|
target.off('scroll')
|
||||||
|
|
||||||
let source0 = source[0];
|
let source0 = source[0]
|
||||||
let target0 = target[0];
|
let target0 = target[0]
|
||||||
|
|
||||||
let percentage = source0.scrollTop / (source0.scrollHeight - source0.offsetHeight);
|
let percentage = source0.scrollTop / (source0.scrollHeight - source0.offsetHeight)
|
||||||
let height = percentage * (target0.scrollHeight - target0.offsetHeight);
|
let height = percentage * (target0.scrollHeight - target0.offsetHeight)
|
||||||
target0.scrollTo(0, height);
|
target0.scrollTo(0, height)
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
target.on("scroll", callback);
|
target.on('scroll', callback)
|
||||||
}, 100);
|
}, 100)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
let default_theme = {
|
export const default_theme = {
|
||||||
BASE: {
|
BASE: {
|
||||||
'text-align': 'left',
|
'text-align': 'left',
|
||||||
'color': '#3f3f3f',
|
'color': '#3f3f3f',
|
||||||
'line-height': '1.75',
|
'line-height': '1.75'
|
||||||
},
|
},
|
||||||
BASE_BLOCK: {
|
BASE_BLOCK: {
|
||||||
'margin': '1em 8px'
|
'margin': '1em 8px'
|
||||||
@ -71,7 +71,7 @@ let default_theme = {
|
|||||||
'color': 'rgb(80, 80, 80)',
|
'color': 'rgb(80, 80, 80)',
|
||||||
'font-family': 'PingFangSC-light, PingFangTC-light, Open Sans, Helvetica Neue, sans-serif',
|
'font-family': 'PingFangSC-light, PingFangTC-light, Open Sans, Helvetica Neue, sans-serif',
|
||||||
'font-size': '1em',
|
'font-size': '1em',
|
||||||
'display': 'inline',
|
'display': 'inline'
|
||||||
},
|
},
|
||||||
|
|
||||||
code: {
|
code: {
|
||||||
@ -83,14 +83,14 @@ let default_theme = {
|
|||||||
'padding': '10px',
|
'padding': '10px',
|
||||||
'line-height': '1.5',
|
'line-height': '1.5',
|
||||||
'border': '1px solid rgb(236,236,236)',
|
'border': '1px solid rgb(236,236,236)',
|
||||||
'margin': '20px 0',
|
'margin': '20px 0'
|
||||||
},
|
},
|
||||||
|
|
||||||
image: {
|
image: {
|
||||||
'border-radius': '4px',
|
'border-radius': '4px',
|
||||||
'display': 'block',
|
'display': 'block',
|
||||||
'margin': '0.1em auto 0.5em',
|
'margin': '0.1em auto 0.5em',
|
||||||
'width': '100% !important',
|
'width': '100% !important'
|
||||||
},
|
},
|
||||||
|
|
||||||
image_org: {
|
image_org: {
|
||||||
@ -115,7 +115,7 @@ let default_theme = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
figure: {
|
figure: {
|
||||||
'margin': '1.5em 8px',
|
'margin': '1.5em 8px'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inline: {
|
inline: {
|
||||||
@ -130,7 +130,7 @@ let default_theme = {
|
|||||||
'color': '#d14',
|
'color': '#d14',
|
||||||
'background': 'rgba(27,31,35,.05)',
|
'background': 'rgba(27,31,35,.05)',
|
||||||
'padding': '3px 5px',
|
'padding': '3px 5px',
|
||||||
'border-radius': '4px',
|
'border-radius': '4px'
|
||||||
},
|
},
|
||||||
|
|
||||||
link: {
|
link: {
|
||||||
@ -139,13 +139,13 @@ let default_theme = {
|
|||||||
|
|
||||||
wx_link: {
|
wx_link: {
|
||||||
'color': '#576b95',
|
'color': '#576b95',
|
||||||
'text-decoration': 'none',
|
'text-decoration': 'none'
|
||||||
},
|
},
|
||||||
|
|
||||||
// 字体加粗样式
|
// 字体加粗样式
|
||||||
strong: {
|
strong: {
|
||||||
'color': 'rgba(15, 76, 129, 0.9)',
|
'color': 'rgba(15, 76, 129, 0.9)',
|
||||||
'font-weight': 'bold',
|
'font-weight': 'bold'
|
||||||
},
|
},
|
||||||
|
|
||||||
table: {
|
table: {
|
||||||
@ -174,4 +174,4 @@ let default_theme = {
|
|||||||
'font-size': '0.8em'
|
'font-size': '0.8em'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
@ -46,8 +46,8 @@
|
|||||||
<link rel="stylesheet" href="libs/css/code-themes/github-v2.min.css">
|
<link rel="stylesheet" href="libs/css/code-themes/github-v2.min.css">
|
||||||
|
|
||||||
<!-- codemirror -->
|
<!-- codemirror -->
|
||||||
<link rel="stylesheet" href="libs/css/codemirror.min.css">
|
<!-- <link rel="stylesheet" href="libs/css/codemirror.min.css"> -->
|
||||||
<link rel="stylesheet" href="libs/css/show-hint.css">
|
<!-- <link rel="stylesheet" href="libs/css/show-hint.css"> -->
|
||||||
<link rel="stylesheet" href="libs/css/style-mirror.css">
|
<link rel="stylesheet" href="libs/css/style-mirror.css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="libs/css/animate.css">
|
<link rel="stylesheet" href="libs/css/animate.css">
|
||||||
@ -59,41 +59,21 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="loading" id="loading">
|
|
||||||
<div class="loading-wrapper">
|
|
||||||
<div class="loading-text">Loading...</div>
|
|
||||||
<div class="loading-anim"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="app" >
|
<div id="app" >
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!--应用主体-->
|
|
||||||
|
|
||||||
<script src="libs/scripts/marked.min.js"></script>
|
|
||||||
|
|
||||||
<!-- codemirror -->
|
<!-- codemirror -->
|
||||||
<script src="libs/scripts/codemirror/codemirror.min.js"></script>
|
<script src="libs/scripts/codemirror/codemirror.min.js"></script>
|
||||||
<script src="libs/scripts/codemirror/css.js"></script>
|
|
||||||
<script src="libs/scripts/codemirror/matchbrackets.js"></script>
|
|
||||||
<script src="libs/scripts/codemirror/active-line.js"></script>
|
|
||||||
<script src="libs/scripts/codemirror/show-hint.js"></script>
|
|
||||||
<script src="libs/scripts/codemirror/css-hint.js"></script>
|
|
||||||
<script src="libs/scripts/codemirror/format.js"></script>
|
|
||||||
|
|
||||||
<script src="libs/scripts/markdown.min.js"></script>
|
<script src="libs/scripts/markdown.min.js"></script>
|
||||||
<script src="libs/scripts/prettify.min.js"></script>
|
<script src="libs/scripts/prettify.min.js"></script>
|
||||||
<!-- <script src="libs/scripts/index.js"></script> -->
|
|
||||||
<script src="libs/scripts/jquery.min.js"></script>
|
<script src="libs/scripts/jquery.min.js"></script>
|
||||||
<script src="libs/scripts/closebrackets.js"></script>
|
<script src="libs/scripts/closebrackets.js"></script>
|
||||||
|
|
||||||
<script src="assets/scripts/sync-scroll.js"></script>
|
<script src="assets/scripts/sync-scroll.js"></script>
|
||||||
<script src="assets/scripts/themes/default-theme.js"></script>
|
|
||||||
<script src="assets/scripts/util.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
$('#loading').hide();
|
|
||||||
window.console
|
window.console
|
||||||
&& window.console.log
|
&& window.console.log
|
||||||
&& (console.log("Think big, train fast, learn deep. See https://github.com/yanglbme"))
|
&& (console.log("Think big, train fast, learn deep. See https://github.com/yanglbme"))
|
||||||
|
@ -1,86 +1,111 @@
|
|||||||
(function() {
|
CodeMirror.extendMode('css', {
|
||||||
|
commentStart: '/*',
|
||||||
CodeMirror.extendMode("css", {
|
commentEnd: '*/',
|
||||||
commentStart: "/*",
|
newlineAfterToken: function (type, content) {
|
||||||
commentEnd: "*/",
|
return /^[;{}]$/.test(content)
|
||||||
newlineAfterToken: function(type, content) {
|
|
||||||
return /^[;{}]$/.test(content);
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// Comment/uncomment the specified range
|
// Comment/uncomment the specified range
|
||||||
CodeMirror.defineExtension("commentRange", function (isComment, from, to) {
|
CodeMirror.defineExtension('commentRange', function (isComment, from, to) {
|
||||||
var cm = this, curMode = CodeMirror.innerMode(cm.getMode(), cm.getTokenAt(from).state).mode;
|
var cm = this
|
||||||
cm.operation(function() {
|
var curMode = CodeMirror.innerMode(cm.getMode(), cm.getTokenAt(from).state)
|
||||||
if (isComment) { // Comment range
|
.mode
|
||||||
cm.replaceRange(curMode.commentEnd, to);
|
cm.operation(function () {
|
||||||
cm.replaceRange(curMode.commentStart, from);
|
if (isComment) {
|
||||||
if (from.line == to.line && from.ch == to.ch) // An empty comment inserted - put cursor inside
|
// Comment range
|
||||||
cm.setCursor(from.line, from.ch + curMode.commentStart.length);
|
cm.replaceRange(curMode.commentEnd, to)
|
||||||
} else { // Uncomment range
|
cm.replaceRange(curMode.commentStart, from)
|
||||||
var selText = cm.getRange(from, to);
|
if (from.line == to.line && from.ch == to.ch) {
|
||||||
var startIndex = selText.indexOf(curMode.commentStart);
|
// An empty comment inserted - put cursor inside
|
||||||
var endIndex = selText.lastIndexOf(curMode.commentEnd);
|
cm.setCursor(from.line, from.ch + curMode.commentStart.length)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Uncomment range
|
||||||
|
var selText = cm.getRange(from, to)
|
||||||
|
var startIndex = selText.indexOf(curMode.commentStart)
|
||||||
|
var endIndex = selText.lastIndexOf(curMode.commentEnd)
|
||||||
if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) {
|
if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) {
|
||||||
// Take string till comment start
|
// Take string till comment start
|
||||||
selText = selText.substr(0, startIndex)
|
selText =
|
||||||
|
selText.substr(0, startIndex) +
|
||||||
// From comment start till comment end
|
// From comment start till comment end
|
||||||
+ selText.substring(startIndex + curMode.commentStart.length, endIndex)
|
selText.substring(
|
||||||
|
startIndex + curMode.commentStart.length,
|
||||||
|
endIndex
|
||||||
|
) +
|
||||||
// From comment end till string end
|
// From comment end till string end
|
||||||
+ selText.substr(endIndex + curMode.commentEnd.length);
|
selText.substr(endIndex + curMode.commentEnd.length)
|
||||||
}
|
}
|
||||||
cm.replaceRange(selText, from, to);
|
cm.replaceRange(selText, from, to)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
// Applies automatic mode-aware indentation to the specified range
|
// Applies automatic mode-aware indentation to the specified range
|
||||||
CodeMirror.defineExtension("autoIndentRange", function (from, to) {
|
CodeMirror.defineExtension('autoIndentRange', function (from, to) {
|
||||||
var cmInstance = this;
|
var cmInstance = this
|
||||||
this.operation(function () {
|
this.operation(function () {
|
||||||
for (var i = from.line; i <= to.line; i++) {
|
for (var i = from.line; i <= to.line; i++) {
|
||||||
cmInstance.indentLine(i, "smart");
|
cmInstance.indentLine(i, 'smart')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
// Applies automatic formatting to the specified range
|
// Applies automatic formatting to the specified range
|
||||||
CodeMirror.defineExtension("autoFormatRange", function (from, to) {
|
CodeMirror.defineExtension('autoFormatRange', function (from, to) {
|
||||||
var cm = this;
|
var cm = this
|
||||||
var outer = cm.getMode(), text = cm.getRange(from, to).split("\n");
|
var outer = cm.getMode()
|
||||||
var state = CodeMirror.copyState(outer, cm.getTokenAt(from).state);
|
var text = cm.getRange(from, to).split('\n')
|
||||||
var tabSize = cm.getOption("tabSize");
|
var state = CodeMirror.copyState(outer, cm.getTokenAt(from).state)
|
||||||
|
var tabSize = cm.getOption('tabSize')
|
||||||
|
|
||||||
var out = "", lines = 0, atSol = from.ch == 0;
|
var out = ''
|
||||||
function newline() {
|
var lines = 0
|
||||||
out += "\n";
|
var atSol = from.ch == 0
|
||||||
atSol = true;
|
function newline () {
|
||||||
++lines;
|
out += '\n'
|
||||||
|
atSol = true
|
||||||
|
++lines
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < text.length; ++i) {
|
for (var i = 0; i < text.length; ++i) {
|
||||||
var stream = new CodeMirror.StringStream(text[i], tabSize);
|
var stream = new CodeMirror.StringStream(text[i], tabSize)
|
||||||
while (!stream.eol()) {
|
while (!stream.eol()) {
|
||||||
var inner = CodeMirror.innerMode(outer, state);
|
var inner = CodeMirror.innerMode(outer, state)
|
||||||
var style = outer.token(stream, state), cur = stream.current();
|
var style = outer.token(stream, state)
|
||||||
stream.start = stream.pos;
|
var cur = stream.current()
|
||||||
|
stream.start = stream.pos
|
||||||
if (!atSol || /\S/.test(cur)) {
|
if (!atSol || /\S/.test(cur)) {
|
||||||
out += cur;
|
out += cur
|
||||||
atSol = false;
|
atSol = false
|
||||||
}
|
}
|
||||||
if (!atSol && inner.mode.newlineAfterToken &&
|
if (
|
||||||
inner.mode.newlineAfterToken(style, cur, stream.string.slice(stream.pos) || text[i+1] || "", inner.state))
|
!atSol &&
|
||||||
newline();
|
inner.mode.newlineAfterToken &&
|
||||||
|
inner.mode.newlineAfterToken(
|
||||||
|
style,
|
||||||
|
cur,
|
||||||
|
stream.string.slice(stream.pos) || text[i + 1] || '',
|
||||||
|
inner.state
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
newline()
|
||||||
}
|
}
|
||||||
if (!stream.pos && outer.blankLine) outer.blankLine(state);
|
}
|
||||||
if (!atSol) newline();
|
if (!stream.pos && outer.blankLine) outer.blankLine(state)
|
||||||
|
if (!atSol) newline()
|
||||||
}
|
}
|
||||||
|
|
||||||
cm.operation(function () {
|
cm.operation(function () {
|
||||||
cm.replaceRange(out, from, to);
|
cm.replaceRange(out, from, to)
|
||||||
for (var cur = from.line + 1, end = from.line + lines; cur <= end; ++cur)
|
for (
|
||||||
cm.indentLine(cur, "smart");
|
var cur = from.line + 1, end = from.line + lines;
|
||||||
cm.setSelection(from, cm.getCursor(false));
|
cur <= end;
|
||||||
});
|
++cur
|
||||||
});
|
) {
|
||||||
})();
|
cm.indentLine(cur, 'smart')
|
||||||
|
}
|
||||||
|
cm.setSelection(from, cm.getCursor(false))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
498
src/App.vue
498
src/App.vue
@ -1,492 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app" class="container">
|
<loading v-if="loading" />
|
||||||
<el-container>
|
<codemirror-editor v-else/>
|
||||||
<el-header class="top">
|
|
||||||
<!-- 图片上传 -->
|
|
||||||
<el-upload action="https://imgkr.com/api/files/upload" :headers="{'Content-Type': 'multipart/form-data'}"
|
|
||||||
:show-file-list="false" :multiple="true" accept=".jpg,.jpeg,.png,.gif" name="file"
|
|
||||||
:before-upload="beforeUpload" :on-success="uploaded">
|
|
||||||
<el-tooltip class="item" effect="dark" content="上传图片" placement="bottom-start">
|
|
||||||
<i class="el-icon-upload" size="medium"> </i>
|
|
||||||
</el-tooltip>
|
|
||||||
</el-upload>
|
|
||||||
<!-- 下载文本文档 -->
|
|
||||||
<el-tooltip class="item" effect="dark" content="下载编辑框Markdown文档" placement="bottom-start">
|
|
||||||
<i class="el-icon-download" size="medium" @click="downloadEditorContent"> </i>
|
|
||||||
</el-tooltip>
|
|
||||||
<!-- 页面重置 -->
|
|
||||||
<el-tooltip class="item" effect="dark" content="重置页面" placement="bottom-start">
|
|
||||||
<i class="el-icon-refresh" size="medium" @click="reset"> </i>
|
|
||||||
</el-tooltip>
|
|
||||||
<!-- 插入表格 -->
|
|
||||||
<el-tooltip class="item" effect="dark" content="插入表格" placement="bottom-start">
|
|
||||||
<i class="el-icon-s-grid" size="medium" @click="dialogFormVisible = true"> </i>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-form size="mini" class="ctrl" :inline=true>
|
|
||||||
<el-form-item>
|
|
||||||
<el-select v-model="currentFont" size="mini" placeholder="选择字体" clearable @change="fontChanged">
|
|
||||||
<el-option v-for="font in builtinFonts" :style="{fontFamily: font.value}" :key="font.value"
|
|
||||||
:label="font.label" :value="font.value">
|
|
||||||
<span class="select-item-left">{{ font.label }}</span>
|
|
||||||
<span class="select-item-right">Abc</span>
|
|
||||||
</el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-select v-model="currentSize" size="mini" placeholder="选择段落字号" clearable @change="sizeChanged">
|
|
||||||
<el-option v-for="size in sizeOption" :key="size.value" :label="size.label" :value="size.value">
|
|
||||||
<span class="select-item-left">{{ size.label }}</span>
|
|
||||||
<span class="select-item-right">{{ size.desc }}</span>
|
|
||||||
</el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-select v-model="currentColor" size="mini" placeholder="选择颜色" clearable @change="colorChanged">
|
|
||||||
<el-option v-for="color in colorOption" :key="color.value" :label="color.label" :value="color.value">
|
|
||||||
<span class="select-item-left">{{ color.label }}</span>
|
|
||||||
<span class="select-item-right">{{ color.hex }}</span>
|
|
||||||
</el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-tooltip content="自定义颜色" placement="top">
|
|
||||||
<el-color-picker v-model="currentColor" size="mini" show-alpha @change="colorChanged"></el-color-picker>
|
|
||||||
</el-tooltip>
|
|
||||||
|
|
||||||
<el-tooltip content="微信外链自动转为文末引用" placement="top">
|
|
||||||
<el-switch v-model="status" active-color="#67c23a" inactive-color="#dcdfe6" @change="statusChanged">
|
|
||||||
</el-switch>
|
|
||||||
</el-tooltip>
|
|
||||||
</el-form>
|
|
||||||
<el-tooltip class="item" effect="dark" content="自定义CSS样式" placement="left">
|
|
||||||
<el-button type="success" plain size="medium" icon="el-icon-setting" @click="customStyle"></el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-button type="success" plain size="medium" @click="copy">复制</el-button>
|
|
||||||
<el-button type="success" plain size="medium" class="about" @click="aboutDialogVisible = true">关于</el-button>
|
|
||||||
</el-header>
|
|
||||||
<el-main class="main-body">
|
|
||||||
<el-row :gutter="10" class="main-section">
|
|
||||||
<el-col :span="12">
|
|
||||||
<textarea id="editor" type="textarea" placeholder="Your markdown text here." v-model="source">
|
|
||||||
</textarea>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12" class="preview-wrapper" id="preview">
|
|
||||||
<section>
|
|
||||||
<div class="preview" contenteditable="true">
|
|
||||||
<section id="output" v-html="output">
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</el-col>
|
|
||||||
<transition name="custom-classes-transition" enter-active-class="animated bounceInRight">
|
|
||||||
<el-col id="cssBox" :span="12" v-show="showBox">
|
|
||||||
<textarea id="cssEditor" type="textarea" placeholder="Your custom css here.">
|
|
||||||
</textarea>
|
|
||||||
</el-col>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
</el-row>
|
|
||||||
</el-main>
|
|
||||||
</el-container>
|
|
||||||
<el-dialog title="关于" :visible.sync="aboutDialogVisible" width="30%" center>
|
|
||||||
<div style="text-align: center;">
|
|
||||||
<h3>一款高度简洁的微信 Markdown 编辑器</h3>
|
|
||||||
</div>
|
|
||||||
<div style="text-align: center;margin-top:10px;">
|
|
||||||
<p>扫码关注我的公众号,原创技术文章第一时间推送!</p>
|
|
||||||
<img src="assets/images/qrcode-for-doocs.jpg" style="width: 40%; display: block; margin: 20px auto 10px;">
|
|
||||||
</div>
|
|
||||||
<span slot="footer" class="dialog-footer">
|
|
||||||
<a href="https://github.com/doocs/md" target="_blank">
|
|
||||||
<el-button type="success" plain>GitHub 仓库</el-button>
|
|
||||||
</a>
|
|
||||||
<a href="https://gitee.com/doocs/md" target="_blank">
|
|
||||||
<el-button type="success" plain>Gitee 仓库</el-button>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</el-dialog>
|
|
||||||
<el-dialog title="插入表格" :visible.sync="dialogFormVisible">
|
|
||||||
<el-form :model="form">
|
|
||||||
<el-form-item label="行数(表头不计入行数)">
|
|
||||||
<el-input v-model="form.rows"></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="列数">
|
|
||||||
<el-input v-model="form.cols"></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<div slot="footer" class="dialog-footer">
|
|
||||||
<el-button type="success" plain @click="dialogFormVisible = false">取 消</el-button>
|
|
||||||
<el-button type="success" @click="insertTable">确 定</el-button>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import CodeMirror from 'codemirror/lib/codemirror'
|
import Loading from './components/Loading'
|
||||||
import 'codemirror/theme/ambiance.css'
|
import CodemirrorEditor from './components/CodemirrorEditor'
|
||||||
import axios from 'axios'
|
|
||||||
import WxRenderer from './scripts/renderers/wx-renderer'
|
|
||||||
import marked from 'marked'
|
|
||||||
import {
|
|
||||||
setColorWithCustomTemplate,
|
|
||||||
setColor,
|
|
||||||
setFontSize,
|
|
||||||
css2json,
|
|
||||||
customCssWithTemplate
|
|
||||||
} from './scripts/util'
|
|
||||||
import DEFAULT_CONTENT from './scripts/default-content'
|
|
||||||
import DEFAULT_CSS_CONTENT from './scripts/themes/default-theme-css'
|
|
||||||
require('codemirror/mode/javascript/javascript')
|
|
||||||
export default {
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
Loading,
|
||||||
|
CodemirrorEditor
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
let d = {
|
return {
|
||||||
wxRenderer: null,
|
loading: true
|
||||||
aboutOutput: '',
|
|
||||||
output: '',
|
|
||||||
source: '',
|
|
||||||
editor: null,
|
|
||||||
cssEditor: null,
|
|
||||||
builtinFonts: [
|
|
||||||
{ label: '无衬线', value: '-apple-system-font,BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB , Microsoft YaHei UI , Microsoft YaHei ,Arial,sans-serif' },
|
|
||||||
{ label: '衬线', value: "Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, 'PingFang SC', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif" }
|
|
||||||
],
|
|
||||||
sizeOption: [
|
|
||||||
{ label: '12px', value: '12px', desc: '更小' },
|
|
||||||
{ label: '13px', value: '13px', desc: '稍小' },
|
|
||||||
{ label: '14px', value: '14px', desc: '推荐' },
|
|
||||||
{ label: '15px', value: '15px', desc: '稍大' },
|
|
||||||
{ label: '16px', value: '16px', desc: '更大' }
|
|
||||||
],
|
|
||||||
colorOption: [
|
|
||||||
{ label: '经典蓝', value: 'rgba(15, 76, 129, 1)', hex: '最新流行' },
|
|
||||||
{ label: '翡翠绿', value: 'rgba(0, 152, 116, 1)', hex: '优雅清新' },
|
|
||||||
{ label: '活力橘', value: 'rgba(250, 81, 81, 1)', hex: '热情活泼' }
|
|
||||||
],
|
|
||||||
showBox: true,
|
|
||||||
aboutDialogVisible: false,
|
|
||||||
dialogFormVisible: false,
|
|
||||||
form: {
|
|
||||||
rows: 1,
|
|
||||||
cols: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.currentFont = d.builtinFonts[0].value
|
|
||||||
d.currentSize = d.sizeOption[2].value
|
|
||||||
d.currentColor = d.colorOption[1].value
|
|
||||||
d.status = '1'
|
|
||||||
return d
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.currentFont = localStorage.getItem('fonts') || this.builtinFonts[0].value
|
|
||||||
this.currentColor = localStorage.getItem('color') || this.colorOption[1].value
|
|
||||||
this.currentSize = localStorage.getItem('size') || this.sizeOption[2].value
|
|
||||||
this.status = localStorage.getItem('status') === 'true'
|
|
||||||
this.showBox = false
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.editor = CodeMirror.fromTextArea(
|
|
||||||
document.getElementById('editor'),
|
|
||||||
{
|
|
||||||
value: '',
|
|
||||||
mode: 'text/x-markdown',
|
|
||||||
theme: 'xq-light',
|
|
||||||
lineNumbers: false,
|
|
||||||
lineWrapping: true,
|
|
||||||
styleActiveLine: true,
|
|
||||||
autoCloseBrackets: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
this.cssEditor = CodeMirror.fromTextArea(
|
|
||||||
document.getElementById('cssEditor'), {
|
|
||||||
value: '',
|
|
||||||
mode: 'css',
|
|
||||||
theme: 'style-mirror',
|
|
||||||
lineNumbers: false,
|
|
||||||
lineWrapping: true,
|
|
||||||
matchBrackets: true,
|
|
||||||
autofocus: true,
|
|
||||||
extraKeys: {
|
|
||||||
'Ctrl-F': function autoFormat (editor) {
|
|
||||||
const totalLines = editor.lineCount()
|
|
||||||
editor.autoFormatRange({ line: 0, ch: 0 }, { line: totalLines })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
// 自动提示
|
|
||||||
this.cssEditor.on('keyup', (cm, e) => {
|
|
||||||
if ((e.keyCode >= 65 && e.keyCode <= 90) || e.keyCode === 189) {
|
|
||||||
cm.showHint(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.editor.on('change', (cm, e) => {
|
|
||||||
this.refresh()
|
|
||||||
this.saveEditorContent(this.editor, '__editor_content')
|
|
||||||
})
|
|
||||||
|
|
||||||
// 粘贴上传图片并插入
|
|
||||||
this.editor.on('paste', (cm, e) => {
|
|
||||||
if (!(e.clipboardData && e.clipboardData.items)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for (let i = 0, len = e.clipboardData.items.length; i < len; ++i) {
|
|
||||||
let item = e.clipboardData.items[i]
|
|
||||||
if (item.kind === 'file') {
|
|
||||||
const pasteFile = item.getAsFile()
|
|
||||||
if (!(this.checkType(pasteFile) && this.checkImageSize(pasteFile))) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let data = new FormData()
|
|
||||||
data.append('file', pasteFile)
|
|
||||||
axios.post(
|
|
||||||
'https://imgkr.com/api/files/upload',
|
|
||||||
data,
|
|
||||||
{
|
|
||||||
headers: { 'Content-Type': 'multipart/form-data' }
|
|
||||||
}
|
|
||||||
).then(resp => {
|
|
||||||
this.uploaded(resp.data)
|
|
||||||
}).catch(err => {
|
|
||||||
console.log(err.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.cssEditor.on('update', (instance) => {
|
|
||||||
this.cssChanged()
|
|
||||||
this.saveEditorContent(this.cssEditor, '__css_content')
|
|
||||||
})
|
|
||||||
// 如果有编辑器内容被保存则读取,否则加载默认内容
|
|
||||||
this.loadLocalStorage(this.editor, '__editor_content', DEFAULT_CONTENT)
|
|
||||||
this.loadLocalStorage(this.cssEditor, '__css_content', DEFAULT_CSS_CONTENT)
|
|
||||||
})
|
|
||||||
this.wxRenderer = new WxRenderer({
|
|
||||||
theme: setColor(this.currentColor),
|
|
||||||
fonts: this.currentFont,
|
|
||||||
size: this.currentSize,
|
|
||||||
status: this.status
|
|
||||||
})
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
renderWeChat (source) {
|
|
||||||
let output = marked(source, { renderer: this.wxRenderer.getRenderer(this.status) })
|
|
||||||
// 去除第一行的 margin-top
|
|
||||||
output = output.replace(/(style=".*?)"/, '$1;margin-top: 0"')
|
|
||||||
if (this.status) {
|
|
||||||
// 引用脚注
|
|
||||||
output += this.wxRenderer.buildFootnotes()
|
|
||||||
// 附加的一些 style
|
|
||||||
output += this.wxRenderer.buildAddition()
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
},
|
|
||||||
editorThemeChanged (editorTheme) {
|
|
||||||
this.editor.setOption('theme', editorTheme)
|
|
||||||
},
|
|
||||||
fontChanged (fonts) {
|
|
||||||
this.wxRenderer.setOptions({
|
|
||||||
fonts: fonts
|
|
||||||
})
|
|
||||||
this.currentFont = fonts
|
|
||||||
localStorage.setItem('fonts', fonts)
|
|
||||||
this.refresh()
|
|
||||||
},
|
|
||||||
sizeChanged (size) {
|
|
||||||
this.wxRenderer.setOptions({
|
|
||||||
size: size
|
|
||||||
})
|
|
||||||
let theme = setFontSize(size.replace('px', ''))
|
|
||||||
theme = setColorWithCustomTemplate(theme, this.currentColor)
|
|
||||||
this.wxRenderer.setOptions({
|
|
||||||
theme: theme
|
|
||||||
})
|
|
||||||
this.currentSize = size
|
|
||||||
localStorage.setItem('size', size)
|
|
||||||
this.refresh()
|
|
||||||
},
|
|
||||||
colorChanged (color) {
|
|
||||||
let theme = setFontSize(this.currentSize.replace('px', ''))
|
|
||||||
theme = setColorWithCustomTemplate(theme, color)
|
|
||||||
this.wxRenderer.setOptions({
|
|
||||||
theme: theme
|
|
||||||
})
|
|
||||||
this.currentColor = color
|
|
||||||
localStorage.setItem('color', color)
|
|
||||||
this.refresh()
|
|
||||||
},
|
|
||||||
cssChanged () {
|
|
||||||
let json = css2json(this.cssEditor.getValue(0))
|
|
||||||
let theme = setFontSize(this.currentSize.replace('px', ''))
|
|
||||||
theme = customCssWithTemplate(json, this.currentColor, theme)
|
|
||||||
this.wxRenderer.setOptions({
|
|
||||||
theme: theme
|
|
||||||
})
|
|
||||||
this.refresh()
|
|
||||||
},
|
|
||||||
// 图片上传前的处理
|
|
||||||
beforeUpload (file) {
|
|
||||||
return this.checkType(file) && this.checkImageSize(file)
|
|
||||||
},
|
|
||||||
// 检查文件类型
|
|
||||||
checkType (file) {
|
|
||||||
if (!/\.(gif|jpg|jpeg|png|GIF|JPG|PNG)$/.test(file.name)) {
|
|
||||||
this.$message({
|
|
||||||
showClose: true,
|
|
||||||
message: '请上传 JPG/PNG/GIF 格式的图片',
|
|
||||||
type: 'error'
|
|
||||||
})
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
// 检查图片大小
|
|
||||||
checkImageSize (file) {
|
|
||||||
if (file.size > 5 * 1024 * 1024) {
|
|
||||||
this.$message({
|
|
||||||
showClose: true,
|
|
||||||
message: '由于公众号限制,图片大小不能超过 5.0M',
|
|
||||||
type: 'error'
|
|
||||||
})
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
// 图片上传结束
|
|
||||||
uploaded (response, file, fileList) {
|
|
||||||
if (response.success) {
|
|
||||||
// 上传成功,获取光标
|
|
||||||
const cursor = this.editor.getCursor()
|
|
||||||
const imageUrl = response.data
|
|
||||||
const markdownImage = `![](${imageUrl})`
|
|
||||||
// 将 Markdown 形式的 URL 插入编辑框光标所在位置
|
|
||||||
this.editor.replaceSelection(`\n${markdownImage}\n`, cursor)
|
|
||||||
this.$message({
|
|
||||||
showClose: true,
|
|
||||||
message: '图片插入成功',
|
|
||||||
type: 'success'
|
|
||||||
})
|
|
||||||
this.refresh()
|
|
||||||
} else {
|
|
||||||
// 上传失败
|
|
||||||
this.$message({
|
|
||||||
showClose: true,
|
|
||||||
message: response.message,
|
|
||||||
type: 'error'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 刷新右侧预览
|
mounted () {
|
||||||
refresh () {
|
setTimeout(() => {
|
||||||
this.output = this.renderWeChat(this.editor.getValue(0))
|
this.loading = false
|
||||||
},
|
}, 200)
|
||||||
// 重置页面
|
|
||||||
reset () {
|
|
||||||
this.$confirm('此操作将丢失本地缓存的文本和自定义样式,是否继续?', '提示', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
confirmButtonClass: 'el-button--success',
|
|
||||||
cancelButtonClass: 'el-button--success is-plain',
|
|
||||||
type: 'warning',
|
|
||||||
center: true
|
|
||||||
}).then(() => {
|
|
||||||
localStorage.clear()
|
|
||||||
this.editor.setValue(DEFAULT_CONTENT)
|
|
||||||
this.cssEditor.setValue(DEFAULT_CSS_CONTENT)
|
|
||||||
this.editor.focus()
|
|
||||||
this.status = '1'
|
|
||||||
this.fontChanged(this.builtinFonts[0].value)
|
|
||||||
this.colorChanged(this.colorOption[1].value)
|
|
||||||
this.sizeChanged(this.sizeOption[2].value)
|
|
||||||
this.cssChanged()
|
|
||||||
}).catch(() => {
|
|
||||||
this.editor.focus()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// 插入表格
|
|
||||||
insertTable () {
|
|
||||||
const cursor = this.editor.getCursor()
|
|
||||||
const rows = parseInt(this.form.rows)
|
|
||||||
const cols = parseInt(this.form.cols)
|
|
||||||
if (isNaN(rows) || isNaN(cols) || rows < 1 || cols < 1) {
|
|
||||||
this.$message({
|
|
||||||
showClose: true,
|
|
||||||
message: '输入的行/列数无效,请重新输入',
|
|
||||||
type: 'error'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let table = ''
|
|
||||||
for (let i = 0; i < rows + 2; ++i) {
|
|
||||||
for (let j = 0; j < cols + 1; ++j) {
|
|
||||||
table += (j === 0 ? '|' : (i !== 1 ? ' |' : ' --- |'))
|
|
||||||
}
|
|
||||||
table += '\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
this.editor.replaceSelection(`\n${table}\n`, cursor)
|
|
||||||
this.dialogFormVisible = false
|
|
||||||
this.refresh()
|
|
||||||
},
|
|
||||||
statusChanged () {
|
|
||||||
localStorage.setItem('status', this.status)
|
|
||||||
this.refresh()
|
|
||||||
},
|
|
||||||
// 将编辑器内容保存到 LocalStorage
|
|
||||||
saveEditorContent (editor, name) {
|
|
||||||
const content = editor.getValue(0)
|
|
||||||
if (content) {
|
|
||||||
localStorage.setItem(name, content)
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem(name)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loadLocalStorage (editor, name, content) {
|
|
||||||
if (localStorage.getItem(name)) {
|
|
||||||
editor.setValue(localStorage.getItem(name))
|
|
||||||
} else {
|
|
||||||
editor.setValue(content)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 下载编辑器内容到本地
|
|
||||||
downloadEditorContent () {
|
|
||||||
let downLink = document.createElement('a')
|
|
||||||
downLink.download = 'content.md'
|
|
||||||
downLink.style.display = 'none'
|
|
||||||
let blob = new Blob([this.editor.getValue(0)])
|
|
||||||
downLink.href = URL.createObjectURL(blob)
|
|
||||||
document.body.appendChild(downLink)
|
|
||||||
downLink.click()
|
|
||||||
document.body.removeChild(downLink)
|
|
||||||
},
|
|
||||||
// 自定义CSS样式
|
|
||||||
async customStyle () {
|
|
||||||
this.showBox = !this.showBox
|
|
||||||
let flag = await localStorage.getItem('__css_content')
|
|
||||||
if (!flag) {
|
|
||||||
this.cssEditor.setValue(DEFAULT_CSS_CONTENT)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 复制渲染后的内容到剪贴板
|
|
||||||
copy () {
|
|
||||||
let clipboardDiv = document.getElementById('output')
|
|
||||||
clipboardDiv.focus()
|
|
||||||
window.getSelection().removeAllRanges()
|
|
||||||
let range = document.createRange()
|
|
||||||
range.setStartBefore(clipboardDiv.firstChild)
|
|
||||||
range.setEndAfter(clipboardDiv.lastChild)
|
|
||||||
window.getSelection().addRange(range)
|
|
||||||
document.execCommand('copy')
|
|
||||||
// 输出提示
|
|
||||||
this.$notify({
|
|
||||||
showClose: true,
|
|
||||||
message: '已复制渲染后的文章到剪贴板,可直接到公众号后台粘贴',
|
|
||||||
offset: 80,
|
|
||||||
duration: 1600,
|
|
||||||
type: 'success'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
502
src/components/CodemirrorEditor.vue
Normal file
502
src/components/CodemirrorEditor.vue
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app" class="container">
|
||||||
|
<el-container>
|
||||||
|
<el-header class="top">
|
||||||
|
<!-- 图片上传 -->
|
||||||
|
<el-upload action="https://imgkr.com/api/files/upload" :headers="{'Content-Type': 'multipart/form-data'}"
|
||||||
|
:show-file-list="false" :multiple="true" accept=".jpg,.jpeg,.png,.gif" name="file"
|
||||||
|
:before-upload="beforeUpload" :on-success="uploaded">
|
||||||
|
<el-tooltip class="item" effect="dark" content="上传图片" placement="bottom-start">
|
||||||
|
<i class="el-icon-upload" size="medium"> </i>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-upload>
|
||||||
|
<!-- 下载文本文档 -->
|
||||||
|
<el-tooltip class="item" effect="dark" content="下载编辑框Markdown文档" placement="bottom-start">
|
||||||
|
<i class="el-icon-download" size="medium" @click="downloadEditorContent"> </i>
|
||||||
|
</el-tooltip>
|
||||||
|
<!-- 页面重置 -->
|
||||||
|
<el-tooltip class="item" effect="dark" content="重置页面" placement="bottom-start">
|
||||||
|
<i class="el-icon-refresh" size="medium" @click="reset"> </i>
|
||||||
|
</el-tooltip>
|
||||||
|
<!-- 插入表格 -->
|
||||||
|
<el-tooltip class="item" effect="dark" content="插入表格" placement="bottom-start">
|
||||||
|
<i class="el-icon-s-grid" size="medium" @click="dialogFormVisible = true"> </i>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-form size="mini" class="ctrl" :inline=true>
|
||||||
|
<el-form-item>
|
||||||
|
<el-select v-model="currentFont" size="mini" placeholder="选择字体" clearable @change="fontChanged">
|
||||||
|
<el-option v-for="font in builtinFonts" :style="{fontFamily: font.value}" :key="font.value"
|
||||||
|
:label="font.label" :value="font.value">
|
||||||
|
<span class="select-item-left">{{ font.label }}</span>
|
||||||
|
<span class="select-item-right">Abc</span>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-select v-model="currentSize" size="mini" placeholder="选择段落字号" clearable @change="sizeChanged">
|
||||||
|
<el-option v-for="size in sizeOption" :key="size.value" :label="size.label" :value="size.value">
|
||||||
|
<span class="select-item-left">{{ size.label }}</span>
|
||||||
|
<span class="select-item-right">{{ size.desc }}</span>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-select v-model="currentColor" size="mini" placeholder="选择颜色" clearable @change="colorChanged">
|
||||||
|
<el-option v-for="color in colorOption" :key="color.value" :label="color.label" :value="color.value">
|
||||||
|
<span class="select-item-left">{{ color.label }}</span>
|
||||||
|
<span class="select-item-right">{{ color.hex }}</span>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-tooltip content="自定义颜色" placement="top">
|
||||||
|
<el-color-picker v-model="currentColor" size="mini" show-alpha @change="colorChanged"></el-color-picker>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip content="微信外链自动转为文末引用" placement="top">
|
||||||
|
<el-switch v-model="status" active-color="#67c23a" inactive-color="#dcdfe6" @change="statusChanged">
|
||||||
|
</el-switch>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-form>
|
||||||
|
<el-tooltip class="item" effect="dark" content="自定义CSS样式" placement="left">
|
||||||
|
<el-button type="success" plain size="medium" icon="el-icon-setting" @click="customStyle"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-button type="success" plain size="medium" @click="copy">复制</el-button>
|
||||||
|
<el-button type="success" plain size="medium" class="about" @click="aboutDialogVisible = true">关于</el-button>
|
||||||
|
</el-header>
|
||||||
|
<el-main class="main-body">
|
||||||
|
<el-row :gutter="10" class="main-section">
|
||||||
|
<el-col :span="12">
|
||||||
|
<textarea id="editor" type="textarea" placeholder="Your markdown text here." v-model="source">
|
||||||
|
</textarea>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12" class="preview-wrapper" id="preview">
|
||||||
|
<section>
|
||||||
|
<div class="preview" contenteditable="true">
|
||||||
|
<section id="output" v-html="output">
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</el-col>
|
||||||
|
<transition name="custom-classes-transition" enter-active-class="animated bounceInRight">
|
||||||
|
<el-col id="cssBox" :span="12" v-show="showBox">
|
||||||
|
<textarea id="cssEditor" type="textarea" placeholder="Your custom css here.">
|
||||||
|
</textarea>
|
||||||
|
</el-col>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
</el-row>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
<el-dialog title="关于" :visible.sync="aboutDialogVisible" width="30%" center>
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<h3>一款高度简洁的微信 Markdown 编辑器</h3>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: center;margin-top:10px;">
|
||||||
|
<p>扫码关注我的公众号,原创技术文章第一时间推送!</p>
|
||||||
|
<img src="assets/images/qrcode-for-doocs.jpg" style="width: 40%; display: block; margin: 20px auto 10px;">
|
||||||
|
</div>
|
||||||
|
<span slot="footer" class="dialog-footer">
|
||||||
|
<a href="https://github.com/doocs/md" target="_blank">
|
||||||
|
<el-button type="success" plain>GitHub 仓库</el-button>
|
||||||
|
</a>
|
||||||
|
<a href="https://gitee.com/doocs/md" target="_blank">
|
||||||
|
<el-button type="success" plain>Gitee 仓库</el-button>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
|
<el-dialog title="插入表格" :visible.sync="dialogFormVisible">
|
||||||
|
<el-form :model="form">
|
||||||
|
<el-form-item label="行数(表头不计入行数)">
|
||||||
|
<el-input v-model="form.rows"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="列数">
|
||||||
|
<el-input v-model="form.cols"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button type="success" plain @click="dialogFormVisible = false">取 消</el-button>
|
||||||
|
<el-button type="success" @click="insertTable">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import CodeMirror from 'codemirror/lib/codemirror'
|
||||||
|
import 'codemirror/lib/codemirror.css'
|
||||||
|
import 'codemirror/mode/css/css.js'
|
||||||
|
import 'codemirror/theme/ambiance.css'
|
||||||
|
import 'codemirror/addon/edit/matchbrackets'
|
||||||
|
import 'codemirror/addon/selection/active-line'
|
||||||
|
import 'codemirror/addon/hint/show-hint.css'
|
||||||
|
import 'codemirror/addon/hint/show-hint.js'
|
||||||
|
import 'codemirror/addon/hint/css-hint.js'
|
||||||
|
import '../scripts/format.js'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import WxRenderer from '../scripts/renderers/wx-renderer'
|
||||||
|
import marked from 'marked'
|
||||||
|
import {
|
||||||
|
setColorWithCustomTemplate,
|
||||||
|
setColor,
|
||||||
|
setFontSize,
|
||||||
|
css2json,
|
||||||
|
customCssWithTemplate
|
||||||
|
} from '../scripts/util'
|
||||||
|
import DEFAULT_CONTENT from '../scripts/default-content'
|
||||||
|
import DEFAULT_CSS_CONTENT from '../scripts/themes/default-theme-css'
|
||||||
|
|
||||||
|
require('codemirror/mode/javascript/javascript')
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
let d = {
|
||||||
|
wxRenderer: null,
|
||||||
|
aboutOutput: '',
|
||||||
|
output: '',
|
||||||
|
source: '',
|
||||||
|
editor: null,
|
||||||
|
cssEditor: null,
|
||||||
|
builtinFonts: [
|
||||||
|
{ label: '无衬线', value: '-apple-system-font,BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB , Microsoft YaHei UI , Microsoft YaHei ,Arial,sans-serif' },
|
||||||
|
{ label: '衬线', value: "Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, 'PingFang SC', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif" }
|
||||||
|
],
|
||||||
|
sizeOption: [
|
||||||
|
{ label: '12px', value: '12px', desc: '更小' },
|
||||||
|
{ label: '13px', value: '13px', desc: '稍小' },
|
||||||
|
{ label: '14px', value: '14px', desc: '推荐' },
|
||||||
|
{ label: '15px', value: '15px', desc: '稍大' },
|
||||||
|
{ label: '16px', value: '16px', desc: '更大' }
|
||||||
|
],
|
||||||
|
colorOption: [
|
||||||
|
{ label: '经典蓝', value: 'rgba(15, 76, 129, 1)', hex: '最新流行' },
|
||||||
|
{ label: '翡翠绿', value: 'rgba(0, 152, 116, 1)', hex: '优雅清新' },
|
||||||
|
{ label: '活力橘', value: 'rgba(250, 81, 81, 1)', hex: '热情活泼' }
|
||||||
|
],
|
||||||
|
showBox: true,
|
||||||
|
aboutDialogVisible: false,
|
||||||
|
dialogFormVisible: false,
|
||||||
|
form: {
|
||||||
|
rows: 1,
|
||||||
|
cols: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.currentFont = d.builtinFonts[0].value
|
||||||
|
d.currentSize = d.sizeOption[2].value
|
||||||
|
d.currentColor = d.colorOption[1].value
|
||||||
|
d.status = '1'
|
||||||
|
return d
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.currentFont = localStorage.getItem('fonts') || this.builtinFonts[0].value
|
||||||
|
this.currentColor = localStorage.getItem('color') || this.colorOption[1].value
|
||||||
|
this.currentSize = localStorage.getItem('size') || this.sizeOption[2].value
|
||||||
|
this.status = localStorage.getItem('status') === 'true'
|
||||||
|
this.showBox = false
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.editor = CodeMirror.fromTextArea(
|
||||||
|
document.getElementById('editor'),
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
mode: 'text/x-markdown',
|
||||||
|
theme: 'xq-light',
|
||||||
|
lineNumbers: false,
|
||||||
|
lineWrapping: true,
|
||||||
|
styleActiveLine: true,
|
||||||
|
autoCloseBrackets: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.cssEditor = CodeMirror.fromTextArea(
|
||||||
|
document.getElementById('cssEditor'), {
|
||||||
|
value: '',
|
||||||
|
mode: 'css',
|
||||||
|
theme: 'style-mirror',
|
||||||
|
lineNumbers: false,
|
||||||
|
lineWrapping: true,
|
||||||
|
matchBrackets: true,
|
||||||
|
autofocus: true,
|
||||||
|
extraKeys: {
|
||||||
|
'Ctrl-F': function autoFormat (editor) {
|
||||||
|
const totalLines = editor.lineCount()
|
||||||
|
editor.autoFormatRange({ line: 0, ch: 0 }, { line: totalLines })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// 自动提示
|
||||||
|
this.cssEditor.on('keyup', (cm, e) => {
|
||||||
|
if ((e.keyCode >= 65 && e.keyCode <= 90) || e.keyCode === 189) {
|
||||||
|
cm.showHint(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.editor.on('change', (cm, e) => {
|
||||||
|
this.refresh()
|
||||||
|
this.saveEditorContent(this.editor, '__editor_content')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 粘贴上传图片并插入
|
||||||
|
this.editor.on('paste', (cm, e) => {
|
||||||
|
if (!(e.clipboardData && e.clipboardData.items)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (let i = 0, len = e.clipboardData.items.length; i < len; ++i) {
|
||||||
|
let item = e.clipboardData.items[i]
|
||||||
|
if (item.kind === 'file') {
|
||||||
|
const pasteFile = item.getAsFile()
|
||||||
|
if (!(this.checkType(pasteFile) && this.checkImageSize(pasteFile))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let data = new FormData()
|
||||||
|
data.append('file', pasteFile)
|
||||||
|
axios.post(
|
||||||
|
'https://imgkr.com/api/files/upload',
|
||||||
|
data,
|
||||||
|
{
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
}
|
||||||
|
).then(resp => {
|
||||||
|
this.uploaded(resp.data)
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.cssEditor.on('update', (instance) => {
|
||||||
|
this.cssChanged()
|
||||||
|
this.saveEditorContent(this.cssEditor, '__css_content')
|
||||||
|
})
|
||||||
|
// 如果有编辑器内容被保存则读取,否则加载默认内容
|
||||||
|
this.loadLocalStorage(this.editor, '__editor_content', DEFAULT_CONTENT)
|
||||||
|
this.loadLocalStorage(this.cssEditor, '__css_content', DEFAULT_CSS_CONTENT)
|
||||||
|
})
|
||||||
|
this.wxRenderer = new WxRenderer({
|
||||||
|
theme: setColor(this.currentColor),
|
||||||
|
fonts: this.currentFont,
|
||||||
|
size: this.currentSize,
|
||||||
|
status: this.status
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
renderWeChat (source) {
|
||||||
|
let output = marked(source, { renderer: this.wxRenderer.getRenderer(this.status) })
|
||||||
|
// 去除第一行的 margin-top
|
||||||
|
output = output.replace(/(style=".*?)"/, '$1;margin-top: 0"')
|
||||||
|
if (this.status) {
|
||||||
|
// 引用脚注
|
||||||
|
output += this.wxRenderer.buildFootnotes()
|
||||||
|
// 附加的一些 style
|
||||||
|
output += this.wxRenderer.buildAddition()
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
},
|
||||||
|
editorThemeChanged (editorTheme) {
|
||||||
|
this.editor.setOption('theme', editorTheme)
|
||||||
|
},
|
||||||
|
fontChanged (fonts) {
|
||||||
|
this.wxRenderer.setOptions({
|
||||||
|
fonts: fonts
|
||||||
|
})
|
||||||
|
this.currentFont = fonts
|
||||||
|
localStorage.setItem('fonts', fonts)
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
sizeChanged (size) {
|
||||||
|
this.wxRenderer.setOptions({
|
||||||
|
size: size
|
||||||
|
})
|
||||||
|
let theme = setFontSize(size.replace('px', ''))
|
||||||
|
theme = setColorWithCustomTemplate(theme, this.currentColor)
|
||||||
|
this.wxRenderer.setOptions({
|
||||||
|
theme: theme
|
||||||
|
})
|
||||||
|
this.currentSize = size
|
||||||
|
localStorage.setItem('size', size)
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
colorChanged (color) {
|
||||||
|
let theme = setFontSize(this.currentSize.replace('px', ''))
|
||||||
|
theme = setColorWithCustomTemplate(theme, color)
|
||||||
|
this.wxRenderer.setOptions({
|
||||||
|
theme: theme
|
||||||
|
})
|
||||||
|
this.currentColor = color
|
||||||
|
localStorage.setItem('color', color)
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
cssChanged () {
|
||||||
|
let json = css2json(this.cssEditor.getValue(0))
|
||||||
|
let theme = setFontSize(this.currentSize.replace('px', ''))
|
||||||
|
theme = customCssWithTemplate(json, this.currentColor, theme)
|
||||||
|
this.wxRenderer.setOptions({
|
||||||
|
theme: theme
|
||||||
|
})
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
// 图片上传前的处理
|
||||||
|
beforeUpload (file) {
|
||||||
|
return this.checkType(file) && this.checkImageSize(file)
|
||||||
|
},
|
||||||
|
// 检查文件类型
|
||||||
|
checkType (file) {
|
||||||
|
if (!/\.(gif|jpg|jpeg|png|GIF|JPG|PNG)$/.test(file.name)) {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '请上传 JPG/PNG/GIF 格式的图片',
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
// 检查图片大小
|
||||||
|
checkImageSize (file) {
|
||||||
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '由于公众号限制,图片大小不能超过 5.0M',
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
// 图片上传结束
|
||||||
|
uploaded (response, file, fileList) {
|
||||||
|
if (response.success) {
|
||||||
|
// 上传成功,获取光标
|
||||||
|
const cursor = this.editor.getCursor()
|
||||||
|
const imageUrl = response.data
|
||||||
|
const markdownImage = `![](${imageUrl})`
|
||||||
|
// 将 Markdown 形式的 URL 插入编辑框光标所在位置
|
||||||
|
this.editor.replaceSelection(`\n${markdownImage}\n`, cursor)
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '图片插入成功',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
this.refresh()
|
||||||
|
} else {
|
||||||
|
// 上传失败
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: response.message,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 刷新右侧预览
|
||||||
|
refresh () {
|
||||||
|
this.output = this.renderWeChat(this.editor.getValue(0))
|
||||||
|
},
|
||||||
|
// 重置页面
|
||||||
|
reset () {
|
||||||
|
this.$confirm('此操作将丢失本地缓存的文本和自定义样式,是否继续?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
confirmButtonClass: 'el-button--success',
|
||||||
|
cancelButtonClass: 'el-button--success is-plain',
|
||||||
|
type: 'warning',
|
||||||
|
center: true
|
||||||
|
}).then(() => {
|
||||||
|
localStorage.clear()
|
||||||
|
this.editor.setValue(DEFAULT_CONTENT)
|
||||||
|
this.cssEditor.setValue(DEFAULT_CSS_CONTENT)
|
||||||
|
this.editor.focus()
|
||||||
|
this.status = '1'
|
||||||
|
this.fontChanged(this.builtinFonts[0].value)
|
||||||
|
this.colorChanged(this.colorOption[1].value)
|
||||||
|
this.sizeChanged(this.sizeOption[2].value)
|
||||||
|
this.cssChanged()
|
||||||
|
}).catch(() => {
|
||||||
|
this.editor.focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 插入表格
|
||||||
|
insertTable () {
|
||||||
|
const cursor = this.editor.getCursor()
|
||||||
|
const rows = parseInt(this.form.rows)
|
||||||
|
const cols = parseInt(this.form.cols)
|
||||||
|
if (isNaN(rows) || isNaN(cols) || rows < 1 || cols < 1) {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: '输入的行/列数无效,请重新输入',
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let table = ''
|
||||||
|
for (let i = 0; i < rows + 2; ++i) {
|
||||||
|
for (let j = 0; j < cols + 1; ++j) {
|
||||||
|
table += (j === 0 ? '|' : (i !== 1 ? ' |' : ' --- |'))
|
||||||
|
}
|
||||||
|
table += '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editor.replaceSelection(`\n${table}\n`, cursor)
|
||||||
|
this.dialogFormVisible = false
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
statusChanged () {
|
||||||
|
localStorage.setItem('status', this.status)
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
// 将编辑器内容保存到 LocalStorage
|
||||||
|
saveEditorContent (editor, name) {
|
||||||
|
const content = editor.getValue(0)
|
||||||
|
if (content) {
|
||||||
|
localStorage.setItem(name, content)
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem(name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadLocalStorage (editor, name, content) {
|
||||||
|
if (localStorage.getItem(name)) {
|
||||||
|
editor.setValue(localStorage.getItem(name))
|
||||||
|
} else {
|
||||||
|
editor.setValue(content)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 下载编辑器内容到本地
|
||||||
|
downloadEditorContent () {
|
||||||
|
let downLink = document.createElement('a')
|
||||||
|
downLink.download = 'content.md'
|
||||||
|
downLink.style.display = 'none'
|
||||||
|
let blob = new Blob([this.editor.getValue(0)])
|
||||||
|
downLink.href = URL.createObjectURL(blob)
|
||||||
|
document.body.appendChild(downLink)
|
||||||
|
downLink.click()
|
||||||
|
document.body.removeChild(downLink)
|
||||||
|
},
|
||||||
|
// 自定义CSS样式
|
||||||
|
async customStyle () {
|
||||||
|
this.showBox = !this.showBox
|
||||||
|
let flag = await localStorage.getItem('__css_content')
|
||||||
|
if (!flag) {
|
||||||
|
this.cssEditor.setValue(DEFAULT_CSS_CONTENT)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 复制渲染后的内容到剪贴板
|
||||||
|
copy () {
|
||||||
|
let clipboardDiv = document.getElementById('output')
|
||||||
|
clipboardDiv.focus()
|
||||||
|
window.getSelection().removeAllRanges()
|
||||||
|
let range = document.createRange()
|
||||||
|
range.setStartBefore(clipboardDiv.firstChild)
|
||||||
|
range.setEndAfter(clipboardDiv.lastChild)
|
||||||
|
window.getSelection().addRange(range)
|
||||||
|
document.execCommand('copy')
|
||||||
|
// 输出提示
|
||||||
|
this.$notify({
|
||||||
|
showClose: true,
|
||||||
|
message: '已复制渲染后的文章到剪贴板,可直接到公众号后台粘贴',
|
||||||
|
offset: 80,
|
||||||
|
duration: 1600,
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
</style>
|
@ -1,59 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="hello">
|
<div>
|
||||||
<h1>{{ msg }}</h1>
|
|
||||||
<p>
|
|
||||||
For a guide and recipes on how to configure / customize this project,<br>
|
|
||||||
check out the
|
|
||||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
|
||||||
</p>
|
|
||||||
<h3>Installed CLI Plugins</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-jest" target="_blank" rel="noopener">unit-jest</a></li>
|
|
||||||
</ul>
|
|
||||||
<h3>Essential Links</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
|
||||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
|
||||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
|
||||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
|
||||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
|
||||||
</ul>
|
|
||||||
<h3>Ecosystem</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
|
||||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
|
||||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'HelloWorld',
|
|
||||||
props: {
|
|
||||||
msg: String
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<style lang="scss" scoped>
|
||||||
<style scoped lang="scss">
|
|
||||||
h3 {
|
|
||||||
margin: 40px 0 0;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
18
src/components/Loading.vue
Normal file
18
src/components/Loading.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<div class="loading" id="loading">
|
||||||
|
<div class="loading-wrapper">
|
||||||
|
<div class="loading-text">Loading...</div>
|
||||||
|
<div class="loading-anim"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
83
src/scripts/format.js
Normal file
83
src/scripts/format.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
(function () {
|
||||||
|
CodeMirror.extendMode('css', {
|
||||||
|
commentStart: '/*',
|
||||||
|
commentEnd: '*/',
|
||||||
|
newlineAfterToken: function (type, content) {
|
||||||
|
return /^[;{}]$/.test(content)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Comment/uncomment the specified range
|
||||||
|
CodeMirror.defineExtension('commentRange', function (isComment, from, to) {
|
||||||
|
var cm = this; var curMode = CodeMirror.innerMode(cm.getMode(), cm.getTokenAt(from).state).mode
|
||||||
|
cm.operation(function () {
|
||||||
|
if (isComment) { // Comment range
|
||||||
|
cm.replaceRange(curMode.commentEnd, to)
|
||||||
|
cm.replaceRange(curMode.commentStart, from)
|
||||||
|
if (from.line == to.line && from.ch == to.ch) // An empty comment inserted - put cursor inside
|
||||||
|
{ cm.setCursor(from.line, from.ch + curMode.commentStart.length) }
|
||||||
|
} else { // Uncomment range
|
||||||
|
var selText = cm.getRange(from, to)
|
||||||
|
var startIndex = selText.indexOf(curMode.commentStart)
|
||||||
|
var endIndex = selText.lastIndexOf(curMode.commentEnd)
|
||||||
|
if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) {
|
||||||
|
// Take string till comment start
|
||||||
|
selText = selText.substr(0, startIndex) +
|
||||||
|
// From comment start till comment end
|
||||||
|
selText.substring(startIndex + curMode.commentStart.length, endIndex) +
|
||||||
|
// From comment end till string end
|
||||||
|
selText.substr(endIndex + curMode.commentEnd.length)
|
||||||
|
}
|
||||||
|
cm.replaceRange(selText, from, to)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Applies automatic mode-aware indentation to the specified range
|
||||||
|
CodeMirror.defineExtension('autoIndentRange', function (from, to) {
|
||||||
|
var cmInstance = this
|
||||||
|
this.operation(function () {
|
||||||
|
for (var i = from.line; i <= to.line; i++) {
|
||||||
|
cmInstance.indentLine(i, 'smart')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Applies automatic formatting to the specified range
|
||||||
|
CodeMirror.defineExtension('autoFormatRange', function (from, to) {
|
||||||
|
var cm = this
|
||||||
|
var outer = cm.getMode(); var text = cm.getRange(from, to).split('\n')
|
||||||
|
var state = CodeMirror.copyState(outer, cm.getTokenAt(from).state)
|
||||||
|
var tabSize = cm.getOption('tabSize')
|
||||||
|
|
||||||
|
var out = ''; var lines = 0; var atSol = from.ch == 0
|
||||||
|
function newline () {
|
||||||
|
out += '\n'
|
||||||
|
atSol = true
|
||||||
|
++lines
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < text.length; ++i) {
|
||||||
|
var stream = new CodeMirror.StringStream(text[i], tabSize)
|
||||||
|
while (!stream.eol()) {
|
||||||
|
var inner = CodeMirror.innerMode(outer, state)
|
||||||
|
var style = outer.token(stream, state); var cur = stream.current()
|
||||||
|
stream.start = stream.pos
|
||||||
|
if (!atSol || /\S/.test(cur)) {
|
||||||
|
out += cur
|
||||||
|
atSol = false
|
||||||
|
}
|
||||||
|
if (!atSol && inner.mode.newlineAfterToken &&
|
||||||
|
inner.mode.newlineAfterToken(style, cur, stream.string.slice(stream.pos) || text[i + 1] || '', inner.state)) { newline() }
|
||||||
|
}
|
||||||
|
if (!stream.pos && outer.blankLine) outer.blankLine(state)
|
||||||
|
if (!atSol) newline()
|
||||||
|
}
|
||||||
|
|
||||||
|
cm.operation(function () {
|
||||||
|
cm.replaceRange(out, from, to)
|
||||||
|
for (var cur = from.line + 1, end = from.line + lines; cur <= end; ++cur) { cm.indentLine(cur, 'smart') }
|
||||||
|
cm.setSelection(from, cm.getCursor(false))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})()
|
@ -1,3 +1,4 @@
|
|||||||
|
import marked from 'marked'
|
||||||
const WxRenderer = function (opts) {
|
const WxRenderer = function (opts) {
|
||||||
this.opts = opts
|
this.opts = opts
|
||||||
let ENV_STRETCH_IMAGE = true
|
let ENV_STRETCH_IMAGE = true
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
let default_theme = {
|
export default {
|
||||||
BASE: {
|
BASE: {
|
||||||
'text-align': 'left',
|
'text-align': 'left',
|
||||||
'color': '#3f3f3f',
|
'color': '#3f3f3f',
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import default_theme from "./themes/default-theme";
|
||||||
|
|
||||||
// 设置自定义颜色
|
// 设置自定义颜色
|
||||||
export function setColorWithTemplate (template) {
|
export function setColorWithTemplate (template) {
|
||||||
return function (color) {
|
return function (color) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home">
|
<div class="home">
|
||||||
<img alt="Vue logo" src="../assets/logo.png">
|
|
||||||
<HelloWorld msg="Welcome to Your Vue.js App"/>
|
<HelloWorld msg="Welcome to Your Vue.js App"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -2,11 +2,5 @@ import { shallowMount } from '@vue/test-utils'
|
|||||||
import HelloWorld from '@/components/HelloWorld.vue'
|
import HelloWorld from '@/components/HelloWorld.vue'
|
||||||
|
|
||||||
describe('HelloWorld.vue', () => {
|
describe('HelloWorld.vue', () => {
|
||||||
it('renders props.msg when passed', () => {
|
|
||||||
const msg = 'new message'
|
|
||||||
const wrapper = shallowMount(HelloWorld, {
|
|
||||||
propsData: { msg }
|
|
||||||
})
|
|
||||||
expect(wrapper.text()).toMatch(msg)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user