mirror of
https://github.com/doocs/md.git
synced 2024-11-24 19:10:34 +08:00
feat: initial commit
Fork from https://github.com/zkqiang/wechat-mdeditor
This commit is contained in:
parent
8514c5957e
commit
ae774c3ac7
207
assets/css/app.css
Normal file
207
assets/css/app.css
Normal file
@ -0,0 +1,207 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input, button, textarea {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: normal !important;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
font-family: 'PingFang SC', BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
text-decoration: none;
|
||||
color: #ff3502
|
||||
}
|
||||
|
||||
.el-message__icon {
|
||||
display: none
|
||||
}
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top {
|
||||
height: 60px;
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.web-title {
|
||||
margin: 0 15px 0 5px;
|
||||
}
|
||||
|
||||
.web-icon {
|
||||
width: auto;
|
||||
height: 1.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#editor {
|
||||
height: 100%;
|
||||
display: block;
|
||||
border: none;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
section {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 0;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.ctrl {
|
||||
flex-basis: 60px;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.preview-wrapper {
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
/* height: 100%; */
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.main-section {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hint {
|
||||
opacity: 0.6;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.preview {
|
||||
margin: 0 -20px;
|
||||
width: 375px;
|
||||
padding: 20px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
box-shadow: 0 0 60px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.preview table {
|
||||
margin-bottom: 10px;
|
||||
border-collapse: collapse;
|
||||
display: table;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/*.preview ul, .preview ol {*/
|
||||
/* padding-left: 40px !important;*/
|
||||
/*}*/
|
||||
|
||||
.select-item-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.select-item-right {
|
||||
float: right;
|
||||
color: #8492a6;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
font-size: 14px;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
font-family: 'PingFang SC', BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
/* ele ui */
|
||||
.el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/*wechat code block*/
|
||||
.rich_media_content .code-snippet *, .rich_media_content .code-snippet__fix * {
|
||||
max-width: 1000% !important;
|
||||
}
|
||||
|
||||
.code-snippet__fix {
|
||||
word-wrap: break-word !important;
|
||||
ont-size: 14px;
|
||||
margin: 10px 0;
|
||||
color: #333;
|
||||
position: relative;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.code-snippet__fix .code-snippet__line-index {
|
||||
counter-reset: line;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
padding: 1em;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.code-snippet__fix .code-snippet__line-index li {
|
||||
list-style-type: none;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.code-snippet__fix .code-snippet__line-index li::before {
|
||||
min-width: 1.5em;
|
||||
text-align: right;
|
||||
left: -2.5em;
|
||||
counter-increment: line;
|
||||
content: counter(line);
|
||||
display: inline;
|
||||
color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.code-snippet__fix pre {
|
||||
overflow-x: auto;
|
||||
padding: 1em 1em 1em 1em;
|
||||
white-space: normal;
|
||||
flex: 1;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.code-snippet__fix code {
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
white-space: pre;
|
||||
display: flex;
|
||||
position: relative;
|
||||
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
}
|
43
assets/css/loading.css
Normal file
43
assets/css/loading.css
Normal file
@ -0,0 +1,43 @@
|
||||
.loading {
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
z-index: 99999;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.loading-wrapper {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: translateX(-50%) translateY(-50%);
|
||||
-moz-transform: translateX(-50%) translateY(-50%);
|
||||
-ms-transform: translateX(-50%) translateY(-50%);
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
line-height: 1.4;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.loading-anim {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
border: 5px solid rgba(189, 189, 189, 0.25);
|
||||
border-left-color: rgba(3, 155, 229, 1);
|
||||
border-top-color: rgba(3, 155, 229, 1);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
animation: rotate 600ms infinite linear;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
to {
|
||||
transform: rotate(1turn)
|
||||
}
|
||||
}
|
108
assets/default-content.md
Normal file
108
assets/default-content.md
Normal file
@ -0,0 +1,108 @@
|
||||
# 公众号 Markdown 编辑器
|
||||
|
||||
### 简介
|
||||
|
||||
这款编辑器可以将 Markdown 转换成微信公众号编辑器的样式,只需将 MD 文档复制到左侧栏,再在右侧栏顶部"点击复制",右侧预览内容就可被复制到公众号后台。
|
||||
|
||||
这让你在公众号创作时,把更多的时间专注于文章本身,而不是繁琐地调整文章样式。
|
||||
|
||||
|
||||
### 功能
|
||||
|
||||
- 支持序号列表和圆点列表,解决了样式会被重置的问题
|
||||
- 外链会自动转换为参考文献索引,并且附在文章末尾
|
||||
- 支持多种字体和样式
|
||||
- 支持日语注音假名、汉语拼音样式
|
||||
- 支持不同于微信的代码配色方案
|
||||
- 支持编辑内容自动保存、预览同步滚动等常见功能
|
||||
|
||||
### 关于 Markdown
|
||||
|
||||
1. Markdown 是一种轻量级标记语言,能将文本换成有效的 XHTML(或者HTML) 文档
|
||||
2. Markdown 强大之处,在于可以用一套格式,在所有支持 Markdown 的编辑器中转换成发布样式,做到最大化兼容,不需要担心复制到不同编辑器中样式被破坏
|
||||
3. 正如你右侧看到的这样,Markdown 被转换成了微信支持的样式,同样你可以在一字不改的情况下,在 Github 等平台上转换类似的样式
|
||||
4. 学习 Markdown 的语法,可以查看 [Markdown 语法入门手册](https://www.w3cschool.cn/markdownyfsm/markdownyfsm-odm6256r.html)
|
||||
|
||||
## 更多样式
|
||||
|
||||
### 注音符号
|
||||
|
||||
[注音符号 W3C 定义](http://www.w3.org/TR/ruby/)。
|
||||
|
||||
支持日语注音假名、汉语拼音。
|
||||
|
||||
用法有以下几种:
|
||||
|
||||
* 世界{せかい}
|
||||
* 小夜時雨{さ・よ・しぐれ}
|
||||
* 食べる{たべる}
|
||||
* 丧心病狂{gàn・de・piào・liang}
|
||||
|
||||
### 图片
|
||||
|
||||
接下来是一张图片。你可以用自己图床,也可以上传到微信媒体库再把图片 URL
|
||||
粘贴回来,或者编辑好以后,在公众号里插入图片。
|
||||
|
||||
![这里可以写图片描述](https://static.zkqiang.cn/images/20191019181145.JPG-slim)
|
||||
|
||||
如果使用图床链接的话,有可能复制后图片不能被上传,需要手动在微信重新上传替换。
|
||||
|
||||
### 代码块
|
||||
|
||||
代码高亮使用了 Github 配色方案,后续会加入更多配色。
|
||||
|
||||
**注意:由于微信编辑器限制,复制后若在微信编辑器中点击代码块,会被微信自动重置后它的配色,只能重新再复制**
|
||||
|
||||
```cpp
|
||||
#include <stdio.h>
|
||||
|
||||
const int MAX = 10;
|
||||
int cache[MAX] = {0};
|
||||
|
||||
int fib(int x) {
|
||||
if (x == 1) return 1;
|
||||
if (x == 0) return 0;
|
||||
if (cache[x] == 0) {
|
||||
int ret = fib(x - 1) + fib(x - 2);
|
||||
cache[x] = ret;
|
||||
}
|
||||
return cache[x];
|
||||
}
|
||||
|
||||
int main() {
|
||||
int i;
|
||||
printf("fibonacci series:\n");
|
||||
for (i = 0; i < MAX; ++i) {
|
||||
printf("%d ", fib(i));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 内联代码
|
||||
|
||||
inline code `{code: 0}`
|
||||
|
||||
### 表格
|
||||
|
||||
表格无法使用自定义样式,暂时没找到解决途径
|
||||
|
||||
| Header 1 | Header 2 |
|
||||
| --- | --- |
|
||||
| Key 1 | Value 1 |
|
||||
| Key 2 | Value 2 |
|
||||
| Key 3 | Value 3 |
|
||||
|
||||
### 超链接
|
||||
|
||||
如果是公众号文章的超链接,是可以点击打开的,但其他链接都无法点击,所以这里使用类似于文献的底部引用。
|
||||
|
||||
例如:
|
||||
|
||||
[这是一篇公众号文章](https://mp.weixin.qq.com/s/ahpV7Poj5wHmtUP6vqy3gg)
|
||||
|
||||
[这是我的博客地址](http://zkqiang.cn)
|
||||
|
||||
[通过引号设置引用名](http://prod.zkqiang.cn/wxeditor "这是自定义的引用名")
|
||||
|
||||
[本项目是 Fork 自 Lyric 原项目后的二次开发,感谢他的贡献!](https://github.com/lyricat/wechat-format "原项目代码库")
|
BIN
assets/images/favicon.png
Normal file
BIN
assets/images/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
163
assets/scripts/editor.js
Normal file
163
assets/scripts/editor.js
Normal file
@ -0,0 +1,163 @@
|
||||
let app = new Vue({
|
||||
el: '#app',
|
||||
data: function () {
|
||||
let d = {
|
||||
aboutOutput: '',
|
||||
output: '',
|
||||
source: '',
|
||||
editorThemes: [
|
||||
{ label: 'base16-light', value: 'base16-light' },
|
||||
{ label: 'duotone-light', value: 'duotone-light' },
|
||||
{ label: 'monokai', value: 'monokai' }
|
||||
],
|
||||
editor: 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: '14px', value: '14px', desc: '稍小' },
|
||||
{ label: '15px', value: '15px', desc: '默认' },
|
||||
{ label: '16px', value: '16px', desc: '稍大' },
|
||||
{ label: '17px', value: '17px', desc: '很大' },
|
||||
],
|
||||
themeOption: [
|
||||
{ label: 'default', value: 'default', author: '张凯强' },
|
||||
{ label: 'lyric', value: 'lyric', author: 'Lyric' },
|
||||
{ label: 'lupeng', value: 'lupeng', author: '鲁鹏' }
|
||||
],
|
||||
styleThemes: {
|
||||
default: defaultTheme,
|
||||
lyric: lyricTheme,
|
||||
lupeng: lupengTheme
|
||||
},
|
||||
aboutDialogVisible: false
|
||||
};
|
||||
d.currentEditorTheme = d.editorThemes[0].value;
|
||||
d.currentFont = d.builtinFonts[0].value;
|
||||
d.currentSize = d.sizeOption[1].value;
|
||||
d.currentTheme = d.themeOption[0].value;
|
||||
return d;
|
||||
},
|
||||
mounted() {
|
||||
let self = this;
|
||||
this.editor = CodeMirror.fromTextArea(
|
||||
document.getElementById('editor'),
|
||||
{
|
||||
lineNumbers: false,
|
||||
lineWrapping: true,
|
||||
styleActiveLine: true,
|
||||
theme: this.currentEditorTheme,
|
||||
mode: 'text/x-markdown',
|
||||
}
|
||||
);
|
||||
this.editor.on("change", function (cm, change) {
|
||||
self.refresh();
|
||||
self.saveEditorContent();
|
||||
});
|
||||
this.wxRenderer = new WxRenderer({
|
||||
theme: this.styleThemes.default,
|
||||
fonts: this.currentFont,
|
||||
size: this.currentSize
|
||||
});
|
||||
// 如果有编辑内容被保存则读取,否则加载默认文档
|
||||
if (localStorage.getItem("__editor_content")) {
|
||||
this.editor.setValue(localStorage.getItem("__editor_content"));
|
||||
} else {
|
||||
axios({
|
||||
method: 'get',
|
||||
url: './assets/default-content.md',
|
||||
}).then(function (resp) {
|
||||
self.editor.setValue(resp.data)
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
renderWeChat: function (source) {
|
||||
let output = marked(source, { renderer: this.wxRenderer.getRenderer() });
|
||||
if (this.wxRenderer.hasFootnotes()) {
|
||||
// 去除第一行的 margin-top
|
||||
output = output.replace(/(style=".*?)"/, '$1;margin-top: 0"');
|
||||
// 引用注脚
|
||||
output += this.wxRenderer.buildFootnotes();
|
||||
// 附加的一些 style
|
||||
output += this.wxRenderer.buildAddition();
|
||||
}
|
||||
return output
|
||||
},
|
||||
editorThemeChanged: function (editorTheme) {
|
||||
this.editor.setOption('theme', editorTheme)
|
||||
},
|
||||
fontChanged: function (fonts) {
|
||||
this.wxRenderer.setOptions({
|
||||
fonts: fonts
|
||||
});
|
||||
this.refresh()
|
||||
},
|
||||
sizeChanged: function (size) {
|
||||
this.wxRenderer.setOptions({
|
||||
size: size
|
||||
});
|
||||
this.refresh()
|
||||
},
|
||||
themeChanged: function (themeName) {
|
||||
let themeObject = this.styleThemes[themeName];
|
||||
this.wxRenderer.setOptions({
|
||||
theme: themeObject
|
||||
});
|
||||
this.refresh()
|
||||
},
|
||||
// 刷新右侧预览
|
||||
refresh: function () {
|
||||
this.output = this.renderWeChat(this.editor.getValue(0))
|
||||
},
|
||||
// 将左侧编辑器内容保存到 LocalStorage
|
||||
saveEditorContent: function () {
|
||||
let content = this.editor.getValue(0);
|
||||
if (content){
|
||||
localStorage.setItem("__editor_content", content);
|
||||
} else {
|
||||
localStorage.removeItem("__editor_content");
|
||||
}
|
||||
},
|
||||
copy: function () {
|
||||
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);
|
||||
|
||||
try {
|
||||
if (document.execCommand('copy')) {
|
||||
this.$message({
|
||||
message: '已复制到剪贴板', type: 'success'
|
||||
})
|
||||
} else {
|
||||
this.$message({
|
||||
message: '未能复制到剪贴板,请全选后右键复制', type: 'warning'
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
this.$message({
|
||||
message: '未能复制到剪贴板,请全选后右键复制', type: 'warning'
|
||||
})
|
||||
}
|
||||
},
|
||||
openWindow: function (url) {
|
||||
window.open(url);
|
||||
}
|
||||
},
|
||||
updated: function () {
|
||||
this.$nextTick(function () {
|
||||
prettyPrint()
|
||||
})
|
||||
}
|
||||
});
|
4
assets/scripts/loading.js
Normal file
4
assets/scripts/loading.js
Normal file
@ -0,0 +1,4 @@
|
||||
// 加载完成隐藏 loading 界面
|
||||
window.onload = () => {
|
||||
$('#loading').hide();
|
||||
};
|
200
assets/scripts/renderers/wx-renderer.js
Normal file
200
assets/scripts/renderers/wx-renderer.js
Normal file
@ -0,0 +1,200 @@
|
||||
let WxRenderer = function (opts) {
|
||||
this.opts = opts;
|
||||
let ENV_USE_REFERENCES = true;
|
||||
let ENV_STRETCH_IMAGE = true;
|
||||
|
||||
let footnotes = [];
|
||||
let footnoteIndex = 0;
|
||||
let styleMapping = null;
|
||||
|
||||
let CODE_FONT_FAMILY = "Menlo, Operator Mono, Consolas, Monaco, monospace";
|
||||
|
||||
let merge = function (base, extend) {
|
||||
return Object.assign({}, base, extend)
|
||||
};
|
||||
|
||||
this.buildTheme = function (themeTpl) {
|
||||
let mapping = {};
|
||||
let base = merge(themeTpl.BASE, {
|
||||
'font-family': this.opts.fonts,
|
||||
'font-size': this.opts.size
|
||||
});
|
||||
let base_block = merge(base, {});
|
||||
for (let ele in themeTpl.inline) {
|
||||
if (themeTpl.inline.hasOwnProperty(ele)) {
|
||||
let style = themeTpl.inline[ele];
|
||||
if (ele === 'codespan') {
|
||||
style['font-family'] = CODE_FONT_FAMILY;
|
||||
style['white-space'] = 'normal';
|
||||
}
|
||||
mapping[ele] = merge(base, style)
|
||||
}
|
||||
}
|
||||
for (let ele in themeTpl.block) {
|
||||
if (themeTpl.block.hasOwnProperty(ele)) {
|
||||
let style = themeTpl.block[ele];
|
||||
if (ele === 'code') {
|
||||
style['font-family'] = CODE_FONT_FAMILY
|
||||
}
|
||||
mapping[ele] = merge(base_block, style)
|
||||
}
|
||||
}
|
||||
return mapping
|
||||
};
|
||||
|
||||
let getStyles = function (tokenName, addition) {
|
||||
let arr = [];
|
||||
let dict = styleMapping[tokenName];
|
||||
if (!dict) return '';
|
||||
for (const key in dict) {
|
||||
arr.push(key + ':' + dict[key])
|
||||
}
|
||||
return `style="${ arr.join(';') + (addition || '') }"`
|
||||
};
|
||||
|
||||
let addFootnote = function (title, link) {
|
||||
footnoteIndex += 1;
|
||||
footnotes.push([footnoteIndex, title, link]);
|
||||
return footnoteIndex
|
||||
};
|
||||
|
||||
this.buildFootnotes = function () {
|
||||
let footnoteArray = footnotes.map(function (x) {
|
||||
if (x[1] === x[2]) {
|
||||
return `<code style="font-size: 90%; opacity: 0.6;">[${ x[0] }]</code>: <i>${ x[1] }</i><br/>`
|
||||
}
|
||||
return `<code style="font-size: 90%; opacity: 0.6;">[${ x[0] }]</code> ${ x[1] }: <i>${ x[2] }</i><br/>`
|
||||
});
|
||||
return `<h3 ${ getStyles('h3') }>References</h3><p ${ getStyles('footnotes') }>${ footnoteArray.join('\n') }</p>`
|
||||
};
|
||||
|
||||
this.buildAddition = function () {
|
||||
return '<style>.preview-wrapper pre::before{' +
|
||||
'font-family:"SourceSansPro","HelveticaNeue",Arial,sans-serif;' +
|
||||
'position:absolute;' +
|
||||
'top:0;' +
|
||||
'right:0;' +
|
||||
'color:#ccc;' +
|
||||
'text-align:right;' +
|
||||
'font-size:0.8em;' +
|
||||
'padding:5px10px0;' +
|
||||
'line-height:15px;' +
|
||||
'height:15px;' +
|
||||
'font-weight:600;' +
|
||||
'}</style>'
|
||||
};
|
||||
|
||||
this.setOptions = function (newOpts) {
|
||||
this.opts = merge(this.opts, newOpts)
|
||||
};
|
||||
|
||||
this.hasFootnotes = function () {
|
||||
return footnotes.length !== 0
|
||||
};
|
||||
|
||||
this.getRenderer = function () {
|
||||
footnotes = [];
|
||||
footnoteIndex = 0;
|
||||
|
||||
styleMapping = this.buildTheme(this.opts.theme);
|
||||
let renderer = new marked.Renderer();
|
||||
FuriganaMD.register(renderer);
|
||||
|
||||
renderer.heading = function (text, level) {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return `<h1 ${ getStyles('h1') }>${ text }</h1>`;
|
||||
case 2:
|
||||
return `<h2 ${ getStyles('h2') }>${ text }</h2>`;
|
||||
case 3:
|
||||
return `<h3 ${ getStyles('h3') }>${ text }</h3>`;
|
||||
default:
|
||||
return `<h4 ${ getStyles('h4') }>${ text }</h4>`;
|
||||
}
|
||||
};
|
||||
renderer.paragraph = function (text) {
|
||||
return `<p ${ getStyles('p') }>${ text }</p>`
|
||||
};
|
||||
renderer.blockquote = function (text) {
|
||||
text = text.replace(/<p.*?>/, `<p ${ getStyles('blockquote_p') }>`);
|
||||
return `<blockquote ${ getStyles('blockquote') }>${ text }</blockquote>`
|
||||
};
|
||||
renderer.code = function (text, infoString) {
|
||||
text = text.replace(/</g, "<");
|
||||
text = text.replace(/>/g, ">");
|
||||
|
||||
let lines = text.split('\n');
|
||||
let codeLines = [];
|
||||
let numbers = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
codeLines.push(`<code><span class="code-snippet_outer">${ (line || '<br>') }</span></code>`);
|
||||
numbers.push('<li></li>')
|
||||
}
|
||||
let lang = infoString || '';
|
||||
return `<section class="code-snippet__fix code-snippet__js">`
|
||||
+ `<ul class="code-snippet__line-index code-snippet__js">${ numbers.join('') }</ul>`
|
||||
+ `<pre class="code-snippet__js prettyprint" data-lang="${ lang }">`
|
||||
+ codeLines.join('')
|
||||
+ `</pre></section>`
|
||||
};
|
||||
renderer.codespan = function (text, infoString) {
|
||||
return `<code ${ getStyles('codespan') }>${ text }</code>`
|
||||
};
|
||||
renderer.listitem = function (text) {
|
||||
return `<span ${ getStyles('listitem') }><span style="margin-right: 10px;"><%s/></span>${ text }</span>`;
|
||||
};
|
||||
renderer.list = function (text, ordered, start) {
|
||||
text = text.replace(/<\/*p.*?>/g, '');
|
||||
let segments = text.split(`<%s/>`);
|
||||
if (!ordered) {
|
||||
text = segments.join('•');
|
||||
return `<p ${ getStyles('ul') }>${ text }</p>`;
|
||||
}
|
||||
text = segments[0];
|
||||
for (let i = 1; i < segments.length; i++) {
|
||||
text = text + i + '.' + segments[i];
|
||||
}
|
||||
return `<p ${ getStyles('ol') }>${ text }</p>`;
|
||||
};
|
||||
renderer.image = function (href, title, text) {
|
||||
let subText = '';
|
||||
if (text) {
|
||||
subText = `<figcaption ${ getStyles('figcaption') }>${ text }</figcaption>`
|
||||
}
|
||||
let figureStyles = getStyles('figure');
|
||||
let imgStyles = getStyles(ENV_STRETCH_IMAGE ? 'image' : 'image_org');
|
||||
return `<figure ${ figureStyles }><img ${ imgStyles } src="${ href }" title="${ title }" alt="${ text }"/>${ subText }</figure>`
|
||||
};
|
||||
renderer.link = function (href, title, text) {
|
||||
if (href.indexOf('https://mp.weixin.qq.com') === 0) {
|
||||
return `<a href="${ href }" title="${ (title || text) }" ${ getStyles('wx_link') }>${ text }</a>`;
|
||||
} else if (href === text) {
|
||||
return text;
|
||||
} else {
|
||||
if (ENV_USE_REFERENCES) {
|
||||
let ref = addFootnote(title || text, href);
|
||||
return `<span ${ getStyles('link') }>${ text }<sup>[${ ref }]</sup></span>`;
|
||||
} else {
|
||||
return `<a href="${ href }" title="${ (title || text) }" ${ getStyles('link') }>${ text }</a>`;
|
||||
}
|
||||
}
|
||||
};
|
||||
renderer.strong = function (text) {
|
||||
return `<strong ${ getStyles('strong') }>${ text }</strong>`;
|
||||
};
|
||||
renderer.em = function (text) {
|
||||
return `<p ${ getStyles('p', ';font-style: italic;')}>${ text }</p>`
|
||||
};
|
||||
renderer.table = function (header, body) {
|
||||
return `<table class="preview-table"><thead ${ getStyles('thead') }>${ header }</thead><tbody>${ body }</tbody></table>`;
|
||||
};
|
||||
renderer.tablecell = function (text, flags) {
|
||||
return `<td ${ getStyles('td') }>${ text }</td>`;
|
||||
};
|
||||
renderer.hr = function () {
|
||||
return `<hr style="border-style: solid;border-width: 1px 0 0;border-color: rgba(0,0,0,0.1);-webkit-transform-origin: 0 0;-webkit-transform: scale(1, 0.5);transform-origin: 0 0;transform: scale(1, 0.5);">`;
|
||||
};
|
||||
return renderer
|
||||
}
|
||||
};
|
150
assets/scripts/themes/default.js
Normal file
150
assets/scripts/themes/default.js
Normal file
@ -0,0 +1,150 @@
|
||||
let defaultTheme = {
|
||||
BASE: {
|
||||
'text-align': 'left',
|
||||
'color': '#3f3f3f',
|
||||
'line-height': '1.75',
|
||||
},
|
||||
BASE_BLOCK: {
|
||||
'margin': '1em 8px'
|
||||
},
|
||||
// block element
|
||||
block: {
|
||||
h1: {
|
||||
'font-size': '1.2em',
|
||||
'text-align': 'center',
|
||||
'font-weight': 'bold',
|
||||
'display': 'table',
|
||||
'margin': '2em auto 1em auto',
|
||||
'padding': '0 1em',
|
||||
'border-bottom': '1px solid rgb(248,57,41)'
|
||||
},
|
||||
h2: {
|
||||
'font-size': '1.2em',
|
||||
'text-align': 'center',
|
||||
'font-weight': 'bold',
|
||||
'display': 'table',
|
||||
'margin': '4em auto 2em auto',
|
||||
'padding': '0 1em',
|
||||
'border-bottom': '1px solid rgb(248,57,41)'
|
||||
},
|
||||
h3: {
|
||||
'font-weight': 'bold',
|
||||
'font-size': '1.1em',
|
||||
'margin': '2em 8px 0.75em 0',
|
||||
'padding-bottom': '.1em',
|
||||
// 'border-bottom': '1px solid #eaecef',
|
||||
'padding-left': '8px',
|
||||
'border-left': '4px solid rgb(248,57,41)'
|
||||
},
|
||||
h4: {
|
||||
'font-weight': 'bold',
|
||||
'font-size': '1em',
|
||||
'margin': '2em 8px 0.5em 8px',
|
||||
},
|
||||
p: {
|
||||
'margin': '1.5em 8px',
|
||||
'letter-spacing': '0.1em'
|
||||
},
|
||||
blockquote: {
|
||||
'font-style': 'normal',
|
||||
'border-left': 'none',
|
||||
'padding': '1em',
|
||||
'border-radius': '4px',
|
||||
'color': '#FEEEED',
|
||||
'background': 'rgba(27,31,35,.05)',
|
||||
'margin': '2em 8px'
|
||||
},
|
||||
blockquote_p: {
|
||||
'letter-spacing': '0.1em',
|
||||
'color': 'rgb(80, 80, 80)',
|
||||
'font-family': 'PingFangSC-light, PingFangTC-light, Open Sans, Helvetica Neue, sans-serif',
|
||||
'font-size': '1em',
|
||||
'display': 'inline',
|
||||
},
|
||||
code: {
|
||||
'font-size': '80%',
|
||||
'overflow': 'auto',
|
||||
'color': '#333',
|
||||
'background': 'rgb(247, 247, 247)',
|
||||
'border-radius': '2px',
|
||||
'padding': '10px',
|
||||
'line-height': '1.5',
|
||||
'border': '1px solid rgb(236,236,236)',
|
||||
'margin': '20px 0',
|
||||
},
|
||||
image: {
|
||||
'border-radius': '4px',
|
||||
'display': 'block',
|
||||
'margin': '0.5em auto',
|
||||
'width': '100%'
|
||||
},
|
||||
image_org: {
|
||||
'border-radius': '4px',
|
||||
'display': 'block'
|
||||
},
|
||||
ol: {
|
||||
'margin-left': '0',
|
||||
'padding-left': '1em'
|
||||
},
|
||||
ul: {
|
||||
'margin-left': '0',
|
||||
'padding-left': '1em',
|
||||
'list-style': 'circle'
|
||||
},
|
||||
footnotes: {
|
||||
'margin': '0.5em 8px',
|
||||
'font-size': '80%'
|
||||
},
|
||||
figure: {
|
||||
'margin': '1.5em 8px',
|
||||
}
|
||||
},
|
||||
inline: {
|
||||
// inline element
|
||||
listitem: {
|
||||
'text-indent': '-1em',
|
||||
'display': 'block',
|
||||
'margin': '0.5em 8px'
|
||||
},
|
||||
codespan: {
|
||||
'font-size': '90%',
|
||||
'color': '#d14',
|
||||
'background': 'rgba(27,31,35,.05)',
|
||||
'padding': '3px 5px',
|
||||
'border-radius': '4px',
|
||||
},
|
||||
link: {
|
||||
'color': '#009926'
|
||||
},
|
||||
wx_link: {
|
||||
'color': '#0080ff',
|
||||
'text-decoration': 'none',
|
||||
'border-bottom': '1px solid #d1e9ff'
|
||||
},
|
||||
strong: {
|
||||
'color': '#ff5f2e',
|
||||
'font-weight': 'bold',
|
||||
},
|
||||
table: {
|
||||
'border-collapse': 'collapse',
|
||||
'text-align': 'center',
|
||||
'margin': '1em 8px'
|
||||
},
|
||||
thead: {
|
||||
'background': 'rgba(0, 0, 0, 0.05)'
|
||||
},
|
||||
td: {
|
||||
'font-size': '80%',
|
||||
'border': '1px solid #dfdfdf',
|
||||
'padding': '0.25em 0.5em'
|
||||
},
|
||||
footnote: {
|
||||
'font-size': '12px'
|
||||
},
|
||||
figcaption: {
|
||||
'text-align': 'center',
|
||||
'color': '#888',
|
||||
'font-size': '0.8em'
|
||||
}
|
||||
}
|
||||
};
|
125
assets/scripts/themes/lupeng.js
Normal file
125
assets/scripts/themes/lupeng.js
Normal file
@ -0,0 +1,125 @@
|
||||
let lupengTheme = {
|
||||
BASE: {
|
||||
'text-align': 'left',
|
||||
'color': '#595959',
|
||||
'line-height': '1.55em',
|
||||
'letter-spacing': '0.06em'
|
||||
},
|
||||
BASE_BLOCK: {
|
||||
'margin': '20px 10px'
|
||||
},
|
||||
block: {
|
||||
h1: {
|
||||
'font-size': '140%',
|
||||
'text-align': 'center',
|
||||
'font-weight': 'normal',
|
||||
'margin': '80px 10px 40px 10px'
|
||||
},
|
||||
h2: {
|
||||
'font-size': '140%',
|
||||
'text-align': 'center',
|
||||
'font-weight': 'normal',
|
||||
'margin': '80px 10px 40px 10px'
|
||||
},
|
||||
h3: {
|
||||
'font-weight': 'bold',
|
||||
'font-size': '120%',
|
||||
'margin': '40px 10px 20px 10px'
|
||||
},
|
||||
h4: {
|
||||
'font-weight': 'bold',
|
||||
'font-size': '100%',
|
||||
'margin': '20px 10px 10px 10px'
|
||||
},
|
||||
p: {
|
||||
'margin': '10px 10px',
|
||||
'line-height': '1.6'
|
||||
},
|
||||
blockquote: {
|
||||
'color': '#9a9a9a',
|
||||
'padding-left': '10px',
|
||||
// 'padding-top': '0.05px',
|
||||
'background-color': '#fefefe',
|
||||
'line-height': '1.6',
|
||||
'border-left': '3px solid #dbdbdb',
|
||||
'font-size': '15px',
|
||||
'margin': '1em 0'
|
||||
},
|
||||
code: {
|
||||
'font-size': '80%',
|
||||
'overflow': 'auto',
|
||||
'color': '#333',
|
||||
'background': 'rgb(247, 247, 247)',
|
||||
'border-radius': '2px',
|
||||
'padding': '10px',
|
||||
'line-height': '1.3',
|
||||
'border': '1px solid rgb(236,236,236)',
|
||||
'margin': '20px 0',
|
||||
},
|
||||
image: {
|
||||
'border-radius': '4px',
|
||||
'display': 'block',
|
||||
'margin': '20px auto',
|
||||
'width': '100%',
|
||||
},
|
||||
image_org: {
|
||||
'border-radius': '4px',
|
||||
'display': 'block',
|
||||
},
|
||||
ol: {
|
||||
'margin-left': '0',
|
||||
'padding-left': '20px'
|
||||
},
|
||||
ul: {
|
||||
'margin-left': '0',
|
||||
'padding-left': '20px',
|
||||
'list-style': 'circle',
|
||||
},
|
||||
footnotes: {
|
||||
'margin': '10px 10px',
|
||||
'font-size': '14px'
|
||||
}
|
||||
},
|
||||
inline: {
|
||||
// inline element
|
||||
listitem: {
|
||||
'text-indent': '-20px',
|
||||
'display': 'block',
|
||||
'margin': '10px 10px',
|
||||
},
|
||||
codespan: {
|
||||
'font-size': '0.8em',
|
||||
'color': '#d14',
|
||||
'background': '#fefefe',
|
||||
'padding': '3px 5px 0px',
|
||||
'margin': '0px 2px',
|
||||
'border': '1px solid #ddd',
|
||||
'border-radius': '3px',
|
||||
},
|
||||
link: {
|
||||
'color': '#ff3502'
|
||||
},
|
||||
wx_link: {
|
||||
'color': '#576b95',
|
||||
'text-decoration': 'none'
|
||||
},
|
||||
strong: {
|
||||
'font-weight': 'bold',
|
||||
},
|
||||
table: {
|
||||
'border-collapse': 'collapse',
|
||||
'margin': '20px 0',
|
||||
},
|
||||
thead: {
|
||||
'background': 'rgba(0,0,0,0.05)',
|
||||
},
|
||||
td: {
|
||||
'font-size': '80%',
|
||||
'border': '1px solid #dfdfdf',
|
||||
'padding': '4px 8px',
|
||||
},
|
||||
footnote: {
|
||||
'font-size': '12px',
|
||||
}
|
||||
}
|
||||
};
|
116
assets/scripts/themes/lyric.js
Normal file
116
assets/scripts/themes/lyric.js
Normal file
@ -0,0 +1,116 @@
|
||||
let lyricTheme = {
|
||||
BASE: {
|
||||
'text-align': 'left',
|
||||
'color': '#3f3f3f',
|
||||
'line-height': '1.5'
|
||||
},
|
||||
BASE_BLOCK: {
|
||||
'margin': '20px 10px'
|
||||
},
|
||||
// block element
|
||||
block: {
|
||||
h1: {
|
||||
'font-size': '140%',
|
||||
'text-align': 'center',
|
||||
'font-weight': 'normal',
|
||||
'margin': '80px 10px 40px 10px'
|
||||
},
|
||||
h2: {
|
||||
'font-size': '140%',
|
||||
'text-align': 'center',
|
||||
'font-weight': 'normal',
|
||||
'margin': '80px 10px 40px 10px'
|
||||
},
|
||||
h3: {
|
||||
'font-weight': 'bold',
|
||||
'font-size': '120%',
|
||||
'margin': '40px 10px 20px 10px'
|
||||
},
|
||||
h4: {
|
||||
'font-weight': 'bold',
|
||||
'font-size': '100%',
|
||||
'margin': '20px 10px 10px 10px'
|
||||
},
|
||||
p: {
|
||||
'margin': '10px 10px',
|
||||
'line-height': '1.6'
|
||||
},
|
||||
blockquote: {
|
||||
'color': 'rgb(91, 91, 91)',
|
||||
'padding': '1px 0 1px 10px',
|
||||
'background': 'rgba(158, 158, 158, 0.1)',
|
||||
'border-left': '3px solid rgb(158,158,158)',
|
||||
},
|
||||
code: {
|
||||
'font-size': '80%',
|
||||
'overflow': 'auto',
|
||||
'color': '#333',
|
||||
'background': 'rgb(247, 247, 247)',
|
||||
'border-radius': '2px',
|
||||
'padding': '10px',
|
||||
'line-height': '1.3',
|
||||
'border': '1px solid rgb(236,236,236)',
|
||||
'margin': '20px 0',
|
||||
},
|
||||
image: {
|
||||
'border-radius': '4px',
|
||||
'display': 'block',
|
||||
'margin': '20px auto',
|
||||
'width': '100%',
|
||||
},
|
||||
image_org: {
|
||||
'border-radius': '4px',
|
||||
'display': 'block',
|
||||
},
|
||||
ol: {
|
||||
'margin-left': '0',
|
||||
'padding-left': '20px'
|
||||
},
|
||||
ul: {
|
||||
'margin-left': '0',
|
||||
'padding-left': '20px',
|
||||
'list-style': 'circle',
|
||||
},
|
||||
footnotes: {
|
||||
'margin': '10px 10px',
|
||||
'font-size': '14px'
|
||||
}
|
||||
},
|
||||
inline: {
|
||||
// inline element
|
||||
listitem: {
|
||||
'text-indent': '-20px',
|
||||
'display': 'block',
|
||||
'margin': '10px 10px',
|
||||
},
|
||||
codespan: {
|
||||
'font-size': '90%',
|
||||
// 'font-family': FONT_FAMILY_MONO,
|
||||
'color': '#ff3502',
|
||||
'background': '#f8f5ec',
|
||||
'padding': '3px 5px',
|
||||
'border-radius': '2px',
|
||||
},
|
||||
link: {
|
||||
'color': '#ff3502'
|
||||
},
|
||||
strong: {
|
||||
'color': '#ff3502'
|
||||
},
|
||||
table: {
|
||||
'border-collapse': 'collapse',
|
||||
'margin': '20px 0',
|
||||
},
|
||||
thead: {
|
||||
'background': 'rgba(0,0,0,0.05)',
|
||||
},
|
||||
td: {
|
||||
'font-size': '80%',
|
||||
'border': '1px solid #dfdfdf',
|
||||
'padding': '4px 8px',
|
||||
},
|
||||
footnote: {
|
||||
'font-size': '12px',
|
||||
}
|
||||
}
|
||||
};
|
139
index.html
Normal file
139
index.html
Normal file
@ -0,0 +1,139 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>微信公众号 Markdown 编辑器</title>
|
||||
<link rel="shortcut icon" href="assets/images/favicon.png">
|
||||
<link rel="apple-touch-icon-precomposed" href="assets/images/favicon.png">
|
||||
|
||||
<link rel="stylesheet" href="assets/css/loading.css">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.11.1/theme-chalk/index.css">
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.org/codemirror/5.48.4/codemirror.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.org/codemirror/5.48.4/theme/base16-light.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.org/codemirror/5.48.4/theme/duotone-light.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.staticfile.org/codemirror/5.48.4/theme/monokai.min.css">
|
||||
|
||||
<link rel="stylesheet" href="libs/prettify/color-themes/github-v2.min.css">
|
||||
|
||||
<link rel="stylesheet" href="assets/css/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<!--loading 界面-->
|
||||
<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" class="container">
|
||||
<el-container>
|
||||
<el-header class="top">
|
||||
<div><img src="assets/images/favicon.png" class="web-icon" alt="icon"> <span
|
||||
class="web-title">公众号 Markdown 编辑器 </span></div>
|
||||
<el-form size="mini" class="ctrl" :inline=true>
|
||||
<el-form-item label="Editor Themes">
|
||||
<el-select v-model="currentEditorTheme" size="mini" placeholder="选择字体" @change="editorThemeChanged">
|
||||
<el-option v-for="editorTheme in editorThemes" :key="editorTheme.value" :label="editorTheme.label"
|
||||
:value="editorTheme.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Fonts">
|
||||
<el-select v-model="currentFont" size="mini" placeholder="选择字体" @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 label="Font Size">
|
||||
<el-select v-model="currentSize" size="mini" placeholder="选择段落字体大小" @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 label="Themes">
|
||||
<el-select v-model="currentTheme" size="mini" placeholder="选择主题样式" @change="themeChanged">
|
||||
<el-option v-for="theme in themeOption" :key="theme.value" :label="theme.label" :value="theme.value">
|
||||
<span class="select-item-left">{{ theme.label }}</span>
|
||||
<span class="select-item-right">{{ theme.author }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button 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 here."
|
||||
v-model="source">
|
||||
</textarea>
|
||||
</el-col>
|
||||
<el-col :span="12" class="preview-wrapper" id="preview">
|
||||
<section>
|
||||
<div class="hint">全选复制或<a href="#" @click="copy" class="copy-button">点此复制</a>,然后在公众号编辑器粘贴</div>
|
||||
<div class="preview" contenteditable="true">
|
||||
<div id="output" v-html="output">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<el-dialog title="关于" :visible.sync="aboutDialogVisible" width="30%" center>
|
||||
<div>
|
||||
<p>一款可以将 Markdown 转换为微信公众号文章的在线编辑器,</p>
|
||||
<p>这让你在公众号创作时,摆脱繁琐地排版样式,</p>
|
||||
<p>可以把更多的时间专注于文章本身。</p>
|
||||
<p>除了常规 Markdown 格式化,还增加了外链引用、注音样式等。</p>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<img src="https://static.zkqiang.cn/images/20191019181436.JPG-slim" style="max-width: 300px">
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button type="primary"
|
||||
@click="openWindow('https://github.com/zkqiang/wechat-format')">查看 GitHub 仓库</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.staticfile.org/vue/2.6.10/vue.min.js"></script>
|
||||
<script src="https://cdn.staticfile.org/axios/0.19.0-beta.1/axios.min.js"></script>
|
||||
<script src="https://cdn.staticfile.org/marked/0.7.0/marked.min.js"></script>
|
||||
<script src="https://cdn.staticfile.org/codemirror/5.48.4/codemirror.min.js"></script>
|
||||
<script src="https://cdn.staticfile.org/codemirror/5.48.4/mode/markdown/markdown.min.js"></script>
|
||||
<script src="https://cdn.staticfile.org/prettify/r298/prettify.min.js"></script>
|
||||
<script src="https://cdn.staticfile.org/element-ui/2.11.1/index.js"></script>
|
||||
<script src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js"></script>
|
||||
|
||||
<script src="libs/sync-scroll.js"></script>
|
||||
<script src="libs/FuriganaMD.js"></script>
|
||||
|
||||
<script src="assets/scripts/themes/default.js"></script>
|
||||
<script src="assets/scripts/themes/lyric.js"></script>
|
||||
<script src="assets/scripts/themes/lupeng.js"></script>
|
||||
<script src="assets/scripts/renderers/wx-renderer.js"></script>
|
||||
<script src="assets/scripts/editor.js"></script>
|
||||
<script src="assets/scripts/loading.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
241
libs/FuriganaMD.js
Normal file
241
libs/FuriganaMD.js
Normal file
@ -0,0 +1,241 @@
|
||||
// 注音功能来自于
|
||||
// https://github.com/amclees/furigana-markdown
|
||||
// 详见上述文档
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global.FuriganaMD = factory());
|
||||
}(this, (function () {
|
||||
'use strict';
|
||||
|
||||
// This function escapes special characters for use in a regex constructor.
|
||||
function escapeForRegex(string) {
|
||||
return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
}
|
||||
|
||||
function emptyStringFilter(block) {
|
||||
return block !== '';
|
||||
}
|
||||
|
||||
const kanjiRange = '\\u4e00-\\u9faf';
|
||||
const kanjiBlockRegex = new RegExp(`[${kanjiRange}]+`, 'g');
|
||||
const nonKanjiBlockRegex = new RegExp(`[^${kanjiRange}]+`, 'g');
|
||||
const kanaWithAnnotations = '\\u3041-\\u3095\\u3099-\\u309c\\u3081-\\u30fa\\u30fc';
|
||||
const furiganaSeperators = '..。・';
|
||||
const seperatorRegex = new RegExp(`[${furiganaSeperators}]`, 'g');
|
||||
|
||||
const singleKanjiRegex = new RegExp(`^[${kanjiRange}]$`);
|
||||
|
||||
function isKanji(character) {
|
||||
return character.match(singleKanjiRegex);
|
||||
}
|
||||
|
||||
const innerRegexString = '(?:[^\\u0000-\\u007F]|\\w)+';
|
||||
|
||||
let regexList = [];
|
||||
let previousFuriganaForms = '';
|
||||
|
||||
function updateRegexList(furiganaForms) {
|
||||
previousFuriganaForms = furiganaForms;
|
||||
let formArray = furiganaForms.split('|');
|
||||
if (formArray.length === 0) {
|
||||
formArray = ['[]:^:()'];
|
||||
}
|
||||
regexList = formArray.map(form => {
|
||||
let furiganaComponents = form.split(':');
|
||||
if (furiganaComponents.length !== 3) {
|
||||
furiganaComponents = ['[]', '^', '()'];
|
||||
}
|
||||
const mainBrackets = furiganaComponents[0];
|
||||
const seperator = furiganaComponents[1];
|
||||
const furiganaBrackets = furiganaComponents[2];
|
||||
return new RegExp(
|
||||
escapeForRegex(mainBrackets[0]) +
|
||||
'(' + innerRegexString + ')' +
|
||||
escapeForRegex(mainBrackets[1]) +
|
||||
escapeForRegex(seperator) +
|
||||
escapeForRegex(furiganaBrackets[0]) +
|
||||
'(' + innerRegexString + ')' +
|
||||
escapeForRegex(furiganaBrackets[1]),
|
||||
'g'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
let autoRegexList = [];
|
||||
let previousAutoBracketSets = '';
|
||||
|
||||
function updateAutoRegexList(autoBracketSets) {
|
||||
previousAutoBracketSets = autoBracketSets;
|
||||
autoRegexList = autoBracketSets.split('|').map(brackets => {
|
||||
/*
|
||||
Sample built regex:
|
||||
/(^|[^\u4e00-\u9faf]|)([\u4e00-\u9faf]+)([\u3041-\u3095\u3099-\u309c\u3081-\u30fa\u30fc]*)【((?:[^【】\u4e00-\u9faf]|w)+)】/g
|
||||
*/
|
||||
return new RegExp(
|
||||
`(^|[^${kanjiRange}]|)` +
|
||||
`([${kanjiRange}]+)` +
|
||||
`([${kanaWithAnnotations}]*)` +
|
||||
escapeForRegex(brackets[0]) +
|
||||
`((?:[^${escapeForRegex(brackets)}\\u0000-\\u007F]|\\w|[${furiganaSeperators}])+)` +
|
||||
escapeForRegex(brackets[1]),
|
||||
'g'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
let replacementTemplate = '';
|
||||
let replacementBrackets = '';
|
||||
|
||||
function updateReplacementTemplate(furiganaFallbackBrackets) {
|
||||
if (furiganaFallbackBrackets.length !== 2) {
|
||||
furiganaFallbackBrackets = '【】';
|
||||
}
|
||||
replacementBrackets = furiganaFallbackBrackets;
|
||||
replacementTemplate = `<ruby>$1<rp>${furiganaFallbackBrackets[0]}</rp><rt style="line-height:1;font-size:10px;">$2</rt><rp>${furiganaFallbackBrackets[1]}</rp></ruby>`;
|
||||
}
|
||||
|
||||
updateReplacementTemplate('【】');
|
||||
|
||||
function addFurigana(text, options) {
|
||||
if (options.furiganaForms !== previousFuriganaForms) {
|
||||
updateRegexList(options.furiganaForms);
|
||||
}
|
||||
if (options.furiganaFallbackBrackets !== replacementBrackets) {
|
||||
updateReplacementTemplate(options.furiganaFallbackBrackets);
|
||||
}
|
||||
regexList.forEach(regex => {
|
||||
text = text.replace(regex, (match, wordText, furiganaText, offset, mainText) => {
|
||||
if (match.indexOf('\\') === -1 && mainText[offset - 1] !== '\\') {
|
||||
if ((!options.furiganaPatternMatching) || wordText.search(kanjiBlockRegex) === -1 || wordText[0].search(kanjiBlockRegex) === -1) {
|
||||
return replacementTemplate.replace('$1', wordText).replace('$2', furiganaText);
|
||||
} else {
|
||||
let originalFuriganaText = (' ' + furiganaText).slice(1);
|
||||
let nonKanji = wordText.split(kanjiBlockRegex).filter(emptyStringFilter);
|
||||
let kanji = wordText.split(nonKanjiBlockRegex).filter(emptyStringFilter);
|
||||
let replacementText = '';
|
||||
let lastUsedKanjiIndex = 0;
|
||||
if (nonKanji.length === 0) {
|
||||
return replacementTemplate.replace('$1', wordText).replace('$2', furiganaText);
|
||||
}
|
||||
|
||||
nonKanji.forEach((currentNonKanji, index) => {
|
||||
if (furiganaText === undefined) {
|
||||
if (index < kanji.length) {
|
||||
replacementText += kanji[index];
|
||||
}
|
||||
|
||||
replacementText += currentNonKanji;
|
||||
return;
|
||||
}
|
||||
let splitFurigana = furiganaText.split(new RegExp(escapeForRegex(currentNonKanji) + '(.*)')).filter(emptyStringFilter);
|
||||
|
||||
lastUsedKanjiIndex = index;
|
||||
replacementText += replacementTemplate.replace('$1', kanji[index]).replace('$2', splitFurigana[0]);
|
||||
replacementText += currentNonKanji;
|
||||
|
||||
furiganaText = splitFurigana[1];
|
||||
});
|
||||
if (furiganaText !== undefined && lastUsedKanjiIndex + 1 < kanji.length) {
|
||||
replacementText += replacementTemplate.replace('$1', kanji[lastUsedKanjiIndex + 1]).replace('$2', furiganaText);
|
||||
} else if (furiganaText !== undefined) {
|
||||
return replacementTemplate.replace('$1', wordText).replace('$2', originalFuriganaText);
|
||||
} else if (lastUsedKanjiIndex + 1 < kanji.length) {
|
||||
replacementText += kanji[lastUsedKanjiIndex + 1];
|
||||
}
|
||||
return replacementText;
|
||||
}
|
||||
} else {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!options.furiganaStrictMode) {
|
||||
if (options.furiganaAutoBracketSets !== previousAutoBracketSets) {
|
||||
updateAutoRegexList(options.furiganaAutoBracketSets);
|
||||
}
|
||||
autoRegexList.forEach(regex => {
|
||||
text = text.replace(regex, (match, preWordTerminator, wordKanji, wordKanaSuffix, furiganaText, offset, mainText) => {
|
||||
if (match.indexOf('\\') === -1) {
|
||||
if (options.furiganaPatternMatching) {
|
||||
let rubies = [];
|
||||
|
||||
let furigana = furiganaText;
|
||||
|
||||
let stem = (' ' + wordKanaSuffix).slice(1);
|
||||
for (let i = furiganaText.length - 1; i >= 0; i--) {
|
||||
if (wordKanaSuffix.length === 0) {
|
||||
furigana = furiganaText.substring(0, i + 1);
|
||||
break;
|
||||
}
|
||||
if (furiganaText[i] !== wordKanaSuffix.slice(-1)) {
|
||||
furigana = furiganaText.substring(0, i + 1);
|
||||
break;
|
||||
}
|
||||
wordKanaSuffix = wordKanaSuffix.slice(0, -1);
|
||||
}
|
||||
|
||||
if (furiganaSeperators.split('').reduce(
|
||||
(noSeperator, seperator) => {
|
||||
return noSeperator && (furigana.indexOf(seperator) === -1);
|
||||
},
|
||||
true
|
||||
)) {
|
||||
rubies = [replacementTemplate.replace('$1', wordKanji).replace('$2', furigana)];
|
||||
} else {
|
||||
let kanaParts = furigana.split(seperatorRegex);
|
||||
let kanji = wordKanji.split('');
|
||||
if (kanaParts.length === 0 || kanaParts.length > kanji.length) {
|
||||
rubies = [replacementTemplate.replace('$1', wordKanji).replace('$2', furigana)];
|
||||
} else {
|
||||
for (let i = 0; i < kanaParts.length - 1; i++) {
|
||||
if (kanji.length === 0) {
|
||||
break;
|
||||
}
|
||||
rubies.push(replacementTemplate.replace('$1', kanji.shift()).replace('$2', kanaParts[i]));
|
||||
}
|
||||
let lastKanaPart = kanaParts.pop();
|
||||
rubies.push(replacementTemplate.replace('$1', kanji.join('')).replace('$2', lastKanaPart));
|
||||
}
|
||||
}
|
||||
|
||||
return preWordTerminator + rubies.join('') + stem;
|
||||
} else {
|
||||
return preWordTerminator + replacementTemplate.replace('$1', wordKanji).replace('$2', furiganaText) + wordKanaSuffix;
|
||||
}
|
||||
} else {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function handleEscapedSpecialBrackets(text) {
|
||||
// By default 【 and 】 cannot be escaped in markdown, this will remove backslashes from in front of them to give that effect.
|
||||
return text.replace(/\\([【】])/g, '$1');
|
||||
}
|
||||
|
||||
let FuriganaMD = {};
|
||||
FuriganaMD.register = function (renderer) {
|
||||
renderer.text = function (text) {
|
||||
let options = {
|
||||
furigana: true,
|
||||
furiganaForms: "()::{}",
|
||||
furiganaFallbackBrackets: "{}",
|
||||
furiganaStrictMode: false,
|
||||
furiganaAutoBracketSets: "{}",
|
||||
furiganaPatternMatching: true,
|
||||
};
|
||||
// console.log('override text render',text);
|
||||
// console.log('after add',addFurigana(text, options));
|
||||
return handleEscapedSpecialBrackets(addFurigana(text, options));
|
||||
};
|
||||
};
|
||||
|
||||
return FuriganaMD;
|
||||
|
||||
})));
|
2
libs/prettify/color-themes/github-v2.min.css
vendored
Normal file
2
libs/prettify/color-themes/github-v2.min.css
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! Color themes for Google Code Prettify | MIT License | github.com/jmblog/color-themes-for-google-code-prettify */
|
||||
.prettyprint{font-family:Menlo,Bitstream Vera Sans Mono,DejaVu Sans Mono,Monaco,Consolas,monospace;border:0!important}.pln{color:#333}ol.linenums{margin-top:0;margin-bottom:0;color:#ccc}li.L0,li.L1,li.L2,li.L3,li.L4,li.L5,li.L6,li.L7,li.L8,li.L9{padding-left:1em;background-color:#fafbfc;list-style-type:decimal}@media screen{.str{color:#183691}.kwd{color:#a71d5d}.com{color:#969896}.typ{color:#0086b3}.lit{color:#0086b3}.pun{color:#333}.opn{color:#333}.clo{color:#333}.tag{color:navy}.atn{color:#795da3}.atv{color:#183691}.dec{color:#333}.var{color:teal}.fun{color:#900}}
|
2
libs/prettify/color-themes/tomorrow-night-eighties.min.css
vendored
Normal file
2
libs/prettify/color-themes/tomorrow-night-eighties.min.css
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! Color themes for Google Code Prettify | MIT License | github.com/jmblog/color-themes-for-google-code-prettify */
|
||||
.prettyprint{background:#2d2d2d!important;font-family:Menlo,Bitstream Vera Sans Mono,DejaVu Sans Mono,Monaco,Consolas,monospace;border:0!important}.pln{color:#ccc}ol.linenums{margin-top:0;margin-bottom:0;color:#999}li.L0,li.L1,li.L2,li.L3,li.L4,li.L5,li.L6,li.L7,li.L8,li.L9{padding-left:1em;background-color:#2d2d2d;list-style-type:decimal}@media screen{.str{color:#9c9}.kwd{color:#c9c}.com{color:#999}.typ{color:#69c}.lit{color:#f99157}.pun{color:#ccc}.opn{color:#ccc}.clo{color:#ccc}.tag{color:#f2777a}.atn{color:#f99157}.atv{color:#6cc}.dec{color:#f99157}.var{color:#f2777a}.fun{color:#69c}}
|
2
libs/prettify/color-themes/tomorrow-night.min.css
vendored
Normal file
2
libs/prettify/color-themes/tomorrow-night.min.css
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! Color themes for Google Code Prettify | MIT License | github.com/jmblog/color-themes-for-google-code-prettify */
|
||||
.prettyprint{background:#1d1f21!important;font-family:Menlo,Bitstream Vera Sans Mono,DejaVu Sans Mono,Monaco,Consolas,monospace;border:0!important}.pln{color:#c5c8c6}ol.linenums{margin-top:0;margin-bottom:0;color:#969896}li.L0,li.L1,li.L2,li.L3,li.L4,li.L5,li.L6,li.L7,li.L8,li.L9{padding-left:1em;background-color:#1d1f21;list-style-type:decimal}@media screen{.str{color:#b5bd68}.kwd{color:#b294bb}.com{color:#969896}.typ{color:#81a2be}.lit{color:#de935f}.pun{color:#c5c8c6}.opn{color:#c5c8c6}.clo{color:#c5c8c6}.tag{color:#c66}.atn{color:#de935f}.atv{color:#8abeb7}.dec{color:#de935f}.var{color:#c66}.fun{color:#81a2be}}
|
2
libs/prettify/color-themes/tomorrow.min.css
vendored
Normal file
2
libs/prettify/color-themes/tomorrow.min.css
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! Color themes for Google Code Prettify | MIT License | github.com/jmblog/color-themes-for-google-code-prettify */
|
||||
.prettyprint{background:#f6f8fa!important;font-family:Menlo,Bitstream Vera Sans Mono,DejaVu Sans Mono,Monaco,Consolas,monospace;border:0!important}.pln{color:#4d4d4c}ol.linenums{margin-top:0;margin-bottom:0;color:#8e908c}li.L0,li.L1,li.L2,li.L3,li.L4,li.L5,li.L6,li.L7,li.L8,li.L9{padding-left:1em;background-color:#f6f8fa;list-style-type:decimal}@media screen{.str{color:#718c00}.kwd{color:#8959a8}.com{color:#8e908c}.typ{color:#4271ae}.lit{color:#f5871f}.pun{color:#4d4d4c}.opn{color:#4d4d4c}.clo{color:#4d4d4c}.tag{color:#c82829}.atn{color:#f5871f}.atv{color:#3e999f}.dec{color:#f5871f}.var{color:#c82829}.fun{color:#4271ae}}
|
27
libs/sync-scroll.js
Normal file
27
libs/sync-scroll.js
Normal file
@ -0,0 +1,27 @@
|
||||
// 左右栏同步滚动
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
let timeout;
|
||||
|
||||
$('div.CodeMirror-scroll, #preview').on("scroll", function callback() {
|
||||
clearTimeout(timeout);
|
||||
|
||||
let source = $(this),
|
||||
target = $(source.is("#preview") ? 'div.CodeMirror-scroll' : '#preview');
|
||||
|
||||
target.off("scroll");
|
||||
|
||||
let source0 = source[0];
|
||||
let target0 = target[0];
|
||||
|
||||
let percentage = source0.scrollTop / (source0.scrollHeight - source0.offsetHeight);
|
||||
let height = percentage * (target0.scrollHeight - target0.offsetHeight);
|
||||
target0.scrollTo(0, height);
|
||||
|
||||
timeout = setTimeout(function () {
|
||||
target.on("scroll", callback);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue
Block a user