first commit
This commit is contained in:
123
xterminal/docs/.vitepress/config.ts
Normal file
123
xterminal/docs/.vitepress/config.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { defineConfig } from "vitepress";
|
||||
|
||||
const TITLE = "XTerminal";
|
||||
const DESCRIPTION = "Build web-based command line interfaces";
|
||||
const IMAGE = "/logo.svg";
|
||||
const LINK = "https://henryhale.github.io/xterminal";
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
base: "/",
|
||||
|
||||
// metadata
|
||||
lang: "en-US",
|
||||
title: TITLE,
|
||||
description: DESCRIPTION,
|
||||
|
||||
head: [
|
||||
// favicon
|
||||
["link", { rel: "shortcut icon", href: IMAGE }],
|
||||
|
||||
// open graph - facebook
|
||||
["meta", { property: "og:type", content: "website" }],
|
||||
["meta", { property: "og:url", content: LINK }],
|
||||
["meta", { property: "og:title", content: TITLE }],
|
||||
["meta", { property: "og:description", content: DESCRIPTION }],
|
||||
["meta", { property: "og:image", content: IMAGE }],
|
||||
|
||||
// twitter
|
||||
["meta", { property: "twitter:card", content: "summary_large_image" }],
|
||||
["meta", { property: "twitter:url", content: LINK }],
|
||||
["meta", { property: "twitter:title", content: TITLE }],
|
||||
["meta", { property: "twitter:description", content: DESCRIPTION }],
|
||||
["meta", { property: "twitter:image", content: IMAGE }]
|
||||
],
|
||||
|
||||
// theme
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
|
||||
siteTitle: TITLE,
|
||||
logo: IMAGE,
|
||||
|
||||
search: {
|
||||
provider: "local"
|
||||
},
|
||||
|
||||
nav: [
|
||||
{ text: "Home", link: "/" },
|
||||
{ text: "Guide", link: "/guide/" },
|
||||
{ text: "Demo", link: "/demo/" },
|
||||
{ text: "Showcase", link: "/showcase/" },
|
||||
{
|
||||
text: "About",
|
||||
items: [
|
||||
{ text: "Team", link: "/about/team" },
|
||||
{ text: "History", link: "/about/history" },
|
||||
{ text: "Code of Conduct", link: "/about/coc" }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
sidebar: [
|
||||
{
|
||||
text: "Guide",
|
||||
items: [{ text: "Introduction", link: "/guide/" }]
|
||||
},
|
||||
{
|
||||
text: "Getting Started",
|
||||
items: [
|
||||
{ text: "Installation", link: "/guide/installation" },
|
||||
{ text: "Quick Start", link: "/guide/quick-start" }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "Essentials",
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: "Initialization", link: "/guide/initialization" },
|
||||
{ text: "Output", link: "/guide/output" },
|
||||
{ text: "Events", link: "/guide/events" },
|
||||
{ text: "Input", link: "/guide/prompt" },
|
||||
{ text: "History", link: "/guide/history" },
|
||||
{ text: "Key Bindings", link: "/guide/keybindings" }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "Advanced",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "AutoComplete", link: "/guide/autocomplete" },
|
||||
{ text: "Batch Mode", link: "/guide/batchmode" },
|
||||
{ text: "Disposal", link: "/guide/disposal" },
|
||||
{ text: "Theme", link: "/guide/theme" }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "API Reference",
|
||||
link: "/api/",
|
||||
items: []
|
||||
}
|
||||
],
|
||||
|
||||
socialLinks: [
|
||||
{ icon: "github", link: "https://github.com/henryhale/xterminal" }
|
||||
],
|
||||
|
||||
editLink: {
|
||||
text: "Edit this page on GitHub",
|
||||
pattern:
|
||||
"https://github.com/henryhale/xterminal/edit/master/docs/:path"
|
||||
},
|
||||
|
||||
footer: {
|
||||
message:
|
||||
'Released under the <a href="https://github.com/henryhale/xterminal/blob/master/LICENSE.txt">MIT License</a>.',
|
||||
copyright: `Copyright © 2023-present, <a href="https://github.com/henryhale">Henry Hale</a>.`
|
||||
},
|
||||
|
||||
outline: {
|
||||
level: [2, 3]
|
||||
}
|
||||
}
|
||||
});
|
||||
24
xterminal/docs/.vitepress/theme/assets/styles.css
Normal file
24
xterminal/docs/.vitepress/theme/assets/styles.css
Normal file
@@ -0,0 +1,24 @@
|
||||
:root {
|
||||
--vp-c-brand-1: var(--vp-c-green-1);
|
||||
--vp-c-brand-2: var(--vp-c-green-2);
|
||||
--vp-button-brand-bg: var(--vp-c-brand-2);
|
||||
--vp-home-hero-image-background-image: linear-gradient(180deg, var(--vp-home-hero-name-color) 50%,#272b33 50%);
|
||||
--vp-home-hero-image-filter: blur(40px);
|
||||
}
|
||||
|
||||
.VPImage {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.VPImage.image-src {
|
||||
width: min(25vw, 200px);
|
||||
height: min(25vw, 200px);
|
||||
}
|
||||
|
||||
.image-src {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.dark .vp-doc .custom-block a {
|
||||
transition: color 0.25s;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
hidelabel: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!props.hidelabel" class="bp-preview">
|
||||
<b>Result: </b>
|
||||
</div>
|
||||
<div class="bp-container">
|
||||
<header class="bp-header">
|
||||
<div class="bp-dots">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
<div class="bp-title">My First Terminal</div>
|
||||
</header>
|
||||
<main class="bp-main">
|
||||
<slot></slot>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
*,
|
||||
*::after,
|
||||
*::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bp-preview {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.bp-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 6px;
|
||||
background-color: var(--vp-code-block-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.bp-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
.bp-header > .bp-title {
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
font-size: min(15px, calc(1.5vw + 7px));
|
||||
}
|
||||
.bp-header .bp-dots > * {
|
||||
display: inline-block;
|
||||
width: min(0.65rem, calc(2.25vw));
|
||||
height: min(0.65rem, calc(2.25vw));
|
||||
border-radius: 50%;
|
||||
margin-right: min(0.25rem, calc(1vw));
|
||||
}
|
||||
.bp-header .bp-dots > *:first-child {
|
||||
background-color: rgba(255, 91, 82, 1);;
|
||||
}
|
||||
.bp-header .bp-dots > *:nth-child(2) {
|
||||
background-color: rgba(83, 195, 43, 1);
|
||||
}
|
||||
.bp-header .bp-dots > *:last-child {
|
||||
background-color: rgba(230, 192, 41, 1);
|
||||
}
|
||||
|
||||
.bp-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 400px;
|
||||
overflow-y: hidden;
|
||||
padding: 0 min(15px, calc(1.5vw + 5px));
|
||||
background-color: rgba(225,225,225,0.05);
|
||||
}
|
||||
|
||||
html.dark .bp-main {
|
||||
background-color: rgb(41, 40, 40);
|
||||
}
|
||||
html.dark .bp-container {
|
||||
color: rgba(239, 239, 239, 0.85);
|
||||
}
|
||||
</style>
|
||||
73
xterminal/docs/.vitepress/theme/components/ProjectCards.vue
Normal file
73
xterminal/docs/.vitepress/theme/components/ProjectCards.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<script setup>
|
||||
const props = defineProps(["projects"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid">
|
||||
<div class="intro">
|
||||
<h1><b>Showcase</b></h1>
|
||||
<p>Below is a list of projects that are using XTerminal.</p>
|
||||
<p>
|
||||
To add yours, edit this
|
||||
<a
|
||||
href="https://github.com/henryhale/xterminal/blob/master/docs/showcase/index.md"
|
||||
>file</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
<div v-for="(p, i) in projects" :key="i" class="row">
|
||||
<div class="grow">
|
||||
<div class="row">
|
||||
<img :src="p.logo" :alt="p.name" width="45" />
|
||||
<span>
|
||||
{{ p.name }}<br />{{ p.desc }}<br />
|
||||
<a :href="p?.author.link">@{{ p?.author.username }}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a :href="p.link">Visit →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 4rem 1rem;
|
||||
}
|
||||
|
||||
.grid > * {
|
||||
margin-bottom: 2rem;
|
||||
width: 100%;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.row > *:not(:last-child) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.intro h1 {
|
||||
font-size: 32px;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 40px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
</style>
|
||||
16
xterminal/docs/.vitepress/theme/index.ts
Normal file
16
xterminal/docs/.vitepress/theme/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import DefaultTheme from "vitepress/theme";
|
||||
|
||||
// @ts-ignore
|
||||
import BrowserPreview from "./components/BrowserPreview.vue";
|
||||
// @ts-ignore
|
||||
import ProjectCards from "./components/ProjectCards.vue";
|
||||
|
||||
import "./assets/styles.css";
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
enhanceApp(ctx) {
|
||||
ctx.app.component('BrowserPreview', BrowserPreview);
|
||||
ctx.app.component('ProjectCards', ProjectCards);
|
||||
}
|
||||
};
|
||||
128
xterminal/docs/about/coc.md
Normal file
128
xterminal/docs/about/coc.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[@devhenryhale](https://twitter.com/devhenryhale) on Twitter.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
5
xterminal/docs/about/history.md
Normal file
5
xterminal/docs/about/history.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# History
|
||||
|
||||
The story behind the development of this project.
|
||||
|
||||
Coming soon...
|
||||
61
xterminal/docs/about/team.md
Normal file
61
xterminal/docs/about/team.md
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
layout: page
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
VPTeamPage,
|
||||
VPTeamPageTitle,
|
||||
VPTeamMembers,
|
||||
VPTeamPageSection
|
||||
} from 'vitepress/theme';
|
||||
|
||||
const members = [
|
||||
{
|
||||
name: 'Henry Hale',
|
||||
title: 'Creator',
|
||||
avatar: 'https://www.github.com/henryhale.png',
|
||||
org: 'xterminal',
|
||||
orgLink: 'https://github.com/henryhale/xterminal',
|
||||
links: [
|
||||
{
|
||||
icon: 'github',
|
||||
link: 'https://github.com/henryhale'
|
||||
},
|
||||
{
|
||||
icon: 'twitter',
|
||||
link: 'https://twitter.com/devhenryhale'
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
const contributors = [
|
||||
{
|
||||
name: 'Enzo Notario',
|
||||
title: 'Full Stack Developer',
|
||||
avatar: 'https://www.github.com/enzonotario.png',
|
||||
links: [
|
||||
{
|
||||
icon: 'github',
|
||||
link: 'https://github.com/enzonotario'
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<VPTeamPage>
|
||||
<VPTeamPageTitle>
|
||||
<template #title>Our Team</template>
|
||||
<template #lead>Say hello to our awesome team.</template>
|
||||
</VPTeamPageTitle>
|
||||
<VPTeamMembers :members="members" />
|
||||
<VPTeamPageSection>
|
||||
<template #title>Contributors</template>
|
||||
<template #lead>A big shout out to these awesome people</template>
|
||||
<template #members>
|
||||
<VPTeamMembers size="small" :members="contributors" />
|
||||
</template>
|
||||
</VPTeamPageSection>
|
||||
</VPTeamPage>
|
||||
663
xterminal/docs/api/index.md
Normal file
663
xterminal/docs/api/index.md
Normal file
@@ -0,0 +1,663 @@
|
||||
# API Reference
|
||||
|
||||
### Application
|
||||
|
||||
- [XTerminal](#xterminal) extends [XEventEmitter](#xeventemitter)
|
||||
- [XTerminal.version](#xterminal-version)
|
||||
- [XTerminal.XEventEmitter](#xterminal-xeventemitter)
|
||||
- [term.mount()](#term-mount)
|
||||
- [term.dispose()](#term-dispose)
|
||||
|
||||
### Input
|
||||
|
||||
- [term.focus()](#term-focus)
|
||||
- [term.blur()](#term-blur)
|
||||
- [term.pause()](#term-pause)
|
||||
- [term.resume()](#term-resume)
|
||||
- [term.setInput()](#term-setinput)
|
||||
- [term.clearInput()](#term-clearinput)
|
||||
- [term.setCompleter()](#term-setcompleter)
|
||||
|
||||
### Output
|
||||
|
||||
- [XTerminal.escapeHTML()](#xterminal-escapehtml)
|
||||
- [term.write()](#term-write)
|
||||
- [term.writeln()](#term-writeln)
|
||||
- [term.writeSafe()](#term-writesafe)
|
||||
- [term.writelnSafe()](#term-writelnsafe)
|
||||
- [term.clear()](#term-clear)
|
||||
- [term.clearLast()](#term-clearlast)
|
||||
|
||||
### History
|
||||
|
||||
- [term.history](#term-history)
|
||||
- [term.clearHistory()](#term-clearhistory)
|
||||
|
||||
---
|
||||
|
||||
## XEventEmitter
|
||||
|
||||
Event emitter
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
// Event Identifier
|
||||
type IEventName = string | symbol;
|
||||
|
||||
// Event Listener
|
||||
type IEventListener = (...args: unknown[]) => void;
|
||||
|
||||
type IEventHandler = (ev: IEventName, listener: IEventListener) => void;
|
||||
|
||||
interface IEventEmitter {
|
||||
on: IEventHandler;
|
||||
once: IEventHandler;
|
||||
off: IEventHandler;
|
||||
emit(ev: IEventName, ...args: unknown[]): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
**Methods**
|
||||
|
||||
`on`: Appends a event listener to the specified event
|
||||
|
||||
`once`: Appends a **one-time** event listener to the specified event
|
||||
|
||||
`off`: Removes an event listener from the specified event
|
||||
|
||||
`emit`: Triggers an event with arguments if any
|
||||
|
||||
- **See also:** [Guide - Events](../guide/events.md)
|
||||
|
||||
## XTerminal
|
||||
|
||||
Creates a terminal instance
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
// ...
|
||||
}
|
||||
|
||||
interface TerminalOptions {
|
||||
target: HTMLElement | string;
|
||||
}
|
||||
|
||||
class Terminal extends XEventEmitter implements XTerminal {
|
||||
constructor(options: TerminalOptions);
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
The constructor takes one argument, `options` containing the `target` element reference.
|
||||
|
||||
If the `target` element is provided, the [term.mount()](#term-mount) method is called automatically.
|
||||
|
||||
- **Example**
|
||||
|
||||
```js
|
||||
import XTerminal from 'xterminal';
|
||||
|
||||
const term = new XTerminal({/* options */});
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Creating a Terminal](../guide/initialization.md#creating-your-first-terminal)
|
||||
|
||||
## XTerminal.version
|
||||
|
||||
The version number
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
readonly version: string;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
This is a static property that stores the version used. It is important when reporting bugs or issues.
|
||||
|
||||
- **Example**
|
||||
|
||||
```js
|
||||
import XTerminal from 'xterminal';
|
||||
|
||||
console.log(XTerminal.version);
|
||||
```
|
||||
|
||||
## XTerminal.XEventEmitter
|
||||
|
||||
The event emitter class
|
||||
|
||||
- **Type**
|
||||
|
||||
Same as [XEventEmitter](#xeventemitter).
|
||||
|
||||
- **Details**
|
||||
|
||||
This is a static property (class) that can be used to create independent instances of the event emitter
|
||||
|
||||
- **Example**
|
||||
|
||||
```js
|
||||
const emitter = new XTerminal.XEventEmitter();
|
||||
```
|
||||
|
||||
## XTerminal.escapeHTML()
|
||||
|
||||
Escapes user input so it can be safely rendered as HTML text.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
static escapeHTML(data?: string): string;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
It preserves all characters by converting them to HTML entities where needed.
|
||||
This is **recommended** for use on user input or any arbitrary data.
|
||||
|
||||
- **Example**
|
||||
|
||||
```js
|
||||
XTerminal.escapeHTML("<b>hello</b>");
|
||||
// => <b>hello</b>
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Safe Output](../guide/output.md#safe-output)
|
||||
|
||||
## term.mount()
|
||||
|
||||
Mounts the terminal instance structure to the specified DOM element.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
mount(target: HTMLElement | string): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
It takes one argument that must be an actual DOM element or a CSS selector. The element's `innerHTML` is cleared first and then the terminal structure is rendered.
|
||||
|
||||
If no argument is passed, it throws an error and nothing is rendered.
|
||||
|
||||
The `term.mount()` method should only be called once for each terminal instance only if the `target` element option in the [constructor](#xterminal) is not provided.
|
||||
|
||||
- **Example**
|
||||
|
||||
```js
|
||||
import XTerminal from 'xterminal';
|
||||
|
||||
const term = new XTerminal();
|
||||
term.mount('#app');
|
||||
```
|
||||
|
||||
or mount to an actual DOM element directly:
|
||||
|
||||
```js
|
||||
term.mount(
|
||||
document.getElementById('app')
|
||||
);
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Creating a Terminal](../guide/initialization.md#creating-your-first-terminal)
|
||||
|
||||
|
||||
## term.dispose()
|
||||
|
||||
Gracefully close the terminal instance.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
dispose(): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
This detaches all event listeners, unmounts the terminal from the DOM and clears the backing functionality of the terminal.
|
||||
|
||||
_The terminal should not be used again once disposed._
|
||||
|
||||
- **Example**
|
||||
|
||||
Dispose on window unload event
|
||||
|
||||
```js
|
||||
window.onunload = () => term.dispose();
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Disposal](../guide/disposal.md)
|
||||
|
||||
## term.focus()
|
||||
|
||||
Focus the terminal input component - ready for input.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
focus(): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
This method takes no argument. It focuses the underlying input component of the terminal.
|
||||
|
||||
Clicking or tapping in the terminal also invokes the method.
|
||||
|
||||
- **Example**
|
||||
|
||||
After mounting the terminal instance
|
||||
|
||||
```js
|
||||
term.focus();
|
||||
```
|
||||
|
||||
## term.blur()
|
||||
|
||||
Blurs the terminal input component.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
blur(): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
This method blurs the input component of the terminal.
|
||||
|
||||
- **Example**
|
||||
|
||||
```js
|
||||
term.blur();
|
||||
```
|
||||
|
||||
## term.pause()
|
||||
|
||||
Deactivate the terminal input component.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
pause(): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
This method will stop events and input from being written to the terminal but rather input will be buffered.
|
||||
|
||||
**NB:** It is used in conjuction with [term.resume()](#term-resume).
|
||||
|
||||
- **Example**
|
||||
|
||||
Prevent a user from sending input (non-interactive mode)
|
||||
|
||||
```js
|
||||
term.pause();
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Pause & Resume](../guide/prompt.md#pause-resume)
|
||||
|
||||
## term.resume()
|
||||
|
||||
Activate the terminal input component
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
resume(): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
This method will enable events dispatch and user input if they were deactivate using [term.pause()](#term-pause).
|
||||
|
||||
- **Example**
|
||||
|
||||
Pause the terminal until user input is required
|
||||
|
||||
```js
|
||||
term.pause();
|
||||
// ...
|
||||
// do something
|
||||
// ...
|
||||
term.resume();
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Pause & Resume](../guide/prompt.md#pause-resume)
|
||||
|
||||
## term.setInput()
|
||||
|
||||
Sets the value of the terminal input buffer
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
setInput(value: string): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
This method will set/modify the contents of the input buffer.
|
||||
|
||||
- **Example**
|
||||
|
||||
Presetting some input when the terminal is loaded or resumed
|
||||
|
||||
```js
|
||||
term.setInput("echo 'Hello World'");
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Set & Clear](../guide/prompt.md#set-clear)
|
||||
|
||||
## term.clearInput()
|
||||
|
||||
Clears the terminal input buffer
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
clearInput(): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
This method will empty/clear the contents of the input buffer.
|
||||
|
||||
- **Example**
|
||||
|
||||
Clearing the input buffer when the terminal [resumes](#term-resume)
|
||||
|
||||
```js
|
||||
term.clearInput();
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Set & Clear](../guide/prompt.md#set-clear)
|
||||
|
||||
## term.setCompleter()
|
||||
|
||||
Sets the autocomplete function that is invoked on Tab key.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
setCompleter(fn: (data: string) => string): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
This method take one argument that is a function which takes a string parameter and returns a string.
|
||||
|
||||
The autocomplete functionality depends highly on the completer function `fn`.
|
||||
|
||||
The `fn` parameter should return a better match for the input data string.
|
||||
|
||||
- **Example**
|
||||
|
||||
```js
|
||||
term.setCompleter(data => {
|
||||
const options = ['.help', '.clear', '.exit'];
|
||||
return options.filter(s => s.startsWith(data))[0] || '';
|
||||
});
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Autocomplete](../guide/autocomplete.md)
|
||||
|
||||
## term.write()
|
||||
|
||||
Write data to the terminal.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
write(data: string | number, callback?: () => void): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
`data`: The data to write to the terminal
|
||||
|
||||
`callback`: Optional function invoked on successful write
|
||||
|
||||
- **Example**
|
||||
|
||||
```js
|
||||
term.write('John: Hello ');
|
||||
term.write('from the Eastside', () => console.log('Done!'));
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Output](../guide/output.md#output)
|
||||
|
||||
## term.writeln()
|
||||
|
||||
Write data to the terminal, followed by a break line character (\n).
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
writeln(data: string | number, callback?: () => void): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
`data`: The data to write to the terminal
|
||||
|
||||
`callback`: Optional function invoked on successful write
|
||||
|
||||
- **Example**
|
||||
|
||||
```js
|
||||
term.writeln('Hello World!');
|
||||
term.writeln('Welcome!', () => console.log('Done!'));
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Output](../guide/output.md#output)
|
||||
|
||||
|
||||
## term.writeSafe()
|
||||
|
||||
Securely write data to the terminal.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
writeSafe(data: string | number, callback?: () => void): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
`data`: The data to write to the terminal
|
||||
|
||||
`callback`: Optional function invoked on successful write
|
||||
|
||||
- **Example**
|
||||
|
||||
```js
|
||||
term.writeSafe('<h1>hello</h1>');
|
||||
// <h1>hello</h1>
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Output](../guide/output.md#output)
|
||||
|
||||
## term.writelnSafe()
|
||||
|
||||
Securely write data to the terminal, followed by a break line character (\n).
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
writelnSafe(data: string | number, callback?: () => void): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
`data`: The data to write to the terminal
|
||||
|
||||
`callback`: Optional function invoked on successful write
|
||||
|
||||
- **Example**
|
||||
|
||||
```js
|
||||
term.writelnSafe('<h1>hello</h1>');
|
||||
// <h1>hello</h1><br/>
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Output](../guide/output.md#output)
|
||||
|
||||
## term.clear()
|
||||
|
||||
Clear the entire terminal.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
clear(): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
When invoked, the entire terminal output is cleared.
|
||||
|
||||
This method also triggers the `clear` event.
|
||||
|
||||
- **Example**
|
||||
|
||||
Clear on CTRL+L using [keypress](../guide/events.md#default-events) event
|
||||
|
||||
```js
|
||||
term.on('keypress', e => {
|
||||
if (e.key == 'l' && e.ctrlKey) {
|
||||
term.clear();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Example using events](../guide/events.md#example)
|
||||
- **See also:** [Guide - Output](../guide/output.md#clear-screen)
|
||||
|
||||
## term.clearLast()
|
||||
|
||||
Remove the element containing the previous output.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
clearLast(): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
This is like the undo for only one write operation.
|
||||
|
||||
- **Example**
|
||||
|
||||
Greet with `Hello World` and replace it with `Hello Dev` after 5 seconds
|
||||
|
||||
```js
|
||||
term.writeln('Hello World!');
|
||||
setTimeout(() => {
|
||||
term.clearLast();
|
||||
term.write('Hello Dev!');
|
||||
}, 5000);
|
||||
```
|
||||
|
||||
- **See also:** [Guide - Output](../guide/output.md#clear-last-output)
|
||||
|
||||
|
||||
## term.history
|
||||
|
||||
Access the history stack.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
history: string[];
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
Manages an array of entries in the history stack.
|
||||
|
||||
- **Example**
|
||||
|
||||
Log the history whenever a new entry is added
|
||||
|
||||
```js
|
||||
term.on('data', () => console.log(term.history));
|
||||
```
|
||||
|
||||
- **See also:** [History](../guide/history.md)
|
||||
|
||||
## term.clearHistory()
|
||||
|
||||
Clear the entire history stack.
|
||||
|
||||
- **Type**
|
||||
|
||||
```ts
|
||||
interface XTerminal {
|
||||
clearHistory(): void;
|
||||
}
|
||||
```
|
||||
|
||||
- **Details**
|
||||
|
||||
It take no argument as its sole role is to clear the entire local history of inputs which are accessible iteratively using `ArrowUp` and `ArrowDown` keys.
|
||||
|
||||
- **Example**
|
||||
|
||||
Clear history on `CTRL+H` using [keypress](../guide/events.md#default-events) event
|
||||
|
||||
```js
|
||||
term.on('keypress', e => {
|
||||
if (e.ctrlKey && e.key == 'h') {
|
||||
term.clearHistory();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- **See also:** [History](../guide/history.md)
|
||||
72
xterminal/docs/demo/index.md
Normal file
72
xterminal/docs/demo/index.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Live Demo
|
||||
|
||||
---
|
||||
|
||||
<style>
|
||||
iframe { border: 0 none; }
|
||||
.demo .bp-main { padding: 0; }
|
||||
</style>
|
||||
|
||||
<div class="demo">
|
||||
|
||||
<a href="../demo.html" target="_blank" rel="noreferrer">View fullpage demo</a>
|
||||
|
||||
<browser-preview hidelabel>
|
||||
|
||||
<iframe src="../demo.html" height="400px"></iframe>
|
||||
|
||||
</browser-preview>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
:::details Code
|
||||
|
||||
:::code-group
|
||||
|
||||
```css [styles.css]
|
||||
@import url('https://unpkg.com/xterminal/dist/xterminal.css');
|
||||
|
||||
.error {
|
||||
color: rgb(248, 88, 88);
|
||||
}
|
||||
|
||||
.spinner:after {
|
||||
animation: changeContent 0.8s linear infinite;
|
||||
content: "⠋";
|
||||
}
|
||||
|
||||
@keyframes changeContent {
|
||||
10% { content: "⠙"; }
|
||||
20% { content: "⠹"; }
|
||||
30% { content: "⠸"; }
|
||||
40% { content: "⠼"; }
|
||||
50% { content: "⠴"; }
|
||||
60% { content: "⠦"; }
|
||||
70% { content: "⠧"; }
|
||||
80% { content: "⠇"; }
|
||||
90% { content: "⠏"; }
|
||||
}
|
||||
```
|
||||
|
||||
```html [index.html]
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="https://unpkg.com/xterminal/dist/xterminal.umd.js"></script>
|
||||
|
||||
<script src="createShell.js"></script>
|
||||
<script src="createTerminal.js"></script>
|
||||
|
||||
<script>
|
||||
window.onload = () => createTerminal('#app');
|
||||
</script>
|
||||
```
|
||||
|
||||
<<< @/public/demo.js#terminal{js:line-numbers} [createTerminal.js]
|
||||
|
||||
<<< @/public/demo.js#shell{js:line-numbers} [createShell.js]
|
||||
|
||||
:::
|
||||
54
xterminal/docs/guide/autocomplete.md
Normal file
54
xterminal/docs/guide/autocomplete.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# AutoComplete
|
||||
|
||||
Tab autocompletion saves a lot of time. It enables the user to type less of what
|
||||
they need thereby being not only interactive but also productive.
|
||||
|
||||
## How does it work?
|
||||
|
||||
The user inputs data in the terminal and presses the `Enter` key, the input is saved in an internal history stack (accessible as an array). When the user types a partial string of an already input string, then presses the `Tab` key, you can loop through the history array for matches and set the most recent one as the input value iteratively.
|
||||
|
||||
:::tip
|
||||
In addition to that, you can also include an external list of strings to use when matching.
|
||||
:::
|
||||
|
||||
## Implementation
|
||||
|
||||
To implement the above methodology, you need the [term.history](../api/index.md#history) which provide an copy of the entries.
|
||||
|
||||
Create and add the basic autocomplete function using [term.setCompleter()](../api/index.md#term-setcompleter).
|
||||
|
||||
```js
|
||||
const matches = [];
|
||||
|
||||
term.setCompleter(str => {
|
||||
if (!matches.length) {
|
||||
matches.push(
|
||||
...term.history.filter(c => c.startsWith(str))
|
||||
);
|
||||
}
|
||||
return matches.pop();
|
||||
});
|
||||
```
|
||||
|
||||
The `matches` array is dynamic as it only keeps strings that start with the partial string `str`. The value on top of the stack, `matches`, is retrieved one at a time until it is empty thereby generating a new list of matched strings.
|
||||
|
||||
At this point, typing a few inputs, followed by the `Enter` key appends the input to our history stack. Typing a partial, followed by the `Tab` key, should do the job.
|
||||
|
||||
## Illustration
|
||||
|
||||
Take the following log as a sample, we can test the tab autocompletion after typing a partial `h`
|
||||
|
||||
<browser-preview>
|
||||
|
||||
[user] $ help
|
||||
help
|
||||
[user] $ hack
|
||||
hack
|
||||
[user] $ ls
|
||||
ls
|
||||
[user] $ history
|
||||
history
|
||||
[user] $ h▊
|
||||
</browser-preview>
|
||||
|
||||
Press `Tab` key just once sets the input value to `history`. Then `hack` after another hit, and finally `help`. Deleting two characters from the input string `help` leaves `he`, pressing the `Tab` key once more only moves on cycle to `help`.
|
||||
65
xterminal/docs/guide/batchmode.md
Normal file
65
xterminal/docs/guide/batchmode.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Batch Mode
|
||||
|
||||
This refers to the non interactive mode where the user does not input anything and the terminal receives input from elsewhere.
|
||||
|
||||
## Implementation
|
||||
|
||||
Suppose that you want the users to only have a readonly interface or you would like to take control from the user for awhile, here is how you can achieve that;
|
||||
|
||||
- Pause the terminal input using [term.pause()](./prompt.md#pause-resume)
|
||||
|
||||
- Trigger events manually with arguments using [term.emit()](./events.md#arguments)
|
||||
|
||||
By default, the `data` event is triggered by the user input followed by `Enter` key. You can manually trigger the `data` event to mimic user interaction.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
// no user interactivity
|
||||
term.pause();
|
||||
|
||||
// executes command
|
||||
function handleInput(command) {
|
||||
switch (command) {
|
||||
case 'install':
|
||||
// ...
|
||||
console.log('installing...');
|
||||
// ...
|
||||
break;
|
||||
case 'commit':
|
||||
// ...
|
||||
console.log('commiting changes...');
|
||||
// ...
|
||||
break;
|
||||
case 'fetch':
|
||||
// ...
|
||||
console.log('fetching state...');
|
||||
// ...
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// register input callback
|
||||
term.on('data', handleInput);
|
||||
|
||||
// demo shell script
|
||||
const script = `
|
||||
# install deps...
|
||||
install
|
||||
|
||||
# save changes...
|
||||
commit
|
||||
|
||||
# load state/resource...
|
||||
fetch
|
||||
`;
|
||||
|
||||
// run it
|
||||
for (const line of script.split(/\n/)) {
|
||||
// skip empty lines and comments
|
||||
if (!line || line.startsWith("#")) continue;
|
||||
// execute line
|
||||
term.emit('data', line);
|
||||
}
|
||||
```
|
||||
58
xterminal/docs/guide/disposal.md
Normal file
58
xterminal/docs/guide/disposal.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Disposal
|
||||
|
||||
Nearly everything that makes up the terminal is disposable. Right from the base class to the events that are dispatched, all these can be disposed.
|
||||
|
||||
## Why dispose?
|
||||
|
||||
The `XTerminal` package is lightweight and on top of that, its efficiency during runtime is greatly considered.
|
||||
|
||||
A disposable object refers to an object that can self detach itself from a parent via a [term.dispose()](../api/index.md#term-dispose) method.
|
||||
|
||||
With reference to the DOM, remember how `document.addEventListener` and `document.removeEventListener` work: one adds an event callback function and the other destroys it from the same event.
|
||||
|
||||
It is nearly the same here.
|
||||
|
||||
The significance of the `dispose` method on an object is not only to manage memory but also ensure that certain functionality only runs at specific times it is needed.
|
||||
|
||||
## Example
|
||||
|
||||
Suppose you have multiple instances of objects with each maintaining it's own state. When the use of the instance is done, we can then dispose its state thereby gracefully saving memory.
|
||||
|
||||
We can implement it like this:
|
||||
|
||||
```js
|
||||
const states = new WeakMap();
|
||||
|
||||
class State {
|
||||
// ...
|
||||
}
|
||||
|
||||
function createState(app) {
|
||||
states.set(app, new State());
|
||||
let disposed = false;
|
||||
return {
|
||||
get state() {
|
||||
return states.get(app);
|
||||
},
|
||||
dispose() {
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
states.delete(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Whenever a new state is created using `createState` from the above example, a _disposable state object_ is returned. This implies that when the `dispose` method on that object is invoked, the entire state for that app is deleted.
|
||||
|
||||
## Terminal Disposal
|
||||
|
||||
It is possible that you might want to close off the terminal and end its usage. In this case, you can entirely dispose the terminal using [term.dispose()](../api/index.md#term-dispose). This will clear states of the underlying objects, dispose events, remove the HTML elements and their DOM events.
|
||||
|
||||
This tears down the entire terminal and renders it not usable thereafter.
|
||||
|
||||
**Example:** On window unload event (free up resources)
|
||||
|
||||
```js
|
||||
window.onunload = () => term.dispose();
|
||||
```
|
||||
179
xterminal/docs/guide/events.md
Normal file
179
xterminal/docs/guide/events.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Events
|
||||
|
||||
The [XTerminal](../api/index.md#xterminal) class, from which we create an instance, extends an internal [EventEmitter](../api/index.md#xeventemitter) class.
|
||||
This implies that we can handle events the same way the browser does to provide interaction through events like:
|
||||
click, keydown, and so on.
|
||||
|
||||
The underlying [EventEmitter](../api/index.md#xeventemitter) exposes, the `on`, `off`, `once`, and `emit` methods.
|
||||
|
||||
- `on` is used to add an event listener that's executed when the event is triggered
|
||||
- `off` is used to remove an event listener from an event
|
||||
- `once` is used to add a one-time event listener, it is triggered only once and then removed using `off`
|
||||
- `emit` is used to trigger an event
|
||||
|
||||
## Custom Events
|
||||
|
||||
Create a `start` event, and as a matter of providing an example, the reaction to the event is a simply outputting to the terminal.
|
||||
|
||||
```js
|
||||
term.on('start', () => {
|
||||
term.writeln('started...');
|
||||
});
|
||||
```
|
||||
|
||||
When we run the `emit` method passing the `start` event,
|
||||
|
||||
```js
|
||||
term.emit('start');
|
||||
```
|
||||
|
||||
the event handler function is triggered, and we get the terminal log.
|
||||
|
||||
### Arguments
|
||||
|
||||
You can pass multiple arguments to the event handler by passing them as additional arguments to `term.emit()`.
|
||||
|
||||
```js
|
||||
term.on('start', (id) => {
|
||||
term.writeln('started...', id);
|
||||
});
|
||||
|
||||
term.emit('start', 5173);
|
||||
```
|
||||
|
||||
Example with multiple arguments:
|
||||
|
||||
```js
|
||||
term.on('start', (start, end) => {
|
||||
term.writeln(`started from ${start} to ${end}`);
|
||||
});
|
||||
|
||||
term.emit('start', 1, 10);
|
||||
```
|
||||
|
||||
### One-Time Event
|
||||
|
||||
In some cases, it might be necessary to only run an operation once and only once.
|
||||
Any event listener added using the `term.once()` method is executed once and deleted thereafter when the event is triggered.
|
||||
|
||||
```js
|
||||
term.once('load', () => {
|
||||
term.writeln('loaded...');
|
||||
});
|
||||
|
||||
term.emit('load');
|
||||
term.emit('load');
|
||||
```
|
||||
|
||||
The `load` event is triggered and will output to the terminal for the first `term.emit('load')`.
|
||||
The second event trigger does nothing since there is no event listener for the `load` event anymore.
|
||||
|
||||
### Symbols
|
||||
|
||||
Apart from strings, JavaScript symbols can as well be used to create events too.
|
||||
|
||||
```js
|
||||
const START_EVENT = Symbol('start');
|
||||
|
||||
term.on(START_EVENT, () => {
|
||||
term.writeln('started with a symbol...');
|
||||
});
|
||||
|
||||
term.emit(START_EVENT);
|
||||
```
|
||||
|
||||
## Default Events
|
||||
|
||||
Every terminal instance has existing events that are used internally and can be used in your application lifecycle.
|
||||
They include:
|
||||
|
||||
- `data` event - triggered when user inputs data and presses the _Enter_ key
|
||||
- `clear` event - triggered on [term.clear()](../api/index.md#term-clear)
|
||||
- `keypress` event - triggered on every key press except _Tab, Enter, ArrowUp_ and _ArrowDown_
|
||||
- `pause` event - triggered on [term.pause()](./prompt.md#pause-resume), when the terminal input is _deactivated_ or _paused_
|
||||
- `resume` event - triggered on [term.resume()](./prompt.md#pause-resume), when the terminal input is _activated_ or _resumed_
|
||||
|
||||
### Example
|
||||
|
||||
In this example, you are going to capture the user's input and simply write it to the terminal.
|
||||
|
||||
First, add an event listener for the `data` event to capture data, output it and then ask for more input thereafter. Clear the terminal on recieving the input matching to `clear` and as a result, everything is erased from the terminal including the prompt style. Additionally, add a `keypress` event to clear the terminal.
|
||||
|
||||
:::details Code
|
||||
|
||||
```js
|
||||
term.on('data', (input) => {
|
||||
if (input == 'clear') {
|
||||
// clear the terminal
|
||||
term.clear();
|
||||
} else {
|
||||
// do something
|
||||
term.writeln('Data: ' + input);
|
||||
}
|
||||
// write the prompt again
|
||||
term.write("$ ");
|
||||
});
|
||||
|
||||
term.on('clear', () => {
|
||||
term.writeln('You cleared the terminal');
|
||||
});
|
||||
|
||||
term.on('keypress', (ev) => {
|
||||
/**
|
||||
* Checkout the event object
|
||||
*/
|
||||
console.log(ev);
|
||||
|
||||
// on CTRL+L - clear
|
||||
if (ev.key.toLowerCase() == 'l' && ev.ctrlKey) {
|
||||
|
||||
// prevent default behaviour
|
||||
ev.cancel();
|
||||
|
||||
// clear and trigger `clear` event
|
||||
term.clear();
|
||||
}
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
The terminal will be cleared incase the user inputs `clear` or presses the shortcut `CTRL+L` which triggers the `clear` event that logs `You cleared the terminal` on the screen.
|
||||
|
||||
## Limitations
|
||||
|
||||
Multiple events can exist on the same terminal instance which is an advantage. However you should keep caution on when every event is triggered.
|
||||
|
||||
:::warning Nested Emits
|
||||
When an event is triggered, it is added on top of the emitting stack and then the listeners attached to the event are invoked synchronously.
|
||||
If you emit the same event within one of the listeners, it will not work.
|
||||
:::
|
||||
|
||||
**Example:**
|
||||
|
||||
The code sample below will not work as expected.
|
||||
|
||||
```js
|
||||
term.on('run', () => {
|
||||
console.log('running...');
|
||||
// ...
|
||||
term.emit('run');
|
||||
});
|
||||
```
|
||||
|
||||
Triggering the event `run` will log in the console: `running...`, do stuff, and attempt to trigger itself again (possible deadlock).
|
||||
|
||||
**Workaround**
|
||||
|
||||
Trigger the same event in the next event loop.
|
||||
|
||||
```js{4}
|
||||
term.on('run', () => {
|
||||
console.log('running...');
|
||||
// ...
|
||||
setTimeout(() => term.emit('run'), 0);
|
||||
});
|
||||
```
|
||||
|
||||
## Next Step
|
||||
|
||||
You'll learn to everything about the prompt including activation, styling, blur and focus.
|
||||
44
xterminal/docs/guide/history.md
Normal file
44
xterminal/docs/guide/history.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# History
|
||||
|
||||
## List
|
||||
|
||||
Whenever the user inputs data in the terminal and presses the `Enter` key, the input is saved in an internal history stack (accessible as an array) via [term.history](../api/index.md#term-history).
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
term.on('data', () => console.log(term.history));
|
||||
```
|
||||
|
||||
The above snippet logs the history list in the console everytime a new entry is added.
|
||||
|
||||
## Changing State
|
||||
|
||||
Sometimes, there might arise a need to swap between application state. You can change the history stack using;
|
||||
|
||||
```js
|
||||
const newHistoryState = [/* ... */];
|
||||
|
||||
term.history = newHistoryState;
|
||||
```
|
||||
|
||||
## Clear History
|
||||
|
||||
You might want to clear the entire history list for some reasons. You can do that using the [term.clearHistory()](../api/index.md#term-clearhistory).
|
||||
|
||||
**Example:**
|
||||
|
||||
Clearing the history on `CTRL+H` using the `keypress` event.
|
||||
|
||||
```js
|
||||
term.on('keypress', (ev) => {
|
||||
if (ev.key.toLowerCase() == 'h' && ev.ctrlKey) {
|
||||
ev.cancel();
|
||||
term.clearHistory();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Next Step
|
||||
|
||||
Enhanced user interaction with key bindings to the terminal
|
||||
40
xterminal/docs/guide/index.md
Normal file
40
xterminal/docs/guide/index.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Introduction
|
||||
|
||||
:wave: Hello Dev!
|
||||
|
||||
You're reading the official documentation for [XTerminal](https://github.com/henryhale/xterminal).
|
||||
|
||||
## What is XTerminal?
|
||||
|
||||
**XTerminal** is a simple and perfomant web-based component written in TypeScript that lets you create command-line interfaces for use in the browser.
|
||||
|
||||
It builds on top of standard HTML, CSS, and JavaScript to provide a simple yet powerful model that helps you develop command-line interfaces in the browser.
|
||||
|
||||
**XTerminal** is dependency-free as it requires no dependency to work. Just ship it in and you are just close to setting up your own in-browser CLI.
|
||||
|
||||
::: tip What You Should Know
|
||||
Basic understanding and familiarity with HTML, CSS, and JavaScript is a major preresquite as the documentation assumes you already know.
|
||||
If you are totally new to frontend development, it might not be the best idea to jump right into the library as your first step - grasp the basics and then come back!
|
||||
:::
|
||||
|
||||
Now that you know something about **XTerminal**, here is a brief definition of what it does;
|
||||
|
||||
- In a nutshell, it provides you with a single class from which your can create several terminal instances and mount them onto your webpage.
|
||||
|
||||
## What XTerminal is not?
|
||||
|
||||
::: warning
|
||||
- XTerminal is not an application that can be downloaded and used just like others on your computer.
|
||||
- XTerminal is not a fully fledged terminal application that comprises of all fundamental utility functions. It can't be connected to your terminal nor ssh, it's entirely browser based.
|
||||
:::
|
||||
|
||||
|
||||
## Main Objectives
|
||||
|
||||
The primary goals of this project are:
|
||||
|
||||
- **Simplicity**: Provide a simple and intuitive API that allows developers to quickly create web-based CLIs without the need for extensive setup or dependencies.
|
||||
|
||||
- **Performance**: Prioritize performance optimizations to ensure a smooth and responsive CLI experience, even with large outputs or complex interactions.
|
||||
|
||||
- **Flexibility**: Enable developers to customize and extend the library to meet the specific requirements of their applications. Provide a solid foundation while allowing for easy integration and customization.
|
||||
72
xterminal/docs/guide/initialization.md
Normal file
72
xterminal/docs/guide/initialization.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Creating Your First Terminal
|
||||
|
||||
## Terminal instance
|
||||
|
||||
The `XTerminal` package exports the [XTerminal](../api/index.md#xterminal) class by default for public consumption.
|
||||
To create your own terminal, you need to create an instance of it.
|
||||
|
||||
```js
|
||||
const term = new XTerminal();
|
||||
```
|
||||
|
||||
## Mounting the terminal
|
||||
|
||||
There will be nothing rendered on your page not until the `target` element is provided via the [constructor options](../api/index.md#xterminal) or [term.mount()](../api/index.md#term-mount) method is called.
|
||||
|
||||
**For example:** Let's say our app container is `#app`, then the markup should be;
|
||||
|
||||
```html
|
||||
<div id="app"></div>
|
||||
```
|
||||
|
||||
Initialize the terminal instance with `#app` as the target using one of the following:
|
||||
|
||||
- CSS selector
|
||||
|
||||
```js
|
||||
const term = new XTerminal();
|
||||
term.mount("#app"); // [!code ++]
|
||||
```
|
||||
|
||||
- DOM reference
|
||||
|
||||
```js
|
||||
const term = new XTerminal();
|
||||
term.mount( // [!code ++]
|
||||
document.querySelector("#app") // [!code ++]
|
||||
); // [!code ++]
|
||||
```
|
||||
|
||||
- Options object
|
||||
|
||||
```js
|
||||
const term = new XTerminal(); // [!code --]
|
||||
const term = new XTerminal({ // [!code ++]
|
||||
target: "#app" // or document.querySelector("#app") // [!code ++]
|
||||
}); // [!code ++]
|
||||
```
|
||||
|
||||
Choosing one of the above basically sets up the terminal HTML structure, key bindings added, and then rendered in the target element `#app`.
|
||||
|
||||
## Multiple terminal instances
|
||||
|
||||
You can create several single terminal instances on the same page since the [XTerminal](../api/index.md#xterminal) class creates an independent instance for each of them.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
const term1 = new XTerminal();
|
||||
term1.mount("#app1");
|
||||
|
||||
const term2 = new XTerminal();
|
||||
term2.mount("#app2");
|
||||
|
||||
const term3 = new XTerminal();
|
||||
term3.mount("#app3");
|
||||
```
|
||||
|
||||
Each one of the created instances can be configured to work differently independent of the others.
|
||||
|
||||
## Next Step
|
||||
|
||||
Configure the terminal to suite your application needs.
|
||||
87
xterminal/docs/guide/installation.md
Normal file
87
xterminal/docs/guide/installation.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Getting Started with XTerminal
|
||||
|
||||
## Installation
|
||||
|
||||
Below are some of the ways `XTerminal` can be installed;
|
||||
|
||||
- [CDN](./installation.md#using-cdn) - (for development with a simple setup)
|
||||
- [NPM](./installation.md#using-npm) - (use this if you are using bundlers or having a build step)
|
||||
|
||||
### Production Builds
|
||||
|
||||
There are two production ready builds:
|
||||
|
||||
- `xterminal.umd.js` - for the browser (no build tools), it's minified
|
||||
- `xterminal.esm.js` - in case of build tools like [Vite](https://vitejs.dev) or Webpack
|
||||
|
||||
## Using NPM
|
||||
|
||||
[NPM](https://npmjs.org) is a popular javascript package manager on which [XTerminal](https://npmjs.org/xterminal) is a public npm package that can be installed by anyone.
|
||||
|
||||
To install it, run one of the following commands;
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh [npm]
|
||||
npm install xterminal
|
||||
```
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add xterminal
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add xterminal
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
It provides a production build of the latest release from it's [GitHub repository](https://github.com/henryhale/xterminal/).
|
||||
|
||||
**Usage**
|
||||
|
||||
First include the styles in your markup:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="./node_modules/xterminal/dist/xterminal.css">
|
||||
```
|
||||
|
||||
Then import the script into your application (ESM build by default).
|
||||
|
||||
```js
|
||||
import XTerminal from 'xterminal';
|
||||
|
||||
console.log(XTerminal.version);
|
||||
```
|
||||
|
||||
## Using CDN
|
||||
|
||||
You can use any CDN that serves npm packages;
|
||||
|
||||
Install via CDN using one of the following;
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [unpkg]
|
||||
<link rel="stylesheet" href="https://unpkg.com/xterminal/dist/xterminal.css">
|
||||
<script src="https://unpkg.com/xterminal/dist/xterminal.umd.js"></script>
|
||||
```
|
||||
|
||||
```html [jsdelivr]
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterminal/dist/xterminal.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterminal/dist/xterminal.umd.js"></script>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Including `XTerminal` javascript file defines a global property `window.XTerminal` on the `window` object. This implies that the `XTerminal` class is globally accessible.
|
||||
|
||||
```js
|
||||
console.log(XTerminal.version);
|
||||
//or
|
||||
console.log(window.XTerminal.version);
|
||||
```
|
||||
|
||||
## Next Step
|
||||
|
||||
Now that you have installed `XTerminal`, it is time to dive into the essential parts.
|
||||
76
xterminal/docs/guide/keybindings.md
Normal file
76
xterminal/docs/guide/keybindings.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Key Bindings
|
||||
|
||||
These are shortcuts to enhance the command-line experience. Basically, they are keyboard keys
|
||||
bound to your terminal to provide functionality that would ease the use.
|
||||
|
||||
::: info Note
|
||||
Key bindings to your terminal only work when the terminal is **focused** so that the action triggered is bound to that instance.
|
||||
:::
|
||||
|
||||
## Enter Key
|
||||
|
||||
When the `Enter` key is pressed, the terminal captures the current input value, clears the input, adds value to the history stack and then fire the `data` event passing the input value.
|
||||
|
||||
## ArrowUp Key
|
||||
|
||||
When the `ArrowUp` key is pressed, it continously interates through the previous entries as it sets each entry as the current input.
|
||||
|
||||
It runs through the local history stack while setting the corresponding entry at a certian index as the current terminal input.
|
||||
|
||||
::: info Note
|
||||
No duplicate entries are pushed to the history stack. If the previous input is the same as the current, the latter won't be pushed to the history stack.
|
||||
:::
|
||||
|
||||
All in all, this key goes backwards in history.
|
||||
|
||||
## ArrowDown Key
|
||||
|
||||
In case the `ArrowUp` key is hit several times, to return to the most recent input, the `ArrowDown` key is used.
|
||||
|
||||
The `ArrowDown` key goes foreward in history by setting the most recent entry as the current input.If no previous input exist, the input is set to the previously buffered input, nothing otherwise.
|
||||
|
||||
## Tab key
|
||||
|
||||
Just like in real terminal applications, the `Tab` key provides the autocomplete future for the commands starting with the characters currently present in the terminal input.
|
||||
|
||||
If the terminal input is empty, then there are no characters to match.
|
||||
|
||||
For effective autocompletion, you must set a function that will work out the best matches.
|
||||
This is can be done using the [term.setCompleter()](../api/index.md#term-setcompleter) method on the terminal instance which is discussed on the next page.
|
||||
|
||||
## Custom Key Bindings
|
||||
|
||||
You can create your own key bindings and add the desired functionality for each one of them. Employ the [keypress event](./events.md#default-events) to attach the key bindings.
|
||||
|
||||
**Example:**
|
||||
|
||||
Suppose that you want to capture these shortcuts: `CTRL+S`, `ALT+D`, `CTRL+SHIFT+K`
|
||||
|
||||
```js
|
||||
term.on('keypress', (ev) => {
|
||||
|
||||
const key = ev.key.toLowerCase();
|
||||
|
||||
// CTRL+S
|
||||
if (ev.ctrlKey && key == 's') {
|
||||
// use `ev.cancel()` to prevent default behaviour
|
||||
ev.cancel();
|
||||
// do something
|
||||
}
|
||||
|
||||
// ALT+D
|
||||
if (ev.altKey && key == 'd') {
|
||||
// do something
|
||||
}
|
||||
|
||||
// CTRL+SHIFT+K
|
||||
if (ev.ctrlKey && ev.shiftKey && key == 'k') {
|
||||
// do something
|
||||
}
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
## Next Step
|
||||
|
||||
Enhance a rich interactive command-line interface with tab autocompletion.
|
||||
255
xterminal/docs/guide/output.md
Normal file
255
xterminal/docs/guide/output.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# Output
|
||||
|
||||
Information maybe output in a number of ways and logged in the terminal instance with the help of [term.write()](../api/index.md#term-write), [term.writeln()](../api/index.md#term-writeln), [term.writeSafe()](../api/index.md#term-writesafe) or [term.writelnSafe()](../api/index.md#term-writelnsafe).
|
||||
|
||||
## Raw Data
|
||||
|
||||
Raw data may include strings and numbers.
|
||||
|
||||
Here is a simple `hello world` example:
|
||||
|
||||
```js
|
||||
term.write("Hello World!");
|
||||
```
|
||||
|
||||
<browser-preview>
|
||||
|
||||
Hello World!▊
|
||||
|
||||
</browser-preview>
|
||||
|
||||
With an optional callback function,
|
||||
|
||||
```js
|
||||
term.write("Hello World!", () => console.log("Done!"));
|
||||
// Done!
|
||||
```
|
||||
|
||||
When dealing with arbitrary (untrustworthy) data like user input or remote resources,
|
||||
use `term.writeSafe` to securely print to the terminal. For more details, see [HTML Strings section](#html-strings).
|
||||
|
||||
```js
|
||||
const someData = "...";
|
||||
|
||||
term.writeSafe(someData);
|
||||
```
|
||||
|
||||
## Escape characters
|
||||
|
||||
Below is a list of available and ready to use escape characters;
|
||||
|
||||
- **`\n` - New line**
|
||||
|
||||
When the `\n` character is encountered in the data to output, it moves the cursor to the next line.
|
||||
The data, after every instance of the `\n` character, is rendereed on a new line.
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
term.write(`Hello World!\n$ `);
|
||||
```
|
||||
|
||||
<browser-preview>
|
||||
|
||||
Hello World!
|
||||
$ ▊
|
||||
|
||||
</browser-preview>
|
||||
|
||||
The same can be achieved using [term.writeln()](../api/index.md#term-writeln) which writes the
|
||||
data passed on the current line, followed by a new line character.
|
||||
|
||||
```js
|
||||
term.writeln(`Hello World!`);
|
||||
term.write("$ ");
|
||||
```
|
||||
|
||||
- **`\t` - Tab**
|
||||
|
||||
The tab character defaults to _four_ (4) space characters.
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
term.writeln(`Hello World!\tYou're Welcome.`);
|
||||
```
|
||||
|
||||
<browser-preview>
|
||||
|
||||
Hello World! You're welcome.
|
||||
▊
|
||||
|
||||
</browser-preview>
|
||||
|
||||
## HTML Strings
|
||||
|
||||
You might want to output some HTML string, here is how you can do it;
|
||||
|
||||
```js
|
||||
term.writeln(`<b>Bold Text</b> - <i>Italics</i>`);
|
||||
```
|
||||
|
||||
<browser-preview>
|
||||
|
||||
<b>Bold Text</b> - <i>Italics</i>
|
||||
<br>▊
|
||||
</browser-preview>
|
||||
|
||||
### Safe Output
|
||||
|
||||
::: warning :warning: SECURITY WARNING
|
||||
- **Use [term.writeSafe()](../api/index.md#term-writesafe) or [term.writelnSafe()](../api/index.md#term-writelnsafe) to safely output arbitrary data to the terminal.**
|
||||
**These methods sanitize the data before being output to the terminal, specifically, before appending it to the DOM.**
|
||||
|
||||
Avoid outputting data from arbitrary sources like user input or remote sources (such as images).
|
||||
Doing so has been proved to allow for malicious attacks like XSS where a user may input some HTML
|
||||
code that could potentially expose user information such as session cookies or even inject malicious scripts on the page.
|
||||
|
||||
For example: `term.writeln("<img onerror=alert('hacked') />")` would run the malicious script and you would see an alert dialog.
|
||||
|
||||
- **RECOMMENDED: Additionally use [XTerminal.escapeHTML()](#xterminal-escapehtml) or external libraries like [DOMPurify](https://www.npmjs.com/package/dompurify) to sanitize arbitrary data before outputting it using `term.write()` or `term.writeln()`. See examples below.**
|
||||
:::
|
||||
|
||||
```js
|
||||
term.writelnSafe(`<b>Bold Text</b> - <i>Italics</i>`);
|
||||
```
|
||||
|
||||
<browser-preview>
|
||||
|
||||
\<b>Bold Text<\/b> - \<i>Italics<\/i>
|
||||
<br>▊
|
||||
</browser-preview>
|
||||
|
||||
Use [XTerminal.escapeHTML()](#xterminal-escapehtml) to sanitize some data before printing it.
|
||||
|
||||
This is helpful when using HTML containers for some other data like showing styled error messages.
|
||||
|
||||
```js
|
||||
const err = `<img onerror="alert('hacked')" />`
|
||||
|
||||
term.writeln(`<p class="error">${XTerminal.escapeHTML(err)}</p>`)
|
||||
```
|
||||
|
||||
<browser-preview>
|
||||
|
||||
\<img onerror="alert('hacked')" \/>
|
||||
<br>▊
|
||||
</browser-preview>
|
||||
|
||||
### Attributes
|
||||
|
||||
To output valid HTML tags with attributes, there must be a **single space** separation between the attributes in every opening tag.
|
||||
|
||||
**For example:** The following won't work as expected
|
||||
|
||||
```js
|
||||
term.writeln('<b class="text-blue">Bold Blue Text</b>');
|
||||
|
||||
term.writeln('<b style="color: dodgerblue ">Bold Blue Text</b>');
|
||||
```
|
||||
|
||||
Here is how it should be done
|
||||
|
||||
```js
|
||||
term.writeln('<b class="text-blue">Bold Blue Text</b>'); // [!code --]
|
||||
term.writeln('<b class="text-blue">Bold Blue Text</b>'); // [!code ++]
|
||||
|
||||
term.writeln('<b style="color: dodgerblue ">Bold Blue Text</b>'); // [!code --]
|
||||
term.writeln('<b style="color: dodgerblue">Bold Blue Text</b>'); // [!code ++]
|
||||
```
|
||||
|
||||
However, multiple spaces are **okay** in between the opening and closing tags.
|
||||
|
||||
**For example:**
|
||||
|
||||
```js
|
||||
term.writeln('<b style="color: dodgerblue">Bold Blue Text</b>');
|
||||
```
|
||||
|
||||
## Clear Screen
|
||||
|
||||
To clear the entire terminal, you can do it programmatically using
|
||||
[term.clear()](../api/index.md#term-clear).
|
||||
|
||||
```js
|
||||
term.clear();
|
||||
```
|
||||
|
||||
## Clear Last Output
|
||||
|
||||
To remove the output for the previous write operation, [term.clearLast()](../api/index.md#term-clearlast) does the job.
|
||||
|
||||
:::info
|
||||
This is like the undo method but for only one output operation.
|
||||
:::
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
term.writeln("Welcome to Space!");
|
||||
term.writeln("Loading...");
|
||||
term.clearLast();
|
||||
```
|
||||
|
||||
<browser-preview>
|
||||
|
||||
Welcome to Space!
|
||||
<br>▊
|
||||
</browser-preview>
|
||||
|
||||
It is useful in several cases for example when implementing a loader.
|
||||
|
||||
:::details Example: 5s loader
|
||||
|
||||
- **Styles**
|
||||
|
||||
```css
|
||||
.spinner:after {
|
||||
animation: changeContent 0.8s linear infinite;
|
||||
content: "⠋";
|
||||
}
|
||||
|
||||
@keyframes changeContent {
|
||||
10% {
|
||||
content: "⠙";
|
||||
}
|
||||
20% {
|
||||
content: "⠹";
|
||||
}
|
||||
30% {
|
||||
content: "⠸";
|
||||
}
|
||||
40% {
|
||||
content: "⠼";
|
||||
}
|
||||
50% {
|
||||
content: "⠴";
|
||||
}
|
||||
60% {
|
||||
content: "⠦";
|
||||
}
|
||||
70% {
|
||||
content: "⠧";
|
||||
}
|
||||
80% {
|
||||
content: "⠇";
|
||||
}
|
||||
90% {
|
||||
content: "⠏";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Script**
|
||||
|
||||
```js
|
||||
term.write('<span class="spinner"></span> Loading...');
|
||||
|
||||
setTimeout(() => term.clearLast(), 5000);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Next Step
|
||||
|
||||
Work with terminal events that help you trigger actions on the go.
|
||||
167
xterminal/docs/guide/prompt.md
Normal file
167
xterminal/docs/guide/prompt.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Input
|
||||
|
||||
## Prompt Style
|
||||
|
||||
In most terminal emulators, the prompt style appears before the cursor. This is from the backing shell (e.g bash, zsh, fish) that prints it in the terminal.
|
||||
|
||||
For example:
|
||||
|
||||
<browser-preview hidelabel>
|
||||
|
||||
user@host:~ $ ▊
|
||||
</browser-preview>
|
||||
|
||||
Or even
|
||||
|
||||
<browser-preview hidelabel>
|
||||
|
||||
┌[user@host]
|
||||
└$ ▊
|
||||
</browser-preview>
|
||||
|
||||
In the same way, you can organize the flow of input with a prompt style just before the cursor.
|
||||
|
||||
Suppose the state of our app defines the `username` and `hostname` like so
|
||||
|
||||
```js
|
||||
const state = {
|
||||
username: 'root',
|
||||
hostname: 'web'
|
||||
};
|
||||
```
|
||||
|
||||
Create a function to write our prompt style to the terminal, let it be `ask()`.
|
||||
|
||||
```js
|
||||
function ask() {
|
||||
term.write(`┌[${state.username}@${state.hostname}]\n`);
|
||||
term.write('└$ ');
|
||||
}
|
||||
```
|
||||
|
||||
## Pause & Resume
|
||||
|
||||
Using [term.pause()](../api/index.md#term-pause) will pause or deactivate the terminal from recieving user input whereas [term.resume()](../api/index.md#term-resume) will do the opposite.
|
||||
|
||||
When invoked, [term.pause()](../api/index.md#term-pause) will trigger the [pause](./events.md#default-events) event whereas [term.resume()](../api/index.md#term-resume) will trigger the [resume](./events.md#default-events) event.
|
||||
|
||||
:::warning Note
|
||||
In both cases, _input_ is affected but not the _output_. You can still do write operations even when the input is deactivated.
|
||||
:::
|
||||
|
||||
**Example:** Pause input for five (5) seconds and resume thereafter while listening for events.
|
||||
|
||||
```js
|
||||
const term = new XTerminal();
|
||||
|
||||
term.mount('#app');
|
||||
|
||||
// capture `pause` event
|
||||
term.on("pause", () => term.writeln("pausing..."));
|
||||
|
||||
// capture `resume` event
|
||||
term.on("resume", () => term.writeln("resuming..."));
|
||||
|
||||
term.pause(); // triggers the `pause` event
|
||||
|
||||
setTimeout(() => {
|
||||
term.resume(); // triggers the `resume` event
|
||||
}, 5000);
|
||||
```
|
||||
|
||||
In the five seconds, any keypress won't do anything but we can observe to write operations in order of the events.
|
||||
|
||||
---
|
||||
|
||||
Suppose that you want to do an async operation, it is a good
|
||||
practice to [pause](../api/index.md#term-pause) the terminal for input and [resume](../api/index.md#term-resume) later when the operation is done.
|
||||
|
||||
Whenever the input is recieved, you can pause the terminal and handle the async operation first.
|
||||
|
||||
```js
|
||||
term.on("data", async input => {
|
||||
term.pause();
|
||||
// ...
|
||||
// do async task
|
||||
// ...
|
||||
term.resume();
|
||||
});
|
||||
```
|
||||
|
||||
Everytime you write the prompt style, you may want to be able to capture the next command input from the user. In this case, you can use the
|
||||
[term.resume()](../api/index.md#term-resume) method.
|
||||
|
||||
```js
|
||||
function ask() {
|
||||
term.write(`┌[${state.username}@${state.hostname}]\n`);
|
||||
term.write('└$ ');
|
||||
term.resume(); // [!code ++]
|
||||
}
|
||||
```
|
||||
|
||||
## Focus & Blur
|
||||
|
||||
You can programmatically focus the terminal input, toggling the keyboard in case of mobile devices, using the [term.focus()](../api/index.md#term-focus) method on the terminal instance.
|
||||
|
||||
Focus the input everytime you ask for input using:
|
||||
|
||||
```js
|
||||
function ask() {
|
||||
term.writeln(`┌[${state.username}@${state.hostname}]`);
|
||||
term.write('└$ ');
|
||||
term.resume();
|
||||
term.focus(); // [!code ++]
|
||||
}
|
||||
```
|
||||
|
||||
In the same way, you might want to blur the terminal for some reason, say after entering
|
||||
data and pressing the Enter key. You can achieve that using the `data` event and the [term.blur()](../api/index.md#term-blur) method.
|
||||
|
||||
```js
|
||||
term.on('data', () => {
|
||||
term.blur();
|
||||
});
|
||||
```
|
||||
|
||||
## Set & Clear
|
||||
|
||||
Use [term.setInput()](../api/index.md#term-setinput) to simulate user input by modifying the value of the input buffer.
|
||||
This is useful in many scenarios, one of which is to preset some input in the terminal a user can use/modify/execute (so that they don't have to type it themselves).
|
||||
|
||||
Here is an example:
|
||||
|
||||
```js
|
||||
term.setInput("help")
|
||||
```
|
||||
|
||||
<browser-preview hidelabel>
|
||||
|
||||
user@host:~ $ help▊
|
||||
</browser-preview>
|
||||
|
||||
Given that we can now change the input buffer, [term.clearInput()](../api/index.md#term-clearinput) allows for clearing the input buffer, for instance, when you want to discard any user input or contents of the input buffer.
|
||||
|
||||
```js
|
||||
term.clearInput()
|
||||
```
|
||||
|
||||
results into
|
||||
|
||||
<browser-preview hidelabel>
|
||||
|
||||
user@host:~ $ ▊
|
||||
</browser-preview>
|
||||
|
||||
A complete example to illustrate both methods in action: the input is set to `help` and cleared after 2 seconds
|
||||
|
||||
```js
|
||||
term.setInput("help")
|
||||
|
||||
setTimeout(() => {
|
||||
term.clearInput()
|
||||
}, 2000)
|
||||
```
|
||||
|
||||
## Next Step
|
||||
|
||||
Learn about the history stack that stores all inputs
|
||||
96
xterminal/docs/guide/quick-start.md
Normal file
96
xterminal/docs/guide/quick-start.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Quick Start
|
||||
|
||||
To get started, you need to [install XTerminal](./installation.md) and ship the `CSS` and `JS` from XTerminal `dist` folder into your application.
|
||||
|
||||
Here is a quick setup using the [CDN installation guide](./installation.md#using-cdn). This setup requires a simple project structure with three essential files; `index.html`, `styles.css` and `main.js` in the same directory.
|
||||
|
||||
::: code-group
|
||||
|
||||
```html :line-numbers [index.html]
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>My First Terminal</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/xterminal/dist/xterminal.css">
|
||||
<link rel="stylesheet" href="./styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="https://unpkg.com/xterminal/dist/xterminal.umd.js"></script>
|
||||
<script src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css :line-numbers [styles.css]
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden; /* prevent page from scrolling */
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100vh; /* occur the entire page */
|
||||
}
|
||||
```
|
||||
|
||||
```js :line-numbers [main.js]
|
||||
// create a new terminal instance
|
||||
const term = new XTerminal();
|
||||
|
||||
// mount the terminal to page
|
||||
term.mount('#app');
|
||||
|
||||
// prompt style
|
||||
const promptStyle = '[user] $ ';
|
||||
|
||||
// write prompt style and prepare for input
|
||||
function ask() {
|
||||
term.write(promptStyle);
|
||||
}
|
||||
|
||||
// capture data event
|
||||
term.on('data', input => {
|
||||
if (input == 'clear') {
|
||||
// clear screen
|
||||
term.clear();
|
||||
} else {
|
||||
// do something
|
||||
term.writeln('Data: ' + input);
|
||||
}
|
||||
// then prompt user for more input
|
||||
ask();
|
||||
});
|
||||
|
||||
// print greeting message
|
||||
term.writeln('Hello World!');
|
||||
|
||||
// initiate
|
||||
ask();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Open the `index.html` file in your browser.
|
||||
|
||||
<browser-preview>
|
||||
|
||||
Hello World!
|
||||
[user] $ ▊
|
||||
</browser-preview>
|
||||
|
||||
::: tip
|
||||
Follow the rest of the guide to customize, add interactivity, and also learn how to setup your own terminal application.
|
||||
:::
|
||||
|
||||
## Next Step
|
||||
|
||||
If you skipped the [introduction](./index.md), you're strongly recommend reading it before moving on to the rest of the documentation.
|
||||
|
||||
Otherwise continue reading the guide. It takes you through details of the library.
|
||||
145
xterminal/docs/guide/theme.md
Normal file
145
xterminal/docs/guide/theme.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# Theme
|
||||
|
||||
Personalize the terminal interface to your desired appearance.
|
||||
|
||||
The entire structure and appearance of the terminal is defined in the CSS file (`xterminal.css`) included during [installation](./installation.md#installation).
|
||||
|
||||
To customize anything, ensure that the new styles are include after the default styles.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="path/to/xterminal.css"/>
|
||||
|
||||
<link rel="stylesheet" href="custom-styles.css"/>
|
||||
<!-- OR -->
|
||||
<style>
|
||||
/* custom styles */
|
||||
</style>
|
||||
```
|
||||
|
||||
## Width & Height
|
||||
|
||||
By default, the terminal occupies the full width and height of the parent element `#app`.
|
||||
|
||||
```html
|
||||
<div id="app"></div>
|
||||
```
|
||||
|
||||
To adjust the dimensions, use one of the following:
|
||||
|
||||
- Parent Element
|
||||
|
||||
```css
|
||||
#app {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
```
|
||||
|
||||
- Terminal CSS classname: `xt`
|
||||
|
||||
```css
|
||||
.xt {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
```
|
||||
|
||||
## Background
|
||||
|
||||
The default background depends on the value of the css variable: `--xt-bg`
|
||||
|
||||
**Example:**
|
||||
|
||||
```css
|
||||
:root {
|
||||
--xt-bg: black;
|
||||
}
|
||||
```
|
||||
|
||||
## Text Color
|
||||
|
||||
To change the color of text, change `--xt-fg` css variable to the desired color
|
||||
|
||||
**Example:**
|
||||
|
||||
```css
|
||||
:root {
|
||||
--xt-fg: lime;
|
||||
}
|
||||
```
|
||||
|
||||
## Font Size
|
||||
|
||||
Adjust the font size using `--xt-font-size`
|
||||
|
||||
**Example:**
|
||||
|
||||
```css
|
||||
:root {
|
||||
--xt-font-size: 1.5rem;
|
||||
}
|
||||
```
|
||||
|
||||
## Font Family
|
||||
|
||||
Set your favourite font style using `--xt-font-family`
|
||||
|
||||
**Example:**
|
||||
|
||||
```css
|
||||
:root {
|
||||
--xt-font-family: 'Lucida Console', monospace;
|
||||
}
|
||||
```
|
||||
|
||||
Using Font URLS
|
||||
|
||||
```css
|
||||
@import url('https://fonts.googleapis.com/css2?family=Fira+Code&display=swap');
|
||||
|
||||
:root {
|
||||
--xt-font-family: 'Fira Code', monospace;
|
||||
}
|
||||
```
|
||||
|
||||
## Padding
|
||||
|
||||
Adjust the padding of the terminal container using `--xt-padding` css variable
|
||||
|
||||
**Example:**
|
||||
|
||||
```css
|
||||
:root {
|
||||
--xt-padding: 10px;
|
||||
}
|
||||
```
|
||||
|
||||
## Line Height
|
||||
|
||||
The default line height depends on the current font size. Adjust the line height using the css classname: `xt-stdout` for the output component
|
||||
|
||||
**Example**
|
||||
|
||||
```css
|
||||
.xt-stdout {
|
||||
line-height: 1.25;
|
||||
}
|
||||
```
|
||||
|
||||
## Blinking Cursor
|
||||
|
||||
To add the blinking effect to the cursor, apply the animation using the class name: `xt-cursor`
|
||||
|
||||
**Example:**
|
||||
|
||||
```css
|
||||
@keyframes blink {
|
||||
0% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
|
||||
.xt-cursor {
|
||||
animation: blink 1s linear infinite;
|
||||
}
|
||||
```
|
||||
41
xterminal/docs/index.md
Normal file
41
xterminal/docs/index.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: XTerminal
|
||||
text: Build Web-based Command-line Interfaces
|
||||
tagline: Simple and perfomant front-end tool for building command-line interfaces in the browser.
|
||||
image:
|
||||
src: /logo.svg
|
||||
alt: XTerminal Logo
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Get Started
|
||||
link: /guide/index
|
||||
- theme: alt
|
||||
text: Demo
|
||||
link: /demo/index
|
||||
- theme: alt
|
||||
text: View on GitHub
|
||||
link: https://github.com/henryhale/xterminal
|
||||
|
||||
features:
|
||||
- icon: 🚀
|
||||
title: Performant
|
||||
details: It is lightweight, fast and designed with performance in mind. This library offers efficient handling of user input, resulting in a responsive and smooth CLI experience.
|
||||
- icon: 📦
|
||||
title: Dependency-free
|
||||
details: This library does not rely on any external dependencies to work, keeping your project lean and reducing the potential for conflicts or versioning issues.
|
||||
- icon: 🟩
|
||||
title: Inspired by Node.js
|
||||
details: Drawing inspiration from the readline module in Node.js, this library provides similar functionality for input/output management, enabling a smooth transition for developers familiar with Node.js CLI development.
|
||||
- icon: 🖌️
|
||||
title: Customizable
|
||||
details: Tailor the appearance and behavior of your CLI to match your application's style and requirements. Customize colors, themes, fonts, and other visual aspects to create a unique and cohesive user experience.
|
||||
- icon: 💎
|
||||
title: Native text formatting
|
||||
details: Enhance your CLI output with native web technologies (HTML & CSS) including styling, inline images, hyperlinks, and other visual enhancements.
|
||||
- icon: 🔌
|
||||
title: Event-driven API
|
||||
details: Utilize an event-driven API to handle user input, execute commands, and respond to various events. Hook into events such as input, keypress, and completion to create dynamic and interactive CLI interactions.
|
||||
---
|
||||
56
xterminal/docs/public/demo.html
Normal file
56
xterminal/docs/public/demo.html
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Live Demo | XTerminal</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/xterminal/dist/xterminal.css">
|
||||
<style>
|
||||
*, *::after, *::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
#app {
|
||||
height: 100vh;
|
||||
}
|
||||
:root {
|
||||
--xt-fg: lime;
|
||||
--xt-bg: black;
|
||||
}
|
||||
.xt {
|
||||
padding: 10px;
|
||||
line-height: 1.215;
|
||||
}
|
||||
.error {
|
||||
color: rgb(248, 88, 88);
|
||||
}
|
||||
.spinner:after {
|
||||
animation: changeContent 0.8s linear infinite;
|
||||
content: "⠋";
|
||||
}
|
||||
|
||||
@keyframes changeContent {
|
||||
10% { content: "⠙"; }
|
||||
20% { content: "⠹"; }
|
||||
30% { content: "⠸"; }
|
||||
40% { content: "⠼"; }
|
||||
50% { content: "⠴"; }
|
||||
60% { content: "⠦"; }
|
||||
70% { content: "⠧"; }
|
||||
80% { content: "⠇"; }
|
||||
90% { content: "⠏"; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="https://unpkg.com/xterminal/dist/xterminal.umd.js"></script>
|
||||
<script src="./demo.js"></script>
|
||||
<script>
|
||||
window.onload = () => createTerminal('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
149
xterminal/docs/public/demo.js
Normal file
149
xterminal/docs/public/demo.js
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Create a demo shell object
|
||||
*/
|
||||
|
||||
// #region shell
|
||||
function createShell() {
|
||||
|
||||
// Help
|
||||
const manual = `XTerminal : version ${XTerminal.version}
|
||||
|
||||
Type 'help' to see this list
|
||||
|
||||
Commands:
|
||||
|
||||
gh (username) search for github users
|
||||
js [expr] execute a JS expression
|
||||
clear clear the terminal screen
|
||||
help display this list
|
||||
`;
|
||||
|
||||
// Get public github user information
|
||||
async function fetchGitHubUser(username) {
|
||||
return fetch('https://api.github.com/users/' + username)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
return(
|
||||
'<table border="0">' +
|
||||
'<tr>' +
|
||||
`<td rowspan="3" width="100"><img width="75" src="${res.avatar_url}" alt="${res.name}" /></td>` +
|
||||
`<td>Name</td>` +
|
||||
`<td>${res.name}</td>` +
|
||||
'</tr>' +
|
||||
'<tr>' +
|
||||
`<td>Bio</td>` +
|
||||
`<td>${res.bio}</td>` +
|
||||
'</tr>' +
|
||||
'<tr>' +
|
||||
`<td>Repos</td>` +
|
||||
`<td>${res.public_repos}</td>` +
|
||||
'</tr>' +
|
||||
'</table>'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// evaluate user input from the terminal
|
||||
// -> can be shared among several terminal objects
|
||||
function execute(term, command = '') {
|
||||
let args = command.split(' ');
|
||||
let cmd = args.shift();
|
||||
// GitHub User Search
|
||||
if (cmd == 'gh') {
|
||||
return new Promise(async (res, rej) => {
|
||||
let output, error;
|
||||
term.write('<span class="spinner"></span> Searching...');
|
||||
await fetchGitHubUser(args.join(''))
|
||||
.then(val => output = val)
|
||||
.catch(err => error = ':( Not found!')
|
||||
.finally(() => term.clearLast());
|
||||
if (error) rej(error);
|
||||
else res(output);
|
||||
});
|
||||
}
|
||||
// JavaScript Evaluation
|
||||
else if (cmd == 'js') {
|
||||
return new Promise((res, rej) => {
|
||||
try {
|
||||
let output = eval(args.join(' ')) + '\n';
|
||||
res(output);
|
||||
} catch (error) {
|
||||
rej(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Help menu
|
||||
else if (cmd == 'help') {
|
||||
return Promise.resolve(manual);
|
||||
}
|
||||
// Clear the terminal
|
||||
else if (cmd == 'clear') {
|
||||
term.clear();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
// Oopps!
|
||||
else {
|
||||
return Promise.reject(`sh: '${cmd}' command not found`);
|
||||
}
|
||||
}
|
||||
|
||||
return { execute };
|
||||
|
||||
}
|
||||
// #endregion shell
|
||||
|
||||
/**
|
||||
* Create a fresh terminal object
|
||||
*/
|
||||
|
||||
// #region terminal
|
||||
function createTerminal(target) {
|
||||
|
||||
const term = new XTerminal({ target });
|
||||
|
||||
const state = {
|
||||
username: "user",
|
||||
hostname: "web"
|
||||
};
|
||||
|
||||
// input evaluator
|
||||
const shell = createShell();
|
||||
|
||||
// print prompt and get ready for user input
|
||||
function promptUser() {
|
||||
term.write(`┌[${state.username}@${state.hostname}]\n`);
|
||||
term.write("└$ ");
|
||||
term.resume();
|
||||
term.focus();
|
||||
}
|
||||
|
||||
// user input handler
|
||||
term.on("data", async input => {
|
||||
|
||||
// deactivate until the execution is done
|
||||
term.pause();
|
||||
|
||||
// execute command
|
||||
await shell.execute(term, input)
|
||||
.then(res => res && term.writeln(res))
|
||||
.catch(err => {
|
||||
if (err) {
|
||||
// sanitize error to prevent xss attacks
|
||||
// error may contain user input or HTML strings (like script tags)
|
||||
term.writeln(`<span class="error">${XTerminal.escapeHTML(err)}</span>\n`)
|
||||
}
|
||||
})
|
||||
.finally(promptUser);
|
||||
});
|
||||
|
||||
// greeting message
|
||||
term.writeln("Welcome to XTerminal (v" + XTerminal.version + ")");
|
||||
term.writeln("Type `help` for available commands\n");
|
||||
|
||||
// kickstart
|
||||
promptUser();
|
||||
|
||||
// remember to free resources
|
||||
window.addEventListener('unload', () => term.dispose());
|
||||
}
|
||||
// #endregion terminal
|
||||
BIN
xterminal/docs/public/logo.ico
Normal file
BIN
xterminal/docs/public/logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
9
xterminal/docs/public/logo.svg
Normal file
9
xterminal/docs/public/logo.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<title>XTerminal Logo</title>
|
||||
<path stroke="#000" d="m-4,-2.5l103.99998,0l0,108l-103.99998,0l0,-108z" stroke-width="0" fill="#444444"/>
|
||||
<rect stroke="#000" height="33" width="16" y="29" x="42" stroke-width="0" fill="#ffffff"/>
|
||||
<rect transform="rotate(-45 23.25 40.25)" stroke="#000" height="18.56497" width="6.17157" y="30.96751" x="20.16421" stroke-width="0" fill="#ffffff"/>
|
||||
<rect transform="rotate(45 22.6036 50.25)" stroke="#000" height="18.75736" width="6" y="40.87132" x="19.60356" stroke-width="0" fill="#ffffff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 616 B |
43
xterminal/docs/question-solved.md
Normal file
43
xterminal/docs/question-solved.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# 问题闭环记录(Question Solved)
|
||||
|
||||
本文档记录已确认问题的现象、根因、修复与回归要点,避免重复踩坑。
|
||||
|
||||
## 目录
|
||||
|
||||
1. [2026-03-03:回车后首字母重复显示(`aasdf`)](#enter-leading-dup)
|
||||
2. [回归检查清单](#regression-checklist)
|
||||
|
||||
---
|
||||
|
||||
<a id="enter-leading-dup"></a>
|
||||
## 2026-03-03:回车后首字母重复显示(`aasdf`)
|
||||
|
||||
### 现象
|
||||
- 输入英文命令(如 `asdf`)后回车,提示符行偶发显示为 `aasdf`。
|
||||
- 服务端实际收到与执行仍是 `asdf`(例如返回 `zsh: command not found: asdf`)。
|
||||
|
||||
### 根因
|
||||
- SSH 回显是分片到达的,典型序列为:`"a"`、`"\b as"`、`"df..."`。
|
||||
- 旧逻辑按帧即时渲染,第一帧字符已写入后,下一帧开头的 `\b` 不能回退上一帧字符,造成视觉重复。
|
||||
- 回显中还可能夹杂 `\r\r\n` 与 ANSI 控制序列,若不按终端语义处理会放大错位和空行问题。
|
||||
|
||||
### 修复
|
||||
- 文件:`demo/main.js`
|
||||
- 策略:
|
||||
1. 在 `renderAnsiToHtml` 中补齐控制字符处理:`BS(\x08)`、`CRLF(\r\n)`、裸 `CR(\r)`。
|
||||
2. 增加短窗口流式合帧(`STREAM_BATCH_MS`),将连续 `stdout/stderr` 分片先合并后渲染,保证跨帧 `\b/\r` 能生效。
|
||||
3. 保留本地回显清理(`clearLast()`),避免与远端回显叠加。
|
||||
|
||||
### 验证
|
||||
- 复现 `asdf` 回车,不再出现 `aasdf`。
|
||||
- 调试日志显示发送仍为 `asdf\n`,与服务端执行结果一致。
|
||||
|
||||
---
|
||||
|
||||
<a id="regression-checklist"></a>
|
||||
## 回归检查清单
|
||||
|
||||
1. 英文输入后回车不出现首字母重复。
|
||||
2. `stdout/stderr` 样式渲染正常(ANSI 颜色不回归)。
|
||||
3. 回车后不新增多余空行。
|
||||
4. 本地回显与远端回显不叠加。
|
||||
50
xterminal/docs/showcase/index.md
Normal file
50
xterminal/docs/showcase/index.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
layout: page
|
||||
---
|
||||
|
||||
<script setup>
|
||||
const showcase = [
|
||||
{
|
||||
name: 'ESJS',
|
||||
desc: 'Lenguaje de programación en Español',
|
||||
link: 'https://es.js.org',
|
||||
logo: 'https://es.js.org/assets/logo.png',
|
||||
author: {
|
||||
username: 'enzonotario',
|
||||
link: 'https://github.com/enzonotario'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'VIX',
|
||||
desc: 'Boostrap your own web based CLI application',
|
||||
link: 'https://henryhale.github.io/vix',
|
||||
logo: 'https://henryhale.github.io/vix/xterminal.png',
|
||||
author: {
|
||||
username: 'henryhale',
|
||||
link: 'https://github.com/henryhale'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'TELEMATE',
|
||||
desc: 'A small scale messaging application for devs',
|
||||
link: 'https://github.com/henryhale/telemate',
|
||||
logo: 'https://github.com/henryhale/telemate/raw/master/client/public/logo.svg',
|
||||
author: {
|
||||
username: 'henryhale',
|
||||
link: 'https://github.com/henryhale'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'DB-ADMIN',
|
||||
desc: 'Interact with your databases in the browser',
|
||||
link: 'https://github.com/henryhale/db-admin',
|
||||
logo: 'https://github.com/henryhale/db-admin/raw/master/client/public/favicon.png',
|
||||
author: {
|
||||
username: 'henryhale',
|
||||
link: 'https://github.com/henryhale'
|
||||
},
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<project-cards :projects='showcase'></project-cards>
|
||||
Reference in New Issue
Block a user