mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-19 21:41:03 +08:00
152 lines
4.5 KiB
JavaScript
152 lines
4.5 KiB
JavaScript
import hljs from 'highlight.js/lib/common'
|
|
|
|
if (typeof window !== 'undefined') {
|
|
const theme =
|
|
document.documentElement.dataset.theme ||
|
|
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
|
if (theme === 'dark') {
|
|
import('highlight.js/styles/atom-one-dark.css')
|
|
} else {
|
|
import('highlight.js/styles/atom-one-light.css')
|
|
}
|
|
}
|
|
import MarkdownIt from 'markdown-it'
|
|
import { toast } from '../main'
|
|
import { tiebaEmoji } from './tiebaEmoji'
|
|
|
|
function mentionPlugin(md) {
|
|
const mentionReg = /^@\[([^\]]+)\]/
|
|
function mention(state, silent) {
|
|
const pos = state.pos
|
|
if (state.src.charCodeAt(pos) !== 0x40) return false
|
|
const match = mentionReg.exec(state.src.slice(pos))
|
|
if (!match) return false
|
|
if (!silent) {
|
|
const tokenOpen = state.push('link_open', 'a', 1)
|
|
tokenOpen.attrs = [
|
|
['href', `/users/${match[1]}`],
|
|
['target', '_blank'],
|
|
['class', 'mention-link'],
|
|
]
|
|
const text = state.push('text', '', 0)
|
|
text.content = `@${match[1]}`
|
|
state.push('link_close', 'a', -1)
|
|
}
|
|
state.pos += match[0].length
|
|
return true
|
|
}
|
|
md.inline.ruler.before('emphasis', 'mention', mention)
|
|
}
|
|
|
|
function tiebaEmojiPlugin(md) {
|
|
md.renderer.rules['tieba-emoji'] = (tokens, idx) => {
|
|
const name = tokens[idx].content
|
|
const file = tiebaEmoji[name]
|
|
return `<img class="emoji" src="${file}" alt="${name}">`
|
|
}
|
|
md.inline.ruler.before('emphasis', 'tieba-emoji', (state, silent) => {
|
|
const pos = state.pos
|
|
if (state.src.charCodeAt(pos) !== 0x3a) return false
|
|
const match = state.src.slice(pos).match(/^:tieba(\d+):/)
|
|
if (!match) return false
|
|
const key = `tieba${match[1]}`
|
|
if (!tiebaEmoji[key]) return false
|
|
if (!silent) {
|
|
const token = state.push('tieba-emoji', '', 0)
|
|
token.content = key
|
|
}
|
|
state.pos += match[0].length
|
|
return true
|
|
})
|
|
}
|
|
|
|
// 链接在新窗口打开
|
|
function linkPlugin(md) {
|
|
const defaultRender =
|
|
md.renderer.rules.link_open ||
|
|
function (tokens, idx, options, env, self) {
|
|
return self.renderToken(tokens, idx, options)
|
|
}
|
|
|
|
md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
|
|
const token = tokens[idx]
|
|
const hrefIndex = token.attrIndex('href')
|
|
|
|
if (hrefIndex >= 0) {
|
|
const href = token.attrs[hrefIndex][1]
|
|
// 如果是外部链接,添加 target="_blank" 和 rel="noopener noreferrer"
|
|
if (href.startsWith('http://') || href.startsWith('https://')) {
|
|
token.attrPush(['target', '_blank'])
|
|
token.attrPush(['rel', 'noopener noreferrer'])
|
|
}
|
|
}
|
|
|
|
return defaultRender(tokens, idx, options, env, self)
|
|
}
|
|
}
|
|
|
|
const md = new MarkdownIt({
|
|
html: false,
|
|
linkify: true,
|
|
breaks: true,
|
|
highlight: (str, lang) => {
|
|
let code = ''
|
|
if (lang && hljs.getLanguage(lang)) {
|
|
code = hljs.highlight(str, { language: lang, ignoreIllegals: true }).value
|
|
} else {
|
|
code = hljs.highlightAuto(str).value
|
|
}
|
|
const lineNumbers = code
|
|
.trim()
|
|
.split('\n')
|
|
.map(() => `<div class="line-number"></div>`)
|
|
return `<pre class="code-block"><button class="copy-code-btn">Copy</button><div class="line-numbers">${lineNumbers.join('')}</div><code class="hljs language-${lang || ''}">${code.trim()}</code></pre>`
|
|
},
|
|
})
|
|
|
|
md.use(mentionPlugin)
|
|
md.use(tiebaEmojiPlugin)
|
|
md.use(linkPlugin) // 添加链接插件
|
|
|
|
export function renderMarkdown(text) {
|
|
return md.render(text || '')
|
|
}
|
|
|
|
export function handleMarkdownClick(e) {
|
|
if (e.target.classList.contains('copy-code-btn')) {
|
|
const pre = e.target.closest('pre')
|
|
const codeEl = pre && pre.querySelector('code')
|
|
if (codeEl) {
|
|
navigator.clipboard.writeText(codeEl.innerText).then(() => {
|
|
toast.success('已复制')
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
export function stripMarkdown(text) {
|
|
const html = md.render(text || '')
|
|
|
|
// 统一使用正则表达式方法,确保服务端和客户端行为一致
|
|
let plainText = html.replace(/<[^>]+>/g, '')
|
|
|
|
// 标准化空白字符处理
|
|
plainText = plainText
|
|
.replace(/\r\n/g, '\n') // Windows换行符转为Unix格式
|
|
.replace(/\r/g, '\n') // 旧Mac换行符转为Unix格式
|
|
.replace(/[ \t]+/g, ' ') // 合并空格和制表符为单个空格
|
|
.replace(/\n{3,}/g, '\n\n') // 最多保留两个连续换行(一个空行)
|
|
.trim()
|
|
|
|
return plainText
|
|
}
|
|
|
|
export function stripMarkdownLength(text, length) {
|
|
const plain = stripMarkdown(text)
|
|
if (!length || plain.length <= length) {
|
|
return plain
|
|
}
|
|
// 截断并加省略号
|
|
return plain.slice(0, length) + '...'
|
|
}
|