diff --git a/src/renderer/components/Preview.vue b/src/renderer/components/Preview.vue index fdb5056c..b95795f8 100644 --- a/src/renderer/components/Preview.vue +++ b/src/renderer/components/Preview.vue @@ -52,6 +52,7 @@ import { revealLineInCenter } from '@fe/services/editor' import { showExport, toUri } from '@fe/services/document' import { getContextMenuItems, toggleAutoPreview } from '@fe/services/view' import { useContextMenu } from '@fe/support/ui/context-menu' +import { DOM_ATTR_NAME } from '@fe/support/constant' import { useI18n } from '@fe/services/i18n' import { getLogger } from '@fe/utils' import type { RenderEnv } from '@fe/types' @@ -236,7 +237,7 @@ export default defineComponent({ return } - const nodes = refViewWrapper.value!.querySelectorAll('.markdown-body .source-line') + const nodes = refViewWrapper.value!.querySelectorAll(`.markdown-body [${DOM_ATTR_NAME.SOURCE_LINE_START}]`) for (let i = 0; i < nodes.length; i++) { const el = nodes[i] const lineNumber = parseInt(el.dataset.sourceLine || '0') diff --git a/src/renderer/context/index.ts b/src/renderer/context/index.ts index a33a0b3b..289c671f 100644 --- a/src/renderer/context/index.ts +++ b/src/renderer/context/index.ts @@ -13,6 +13,7 @@ import * as base from '@fe/services/base' import * as api from '@fe/support/api' import * as embed from '@fe/support/embed' import * as args from '@fe/support/args' +import * as constant from '@fe/support/constant' import * as doc from '@fe/services/document' import * as view from '@fe/services/view' import * as tree from '@fe/services/tree' @@ -29,6 +30,7 @@ const ctx = { base, api, args, + constant, store, action, doc, diff --git a/src/renderer/plugins/editor-attachment.ts b/src/renderer/plugins/editor-attachment.ts index 6d83a5a3..460d345f 100644 --- a/src/renderer/plugins/editor-attachment.ts +++ b/src/renderer/plugins/editor-attachment.ts @@ -11,6 +11,7 @@ import { refreshTree } from '@fe/services/tree' import { upload } from '@fe/services/base' import { isSameRepo } from '@fe/services/document' import { useToast } from '@fe/support/ui/toast' +import { DOM_CLASS_NAME } from '@fe/support/constant' import { t } from '@fe/services/i18n' async function uploadFile (file: any, asImage: boolean) { @@ -24,7 +25,7 @@ async function uploadFile (file: any, asImage: boolean) { if (asImage) { insert(`![Img](${encodeMarkdownLink(assetPath)})\n`) } else { - insert(`[${dayjs().format('YYYY-MM-DD HH:mm')}] [${file.name} (${(file.size / 1024).toFixed(2)}KiB)](${encodeMarkdownLink(assetPath)}){.open}\n`) + insert(`[${dayjs().format('YYYY-MM-DD HH:mm')}] [${file.name} (${(file.size / 1024).toFixed(2)}KiB)](${encodeMarkdownLink(assetPath)}){.${DOM_CLASS_NAME.MARK_OPEN}}\n`) } refreshTree() diff --git a/src/renderer/plugins/markdown-applet.ts b/src/renderer/plugins/markdown-applet.ts index 071d6f0f..bea7a110 100644 --- a/src/renderer/plugins/markdown-applet.ts +++ b/src/renderer/plugins/markdown-applet.ts @@ -40,7 +40,7 @@ const MarkdownItPlugin = (md: Markdown) => { }, iframeProps: { ...token.meta?.attrs, - class: 'source-line applet-iframe', + class: 'applet-iframe', height: '20px', } }) diff --git a/src/renderer/plugins/markdown-echarts.ts b/src/renderer/plugins/markdown-echarts.ts index 46d4f64d..36204ce4 100644 --- a/src/renderer/plugins/markdown-echarts.ts +++ b/src/renderer/plugins/markdown-echarts.ts @@ -138,7 +138,7 @@ const Echarts = defineComponent({ return h('img', { src: imgSrc.value }) } - return h('div', { ...props.attrs, class: 'source-line echarts-wrapper' }, [ + return h('div', { ...props.attrs, class: 'echarts-wrapper' }, [ h('div', { class: 'echarts-action no-print' }, [ h('button', { class: 'small', onClick: () => exportData('png') }, 'PNG'), ]), diff --git a/src/renderer/plugins/markdown-katex.ts b/src/renderer/plugins/markdown-katex.ts index a85baa67..fb358ffb 100644 --- a/src/renderer/plugins/markdown-katex.ts +++ b/src/renderer/plugins/markdown-katex.ts @@ -185,7 +185,7 @@ function math_plugin (md: any, options: any) { options.displayMode = true try { const html = katex.renderToString(latex, options) - return h('p', { ...token.meta?.attrs, class: 'source-line', key: html, innerHTML: html }, '') + return h('p', { ...token.meta?.attrs, key: html, innerHTML: html }, '') } catch (error: any) { if (options.throwOnError) { console.warn(error) } return h('code', {}, `${error.message} [${latex}]`) diff --git a/src/renderer/plugins/markdown-link.ts b/src/renderer/plugins/markdown-link.ts index f748987f..359fc882 100644 --- a/src/renderer/plugins/markdown-link.ts +++ b/src/renderer/plugins/markdown-link.ts @@ -4,6 +4,7 @@ import { Plugin } from '@fe/context' import store from '@fe/support/store' import { sleep } from '@fe/utils' import { isElectron } from '@fe/support/env' +import { DOM_ATTR_NAME, DOM_CLASS_NAME } from '@fe/support/constant' import { basename, dirname, join, resolve } from '@fe/utils/path' import { switchDoc } from '@fe/services/document' import { getRepo, openExternal, openPath } from '@fe/services/base' @@ -27,8 +28,8 @@ const handleLink = (link: HTMLAnchorElement, view: HTMLElement) => { } else if (/^file:\/\//i.test(href)) { openPath(decodeURI(href.replace(/^file:\/\//i, ''))) return true - } else if (link.classList.contains('open')) { - const path = link.getAttribute('origin-href') || decodeURI(href) + } else if (link.classList.contains(DOM_CLASS_NAME.MARK_OPEN)) { + const path = link.getAttribute(DOM_ATTR_NAME.ORIGIN_HREF) || decodeURI(href) openPath(join(getRepo(fileRepo)?.path || '/', path)) return true } else { // relative link @@ -97,7 +98,8 @@ function convertLink (state: StateCore) { } const link = (token: Token) => { - const attrName = token.tag === 'a' ? 'href' : 'src' + const isAnchor = token.tag === 'a' + const attrName = isAnchor ? 'href' : 'src' const attrVal = decodeURIComponent(token.attrGet(attrName) || '') if (!attrVal) { return @@ -110,7 +112,7 @@ function convertLink (state: StateCore) { const basePath = dirname(path) const fileName = basename(attrVal) - if (token.tag === 'a') { + if (isAnchor) { // keep markdown file. if (fileName.endsWith('.md')) { return @@ -122,10 +124,13 @@ function convertLink (state: StateCore) { } // open other file in os - token.attrPush(['class', 'open']) + token.attrPush(['class', DOM_CLASS_NAME.MARK_OPEN]) + } else { + token.attrSet(DOM_ATTR_NAME.LOCAL_IMAGE, 'true') } - token.attrSet(`origin-${attrName}`, attrVal) + const originAttr = isAnchor ? DOM_ATTR_NAME.ORIGIN_HREF : DOM_ATTR_NAME.ORIGIN_SRC + token.attrSet(originAttr, attrVal) const val = attrVal.replace(/[#?].*$/, '') @@ -166,15 +171,15 @@ export default { } ctx.registerHook('VIEW_ON_GET_HTML_FILTER_NODE', async ({ node, options }) => { - const srcAttr = node.getAttribute('src') // local image - if (srcAttr?.startsWith('api/')) { + const srcAttr = node.getAttribute('src') + if (srcAttr && node.getAttribute(DOM_ATTR_NAME.LOCAL_IMAGE)) { if (options.inlineLocalImage) { try { const res: Response = await ctx.api.fetchHttp(srcAttr) const base64Url = await ctx.utils.fileToBase64URL(await res.blob()) node.setAttribute('src', base64Url) - node.removeAttribute('origin-src') + node.removeAttribute(DOM_ATTR_NAME.ORIGIN_SRC) } catch (error) { console.log(error) } @@ -183,10 +188,10 @@ export default { } } - const originSrc = node.getAttribute('origin-src') + const originSrc = node.getAttribute(DOM_ATTR_NAME.ORIGIN_SRC) if (originSrc) { node.setAttribute('src', originSrc) - node.removeAttribute('origin-src') + node.removeAttribute(DOM_ATTR_NAME.ORIGIN_SRC) } }) diff --git a/src/renderer/plugins/markdown-luckysheet.ts b/src/renderer/plugins/markdown-luckysheet.ts index a834e361..a36ea198 100644 --- a/src/renderer/plugins/markdown-luckysheet.ts +++ b/src/renderer/plugins/markdown-luckysheet.ts @@ -13,6 +13,7 @@ import { refreshTree } from '@fe/services/tree' import { buildSrc, IFrame } from '@fe/support/embed' import { getCurrentLanguage, t, useI18n } from '@fe/services/i18n' import { FLAG_DEMO } from '@fe/support/args' +import { DOM_ATTR_NAME } from '@fe/support/constant' import Mask from '@fe/components/Mask.vue' const logger = getLogger('plugin-markdown-luckysheet') @@ -330,7 +331,7 @@ const MarkdownItPlugin = (md: Markdown) => { return linkTemp(tokens, idx, options, env, slf) } - const url = token.attrGet('origin-href') + const url = token.attrGet(DOM_ATTR_NAME.ORIGIN_HREF) if (!url || url.includes(':')) { return linkTemp(tokens, idx, options, env, slf) } diff --git a/src/renderer/plugins/markdown-mermaid.ts b/src/renderer/plugins/markdown-mermaid.ts index d453d389..c9e30abb 100644 --- a/src/renderer/plugins/markdown-mermaid.ts +++ b/src/renderer/plugins/markdown-mermaid.ts @@ -53,7 +53,7 @@ const Mermaid = defineComponent({ onMounted(() => setTimeout(render, 0)) return () => { - return h('div', { ...props.attrs, class: 'source-line mermaid-wrapper' }, [ + return h('div', { ...props.attrs, class: 'mermaid-wrapper' }, [ h('div', { class: 'mermaid-action no-print' }, [ h('button', { class: 'small', onClick: () => exportData() }, 'SVG'), ]), diff --git a/src/renderer/plugins/markdown-mind-map.ts b/src/renderer/plugins/markdown-mind-map.ts index dcf86419..734ebd7e 100644 --- a/src/renderer/plugins/markdown-mind-map.ts +++ b/src/renderer/plugins/markdown-mind-map.ts @@ -367,7 +367,7 @@ const MindMap = defineComponent({ removeHook('I18N_CHANGE_LANGUAGE', onLanguageChange) }) - return () => h('div', { ...props.attrs, ref: container, class: 'source-line mind-map reduce-brightness' }) + return () => h('div', { ...props.attrs, ref: container, class: 'mind-map reduce-brightness' }) } }) diff --git a/src/renderer/plugins/markdown-plantuml.ts b/src/renderer/plugins/markdown-plantuml.ts index cff5ea0e..77036307 100644 --- a/src/renderer/plugins/markdown-plantuml.ts +++ b/src/renderer/plugins/markdown-plantuml.ts @@ -193,7 +193,7 @@ const Plantuml = defineComponent({ ...props.attrs, ref: img, alt: 'plantuml', - class: 'source-line plantuml-img', + class: 'plantuml-img', style, src: src.value, onLoad diff --git a/src/renderer/plugins/markdown-render-vnode.ts b/src/renderer/plugins/markdown-render-vnode.ts index e224b67d..838e2954 100644 --- a/src/renderer/plugins/markdown-render-vnode.ts +++ b/src/renderer/plugins/markdown-render-vnode.ts @@ -1,8 +1,9 @@ import { createVNode, Fragment, Comment, Text, VNode } from 'vue' -import Token from 'markdown-it/lib/token' -import Renderer from 'markdown-it/lib/renderer' +import type Token from 'markdown-it/lib/token' +import type Renderer from 'markdown-it/lib/renderer' import { escapeHtml, unescapeAll } from 'markdown-it/lib/common/utils' -import { Plugin } from '@fe/context' +import { DOM_ATTR_NAME } from '@fe/support/constant' +import type { Plugin } from '@fe/context' const attrNameReg = /^[a-zA-Z_:][a-zA-Z0-9:._-]*$/ const defaultRules = {} as any @@ -36,9 +37,8 @@ export function setSourceLine (token: Token, env?: Record) { const [lineStart, lineEnd] = getLine(token, env) if (token.map) { - token.attrJoin('class', 'source-line') - token.attrSet('data-source-line', String(lineStart + 1)) - token.attrSet('data-source-line-end', String(lineEnd + 1)) + token.attrSet(DOM_ATTR_NAME.SOURCE_LINE_START, String(lineStart + 1)) + token.attrSet(DOM_ATTR_NAME.SOURCE_LINE_END, String(lineEnd + 1)) if (!token.meta.attrs) { token.meta.attrs = {} } @@ -58,9 +58,17 @@ defaultRules.code_inline = function (tokens: Token[], idx: number, _: any, __: a defaultRules.code_block = function (tokens: Token[], idx: number, _: any, __: any, slf: Renderer) { const token = tokens[idx] const attrs: any = slf.renderAttrs(token) + const preAttrs = { + [DOM_ATTR_NAME.SOURCE_LINE_START]: attrs[DOM_ATTR_NAME.SOURCE_LINE_START], + [DOM_ATTR_NAME.SOURCE_LINE_END]: attrs[DOM_ATTR_NAME.SOURCE_LINE_END], + } + + delete attrs[DOM_ATTR_NAME.SOURCE_LINE_START] + delete attrs[DOM_ATTR_NAME.SOURCE_LINE_END] + return createVNode( 'pre', - {}, + preAttrs, [createVNode('code', attrs, [createVNode(Text, {}, token.content)])] ) } @@ -88,11 +96,23 @@ defaultRules.fence = function (tokens: Token[], idx: number, options: any, _: an return highlighted + '\n' } - const buildVNode = (attrs: any) => createVNode( - 'pre', - { 'data-info': info, 'data-lang': langName }, - [createVNode('code', { key: highlighted, ...attrs, innerHTML: highlighted }, [])] - ) + const buildVNode = (attrs: any) => { + const preAttrs = { + 'data-info': info, + 'data-lang': langName, + [DOM_ATTR_NAME.SOURCE_LINE_START]: attrs[DOM_ATTR_NAME.SOURCE_LINE_START], + [DOM_ATTR_NAME.SOURCE_LINE_END]: attrs[DOM_ATTR_NAME.SOURCE_LINE_END], + } + + delete attrs[DOM_ATTR_NAME.SOURCE_LINE_START] + delete attrs[DOM_ATTR_NAME.SOURCE_LINE_END] + + return createVNode( + 'pre', + preAttrs, + [createVNode('code', { key: highlighted, ...attrs, innerHTML: highlighted }, [])] + ) + } // If language exists, inject class gently, without modifying original token. // May be, one day we will add .deepClone() for token and simplify this part, but @@ -272,9 +292,5 @@ export default { md.renderer.renderAttrs = renderAttrs md.renderer.renderToken = renderToken }) - - ctx.registerHook('VIEW_ON_GET_HTML_FILTER_NODE', ({ node }) => { - node.classList.remove('source-line') - }) } } as Plugin diff --git a/src/renderer/plugins/switch-todo.ts b/src/renderer/plugins/switch-todo.ts index 2c1014d9..08d5aacb 100644 --- a/src/renderer/plugins/switch-todo.ts +++ b/src/renderer/plugins/switch-todo.ts @@ -39,7 +39,7 @@ export default { return true } - if (target.tagName === 'INPUT' && target.parentElement!.classList.contains('source-line')) { + if (target.tagName === 'INPUT' && target.parentElement!.dataset.sourceLine) { const line = parseInt(target.parentElement!.dataset.sourceLine || '0') const checked = (target as HTMLInputElement).checked ctx.action.getActionHandler(actionName)(line, checked) diff --git a/src/renderer/plugins/sync-scroll.ts b/src/renderer/plugins/sync-scroll.ts index 7a33b414..3cdf0c0d 100644 --- a/src/renderer/plugins/sync-scroll.ts +++ b/src/renderer/plugins/sync-scroll.ts @@ -66,7 +66,7 @@ export default { if ( ctx.store.state.showEditor && !ctx.store.state.presentation && - target.classList.contains('source-line') && + target.dataset.sourceLine && window.getSelection()!.toString().length < 1 ) { ctx.view.disableSyncScrollAwhile(() => { diff --git a/src/renderer/support/constant.ts b/src/renderer/support/constant.ts new file mode 100644 index 00000000..f50fdaa1 --- /dev/null +++ b/src/renderer/support/constant.ts @@ -0,0 +1,11 @@ +export const DOM_ATTR_NAME = { + SOURCE_LINE_START: 'data-source-line', + SOURCE_LINE_END: 'data-source-line-end', + ORIGIN_SRC: 'origin-src', + ORIGIN_HREF: 'origin-href', + LOCAL_IMAGE: 'local-image', +} + +export const DOM_CLASS_NAME = { + MARK_OPEN: 'open' +}