Files
diodiogod-TTS-Audio-Suite/web/syntax-highlighter.js
diodiogod fa30dc6768 Add DramaBox workflow and quotation highlighting
- Add the DramaBox integration workflow and matching cover art
- Link the workflow from the README engine workflow table
- Highlight complete straight and curly quoted spans in the Multiline TTS Tag Editor
- Preserve existing tag colors and ignore unmatched or multiline quote pairs
2026-07-25 12:30:32 -03:00

174 lines
6.5 KiB
JavaScript

/**
* 🏷️ Syntax Highlighter
* Handles syntax highlighting for SRT content, tags, and special characters
*/
export class SyntaxHighlighter {
static markQuotedSpans(text) {
let output = "";
let index = 0;
while (index < text.length) {
const character = text[index];
// Parameter and inline tags have their own stronger highlighting.
if (character === "[" || character === "<") {
const closingCharacter = character === "[" ? "]" : ">";
const closingIndex = text.indexOf(closingCharacter, index + 1);
if (closingIndex !== -1) {
output += text.slice(index, closingIndex + 1);
index = closingIndex + 1;
continue;
}
}
const closingQuote = character === "\"" ? "\"" : character === "“" ? "”" : null;
if (closingQuote) {
const closingIndex = text.indexOf(closingQuote, index + 1);
const lineEndIndex = text.indexOf("\n", index + 1);
if (
closingIndex !== -1 &&
(lineEndIndex === -1 || closingIndex < lineEndIndex)
) {
output += `\x00QUOTE_START\x00${text.slice(index, closingIndex + 1)}\x00QUOTE_END\x00`;
index = closingIndex + 1;
continue;
}
}
output += character;
index += 1;
}
return output;
}
static getPlainText(editor) {
const clone = editor.cloneNode(true);
const spans = clone.querySelectorAll('span');
spans.forEach(span => {
while (span.firstChild) {
span.parentNode.insertBefore(span.firstChild, span);
}
span.parentNode.removeChild(span);
});
return clone.textContent;
}
static getCaretPos(editor) {
const selection = window.getSelection();
if (selection.rangeCount === 0) return 0;
const range = selection.getRangeAt(0);
const preRange = range.cloneRange();
preRange.selectNodeContents(editor);
preRange.setEnd(range.endContainer, range.endOffset);
const tempDiv = document.createElement('div');
tempDiv.appendChild(preRange.cloneContents());
const spans = tempDiv.querySelectorAll('span');
spans.forEach(span => {
while (span.firstChild) {
span.parentNode.insertBefore(span.firstChild, span);
}
span.parentNode.removeChild(span);
});
return tempDiv.textContent.length;
}
static setCaretPos(editor, pos) {
const selection = window.getSelection();
const range = document.createRange();
let charCount = 0;
let nodeStack = [editor];
let node;
let foundStart = false;
while (!foundStart && (node = nodeStack.pop())) {
if (node.nodeType === Node.TEXT_NODE) {
const nextCharCount = charCount + node.length;
if (pos <= nextCharCount) {
range.setStart(node, pos - charCount);
foundStart = true;
}
charCount = nextCharCount;
} else {
let i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
if (foundStart) {
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}
}
static updateHighlights(editor) {
const plainText = this.getPlainText(editor);
const caretPos = this.getCaretPos(editor);
let html = plainText;
// Highlight SRT sequence numbers - bright red
html = html.replace(
/^(\d+)\s*\n(\d{2}:\d{2}:\d{2},\d{3}\s+-->\s+\d{2}:\d{2}:\d{2},\d{3})/gm,
'\x00NUM_START\x00$1\x00NUM_END\x00\n$2'
);
// Highlight SRT timings - bright orange
html = html.replace(
/\d{2}:\d{2}:\d{2},\d{3}\s+-->\s+\d{2}:\d{2}:\d{2},\d{3}/g,
'\x00SRT_START\x00$&\x00SRT_END\x00'
);
// Highlight tags - bright cyan
html = html.replace(
/(\[[^\]]+\])/g,
'\x00TAG_START\x00$1\x00TAG_END\x00'
);
// Highlight complete straight or curly quoted spans.
html = this.markQuotedSpans(html);
// Highlight commas - green
html = html.replace(/,/g, '\x00COMMA_START\x00,\x00COMMA_END\x00');
// Highlight periods - golden yellow
html = html.replace(/\./g, '\x00PERIOD_START\x00.\x00PERIOD_END\x00');
// Highlight punctuation - light salmon
html = html.replace(/[?!;]/g, '\x00PUNCT_START\x00$&\x00PUNCT_END\x00');
// Highlight multiple spaces (2 or more) - subtle background
html = html.replace(/ +/g, '\x00SPACE_START\x00$&\x00SPACE_END\x00');
// Escape HTML
html = html
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
// Replace placeholders with spans
html = html
.replace(/\x00NUM_START\x00(.*?)\x00NUM_END\x00/g, '<span style="color: #ff5555; font-weight: bold;">$1</span>')
.replace(/\x00SRT_START\x00(.*?)\x00SRT_END\x00/g, '<span style="color: #ffaa00; font-weight: bold;">$1</span>')
.replace(/\x00TAG_START\x00(.*?)\x00TAG_END\x00/g, '<span style="color: #00ffff; font-weight: bold;">$1</span>')
.replace(/\x00COMMA_START\x00(.*?)\x00COMMA_END\x00/g, '<span style="color: #66ff66; font-weight: bold;">$1</span>')
.replace(/\x00PERIOD_START\x00(.*?)\x00PERIOD_END\x00/g, '<span style="color: #ffcc33; font-weight: bold;">$1</span>')
.replace(/\x00PUNCT_START\x00(.*?)\x00PUNCT_END\x00/g, '<span style="color: #ff9999;">$1</span>')
.replace(/\x00SPACE_START\x00(.*?)\x00SPACE_END\x00/g, '<span style="background: #2a2a2a; color: #eee;">$1</span>')
.replace(/\x00QUOTE_START\x00(.*?)\x00QUOTE_END\x00/g, '<span class="string-multiline-tag-editor-quote-token">$1</span>');
// Update only if changed to avoid flicker
if (editor.innerHTML !== html) {
editor.innerHTML = html;
this.setCaretPos(editor, caretPos);
}
}
}