refactor: DRY Markdown post-processing logic
This commit is contained in:
parent
c05da389ba
commit
0e7e5db661
|
|
@ -76,118 +76,128 @@
|
||||||
html: string;
|
html: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function enhanceLinks(html: string): string {
|
/**
|
||||||
if (!browser || !html.includes('<a')) {
|
* Helper to process HTML with a temporary DOM element.
|
||||||
|
* Returns original HTML if not in browser or tag not found.
|
||||||
|
*/
|
||||||
|
function processHtml(
|
||||||
|
html: string,
|
||||||
|
tagCheck: string,
|
||||||
|
processor: (tempDiv: HTMLDivElement) => boolean
|
||||||
|
): string {
|
||||||
|
if (!browser || !html.includes(tagCheck)) {
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tempDiv = document.createElement('div');
|
const tempDiv = document.createElement('div');
|
||||||
tempDiv.innerHTML = html;
|
tempDiv.innerHTML = html;
|
||||||
|
|
||||||
// Make all links open in new tabs
|
const mutated = processor(tempDiv);
|
||||||
const linkElements = tempDiv.querySelectorAll('a[href]');
|
|
||||||
let mutated = false;
|
|
||||||
|
|
||||||
for (const link of linkElements) {
|
|
||||||
const target = link.getAttribute('target');
|
|
||||||
const rel = link.getAttribute('rel');
|
|
||||||
|
|
||||||
if (target !== '_blank' || rel !== 'noopener noreferrer') {
|
|
||||||
mutated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
link.setAttribute('target', '_blank');
|
|
||||||
link.setAttribute('rel', 'noopener noreferrer');
|
|
||||||
}
|
|
||||||
|
|
||||||
return mutated ? tempDiv.innerHTML : html;
|
return mutated ? tempDiv.innerHTML : html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function enhanceCodeBlocks(html: string): string {
|
function enhanceLinks(html: string): string {
|
||||||
if (!browser || !html.includes('<pre')) {
|
return processHtml(html, '<a', (tempDiv) => {
|
||||||
return html;
|
const linkElements = tempDiv.querySelectorAll('a[href]');
|
||||||
}
|
let mutated = false;
|
||||||
|
|
||||||
const tempDiv = document.createElement('div');
|
for (const link of linkElements) {
|
||||||
tempDiv.innerHTML = html;
|
const target = link.getAttribute('target');
|
||||||
|
const rel = link.getAttribute('rel');
|
||||||
|
|
||||||
const preElements = tempDiv.querySelectorAll('pre');
|
if (target !== '_blank' || rel !== 'noopener noreferrer') {
|
||||||
let mutated = false;
|
mutated = true;
|
||||||
|
|
||||||
for (const [index, pre] of Array.from(preElements).entries()) {
|
|
||||||
const codeElement = pre.querySelector('code');
|
|
||||||
|
|
||||||
if (!codeElement) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutated = true;
|
|
||||||
|
|
||||||
let language = 'text';
|
|
||||||
const classList = Array.from(codeElement.classList);
|
|
||||||
|
|
||||||
for (const className of classList) {
|
|
||||||
if (className.startsWith('language-')) {
|
|
||||||
language = className.replace('language-', '');
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
link.setAttribute('target', '_blank');
|
||||||
|
link.setAttribute('rel', 'noopener noreferrer');
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawCode = codeElement.textContent || '';
|
return mutated;
|
||||||
const codeId = `code-${uuid()}-${index}`;
|
});
|
||||||
codeElement.setAttribute('data-code-id', codeId);
|
}
|
||||||
codeElement.setAttribute('data-raw-code', rawCode);
|
|
||||||
|
|
||||||
const wrapper = document.createElement('div');
|
function enhanceCodeBlocks(html: string): string {
|
||||||
wrapper.className = 'code-block-wrapper';
|
return processHtml(html, '<pre', (tempDiv) => {
|
||||||
|
const preElements = tempDiv.querySelectorAll('pre');
|
||||||
|
let mutated = false;
|
||||||
|
|
||||||
const header = document.createElement('div');
|
for (const [index, pre] of Array.from(preElements).entries()) {
|
||||||
header.className = 'code-block-header';
|
const codeElement = pre.querySelector('code');
|
||||||
|
|
||||||
const languageLabel = document.createElement('span');
|
if (!codeElement) {
|
||||||
languageLabel.className = 'code-language';
|
continue;
|
||||||
languageLabel.textContent = language;
|
}
|
||||||
|
|
||||||
const copyButton = document.createElement('button');
|
mutated = true;
|
||||||
copyButton.className = 'copy-code-btn';
|
|
||||||
copyButton.setAttribute('data-code-id', codeId);
|
|
||||||
copyButton.setAttribute('title', 'Copy code');
|
|
||||||
copyButton.setAttribute('type', 'button');
|
|
||||||
|
|
||||||
copyButton.innerHTML = `
|
let language = 'text';
|
||||||
|
const classList = Array.from(codeElement.classList);
|
||||||
|
|
||||||
|
for (const className of classList) {
|
||||||
|
if (className.startsWith('language-')) {
|
||||||
|
language = className.replace('language-', '');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawCode = codeElement.textContent || '';
|
||||||
|
const codeId = `code-${uuid()}-${index}`;
|
||||||
|
codeElement.setAttribute('data-code-id', codeId);
|
||||||
|
codeElement.setAttribute('data-raw-code', rawCode);
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'code-block-wrapper';
|
||||||
|
|
||||||
|
const header = document.createElement('div');
|
||||||
|
header.className = 'code-block-header';
|
||||||
|
|
||||||
|
const languageLabel = document.createElement('span');
|
||||||
|
languageLabel.className = 'code-language';
|
||||||
|
languageLabel.textContent = language;
|
||||||
|
|
||||||
|
const copyButton = document.createElement('button');
|
||||||
|
copyButton.className = 'copy-code-btn';
|
||||||
|
copyButton.setAttribute('data-code-id', codeId);
|
||||||
|
copyButton.setAttribute('title', 'Copy code');
|
||||||
|
copyButton.setAttribute('type', 'button');
|
||||||
|
|
||||||
|
copyButton.innerHTML = `
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy-icon lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy-icon lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const actions = document.createElement('div');
|
const actions = document.createElement('div');
|
||||||
actions.className = 'code-block-actions';
|
actions.className = 'code-block-actions';
|
||||||
|
|
||||||
actions.appendChild(copyButton);
|
actions.appendChild(copyButton);
|
||||||
|
|
||||||
if (language.toLowerCase() === 'html') {
|
if (language.toLowerCase() === 'html') {
|
||||||
const previewButton = document.createElement('button');
|
const previewButton = document.createElement('button');
|
||||||
previewButton.className = 'preview-code-btn';
|
previewButton.className = 'preview-code-btn';
|
||||||
previewButton.setAttribute('data-code-id', codeId);
|
previewButton.setAttribute('data-code-id', codeId);
|
||||||
previewButton.setAttribute('title', 'Preview code');
|
previewButton.setAttribute('title', 'Preview code');
|
||||||
previewButton.setAttribute('type', 'button');
|
previewButton.setAttribute('type', 'button');
|
||||||
|
|
||||||
previewButton.innerHTML = `
|
previewButton.innerHTML = `
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye lucide-eye-icon"><path d="M2.062 12.345a1 1 0 0 1 0-.69C3.5 7.73 7.36 5 12 5s8.5 2.73 9.938 6.655a1 1 0 0 1 0 .69C20.5 16.27 16.64 19 12 19s-8.5-2.73-9.938-6.655"/><circle cx="12" cy="12" r="3"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye lucide-eye-icon"><path d="M2.062 12.345a1 1 0 0 1 0-.69C3.5 7.73 7.36 5 12 5s8.5 2.73 9.938 6.655a1 1 0 0 1 0 .69C20.5 16.27 16.64 19 12 19s-8.5-2.73-9.938-6.655"/><circle cx="12" cy="12" r="3"/></svg>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
actions.appendChild(previewButton);
|
actions.appendChild(previewButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
header.appendChild(languageLabel);
|
||||||
|
header.appendChild(actions);
|
||||||
|
wrapper.appendChild(header);
|
||||||
|
|
||||||
|
const clonedPre = pre.cloneNode(true) as HTMLElement;
|
||||||
|
wrapper.appendChild(clonedPre);
|
||||||
|
|
||||||
|
pre.parentNode?.replaceChild(wrapper, pre);
|
||||||
}
|
}
|
||||||
|
|
||||||
header.appendChild(languageLabel);
|
return mutated;
|
||||||
header.appendChild(actions);
|
});
|
||||||
wrapper.appendChild(header);
|
|
||||||
|
|
||||||
const clonedPre = pre.cloneNode(true) as HTMLElement;
|
|
||||||
wrapper.appendChild(clonedPre);
|
|
||||||
|
|
||||||
pre.parentNode?.replaceChild(wrapper, pre);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mutated ? tempDiv.innerHTML : html;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCodeInfoFromTarget(target: HTMLElement) {
|
function getCodeInfoFromTarget(target: HTMLElement) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue