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.
当前为
// ==UserScript==
// @name 4d4y Markdown Enhancer
// @namespace http://tampermonkey.net/
// @version 2.0
// @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
// ==/UserScript==
(function() {
'use strict';
function markdownToHtml(md) {
if (!md) return '';
let blocks = {};
let blockIndex = 0;
// **1. 保护 blockquote 和 blockcode**
md = md.replace(/<(blockquote|div class="blockcode")>[\s\S]*?<\/\1>/g, (match) => {
let placeholder = `%%BLOCK${blockIndex}%%`;
blocks[placeholder] = match;
blockIndex++;
return placeholder;
});
// **2. 还原 Markdown 形式的超链接**
md = md.replace(/\[([^\]]+)\]\(<a href="([^"]+)"[^>]*>.*?<\/a>\)/g, '[$1]($2)');
// **3. 处理标题**
md = md.replace(/^### (.*$)/gm, '<h3>$1</h3>')
.replace(/^## (.*$)/gm, '<h2>$1</h2>')
.replace(/^# (.*$)/gm, '<h1>$1</h1>');
// **4. 处理加粗、斜体、行内代码**
md = md.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/`(.*?)`/g, '<code>$1</code>');
// **5. 解析 Markdown 列表**
md = processLists(md);
// **6. 恢复 blockquote、blockcode**
Object.keys(blocks).forEach((placeholder) => {
md = md.replace(placeholder, blocks[placeholder]);
});
// **7. 还原 Markdown 超链接为标准 HTML `<a>`**
// 还原 Markdown 超链接,支持各种协议(http, https, chrome-extension, file, mailto 等)
md = md.replace(/\[([^\[\]]+)\]\(\s*(([a-zA-Z][a-zA-Z\d+\-.]*):\/\/[^\s)]+)\s*\)/g, '<a href="$2">$1</a>');
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() === '<br>';
if (isNewLine) {
prevWasNewList = true;
output.push(line);
return;
}
let cleanedLine = line.replace(/<br>$/, '');
let spaces = (cleanedLine.match(/^(?: )+/) || [''])[0].length / 6;
let reducedLine = cleanedLine.replace(/^(?: )+/, '').trim();
let matchListItem = reducedLine.match(/^(\d+\.)|(-) (.+)$/);
if (matchListItem) {
let number = matchListItem[1] || ''; // 有序列表编号
let dash = matchListItem[2] || ''; // 无序列表符号
let content = matchListItem[3] || reducedLine; // 列表项内容
if (number) {
content = content.replace(/^\d+\.\s*/, ''); // 删除已有编号,防止重复
}
let marker = number ? number : '•'; // 1. 或 •
let marginLeft = spaces * 20; // 每级缩进 20px
let listItem = `<div style="margin-left: ${marginLeft}px;"><span style="font-weight:bold;">${marker}</span> ${content}</div>`;
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();
});
})();