mirror of
https://github.com/purocean/yn.git
synced 2026-09-24 15:46:02 +08:00
feat: improve handling of wiki links and anchors in markdown rendering
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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) || '')
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user