Files
ausboss-ComfyUI-AusBoss/js/shared/script_colours_math.mjs
ausboss 9ba82c45b4 H3 Prompt Writer: timeline, colored words, Sound and Music boxes
- Words light up as you type (@1, times, cuts, quotes, labels), with @
  autocomplete and a ? cheat sheet
- Timeline bar shows every picture a line names; drag a block to reorder,
  drag its edge to set its time, click an edge for a cut, click a block to
  jump to its line
- Sound and Music boxes (new optional inputs, appended) fill H3's
  overall_soundscape and non_diegetic_music
- Pictures and video as chips with their jobs; Length, Camera and Look stay
  plain rows so they can still be wired
- Help page and changelog updated; tests for the colors, autocomplete,
  timeline edits and the new inputs
2026-11-03 11:09:26 -06:00

52 lines
2.3 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Pure decisions behind js/shared/script_colours.mjs: which parts of a script
// get which colour. No DOM, so tests run it under node:test.
const escape = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
const span = (kind, text) => `<span class="${kind}">${escape(text)}</span>`;
const TOKENS = /(@\d)|("[^"\n]*"?)/g;
const LABEL = /^\s*(sound|music|camera|look):/i;
function lineMarkup(line, wired) {
let head = "";
let rest = line;
const time = rest.match(/^(\s*)(\d+(?:\.\d+)?s:)/);
const key = rest.match(/^(\s*)(sound|music|camera|look):/i);
if (time) { head = escape(time[1]) + span("time", time[2]); rest = rest.slice(time[0].length); }
else if (key) { head = escape(key[1]) + span("key", `${key[2]}:`); rest = rest.slice(key[0].length); }
let out = head;
let at = 0;
for (const match of rest.matchAll(TOKENS)) {
out += escape(rest.slice(at, match.index));
if (match[1]) {
const number = Number(match[1].slice(1));
out += span(number >= 1 && number <= 4 && wired(number) ? "at" : "at unknown", match[1]);
} else {
out += span("say", match[2]);
}
at = match.index + match[0].length;
}
return out + escape(rest.slice(at));
}
// The coloured copy of a script, as HTML. wired(number) says whether a
// picture is plugged in under that @ number; one that isn't shows amber.
// A blank line between two lines is a cut, unless the script says no cuts or
// what follows is a label line.
export function scriptMarkup(text, wired = () => true) {
const lines = String(text ?? "").split("\n");
const cuts = !/\bno cuts\b/i.test(String(text ?? ""));
const filled = lines.map((line) => line.trim() !== "");
// A sound:, music:, camera: or look: line on its own is for the whole clip, not a new shot.
const label = lines.map((line) => LABEL.test(line));
const html = lines.map((line, index) => {
if (filled[index]) return lineMarkup(line, wired);
const before = filled.slice(0, index).some(Boolean);
const next = filled.indexOf(true, index + 1);
const after = next >= 0 && !label[next];
const firstBlank = index > 0 && filled[index - 1];
return cuts && before && after && firstBlank ? '<span class="cut">​</span>' : escape(line);
});
// A box that ends on a new line shows one more empty line.
return html.join("\n") + (String(text ?? "").endsWith("\n") ? " " : "");
}