// ==UserScript== // @name 4d4y Markdown Enhancer // @namespace http://tampermonkey.net/ // @version 2.5 // @description Convert potential Markdown syntax into HTML in 4d4y forum posts without removing existing HTML elements. Toggle original text with Ctrl+M, with a mode switch notification. // @match https://www.4d4y.com/forum/* // @author 屋大维 + ChatGPT // @license MIT // @grant none // @run-at document-end // @downloadURL https://update.greasyfork.icu/scripts/526144/4d4y%20Markdown%20Enhancer.user.js // @updateURL https://update.greasyfork.icu/scripts/526144/4d4y%20Markdown%20Enhancer.meta.js // ==/UserScript== (function () { "use strict"; window.copyCode = function (button) { let codeElement = button.parentElement.querySelector("pre code"); if (!codeElement) return; let text = codeElement.innerText; navigator.clipboard.writeText(text).then(() => { button.innerText = "已复制!"; setTimeout(() => (button.innerText = "复制"), 1500); }); }; function markdownToHtml(md) { if (!md) return ""; let blocks = {}; let blockIndex = 0; // **1. 处理带语言标签的代码块** md = md.replace(/```(\w+)\s*
\s*([\s\S]*?)```/g, (match, lang, code) => { let placeholder = `%%CODE${blockIndex}%%`; let cleanCode = code.replace(//g, "").trim(); let langLabel = `
${lang}
`; let copyButton = ``; blocks[placeholder] = `
${langLabel} ${copyButton}
${cleanCode.replace(//g, ">")}
`; blockIndex++; return placeholder; }); // **2. 处理普通代码块** md = md.replace(/```([\s\S]*?)```/g, (match, code) => { let placeholder = `%%CODE${blockIndex}%%`; let cleanCode = code.replace(//g, "").trim(); let copyButton = ``; blocks[placeholder] = `
${copyButton}
${cleanCode.replace(//g, ">")}
`; blockIndex++; return placeholder; }); // **3. 还原 Markdown 形式的超链接** md = md.replace( /\[([^\]]+)\]\(]*>.*?<\/a>\)/g, "[$1]($2)", ); // **4. 处理标题** md = md .replace(/^### (.*$)/gm, "

$1

") .replace(/^## (.*$)/gm, "

$1

") .replace(/^# (.*$)/gm, "

$1

"); // **5. 处理加粗、斜体** md = md .replace(/\*\*(.*?)\*\*/g, "$1") .replace(/\*(.*?)\*/g, "$1"); // **6. 解析 Markdown 列表** md = processLists(md); // **7. 处理行内代码** md = md.replace( /`([^`]+)`/g, `$1`, ); // **8. 恢复代码块** Object.keys(blocks).forEach((placeholder) => { md = md.replace(placeholder, blocks[placeholder]); }); // **9. 还原 Markdown 超链接为标准 HTML `
`** // 还原 Markdown 超链接,支持各种协议(http, https, chrome-extension, file, mailto 等) md = md.replace( /\[([^\[\]]+)\]\(\s*(([a-zA-Z][a-zA-Z\d+\-.]*):\/\/[^\s)]+)\s*\)/g, '$1', ); return md; } function processLists(md) { if (!md) return ""; let lines = md.split("\n"); let output = []; let prevWasNewList = true; lines.forEach((line) => { let isNewLine = line.trim() === "
"; if (isNewLine) { prevWasNewList = true; output.push(line); return; } let cleanedLine = line.replace(/
$/, ""); let spaces = (cleanedLine.match(/^(?: )+/) || [""])[0].length / 6; let reducedLine = cleanedLine.replace(/^(?: )+/, "").trim(); // 检查有序列表 (必须是整数 + 点 + 空格) let matchOrdered = reducedLine.match(/^(\d+)\.\s+(.+)$/); // 检查无序列表 (- 或 * 后跟空格) let matchUnordered = reducedLine.match(/^([-*])\s+(.+)$/); if (matchOrdered) { let number = matchOrdered[1]; let content = matchOrdered[2]; let marginLeft = spaces * 20; // 每级缩进 20px let listItem = `
${number}. ${content}
`; output.push(listItem); prevWasNewList = false; } else if (matchUnordered) { let bullet = matchUnordered[1] === "-" ? "•" : "◦"; // 使用不同符号区分 - 和 * let content = matchUnordered[2]; let marginLeft = spaces * 20; let listItem = `
${bullet} ${content}
`; output.push(listItem); prevWasNewList = false; } else { output.push(line); prevWasNewList = false; } }); return output.join("\n"); } function processForumPosts() { document.querySelectorAll("td.t_msgfont").forEach((td) => { if (!td.dataset.processed) { let originalDiv = document.createElement("div"); let markdownDiv = document.createElement("div"); originalDiv.innerHTML = td.innerHTML; markdownDiv.innerHTML = markdownToHtml(td.innerHTML); markdownDiv.style.display = "block"; originalDiv.style.display = "none"; td.innerHTML = ""; td.appendChild(markdownDiv); td.appendChild(originalDiv); td.dataset.processed = "true"; td.dataset.toggled = "true"; // **默认 Markdown 模式** } }); } function toggleMarkdown(showNotification = true) { document.querySelectorAll("td.t_msgfont").forEach((td) => { if (td.dataset.processed) { let markdownDiv = td.children[0]; let originalDiv = td.children[1]; if (td.dataset.toggled === "true") { markdownDiv.style.display = "none"; originalDiv.style.display = "block"; td.dataset.toggled = "false"; if (showNotification) showToggleNotification("原始文本模式已启用"); } else { markdownDiv.style.display = "block"; originalDiv.style.display = "none"; td.dataset.toggled = "true"; if (showNotification) showToggleNotification("Markdown 模式已启用"); } } }); } function showToggleNotification(message) { let notification = document.createElement("div"); notification.textContent = message; notification.style.position = "fixed"; notification.style.top = "10px"; notification.style.left = "50%"; notification.style.transform = "translateX(-50%)"; notification.style.padding = "10px 20px"; notification.style.backgroundColor = "black"; notification.style.color = "white"; notification.style.fontSize = "16px"; notification.style.borderRadius = "5px"; notification.style.zIndex = "1000"; notification.style.opacity = "1"; notification.style.transition = "opacity 1s ease-in-out"; document.body.appendChild(notification); setTimeout(() => { notification.style.opacity = "0"; setTimeout(() => document.body.removeChild(notification), 1000); }, 2000); } function setupKeyboardShortcut() { document.addEventListener("keydown", function (event) { if (event.ctrlKey && event.key === "m") { toggleMarkdown(true); // **按 Ctrl+M 时,一定要弹出通知** event.preventDefault(); } }); } window.addEventListener("load", () => { processForumPosts(); // **默认 Markdown 模式** setupKeyboardShortcut(); }); })();