mirror of
https://github.com/doocs/md.git
synced 2025-01-22 20:04:39 +08:00
Compare commits
5 Commits
f06cef1fbc
...
43191b3535
Author | SHA1 | Date | |
---|---|---|---|
|
43191b3535 | ||
|
fc1712f948 | ||
|
8f09acb06d | ||
|
4f73811f0e | ||
|
67ef9dc21c |
51
package-lock.json
generated
51
package-lock.json
generated
@ -22,6 +22,7 @@
|
|||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"es-toolkit": "^1.27.0",
|
"es-toolkit": "^1.27.0",
|
||||||
"form-data": "4.0.1",
|
"form-data": "4.0.1",
|
||||||
|
"front-matter": "^4.0.2",
|
||||||
"highlight.js": "^11.10.0",
|
"highlight.js": "^11.10.0",
|
||||||
"juice": "^11.0.0",
|
"juice": "^11.0.0",
|
||||||
"lucide-vue-next": "^0.462.0",
|
"lucide-vue-next": "^0.462.0",
|
||||||
@ -9217,6 +9218,19 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/esprima": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"bin": {
|
||||||
|
"esparse": "bin/esparse.js",
|
||||||
|
"esvalidate": "bin/esvalidate.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esquery": {
|
"node_modules/esquery": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz",
|
"resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz",
|
||||||
@ -9704,6 +9718,37 @@
|
|||||||
"url": "https://github.com/sponsors/rawify"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/front-matter": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"js-yaml": "^3.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/front-matter/node_modules/argparse": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sprintf-js": "~1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/front-matter/node_modules/js-yaml": {
|
||||||
|
"version": "3.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||||
|
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"argparse": "^1.0.7",
|
||||||
|
"esprima": "^4.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"js-yaml": "bin/js-yaml.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs-extra": {
|
"node_modules/fs-extra": {
|
||||||
"version": "11.2.0",
|
"version": "11.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.2.0.tgz",
|
||||||
@ -16856,6 +16901,12 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sprintf-js": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/stable-hash": {
|
"node_modules/stable-hash": {
|
||||||
"version": "0.0.4",
|
"version": "0.0.4",
|
||||||
"resolved": "https://registry.npmmirror.com/stable-hash/-/stable-hash-0.0.4.tgz",
|
"resolved": "https://registry.npmmirror.com/stable-hash/-/stable-hash-0.0.4.tgz",
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"es-toolkit": "^1.27.0",
|
"es-toolkit": "^1.27.0",
|
||||||
"form-data": "4.0.1",
|
"form-data": "4.0.1",
|
||||||
|
"front-matter": "^4.0.2",
|
||||||
"highlight.js": "^11.10.0",
|
"highlight.js": "^11.10.0",
|
||||||
"juice": "^11.0.0",
|
"juice": "^11.0.0",
|
||||||
"lucide-vue-next": "^0.462.0",
|
"lucide-vue-next": "^0.462.0",
|
||||||
|
@ -79,12 +79,11 @@ const defaultTheme: Theme = {
|
|||||||
// 引用
|
// 引用
|
||||||
blockquote: {
|
blockquote: {
|
||||||
'font-style': `normal`,
|
'font-style': `normal`,
|
||||||
'border-left': `none`,
|
|
||||||
'padding': `1em`,
|
'padding': `1em`,
|
||||||
'border-radius': `8px`,
|
'border-left': `4px solid var(--md-primary-color)`,
|
||||||
|
'border-radius': `6px`,
|
||||||
'color': `rgba(0,0,0,0.5)`,
|
'color': `rgba(0,0,0,0.5)`,
|
||||||
'background': `var(--blockquote-background)`,
|
'background': `var(--blockquote-background)`,
|
||||||
'margin': `2em 8px`,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 引用内容
|
// 引用内容
|
||||||
@ -206,12 +205,14 @@ const defaultTheme: Theme = {
|
|||||||
|
|
||||||
hr: {
|
hr: {
|
||||||
'border-style': `solid`,
|
'border-style': `solid`,
|
||||||
'border-width': `1px 0 0`,
|
'border-width': `2px 0 0`,
|
||||||
'border-color': `rgba(0,0,0,0.1)`,
|
'border-color': `rgba(0,0,0,0.1)`,
|
||||||
'-webkit-transform-origin': `0 0`,
|
'-webkit-transform-origin': `0 0`,
|
||||||
'-webkit-transform': `scale(1, 0.5)`,
|
'-webkit-transform': `scale(1, 0.5)`,
|
||||||
'transform-origin': `0 0`,
|
'transform-origin': `0 0`,
|
||||||
'transform': `scale(1, 0.5)`,
|
'transform': `scale(1, 0.5)`,
|
||||||
|
'height': `0.4em`,
|
||||||
|
'margin': `1.5em 0`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
inline: {
|
inline: {
|
||||||
|
@ -179,7 +179,8 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
codeThemeChange()
|
codeThemeChange()
|
||||||
renderer.reset({ citeStatus: isCiteStatus.value, legend: legend.value, isUseIndent: isUseIndent.value })
|
renderer.reset({ citeStatus: isCiteStatus.value, legend: legend.value, isUseIndent: isUseIndent.value })
|
||||||
|
|
||||||
let outputTemp = marked.parse(editor.value!.getValue()) as string
|
const { markdownContent } = renderer.parseFrontMatterAndContent(editor.value!.getValue())
|
||||||
|
let outputTemp = marked.parse(markdownContent) as string
|
||||||
|
|
||||||
// 去除第一行的 margin-top
|
// 去除第一行的 margin-top
|
||||||
outputTemp = outputTemp.replace(/(style=".*?)"/, `$1;margin-top: 0"`)
|
outputTemp = outputTemp.replace(/(style=".*?)"/, `$1;margin-top: 0"`)
|
||||||
|
@ -2,14 +2,18 @@ import type { ExtendedProperties, IOpts, ThemeStyles } from '@/types'
|
|||||||
import type { PropertiesHyphen } from 'csstype'
|
import type { PropertiesHyphen } from 'csstype'
|
||||||
import type { Renderer, RendererObject, Tokens } from 'marked'
|
import type { Renderer, RendererObject, Tokens } from 'marked'
|
||||||
import { cloneDeep, toMerged } from 'es-toolkit'
|
import { cloneDeep, toMerged } from 'es-toolkit'
|
||||||
import hljs from 'highlight.js'
|
import frontMatter from 'front-matter'
|
||||||
|
|
||||||
|
import hljs from 'highlight.js'
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
import mermaid from 'mermaid'
|
import mermaid from 'mermaid'
|
||||||
import { getStyleString } from '.'
|
import { getStyleString } from '.'
|
||||||
import markedAlert from './MDAlert'
|
import markedAlert from './MDAlert'
|
||||||
import { MDKatex } from './MDKatex'
|
import { MDKatex } from './MDKatex'
|
||||||
|
|
||||||
|
marked.setOptions({
|
||||||
|
breaks: true,
|
||||||
|
})
|
||||||
marked.use(MDKatex({ nonStandard: true }))
|
marked.use(MDKatex({ nonStandard: true }))
|
||||||
|
|
||||||
function buildTheme({ theme: _theme, fonts, size, isUseIndent }: IOpts): ThemeStyles {
|
function buildTheme({ theme: _theme, fonts, size, isUseIndent }: IOpts): ThemeStyles {
|
||||||
@ -36,6 +40,16 @@ function buildTheme({ theme: _theme, fonts, size, isUseIndent }: IOpts): ThemeSt
|
|||||||
} as ThemeStyles
|
} as ThemeStyles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text: string): string {
|
||||||
|
return text
|
||||||
|
.replace(/&/g, `&`) // 转义 &
|
||||||
|
.replace(/</g, `<`) // 转义 <
|
||||||
|
.replace(/>/g, `>`) // 转义 >
|
||||||
|
.replace(/"/g, `"`) // 转义 "
|
||||||
|
.replace(/'/g, `'`) // 转义 '
|
||||||
|
.replace(/`/g, ```) // 转义 `
|
||||||
|
}
|
||||||
|
|
||||||
function buildAddition(): string {
|
function buildAddition(): string {
|
||||||
return `
|
return `
|
||||||
<style>
|
<style>
|
||||||
@ -107,6 +121,19 @@ export function initRenderer(opts: IOpts) {
|
|||||||
return getStyles(styleMapping, tag, addition)
|
return getStyles(styleMapping, tag, addition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseFrontMatterAndContent(markdownText: string) {
|
||||||
|
try {
|
||||||
|
const parsed = frontMatter(markdownText)
|
||||||
|
const yamlData = parsed.attributes
|
||||||
|
const markdownContent = parsed.body
|
||||||
|
return { yamlData, markdownContent }
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(`Error parsing front-matter:`, error)
|
||||||
|
return { yamlData: {}, markdownContent: markdownText }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function styledContent(styleLabel: string, content: string, tagName?: string): string {
|
function styledContent(styleLabel: string, content: string, tagName?: string): string {
|
||||||
const tag = tagName ?? styleLabel
|
const tag = tagName ?? styleLabel
|
||||||
return `<${tag} ${styles(styleLabel)}>${content}</${tag}>`
|
return `<${tag} ${styles(styleLabel)}>${content}</${tag}>`
|
||||||
@ -175,7 +202,7 @@ export function initRenderer(opts: IOpts) {
|
|||||||
const language = hljs.getLanguage(langText) ? langText : `plaintext`
|
const language = hljs.getLanguage(langText) ? langText : `plaintext`
|
||||||
let highlighted = hljs.highlight(text, { language }).value
|
let highlighted = hljs.highlight(text, { language }).value
|
||||||
// tab to 4 spaces
|
// tab to 4 spaces
|
||||||
highlighted = highlighted.replace(/\t/g, ' ')
|
highlighted = highlighted.replace(/\t/g, ` `)
|
||||||
highlighted = highlighted
|
highlighted = highlighted
|
||||||
.replace(/\r\n/g, `<br/>`)
|
.replace(/\r\n/g, `<br/>`)
|
||||||
.replace(/\n/g, `<br/>`)
|
.replace(/\n/g, `<br/>`)
|
||||||
@ -186,7 +213,8 @@ export function initRenderer(opts: IOpts) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
codespan({ text }: Tokens.Codespan): string {
|
codespan({ text }: Tokens.Codespan): string {
|
||||||
return styledContent(`codespan`, text, `code`)
|
const escapedText = escapeHtml(text)
|
||||||
|
return styledContent(`codespan`, escapedText, `code`)
|
||||||
},
|
},
|
||||||
|
|
||||||
listitem(item: Tokens.ListItem): string {
|
listitem(item: Tokens.ListItem): string {
|
||||||
@ -276,6 +304,7 @@ export function initRenderer(opts: IOpts) {
|
|||||||
buildFootnotes,
|
buildFootnotes,
|
||||||
setOptions,
|
setOptions,
|
||||||
reset,
|
reset,
|
||||||
|
parseFrontMatterAndContent,
|
||||||
createContainer(content: string) {
|
createContainer(content: string) {
|
||||||
return styledContent(`container`, content, `section`)
|
return styledContent(`container`, content, `section`)
|
||||||
},
|
},
|
||||||
|
@ -170,12 +170,15 @@ watch(isDark, () => {
|
|||||||
toRaw(editor.value)?.setOption?.(`theme`, theme)
|
toRaw(editor.value)?.setOption?.(`theme`, theme)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const charCount = ref(0)
|
||||||
|
|
||||||
// 初始化编辑器
|
// 初始化编辑器
|
||||||
function initEditor() {
|
function initEditor() {
|
||||||
const editorDom = document.querySelector<HTMLTextAreaElement>(`#editor`)!
|
const editorDom = document.querySelector<HTMLTextAreaElement>(`#editor`)!
|
||||||
|
|
||||||
if (!editorDom.value) {
|
if (!editorDom.value) {
|
||||||
editorDom.value = store.posts[store.currentPostIndex].content
|
editorDom.value = store.posts[store.currentPostIndex].content
|
||||||
|
charCount.value = store.posts[store.currentPostIndex].content.replace(/\s/g, ``).length
|
||||||
}
|
}
|
||||||
editor.value = CodeMirror.fromTextArea(editorDom, {
|
editor.value = CodeMirror.fromTextArea(editorDom, {
|
||||||
mode: `text/x-markdown`,
|
mode: `text/x-markdown`,
|
||||||
@ -222,7 +225,9 @@ function initEditor() {
|
|||||||
clearTimeout(changeTimer.value)
|
clearTimeout(changeTimer.value)
|
||||||
changeTimer.value = setTimeout(() => {
|
changeTimer.value = setTimeout(() => {
|
||||||
onEditorRefresh()
|
onEditorRefresh()
|
||||||
store.posts[store.currentPostIndex].content = e.getValue()
|
const value = e.getValue()
|
||||||
|
store.posts[store.currentPostIndex].content = value
|
||||||
|
charCount.value = value.replace(/\s/g, ``).length
|
||||||
}, 300)
|
}, 300)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -414,6 +419,7 @@ onMounted(() => {
|
|||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="relative flex-1">
|
||||||
<div
|
<div
|
||||||
id="preview"
|
id="preview"
|
||||||
ref="preview"
|
ref="preview"
|
||||||
@ -432,6 +438,10 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bg-muted absolute bottom-0 left-0 p-2 text-xs shadow">
|
||||||
|
{{ charCount }} 个字符
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<CssEditor class="flex-1" />
|
<CssEditor class="flex-1" />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
Loading…
Reference in New Issue
Block a user