Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Termynal #488

Merged
merged 2 commits into from
Apr 14, 2023
Merged
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
18 changes: 17 additions & 1 deletion docs/extra.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,20 @@ a.toctree-l3 {

.document hr {
border-top: 1px solid #666;
}
}

/* Termynal */

[data-termynal]:after {
content: ''; /* Don't display a window title */
}

[data-termynal] {
width: 100%;
font-size: 0.9rem;
margin-bottom: 1rem;
}

[data-termynal] > [data-ty="comment"] {
color: #DAD6AF;
}
10 changes: 9 additions & 1 deletion docs/extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,12 @@ document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll("table").forEach(function (table) {
table.classList.add("docutils");
});
});

document.querySelectorAll(".termynal").forEach(function (termynalEl) {
new Termynal(termynalEl);
});
});




26 changes: 25 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,31 @@ Gitlint is a git commit message linter written in python: it checks your commit
Great for use as a [commit-msg git hook](#using-gitlint-as-a-commit-msg-hook) or as part of your gating script in a
[CI pipeline (e.g. Jenkins)](index.md#using-gitlint-in-a-ci-environment).

<script type="text/javascript" src="https://asciinema.org/a/30477.js" id="asciicast-30477" async></script>
<!-- <script type="text/javascript" src="https://asciinema.org/a/30477.js" id="asciicast-30477" async></script> -->

<div class="termynal" data-termynal data-ty-typeDelay="25" data-ty-startDelay="600" data-ty-lineDelay="500">
<span data-ty="input">pip install gitlint</span>
<span data-ty="progress"></span>
<span data-ty>Successfully installed gitlint-core, gitlint</span>
<span data-ty></span>
<span data-ty="comment"># Add a bad commit message</span>
<span data-ty="input">git commit --allow-empty -m " WIP: My bad commit title." -m "who &emsp; cares "</span>
<span>[main a9f9368] WIP: My bad commit title.</span>
<span data-ty></span>
<span data-ty="comment"># Run gitlint!</span>
<span data-ty="input">gitlint</span>e
<span data-ty>1: T3 Title has trailing punctuation (.): " WIP: My bad commit title."<br />
1: T5 Title contains the word 'WIP' (case-insensitive): " WIP: My bad commit title."<br />
1: T6 Title has leading whitespace: " WIP: My bad commit title."<br />
3: B2 Line has trailing whitespace: "who &emsp; cares "<br />
3: B3 Line contains hard tab characters (\t): "who &emsp; cares "<br />
3: B5 Body message is too short (10&lt;20): "who &emsp; cares "
</span>
<span data-ty data-ty-delay="2000"></span>
<span data-ty></span>
<span data-ty="comment"># Gitlint is perfect for use in your CI pipeline.</span>
<span data-ty="comment"># Also available as a commit-msg hook or via pre-commit.</span>
</div>

!!! note
**Gitlint works on Windows**, but [there are some known issues](https://github.com/jorisroovers/gitlint/issues?q=is%3Aissue+is%3Aopen+label%3Awindows).
Expand Down
101 changes: 101 additions & 0 deletions docs/termynal.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* termynal.js
*
* @author Ines Montani <ines@ines.io>
* @version 0.0.1
* @license MIT
*/

:root {
--color-bg: #252a33;
--color-text: #eee;
--color-text-subtle: #a2a2a2;
}

[data-termynal] {
width: 750px;
max-width: 100%;
background: var(--color-bg);
color: var(--color-text);
font-size: 18px;
font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace;
border-radius: 4px;
padding: 75px 45px 35px;
position: relative;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}

[data-termynal]:before {
content: '';
position: absolute;
top: 15px;
left: 15px;
display: inline-block;
width: 15px;
height: 15px;
border-radius: 50%;
/* A little hack to display the window buttons in one pseudo element. */
background: #d9515d;
-webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930;
box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930;
}

[data-termynal]:after {
content: 'bash';
position: absolute;
color: var(--color-text-subtle);
top: 5px;
left: 0;
width: 100%;
text-align: center;
}

[data-ty] {
display: block;
line-height: 2;
}

[data-ty]:before {
/* Set up defaults and ensure empty lines are displayed. */
content: '';
display: inline-block;
vertical-align: middle;
}

[data-ty="input"]:before,
[data-ty-prompt]:before {
margin-right: 0.75em;
color: var(--color-text-subtle);
}

[data-ty="input"]:before {
content: '$';
}

[data-ty][data-ty-prompt]:before {
content: attr(data-ty-prompt);
}

[data-ty-cursor]:after {
content: attr(data-ty-cursor);
font-family: monospace;
margin-left: 0.5em;
-webkit-animation: blink 1s infinite;
animation: blink 1s infinite;
}


/* Cursor animation */

@-webkit-keyframes blink {
50% {
opacity: 0;
}
}

@keyframes blink {
50% {
opacity: 0;
}
}
197 changes: 197 additions & 0 deletions docs/termynal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/**
* termynal.js
* A lightweight, modern and extensible animated terminal window, using
* async/await.
*
* @author Ines Montani <ines@ines.io>
* @version 0.0.1
* @license MIT
*/

'use strict';

/** Generate a terminal widget. */
class Termynal {
/**
* Construct the widget's settings.
* @param {(string|Node)=} container - Query selector or container element.
* @param {Object=} options - Custom settings.
* @param {string} options.prefix - Prefix to use for data attributes.
* @param {number} options.startDelay - Delay before animation, in ms.
* @param {number} options.typeDelay - Delay between each typed character, in ms.
* @param {number} options.lineDelay - Delay between each line, in ms.
* @param {number} options.progressLength - Number of characters displayed as progress bar.
* @param {string} options.progressChar – Character to use for progress bar, defaults to █.
* @param {number} options.progressPercent - Max percent of progress.
* @param {string} options.cursor – Character to use for cursor, defaults to ▋.
* @param {Object[]} lineData - Dynamically loaded line data objects.
* @param {boolean} options.noInit - Don't initialise the animation.
*/
constructor(container = '#termynal', options = {}) {
this.container = (typeof container === 'string') ? document.querySelector(container) : container;
this.pfx = `data-${options.prefix || 'ty'}`;
this.startDelay = options.startDelay
|| parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600;
this.typeDelay = options.typeDelay
|| parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90;
this.lineDelay = options.lineDelay
|| parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500;
this.progressLength = options.progressLength
|| parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40;
this.progressChar = options.progressChar
|| this.container.getAttribute(`${this.pfx}-progressChar`) || '█';
this.progressPercent = options.progressPercent
|| parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100;
this.cursor = options.cursor
|| this.container.getAttribute(`${this.pfx}-cursor`) || '▋';
this.lineData = this.lineDataToElements(options.lineData || []);
if (!options.noInit) this.init()
}

/**
* Initialise the widget, get lines, clear container and start animation.
*/
init() {
// Appends dynamically loaded lines to existing line elements.
this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData);

/**
* Calculates width and height of Termynal container.
* If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS.
*/
const containerStyle = getComputedStyle(this.container);
this.container.style.width = containerStyle.width !== '0px' ?
containerStyle.width : undefined;
this.container.style.minHeight = containerStyle.height !== '0px' ?
containerStyle.height : undefined;

this.container.setAttribute('data-termynal', '');
this.container.innerHTML = '';
this.start();
}

/**
* Start the animation and rener the lines depending on their data attributes.
*/
async start() {
await this._wait(this.startDelay);

for (let line of this.lines) {
const type = line.getAttribute(this.pfx);
const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay;

if (type == 'input') {
line.setAttribute(`${this.pfx}-cursor`, this.cursor);
await this.type(line);
await this._wait(delay);
}

else if (type == 'progress') {
await this.progress(line);
await this._wait(delay);
}

else {
this.container.appendChild(line);
await this._wait(delay);
}

line.removeAttribute(`${this.pfx}-cursor`);
}
}

/**
* Animate a typed line.
* @param {Node} line - The line element to render.
*/
async type(line) {
const chars = [...line.textContent];
const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay;
line.textContent = '';
this.container.appendChild(line);

for (let char of chars) {
await this._wait(delay);
line.textContent += char;
}
}

/**
* Animate a progress bar.
* @param {Node} line - The line element to render.
*/
async progress(line) {
const progressLength = line.getAttribute(`${this.pfx}-progressLength`)
|| this.progressLength;
const progressChar = line.getAttribute(`${this.pfx}-progressChar`)
|| this.progressChar;
const chars = progressChar.repeat(progressLength);
const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`)
|| this.progressPercent;
line.textContent = '';
this.container.appendChild(line);

for (let i = 1; i < chars.length + 1; i++) {
await this._wait(this.typeDelay);
const percent = Math.round(i / chars.length * 100);
line.textContent = `${chars.slice(0, i)} ${percent}%`;
if (percent>progressPercent) {
break;
}
}
}

/**
* Helper function for animation delays, called with `await`.
* @param {number} time - Timeout, in ms.
*/
_wait(time) {
return new Promise(resolve => setTimeout(resolve, time));
}

/**
* Converts line data objects into line elements.
*
* @param {Object[]} lineData - Dynamically loaded lines.
* @param {Object} line - Line data object.
* @returns {Element[]} - Array of line elements.
*/
lineDataToElements(lineData) {
return lineData.map(line => {
let div = document.createElement('div');
div.innerHTML = `<span ${this._attributes(line)}>${line.value || ''}</span>`;

return div.firstElementChild;
});
}

/**
* Helper function for generating attributes string.
*
* @param {Object} line - Line data object.
* @returns {string} - String of attributes.
*/
_attributes(line) {
let attrs = '';
for (let prop in line) {
attrs += this.pfx;

if (prop === 'type') {
attrs += `="${line[prop]}" `
} else if (prop !== 'value') {
attrs += `-${prop}="${line[prop]}" `
}
}

return attrs;
}
}

/**
* HTML API: If current script has container(s) specified, initialise Termynal.
*/
if (document.currentScript.hasAttribute('data-termynal-container')) {
const containers = document.currentScript.getAttribute('data-termynal-container');
containers.split('|')
.forEach(container => new Termynal(container))
}
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ theme:
navigation_depth: 2
strict: true
extra_css:
- termynal.css
- extra.css
extra_javascript:
- termynal.js
- extra.js