| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- import {html, htmlRaw} from '../utils/html.ts';
-
- type Processor = (el: HTMLElement) => string | HTMLElement | void;
-
- type Processors = {
- [tagName: string]: Processor;
- };
-
- type ProcessorContext = {
- elementIsFirst: boolean;
- elementIsLast: boolean;
- listNestingLevel: number;
- };
-
- function prepareProcessors(ctx:ProcessorContext): Processors {
- const processors: Processors = {
- H1(el: HTMLElement) {
- const level = parseInt(el.tagName.slice(1));
- el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`;
- },
- STRONG(el: HTMLElement) {
- return `**${el.textContent}**`;
- },
- EM(el: HTMLElement) {
- return `_${el.textContent}_`;
- },
- DEL(el: HTMLElement) {
- return `~~${el.textContent}~~`;
- },
- A(el: HTMLElement) {
- const text = el.textContent || 'link';
- const href = el.getAttribute('href');
- if (/^https?:/.test(text) && text === href) {
- return text;
- }
- return href ? `[${text}](${href})` : text;
- },
- IMG(el: HTMLElement) {
- const alt = el.getAttribute('alt') || 'image';
- const src = el.getAttribute('src');
- const widthAttr = el.hasAttribute('width') ? htmlRaw` width="${el.getAttribute('width') || ''}"` : '';
- const heightAttr = el.hasAttribute('height') ? htmlRaw` height="${el.getAttribute('height') || ''}"` : '';
- if (widthAttr || heightAttr) {
- return html`<img alt="${alt}"${widthAttr}${heightAttr} src="${src}">`;
- }
- return ``;
- },
- P(el: HTMLElement) {
- el.textContent = `${el.textContent}\n`;
- },
- BLOCKQUOTE(el: HTMLElement) {
- el.textContent = `${el.textContent.replace(/^/mg, '> ')}\n`;
- },
- OL(el: HTMLElement) {
- const preNewLine = ctx.listNestingLevel ? '\n' : '';
- el.textContent = `${preNewLine}${el.textContent}\n`;
- },
- LI(el: HTMLElement) {
- const parent = el.parentNode as HTMLElement;
- const bullet = parent.tagName === 'OL' ? `1. ` : '* ';
- const nestingIdentLevel = Math.max(0, ctx.listNestingLevel - 1);
- el.textContent = `${' '.repeat(nestingIdentLevel * 4)}${bullet}${el.textContent}${ctx.elementIsLast ? '' : '\n'}`;
- return el;
- },
- INPUT(el: HTMLElement) {
- return (el as HTMLInputElement).checked ? '[x] ' : '[ ] ';
- },
- CODE(el: HTMLElement) {
- const text = el.textContent;
- if (el.parentNode && (el.parentNode as HTMLElement).tagName === 'PRE') {
- el.textContent = `\`\`\`\n${text}\n\`\`\`\n`;
- return el;
- }
- if (text.includes('`')) {
- return `\`\` ${text} \`\``;
- }
- return `\`${text}\``;
- },
- };
- processors['UL'] = processors.OL;
- for (let level = 2; level <= 6; level++) {
- processors[`H${level}`] = processors.H1;
- }
- return processors;
- }
-
- function processElement(ctx :ProcessorContext, processors: Processors, el: HTMLElement): string | void {
- if (el.hasAttribute('data-markdown-generated-content')) return el.textContent;
- if (el.tagName === 'A' && el.children.length === 1 && el.children[0].tagName === 'IMG') {
- return processElement(ctx, processors, el.children[0] as HTMLElement);
- }
-
- const isListContainer = el.tagName === 'OL' || el.tagName === 'UL';
- if (isListContainer) ctx.listNestingLevel++;
- for (let i = 0; i < el.children.length; i++) {
- ctx.elementIsFirst = i === 0;
- ctx.elementIsLast = i === el.children.length - 1;
- processElement(ctx, processors, el.children[i] as HTMLElement);
- }
- if (isListContainer) ctx.listNestingLevel--;
-
- if (processors[el.tagName]) {
- const ret = processors[el.tagName](el);
- if (ret && ret !== el) {
- el.replaceWith(typeof ret === 'string' ? document.createTextNode(ret) : ret);
- }
- }
- }
-
- export function convertHtmlToMarkdown(el: HTMLElement): string {
- const div = document.createElement('div');
- div.append(el);
- const ctx = {} as ProcessorContext;
- ctx.listNestingLevel = 0;
- processElement(ctx, prepareProcessors(ctx), el);
- return div.textContent;
- }
|