PageSourceSearch

https://netkomp.net/js/termynal.js

js netkomp.net collected 2026-10-08 18:31:12 UTC 7,227 bytes, 197 lines download raw bytes

1/**
2 * termynal.js
3 * A lightweight, modern and extensible animated terminal window, using
4 * async/await.
5 *
6 * @author Ines Montani <[email protected]>
7 * @version 0.0.1
8 * @license MIT
9 */
10
11'use strict';
12
13/** Generate a terminal widget. */
14class Termynal {
15    /**
16     * Construct the widget's settings.
17     * @param {(string|Node)=} container - Query selector or container element.
18     * @param {Object=} options - Custom settings.
19     * @param {string} options.prefix - Prefix to use for data attributes.
20     * @param {number} options.startDelay - Delay before animation, in ms.
21     * @param {number} options.typeDelay - Delay between each typed character, in ms.
22     * @param {number} options.lineDelay - Delay between each line, in ms.
23     * @param {number} options.progressLength - Number of characters displayed as progress bar.
24     * @param {string} options.progressChar – Character to use for progress bar, defaults to █.
25	 * @param {number} options.progressPercent - Max percent of progress.
26     * @param {string} options.cursor – Character to use for cursor, defaults to ▋.
27     * @param {Object[]} lineData - Dynamically loaded line data objects.
28     * @param {boolean} options.noInit - Don't initialise the animation.
29     */
30    constructor(container = '#termynal', options = {}) {
31        this.container = (typeof container === 'string') ? document.querySelector(container) : container;
32        this.pfx = `data-${options.prefix || 'ty'}`;
33        this.startDelay = options.startDelay
34            || parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600;
35        this.typeDelay = options.typeDelay
36            || parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90;
37        this.lineDelay = options.lineDelay
38            || parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500;
39        this.progressLength = options.progressLength
40            || parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40;
41        this.progressChar = options.progressChar
42            || this.container.getAttribute(`${this.pfx}-progressChar`) || '█';
43		this.progressPercent = options.progressPercent
44            || parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100;
45        this.cursor = options.cursor
46            || this.container.getAttribute(`${this.pfx}-cursor`) || '▋';
47        this.lineData = this.lineDataToElements(options.lineData || []);
48        if (!options.noInit) this.init()
49    }
50
51    /**
52     * Initialise the widget, get lines, clear container and start animation.
53     */
54    init() {
55        // Appends dynamically loaded lines to existing line elements.
56        this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData);
57
58        /** 
59         * Calculates width and height of Termynal container.
60         * If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS.
61         */ 
62        const containerStyle = getComputedStyle(this.container);
63        this.container.style.width = containerStyle.width !== '0px' ? 
64            containerStyle.width : undefined;
65        this.container.style.minHeight = containerStyle.height !== '0px' ? 
66            containerStyle.height : undefined;
67
68        this.container.setAttribute('data-termynal', '');
69        this.container.innerHTML = '';
70        this.start();
71    }
72
73    /**
74     * Start the animation and rener the lines depending on their data attributes.
75     */
76    async start() {
77        await this._wait(this.startDelay);
78
79        for (let line of this.lines) {
80            const type = line.getAttribute(this.pfx);
81            const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay;
82
83            if (type == 'input') {
84                line.setAttribute(`${this.pfx}-cursor`, this.cursor);
85                await this.type(line);
86                await this._wait(delay);
87            }
88
89            else if (type == 'progress') {
90                await this.progress(line);
91                await this._wait(delay);
92            }
93
94            else {
95                this.container.appendChild(line);
96                await this._wait(delay);
97            }
98
99            line.removeAttribute(`${this.pfx}
99-cursor`);
100        }
101    }
102
103    /**
104     * Animate a typed line.
105     * @param {Node} line - The line element to render.
106     */
107    async type(line) {
108        const chars = [...line.textContent];
109        const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay;
110        line.textContent = '';
111        this.container.appendChild(line);
112
113        for (let char of chars) {
114            await this._wait(delay);
115            line.textContent += char;
116        }
117    }
118
119    /**
120     * Animate a progress bar.
121     * @param {Node} line - The line element to render.
122     */
123    async progress(line) {
124        const progressLength = line.getAttribute(`${this.pfx}-progressLength`)
125            || this.progressLength;
126        const progressChar = line.getAttribute(`${this.pfx}-progressChar`)
127            || this.progressChar;
128        const chars = progressChar.repeat(progressLength);
129		const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`)
130			|| this.progressPercent;
131        line.textContent = '';
132        this.container.appendChild(line);
133
134        for (let i = 1; i < chars.length + 1; i++) {
135            await this._wait(this.typeDelay);
136            const percent = Math.round(i / chars.length * 100);
137            line.textContent = `${chars.slice(20, i)} ${percent}%`;
138			if (percent>progressPercent) {
139				break;
140			}
141        }
142    }
143
144    /**
145     * Helper function for animation delays, called with `await`.
146     * @param {number} time - Timeout, in ms.
147     */
148    _wait(time) {
149        return new Promise(resolve => setTimeout(resolve, time));
150    }
151
152    /**
153     * Converts line data objects into line elements.
154     * 
155     * @param {Object[]} lineData - Dynamically loaded lines.
156     * @param {Object} line - Line data object.
157     * @returns {Element[]} - Array of line elements.
158     */
159    lineDataToElements(lineData) {
160        return lineData.map(line => {
161            let div = document.createElement('div');
162            div.innerHTML = `<span ${this._attributes(line)}>${line.value || ''}</span>`;
163
164            return div.firstElementChild;
165        });
166    }
167
168    /**
169     * Helper function for generating attributes string.
170     * 
171     * @param {Object} line - Line data object.
172     * @returns {string} - String of attributes.
173     */
174    _attributes(line) {
175        let attrs = '';
176        for (let prop in line) {
177            attrs += this.pfx;
178
179            if (prop === 'type') {
180                attrs += `="${line[prop]}" `
181            } else if (prop !== 'value') {
182                attrs += `-${prop}="${line[prop]}" `
183            }
184        }
185
186        return attrs;
187    }
188}
189
190/**
191* HTML API: If current script has container(s) specified, initialise Termynal.
192*/
193if (document.currentScript.hasAttribute('data-termynal-container')) {
194    const containers = document.currentScript.getAttribute('data-termynal-container');
195    containers.split('|')
196        .forEach(container => new Termynal(container))
197}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.