From cefd974498a8aadea4a19c91f3fceec03d5ceb4c Mon Sep 17 00:00:00 2001 From: caishi <1149225589@qq.com> Date: Fri, 31 Jul 2020 11:21:02 +0800 Subject: [PATCH] =?UTF-8?q?md=20p=E6=A0=87=E7=AD=BE=E4=B8=8D=E6=8D=A2?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.css | 1 + src/common/UrlTool.js | 8 -- src/common/marked.js | 129 ++++++++++++++---- src/components/render-html.jsx | 2 +- src/forge/Upload/Index.js | 12 +- src/forge/comments/comments.js | 2 +- .../tpm/challengesnew/tpm-md-editor.js | 16 +-- 7 files changed, 118 insertions(+), 52 deletions(-) diff --git a/src/App.css b/src/App.css index c13f28da..205c64f7 100644 --- a/src/App.css +++ b/src/App.css @@ -66,6 +66,7 @@ body { margin:10px 0px!important; font-size: 16px !important; line-height: 2 !important; + white-space: pre-wrap; } .markdown-body>p { diff --git a/src/common/UrlTool.js b/src/common/UrlTool.js index ac980f32..0516da60 100644 --- a/src/common/UrlTool.js +++ b/src/common/UrlTool.js @@ -78,14 +78,6 @@ export function setImagesUrl(path){ } export function getUrl(path, goTest) { - // https://www.educoder.net - // https://testbdweb.trustie.net - - // 如果想所有url定位到测试版,可以反注释掉下面这行 - //goTest = true - // testbdweb.educoder.net testbdweb.trustie.net - // const local = goTest ? 'https://testeduplus2.educoder.net' : 'http://localhost:3000' - // const local = 'https://testeduplus2.educoder.net' const local = 'https://testforgeplus.trustie.net' if (isDev) { return `${local}${path?path:''}` diff --git a/src/common/marked.js b/src/common/marked.js index 681a2420..6a23c81e 100644 --- a/src/common/marked.js +++ b/src/common/marked.js @@ -1,8 +1,55 @@ import marked from 'marked' -import { escape, unescape } from 'marked/src/helpers' -import { renderToString } from 'katex'; +import { escape, rtrim } from 'marked/src/helpers' +import { renderToString } from 'katex' -const latexRegex = /\$+([^\$\n]+?)\$+/g +function unescape(str) { + str = str + .replace(/( |\u00a0| )/g, '') + .replace(/>/g, '>') + .replace(/</g, '<') + .replace(/\\$/g, '') + .replace(/^\\(?:{)/, '\\\\{') + if (!str.match(/\S/)) { + return ''; + } + return str +} + +function toKatex(str) { + return renderToString(unescape(str), { + throwOnError: false + }) +} + +function indentCodeCompensation(raw, text) { + const matchIndentToCode = raw.match(/^(\s+)(?:```)/); + + if (matchIndentToCode === null) { + return text; + } + + const indentToCode = matchIndentToCode[1]; + + return text + .split('\n') + .map(node => { + const matchIndentInNode = node.match(/^\s+/); + if (matchIndentInNode === null) { + return node; + } + + const [indentInNode] = matchIndentInNode; + + if (indentInNode.length >= indentToCode.length) { + return node.slice(indentToCode.length); + } + + return node; + }) + .join('\n'); +} + +const latexRegex = /\`?\${2}([^\$\n]+?)\${2}\`?/g //兼容之前的 ##标题式写法 const headingRegex = /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/ let toc = [] @@ -40,36 +87,75 @@ export function getTocContent() { const tokenizer = { heading(src) { - const cap = headingRegex.exec(src); + const cap = headingRegex.exec(src) if (cap) { return { type: 'heading', raw: cap[0], depth: cap[1].length, text: cap[2] + } + } + }, + paragraph(src) { + const cap = this.rules.block.paragraph.exec(src) + let text = cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1] + let match = text.match(latexRegex) + if (match) { + text = text.replace(latexRegex, (_, $1) => { + return toKatex($1) + }) + } + if (cap) { + return { + type: 'paragraph', + raw: cap[0], + text }; } }, - codespan(src) { - const cap = this.rules.inline.code.exec(src); + code(src, tokens) { + const cap = this.rules.block.code.exec(src) if (cap) { - let text = cap[2].replace(/\n/g, ' '); - const hasNonSpaceChars = /[^ ]/.test(text); - const hasSpaceCharsOnBothEnds = text.startsWith(' ') && text.endsWith(' '); - if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) { - text = text.substring(1, text.length - 1) + const lastToken = tokens[tokens.length - 1]; + // An indented code block cannot interrupt a paragraph. + if (lastToken && lastToken.type === 'paragraph') { + return { + raw: cap[0], + text: cap[0].trimRight() + } } + + let text = cap[0].replace(/^ {4}/gm, '') + text = !this.options.pedantic ? rtrim(text, '\n') : text let match = text.match(latexRegex) if (match) { text = text.replace(latexRegex, (_, $1) => { - return renderToString(unescape($1)) + return toKatex($1) }) - } else { - text = escape(text, true); + } + + return { + type: 'code', + raw: cap[0], + codeBlockStyle: 'indented', + text + } + } + }, + fences(src) { + const cap = this.rules.block.fences.exec(src) + if (cap) { + const raw = cap[0] + let text = indentCodeCompensation(raw, cap[3] || '') + const lang = cap[2] ? cap[2].trim() : cap[2] + if (['latex', 'katex', 'math'].indexOf(lang) >= 0) { + text = toKatex(text) } return { - type: 'codespan', - raw: cap[0], + type: 'code', + raw, + lang, text } } @@ -86,19 +172,10 @@ const renderer = { } if (['latex', 'katex', 'math'].indexOf(lang) >= 0) { - return `

${renderToString(unescape(code))}

` + return `

${code}

` } else { return `
${escaped ? code : escape(code, true)}
\n` } - } - , paragraph(text) { - let match = text.match(latexRegex) - if (match) { - text = text.replace(latexRegex, (_, $1) => { - return renderToString(unescape($1)) - }) - } - return '

' + text + '

\n'; }, heading(text, level, raw, slugger) { let anchor = this.options.headerPrefix + raw.toLowerCase().replace(/[^\w\\u4e00-\\u9fa5]]+/g, '-'); diff --git a/src/components/render-html.jsx b/src/components/render-html.jsx index f8154f9c..c16a76eb 100644 --- a/src/components/render-html.jsx +++ b/src/components/render-html.jsx @@ -14,7 +14,7 @@ export default ({ value = "", is_md = true, className, style = {} }) => { } html = html.replace(/▁/g, "▁▁▁"); - // html = html.replace(/\n/g,"
"); + // html = html.replace(/\n/g,"
"); const el = useRef(); function onAncherHandler(e) { diff --git a/src/forge/Upload/Index.js b/src/forge/Upload/Index.js index 3ea58a45..f366d5da 100644 --- a/src/forge/Upload/Index.js +++ b/src/forge/Upload/Index.js @@ -73,7 +73,7 @@ class Index extends Component { array && this.props.load && this.props.load(array); } - beforeUpload = (file)=>{ + beforeUpload = (file) => { const { size } = this.props; const isLt100M = file.size / 1024 / 1024 < size; if (!isLt100M) { @@ -84,7 +84,7 @@ class Index extends Component { render() { //判断是否已经提交,如已提交评论则上一条评论数据清除 - const { isComplete , icon } = this.props; + const { isComplete, icon } = this.props; const { fileList } = this.state; let list = isComplete === true ? fileList : undefined; @@ -94,14 +94,14 @@ class Index extends Component { action: `${getUploadActionUrl()}`, onChange: this.handleChange, onRemove: this.onAttachmentRemove, - beforeUpload:this.beforeUpload + beforeUpload: this.beforeUpload }; return ( - { icon || } -

拖动文件或点击此处上传

-
+ {icon || } +

拖动文件或点击此处上传

+ ) } } diff --git a/src/forge/comments/comments.js b/src/forge/comments/comments.js index 24ef94f3..bd6fd748 100644 --- a/src/forge/comments/comments.js +++ b/src/forge/comments/comments.js @@ -12,7 +12,7 @@ import RenderHtml from "../../components/render-html"; import ChildrenComments from "./children_comments"; import "../Order/order.css"; const { TabPane } = Tabs; -class comments extends Component { +class comments extends Component { constructor(props) { super(props); this.state = { diff --git a/src/modules/tpm/challengesnew/tpm-md-editor.js b/src/modules/tpm/challengesnew/tpm-md-editor.js index 9105f23f..0fa0e1fd 100644 --- a/src/modules/tpm/challengesnew/tpm-md-editor.js +++ b/src/modules/tpm/challengesnew/tpm-md-editor.js @@ -75,14 +75,14 @@ function md_elocalStorage(editor, mdu, id) { } -export default ({ mdID,onChange,autoFocus,onCMBeforeChange, onCMBlur, error = false, className = '', noStorage = false, imageExpand = true, placeholder = '', width = '100%', height = 400, initValue = '', emoji, watch, showNullButton = false, showResizeBar = false, startInit = true }) => { +export default ({ mdID, onChange, onCMBeforeChange, onCMBlur, error = false, className = '', noStorage = false, imageExpand = true, placeholder = '', width = '100%', height = 400, initValue = '', emoji, watch, showNullButton = false, showResizeBar = false, startInit = true }) => { const editorEl = useRef() const resizeBarEl = useRef() const [editorInstance, setEditorInstance] = useState() const containerId = `mdEditor_${mdID}` const editorBodyId = `mdEditors_${mdID}` - const tipId = `e_tips_mdEditor_${mdID}` + const tipId = `e_tips_mdEditor_${mdID}` function onLayout() { let ro @@ -92,10 +92,7 @@ export default ({ mdID,onChange,autoFocus,onCMBeforeChange, onCMBlur, error = fa if (entry.target.offsetHeight > 0 || entry.target.offsetWidth > 0) { editorInstance.resize() editorInstance.cm.refresh() - // if(autofocus){ - // editorInstance.cm.focus() - // } - + editorInstance.cm.focus() } } }) @@ -105,7 +102,6 @@ export default ({ mdID,onChange,autoFocus,onCMBeforeChange, onCMBlur, error = fa } useEffect(() => { - if (editorInstance) { return } @@ -124,7 +120,7 @@ export default ({ mdID,onChange,autoFocus,onCMBeforeChange, onCMBlur, error = fa searchReplace: true, htmlDecode: "style,script,iframe", sequenceDiagram: true, - autoFocus: autoFocus === undefined ? false : autoFocus, + autoFocus: false, watch: watch === undefined ? true : watch, saveHTMLToTextarea: true, @@ -256,8 +252,8 @@ export default ({ mdID,onChange,autoFocus,onCMBeforeChange, onCMBlur, error = fa } } }, [ - editorInstance, resizeBarEl - ]) + editorInstance, resizeBarEl + ]) return (