Greasy Fork is available in English.
Enhancing reading experience by highlighing 「dialogues」, 〈keywords〉, etc
// ==UserScript==
// @name Lightnovel Syntax Highlighting
// @name:en Lightnovel Syntax Highlighting
// @name:zh 轻小说语法高亮
// @namespace http://tampermonkey.net/
// @version 1.2.0
// @description Enhancing reading experience by highlighing 「dialogues」, 〈keywords〉, etc
// @description:en Enhancing reading experience by highlighing 「dialogues」, 〈keywords〉, etc
// @description:zh 通过语法高亮「对白」、〈关键词〉等内容增强阅读体验
// @author Boring3
// @match *://*.lightnovel.app/read/*
// @match *://*.masiro.me/admin/novelReading*
// @match *://*.wenku8.net/modules/article/reader.php*
// @match *://*.esjzone.cc/forum/*/*.html
// @match *://*.lightnovel.fun/detail/*
// @match *://*.syosetu.com/*/*
// @match *://*.linovelib.com/novel/*/*.html*
// @icon https://www.google.com/s2/favicons?domain=masiro.me
// @license GPLv3
// @run-at document-end
// @grant none
// ==/UserScript==
(function () {
'use strict';
const SELECTOR = [
".read", // 轻书架
".nvl-content", // 真白萌
"#contentmain>#content", // Wenku8
".forum-content", // ESJ
"#article-main-contents", // 轻国
".p-novel__body", // syosetu
"#TextContent", // 哔哩
].join(',');
const RULES = [
{
name: 'hlTalk',
color: '#ff4482',
pairs: [['「', '」'], ['“', '”']]
},
{
name: 'hlTalk2',
color: '#d36d33',
pairs: [['『', '』'], ['‘', '’']]
},
{
name: 'hlEnclosed',
color: '#33aa04',
pairs: [['(', ')'], ['(', ')'], ['[', ']'], ['[', ']']]
},
{
name: 'hlTitle',
color: '#dddd82',
pairs: [['《', '》'], ['〈', '〉']]
},
{
name: 'hlBrace',
color: '#cccc00',
pairs: [['【', '】'], ['〖', '〗'], ['〔', '〕']]
}
];
const styleContent = [
...RULES.map(r => `.${r.name} { color: ${r.color}; }`),
`hl-processed { display: none; }`,
].join('\n');
const styleEl = document.createElement('style');
styleEl.textContent = styleContent;
document.head.appendChild(styleEl);
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function hasProcessedMarker(el) {
if (!el) return false;
for (const child of el.children) {
if ((child.tagName || '').toLowerCase() === 'hl-processed') return true;
}
return false;
}
function addProcessedMarker(el) {
if (!el || hasProcessedMarker(el)) return;
const marker = document.createElement('hl-processed');
marker.setAttribute('aria-hidden', 'true');
el.appendChild(marker);
}
function highlightContent(contentEl) {
if (!contentEl || hasProcessedMarker(contentEl)) return;
let html = contentEl.innerHTML;
let hasReplacement = false;
RULES.forEach(rule => {
if (!rule.pairs || rule.pairs.length === 0) return;
const patterns = rule.pairs.map(([s, e]) => {
const start = escapeRegExp(s);
const end = escapeRegExp(e);
return `${start}(.*?)${end}`;
}).join('|');
const regex = new RegExp(patterns, "gi");
html = html.replace(regex, (match) => {
hasReplacement = true;
return `<span class="${rule.name}">${match}</span>`;
});
});
if (hasReplacement) {
contentEl.innerHTML = html;
addProcessedMarker(contentEl); // 通过子元素避免SPA局部更新问题
}
console.log("Lightnovel Highlighting: processed")
}
const observer = new MutationObserver(() => {
const contentEl = document.querySelector(SELECTOR);
if (contentEl && !hasProcessedMarker(contentEl)) {
highlightContent(contentEl);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
highlightContent(document.querySelector(SELECTOR));
})();