From 6893b4061d07c838b7f6aaf5a3a19cebc86f1e68 Mon Sep 17 00:00:00 2001 From: purocean Date: Wed, 19 Jun 2024 01:02:30 +0800 Subject: [PATCH] feat: improve handling of wiki links and anchors in markdown rendering --- .../components/DefaultPreviewerRender.ce.vue | 6 ++- src/renderer/plugins/markdown-link.ts | 50 +++++++++++-------- src/renderer/plugins/markdown-wiki-links.ts | 19 ++++--- src/renderer/support/args.ts | 1 + 4 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/renderer/components/DefaultPreviewerRender.ce.vue b/src/renderer/components/DefaultPreviewerRender.ce.vue index 9c0d180d..c8c63e30 100644 --- a/src/renderer/components/DefaultPreviewerRender.ce.vue +++ b/src/renderer/components/DefaultPreviewerRender.ce.vue @@ -340,7 +340,8 @@ body.find-in-preview-highlight ::selection { } } - a:not([href^="#fn"])[href^="#"]:after { + a:not([href^="#fn"])[href^="#"]:after, + a[is-anchor]:after { content: '\200D\2002'; padding: 2px; background-repeat: no-repeat; @@ -359,7 +360,8 @@ body.find-in-preview-highlight ::selection { a[href$=".md"], a[href*=".md:"], - a[href*=".md#"] { + a[href*=".md#"], + a:not([is-anchor])[wiki-link] { &:after { content: '\200D\2002'; padding: 1px; diff --git a/src/renderer/plugins/markdown-link.ts b/src/renderer/plugins/markdown-link.ts index 168ca373..a0051713 100644 --- a/src/renderer/plugins/markdown-link.ts +++ b/src/renderer/plugins/markdown-link.ts @@ -30,9 +30,9 @@ async function getElement (id: string) { return _find(id) || _find(id.toUpperCase()) } -async function getFirstMatchPath (repo: string, dir: string, path: string) { - if (path.includes('/')) { - return path +async function getFirstMatchPath (repo: string, dir: string, fileName: string) { + if (fileName.includes('/')) { + return fileName } const findInDir = (items: Components.Tree.Node[]): string | null => { @@ -40,8 +40,8 @@ async function getFirstMatchPath (repo: string, dir: string, path: string) { const p = normalizeSep(item.path) if ( item.type === 'file' && - (p === normalizeSep(join(dir, path)) || - p === normalizeSep(join(dir, `${path}.md`))) + (p === normalizeSep(join(dir, fileName)) || + p === normalizeSep(join(dir, `${fileName}.md`))) ) { return item.path } @@ -59,7 +59,7 @@ async function getFirstMatchPath (repo: string, dir: string, path: string) { const findByName = (items: Components.Tree.Node[]): string | null => { for (const item of items) { - if (item.type === 'file' && (item.name === path || item.name === `${path}.md`)) { + if (item.type === 'file' && (item.name === fileName || item.name === `${fileName}.md`)) { return item.path } @@ -167,13 +167,17 @@ function handleLink (link: HTMLAnchorElement): boolean { return { pos, path } } + const isWikiLink = !!link.getAttribute(DOM_ATTR_NAME.WIKI_LINK) + const _switchDoc = async () => { let { path, pos } = parsePathPos(normalizeSep(tmp[0])) const dir = dirname(filePath || '') - // wiki link - if (link.getAttribute(DOM_ATTR_NAME.WIKI_LINK)) { - path = (await getFirstMatchPath(fileRepo, dir, path)) || path + if (isWikiLink) { + path = normalizeSep(path) + path = path.replace(/:/g, '/') // replace all ':' to '/' + path = await getFirstMatchPath(fileRepo, dir, path) || path + path = path.endsWith('.md') ? path : `${path}.md` } if (!path.startsWith('/')) { // to absolute path @@ -184,17 +188,7 @@ function handleLink (link: HTMLAnchorElement): boolean { return switchDoc(file).then(() => { pos && setPosition(pos[0], pos[1]) - }) - } - - const path = normalizeSep(tmp[0]) - const file: Doc = { path, type: 'file', name: basename(path), repo: fileRepo } - - const isMarkdownFile = /(\.md$|\.md#|\.md:)/.test(href) - const supportOpenDirectly = isMarkdownFile || getAllCustomEditors().some(x => x.when?.({ doc: file })) - - if (supportOpenDirectly) { - _switchDoc().then(async () => { + }).then(async () => { const hash = tmp.slice(1).join('#') // jump anchor if (hash) { @@ -212,7 +206,16 @@ function handleLink (link: HTMLAnchorElement): boolean { } } }) + } + const path = normalizeSep(tmp[0]) + const file: Doc = { path, type: 'file', name: basename(path), repo: fileRepo } + + const isMarkdownFile = /(\.md$|\.md#|\.md:)/.test(href) + const supportOpenDirectly = isMarkdownFile || getAllCustomEditors().some(x => x.when?.({ doc: file })) + + if (supportOpenDirectly) { + _switchDoc() return true } else if (href && href.startsWith('#')) { // for anchor getElement(href.replace(/^#/, '')).then(el => { @@ -223,6 +226,9 @@ function handleLink (link: HTMLAnchorElement): boolean { const { pos } = parsePathPos(href) pos && setPosition(pos[0], pos[1]) return true + } else if (isWikiLink) { + _switchDoc() + return true } else { return false } @@ -238,6 +244,10 @@ function convertLink (state: StateCore) { } const link = (token: Token) => { + if (token.attrGet(DOM_ATTR_NAME.WIKI_LINK)) { + return + } + const isAnchor = token.tag === 'a' const attrName = isAnchor ? 'href' : 'src' const attrVal = decodeURIComponent(token.attrGet(attrName) || '') diff --git a/src/renderer/plugins/markdown-wiki-links.ts b/src/renderer/plugins/markdown-wiki-links.ts index 3990e485..b606776a 100644 --- a/src/renderer/plugins/markdown-wiki-links.ts +++ b/src/renderer/plugins/markdown-wiki-links.ts @@ -3,7 +3,6 @@ import type StateInline from 'markdown-it/lib/rules_inline/state_inline' const reMatch = /^([^[#|]*)?(?:#([^#|]*))?(?:\|([^\]]*))?$/ const reExternalLink = /^[a-zA-Z]{1,8}:\/\/.*/ -const reExtName = /\.[^/]+$/ const rePos = /(:\d{1,6},?\d{0,6})$/ function wikiLinks (state: StateInline, silent?: boolean) { @@ -31,6 +30,7 @@ function wikiLinks (state: StateInline, silent?: boolean) { let url = link + hashStr let text = label || url + let isAnchor = false // internal link if (!reExternalLink.test(link)) { @@ -44,17 +44,24 @@ function wikiLinks (state: StateInline, silent?: boolean) { const fileName = link.split('/').pop() text = label || (fileName ? (fileName + posStr + hashStr) : url) - // no extension name - if (link && !reExtName.test(link)) { - url = `${link}.md${posStr}${hashStr}` - } else if (!link) { + if (!link) { url = posStr + hashStr text = label || hash || url + isAnchor = true } } if (!silent) { - state.push('link_open', 'a', 1).attrs = [['href', url], [ctx.args.DOM_ATTR_NAME.WIKI_LINK, 'true']] + const attrs: [string, string][] = [ + ['href', url], + [ctx.args.DOM_ATTR_NAME.WIKI_LINK, 'true'], + ] + + if (isAnchor) { + attrs.push([ctx.args.DOM_ATTR_NAME.IS_ANCHOR, 'true']) + } + + state.push('link_open', 'a', 1).attrs = attrs state.push('text', '', 0).content = text state.push('link_close', 'a', -1) } diff --git a/src/renderer/support/args.ts b/src/renderer/support/args.ts index 1c6582ec..a6ff41b5 100644 --- a/src/renderer/support/args.ts +++ b/src/renderer/support/args.ts @@ -39,6 +39,7 @@ export const DOM_ATTR_NAME = { TOKEN_IDX: 'data-token-idx', DISPLAY_NONE: 'display-none', WIKI_LINK: 'wiki-link', + IS_ANCHOR: 'is-anchor', } export const DOM_CLASS_NAME = {