-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
57dad78
commit 0b8d9e0
Showing
17 changed files
with
302 additions
and
293 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { Command } from "./Command"; | ||
|
||
export abstract class AutocompleteCommand extends Command { | ||
abstract autocomplete(arg: string): string[] | null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Console } from "./Console"; | ||
|
||
export interface Command { | ||
readonly name: string; | ||
execute(console: Console, args: string[]): void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import { Command } from "./Command"; | ||
import { getCommandFromInput, longestCommonPrefix } from "./helpers"; | ||
import { ChangeDirectory } from "./commands/ChangeDirectory"; | ||
import { Clear } from "./commands/Clear"; | ||
import { Help } from "./commands/Help"; | ||
import { Kitties } from "./commands/Kitties"; | ||
import { List } from "./commands/List"; | ||
|
||
export class Console { | ||
private inputElement = document.getElementById("prompt-input") as HTMLSpanElement; | ||
private promptBlur = document.getElementById("prompt-blur") as HTMLSpanElement; | ||
private consoleElement = document.getElementById("console") as HTMLDivElement; | ||
private nav = document.getElementsByTagName("nav")[0]; | ||
|
||
static commands: Command[] = [ | ||
new ChangeDirectory(), | ||
new Clear(), | ||
new Help(), | ||
new Kitties(), | ||
new List(), | ||
]; | ||
|
||
public initialise(): void { | ||
// Focus prompt input on page load for browsers that don't support autofocus on contenteditable elements. | ||
this.inputElement.focus(); | ||
|
||
this.mirrorInputPromptToBlurredPrompt(); | ||
this.moveCaretToEndOnFocus(); | ||
this.listenForKeyboardInput(); | ||
this.focusPromptOnClick(); | ||
} | ||
|
||
private mirrorInputPromptToBlurredPrompt(): void { | ||
this.inputElement.addEventListener("input", () => { | ||
this.setInput(this.inputElement.textContent.replace(/\s/g, "\xA0")); | ||
}); | ||
} | ||
|
||
private moveCaretToEndOnFocus(): void { | ||
this.inputElement.addEventListener("focusin", () => { | ||
this.moveCaretToEnd(); | ||
}); | ||
} | ||
|
||
private listenForKeyboardInput(): void { | ||
/** | ||
* Handle enter key press to clear the prompt input. | ||
*/ | ||
document.addEventListener("keydown", (event: KeyboardEvent) => { | ||
const input = this.inputElement.textContent.replace(/\xA0/g, " ").trim(); | ||
|
||
switch (event.key) { | ||
case "ArrowLeft": | ||
case "ArrowRight": | ||
case "ArrowUp": | ||
case "ArrowDown": | ||
event.preventDefault(); | ||
break; | ||
|
||
// Clear prompt input | ||
case "Enter": | ||
event.preventDefault(); | ||
this.onEnter(input); | ||
break; | ||
|
||
case "Tab": | ||
event.preventDefault(); | ||
this.onTab(input); | ||
break; | ||
|
||
// Remove focus from prompt input | ||
case "Escape": | ||
this.inputElement.blur(); | ||
break; | ||
} | ||
}); | ||
} | ||
|
||
private focusPromptOnClick(): void { | ||
/** | ||
* Focus prompt input when clicking on the header. | ||
*/ | ||
this.nav.addEventListener("click", (event: MouseEvent) => { | ||
// Prevent focusing prompt input when clicking on a link. | ||
if (event.target instanceof HTMLAnchorElement) { | ||
return; | ||
} | ||
|
||
this.inputElement.focus(); | ||
}); | ||
} | ||
|
||
private onEnter(input: string) { | ||
const { command, args } = getCommandFromInput(input); | ||
this.setInput(); | ||
this.clearOutput(); | ||
|
||
if (command) { | ||
command.execute(this, args); | ||
return; | ||
} | ||
|
||
this.print("Command not found. Type `help` for a list of available commands."); | ||
return; | ||
} | ||
|
||
private onTab(input: string) { | ||
if (input.length === 0) { | ||
return; | ||
} | ||
|
||
const matchingCommands = Console.commands.filter((command: Command) => | ||
command.name.startsWith(input), | ||
); | ||
|
||
switch (matchingCommands.length) { | ||
case 0: | ||
return; | ||
|
||
case 1: | ||
const matchingCommand = matchingCommands[0]; | ||
if (input.length < matchingCommand.name.length) { | ||
this.inputElement.textContent = matchingCommand.name; | ||
this.promptBlur.textContent = matchingCommand.name; | ||
this.moveCaretToEnd(); | ||
} | ||
return; | ||
|
||
default: | ||
const matchingPrefix = longestCommonPrefix( | ||
matchingCommands.map((command: Command) => command.name), | ||
); | ||
this.setInput(""); | ||
this.inputElement.textContent = matchingPrefix; | ||
this.promptBlur.textContent = matchingPrefix; | ||
this.moveCaretToEnd(); | ||
} | ||
return; | ||
} | ||
|
||
private moveCaretToEnd() { | ||
const range = document.createRange(); | ||
const selection = window.getSelection(); | ||
|
||
// Move caret to end of prompt input. | ||
range?.setStart(this.inputElement, this.inputElement.childNodes.length); | ||
range?.collapse(false); | ||
selection?.removeAllRanges(); | ||
selection?.addRange(range); | ||
} | ||
|
||
public print(...lines: string[]) { | ||
lines.forEach((line: string) => { | ||
const outputElement = document.createElement("pre"); | ||
outputElement.textContent = line; | ||
this.consoleElement.appendChild(outputElement); | ||
}); | ||
} | ||
|
||
public setInput(newInput: string = "") { | ||
this.inputElement.textContent = newInput; | ||
this.promptBlur.textContent = newInput; | ||
this.moveCaretToEnd(); | ||
} | ||
|
||
public clearOutput() { | ||
while (this.consoleElement?.firstChild) { | ||
this.consoleElement.removeChild(this.consoleElement.firstChild); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Command } from "../Command"; | ||
import { Console } from "../Console"; | ||
|
||
export class Clear implements Command { | ||
public readonly name: string = "clear"; | ||
|
||
public execute(console: Console, args: string[]): void { | ||
if (args.length > 0) { | ||
console.print("clear: too many arguments"); | ||
return; | ||
} | ||
|
||
console.clearOutput(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { Command } from "../Command"; | ||
import { Console } from "../Console"; | ||
|
||
export class Help implements Command { | ||
public readonly name: string = "help"; | ||
|
||
public execute(console: Console, args: string[]): void { | ||
if (args.length > 0) { | ||
console.print("help: too many arguments"); | ||
return; | ||
} | ||
|
||
const output = Console.commands.map((command: Command) => command.name).join(" "); | ||
|
||
console.print(output); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Command } from "../Command"; | ||
|
||
import { commands, Console } from "../Console"; | ||
|
||
export class Kitties implements Command { | ||
public readonly name: string = "kitties"; | ||
|
||
public execute(console: Console, args: string[]): void { | ||
if (args.length > 0) { | ||
console.print("kitties: too many arguments"); | ||
return; | ||
} | ||
|
||
window.location.href = "https://hamana.nl/"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Command } from "../Command"; | ||
|
||
import { HugoPage } from "../../types/hugo"; | ||
import { getPagesInPath } from "../helpers"; | ||
import { Console } from "../Console"; | ||
|
||
export class List implements Command { | ||
public readonly name: string = "ls"; | ||
|
||
public execute(console: Console, args: string[]): void { | ||
if (args.length > 0) { | ||
console.print("ls: too many arguments"); | ||
return; | ||
} | ||
|
||
const currentPath = window.location.pathname; | ||
let pages = getPagesInPath(currentPath); | ||
|
||
console.print("."); | ||
|
||
if (currentPath !== "/") { | ||
console.print(".."); | ||
} | ||
|
||
const paths = pages.map((page: HugoPage): string => { | ||
if (page.Slug) { | ||
return page.Slug.concat("/"); | ||
} else { | ||
return page.Path.replace(currentPath, "").concat("/"); | ||
} | ||
}); | ||
|
||
console.print(...paths); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.