feat: support custom code block theme (#112)

close #75
This commit is contained in:
miaogaolin 2021-12-03 14:35:25 +08:00 committed by GitHub
parent 10d5ce856b
commit fe4d6b0324
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 107 additions and 255 deletions

5
.gitignore vendored
View File

@ -43,5 +43,8 @@ yarn-error.log*
# mockm
httpData
package-lock.json
public/upload/**
!public/upload/*.gitkeep
!public/upload/*.gitkeep

View File

@ -21,6 +21,7 @@
"crypto-js": "^4.1.1",
"element-ui": "^2.15.6",
"form-data": "4.0.0",
"highlight.js": "^11.3.1",
"jquery": "^3.6.0",
"juice": "^8.0.0",
"marked": "^4.0.5",
@ -39,6 +40,7 @@
"@vue/cli-service": "~4.5.15",
"async-validator": "^4.0.7",
"babel-plugin-import": "^1.13.3",
"cache-loader": "^4.1.0",
"cross-env": "^7.0.3",
"jest": "^27.4.0",
"less": "^4.1.2",

View File

@ -20,7 +20,7 @@ body,
/* 每个页面公共css */
@import url("./assets/less/style-mirror.css");
@import url("./assets/less/theme.less");
@import url("./assets/less/code-theme.less");
::-webkit-scrollbar {
width: 6px;
height: 6px;

View File

@ -1,2 +0,0 @@
@import url("./codeTheme/wechat-code-block.less");
@import url("./codeTheme/github-code-block.less");

View File

@ -1,49 +0,0 @@
@import url("../github-v2.min.css");
/*github code block*/
.code-snippet__github {
display: flex;
font-size: 12px;
margin: 10px 8px;
position: relative;
height: auto;
background-color: #f7f7f7;
border-radius: 8px;
.code-snippet__line-index {
display: none;
}
.code__pre {
display: grid;
position: relative;
counter-reset: line;
overflow-x: auto;
padding: 1em;
white-space: normal;
flex: 1;
line-height: 20px;
font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;
-webkit-overflow-scrolling: touch;
}
pre {
display: inline-block;
font-size: 12px;
}
code {
display: flex;
position: relative;
padding-right: 8px;
text-align: left;
white-space: pre;
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
&::before {
display: none;
}
}
ul li {
list-style: none;
}
}

View File

@ -1,62 +0,0 @@
/*wechat code block*/
.rich_media_content .code-snippet *,
.rich_media_content .code-snippet__wechat * {
max-width: 1000% !important;
}
.code-snippet__wechat {
word-wrap: break-word !important;
font-size: 14px;
margin: 10px 8px;
color: #333;
position: relative;
background-color: rgba(27, 31, 35, 0.05);
border: 1px solid #f0f0f0;
border-radius: 2px;
display: flex;
line-height: 24px;
}
.code-snippet__wechat .code-snippet__line-index {
counter-reset: line;
flex-shrink: 0;
height: 100%;
padding: 1em;
list-style-type: none;
}
.code-snippet__wechat .code-snippet__line-index li {
list-style-type: none;
text-align: right;
}
.code-snippet__wechat .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__wechat pre {
overflow-x: auto;
padding: 1em 1em 1em 1em;
white-space: normal;
flex: 1;
-webkit-overflow-scrolling: touch;
}
.code-snippet__wechat code {
text-align: left;
font-size: 14px;
white-space: pre;
display: flex;
position: relative;
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
.code-snippet__wechat ul li {
list-style: none;
}

View File

@ -1,72 +0,0 @@
/*! 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: #fff;
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;
}
}

View File

@ -57,14 +57,29 @@ export default {
],
codeThemeOption: [
{
label: "微信",
value: "wechat",
desc: "默认样式",
label: "github",
value: "https://lib.baomitu.com/highlight.js/10.7.3/styles/github.min.css",
desc: "light",
},
{
label: "GitHub",
value: "github",
desc: "精简风格",
label: "solarized-light",
value: "https://lib.baomitu.com/highlight.js/11.3.1/styles/base16/solarized-light.min.css",
desc: "light",
},
{
label: "atom-one-dark",
value: "https://lib.baomitu.com/highlight.js/11.3.1/styles/atom-one-dark.min.css",
desc: "dark",
},
{
label: "obsidian",
value: "https://lib.baomitu.com/highlight.js/11.3.1/styles/obsidian.min.css",
desc: "dark",
},
{
label: "vs2015",
value: "https://lib.baomitu.com/highlight.js/11.3.1/styles/vs2015.min.css",
desc: "dark",
},
],
form: {

View File

@ -1,4 +1,5 @@
import { Renderer } from "marked";
import hljs from 'highlight.js';
class WxRenderer {
constructor(opts) {
@ -7,9 +8,6 @@ class WxRenderer {
let footnoteIndex = 0;
let styleMapping = new Map();
const CODE_FONT_FAMILY =
"Menlo, Operator Mono, Consolas, Monaco, monospace";
let merge = (base, extend) => Object.assign({}, base, extend);
this.buildTheme = (themeTpl) => {
@ -25,13 +23,10 @@ class WxRenderer {
}
}
let base_block = merge(base, {});
let base_block = merge(base, {});
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);
}
}
@ -126,23 +121,17 @@ class WxRenderer {
return `<blockquote ${getStyles("blockquote")}>${text}</blockquote>`;
};
renderer.code = (text, lang) => {
text = text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
const codeLines = text
.split("\n")
.map(
(line) =>
`<code class="prettyprint"><span class="code-snippet_outer">${
line || " "
}</span></code>`
);
const codeTheme = "github";
return `
<section class="code-snippet__${codeTheme}">
<pre class="code__pre" data-lang="${lang}">
${codeLines.join("")}
</pre>
</section>
`;
lang = hljs.getLanguage(lang) ? lang : 'plaintext';
text = hljs.highlight(text, {language: lang}).value;
text = text.replace(/\r\n/g,"<br/>")
.replace(/\n/g,"<br/>")
.replace(/(>[^<]+)|(^[^<]+)/g, function(str) {
return str.replace(/\s/g, '&nbsp;')
});
return `<pre class="hljs code__pre" ${getStyles("code_pre")}><code class="prettyprint language-${lang}" ${getStyles("code")}>${text}</code></pre>`
};
renderer.codespan = (text, lang) =>
`<code ${getStyles("codespan")}>${text}</code>`;

View File

@ -1,11 +1,9 @@
let baseColor = "#3f3f3f"
export default {
BASE: {
"text-align": "left",
color: "#3f3f3f",
"line-height": "1.75",
},
BASE_BLOCK: {
margin: "1em 8px",
"line-height": "1.75"
},
block: {
// 一级标题样式
@ -17,6 +15,7 @@ export default {
margin: "2em auto 1em",
padding: "0 1em",
"border-bottom": "2px solid rgba(0, 152, 116, 0.9)",
color: baseColor,
},
// 二级标题样式
@ -39,6 +38,7 @@ export default {
"line-height": "1.2",
"padding-left": "8px",
"border-left": "3px solid rgba(0, 152, 116, 0.9)",
color: baseColor,
},
// 四级标题样式
@ -53,6 +53,7 @@ export default {
p: {
margin: "1.5em 8px",
"letter-spacing": "0.1em",
color: baseColor,
},
// 引用样式
@ -72,20 +73,20 @@ export default {
"font-size": "1em",
display: "block",
},
code: {
"font-size": "80%",
overflow: "auto",
color: "#333",
"white-space": "pre",
background: "rgb(247, 247, 247)",
code_pre: {
"font-size": "14px",
"overflow-x": "auto",
"border-radius": "8px",
padding: "10px",
padding: "1em",
"line-height": "1.5",
border: "1px solid rgb(236,236,236)",
margin: "20px 0",
margin: "10px 8px"
},
code: {
"margin": 0,
"white-space": "nowrap",
"font-family": "Menlo, Operator Mono, Consolas, Monaco, monospace"
},
image: {
"border-radius": "4px",
display: "block",
@ -96,21 +97,25 @@ export default {
ol: {
"margin-left": "0",
"padding-left": "1em",
color: baseColor,
},
ul: {
"margin-left": "0",
"padding-left": "1em",
"list-style": "circle",
color: baseColor,
},
footnotes: {
margin: "0.5em 8px",
"font-size": "80%",
color: baseColor,
},
figure: {
margin: "1.5em 8px",
color: baseColor,
},
hr: {
"border-style": "solid",
@ -127,6 +132,7 @@ export default {
"text-indent": "-1em",
display: "block",
margin: "0.2em 8px",
color: baseColor,
},
codespan: {
@ -157,20 +163,24 @@ export default {
"border-collapse": "collapse",
"text-align": "center",
margin: "1em 8px",
color: baseColor,
},
thead: {
background: "rgba(0, 0, 0, 0.05)",
"font-weight": "bold",
color: baseColor,
},
td: {
border: "1px solid #dfdfdf",
padding: "0.25em 0.5em",
color: baseColor,
},
footnote: {
"font-size": "12px",
color: baseColor,
},
figcaption: {

View File

@ -215,15 +215,6 @@ export function formatCss(content) {
return doc;
}
export function fixCodeWhiteSpace(value = "pre") {
const preDomList = document.getElementsByClassName("code__pre");
if (preDomList.length > 0) {
preDomList.forEach((pre) => {
pre.style.whiteSpace = value;
});
}
}
/**
* 导出原始 Markdown 文档
* @param {文档内容} doc
@ -264,7 +255,6 @@ export function exportHTML() {
function setStyles(element) {
switch (true) {
case isSection(element):
case isPre(element):
case isCode(element):
case isSpan(element):
@ -275,13 +265,6 @@ export function exportHTML() {
Array.from(element.children).forEach((child) => setStyles(child));
}
// 判断是否是包裹代码块的 section 元素
function isSection(element) {
return (
element.tagName === "SECTION" &&
Array.from(element.classList).includes("code-snippet__github")
);
}
// 判断是否是包裹代码块的 pre 元素
function isPre(element) {
return (

View File

@ -109,6 +109,22 @@
<span class="select-item-right">{{ color.desc }}</span>
</el-option>
</el-select>
<el-select
v-model="selectCodeTheme"
size="mini"
placeholder="代码主题"
@change="codeThemeChanged"
>
<el-option
v-for="code in config.codeThemeOption"
:key="code.value"
:label="code.label"
:value="code.value"
>
<span class="select-item-left">{{ code.label }}</span>
<span class="select-item-right">{{ code.desc }}</span>
</el-option>
</el-select>
<el-tooltip content="自定义颜色" :effect="effect" placement="top">
<el-color-picker
v-model="selectColor"
@ -205,7 +221,7 @@ export default {
selectFont: "",
selectSize: "",
selectColor: "",
selectCodeTheme: "github",
selectCodeTheme: config.codeThemeOption[0].value
};
},
components: {
@ -275,7 +291,6 @@ export default {
setTimeout(() => {
let clipboardDiv = document.getElementById("output");
solveWeChatImage();
fixCodeWhiteSpace();
solveHtml();
clipboardDiv.focus();
window.getSelection().removeAllRanges();
@ -286,7 +301,6 @@ export default {
window.getSelection().addRange(range);
document.execCommand("copy");
window.getSelection().removeAllRanges();
fixCodeWhiteSpace("normal");
clipboardDiv.innerHTML = this.output;
//
this.$notify({
@ -326,11 +340,13 @@ export default {
this.fontChanged(this.config.builtinFonts[0].value);
this.colorChanged(this.config.colorOption[0].value);
this.sizeChanged(this.config.sizeOption[2].value);
this.codeThemeChanged(this.config.codeThemeOption[0].value)
this.$emit("cssChanged");
this.selectFont = this.currentFont;
this.selectSize = this.currentSize;
this.selectColor = this.currentColor;
this.showResetConfirm = false;
this.selectCodeTheme = this.codeTheme;
},
cancelReset() {
this.showResetConfirm = false;

View File

@ -149,6 +149,7 @@ export default {
currentColor: (state) => state.currentColor,
nightMode: (state) => state.nightMode,
rightClickMenuVisible: (state) => state.rightClickMenuVisible,
codeTheme: (state) => state.codeTheme,
}),
},
created() {
@ -223,6 +224,21 @@ export default {
});
this.onEditorRefresh();
},
// highlight.js
codeThemeChanged() {
let cssUrl = this.codeTheme;
let el = document.getElementById('hljs')
if (el != undefined) {
el.setAttribute('href', cssUrl);
} else {
var link = document.createElement('link');
link.setAttribute('type','text/css');
link.setAttribute('rel','stylesheet');
link.setAttribute('href',cssUrl);
link.setAttribute('id','hljs');
document.head.appendChild(link);
}
},
beforeUpload(file) {
// validate image
const checkResult = checkImage(file);
@ -318,6 +334,7 @@ export default {
},
//
onEditorRefresh() {
this.codeThemeChanged(this.codeTheme);
this.editorRefresh();
setTimeout(() => PR.prettyPrint(), 0);
},
@ -532,8 +549,10 @@ export default {
transform: none;
}
}
.codeMirror-wrapper {
overflow-x: auto;
}
</style>
<style lang="less" scoped>
@import url("../../../assets/less/app.less");
@import url("../../../assets/less/github-v2.min.css");
</style>

View File

@ -21,7 +21,7 @@ const state = {
currentColor: "",
citeStatus: 0,
nightMode: false,
codeTheme: "github",
codeTheme: config.codeThemeOption[0].value,
rightClickMenuVisible: false,
};
const mutations = {