Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core/highlight-vars): run in exported documents also #4872

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/core/dfn-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ export async function run() {
el.tabIndex = 0;
el.setAttribute("aria-haspopup", "dialog");
}
document.body.append(panels);
if (document.body.querySelector("script")) {
document.body.querySelector("script").before(panels);
} else {
document.body.append(panels);
}

const script = document.createElement("script");
script.id = "respec-dfn-panel";
Expand Down
99 changes: 20 additions & 79 deletions src/core/highlight-vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,100 +8,41 @@
* on export.
*/
import css from "../styles/var.css.js";
import { norm } from "./utils.js";
import { fetchBase } from "./text-loader.js";
import { sub } from "./pubsubhub.js";

export const name = "core/highlight-vars";

export function run(conf) {
export async function run(conf) {
if (!conf.highlightVars) {
return;
}

const styleElement = document.createElement("style");
styleElement.textContent = css;
styleElement.classList.add("removeOnSave");
document.head.appendChild(styleElement);

document
.querySelectorAll("var")
.forEach(varElem => varElem.addEventListener("click", highlightListener));
const script = document.createElement("script");
script.id = "respec-highlight-vars";
script.textContent = await loadScript();
document.body.append(script);

// remove highlights, cleanup empty class/style attributes
sub("beforesave", outputDoc => {
outputDoc.querySelectorAll("var.respec-hl").forEach(removeHighlight);
sub("beforesave", (/** @type {Document} */ outputDoc) => {
outputDoc.querySelectorAll("var.respec-hl").forEach(el => {
const classesToRemove = [...el.classList.values()].filter(cls =>
cls.startsWith("respec-hl")
);
el.classList.remove(...classesToRemove);
if (!el.classList.length) el.removeAttribute("class");
});
});
}

function highlightListener(ev) {
ev.stopPropagation();
const { target: varElem } = ev;
const hightligtedElems = highlightVars(varElem);
const resetListener = () => {
const hlColor = getHighlightColor(varElem);
hightligtedElems.forEach(el => removeHighlight(el, hlColor));
[...HL_COLORS.keys()].forEach(key => HL_COLORS.set(key, true));
};
if (hightligtedElems.length) {
document.body.addEventListener("click", resetListener, { once: true });
}
}

// availability of highlight colors. colors from var.css
const HL_COLORS = new Map([
["respec-hl-c1", true],
["respec-hl-c2", true],
["respec-hl-c3", true],
["respec-hl-c4", true],
["respec-hl-c5", true],
["respec-hl-c6", true],
["respec-hl-c7", true],
]);

function getHighlightColor(target) {
// return current colors if applicable
const { value } = target.classList;
const re = /respec-hl-\w+/;
const activeClass = re.test(value) && value.match(re);
if (activeClass) return activeClass[0];

// first color preference
if (HL_COLORS.get("respec-hl-c1") === true) return "respec-hl-c1";

// otherwise get some other available color
return [...HL_COLORS.keys()].find(c => HL_COLORS.get(c)) || "respec-hl-c1";
}

function highlightVars(varElem) {
const textContent = norm(varElem.textContent);
const parent = varElem.closest(".algorithm, section");
const highlightColor = getHighlightColor(varElem);

const varsToHighlight = [...parent.querySelectorAll("var")].filter(
el =>
norm(el.textContent) === textContent &&
el.closest(".algorithm, section") === parent
);

// update availability of highlight color
const colorStatus = varsToHighlight[0].classList.contains("respec-hl");
HL_COLORS.set(highlightColor, colorStatus);

// highlight vars
if (colorStatus) {
varsToHighlight.forEach(el => removeHighlight(el, highlightColor));
return [];
} else {
varsToHighlight.forEach(el => addHighlight(el, highlightColor));
async function loadScript() {
try {
return (await import("text!./highlight-vars.runtime.js")).default;
} catch {
return fetchBase("./src/core/highlight-vars.runtime.js");
}
return varsToHighlight;
}

function removeHighlight(el, highlightColor) {
el.classList.remove("respec-hl", highlightColor);
// clean up empty class attributes so they don't come in export
if (!el.classList.length) el.removeAttribute("class");
}

function addHighlight(elem, highlightColor) {
elem.classList.add("respec-hl", highlightColor);
}
96 changes: 96 additions & 0 deletions src/core/highlight-vars.runtime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// @ts-check

if (document.respec) {
document.respec.ready.then(setupVarHighlighter);
} else {
setupVarHighlighter();
}

function setupVarHighlighter() {
document
.querySelectorAll("var")
.forEach(varElem => varElem.addEventListener("click", highlightListener));
}

function highlightListener(ev) {
ev.stopPropagation();
const { target: varElem } = ev;
const hightligtedElems = highlightVars(varElem);
const resetListener = () => {
const hlColor = getHighlightColor(varElem);
hightligtedElems.forEach(el => removeHighlight(el, hlColor));
[...HL_COLORS.keys()].forEach(key => HL_COLORS.set(key, true));
};
if (hightligtedElems.length) {
document.body.addEventListener("click", resetListener, { once: true });
}
}

// availability of highlight colors. colors from var.css
const HL_COLORS = new Map([
["respec-hl-c1", true],
["respec-hl-c2", true],
["respec-hl-c3", true],
["respec-hl-c4", true],
["respec-hl-c5", true],
["respec-hl-c6", true],
["respec-hl-c7", true],
]);

function getHighlightColor(target) {
// return current colors if applicable
const { value } = target.classList;
const re = /respec-hl-\w+/;
const activeClass = re.test(value) && value.match(re);
if (activeClass) return activeClass[0];

// first color preference
if (HL_COLORS.get("respec-hl-c1") === true) return "respec-hl-c1";

// otherwise get some other available color
return [...HL_COLORS.keys()].find(c => HL_COLORS.get(c)) || "respec-hl-c1";
}

function highlightVars(varElem) {
const textContent = norm(varElem.textContent);
const parent = varElem.closest(".algorithm, section");
const highlightColor = getHighlightColor(varElem);

const varsToHighlight = [...parent.querySelectorAll("var")].filter(
el =>
norm(el.textContent) === textContent &&
el.closest(".algorithm, section") === parent
);

// update availability of highlight color
const colorStatus = varsToHighlight[0].classList.contains("respec-hl");
HL_COLORS.set(highlightColor, colorStatus);

// highlight vars
if (colorStatus) {
varsToHighlight.forEach(el => removeHighlight(el, highlightColor));
return [];
} else {
varsToHighlight.forEach(el => addHighlight(el, highlightColor));
}
return varsToHighlight;
}

function removeHighlight(el, highlightColor) {
el.classList.remove("respec-hl", highlightColor);
// clean up empty class attributes so they don't come in export
if (!el.classList.length) el.removeAttribute("class");
}

function addHighlight(elem, highlightColor) {
elem.classList.add("respec-hl", highlightColor);
}

/**
* Same as `norm` from src/core/utils, but our build process doesn't allow
* imports in runtime scripts, so duplicated here.
* @param {string} str
*/
function norm(str) {
return str.trim().replace(/\s+/g, " ");
}
Loading