// ==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();
});
})();