From f10f044060911fe6593f15f96c1948e35eca6246 Mon Sep 17 00:00:00 2001 From: Nathan Pierce Date: Sat, 1 Mar 2025 07:52:06 -0600 Subject: [PATCH] support for html tags for coloring inside of the data-ty=input + example --- README.md | 2 ++ example.html | 10 ++++---- termynal.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9397a6a..c848663 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,8 @@ Each `` within the container represents a line of code. You can customise | `input` | Simple prompt with user input and cursor | `pip install spacy` | | `progress` | Animated progress bar | `` | +Both `data-ty` and `data-ty="input"` support html tags inside the text. This is useful for highlighting parts of the text with specific colors or styles. See the [simple example](example.html) for an example. + ### `data-ty-prompt`: prompt style The prompt style specifies the characters that are displayed before each line, for example, to indicate command line inputs or interpreters (like `>>>` for Python). By default, Termynal displays a `$` before each user input line. diff --git a/example.html b/example.html index 8415bd5..7414175 100644 --- a/example.html +++ b/example.html @@ -22,17 +22,17 @@ -
- pip install spacy +
+ pip install spacy - Successfully installed spacy + Successfully installed spacy - python -m spacy download en + python -m spacy download en Installed model 'en' python - import spacy + import spacy nlp = spacy.load('en') doc = nlp(u'Hello world') print([(w.text, w.pos_) for w in doc]) diff --git a/termynal.js b/termynal.js index 77ec6cb..c41c60d 100644 --- a/termynal.js +++ b/termynal.js @@ -105,17 +105,78 @@ class Termynal { * @param {Node} line - The line element to render. */ async type(line) { - const chars = [...line.textContent]; + // Store the original HTML content + const originalHTML = line.innerHTML; + // Create a temporary element to parse the HTML + const tempElement = document.createElement('div'); + tempElement.innerHTML = originalHTML; + // Get the text content to animate typing + const chars = [...tempElement.textContent]; + // Get the delay from the attribute or use the default const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay; + + // Clear the line and add it to the container line.textContent = ''; this.container.appendChild(line); - for (let char of chars) { + // Type each character + let currentTextLength = 0; + for (let i = 0; i < chars.length; i++) { await this._wait(delay); - line.textContent += char; + currentTextLength++; + + // Update the visible portion of the HTML + // This preserves HTML tags while only showing the typed characters + tempElement.innerHTML = originalHTML; + const visibleHTML = this.getVisibleHTML(tempElement, currentTextLength); + line.innerHTML = visibleHTML; } } + /** + * Helper function to get visible portion of HTML while preserving tags + * @param {Node} element - Element containing the HTML + * @param {number} visibleChars - Number of text characters to show + * @returns {string} - HTML string with only the visible characters + */ + getVisibleHTML(element, visibleChars) { + let textCount = 0; + let result = ''; + + function processNode(node) { + if (textCount >= visibleChars) return; + + if (node.nodeType === Node.TEXT_NODE) { + const text = node.textContent; + const visibleText = text.substring(0, visibleChars - textCount); + result += visibleText; + textCount += text.length; + } else if (node.nodeType === Node.ELEMENT_NODE) { + result += `<${node.tagName.toLowerCase()}`; + + // Add attributes + for (const attr of node.attributes) { + result += ` ${attr.name}="${attr.value}"`; + } + + result += '>'; + + // Process child nodes + for (const child of node.childNodes) { + processNode(child); + } + + result += ``; + } + } + + for (const child of element.childNodes) { + processNode(child); + } + + return result; + } + /** * Animate a progress bar. * @param {Node} line - The line element to render. @@ -194,4 +255,4 @@ if (document.currentScript.hasAttribute('data-termynal-container')) { const containers = document.currentScript.getAttribute('data-termynal-container'); containers.split('|') .forEach(container => new Termynal(container)) -} +} \ No newline at end of file