import { isMatch } from "./utils"; import { getWebfix, setWebfix } from "./storage"; import { apiFetch } from "../apis"; /** * 修复程序类型 */ const FIXER_BR = "br"; const FIXER_BN = "bn"; const FIXER_FONTSIZE = "fontSize"; /** * 需要修复的站点列表 * - pattern 匹配网址 * - selector 需要修复的选择器 * - rootSelector 需要监听的选择器,可留空 * - fixer 修复函数,可针对不同网址,选用不同修复函数 */ const DEFAULT_SITES = [ { pattern: "www.phoronix.com", selector: ".content", rootSelector: "", fixer: FIXER_BR, }, { pattern: "t.me/s/", selector: ".tgme_widget_message_text", rootSelector: ".tgme_channel_history", fixer: FIXER_BR, }, { pattern: "baidu.com", selector: "html", rootSelector: "", fixer: FIXER_FONTSIZE, }, { pattern: "chat.openai.com", selector: "div[data-testid^=conversation-turn] .items-start > div", rootSelector: "", fixer: FIXER_BN, }, ]; /** * 修复过的标记 */ const fixedSign = "kissfixed"; /** * 采用 `br` 换行网站的修复函数 * 目标是将 `br` 替换成 `p` * @param {*} node * @returns */ function brFixer(node) { if (node.hasAttribute(fixedSign)) { return; } node.setAttribute(fixedSign, "true"); var gapTags = ["BR", "WBR"]; var newlineTags = [ "DIV", "UL", "OL", "LI", "H1", "H2", "H3", "H4", "H5", "H6", "P", "HR", "PRE", "TABLE", ]; var html = ""; node.childNodes.forEach(function (child, index) { if (index === 0) { html += "
"; } if (gapTags.indexOf(child.nodeName) !== -1) { html += "
"; } else if (newlineTags.indexOf(child.nodeName) !== -1) { html += "
" + child.outerHTML + ""; } else if (child.outerHTML) { html += child.outerHTML; } else if (child.nodeValue) { html += child.nodeValue; } if (index === node.childNodes.length - 1) { html += "
"; } }); node.innerHTML = html; } /** * 目标是将 `\n` 替换成 `p` * @param {*} node * @returns */ function bnFixer(node) { if (node.hasAttribute(fixedSign)) { return; } node.setAttribute(fixedSign, "true"); const childs = node.childNodes; if (childs.length === 1 && childs[0].nodeName === "#text") { node.innerHTML = node.innerHTML .split("\n") .map((item) => `${item || " "}
`) .join(""); } } /** * 修复字体大小问题,如 baidu.com * @param {*} node */ function fontSizeFixer(node) { node.style.cssText += "font-size:1em;"; } /** * 修复程序映射 */ const fixerMap = { [FIXER_BR]: brFixer, [FIXER_BN]: bnFixer, [FIXER_FONTSIZE]: fontSizeFixer, }; /** * 查找、监听节点,并执行修复函数 * @param {*} selector * @param {*} fixer * @param {*} rootSelector */ function run(selector, fixer, rootSelector) { var mutaObserver = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { mutation.addedNodes.forEach(function (addNode) { addNode?.querySelectorAll(selector).forEach(fixer); }); }); }); var rootNodes = [document]; if (rootSelector) { rootNodes = document.querySelectorAll(rootSelector); } rootNodes.forEach(function (rootNode) { rootNode.querySelectorAll(selector).forEach(fixer); mutaObserver.observe(rootNode, { childList: true, subtree: true, }); }); } /** * 同步远程数据 * @param {*} url * @returns */ export const syncWebfix = async (url) => { const sites = await apiFetch(url); await setWebfix(url, sites); return sites; }; /** * 从缓存或远程加载修复站点 * @param {*} url * @returns */ export const loadOrFetchWebfix = async (url) => { try { let sites = await getWebfix(url); if (sites?.length) { return sites; } return syncWebfix(url); } catch (err) { console.log("[load webfix]", err.message); return DEFAULT_SITES; } }; /** * 匹配站点 */ export async function runWebfix({ injectWebfix }) { try { if (!injectWebfix) { return; } const href = document.location.href; const sites = await loadOrFetchWebfix(process.env.REACT_APP_WEBFIXURL); for (var i = 0; i < sites.length; i++) { var site = sites[i]; if (isMatch(href, site.pattern)) { if (fixerMap[site.fixer]) { run(site.selector, fixerMap[site.fixer], site.rootSelector); } break; } } } catch (err) { console.error(`[kiss-webfix]: ${err.message}`); } }