Skip to content

Commit

Permalink
Merge pull request #705 from w3c/export-minutes-tool-multiline-code
Browse files Browse the repository at this point in the history
Update minute export tool to support rendered code
  • Loading branch information
Rob--W authored Oct 24, 2024
2 parents 5d1ab85 + c5edc76 commit 0ac7d66
Showing 1 changed file with 102 additions and 9 deletions.
111 changes: 102 additions & 9 deletions _minutes/export-minutes.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
}
#extraInfoOutput {
white-space: pre-wrap;
height: 7em;
height: 8em;
overflow-y: auto;
}
#input, #output {
flex: 1;
Expand Down Expand Up @@ -75,6 +76,10 @@
- Issues: ${serializeIssues(issues)}
- PRs: ${serializeIssues(prs)}
- Mentioned issues without link to issue: ${serializeIssues(mentionedWithoutLink)}`;
if (markdownText.includes("```")) {
extraInfoOutput.textContent += `
WARNING: ${markdownText.match(/```/g).length / 2} code blocks (\`\`\`) found. You should verify the rendered output!`;
}
};

/**
Expand All @@ -86,7 +91,7 @@
- Replace boldfaced with **xx**
- Replace italic with _xx_
- Replace links with [text](anchor)
- Replace h1, h2, h3 with #, ## and ###
- Replace h1, h2, h3, h4 with #, ##, ### and ####
- Format h1 header for consistency.
- Replace ol,ul and li with correctly indented list items.
- Fixup whitespace.
Expand All @@ -95,12 +100,9 @@
let root = elemRootInput.cloneNode(true);

// Apply code formatting first, before escaping characters.
for (let c of root.querySelectorAll(`span[style*="font-family:'Courier New'"]`)) {
c.prepend("`");
c.append("`");
// replaceAllInTextNodes skips ` only if they are in the same text node.
c.normalize();
}
// To avoid interference by transformations below, the code is replaced
// with placeholders, which we should restore in the end.
const { finalRestoreCodeBlocks } = replaceAllCodeBlocks(root);

// Escape < to avoid rendering as HTML.
replaceAllInTextNodes(root, "<", "&lt;");
Expand Down Expand Up @@ -148,6 +150,9 @@
for (let h of root.querySelectorAll("h3")) {
h.prepend(`\n### `);
}
for (let h of root.querySelectorAll("h4")) {
h.prepend(`\n#### `);
}

for (let li of root.querySelectorAll("li")) {
let level = 0;
Expand Down Expand Up @@ -190,7 +195,7 @@
elem.after("\n");
}
// Blank line after every header.
for (let elem of root.querySelectorAll("h1,h2,h3")) {
for (let elem of root.querySelectorAll("h1,h2,h3,h4")) {
elem.after("\n\n");
}

Expand Down Expand Up @@ -218,6 +223,8 @@
// Trim leading whitespace.
textContent = textContent.trim();

textContent = finalRestoreCodeBlocks(textContent);

return textContent;
}

Expand Down Expand Up @@ -248,6 +255,92 @@
node.parentNode.replaceChild(document.createTextNode(proposed), node);
}
}

// Replaces code elements in |root| with.
function replaceAllCodeBlocks(root, getPlaceholder) {
// To prevent code blocks from being affected by text-based transformations
// in the end, replace the text with placeholders.
const codeTexts = new Map();
let nextCodeId = 1000;
function getPlaceholder(txt) {
// Assuming that minutes will never contain MINUTE_PLACEHOLDER_.
let placeholder = `^^^MINUTE_PLACEHOLDER_${nextCodeId++}===`;
codeTexts.set(placeholder, txt);
return placeholder;
}
function restorePlaceholders(txt) {
return txt.replace(
/\^\^\^MINUTE_PLACEHOLDER_\d+===/g,
placeholder => codeTexts.get(placeholder)
);
}

// First pass: Detect code lines (possibly multiline code) and inline code.
for (let c of root.querySelectorAll(`span[style*="font-family"][style*="monospace"]`)) {
if (c.style.fontFamily.includes("monospace")) {
if (c.closest("[this_is_really_a_code_block]")) {
// Already processed (determined that parent is code block).
continue;
}
if (
c.parentNode.tagName === "P" &&
!c.parentNode.querySelector(`span[style*="font-family"]:not([style*="monospace"])`)
) {
// Part of code block.
c.parentNode.setAttribute("this_is_really_a_code_block", "");
} else {
// Has siblings that is not code.
c.setAttribute("this_is_really_a_code_block", "");
}
}
}
// Second pass: Collapse multiline code with ```, use ` otherwise.
for (let c of root.querySelectorAll("[this_is_really_a_code_block]")) {
if (!root.contains(c)) {
// Already processed and remove()d below.
continue;
}
let codeNodes = [];
for (
let nod = c;
nod?.matches?.("[this_is_really_a_code_block],br");
nod = nod.nextSibling
) {
codeNodes.push(nod);
}
let codeText = "";
for (let nod of codeNodes) {
// br can be top-level, sole child of p, or wrapped in span.
for (let br of nod.querySelectorAll("br")) {
br.replaceWith("\n");
}
codeText += nod.textContent;
if (nod.tagName === "P" || nod.tagName === "BR") {
codeText += "\n";
}
}
codeText = codeText.replace(/\n+$/, "");

// Replace actual content with placeholder to prevent other logic such as
// the link wrapping / text replacement logic from mangling the code block.
c.textContent = getPlaceholder(codeText);

if (codeText.trim().includes("\n")) {
c.textContent = "```\n" + codeText + "\n```";
} else {
c.textContent = "`" + codeText + "`";
}
// codeNodes[0] === c; remove all except c.
codeNodes.slice(1).forEach(nod => nod.remove());
}

function finalRestoreCodeBlocks(textContent) {
textContent = restorePlaceholders(textContent);
return textContent;
}

return { finalRestoreCodeBlocks };
}
</script>
</body>
</html>

0 comments on commit 0ac7d66

Please sign in to comment.