diff --git a/registry/lib/components/utils/active-video-links/index.ts b/registry/lib/components/utils/active-video-links/index.ts
index 42bac6ac2..013040ac2 100644
--- a/registry/lib/components/utils/active-video-links/index.ts
+++ b/registry/lib/components/utils/active-video-links/index.ts
@@ -18,47 +18,85 @@ const processAcgLinks = () => {
})
}
-const isTextInsideLink = (html: string, position: number): boolean => {
- const beforeText = html.substring(0, position)
- const lastOpenTag = beforeText.lastIndexOf('')
- return lastOpenTag > lastCloseTag
-}
-
const processDescLinks = () => {
const descContainer = document.querySelector('.desc-info-text')
if (!descContainer) {
return
}
-
- const content = descContainer.innerHTML
- let newContent = content
-
- const matches = [...content.matchAll(webRegex)]
- for (let i = matches.length - 1; i >= 0; i--) {
- const match = matches[i]
- const matchText = match[0]
- const startIndex = match.index ?? 0
-
- if (isTextInsideLink(content, startIndex)) {
- continue
- }
-
- const link = matchText.replace(/^https?:\/\//, '//').replace(/^www\./, '//')
- const replacement = `${matchText}`
-
- newContent =
- newContent.substring(0, startIndex) +
- replacement +
- newContent.substring(startIndex + matchText.length)
+ const walker = document.createTreeWalker(descContainer, NodeFilter.SHOW_TEXT, {
+ acceptNode: node => {
+ if (node.parentElement?.closest('a')) {
+ return NodeFilter.FILTER_REJECT
+ }
+ return webRegex.test(node.textContent || '')
+ ? NodeFilter.FILTER_ACCEPT
+ : NodeFilter.FILTER_REJECT
+ },
+ })
+ const textNodes: Text[] = []
+ let currentNode: Node | null = walker.nextNode()
+ while (currentNode !== null) {
+ textNodes.push(currentNode as Text)
+ currentNode = walker.nextNode()
}
+ textNodes.forEach(textNode => {
+ const frag = document.createDocumentFragment()
+ const text = textNode.textContent || ''
+ let lastIndex = 0
+ let match: RegExpExecArray | null
+ webRegex.lastIndex = 0
+ match = webRegex.exec(text)
+ while (match !== null) {
+ const start = match.index
+ if (lastIndex < start) {
+ frag.appendChild(document.createTextNode(text.slice(lastIndex, start)))
+ }
+ const urlText = match[0]
+ const linkHref = urlText.replace(/^https?:\/\//, '//').replace(/^www\./, '//')
+ const a = document.createElement('a')
+ a.href = linkHref
+ a.target = '_blank'
+ a.textContent = urlText
+ frag.appendChild(a)
+ lastIndex = start + urlText.length
+ match = webRegex.exec(text)
+ }
+ if (lastIndex < text.length) {
+ frag.appendChild(document.createTextNode(text.slice(lastIndex)))
+ }
+ textNode.parentNode?.replaceChild(frag, textNode)
+ })
+}
- if (newContent !== content) {
- descContainer.innerHTML = newContent
+const normalizeNicoDescLinks = () => {
+ const descContainer = document.querySelector('.desc-info-text')
+ if (!descContainer) {
+ return
+ }
+ const anchors = Array.from(descContainer.querySelectorAll('a'))
+ for (let i = 0; i < anchors.length - 1; i++) {
+ const a1 = anchors[i]
+ const a2 = anchors[i + 1]
+ const href1 = a1.getAttribute('href') || ''
+ const text2 = a2.textContent?.trim() || ''
+ const href2 = a2.getAttribute('href') || ''
+ // first link is base watch URL, second has sm ID text
+ if (
+ /^(https?:)?\/\/(www\.)?nicovideo\.jp\/watch\/?$/.test(href1) &&
+ /^sm\d+$/.test(text2) &&
+ href2.includes(`/watch/${text2}`)
+ ) {
+ const newHref = href2.replace(/^(https?:)?/, '//')
+ const newA = document.createElement('a')
+ newA.href = newHref
+ newA.target = '_blank'
+ newA.textContent = text2
+ a1.replaceWith(newA)
+ a2.remove()
+ console.log(`Merged Nico link: ${text2}`)
+ // skip the next element
+ i++
+ }
}
}
@@ -66,6 +104,7 @@ const processLinks = () => {
try {
processAcgLinks()
processDescLinks()
+ normalizeNicoDescLinks()
} catch (error) {
console.error('处理链接时遇到 Error:', error)
}