✨ Add termynal for awesome docs
This commit is contained in:
commit
c004fb6af8
6 changed files with 429 additions and 81 deletions
4
docs/css/custom.css
Normal file
4
docs/css/custom.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.termynal-comment {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
101
docs/css/termynal.css
Normal file
101
docs/css/termynal.css
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
119
docs/index.md
119
docs/index.md
|
@ -44,9 +44,13 @@ Typer stands on the shoulders of a giant. Its only internal dependency is <a hre
|
|||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install typer
|
||||
<div class="termy">
|
||||
```console
|
||||
$ pip install typer
|
||||
---> 100%
|
||||
Successfully installed typer
|
||||
```
|
||||
</div>
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -70,45 +74,33 @@ if __name__ == "__main__":
|
|||
|
||||
Run your application:
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
<div class="termy">
|
||||
```console
|
||||
// Run your application
|
||||
$ python main.py
|
||||
|
||||
you will get a response like:
|
||||
|
||||
```
|
||||
// You get a nice error, you are missing NAME
|
||||
Usage: main.py [OPTIONS] NAME
|
||||
Try "main.py --help" for help.
|
||||
|
||||
Error: Missing argument "NAME".
|
||||
```
|
||||
|
||||
Now pass the `NAME` *argument*:
|
||||
// You get a --help for free
|
||||
$ python main.py --help
|
||||
|
||||
```bash
|
||||
python main.py Camila
|
||||
```
|
||||
|
||||
You will get a response like:
|
||||
|
||||
```
|
||||
Hello Camila
|
||||
```
|
||||
|
||||
And you automatically get a `--help` command:
|
||||
|
||||
```bash
|
||||
python main.py --help
|
||||
```
|
||||
|
||||
shows:
|
||||
|
||||
```
|
||||
Usage: main.py [OPTIONS] NAME
|
||||
|
||||
Options:
|
||||
--help Show this message and exit.
|
||||
|
||||
// Now pass the NAME argument
|
||||
$ python main.py Camila
|
||||
|
||||
Hello Camila
|
||||
|
||||
// It works! 🎉
|
||||
```
|
||||
</div>
|
||||
|
||||
## Example upgrade
|
||||
|
||||
|
@ -154,15 +146,11 @@ And that will:
|
|||
|
||||
### Run the upgraded example
|
||||
|
||||
Get the main `--help`:
|
||||
<div class="termy">
|
||||
```console
|
||||
// Check the --help
|
||||
$ python main.py --help
|
||||
|
||||
```bash
|
||||
python main.py --help
|
||||
```
|
||||
|
||||
shows:
|
||||
|
||||
```
|
||||
Usage: main.py [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
Options:
|
||||
|
@ -171,80 +159,49 @@ Options:
|
|||
Commands:
|
||||
goodbye
|
||||
hello
|
||||
```
|
||||
|
||||
You have 2 sub-commands (the 2 functions), `goodbye` and `hello`.
|
||||
// You have 2 sub-commands (the 2 functions): goodbye and hello
|
||||
|
||||
Now get the help for `hello`:
|
||||
// Now get the --help for hello
|
||||
|
||||
```bash
|
||||
python main.py hello --help
|
||||
```
|
||||
$ python main.py hello --help
|
||||
|
||||
shows:
|
||||
|
||||
```
|
||||
Usage: main.py hello [OPTIONS] NAME
|
||||
|
||||
Options:
|
||||
--help Show this message and exit.
|
||||
```
|
||||
|
||||
And now get the help for `goodbye`:
|
||||
// And now get the --help for goodbye
|
||||
|
||||
```bash
|
||||
python main.py goodbye --help
|
||||
```
|
||||
$ python main.py goodbye --help
|
||||
|
||||
shows:
|
||||
|
||||
```
|
||||
Usage: main.py goodbye [OPTIONS] NAME
|
||||
|
||||
Options:
|
||||
--formal / --no-formal
|
||||
--help Show this message and exit.
|
||||
```
|
||||
|
||||
Notice how it automatically creates a `--formal` and `--no-formal` for your `bool` *option*.
|
||||
// Automatic --formal and --no-formal for the bool option 🎉
|
||||
|
||||
---
|
||||
// And if you use it with the hello command
|
||||
|
||||
And of course, if you use it, it does what you expect:
|
||||
$ python main.py hello Camila
|
||||
|
||||
```bash
|
||||
python main.py hello Camila
|
||||
```
|
||||
|
||||
shows:
|
||||
|
||||
```
|
||||
Hello Camila
|
||||
```
|
||||
|
||||
Then:
|
||||
// And with the goodbye command
|
||||
|
||||
```bash
|
||||
python main.py goodbye Camila
|
||||
```
|
||||
$ python main.py goodbye Camila
|
||||
|
||||
shows:
|
||||
|
||||
```
|
||||
Bye Camila!
|
||||
```
|
||||
|
||||
And:
|
||||
// And with --formal
|
||||
|
||||
```bash
|
||||
python main.py goodbye --formal Camila
|
||||
```
|
||||
$ python main.py goodbye --formal Camila
|
||||
|
||||
shows:
|
||||
|
||||
```
|
||||
Goodbye Ms. Camila. Have a good day.
|
||||
```
|
||||
</div>
|
||||
|
||||
### Recap
|
||||
|
||||
|
|
76
docs/js/custom.js
Normal file
76
docs/js/custom.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
document.querySelectorAll(".use-termynal").forEach(node => {
|
||||
node.style.display = "block";
|
||||
new Termynal(node);
|
||||
});
|
||||
const progressLiteralStart = "---> 100%";
|
||||
const promptLiteralStart = "$ ";
|
||||
const termynalActivateClass = "termy";
|
||||
|
||||
function loadVisibleTermynals() {
|
||||
document
|
||||
.querySelectorAll(`.${termynalActivateClass} .codehilite`)
|
||||
.forEach(node => {
|
||||
if (node.getBoundingClientRect().top - innerHeight <= 0) {
|
||||
const text = node.textContent;
|
||||
const lines = text.split("\n");
|
||||
const useLines = [];
|
||||
let buffer = [];
|
||||
function saveBuffer() {
|
||||
if (buffer.length) {
|
||||
let isBlankSpace = true
|
||||
buffer.forEach(line => {
|
||||
if (line) {
|
||||
isBlankSpace = false
|
||||
}
|
||||
})
|
||||
dataValue = {}
|
||||
if (isBlankSpace) {
|
||||
dataValue["delay"] = 0
|
||||
}
|
||||
if (buffer.length > 1 && buffer[buffer.length - 1] === "") {
|
||||
// The last single <br> won't have effect
|
||||
// so put an additional one
|
||||
buffer.push("");
|
||||
}
|
||||
const bufferValue = buffer.join("<br>");
|
||||
dataValue["value"] = bufferValue
|
||||
useLines.push(dataValue);
|
||||
buffer = [];
|
||||
}
|
||||
}
|
||||
for (let line of lines) {
|
||||
if (line === progressLiteralStart) {
|
||||
saveBuffer();
|
||||
useLines.push({
|
||||
type: "progress"
|
||||
});
|
||||
} else if (line.startsWith(promptLiteralStart)) {
|
||||
saveBuffer();
|
||||
const value = line.replace(promptLiteralStart, "").trimEnd();
|
||||
useLines.push({
|
||||
type: "input",
|
||||
value: value
|
||||
});
|
||||
} else if (line.startsWith("// ")) {
|
||||
saveBuffer();
|
||||
const value = line.replace("// ", "").trimEnd();
|
||||
useLines.push({
|
||||
value: value,
|
||||
class: "termynal-comment",
|
||||
delay: 0
|
||||
});
|
||||
} else {
|
||||
buffer.push(line);
|
||||
}
|
||||
}
|
||||
saveBuffer();
|
||||
const div = document.createElement("div");
|
||||
node.replaceWith(div);
|
||||
const termynal = new Termynal(div, {
|
||||
lineData: useLines
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
window.addEventListener("scroll", loadVisibleTermynals);
|
||||
loadVisibleTermynals();
|
202
docs/js/termynal.js
Normal file
202
docs/js/termynal.js
Normal file
|
@ -0,0 +1,202 @@
|
|||
/**
|
||||
* 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) {
|
||||
// Custom add class
|
||||
if (prop === 'class') {
|
||||
attrs += ` class=${line[prop]} `
|
||||
continue
|
||||
}
|
||||
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))
|
||||
}
|
|
@ -53,3 +53,11 @@ extra:
|
|||
link: 'https://medium.com/@tiangolo'
|
||||
- type: 'globe'
|
||||
link: 'https://tiangolo.com'
|
||||
|
||||
extra_css:
|
||||
- 'css/termynal.css'
|
||||
- 'css/custom.css'
|
||||
|
||||
extra_javascript:
|
||||
- 'js/termynal.js'
|
||||
- 'js/custom.js'
|
||||
|
|
Loading…
Reference in a new issue