#!/usr/bin/env python3
# SPDX-FileCopyrightText: Chris Pressey, the creator of this work, has dedicated it to the public domain.
# For more information, please refer to <https://unlicense.org/>
# SPDX-License-Identifier: Unlicense
# Python script to create a JavaScript bookmarklet that converts the HTML contents
# of a pre-specified element to Markdown, and copies that to the clipboard.
import sys
JAVASCRIPT = r"""
function elemToMarkdown(elem) {
function processNode(node, listDepth = 0) {
if (node.nodeType === 3) return node.textContent;
if (node.nodeType !== 1) return '';
if (node.dataset.state === "closed") return '';
let tag = node.tagName.toLowerCase();
const indent = ' '.repeat(listDepth);
let result = '';
if (tag === 'div' && node.dataset.testid === 'user-message') tag = 'blockquote';
switch(tag) {
case 'h1': return '# ' + node.textContent + '\n\n';
case 'h2': return '## ' + node.textContent + '\n\n';
case 'h3': return '### ' + node.textContent + '\n\n';
case 'h4': return '#### ' + node.textContent + '\n\n';
case 'h5': return '##### ' + node.textContent + '\n\n';
case 'h6': return '###### ' + node.textContent + '\n\n';
case 'p': return processChildren(node, listDepth) + '\n\n';
case 'br': return '\n';
case 'strong': case 'b': return '**' + node.textContent + '**';
case 'em': case 'i': return '*' + node.textContent + '*';
case 'code': return '`' + node.textContent + '`';
case 'pre': return '```\n' + node.textContent + '\n```\n\n';
case 'a': return '[' + node.textContent + '](' + (node.href || '') + ')';
case 'ul':
for (const li of node.children) {
if (li.tagName.toLowerCase() === 'li') {
result += indent + '- ' + processChildren(li, listDepth + 1).trim() + '\n';
}
}
return result + (listDepth === 0 ? '\n' : '');
case 'ol':
let i = 1;
for (const li of node.children) {
if (li.tagName.toLowerCase() === 'li') {
result += indent + i++ + '. ' + processChildren(li, listDepth + 1).trim() + '\n';
}
}
return result + (listDepth === 0 ? '\n' : '');
case 'blockquote':
return '> ' + processChildren(node, listDepth).trim().replace(/\n/g, '\n> ') + '\n\n';
case 'table': {
const rows = [...node.querySelectorAll('tr')];
if (rows.length === 0) return '';
const cells = row => [...row.querySelectorAll('td, th')].map(c => processChildren(c, listDepth).trim().replace(/\|/g, '\\|'));
const firstRowCells = cells(rows[0]);
const isHeaderRow = row => [...row.querySelectorAll('th')].length > 0;
let headerCells, dataRows;
if (isHeaderRow(rows[0])) {
headerCells = firstRowCells;
dataRows = rows.slice(1);
} else {
headerCells = firstRowCells.map(() => '');
dataRows = rows;
}
const separator = headerCells.map(() => '---');
const toRow = cols => '| ' + cols.join(' | ') + ' |';
result = toRow(headerCells) + '\n' + toRow(separator) + '\n';
for (const row of dataRows) {
result += toRow(cells(row)) + '\n';
}
return result + '\n';
}
case 'button':
return '';
default:
return processChildren(node, listDepth);
}
}
function processChildren(node, listDepth) {
let result = '';
for (const child of node.childNodes) {
result += processNode(child, listDepth);
}
return result;
}
return processNode(elem).trim();
}
async function convertAndCopy(selector) {
const el = document.querySelector(selector);
if (!el) {
alert('Element not found: ' + selector);
return;
}
const md = elemToMarkdown(el);
try {
await navigator.clipboard.writeText(md);
console.log('Markdown copied to clipboard!');
return md;
} catch (err) {
console.error('Failed to copy:', err);
alert('Failed to copy to clipboard');
}
}
"""
def main():
minified_code = JAVASCRIPT.replace("\n", "")
selector = ".flex-1.flex.flex-col.px-4.max-w-3xl.mx-auto.w-full.pt-1"
sys.stdout.write("javascript:(function(){" + minified_code + ";convertAndCopy('" + selector + "');})();\n");
if __name__ == "__main__":
main()