Skip to content
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Each `<span>` within the container represents a line of code. You can customise
| `input` | Simple prompt with user input and cursor | `<span data-ty="input">pip install spacy</span>` |
| `progress` | Animated progress bar | `<span data-ty="progress"></span>` |

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.
Expand Down
10 changes: 5 additions & 5 deletions example.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@
</head>
<body>
<!-- the termynal container -->
<div id="termynal" data-termynal>
<span data-ty="input">pip install spacy</span>
<div id="termynal">
<span data-ty="input">pip install <i style="color: red">spacy</i></span>
<span data-ty="progress"></span>
<span data-ty>Successfully installed spacy</span>
<span data-ty>Successfully installed <i style="color: red">spacy</i></span>
<span data-ty></span>
<span data-ty="input">python -m spacy download en</span>
<span data-ty="input">python -m <i style="color: red">spacy</i> download en</span>
<span data-ty="progress"></span>
<span data-ty>Installed model 'en'</span>
<span data-ty></span>
<span data-ty="input">python</span>
<span data-ty="input" data-ty-prompt=">>>">import spacy</span>
<span data-ty="input" data-ty-prompt=">>>">import <i style="color: red">spacy</i></span>
<span data-ty="input" data-ty-prompt=">>>">nlp = spacy.load('en')</span>
<span data-ty="input" data-ty-prompt=">>>">doc = nlp(u'Hello world')</span>
<span data-ty="input" data-ty-prompt=">>>">print([(w.text, w.pos_) for w in doc])</span>
Expand Down
69 changes: 65 additions & 4 deletions termynal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 += `</${node.tagName.toLowerCase()}>`;
}
}

for (const child of element.childNodes) {
processNode(child);
}

return result;
}

/**
* Animate a progress bar.
* @param {Node} line - The line element to render.
Expand Down Expand Up @@ -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))
}
}