mirror of
https://github.com/doocs/md.git
synced 2024-11-24 19:10:34 +08:00
refactor: use uni-app framework (#55)
* update framework to uni-app * fix: bug fix * fix: bug fix * 修改输出路径 * feat: publicPath * feat: manifest update * fix: cssEditor theme * fix: style * style: format code with prettier * fix: table style & copy style on the night mode * fix: upload image * fix: style Co-authored-by: yanglbme <szuyanglb@outlook.com>
This commit is contained in:
parent
432db15576
commit
b50ae32834
0
.automator/h5/.automator.json
Normal file
0
.automator/h5/.automator.json
Normal file
@ -1,2 +0,0 @@
|
||||
> 1%
|
||||
last 2 versions
|
@ -1,5 +1,18 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
# https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 80
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = 0
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[COMMIT_EDITMSG]
|
||||
max_line_length = 0
|
||||
|
@ -1,3 +1,60 @@
|
||||
const plugins = [];
|
||||
|
||||
if (process.env.UNI_OPT_TREESHAKINGNG) {
|
||||
plugins.push(
|
||||
require("@dcloudio/vue-cli-plugin-uni-optimize/packages/babel-plugin-uni-api/index.js")
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
(process.env.UNI_PLATFORM === "app-plus" && process.env.UNI_USING_V8) ||
|
||||
(process.env.UNI_PLATFORM === "h5" &&
|
||||
process.env.UNI_H5_BROWSER === "builtin")
|
||||
) {
|
||||
const path = require("path");
|
||||
|
||||
const isWin = /^win/.test(process.platform);
|
||||
|
||||
const normalizePath = (path) => (isWin ? path.replace(/\\/g, "/") : path);
|
||||
|
||||
const input = normalizePath(process.env.UNI_INPUT_DIR);
|
||||
try {
|
||||
plugins.push([
|
||||
require("@dcloudio/vue-cli-plugin-hbuilderx/packages/babel-plugin-console"),
|
||||
{
|
||||
file(file) {
|
||||
file = normalizePath(file);
|
||||
if (file.indexOf(input) === 0) {
|
||||
return path.relative(input, file);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
]);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
process.UNI_LIBRARIES = process.UNI_LIBRARIES || ["@dcloudio/uni-ui"];
|
||||
process.UNI_LIBRARIES.forEach((libraryName) => {
|
||||
plugins.push([
|
||||
"import",
|
||||
{
|
||||
libraryName: libraryName,
|
||||
customName: (name) => {
|
||||
return `${libraryName}/lib/${name}/${name}`;
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"],
|
||||
presets: [
|
||||
[
|
||||
"@vue/app",
|
||||
{
|
||||
modules: "commonjs",
|
||||
useBuiltIns: process.env.UNI_PLATFORM === "h5" ? "usage" : "entry",
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins,
|
||||
};
|
||||
|
@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
preset: "@vue/cli-plugin-unit-jest",
|
||||
};
|
27644
package-lock.json
generated
27644
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
115
package.json
115
package.json
@ -1,53 +1,112 @@
|
||||
{
|
||||
"name": "vue-md",
|
||||
"version": "1.4.7",
|
||||
"homepage": ".",
|
||||
"description": "An open-source wechat markdown editor.",
|
||||
"author": "doocs",
|
||||
"name": "md",
|
||||
"version": "1.5.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"test:unit": "vue-cli-service test:unit"
|
||||
"serve": "npm run dev:h5",
|
||||
"build": "npm run build:h5",
|
||||
"build:app-plus": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus vue-cli-service uni-build",
|
||||
"build:custom": "cross-env NODE_ENV=production uniapp-cli custom",
|
||||
"build:h5": "cross-env NODE_ENV=production UNI_OUTPUT_DIR=dist UNI_PLATFORM=h5 vue-cli-service uni-build",
|
||||
"build:mp-360": "cross-env NODE_ENV=production UNI_PLATFORM=mp-360 vue-cli-service uni-build",
|
||||
"build:mp-alipay": "cross-env NODE_ENV=production UNI_PLATFORM=mp-alipay vue-cli-service uni-build",
|
||||
"build:mp-baidu": "cross-env NODE_ENV=production UNI_PLATFORM=mp-baidu vue-cli-service uni-build",
|
||||
"build:mp-kuaishou": "cross-env NODE_ENV=production UNI_PLATFORM=mp-kuaishou vue-cli-service uni-build",
|
||||
"build:mp-qq": "cross-env NODE_ENV=production UNI_PLATFORM=mp-qq vue-cli-service uni-build",
|
||||
"build:mp-toutiao": "cross-env NODE_ENV=production UNI_PLATFORM=mp-toutiao vue-cli-service uni-build",
|
||||
"build:mp-weixin": "cross-env NODE_ENV=production UNI_PLATFORM=mp-weixin vue-cli-service uni-build",
|
||||
"build:quickapp-native": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-native vue-cli-service uni-build",
|
||||
"build:quickapp-webview": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview vue-cli-service uni-build",
|
||||
"build:quickapp-webview-huawei": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview-huawei vue-cli-service uni-build",
|
||||
"build:quickapp-webview-union": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview-union vue-cli-service uni-build",
|
||||
"dev:app-plus": "cross-env NODE_ENV=development UNI_PLATFORM=app-plus vue-cli-service uni-build --watch",
|
||||
"dev:custom": "cross-env NODE_ENV=development uniapp-cli custom",
|
||||
"dev:h5": "cross-env NODE_ENV=development UNI_OUTPUT_DIR=dist UNI_PLATFORM=h5 vue-cli-service uni-serve",
|
||||
"dev:mp-360": "cross-env NODE_ENV=development UNI_PLATFORM=mp-360 vue-cli-service uni-build --watch",
|
||||
"dev:mp-alipay": "cross-env NODE_ENV=development UNI_PLATFORM=mp-alipay vue-cli-service uni-build --watch",
|
||||
"dev:mp-baidu": "cross-env NODE_ENV=development UNI_PLATFORM=mp-baidu vue-cli-service uni-build --watch",
|
||||
"dev:mp-kuaishou": "cross-env NODE_ENV=development UNI_PLATFORM=mp-kuaishou vue-cli-service uni-build --watch",
|
||||
"dev:mp-qq": "cross-env NODE_ENV=development UNI_PLATFORM=mp-qq vue-cli-service uni-build --watch",
|
||||
"dev:mp-toutiao": "cross-env NODE_ENV=development UNI_PLATFORM=mp-toutiao vue-cli-service uni-build --watch",
|
||||
"dev:mp-weixin": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch",
|
||||
"dev:quickapp-native": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-native vue-cli-service uni-build --watch",
|
||||
"dev:quickapp-webview": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview vue-cli-service uni-build --watch",
|
||||
"dev:quickapp-webview-huawei": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview-huawei vue-cli-service uni-build --watch",
|
||||
"dev:quickapp-webview-union": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview-union vue-cli-service uni-build --watch",
|
||||
"info": "node node_modules/@dcloudio/vue-cli-plugin-uni/commands/info.js",
|
||||
"serve:quickapp-native": "node node_modules/@dcloudio/uni-quickapp-native/bin/serve.js",
|
||||
"test:android": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=android jest -i",
|
||||
"test:h5": "cross-env UNI_PLATFORM=h5 jest -i",
|
||||
"test:ios": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=ios jest -i",
|
||||
"test:mp-baidu": "cross-env UNI_PLATFORM=mp-baidu jest -i",
|
||||
"test:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin jest -i"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app-plus": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-h5": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-helper-json": "*",
|
||||
"@dcloudio/uni-mp-360": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-mp-alipay": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-mp-baidu": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-mp-qq": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-mp-toutiao": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-mp-vue": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-mp-weixin": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-quickapp-native": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-quickapp-webview": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-stat": "^2.0.0-30720210122002",
|
||||
"@vue/shared": "^3.0.0",
|
||||
"ali-oss": "^6.11.2",
|
||||
"axios": "^0.21.0",
|
||||
"buffer-from": "^1.1.1",
|
||||
"codemirror": "^5.58.3",
|
||||
"core-js": "^3.7.0",
|
||||
"core-js": "^3.8.3",
|
||||
"cos-js-sdk-v5": "^1.1.0",
|
||||
"crypto-js": "^4.0.0",
|
||||
"element-ui": "^2.14.1",
|
||||
"flyio": "^0.6.2",
|
||||
"jquery": "^3.5.1",
|
||||
"juice": "^7.0.0",
|
||||
"less": "^3.12.2",
|
||||
"marked": "^2.0.0",
|
||||
"prettier": "^2.2.0",
|
||||
"prettify": "^0.1.7",
|
||||
"qiniu-js": "^3.1.2",
|
||||
"regenerator-runtime": "^0.12.1",
|
||||
"uuid": "^8.3.1",
|
||||
"vue": "^2.6.12",
|
||||
"vue-router": "^3.4.9",
|
||||
"vuex": "^3.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.5.9",
|
||||
"@vue/cli-plugin-eslint": "^4.5.9",
|
||||
"@vue/cli-plugin-unit-jest": "^4.5.9",
|
||||
"@vue/cli-service": "^4.5.9",
|
||||
"@vue/eslint-config-standard": "^5.1.2",
|
||||
"@vue/test-utils": "1.1.1",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.14.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"eslint-plugin-vue": "^7.1.0",
|
||||
"less-loader": "^7.1.0",
|
||||
"node-sass": "^5.0.0",
|
||||
"sass-loader": "^10.1.0",
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
"@dcloudio/types": "*",
|
||||
"@dcloudio/uni-automator": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-cli-shared": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-migration": "^2.0.0-30720210122002",
|
||||
"@dcloudio/uni-template-compiler": "^2.0.0-30720210122002",
|
||||
"@dcloudio/vue-cli-plugin-hbuilderx": "^2.0.0-30720210122002",
|
||||
"@dcloudio/vue-cli-plugin-uni": "^2.0.0-30720210122002",
|
||||
"@dcloudio/vue-cli-plugin-uni-optimize": "^2.0.0-30720210122002",
|
||||
"@dcloudio/webpack-uni-mp-loader": "^2.0.0-30720210122002",
|
||||
"@dcloudio/webpack-uni-pages-loader": "^2.0.0-30720210122002",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"async-validator": "^1.11.5",
|
||||
"babel-plugin-import": "^1.11.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"jest": "^25.4.0",
|
||||
"less": "^3.12.2",
|
||||
"less-loader": "^5.0.0",
|
||||
"mini-types": "*",
|
||||
"miniprogram-api-typings": "*",
|
||||
"postcss-comment": "^2.0.0",
|
||||
"sass-loader": "^11.0.1",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"browserslist": [
|
||||
"Android >= 4",
|
||||
"ios >= 8"
|
||||
],
|
||||
"uni-app": {
|
||||
"scripts": {}
|
||||
}
|
||||
}
|
||||
|
22
postcss.config.js
Normal file
22
postcss.config.js
Normal file
@ -0,0 +1,22 @@
|
||||
const path = require("path");
|
||||
module.exports = {
|
||||
parser: require("postcss-comment"),
|
||||
plugins: [
|
||||
require("postcss-import")({
|
||||
resolve(id, basedir, importOptions) {
|
||||
if (id.startsWith("~@/")) {
|
||||
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(3));
|
||||
} else if (id.startsWith("@/")) {
|
||||
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(2));
|
||||
} else if (id.startsWith("/") && !id.startsWith("//")) {
|
||||
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(1));
|
||||
}
|
||||
return id;
|
||||
},
|
||||
}),
|
||||
require("autoprefixer")({
|
||||
remove: process.env.UNI_PLATFORM !== "h5",
|
||||
}),
|
||||
require("@dcloudio/vue-cli-plugin-uni/packages/postcss"),
|
||||
],
|
||||
};
|
@ -1,33 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta
|
||||
name="keywords"
|
||||
content="md,markdown,markdown-editor,wechat,official-account,yanglbme,doocs"
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
content="Wechat Markdown Editor | 一款高度简洁的微信 Markdown 编辑器"
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
|
||||
/>
|
||||
<title>微信 Markdown 编辑器</title>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href="https://gitee.com/yanglbme/resource/raw/master/doocs-md/favicon.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon-precomposed"
|
||||
href="https://gitee.com/yanglbme/resource/raw/master/doocs-md/qrcode.png"
|
||||
/>
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/prettify/r224/prettify.min.js"></script>
|
||||
</head>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<body>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="keywords" content="md,markdown,markdown-editor,wechat,official-account,yanglbme,doocs" />
|
||||
<meta name="description" content="Wechat Markdown Editor | 一款高度简洁的微信 Markdown 编辑器" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
|
||||
<!-- <title>
|
||||
<%= htmlWebpackPlugin.options.title %>
|
||||
</title> -->
|
||||
<title>微信 Markdown 编辑器</title>
|
||||
<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
|
||||
<link rel="shortcut icon" href="https://gitee.com/yanglbme/resource/raw/master/doocs-md/favicon.png" />
|
||||
<link rel="apple-touch-icon-precomposed" href="https://gitee.com/yanglbme/resource/raw/master/doocs-md/qrcode.png" />
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/prettify/r224/prettify.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>Please enable JavaScript to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
95
src/App.vue
95
src/App.vue
@ -1,43 +1,76 @@
|
||||
<template>
|
||||
<transition name="fade" v-if="loading">
|
||||
<loading />
|
||||
</transition>
|
||||
<codemirror-editor v-else />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Loading from "./components/Loading";
|
||||
import CodemirrorEditor from "./view/CodemirrorEditor";
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
Loading,
|
||||
CodemirrorEditor,
|
||||
onLaunch: function () {
|
||||
console.log("App Launch");
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
};
|
||||
onShow: function () {
|
||||
console.log("App Show");
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
}, 100);
|
||||
onHide: function () {
|
||||
console.log("App Hide");
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
<style lang="less">
|
||||
/* 每个页面公共css */
|
||||
@import url("./assets/less/style-mirror.css");
|
||||
@import url("./assets/less/theme.less");
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.fade-enter-to,
|
||||
.fade-leave {
|
||||
opacity: 1;
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
border-radius: 6px;
|
||||
background-color: rgba(200, 200, 200, 0.3);
|
||||
}
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: all 1s;
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 6px;
|
||||
background-color: rgba(144, 146, 152, 0.5);
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(144, 146, 152, 0.5);
|
||||
}
|
||||
/* CSS-hints */
|
||||
.CodeMirror-hints {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
list-style: none;
|
||||
|
||||
margin: 0;
|
||||
padding: 2px;
|
||||
|
||||
border-radius: 4px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.08);
|
||||
min-width: 200px;
|
||||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
|
||||
max-height: 20em;
|
||||
overflow-y: auto;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.CodeMirror-hint {
|
||||
margin: 0;
|
||||
margin-top: 10px;
|
||||
padding: 4px 6px;
|
||||
border-radius: 2px;
|
||||
white-space: pre;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
}
|
||||
.CodeMirror-hint:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
.CodeMirror-hint:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
</style>
|
||||
|
@ -41,14 +41,6 @@ body {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top {
|
||||
height: 60px;
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.web-title {
|
||||
margin: 0 15px 0 5px;
|
||||
}
|
||||
@ -75,7 +67,6 @@ section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 0;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.ctrl {
|
||||
@ -92,8 +83,8 @@ section {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
overflow: scroll;
|
||||
word-break: break-all;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.main-section {
|
||||
@ -107,6 +98,7 @@ section {
|
||||
}
|
||||
|
||||
.preview {
|
||||
position: relative;
|
||||
margin: 0 -20px;
|
||||
width: 375px;
|
||||
padding: 20px;
|
||||
@ -123,31 +115,6 @@ section {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/*
|
||||
.preview table tr:nth-child(even){
|
||||
background: rgb(250, 250, 250);
|
||||
}
|
||||
*/
|
||||
.select-item-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.select-item-right {
|
||||
float: right;
|
||||
color: #8492a6;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: 100% !important;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
font-size: 14px;
|
||||
padding: 20px;
|
||||
width: 100% !important;
|
||||
font-family: "PingFang SC", BlinkMacSystemFont, Roboto, "Helvetica Neue",
|
||||
sans-serif !important;
|
||||
}
|
||||
|
||||
/* ele ui */
|
||||
.el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
@ -157,33 +124,10 @@ section {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
border-radius: 6px;
|
||||
background-color: rgba(200, 200, 200, 0.3);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 6px;
|
||||
background-color: rgba(144, 146, 152, 0.5);
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(144, 146, 152, 0.5);
|
||||
}
|
||||
|
||||
.CodeMirror-vscrollbar:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll,
|
||||
.preview-wrapper {
|
||||
overflow: unset;
|
||||
overflow-y: scroll;
|
||||
uni-page-body,
|
||||
uni-page-refresh {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -9,13 +9,19 @@
|
||||
*/
|
||||
|
||||
.cm-s-style-mirror.CodeMirror {
|
||||
background: #f5f5f5;
|
||||
color: #444;
|
||||
font-size: 16px;
|
||||
padding: 20px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.cm-s-style-mirror .CodeMirror-scroll {
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden!important;
|
||||
overflow-y: scroll!important;
|
||||
}
|
||||
|
||||
.cm-s-style-mirror div.CodeMirror-selected {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
@ -129,3 +129,32 @@
|
||||
background-color: @nightCodeMirrorColor;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
padding-bottom: 0;
|
||||
height: 100% !important;
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
font-size: 14px;
|
||||
font-family: "PingFang SC", BlinkMacSystemFont, Roboto, "Helvetica Neue",
|
||||
sans-serif !important;
|
||||
}
|
||||
|
||||
.CodeMirror-vscrollbar:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
padding: 20px;
|
||||
padding-bottom: 0px;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
.CodeMirror-vscrollbar {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.codeMirror-wrapper {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
@ -111,10 +111,7 @@ class WxRenderer {
|
||||
}
|
||||
};
|
||||
renderer.paragraph = (text) => {
|
||||
if (
|
||||
text.indexOf("<figure") != -1 &&
|
||||
text.indexOf("<img") != -1
|
||||
) {
|
||||
if (text.indexOf("<figure") != -1 && text.indexOf("<img") != -1) {
|
||||
return text;
|
||||
}
|
||||
return text.replace(/ /g, "") === ""
|
||||
@ -123,13 +120,8 @@ class WxRenderer {
|
||||
};
|
||||
|
||||
renderer.blockquote = (text) => {
|
||||
text = text.replace(
|
||||
/<p.*?>/g,
|
||||
`<p ${getStyles("blockquote_p")}>`
|
||||
);
|
||||
return `<blockquote ${getStyles(
|
||||
"blockquote"
|
||||
)}>${text}</blockquote>`;
|
||||
text = text.replace(/<p.*?>/g, `<p ${getStyles("blockquote_p")}>`);
|
||||
return `<blockquote ${getStyles("blockquote")}>${text}</blockquote>`;
|
||||
};
|
||||
renderer.code = (text, lang) => {
|
||||
text = text.replace(/</g, "<").replace(/>/g, ">");
|
||||
@ -178,24 +170,20 @@ class WxRenderer {
|
||||
)}>${text}</figcaption>`;
|
||||
}
|
||||
let figureStyles = getStyles("figure");
|
||||
let imgStyles = getStyles(
|
||||
ENV_STRETCH_IMAGE ? "image" : "image_org"
|
||||
);
|
||||
let imgStyles = getStyles(ENV_STRETCH_IMAGE ? "image" : "image_org");
|
||||
return `<figure ${figureStyles}><img ${imgStyles} src="${href}" title="${title}" alt="${text}"/>${subText}</figure>`;
|
||||
};
|
||||
renderer.link = (href, title, text) => {
|
||||
if (href.startsWith("https://mp.weixin.qq.com")) {
|
||||
return `<a href="${href}" title="${
|
||||
title || text
|
||||
}" ${getStyles("wx_link")}>${text}</a>`;
|
||||
return `<a href="${href}" title="${title || text}" ${getStyles(
|
||||
"wx_link"
|
||||
)}>${text}</a>`;
|
||||
}
|
||||
if (href === text || !status) {
|
||||
return text;
|
||||
}
|
||||
let ref = addFootnote(title || text, href);
|
||||
return `<span ${getStyles(
|
||||
"link"
|
||||
)}>${text}<sup>[${ref}]</sup></span>`;
|
||||
return `<span ${getStyles("link")}>${text}<sup>[${ref}]</sup></span>`;
|
||||
};
|
||||
renderer.strong = (text) =>
|
||||
`<strong ${getStyles("strong")}>${text}</strong>`;
|
||||
|
@ -49,9 +49,7 @@ export function utf8to16(str) {
|
||||
char2 = str.charCodeAt(i++);
|
||||
char3 = str.charCodeAt(i++);
|
||||
out += String.fromCharCode(
|
||||
((c & 0x0f) << 12) |
|
||||
((char2 & 0x3f) << 6) |
|
||||
((char3 & 0x3f) << 0)
|
||||
((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0)
|
||||
);
|
||||
break;
|
||||
}
|
||||
@ -208,9 +206,7 @@ export function base64encode(str) {
|
||||
c2 = str.charCodeAt(i++);
|
||||
if (i == len) {
|
||||
out += base64EncodeChars.charAt(c1 >> 2);
|
||||
out += base64EncodeChars.charAt(
|
||||
((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4)
|
||||
);
|
||||
out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4));
|
||||
out += base64EncodeChars.charAt((c2 & 0xf) << 2);
|
||||
out += "=";
|
||||
break;
|
||||
|
@ -110,11 +110,7 @@ export function css2json(css) {
|
||||
// 初始化返回值
|
||||
let json = {};
|
||||
|
||||
while (
|
||||
css.length > 0 &&
|
||||
css.indexOf("{") !== -1 &&
|
||||
css.indexOf("}") !== -1
|
||||
) {
|
||||
while (css.length > 0 && css.indexOf("{") !== -1 && css.indexOf("}") !== -1) {
|
||||
// 存储第一个左/右花括号的下标
|
||||
const lbracket = css.indexOf("{");
|
||||
const rbracket = css.indexOf("}");
|
||||
|
@ -1,11 +1,8 @@
|
||||
<template>
|
||||
<el-container class="top is-dark">
|
||||
<div class="left-side">
|
||||
<!-- 图片上传 -->
|
||||
<el-tooltip
|
||||
:effect="effect"
|
||||
content="上传图片"
|
||||
placement="bottom-start"
|
||||
>
|
||||
<el-tooltip :effect="effect" content="上传图片" placement="bottom-start">
|
||||
<i
|
||||
class="el-icon-upload"
|
||||
size="medium"
|
||||
@ -51,8 +48,6 @@
|
||||
@click="$emit('show-dialog-form')"
|
||||
></i>
|
||||
</el-tooltip>
|
||||
<el-form size="mini" class="ctrl" :inline="true">
|
||||
<el-form-item>
|
||||
<el-select
|
||||
v-model="selectFont"
|
||||
size="mini"
|
||||
@ -71,8 +66,6 @@
|
||||
<span class="select-item-right">Abc</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select
|
||||
v-model="selectSize"
|
||||
size="mini"
|
||||
@ -90,8 +83,6 @@
|
||||
<span class="select-item-right">{{ size.desc }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select
|
||||
v-model="selectColor"
|
||||
size="mini"
|
||||
@ -109,7 +100,6 @@
|
||||
<span class="select-item-right">{{ color.desc }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-tooltip content="自定义颜色" :effect="effect" placement="top">
|
||||
<el-color-picker
|
||||
v-model="selectColor"
|
||||
@ -132,7 +122,8 @@
|
||||
>
|
||||
</el-switch>
|
||||
</el-tooltip>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="right-side">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
:effect="effect"
|
||||
@ -175,6 +166,7 @@
|
||||
></div>
|
||||
<div class="mode__switch" v-else @click="themeChanged"></div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<resetDialog
|
||||
:showResetConfirm="showResetConfirm"
|
||||
@confirm="confirmReset"
|
||||
@ -291,8 +283,7 @@ export default {
|
||||
// 输出提示
|
||||
this.$notify({
|
||||
showClose: true,
|
||||
message:
|
||||
"已复制渲染后的文章到剪贴板,可直接到公众号后台粘贴",
|
||||
message: "已复制渲染后的文章到剪贴板,可直接到公众号后台粘贴",
|
||||
offset: 80,
|
||||
duration: 1600,
|
||||
type: "success",
|
||||
@ -300,7 +291,6 @@ export default {
|
||||
this.$emit("refresh");
|
||||
this.$emit("endCopy");
|
||||
}, 350);
|
||||
e.target.blur();
|
||||
},
|
||||
// 自定义CSS样式
|
||||
async customStyle() {
|
||||
@ -375,6 +365,7 @@ export default {
|
||||
}
|
||||
.mode__switch {
|
||||
margin-left: 24px;
|
||||
margin-right: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: url("../../assets/images/night.png") no-repeat;
|
||||
@ -386,6 +377,37 @@ export default {
|
||||
background-size: cover;
|
||||
}
|
||||
.top {
|
||||
height: 60px;
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 0;
|
||||
}
|
||||
.el-select {
|
||||
margin-right: 12px;
|
||||
}
|
||||
.left-side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
.right-side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/*
|
||||
.preview table tr:nth-child(even){
|
||||
background: rgb(250, 250, 250);
|
||||
}
|
||||
*/
|
||||
.select-item-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.select-item-right {
|
||||
float: right;
|
||||
color: #8492a6;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
@ -47,9 +47,7 @@
|
||||
<el-button :type="btnType" plain @click="$emit('input', false)"
|
||||
>取 消</el-button
|
||||
>
|
||||
<el-button :type="btnType" @click="insertTable" plain
|
||||
>确 定</el-button
|
||||
>
|
||||
<el-button :type="btnType" @click="insertTable" plain>确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
@ -5,13 +5,9 @@
|
||||
:visible="showResetConfirm"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<div class="text">
|
||||
此操作将丢失本地缓存的文本和自定义样式,是否继续?
|
||||
</div>
|
||||
<div class="text">此操作将丢失本地缓存的文本和自定义样式,是否继续?</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button :type="btnType" plain @click="$emit('close')"
|
||||
>取 消</el-button
|
||||
>
|
||||
<el-button :type="btnType" plain @click="$emit('close')">取 消</el-button>
|
||||
<el-button :type="btnType" @click="$emit('confirm')" plain
|
||||
>确 定</el-button
|
||||
>
|
||||
|
@ -23,7 +23,7 @@
|
||||
</el-select>
|
||||
<el-upload
|
||||
drag
|
||||
action
|
||||
action=""
|
||||
:headers="{ 'Content-Type': 'multipart/form-data' }"
|
||||
:show-file-list="false"
|
||||
:multiple="true"
|
||||
@ -68,14 +68,11 @@
|
||||
type="primary"
|
||||
href="https://gitee.com/profile/personal_access_tokens"
|
||||
target="_blank"
|
||||
>请在
|
||||
Gitee「设置->安全设置->私人令牌」中生成</el-link
|
||||
>请在 Gitee「设置->安全设置->私人令牌」中生成</el-link
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="saveGiteeConfiguration"
|
||||
<el-button type="primary" @click="saveGiteeConfiguration"
|
||||
>保存配置</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
@ -114,9 +111,7 @@
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="saveGitHubConfiguration"
|
||||
<el-button type="primary" @click="saveGitHubConfiguration"
|
||||
>保存配置</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
@ -173,9 +168,7 @@
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="saveAliOSSConfiguration"
|
||||
<el-button type="primary" @click="saveAliOSSConfiguration"
|
||||
>保存配置</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
@ -232,9 +225,7 @@
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="saveTxCOSConfiguration"
|
||||
<el-button type="primary" @click="saveTxCOSConfiguration"
|
||||
>保存配置</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
@ -291,9 +282,7 @@
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="saveQiniuConfiguration"
|
||||
<el-button type="primary" @click="saveQiniuConfiguration"
|
||||
>保存配置</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
@ -401,23 +390,16 @@ export default {
|
||||
},
|
||||
saveGitHubConfiguration() {
|
||||
if (!(this.formGitHub.repo && this.formGitHub.accessToken)) {
|
||||
const blankElement = this.formGitHub.repo
|
||||
? "token"
|
||||
: "GitHub 仓库";
|
||||
const blankElement = this.formGitHub.repo ? "token" : "GitHub 仓库";
|
||||
this.$message.error(`参数「${blankElement}」不能为空`);
|
||||
return;
|
||||
}
|
||||
localStorage.setItem(
|
||||
"githubConfig",
|
||||
JSON.stringify(this.formGitHub)
|
||||
);
|
||||
localStorage.setItem("githubConfig", JSON.stringify(this.formGitHub));
|
||||
this.$message.success("保存成功");
|
||||
},
|
||||
saveGiteeConfiguration() {
|
||||
if (!(this.formGitee.repo && this.formGitee.accessToken)) {
|
||||
const blankElement = this.formGitee.repo
|
||||
? "私人令牌"
|
||||
: "Gitee 仓库";
|
||||
const blankElement = this.formGitee.repo ? "私人令牌" : "Gitee 仓库";
|
||||
this.$message.error(`参数「${blankElement}」不能为空`);
|
||||
return;
|
||||
}
|
||||
@ -436,10 +418,7 @@ export default {
|
||||
this.$message.error(`阿里云 OSS 参数配置不全`);
|
||||
return;
|
||||
}
|
||||
localStorage.setItem(
|
||||
"aliOSSConfig",
|
||||
JSON.stringify(this.formAliOSS)
|
||||
);
|
||||
localStorage.setItem("aliOSSConfig", JSON.stringify(this.formAliOSS));
|
||||
this.$message.success("保存成功");
|
||||
},
|
||||
|
||||
|
12
src/main.js
12
src/main.js
@ -1,5 +1,5 @@
|
||||
import Vue from "vue";
|
||||
import App from "./App.vue";
|
||||
import App from "./App";
|
||||
import store from "./store";
|
||||
import ElementUI from "element-ui";
|
||||
import "element-ui/lib/theme-chalk/index.css";
|
||||
@ -13,13 +13,15 @@ import "codemirror/addon/edit/matchbrackets";
|
||||
import "codemirror/addon/selection/active-line";
|
||||
import "codemirror/addon/hint/show-hint.js";
|
||||
import "codemirror/addon/hint/css-hint.js";
|
||||
import "./assets/less/theme.less";
|
||||
|
||||
Vue.use(ElementUI);
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
new Vue({
|
||||
App.mpType = "app";
|
||||
|
||||
const app = new Vue({
|
||||
store,
|
||||
render: (h) => h(App),
|
||||
}).$mount("#app");
|
||||
...App,
|
||||
});
|
||||
app.$mount();
|
||||
|
71
src/manifest.json
Normal file
71
src/manifest.json
Normal file
@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "uni-md",
|
||||
"appid": "",
|
||||
"description": "",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
"h5": {
|
||||
"publicPath": "/md/"
|
||||
},
|
||||
"app-plus": {
|
||||
"usingComponents": true,
|
||||
"splashscreen": {
|
||||
"alwaysShowBeforeRender": true,
|
||||
"waiting": true,
|
||||
"autoclose": true,
|
||||
"delay": 0
|
||||
},
|
||||
"modules": {},
|
||||
"distribute": {
|
||||
"android": {
|
||||
"permissions": [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
"ios": {},
|
||||
"sdkConfigs": {}
|
||||
}
|
||||
},
|
||||
"quickapp": {},
|
||||
"mp-weixin": {
|
||||
"appid": "",
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
},
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-alipay": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-baidu": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-toutiao": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-qq": {
|
||||
"usingComponents": true
|
||||
}
|
||||
}
|
17
src/pages.json
Normal file
17
src/pages.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "uni-app",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
}
|
||||
}
|
43
src/pages/index/index.vue
Normal file
43
src/pages/index/index.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<transition name="fade" v-if="loading">
|
||||
<loading />
|
||||
</transition>
|
||||
<codemirror-editor v-else />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Loading from "../../components/Loading";
|
||||
import CodemirrorEditor from "./view/CodemirrorEditor";
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
Loading,
|
||||
CodemirrorEditor,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
}, 100);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.fade-enter-to,
|
||||
.fade-leave {
|
||||
opacity: 1;
|
||||
}
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: all 1s;
|
||||
}
|
||||
</style>
|
486
src/pages/index/view/CodemirrorEditor.vue
Normal file
486
src/pages/index/view/CodemirrorEditor.vue
Normal file
@ -0,0 +1,486 @@
|
||||
<template>
|
||||
<div class="container" :class="{ container_night: nightMode }">
|
||||
<el-container>
|
||||
<el-header class="editor__header">
|
||||
<editor-header
|
||||
ref="header"
|
||||
@refresh="onEditorRefresh"
|
||||
@cssChanged="cssChanged"
|
||||
@download="downloadEditorContent"
|
||||
@showCssEditor="showCssEditor = !showCssEditor"
|
||||
@show-about-dialog="aboutDialogVisible = true"
|
||||
@show-dialog-form="dialogFormVisible = true"
|
||||
@show-dialog-upload-img="dialogUploadImgVisible = true"
|
||||
@startCopy="(isCoping = true), (backLight = true)"
|
||||
@endCopy="endCopy"
|
||||
/>
|
||||
</el-header>
|
||||
<el-main class="main-body">
|
||||
<el-row class="main-section">
|
||||
<el-col
|
||||
:span="12"
|
||||
class="codeMirror-wrapper"
|
||||
@contextmenu.prevent.native="openMenu($event)"
|
||||
>
|
||||
<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"
|
||||
ref="preview"
|
||||
:class="{
|
||||
'preview-wrapper_night': nightMode && isCoping,
|
||||
}"
|
||||
>
|
||||
<section
|
||||
id="output-wrapper"
|
||||
:class="{ output_night: nightMode && !backLight }"
|
||||
>
|
||||
<div class="preview">
|
||||
<section id="output" v-html="output"></section>
|
||||
<div class="loading-mask" v-if="nightMode && isCoping">
|
||||
<div class="loading__img"></div>
|
||||
<span>正在生成</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</el-col>
|
||||
<transition
|
||||
name="custom-classes-transition"
|
||||
enter-active-class="bounceInRight"
|
||||
>
|
||||
<el-col
|
||||
id="cssBox"
|
||||
v-show="showCssEditor"
|
||||
:span="12"
|
||||
class="cssEditor-wrapper"
|
||||
>
|
||||
<textarea
|
||||
id="cssEditor"
|
||||
type="textarea"
|
||||
placeholder="Your custom css here."
|
||||
>
|
||||
</textarea>
|
||||
</el-col>
|
||||
</transition>
|
||||
</el-row>
|
||||
</el-main>
|
||||
</el-container>
|
||||
<upload-img-dialog
|
||||
v-model="dialogUploadImgVisible"
|
||||
@close="dialogUploadImgVisible = false"
|
||||
@beforeUpload="beforeUpload"
|
||||
@uploadImage="uploadImage"
|
||||
@uploaded="uploaded"
|
||||
/>
|
||||
<about-dialog v-model="aboutDialogVisible" />
|
||||
<insert-form-dialog v-model="dialogFormVisible" />
|
||||
<right-click-menu
|
||||
v-model="rightClickMenuVisible"
|
||||
:left="mouseLeft"
|
||||
:top="mouseTop"
|
||||
@menuTick="onMenuEvent"
|
||||
@closeMenu="closeRightClickMenu"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import editorHeader from "../../../components/CodemirrorEditor/header";
|
||||
import aboutDialog from "../../../components/CodemirrorEditor/aboutDialog";
|
||||
import insertFormDialog from "../../../components/CodemirrorEditor/insertForm";
|
||||
import rightClickMenu from "../../../components/CodemirrorEditor/rightClickMenu";
|
||||
import uploadImgDialog from "../../../components/CodemirrorEditor/uploadImgDialog";
|
||||
|
||||
import {
|
||||
css2json,
|
||||
downloadMD,
|
||||
formatDoc,
|
||||
setFontSize,
|
||||
saveEditorContent,
|
||||
customCssWithTemplate,
|
||||
checkImage,
|
||||
} from "../../../assets/scripts/util";
|
||||
|
||||
import { toBase64 } from "../../../assets/scripts/util";
|
||||
import fileApi from "../../../api/file";
|
||||
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
showCssEditor: false,
|
||||
aboutDialogVisible: false,
|
||||
dialogUploadImgVisible: false,
|
||||
dialogFormVisible: false,
|
||||
isCoping: false,
|
||||
isImgLoading: false,
|
||||
backLight: false,
|
||||
timeout: null,
|
||||
changeTimer: null,
|
||||
source: "",
|
||||
mouseLeft: 0,
|
||||
mouseTop: 0,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
editorHeader,
|
||||
aboutDialog,
|
||||
insertFormDialog,
|
||||
rightClickMenu,
|
||||
uploadImgDialog,
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
wxRenderer: (state) => state.wxRenderer,
|
||||
output: (state) => state.output,
|
||||
editor: (state) => state.editor,
|
||||
cssEditor: (state) => state.cssEditor,
|
||||
currentSize: (state) => state.currentSize,
|
||||
currentColor: (state) => state.currentColor,
|
||||
nightMode: (state) => state.nightMode,
|
||||
rightClickMenuVisible: (state) => state.rightClickMenuVisible,
|
||||
}),
|
||||
},
|
||||
created() {
|
||||
this.initEditorState();
|
||||
this.$nextTick(() => {
|
||||
this.initEditor();
|
||||
this.initCssEditor();
|
||||
this.onEditorRefresh();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initEditor() {
|
||||
this.initEditorEntity();
|
||||
this.editor.on("change", (cm, e) => {
|
||||
if (this.changeTimer) clearTimeout(this.changeTimer);
|
||||
this.changeTimer = setTimeout(() => {
|
||||
this.onEditorRefresh();
|
||||
saveEditorContent(this.editor, "__editor_content");
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// 粘贴上传图片并插入
|
||||
this.editor.on("paste", (cm, e) => {
|
||||
if (!(e.clipboardData && e.clipboardData.items) || this.isImgLoading) {
|
||||
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();
|
||||
const isValid = this.beforeUpload(pasteFile);
|
||||
if (!isValid) {
|
||||
continue;
|
||||
}
|
||||
this.uploadImage(pasteFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.on("mousedown", () => {
|
||||
this.$store.commit("setRightClickMenuVisible", false);
|
||||
});
|
||||
this.editor.on("blur", () => {
|
||||
//!影响到右键菜单的点击事件,右键菜单的点击事件在组件内通过mousedown触发
|
||||
this.$store.commit("setRightClickMenuVisible", false);
|
||||
});
|
||||
this.editor.on("scroll", () => {
|
||||
this.$store.commit("setRightClickMenuVisible", false);
|
||||
});
|
||||
},
|
||||
initCssEditor() {
|
||||
this.initCssEditorEntity();
|
||||
// 自动提示
|
||||
this.cssEditor.on("keyup", (cm, e) => {
|
||||
if ((e.keyCode >= 65 && e.keyCode <= 90) || e.keyCode === 189) {
|
||||
cm.showHint(e);
|
||||
}
|
||||
});
|
||||
this.cssEditor.on("update", (instance) => {
|
||||
this.cssChanged();
|
||||
saveEditorContent(this.cssEditor, "__css_content");
|
||||
});
|
||||
},
|
||||
cssChanged() {
|
||||
let json = css2json(this.cssEditor.getValue(0));
|
||||
let theme = setFontSize(this.currentSize.replace("px", ""));
|
||||
|
||||
theme = customCssWithTemplate(json, this.currentColor, theme);
|
||||
this.setWxRendererOptions({
|
||||
theme: theme,
|
||||
});
|
||||
this.onEditorRefresh();
|
||||
},
|
||||
beforeUpload(file) {
|
||||
// validate image
|
||||
const checkResult = checkImage(file);
|
||||
if (!checkResult.ok) {
|
||||
this.$message.error(checkResult.msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// check image host
|
||||
let imgHost = localStorage.getItem("imgHost");
|
||||
imgHost = imgHost ? imgHost : "default";
|
||||
localStorage.setItem("imgHost", imgHost);
|
||||
|
||||
const config = localStorage.getItem(`${imgHost}Config`);
|
||||
const isValidHost = imgHost == "default" || config;
|
||||
if (!isValidHost) {
|
||||
this.$message.error(`请先配置 ${imgHost} 图床参数`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
uploadImage(file) {
|
||||
this.isImgLoading = true;
|
||||
toBase64(file)
|
||||
.then((base64Content) => {
|
||||
fileApi
|
||||
.fileUpload(base64Content, file)
|
||||
.then((url) => {
|
||||
this.uploaded(url);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.$message.error(err.message);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
this.$message.error(err.message);
|
||||
});
|
||||
this.isImgLoading = false;
|
||||
},
|
||||
// 图片上传结束
|
||||
uploaded(response) {
|
||||
if (!response) {
|
||||
this.$message.error("上传图片未知异常");
|
||||
return;
|
||||
}
|
||||
this.dialogUploadImgVisible = false;
|
||||
// 上传成功,获取光标
|
||||
const cursor = this.editor.getCursor();
|
||||
const imageUrl = response;
|
||||
const markdownImage = `![](${imageUrl})`;
|
||||
// 将 Markdown 形式的 URL 插入编辑框光标所在位置
|
||||
this.editor.replaceSelection(`\n${markdownImage}\n`, cursor);
|
||||
this.$message.success("图片上传成功");
|
||||
this.onEditorRefresh();
|
||||
},
|
||||
// 左右滚动
|
||||
leftAndRightScroll() {
|
||||
const scrollCB = (text) => {
|
||||
let source, target;
|
||||
|
||||
clearTimeout(this.timeout);
|
||||
if (text === "preview") {
|
||||
source = this.$refs.preview.$el;
|
||||
target = document.getElementsByClassName("CodeMirror-scroll")[0];
|
||||
this.editor.off("scroll", editorScrollCB);
|
||||
this.timeout = setTimeout(() => {
|
||||
this.editor.on("scroll", editorScrollCB);
|
||||
}, 300);
|
||||
} else if (text === "editor") {
|
||||
source = document.getElementsByClassName("CodeMirror-scroll")[0];
|
||||
target = this.$refs.preview.$el;
|
||||
target.removeEventListener("scroll", previewScrollCB, false);
|
||||
this.timeout = setTimeout(() => {
|
||||
target.addEventListener("scroll", previewScrollCB, false);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
let percentage =
|
||||
source.scrollTop / (source.scrollHeight - source.offsetHeight);
|
||||
let height = percentage * (target.scrollHeight - target.offsetHeight);
|
||||
|
||||
target.scrollTo(0, height);
|
||||
};
|
||||
const editorScrollCB = () => {
|
||||
scrollCB("editor");
|
||||
};
|
||||
const previewScrollCB = () => {
|
||||
scrollCB("preview");
|
||||
};
|
||||
|
||||
this.$refs.preview.$el.addEventListener("scroll", previewScrollCB, false);
|
||||
this.editor.on("scroll", editorScrollCB);
|
||||
},
|
||||
// 更新编辑器
|
||||
onEditorRefresh() {
|
||||
this.editorRefresh();
|
||||
setTimeout(() => PR.prettyPrint(), 0);
|
||||
},
|
||||
// 复制结束
|
||||
endCopy() {
|
||||
this.backLight = false;
|
||||
setTimeout(() => {
|
||||
this.isCoping = false;
|
||||
}, 800);
|
||||
},
|
||||
// 下载编辑器内容到本地
|
||||
downloadEditorContent() {
|
||||
downloadMD(this.editor.getValue(0));
|
||||
},
|
||||
// 格式化文档
|
||||
formatContent() {
|
||||
const doc = formatDoc(this.editor.getValue(0));
|
||||
localStorage.setItem("__editor_content", doc);
|
||||
this.editor.setValue(doc);
|
||||
},
|
||||
// 右键菜单
|
||||
openMenu(e) {
|
||||
const menuMinWidth = 105;
|
||||
const offsetLeft = this.$el.getBoundingClientRect().left;
|
||||
const offsetWidth = this.$el.offsetWidth;
|
||||
const maxLeft = offsetWidth - menuMinWidth;
|
||||
const left = e.clientX - offsetLeft;
|
||||
this.mouseLeft = Math.min(maxLeft, left);
|
||||
this.mouseTop = e.clientY + 10;
|
||||
this.$store.commit("setRightClickMenuVisible", true);
|
||||
},
|
||||
closeRightClickMenu() {
|
||||
this.$store.commit("setRightClickMenuVisible", false);
|
||||
},
|
||||
onMenuEvent(type, info = {}) {
|
||||
switch (type) {
|
||||
case "pageReset":
|
||||
this.$refs.header.showResetConfirm = true;
|
||||
break;
|
||||
case "insertPic":
|
||||
this.dialogUploadImgVisible = true;
|
||||
break;
|
||||
case "download":
|
||||
this.downloadEditorContent();
|
||||
break;
|
||||
case "insertTable":
|
||||
this.dialogFormVisible = true;
|
||||
break;
|
||||
case "formatMarkdown":
|
||||
this.formatContent();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
...mapMutations([
|
||||
"initEditorState",
|
||||
"initEditorEntity",
|
||||
"setWxRendererOptions",
|
||||
"editorRefresh",
|
||||
"initCssEditorEntity",
|
||||
]),
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.leftAndRightScroll();
|
||||
PR.prettyPrint();
|
||||
}, 300);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.main-body {
|
||||
padding-top: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.el-main {
|
||||
transition: all 0.3s;
|
||||
padding: 0;
|
||||
margin: 20px;
|
||||
margin-top: 0;
|
||||
}
|
||||
.container {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.textarea-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
.preview-wrapper_night {
|
||||
overflow-y: inherit;
|
||||
position: relative;
|
||||
left: -3px;
|
||||
.preview {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
#output-wrapper {
|
||||
position: relative;
|
||||
user-select: text;
|
||||
}
|
||||
.loading-mask {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 376px;
|
||||
height: 101%;
|
||||
padding-top: 1px;
|
||||
font-size: 15px;
|
||||
color: gray;
|
||||
background-color: #1e1e1e;
|
||||
.loading__img {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 330px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
transform: translate(-50%, -50%);
|
||||
background: url("../../../assets/images/favicon.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
span {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 390px;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
.bounceInRight {
|
||||
animation-name: bounceInRight;
|
||||
animation-duration: 1s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
/deep/ .preview-table {
|
||||
border-spacing: 0px;
|
||||
}
|
||||
|
||||
@keyframes bounceInRight {
|
||||
0%,
|
||||
60%,
|
||||
75%,
|
||||
90%,
|
||||
100% {
|
||||
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate3d(3000px, 0, 0);
|
||||
}
|
||||
60% {
|
||||
opacity: 1;
|
||||
transform: translate3d(-25px, 0, 0);
|
||||
}
|
||||
75% {
|
||||
transform: translate3d(10px, 0, 0);
|
||||
}
|
||||
90% {
|
||||
transform: translate3d(-5px, 0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
@import url("../../../assets/less/app.less");
|
||||
@import url("../../../assets/less/github-v2.min.css");
|
||||
</style>
|
@ -69,8 +69,7 @@ const mutations = {
|
||||
state.currentSize =
|
||||
localStorage.getItem("size") || config.sizeOption[2].value;
|
||||
state.codeTheme =
|
||||
localStorage.getItem("codeTheme") ||
|
||||
config.codeThemeOption[0].value;
|
||||
localStorage.getItem("codeTheme") || config.codeThemeOption[0].value;
|
||||
state.citeStatus = localStorage.getItem("citeStatus") === "true";
|
||||
state.nightMode = localStorage.getItem("nightMode") === "true";
|
||||
state.wxRenderer = new WxRenderer({
|
||||
@ -81,10 +80,13 @@ const mutations = {
|
||||
});
|
||||
},
|
||||
initEditorEntity(state) {
|
||||
state.editor = CodeMirror.fromTextArea(
|
||||
document.getElementById("editor"),
|
||||
{
|
||||
value: "",
|
||||
const editorDom = document.getElementById("editor");
|
||||
|
||||
if (!editorDom.value) {
|
||||
editorDom.value =
|
||||
localStorage.getItem("__editor_content") || formatDoc(DEFAULT_CONTENT);
|
||||
}
|
||||
state.editor = CodeMirror.fromTextArea(editorDom, {
|
||||
mode: "text/x-markdown",
|
||||
theme: "xq-light",
|
||||
lineNumbers: false,
|
||||
@ -99,20 +101,16 @@ const mutations = {
|
||||
},
|
||||
"Ctrl-S": function save(editor) {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// 如果有编辑器内容被保存则读取,否则加载默认内容
|
||||
state.editor.setValue(
|
||||
localStorage.getItem("__editor_content") ||
|
||||
formatDoc(DEFAULT_CONTENT)
|
||||
);
|
||||
});
|
||||
},
|
||||
initCssEditorEntity(state) {
|
||||
state.cssEditor = CodeMirror.fromTextArea(
|
||||
document.getElementById("cssEditor"),
|
||||
{
|
||||
value: "",
|
||||
const cssEditorDom = document.getElementById("cssEditor");
|
||||
|
||||
if (!cssEditorDom.value) {
|
||||
cssEditorDom.value =
|
||||
localStorage.getItem("__css_content") || DEFAULT_CSS_CONTENT;
|
||||
}
|
||||
state.cssEditor = CodeMirror.fromTextArea(cssEditorDom, {
|
||||
mode: "css",
|
||||
theme: "style-mirror",
|
||||
lineNumbers: false,
|
||||
@ -127,13 +125,7 @@ const mutations = {
|
||||
},
|
||||
"Ctrl-S": function save(editor) {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// 如果有编辑器内容被保存则读取,否则加载默认内容
|
||||
state.cssEditor.setValue(
|
||||
localStorage.getItem("__css_content") || DEFAULT_CSS_CONTENT
|
||||
);
|
||||
});
|
||||
},
|
||||
editorRefresh(state) {
|
||||
let output = marked(state.editor.getValue(0), {
|
||||
@ -152,6 +144,7 @@ const mutations = {
|
||||
},
|
||||
clearEditorToDefault(state) {
|
||||
const doc = formatDoc(DEFAULT_CONTENT);
|
||||
|
||||
state.editor.setValue(doc);
|
||||
state.cssEditor.setValue(DEFAULT_CSS_CONTENT);
|
||||
},
|
||||
|
@ -1,505 +0,0 @@
|
||||
<template>
|
||||
<div class="container" :class="{ container_night: nightMode }">
|
||||
<el-container>
|
||||
<el-header class="editor__header">
|
||||
<editor-header
|
||||
ref="header"
|
||||
@refresh="onEditorRefresh"
|
||||
@cssChanged="cssChanged"
|
||||
@download="downloadEditorContent"
|
||||
@showCssEditor="showCssEditor = !showCssEditor"
|
||||
@show-about-dialog="aboutDialogVisible = true"
|
||||
@show-dialog-form="dialogFormVisible = true"
|
||||
@show-dialog-upload-img="dialogUploadImgVisible = true"
|
||||
@startCopy="(isCoping = true), (backLight = true)"
|
||||
@endCopy="endCopy"
|
||||
/>
|
||||
</el-header>
|
||||
<el-main class="main-body">
|
||||
<el-row class="main-section">
|
||||
<el-col
|
||||
:span="12"
|
||||
@contextmenu.prevent.native="openMenu($event)"
|
||||
>
|
||||
<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"
|
||||
ref="preview"
|
||||
:class="{
|
||||
'preview-wrapper_night': nightMode && isCoping,
|
||||
}"
|
||||
>
|
||||
<section
|
||||
id="output-wrapper"
|
||||
:class="{ output_night: nightMode && !backLight }"
|
||||
>
|
||||
<div class="preview">
|
||||
<section id="output" v-html="output"></section>
|
||||
<div
|
||||
class="loading-mask"
|
||||
v-if="nightMode && isCoping"
|
||||
>
|
||||
<div class="loading__img"></div>
|
||||
<span>正在生成</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</el-col>
|
||||
<transition
|
||||
name="custom-classes-transition"
|
||||
enter-active-class="bounceInRight"
|
||||
>
|
||||
<el-col id="cssBox" :span="12" v-show="showCssEditor">
|
||||
<textarea
|
||||
id="cssEditor"
|
||||
type="textarea"
|
||||
placeholder="Your custom css here."
|
||||
>
|
||||
</textarea>
|
||||
</el-col>
|
||||
</transition>
|
||||
</el-row>
|
||||
</el-main>
|
||||
</el-container>
|
||||
<upload-img-dialog
|
||||
v-model="dialogUploadImgVisible"
|
||||
@close="dialogUploadImgVisible = false"
|
||||
@beforeUpload="beforeUpload"
|
||||
@uploadImage="uploadImage"
|
||||
@uploaded="uploaded"
|
||||
/>
|
||||
<about-dialog v-model="aboutDialogVisible" />
|
||||
<insert-form-dialog v-model="dialogFormVisible" />
|
||||
<right-click-menu
|
||||
v-model="rightClickMenuVisible"
|
||||
:left="mouseLeft"
|
||||
:top="mouseTop"
|
||||
@menuTick="onMenuEvent"
|
||||
@closeMenu="closeRightClickMenu"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import editorHeader from "../components/CodemirrorEditor/header";
|
||||
import aboutDialog from "../components/CodemirrorEditor/aboutDialog";
|
||||
import insertFormDialog from "../components/CodemirrorEditor/insertForm";
|
||||
import rightClickMenu from "../components/CodemirrorEditor/rightClickMenu";
|
||||
import uploadImgDialog from "../components/CodemirrorEditor/uploadImgDialog";
|
||||
|
||||
import {
|
||||
css2json,
|
||||
downloadMD,
|
||||
formatDoc,
|
||||
setFontSize,
|
||||
saveEditorContent,
|
||||
customCssWithTemplate,
|
||||
checkImage,
|
||||
} from "../assets/scripts/util";
|
||||
|
||||
import { toBase64 } from "../assets/scripts/util";
|
||||
import fileApi from "../api/file";
|
||||
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
showCssEditor: false,
|
||||
aboutDialogVisible: false,
|
||||
dialogUploadImgVisible: false,
|
||||
dialogFormVisible: false,
|
||||
isCoping: false,
|
||||
isImgLoading: false,
|
||||
backLight: false,
|
||||
timeout: null,
|
||||
changeTimer: null,
|
||||
source: "",
|
||||
mouseLeft: 0,
|
||||
mouseTop: 0,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
editorHeader,
|
||||
aboutDialog,
|
||||
insertFormDialog,
|
||||
rightClickMenu,
|
||||
uploadImgDialog,
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
wxRenderer: (state) => state.wxRenderer,
|
||||
output: (state) => state.output,
|
||||
editor: (state) => state.editor,
|
||||
cssEditor: (state) => state.cssEditor,
|
||||
currentSize: (state) => state.currentSize,
|
||||
currentColor: (state) => state.currentColor,
|
||||
nightMode: (state) => state.nightMode,
|
||||
rightClickMenuVisible: (state) => state.rightClickMenuVisible,
|
||||
}),
|
||||
},
|
||||
created() {
|
||||
this.initEditorState();
|
||||
this.$nextTick(() => {
|
||||
this.initEditor();
|
||||
this.initCssEditor();
|
||||
this.onEditorRefresh();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initEditor() {
|
||||
this.initEditorEntity();
|
||||
this.editor.on("change", (cm, e) => {
|
||||
if (this.changeTimer) clearTimeout(this.changeTimer);
|
||||
this.changeTimer = setTimeout(() => {
|
||||
this.onEditorRefresh();
|
||||
saveEditorContent(this.editor, "__editor_content");
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// 粘贴上传图片并插入
|
||||
this.editor.on("paste", (cm, e) => {
|
||||
if (
|
||||
!(e.clipboardData && e.clipboardData.items) ||
|
||||
this.isImgLoading
|
||||
) {
|
||||
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();
|
||||
const isValid = this.beforeUpload(pasteFile);
|
||||
if (!isValid) {
|
||||
continue;
|
||||
}
|
||||
this.uploadImage(pasteFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.on("mousedown", () => {
|
||||
this.$store.commit("setRightClickMenuVisible", false);
|
||||
});
|
||||
this.editor.on("blur", () => {
|
||||
//!影响到右键菜单的点击事件,右键菜单的点击事件在组件内通过mousedown触发
|
||||
this.$store.commit("setRightClickMenuVisible", false);
|
||||
});
|
||||
this.editor.on("scroll", () => {
|
||||
this.$store.commit("setRightClickMenuVisible", false);
|
||||
});
|
||||
},
|
||||
initCssEditor() {
|
||||
this.initCssEditorEntity();
|
||||
// 自动提示
|
||||
this.cssEditor.on("keyup", (cm, e) => {
|
||||
if ((e.keyCode >= 65 && e.keyCode <= 90) || e.keyCode === 189) {
|
||||
cm.showHint(e);
|
||||
}
|
||||
});
|
||||
this.cssEditor.on("update", (instance) => {
|
||||
this.cssChanged();
|
||||
saveEditorContent(this.cssEditor, "__css_content");
|
||||
});
|
||||
},
|
||||
cssChanged() {
|
||||
let json = css2json(this.cssEditor.getValue(0));
|
||||
let theme = setFontSize(this.currentSize.replace("px", ""));
|
||||
|
||||
theme = customCssWithTemplate(json, this.currentColor, theme);
|
||||
this.setWxRendererOptions({
|
||||
theme: theme,
|
||||
});
|
||||
this.onEditorRefresh();
|
||||
},
|
||||
beforeUpload(file) {
|
||||
// validate image
|
||||
const checkResult = checkImage(file);
|
||||
if (!checkResult.ok) {
|
||||
this.$message.error(checkResult.msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// check image host
|
||||
let imgHost = localStorage.getItem("imgHost");
|
||||
imgHost = imgHost ? imgHost : "default";
|
||||
localStorage.setItem("imgHost", imgHost);
|
||||
|
||||
const config = localStorage.getItem(`${imgHost}Config`);
|
||||
const isValidHost = imgHost == "default" || config;
|
||||
if (!isValidHost) {
|
||||
this.$message.error(`请先配置 ${imgHost} 图床参数`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
uploadImage(file) {
|
||||
this.isImgLoading = true;
|
||||
toBase64(file)
|
||||
.then((base64Content) => {
|
||||
fileApi
|
||||
.fileUpload(base64Content, file)
|
||||
.then((url) => {
|
||||
this.uploaded(url);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.$message.error(err.message);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
this.$message.error(err.message);
|
||||
});
|
||||
this.isImgLoading = false;
|
||||
},
|
||||
// 图片上传结束
|
||||
uploaded(response) {
|
||||
if (!response) {
|
||||
this.$message.error("上传图片未知异常");
|
||||
return;
|
||||
}
|
||||
this.dialogUploadImgVisible = false;
|
||||
// 上传成功,获取光标
|
||||
const cursor = this.editor.getCursor();
|
||||
const imageUrl = response;
|
||||
const markdownImage = `![](${imageUrl})`;
|
||||
// 将 Markdown 形式的 URL 插入编辑框光标所在位置
|
||||
this.editor.replaceSelection(`\n${markdownImage}\n`, cursor);
|
||||
this.$message.success("图片上传成功");
|
||||
this.onEditorRefresh();
|
||||
},
|
||||
// 左右滚动
|
||||
leftAndRightScroll() {
|
||||
const scrollCB = (text) => {
|
||||
let source, target;
|
||||
|
||||
clearTimeout(this.timeout);
|
||||
if (text === "preview") {
|
||||
source = this.$refs.preview.$el;
|
||||
target = document.getElementsByClassName(
|
||||
"CodeMirror-scroll"
|
||||
)[0];
|
||||
this.editor.off("scroll", editorScrollCB);
|
||||
this.timeout = setTimeout(() => {
|
||||
this.editor.on("scroll", editorScrollCB);
|
||||
}, 300);
|
||||
} else if (text === "editor") {
|
||||
source = document.getElementsByClassName(
|
||||
"CodeMirror-scroll"
|
||||
)[0];
|
||||
target = this.$refs.preview.$el;
|
||||
target.removeEventListener(
|
||||
"scroll",
|
||||
previewScrollCB,
|
||||
false
|
||||
);
|
||||
this.timeout = setTimeout(() => {
|
||||
target.addEventListener(
|
||||
"scroll",
|
||||
previewScrollCB,
|
||||
false
|
||||
);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
let percentage =
|
||||
source.scrollTop /
|
||||
(source.scrollHeight - source.offsetHeight);
|
||||
let height =
|
||||
percentage * (target.scrollHeight - target.offsetHeight);
|
||||
|
||||
target.scrollTo(0, height);
|
||||
};
|
||||
const editorScrollCB = () => {
|
||||
scrollCB("editor");
|
||||
};
|
||||
const previewScrollCB = () => {
|
||||
scrollCB("preview");
|
||||
};
|
||||
|
||||
this.$refs.preview.$el.addEventListener(
|
||||
"scroll",
|
||||
previewScrollCB,
|
||||
false
|
||||
);
|
||||
this.editor.on("scroll", editorScrollCB);
|
||||
},
|
||||
// 更新编辑器
|
||||
onEditorRefresh() {
|
||||
this.editorRefresh();
|
||||
setTimeout(() => PR.prettyPrint(), 0);
|
||||
},
|
||||
// 复制结束
|
||||
endCopy() {
|
||||
this.backLight = false;
|
||||
setTimeout(() => {
|
||||
this.isCoping = false;
|
||||
}, 800);
|
||||
},
|
||||
// 下载编辑器内容到本地
|
||||
downloadEditorContent() {
|
||||
downloadMD(this.editor.getValue(0));
|
||||
},
|
||||
// 格式化文档
|
||||
formatContent() {
|
||||
const doc = formatDoc(this.editor.getValue(0));
|
||||
localStorage.setItem("__editor_content", doc);
|
||||
this.editor.setValue(doc);
|
||||
},
|
||||
// 右键菜单
|
||||
openMenu(e) {
|
||||
const menuMinWidth = 105;
|
||||
const offsetLeft = this.$el.getBoundingClientRect().left;
|
||||
const offsetWidth = this.$el.offsetWidth;
|
||||
const maxLeft = offsetWidth - menuMinWidth;
|
||||
const left = e.clientX - offsetLeft;
|
||||
this.mouseLeft = Math.min(maxLeft, left);
|
||||
this.mouseTop = e.clientY + 10;
|
||||
this.$store.commit("setRightClickMenuVisible", true);
|
||||
},
|
||||
closeRightClickMenu() {
|
||||
this.$store.commit("setRightClickMenuVisible", false);
|
||||
},
|
||||
onMenuEvent(type, info = {}) {
|
||||
switch (type) {
|
||||
case "pageReset":
|
||||
this.$refs.header.showResetConfirm = true;
|
||||
break;
|
||||
case "insertPic":
|
||||
this.dialogUploadImgVisible = true;
|
||||
break;
|
||||
case "download":
|
||||
this.downloadEditorContent();
|
||||
break;
|
||||
case "insertTable":
|
||||
this.dialogFormVisible = true;
|
||||
break;
|
||||
case "formatMarkdown":
|
||||
this.formatContent();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
...mapMutations([
|
||||
"initEditorState",
|
||||
"initEditorEntity",
|
||||
"setWxRendererOptions",
|
||||
"editorRefresh",
|
||||
"initCssEditorEntity",
|
||||
]),
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.leftAndRightScroll();
|
||||
PR.prettyPrint();
|
||||
}, 300);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.main-body {
|
||||
padding-top: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.el-main {
|
||||
transition: all 0.3s;
|
||||
padding: 0;
|
||||
margin: 20px;
|
||||
margin-top: 0;
|
||||
}
|
||||
.container {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.preview {
|
||||
transition: background 0s;
|
||||
transition-delay: 0.2s;
|
||||
}
|
||||
.preview-wrapper_night {
|
||||
overflow-y: inherit;
|
||||
position: relative;
|
||||
left: -3px;
|
||||
.preview {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
#output-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.loading-mask {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 376px;
|
||||
height: 101%;
|
||||
padding-top: 1px;
|
||||
font-size: 15px;
|
||||
color: gray;
|
||||
background-color: #1e1e1e;
|
||||
.loading__img {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 330px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
transform: translate(-50%, -50%);
|
||||
background: url("../assets/images/favicon.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
span {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 390px;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
.bounceInRight {
|
||||
animation-name: bounceInRight;
|
||||
animation-duration: 1s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
@keyframes bounceInRight {
|
||||
0%,
|
||||
60%,
|
||||
75%,
|
||||
90%,
|
||||
100% {
|
||||
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate3d(3000px, 0, 0);
|
||||
}
|
||||
60% {
|
||||
opacity: 1;
|
||||
transform: translate3d(-25px, 0, 0);
|
||||
}
|
||||
75% {
|
||||
transform: translate3d(10px, 0, 0);
|
||||
}
|
||||
90% {
|
||||
transform: translate3d(-5px, 0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
@import url("../assets/less/app.less");
|
||||
@import url("../assets/less/style-mirror.css");
|
||||
@import url("../assets/less/github-v2.min.css");
|
||||
</style>
|
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"@dcloudio/types",
|
||||
"miniprogram-api-typings",
|
||||
"mini-types"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,4 +1,20 @@
|
||||
module.exports = {
|
||||
outputDir: "dist",
|
||||
publicPath: process.env.NETLIFY ? "/" : "/md/",
|
||||
};
|
||||
const fs = require("fs");
|
||||
function writeManifestJson() {
|
||||
fs.readFile("./src/manifest.json", function (err, data) {
|
||||
if (err) {
|
||||
return console.error(err);
|
||||
}
|
||||
const strData = data.toString();
|
||||
const manifest = JSON.parse(strData);
|
||||
|
||||
manifest.h5.publicPath = process.env.NETLIFY ? "/" : "/md/";
|
||||
const result = JSON.stringify(manifest, null, 4);
|
||||
|
||||
fs.writeFile("./src/manifest.json", result, function (err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
writeManifestJson();
|
||||
|
Loading…
Reference in New Issue
Block a user