// ==UserScript== // @name Get Markdown // @namespace http://tampermonkey.net/ // @version 2.0 // @description Select any element to view HTML or convert to Markdown with Turndown // @author zabk // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_addStyle // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @require https://cdn.jsdelivr.net/npm/turndown@7.2.0/dist/turndown.js // @run-at document-end // ==/UserScript== (function() { 'use strict'; // CSS for highlighting and overlay const STYLES = ` #element-selector-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.01); z-index: 2147483646; cursor: crosshair; display: none; pointer-events: auto; } #element-selector-overlay.active { display: block; } .element-highlight { outline: 3px dashed #00d1b2 !important; outline-offset: -3px !important; box-shadow: 0 0 0 4px rgba(0, 209, 178, 0.2) !important; transition: none !important; } #element-selector-hint { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: #333; color: #fff; padding: 12px 24px; border-radius: 8px; font-size: 14px; z-index: 2147483647; box-shadow: 0 4px 12px rgba(0,0,0,0.3); display: none; max-width: 90vw; text-align: center; pointer-events: none; } #element-selector-hint.active { display: block; animation: slideDown 0.2s ease; } @keyframes slideDown { from { opacity: 0; transform: translate(-50%, -20px); } to { opacity: 1; transform: translate(-50%, 0); } } .swal2-html-container { text-align: left !important; max-height: 60vh !important; overflow: auto !important; } .swal2-html-container pre, .swal2-html-container textarea { margin: 0; font-family: 'Consolas', 'Monaco', monospace; font-size: 12px; white-space: pre-wrap; word-break: break-all; background: #f6f8fa; padding: 12px; border-radius: 6px; border: 1px solid #e1e4e8; width: 100%; box-sizing: border-box; min-height: 200px; resize: vertical; } .swal2-html-container textarea { font-family: 'Consolas', 'Monaco', monospace; } .md-preview { background: #fff; padding: 12px; border-radius: 6px; border: 1px solid #e1e4e8; margin-top: 10px; max-height: 300px; overflow: auto; } .swal2-actions { flex-wrap: wrap !important; gap: 8px !important; } .swal2-actions button { margin: 2px !important; } `; let isActive = false; let currentHighlight = null; let overlay, hint; let turndownService = null; GM_addStyle(STYLES); // Initialize Turndown with common options function initTurndown() { if (typeof TurndownService === 'undefined') return null; const td = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced', fence: '```', emDelimiter: '*', bulletListMarker: '-', strongDelimiter: '**', linkStyle: 'inlined' }); // 🗑️ REMOVE style, script, noscript, iframe, etc. td.addRule('remove-unwanted', { filter: ['style', 'script', 'noscript', 'iframe', 'object', 'embed', 'canvas', 'svg'], replacement: () => '' }); // 🗑️ Remove elements with hidden/invisible attributes td.addRule('remove-hidden', { filter: (node) => { if (node.nodeType !== 1) return false; // Skip if hidden attribute or aria-hidden if (node.hasAttribute('hidden')) return true; if (node.getAttribute('aria-hidden') === 'true') return true; // Skip if style contains display:none or visibility:hidden const style = node.getAttribute('style') || ''; if (/display\s*:\s*none|visibility\s*:\s*hidden/i.test(style)) return true; return false; }, replacement: () => '' }); // ✅ Preserve
blocks properly
td.addRule('preCode', {
filter: (node) => node.nodeName === 'PRE' && node.querySelector('code'),
replacement: (content, node) => {
const code = node.querySelector('code');
const lang = code.className?.match(/language-(\w+)/)?.[1] || '';
return `\n\`\`\`${lang}\n${code.textContent}\n\`\`\`\n\n`;
}
});
// ✅ Handle images with alt text
td.addRule('img', {
filter: 'img',
replacement: (content, node) => {
const alt = node.alt || '';
const src = node.src || '';
const title = node.title ? ` "${node.title}"` : '';
return src ? `` : '';
}
});
return td;
}
function createUI() {
overlay = document.createElement('div');
overlay.id = 'element-selector-overlay';
document.documentElement.appendChild(overlay);
hint = document.createElement('div');
hint.id = 'element-selector-hint';
hint.textContent = '🔍 Hover to highlight • Click to select • Press ESC to cancel';
document.documentElement.appendChild(hint);
overlay.addEventListener('click', handleClick, true);
overlay.addEventListener('mousemove', handleMouseMove, true);
overlay.addEventListener('mouseleave', clearHighlight, true);
document.addEventListener('keydown', handleKeydown, true);
}
function handleKeydown(e) {
if (e.key === 'Escape' && isActive) {
e.preventDefault();
e.stopPropagation();
deactivate();
}
}
function handleMouseMove(e) {
e.preventDefault();
e.stopPropagation();
overlay.style.pointerEvents = 'none';
const target = document.elementFromPoint(e.clientX, e.clientY);
overlay.style.pointerEvents = 'auto';
if (target && isValidElement(target)) highlightElement(target);
}
function handleClick(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
overlay.style.pointerEvents = 'none';
const target = document.elementFromPoint(e.clientX, e.clientY);
overlay.style.pointerEvents = 'auto';
if (target && isValidElement(target)) selectElement(target);
}
function isValidElement(el) {
if (!el || el.nodeType !== 1) return false;
if (el.id === 'element-selector-overlay' || el.id === 'element-selector-hint') return false;
if (el.closest?.('#element-selector-overlay, #element-selector-hint')) return false;
return true;
}
function highlightElement(el) {
clearHighlight();
if (el && isValidElement(el)) {
if (el.classList) {
el.classList.add('element-highlight');
} else {
el.setAttribute('data-element-highlight', 'true');
el.style.outline = '3px dashed #00d1b2';
el.style.boxShadow = '0 0 0 4px rgba(0, 209, 178, 0.2)';
}
currentHighlight = el;
}
}
function clearHighlight() {
if (currentHighlight) {
if (currentHighlight.classList) {
currentHighlight.classList.remove('element-highlight');
} else {
currentHighlight.removeAttribute('data-element-highlight');
currentHighlight.style.outline = '';
currentHighlight.style.boxShadow = '';
}
currentHighlight = null;
}
}
function formatHTML(html) {
let formatted = '';
let indent = 0;
const tab = ' ';
const tokens = html.split(/(<\/?[^>]+>|)/g).filter(t => t.trim());
for (let token of tokens) {
const trimmed = token.trim();
if (!trimmed) continue;
if (trimmed.startsWith('')) {
indent = Math.max(0, indent - 1);
formatted += tab.repeat(indent) + trimmed + '\n';
} else if (trimmed.startsWith('<') && (trimmed.endsWith('/>') || isVoidElement(trimmed))) {
formatted += tab.repeat(indent) + trimmed + '\n';
} else if (trimmed.startsWith('<')) {
formatted += tab.repeat(indent) + trimmed + '\n';
if (!trimmed.endsWith('/>') && !isVoidElement(trimmed)) indent++;
} else {
const content = trimmed;
if (content && !/^\s*$/.test(content)) {
formatted += tab.repeat(indent) + escapeHtml(content) + '\n';
}
}
}
return formatted.trim();
}
function isVoidElement(tag) {
const voidEls = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
const match = tag.match(/<(\w+)/i);
return match && voidEls.includes(match[1].toLowerCase());
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function copyToClipboard(text, successMsg = '📋 Copied!', failMsg = '❌ Copy failed') {
return navigator.clipboard.writeText(text)
.then(() => Swal.showValidationMessage(successMsg))
.catch(() => Swal.showValidationMessage(failMsg));
}
function selectElement(el) {
if (!el || !isValidElement(el)) return;
deactivate();
try {
const html = el.outerHTML;
const tagName = el.tagName.toLowerCase();
const id = el.id ? `#${el.id}` : '';
const className = el.className && typeof el.className === 'string'
? el.className.trim().split(/\s+/)[0] : null;
const classSelector = className ? `.${className}` : '';
const formattedHTML = formatHTML(html);
// Lazy init Turndown
if (turndownService === null) {
turndownService = initTurndown();
}
Swal.fire({
title: `<${tagName}${id}${classSelector}>`,
html: `
📄 Raw HTML
${escapeHtml(formattedHTML)}
`,
width: '750px',
showConfirmButton: true,
confirmButtonText: '📋 Copy HTML',
showCancelButton: true,
cancelButtonText: '✕ Close',
showDenyButton: true,
denyButtonText: '🔄 Convert to Markdown',
customClass: {
htmlContainer: 'swal2-monospace'
},
preConfirm: () => {
const htmlContent = document.getElementById('swal-html-content')?.textContent || html;
return copyToClipboard(htmlContent);
},
preDeny: () => {
if (!turndownService) {
Swal.showValidationMessage('⚠️ Turndown not loaded');
return false;
}
try {
const md = turndownService.turndown(html);
console.log(md);
Swal.fire({html:``})
} catch (err) {
console.error('[Turndown Error]', err);
Swal.showValidationMessage('❌ Conversion failed: ' + err.message);
return false;
}
return false; // Keep dialog open
}
});
// Add toggle back to HTML after MD conversion
Swal.getDenyButton()?.addEventListener('click', () => {
if (Swal.getDenyButton()?.textContent?.includes('🔙')) {
const mdSection = document.getElementById('swal-md-section');
if (mdSection) mdSection.style.display = 'none';
Swal.update({
confirmButtonText: '📋 Copy HTML',
denyButtonText: '🔄 Convert to Markdown',
preConfirm: () => {
const htmlContent = document.getElementById('swal-html-content')?.textContent || html;
return copyToClipboard(htmlContent);
}
});
}
});
} catch (err) {
console.error('[Element Viewer] Error:', err);
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Could not process element: ' + err.message
});
}
}
function activate() {
if (isActive) return;
isActive = true;
if (!overlay) createUI();
overlay.classList.add('active');
hint.classList.add('active');
document.documentElement.dataset.originalOverflow = document.documentElement.style.overflow;
document.documentElement.style.overflow = 'hidden';
}
function deactivate() {
if (!isActive) return;
isActive = false;
clearHighlight();
if (overlay) overlay.classList.remove('active');
if (hint) hint.classList.remove('active');
if (document.documentElement.dataset.originalOverflow !== undefined) {
document.documentElement.style.overflow = document.documentElement.dataset.originalOverflow;
delete document.documentElement.dataset.originalOverflow;
}
}
function toggleSelector() {
isActive ? deactivate() : activate();
}
GM_registerMenuCommand('🔍 Select Element (HTML/MD)', toggleSelector, 'S');
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'e' && !isActive) {
e.preventDefault();
toggleSelector();
}
}, true);
window.addEventListener('beforeunload', () => deactivate());
})();