From 5e5968f09ec7e022dd0590b207aa3d3df4c45802 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Thu, 24 Feb 2022 17:24:49 +0100 Subject: [PATCH] Tons of progress --- .DS_Store | Bin 8196 -> 10244 bytes .vscode/noot.code-workspace | 3 + noot.code-workspace | 16 ++ plugins/.gitignore | 1 + plugins/.vscode/settings.json | 4 + plugins/Makefile | 5 + plugins/bundle.ts | 115 +++++++++++++ plugins/core/core.plugin.json | 48 ++++++ plugins/core/dates.ts | 6 + plugins/core/lib/db.ts | 9 ++ plugins/core/lib/event.ts | 5 + plugins/core/lib/syscall.ts | 16 ++ plugins/core/link_navigate.ts | 8 + plugins/core/markup.ts | 33 ++++ plugins/core/word_count_command.ts | 20 +++ server/run.sh | 3 + webapp/.DS_Store | Bin 6148 -> 0 bytes webapp/.gitignore | 2 + webapp/package.json | 2 + webapp/src/buildContext.tsx | 13 ++ webapp/src/components/commandpalette.tsx | 19 ++- webapp/src/{app.tsx => editor.tsx} | 133 ++++++++++----- webapp/src/index.html | 2 +- webapp/src/index.ts | 2 - webapp/src/plugin_sw.ts | 96 +++++++++++ webapp/src/plugins/browser_system.ts | 56 +++++++ webapp/src/plugins/function_worker.ts | 81 ++++++++++ webapp/src/plugins/runtime.ts | 196 +++++++++++++++++++++++ webapp/src/plugins/types.ts | 27 ++++ webapp/src/reducer.ts | 5 + webapp/src/syscalls/db.localstorage.ts | 10 ++ webapp/src/syscalls/editor.browser.ts | 72 +++++++++ webapp/src/syscalls/event.native.ts | 7 + webapp/src/syscalls/ui.browser.ts | 22 +++ webapp/src/types.ts | 20 ++- webapp/src/util.ts | 14 ++ webapp/tsconfig.json | 20 +-- webapp/yarn.lock | 92 ++--------- 38 files changed, 1039 insertions(+), 144 deletions(-) create mode 100644 noot.code-workspace create mode 100644 plugins/.gitignore create mode 100644 plugins/.vscode/settings.json create mode 100644 plugins/Makefile create mode 100644 plugins/bundle.ts create mode 100644 plugins/core/core.plugin.json create mode 100644 plugins/core/dates.ts create mode 100644 plugins/core/lib/db.ts create mode 100644 plugins/core/lib/event.ts create mode 100644 plugins/core/lib/syscall.ts create mode 100644 plugins/core/link_navigate.ts create mode 100644 plugins/core/markup.ts create mode 100644 plugins/core/word_count_command.ts create mode 100755 server/run.sh delete mode 100644 webapp/.DS_Store create mode 100644 webapp/.gitignore create mode 100644 webapp/src/buildContext.tsx rename webapp/src/{app.tsx => editor.tsx} (79%) delete mode 100644 webapp/src/index.ts create mode 100644 webapp/src/plugin_sw.ts create mode 100644 webapp/src/plugins/browser_system.ts create mode 100644 webapp/src/plugins/function_worker.ts create mode 100644 webapp/src/plugins/runtime.ts create mode 100644 webapp/src/plugins/types.ts create mode 100644 webapp/src/syscalls/db.localstorage.ts create mode 100644 webapp/src/syscalls/editor.browser.ts create mode 100644 webapp/src/syscalls/event.native.ts create mode 100644 webapp/src/syscalls/ui.browser.ts diff --git a/.DS_Store b/.DS_Store index ae40ef6bc4f45578f5605df105c11b71aa3afa4a..90d1f2d2876f7cdbfa67c1f3cf3b27eaec4aab94 100644 GIT binary patch delta 255 zcmZp1XbF&DU|?W$DortDU{C-uIe-{M3-C-V6q~50C<+o_1o9aeG8yt1ij&HV3zBj+ z7EWWI*ucA)or8r#9;AYup@1QWp_CyVSyg@#P-f@konnO=lGW9Qrk0jE3dUx}wK@vb zmPQ6T3MR&8wY8iaqRRT#LGjr+xq12BlLN)&<$Ev>ZfxLY7i0zs1AziJkZ=XLePiKw=E?jrfg(&0A8CN3fxa*R(US{gdN=ckFf#)H DAzL*` delta 146 zcmZn(XmOBWU|?W$DortDU;r^WfEYvza8E20o2aKK$_A1L@);OX8H$q1iwlx+@{=|e zPGg_gz`L29gN1{UZ8M{wB0GnPxsi^7v8mZ)Es0Bl%s^d0Ai)hJTtTL7Ed0(qnO`N4 X185e=4u;9`JX1G+5;b5#)_5KOeFhu} diff --git a/.vscode/noot.code-workspace b/.vscode/noot.code-workspace index f707c56b..dc9b9d5b 100644 --- a/.vscode/noot.code-workspace +++ b/.vscode/noot.code-workspace @@ -5,6 +5,9 @@ }, { "path": "../server" + }, + { + "path": "../plugin-bundler" } ], "settings": { diff --git a/noot.code-workspace b/noot.code-workspace new file mode 100644 index 00000000..e58cdaa0 --- /dev/null +++ b/noot.code-workspace @@ -0,0 +1,16 @@ +{ + "folders": [ + { + "path": "webapp" + }, + { + "path": "plugins" + }, + { + "path": "server" + } + ], + "settings": { + "editor.formatOnSave": true + } +} diff --git a/plugins/.gitignore b/plugins/.gitignore new file mode 100644 index 00000000..53c37a16 --- /dev/null +++ b/plugins/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/plugins/.vscode/settings.json b/plugins/.vscode/settings.json new file mode 100644 index 00000000..8675ad57 --- /dev/null +++ b/plugins/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "deno.enable": true, + "deno.unstable": true +} \ No newline at end of file diff --git a/plugins/Makefile b/plugins/Makefile new file mode 100644 index 00000000..b00e1241 --- /dev/null +++ b/plugins/Makefile @@ -0,0 +1,5 @@ +DENO_BUNDLE=deno run --allow-read --allow-write --unstable bundle.ts --debug +build: * + mkdir -p dist + $(DENO_BUNDLE) core/core.plugin.json dist/core.plugin.json + diff --git a/plugins/bundle.ts b/plugins/bundle.ts new file mode 100644 index 00000000..c7fd32ae --- /dev/null +++ b/plugins/bundle.ts @@ -0,0 +1,115 @@ +import { parse } from "https://deno.land/std@0.121.0/flags/mod.ts"; + +// import { mime } from "https://deno.land/x/mimetypes@v1.0.0/mod.ts"; +// +// async function dataEncodeUint8Array(path : string, data: Uint8Array): Promise { +// const base64url: string = await new Promise((r) => { +// const reader = new FileReader(); +// reader.onload = () => r(reader.result as string); +// reader.readAsDataURL(new Blob([data])) +// }) +// let [meta, content] = base64url.split(';'); +// let [prefix, mimeType] = meta.split(':'); +// return `data:${mime.getType(path)};${content}`; +// } +import * as path from "https://deno.land/std@0.121.0/path/mod.ts"; +import { Manifest, FunctionDef } from "../webapp/src/plugins/types.ts"; + +async function compile( + filePath: string, + prettyFunctionName: string, + jsFunctionName: string, + sourceMaps: boolean +): Promise { + // @ts-ignore for Deno.emit (unstable API) + let { files, diagnostics } = await Deno.emit(filePath, { + bundle: "classic", + check: true, + compilerOptions: { + lib: ["WebWorker", "ES2020"], + inlineSourceMap: sourceMaps, + sourceMap: false, + }, + }); + let bundleSource = files["deno:///bundle.js"]; + + if (diagnostics.length > 0) { + for (let diagnostic of diagnostics) { + if (diagnostic.start) { + console.error( + `In ${diagnostic.fileName}:${diagnostic.start!.line + 1}: ${ + diagnostic.messageText + }` + ); + } else { + console.error(diagnostic); + } + } + throw new Error("Diagnostics"); + } + return `const mod = ${bundleSource} + +self.addEventListener('invoke-function', async e => { + try { + let result = await mod['${jsFunctionName}'](...e.detail.args); + self.dispatchEvent(new CustomEvent('result', {detail: result})); + } catch(e) { + console.error(\`Error while running ${jsFunctionName}\`, e); + self.dispatchEvent(new CustomEvent('app-error', {detail: e.message})); + } +}); +`; +} + +async function bundle( + manifestPath: string, + sourceMaps: boolean +): Promise { + const rootPath = path.dirname(manifestPath); + const manifest = JSON.parse( + new TextDecoder().decode(await Deno.readFile(manifestPath)) + ) as Manifest; + + for (let [name, def] of Object.entries(manifest.functions) as Array< + [string, FunctionDef] + >) { + let jsFunctionName, + filePath = path.join(rootPath, def.path); + if (filePath.indexOf(":") !== 0) { + [filePath, jsFunctionName] = filePath.split(":"); + } else { + jsFunctionName = "default"; + } + + def.code = await compile(filePath, name, jsFunctionName, sourceMaps); + } + return manifest; + // let files: { [key: string]: string } = {}; + // for await (const entry of walk(path, {includeDirs: false})) { + // let content = await Deno.readFile(entry.path); + // files[entry.path.substring(path.length + 1)] = await dataEncodeUint8Array(entry.path, content); + // } + // return files; +} + +let commandLineArguments = parse(Deno.args, { + boolean: true, +}); + +let [manifestPath, outputPath] = commandLineArguments._ as string[]; +console.log(`Generating bundle for ${manifestPath} to ${outputPath}`); +let b = await bundle(manifestPath, !!commandLineArguments.debug); +await Deno.writeFile( + outputPath, + new TextEncoder().encode(JSON.stringify(b, null, 2)) +); +/* +const watcher = Deno.watchFs("test_app"); + +for await (const event of watcher) { + console.log("Updating bundle..."); + let b = await bundle("test_app/test.cartridge.json"); + await Deno.writeFile("test_app.bundle.json", new TextEncoder().encode(JSON.stringify(b, null, 2))); +} + + */ diff --git a/plugins/core/core.plugin.json b/plugins/core/core.plugin.json new file mode 100644 index 00000000..14267d82 --- /dev/null +++ b/plugins/core/core.plugin.json @@ -0,0 +1,48 @@ +{ + "commands": { + "Count Words": { + "invoke": "word_count_command", + "requiredContext": { + "text": true + } + }, + "Navigate To page": { + "invoke": "link_navigate", + "key": "Ctrl-Enter", + "mac": "Cmd-Enter", + "requiredContext": { + } + }, + "Insert Current Date": { + "invoke": "insert_nice_date" + }, + "Toggle : Heading 1": { + "invoke": "toggle_h1", + "mac": "Cmd-1", + "key": "Ctrl-1" + }, + "Toggle : Heading 2": { + "invoke": "toggle_h2", + "mac": "Cmd-2", + "key": "Ctrl-2" + } + }, + "events": {}, + "functions": { + "word_count_command": { + "path": "./word_count_command.ts:wordCount" + }, + "link_navigate": { + "path": "./link_navigate.ts:linkNavigate" + }, + "insert_nice_date": { + "path": "./dates.ts:insertToday" + }, + "toggle_h1": { + "path": "./markup.ts:toggleH1" + }, + "toggle_h2": { + "path": "./markup.ts:toggleH2" + } + } +} diff --git a/plugins/core/dates.ts b/plugins/core/dates.ts new file mode 100644 index 00000000..3ea50568 --- /dev/null +++ b/plugins/core/dates.ts @@ -0,0 +1,6 @@ +import { syscall } from "./lib/syscall.ts"; + +export async function insertToday() { + let niceDate = new Date().toISOString().split("T")[0]; + await syscall("editor.insertAtCursor", niceDate); +} diff --git a/plugins/core/lib/db.ts b/plugins/core/lib/db.ts new file mode 100644 index 00000000..12a19448 --- /dev/null +++ b/plugins/core/lib/db.ts @@ -0,0 +1,9 @@ +import {syscall} from "./syscall.ts"; + +export async function put(key: string, value: any) { + return await syscall("db.put", key, value); +} + +export async function get(key: string) { + return await syscall("db.get", key); +} diff --git a/plugins/core/lib/event.ts b/plugins/core/lib/event.ts new file mode 100644 index 00000000..9dae53e5 --- /dev/null +++ b/plugins/core/lib/event.ts @@ -0,0 +1,5 @@ +import {syscall} from "./syscall.ts"; + +export async function publish(event: string, data?: object) { + return await syscall("event.publish", event, data); +} diff --git a/plugins/core/lib/syscall.ts b/plugins/core/lib/syscall.ts new file mode 100644 index 00000000..37f826dc --- /dev/null +++ b/plugins/core/lib/syscall.ts @@ -0,0 +1,16 @@ +export function syscall(name: string, ...args: Array): any { + let reqId = Math.floor(Math.random() * 1000000); + // console.log("Syscall", name, reqId); + return new Promise((resolve, reject) => { + self.dispatchEvent( + new CustomEvent("syscall", { + detail: { + id: reqId, + name: name, + args: args, + callback: resolve, + }, + }), + ); + }); +} diff --git a/plugins/core/link_navigate.ts b/plugins/core/link_navigate.ts new file mode 100644 index 00000000..b8c2485b --- /dev/null +++ b/plugins/core/link_navigate.ts @@ -0,0 +1,8 @@ +import { syscall } from "./lib/syscall.ts"; + +export async function linkNavigate({ text }: { text: string }) { + let syntaxNode = await syscall("editor.getSyntaxNodeUnderCursor"); + if (syntaxNode && syntaxNode.name === "WikiLinkPage") { + await syscall("editor.navigate", syntaxNode.text); + } +} diff --git a/plugins/core/markup.ts b/plugins/core/markup.ts new file mode 100644 index 00000000..e5afaa86 --- /dev/null +++ b/plugins/core/markup.ts @@ -0,0 +1,33 @@ +import { syscall } from "./lib/syscall.ts"; + +export async function toggleH1() { + await togglePrefix("# "); +} + +export async function toggleH2() { + await togglePrefix("## "); +} + +function lookBack(s: string, pos: number, backString: string): boolean { + return s.substring(pos - backString.length, pos) === backString; +} + +async function togglePrefix(prefix: string) { + let text = (await syscall("editor.getText")) as string; + let pos = (await syscall("editor.getCursor")) as number; + if (text[pos] === "\n") { + pos--; + } + while (pos > 0 && text[pos] !== "\n") { + if (lookBack(text, pos, prefix)) { + // Already has this prefix, let's flip it + await syscall("editor.replaceRange", pos - prefix.length, pos, ""); + return; + } + pos--; + } + if (pos) { + pos++; + } + await syscall("editor.insertAtPos", prefix, pos); +} diff --git a/plugins/core/word_count_command.ts b/plugins/core/word_count_command.ts new file mode 100644 index 00000000..301bfeaa --- /dev/null +++ b/plugins/core/word_count_command.ts @@ -0,0 +1,20 @@ +function countWords(str: string): number { + var matches = str.match(/[\w\d\'\'-]+/gi); + return matches ? matches.length : 0; +} + +function readingTime(wordCount: number): number { + // 225 is average word reading speed for adults + return Math.ceil(wordCount / 225); +} + +import { syscall } from "./lib/syscall.ts"; + +export async function wordCount({ text }: { text: string }) { + let sysCallText = (await syscall("editor.getText")) as string; + const count = countWords(sysCallText); + console.log("Word count", count); + let syntaxNode = await syscall("editor.getSyntaxNodeUnderCursor"); + console.log("Syntax node", syntaxNode); + return count; +} diff --git a/server/run.sh b/server/run.sh new file mode 100755 index 00000000..0a2fd67b --- /dev/null +++ b/server/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +deno run --allow-net --allow-read --allow-write server.ts \ No newline at end of file diff --git a/webapp/.DS_Store b/webapp/.DS_Store deleted file mode 100644 index a090ae5e90f3e0c265d7ecc483fb8e64bfec38c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5Z-O8Nhm@O3Oz1(Em+%H5icRu7cim+m718M!I&*cYYwH5v%Zi|;`2DO zy8(+mi`W_1{pNQ!`$6`HF~+@lbjVnXF=j(UbgvE7Ofn+JG4g4c$PlcLaB5#Nm%JYcok2I zaclQXrn4YUCz&dUqY0$kUdL%93s26|C|9+<4mghOj9cx+VsPB+i2l)V*%6DAUbiC# z{o!)y*n9g2rx)Yrtv!8<5sHLre_CNg~lPnBI|5fTH$05L!eY&HYt zT(Fy)O#`i-7$64LFo64mfQINAEH$dF13J7uW4wWg0y@4W5QRb4V5t!zAY7LM>QZi= z7+jZwUzj}CV5w1;Gp=Taam>usPi2-6@m4Ui8TX_DT!!NV;k-u6( z7BN5!{4)l4W8{xKSd=+ie=HBrS^@1I8Vcr>sDOaJatQzf_mQ>=>bO81@?3+ZMjQqG RsvM9m0*Vmoh=E^V;0thtNr(Ud diff --git a/webapp/.gitignore b/webapp/.gitignore new file mode 100644 index 00000000..db4c6d9b --- /dev/null +++ b/webapp/.gitignore @@ -0,0 +1,2 @@ +dist +node_modules \ No newline at end of file diff --git a/webapp/package.json b/webapp/package.json index e238aef1..6e6c5a35 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -22,6 +22,8 @@ "@codemirror/lang-markdown": "^0.19.6", "@codemirror/state": "^0.19.7", "@codemirror/view": "^0.19.42", + "@parcel/service-worker": "^2.3.2", + "idb": "^7.0.0", "react": "^17.0.2", "react-dom": "^17.0.2" } diff --git a/webapp/src/buildContext.tsx b/webapp/src/buildContext.tsx new file mode 100644 index 00000000..1783e19e --- /dev/null +++ b/webapp/src/buildContext.tsx @@ -0,0 +1,13 @@ +import { Editor } from "./editor"; +import { AppCommand, CommandContext } from "./types"; + +export function buildContext(cmd: AppCommand, editor: Editor) { + let ctx: CommandContext = {}; + if (!cmd.command.requiredContext) { + return ctx; + } + if (cmd.command.requiredContext.text) { + ctx.text = editor.editorView?.state.sliceDoc(); + } + return ctx; +} diff --git a/webapp/src/components/commandpalette.tsx b/webapp/src/components/commandpalette.tsx index 426784ab..33cd7975 100644 --- a/webapp/src/components/commandpalette.tsx +++ b/webapp/src/components/commandpalette.tsx @@ -1,20 +1,29 @@ import { AppCommand } from "../types"; -import { FilterList } from "./filter"; +import { FilterList, Option } from "./filter"; export function CommandPalette({ commands, onTrigger, }: { - commands: AppCommand[]; - onTrigger: (command: AppCommand) => void; + commands: Map; + onTrigger: (command: AppCommand | undefined) => void; }) { + let options: Option[] = []; + for (let [name, def] of commands.entries()) { + options.push({ name: name }); + } + console.log("Commands", options); return ( { - onTrigger(opt as AppCommand); + if (opt) { + onTrigger(commands.get(opt.name)); + } else { + onTrigger(undefined); + } }} /> ); diff --git a/webapp/src/app.tsx b/webapp/src/editor.tsx similarity index 79% rename from webapp/src/app.tsx rename to webapp/src/editor.tsx index e7d25395..3603ae6a 100644 --- a/webapp/src/app.tsx +++ b/webapp/src/editor.tsx @@ -1,11 +1,17 @@ -import { autocompletion, completionKeymap } from "@codemirror/autocomplete"; +import { + autocompletion, + CompletionContext, + completionKeymap, + CompletionResult, +} from "@codemirror/autocomplete"; import { closeBrackets, closeBracketsKeymap } from "@codemirror/closebrackets"; import { indentWithTab, standardKeymap } from "@codemirror/commands"; import { history, historyKeymap } from "@codemirror/history"; -import { indentOnInput } from "@codemirror/language"; +import { indentOnInput, syntaxTree } from "@codemirror/language"; import { bracketMatching } from "@codemirror/matchbrackets"; import { searchKeymap } from "@codemirror/search"; import { EditorState, StateField, Transaction } from "@codemirror/state"; +import { KeyBinding } from "@codemirror/view"; import { drawSelection, dropCursor, @@ -13,33 +19,33 @@ import { highlightSpecialChars, keymap, } from "@codemirror/view"; -import React, { useEffect, useReducer, useRef } from "react"; +import React, { useEffect, useReducer } from "react"; import ReactDOM from "react-dom"; +import coreManifest from "../../plugins/dist/core.plugin.json"; +import { buildContext } from "./buildContext"; import * as commands from "./commands"; import { CommandPalette } from "./components/commandpalette"; +import { NavigationBar } from "./components/navigation_bar"; import { NoteNavigator } from "./components/notenavigator"; +import { StatusBar } from "./components/status_bar"; import { FileSystem, HttpFileSystem } from "./fs"; import { lineWrapper } from "./lineWrapper"; import { markdown } from "./markdown"; import customMarkDown from "./parser"; +import { BrowserSystem } from "./plugins/browser_system"; +import { Manifest } from "./plugins/types"; import reducer from "./reducer"; import customMarkdownStyle from "./style"; -import { Action, AppViewState } from "./types"; - -import { syntaxTree } from "@codemirror/language"; -import * as util from "./util"; -import { NoteMeta } from "./types"; - -const initialViewState: AppViewState = { - isSaved: false, - showNoteNavigator: false, - showCommandPalette: false, - allNotes: [], -}; - -import { CompletionContext, CompletionResult } from "@codemirror/autocomplete"; -import { NavigationBar } from "./components/navigation_bar"; -import { StatusBar } from "./components/status_bar"; +import dbSyscalls from "./syscalls/db.localstorage"; +import editorSyscalls from "./syscalls/editor.browser"; +import { + Action, + AppCommand, + AppViewState, + CommandContext, + initialViewState, +} from "./types"; +import { safeRun } from "./util"; class NoteState { editorState: EditorState; @@ -51,15 +57,18 @@ class NoteState { } } -class Editor { +export class Editor { editorView?: EditorView; viewState: AppViewState; viewDispatch: React.Dispatch; $hashChange?: () => void; openNotes: Map; fs: FileSystem; + editorCommands: Map; constructor(fs: FileSystem, parent: Element) { + this.editorCommands = new Map(); + this.openNotes = new Map(); this.fs = fs; this.viewState = initialViewState; this.viewDispatch = () => {}; @@ -69,9 +78,37 @@ class Editor { parent: document.getElementById("editor")!, }); this.addListeners(); - this.loadNoteList(); - this.openNotes = new Map(); + } + + async init() { + await this.loadNoteList(); + await this.loadPlugins(); this.$hashChange!(); + this.focus(); + } + + async loadPlugins() { + const system = new BrowserSystem("plugin"); + system.registerSyscalls(dbSyscalls, editorSyscalls(this)); + + await system.bootServiceWorker(); + console.log("Now loading core plugin"); + let mainCartridge = await system.load("core", coreManifest as Manifest); + this.editorCommands = new Map(); + const cmds = mainCartridge.manifest!.commands; + for (let name in cmds) { + let cmd = cmds[name]; + this.editorCommands.set(name, { + command: cmd, + run: async (arg: CommandContext): Promise => { + return await mainCartridge.invoke(cmd.invoke, [arg]); + }, + }); + } + this.viewDispatch({ + type: "update-commands", + commands: this.editorCommands, + }); } get currentNote(): string | undefined { @@ -80,6 +117,23 @@ class Editor { createEditorState(text: string): EditorState { const editor = this; + let commandKeyBindings: KeyBinding[] = []; + for (let def of this.editorCommands.values()) { + if (def.command.key) { + commandKeyBindings.push({ + key: def.command.key, + mac: def.command.mac, + run: (): boolean => { + Promise.resolve() + .then(async () => { + await def.run(buildContext(def, this)); + }) + .catch((e) => console.error(e)); + return true; + }, + }); + } + } return EditorState.create({ doc: text, extensions: [ @@ -110,6 +164,7 @@ class Editor { ...historyKeymap, ...completionKeymap, indentWithTab, + ...commandKeyBindings, { key: "Ctrl-b", mac: "Cmd-b", @@ -133,25 +188,6 @@ class Editor { return true; }, }, - { - key: "Ctrl-Enter", - mac: "Cmd-Enter", - run: (target): boolean => { - // TODO: Factor this and click handler into one action - let selection = target.state.selection.main; - if (selection.empty) { - let node = syntaxTree(target.state).resolveInner( - selection.from - ); - if (node && node.name === "WikiLinkPage") { - let noteName = target.state.sliceDoc(node.from, node.to); - this.navigate(noteName); - return true; - } - } - return false; - }, - }, { key: "Ctrl-p", mac: "Cmd-p", @@ -371,10 +407,13 @@ class Editor { dispatch({ type: "hide-palette" }); editor!.focus(); if (cmd) { - console.log("Run", cmd); + safeRun(async () => { + let result = await cmd.run(buildContext(cmd, editor)); + console.log("Result of command", result); + }); } }} - commands={[{ name: "My command", run: () => {} }]} + commands={viewState.commands} /> )} { + console.error(e); +}); + +safeRun(async () => { + await ed.init(); +}); // @ts-ignore window.editor = ed; diff --git a/webapp/src/index.html b/webapp/src/index.html index 04cb059f..c407ab0a 100644 --- a/webapp/src/index.html +++ b/webapp/src/index.html @@ -4,7 +4,7 @@ Noot - + diff --git a/webapp/src/index.ts b/webapp/src/index.ts deleted file mode 100644 index 139597f9..00000000 --- a/webapp/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/webapp/src/plugin_sw.ts b/webapp/src/plugin_sw.ts new file mode 100644 index 00000000..094baa1b --- /dev/null +++ b/webapp/src/plugin_sw.ts @@ -0,0 +1,96 @@ +import { Manifest } from "./plugins/types"; + +import { openDB, wrap, unwrap } from "idb"; + +const rootUrl = location.origin + "/plugin"; + +// Storing manifests in IndexedDB, y'all +let manifestCache = caches.open("manifests"); + +const db = openDB("manifests-store", undefined, { + upgrade(db) { + db.createObjectStore("manifests"); + }, +}); + +async function saveManifest(name: string, manifest: Manifest) { + await (await db).put("manifests", manifest, name); +} + +async function getManifest(name: string): Promise { + return (await (await db).get("manifests", name)) as Manifest | undefined; +} + +self.addEventListener("install", (event) => { + console.log("Installing"); + // @ts-ignore + self.skipWaiting(); + // event.waitUntil(fetchBundle()); +}); + +async function handlePut(req: Request, path: string) { + console.log("Got manifest load for", path); + let manifest = (await req.json()) as Manifest; + await saveManifest(path, manifest); + // loadedBundles.set(path, manifest); + return new Response("ok"); +} + +self.addEventListener("fetch", (event: any) => { + const req = event.request; + if (req.url.startsWith(rootUrl)) { + let path = req.url.substring(rootUrl.length + 1); + event.respondWith( + (async () => { + // console.log("Service worker is serving", path); + if (path === `$ping`) { + // console.log("Got ping"); + return new Response("ok"); + } + + if (req.method === "PUT") { + return await handlePut(req, path); + } + + let [cartridgeName, resourceType, functionName] = path.split("/"); + + let manifest = await getManifest(cartridgeName); + + if (!manifest) { + // console.log("Ain't got", cartridgeName); + return new Response(`Cartridge not loaded: ${cartridgeName}`, { + status: 404, + }); + } + + if (resourceType === "$manifest") { + return new Response(JSON.stringify(manifest)); + } + + if (resourceType === "function") { + let func = manifest.functions[functionName]; + // console.log("Serving function", functionName, func); + if (!func) { + return new Response("Not found", { + status: 404, + }); + } + return new Response(func.code, { + status: 200, + headers: { + "Content-type": "application/javascript", + }, + }); + } + })() + ); + } +}); + +self.addEventListener("activate", (event) => { + // console.log("Now ready to pick up fetches"); + // @ts-ignore + event.waitUntil(self.clients.claim()); +}); + +// console.log("I'm a service worker, look at me!", location.href); diff --git a/webapp/src/plugins/browser_system.ts b/webapp/src/plugins/browser_system.ts new file mode 100644 index 00000000..a144029a --- /dev/null +++ b/webapp/src/plugins/browser_system.ts @@ -0,0 +1,56 @@ +import { CartridgeLoader, System } from "./runtime"; +import { Manifest } from "./types"; +import { sleep } from "../util"; + +export class BrowserLoader implements CartridgeLoader { + readonly pathPrefix: string; + + constructor(pathPrefix: string) { + this.pathPrefix = pathPrefix; + } + + async load(name: string, manifest: Manifest): Promise { + await fetch(`${this.pathPrefix}/${name}`, { + method: "PUT", + body: JSON.stringify(manifest), + }); + } +} + +export class BrowserSystem extends System { + constructor(pathPrefix: string) { + super(new BrowserLoader(pathPrefix), pathPrefix); + } + // Service worker stuff + async pollServiceWorkerActive() { + for (let i = 0; i < 25; i++) { + try { + console.log("Pinging...", `${this.pathPrefix}/$ping`); + let ping = await fetch(`${this.pathPrefix}/$ping`); + let text = await ping.text(); + if (ping.status === 200 && text === "ok") { + return; + } + } catch (e) { + console.log("Not yet"); + } + await sleep(100); + } + // Alright, something's messed up + throw new Error("Worker not successfully activated"); + } + + async bootServiceWorker() { + // @ts-ignore + let reg = navigator.serviceWorker.register( + new URL("../plugin_sw.ts", import.meta.url), + { + type: "module", + } + ); + + console.log("Service worker registered successfully"); + + await this.pollServiceWorkerActive(); + } +} diff --git a/webapp/src/plugins/function_worker.ts b/webapp/src/plugins/function_worker.ts new file mode 100644 index 00000000..ec815570 --- /dev/null +++ b/webapp/src/plugins/function_worker.ts @@ -0,0 +1,81 @@ +function safeRun(fn: () => Promise) { + fn().catch((e) => { + console.error(e); + }); +} + +let func = null; +let pendingRequests: { + [key: number]: any; +} = {}; + +self.addEventListener("syscall", (event) => { + let customEvent = event as CustomEvent; + let detail = customEvent.detail; + pendingRequests[detail.id] = detail.callback; + self.postMessage({ + type: "syscall", + id: detail.id, + name: detail.name, + args: detail.args, + }); +}); + +self.addEventListener("result", (event) => { + let customEvent = event as CustomEvent; + self.postMessage({ + type: "result", + result: customEvent.detail, + }); +}); + +self.addEventListener("app-error", (event) => { + let customEvent = event as CustomEvent; + postMessage({ + type: "error", + reason: customEvent.detail, + }); +}); + +self.addEventListener("message", (event) => { + safeRun(async () => { + let messageEvent = event as MessageEvent; + let data = messageEvent.data; + switch (data.type) { + case "boot": + console.log("Booting", `./${data.prefix}/function/${data.name}`); + importScripts(`./${data.prefix}/function/${data.name}`); + // if (data.userAgent && data.userAgent.indexOf("Firefox") !== -1) { + // // @ts-ignore + // } else { + // await import(`./${data.prefix}/function/${data.name}`); + // } + self.postMessage({ + type: "inited", + }); + break; + case "invoke": + self.dispatchEvent( + new CustomEvent("invoke-function", { + detail: { + args: data.args || [], + }, + }) + ); + break; + case "syscall-response": + let id = data.id; + const lookup = pendingRequests[id]; + if (!lookup) { + console.log( + "Current outstanding requests", + pendingRequests, + "looking up", + id + ); + throw Error("Invalid request id"); + } + return await lookup(data.data); + } + }); +}); diff --git a/webapp/src/plugins/runtime.ts b/webapp/src/plugins/runtime.ts new file mode 100644 index 00000000..04f5920a --- /dev/null +++ b/webapp/src/plugins/runtime.ts @@ -0,0 +1,196 @@ +import { Manifest } from "./types"; + +export class SyscallContext { + public cartridge: Cartridge; + + constructor(cartridge: Cartridge) { + this.cartridge = cartridge; + } +} + +interface SysCallMapping { + // TODO: Better typing + [key: string]: any; +} + +export class FunctionWorker { + private worker: Worker; + private inited: Promise; + private initCallback: any; + private invokeResolve?: (result?: any) => void; + private invokeReject?: (reason?: any) => void; + private cartridge: Cartridge; + + constructor(cartridge: Cartridge, pathPrefix: string, name: string) { + this.worker = new Worker(new URL("function_worker.ts", import.meta.url)); + // console.log("Starting worker", this.worker); + this.worker.onmessage = this.onmessage.bind(this); + this.worker.postMessage({ + type: "boot", + prefix: pathPrefix, + name: name, + // @ts-ignore + userAgent: navigator.userAgent, + }); + this.inited = new Promise((resolve) => { + this.initCallback = resolve; + }); + this.cartridge = cartridge; + } + + async onmessage(evt: MessageEvent) { + let data = evt.data; + if (!data) return; + switch (data.type) { + case "inited": + this.initCallback(); + break; + case "syscall": + const ctx = new SyscallContext(this.cartridge); + let result = await this.cartridge.system.syscall( + ctx, + data.name, + data.args + ); + + this.worker.postMessage({ + type: "syscall-response", + id: data.id, + data: result, + }); + break; + case "result": + this.invokeResolve!(data.result); + break; + case "error": + this.invokeReject!(data.reason); + break; + default: + console.error("Unknown message type", data); + } + } + + async invoke(args: Array): Promise { + await this.inited; + this.worker.postMessage({ + type: "invoke", + args: args, + }); + return new Promise((resolve, reject) => { + this.invokeResolve = resolve; + this.invokeReject = reject; + }); + } + + stop() { + this.worker.terminate(); + } +} + +export interface CartridgeLoader { + load(name: string, manifest: Manifest): Promise; +} + +export class Cartridge { + pathPrefix: string; + system: System; + private runningFunctions: Map; + public manifest?: Manifest; + private name: string; + + constructor(system: System, pathPrefix: string, name: string) { + this.name = name; + this.pathPrefix = `${pathPrefix}/${name}`; + this.system = system; + this.runningFunctions = new Map(); + } + + async load(manifest: Manifest) { + this.manifest = manifest; + await this.system.cartridgeLoader.load(this.name, manifest); + await this.dispatchEvent("load"); + } + + async invoke(name: string, args: Array): Promise { + if (!this.runningFunctions.has(name)) { + this.runningFunctions.set( + name, + new FunctionWorker(this, this.pathPrefix, name) + ); + } + return await this.runningFunctions.get(name)!.invoke(args); + } + + async dispatchEvent(name: string, data?: any) { + let functionsToSpawn = this.manifest!.events[name]; + if (functionsToSpawn) { + await Promise.all( + functionsToSpawn.map(async (functionToSpawn: string) => { + await this.invoke(functionToSpawn, [data]); + }) + ); + } + } + + async stop() { + for (const [functionname, worker] of Object.entries( + this.runningFunctions + )) { + console.log(`Stopping ${functionname}`); + worker.stop(); + } + this.runningFunctions = new Map(); + } +} + +export class System { + protected cartridges: Map; + protected pathPrefix: string; + registeredSyscalls: SysCallMapping; + cartridgeLoader: CartridgeLoader; + + constructor(cartridgeLoader: CartridgeLoader, pathPrefix: string) { + this.cartridgeLoader = cartridgeLoader; + this.pathPrefix = pathPrefix; + this.cartridges = new Map(); + this.registeredSyscalls = {}; + } + + registerSyscalls(...registrationObjects: Array) { + for (const registrationObject of registrationObjects) { + for (let p in registrationObject) { + this.registeredSyscalls[p] = registrationObject[p]; + } + } + } + + async syscall( + ctx: SyscallContext, + name: string, + args: Array + ): Promise { + const callback = this.registeredSyscalls[name]; + if (!name) { + throw Error(`Unregistered syscall ${name}`); + } + if (!callback) { + throw Error(`Registered but not implemented syscall ${name}`); + } + return Promise.resolve(callback(ctx, ...args)); + } + + async load(name: string, manifest: Manifest): Promise { + const cartridge = new Cartridge(this, this.pathPrefix, name); + await cartridge.load(manifest); + this.cartridges.set(name, cartridge); + return cartridge; + } + + async stop(): Promise { + return Promise.all( + Array.from(this.cartridges.values()).map((cartridge) => cartridge.stop()) + ); + } +} + +console.log("Starting"); diff --git a/webapp/src/plugins/types.ts b/webapp/src/plugins/types.ts new file mode 100644 index 00000000..cc409302 --- /dev/null +++ b/webapp/src/plugins/types.ts @@ -0,0 +1,27 @@ +export interface Manifest { + events: { [key: string]: string[] }; + commands: { + [key: string]: CommandDef; + }; + functions: { + [key: string]: FunctionDef; + }; +} + +export interface CommandDef { + // Function name to invoke + invoke: string; + + // Bind to keyboard shortcut + key?: string; + mac?: string; + // Required context to be passed in as function arguments + requiredContext?: { + text?: boolean; + }; +} + +export interface FunctionDef { + path: string; + code?: string; +} diff --git a/webapp/src/reducer.ts b/webapp/src/reducer.ts index b5b9c8dd..a8ca78c8 100644 --- a/webapp/src/reducer.ts +++ b/webapp/src/reducer.ts @@ -51,6 +51,11 @@ export default function reducer( ...state, showCommandPalette: false, }; + case "update-commands": + return { + ...state, + commands: action.commands, + }; } return state; } diff --git a/webapp/src/syscalls/db.localstorage.ts b/webapp/src/syscalls/db.localstorage.ts new file mode 100644 index 00000000..7fa9bb41 --- /dev/null +++ b/webapp/src/syscalls/db.localstorage.ts @@ -0,0 +1,10 @@ +import { SyscallContext } from "../plugins/runtime"; + +export default { + "db.put": (ctx: SyscallContext, key: string, value: any) => { + localStorage.setItem(key, value); + }, + "db.get": (ctx: SyscallContext, key: string) => { + return localStorage.getItem(key); + }, +}; diff --git a/webapp/src/syscalls/editor.browser.ts b/webapp/src/syscalls/editor.browser.ts new file mode 100644 index 00000000..c6993552 --- /dev/null +++ b/webapp/src/syscalls/editor.browser.ts @@ -0,0 +1,72 @@ +import { Editor } from "../editor"; +import { SyscallContext } from "../plugins/runtime"; +import { syntaxTree } from "@codemirror/language"; + +export default (editor: Editor) => ({ + "editor.getText": (ctx: SyscallContext) => { + return editor.editorView?.state.sliceDoc(); + }, + "editor.getCursor": (ctx: SyscallContext): number => { + return editor.editorView!.state.selection.main.from; + }, + "editor.navigate": async (ctx: SyscallContext, name: string) => { + await editor.navigate(name); + }, + "editor.insertAtPos": (ctx: SyscallContext, text: string, pos: number) => { + editor.editorView!.dispatch({ + changes: { + insert: text, + from: pos, + }, + }); + }, + "editor.replaceRange": ( + ctx: SyscallContext, + from: number, + to: number, + text: string + ) => { + editor.editorView!.dispatch({ + changes: { + insert: text, + from: from, + to: to, + }, + }); + }, + "editor.moveCursor": (ctx: SyscallContext, pos: number) => { + editor.editorView!.dispatch({ + selection: { + anchor: pos, + }, + }); + }, + "editor.insertAtCursor": (ctx: SyscallContext, text: string) => { + let editorView = editor.editorView!; + let from = editorView.state.selection.main.from; + editorView.dispatch({ + changes: { + insert: text, + from: from, + }, + selection: { + anchor: from + text.length, + }, + }); + }, + "editor.getSyntaxNodeUnderCursor": ( + ctx: SyscallContext + ): { name: string; text: string } | undefined => { + const editorState = editor.editorView!.state; + let selection = editorState.selection.main; + if (selection.empty) { + let node = syntaxTree(editorState).resolveInner(selection.from); + if (node) { + return { + name: node.name, + text: editorState.sliceDoc(node.from, node.to), + }; + } + } + }, +}); diff --git a/webapp/src/syscalls/event.native.ts b/webapp/src/syscalls/event.native.ts new file mode 100644 index 00000000..20a7b0c0 --- /dev/null +++ b/webapp/src/syscalls/event.native.ts @@ -0,0 +1,7 @@ +import { SyscallContext } from "../plugins/runtime"; + +export default { + "event.publish": async (ctx: SyscallContext, name: string, data: any) => { + await ctx.cartridge.dispatchEvent(name, data); + }, +}; diff --git a/webapp/src/syscalls/ui.browser.ts b/webapp/src/syscalls/ui.browser.ts new file mode 100644 index 00000000..caaf2e60 --- /dev/null +++ b/webapp/src/syscalls/ui.browser.ts @@ -0,0 +1,22 @@ +import { SyscallContext } from "../plugins/runtime"; + +// @ts-ignore +let frameTest = document.getElementById("main-frame"); + +window.addEventListener("message", async (event) => { + let messageEvent = event as MessageEvent; + let data = messageEvent.data; + if (data.type === "iframe_event") { + // @ts-ignore + window.mainCartridge.dispatchEvent(data.data.event, data.data.data); + } +}); + +export default { + "ui.update": function (ctx: SyscallContext, doc: any) { + // frameTest.contentWindow.postMessage({ + // type: "loadContent", + // doc: doc, + // }); + }, +}; diff --git a/webapp/src/types.ts b/webapp/src/types.ts index a704476e..ea49abfe 100644 --- a/webapp/src/types.ts +++ b/webapp/src/types.ts @@ -1,10 +1,16 @@ +import { CommandDef } from "./plugins/types"; + export type NoteMeta = { name: string; }; +export type CommandContext = { + text?: string; +}; + export type AppCommand = { - name: string; - run: () => void; + command: CommandDef; + run: (ctx: CommandContext) => Promise; }; export type AppViewState = { @@ -13,6 +19,15 @@ export type AppViewState = { showNoteNavigator: boolean; showCommandPalette: boolean; allNotes: NoteMeta[]; + commands: Map; +}; + +export const initialViewState: AppViewState = { + isSaved: false, + showNoteNavigator: false, + showCommandPalette: false, + allNotes: [], + commands: new Map(), }; export type Action = @@ -22,5 +37,6 @@ export type Action = | { type: "notes-listed"; notes: NoteMeta[] } | { type: "start-navigate" } | { type: "stop-navigate" } + | { type: "update-commands"; commands: Map } | { type: "show-palette" } | { type: "hide-palette" }; diff --git a/webapp/src/util.ts b/webapp/src/util.ts index 73e4a99b..16b5ca40 100644 --- a/webapp/src/util.ts +++ b/webapp/src/util.ts @@ -7,3 +7,17 @@ export function readingTime(wordCount: number): number { // 225 is average word reading speed for adults return Math.ceil(wordCount / 225); } + +export function safeRun(fn: () => Promise) { + fn().catch((e) => { + console.error(e); + }); +} + +export function sleep(ms: number): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, ms); + }); +} diff --git a/webapp/tsconfig.json b/webapp/tsconfig.json index ded63c9c..cab209a3 100644 --- a/webapp/tsconfig.json +++ b/webapp/tsconfig.json @@ -1,10 +1,12 @@ { - "include": ["src/**/*"], - "compilerOptions": { - "target": "es2021", - "strict": true , - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "jsx": "react-jsx" - } - } \ No newline at end of file + "include": ["src/**/*"], + "compilerOptions": { + "target": "esnext", + "strict": true, + "moduleResolution": "node", + "module": "ESNext", + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "jsx": "react-jsx" + } +} diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 061bfc1c..cf8f71dd 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -23,13 +23,6 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/runtime@^7.12.5": - version "7.17.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" - integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== - dependencies: - regenerator-runtime "^0.13.4" - "@codemirror/autocomplete@^0.19.0": version "0.19.12" resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-0.19.12.tgz#4c9e4487b45e6877807e4f16c1fffd5e7639ae52" @@ -694,6 +687,11 @@ "@parcel/utils" "2.3.2" nullthrows "^1.1.1" +"@parcel/service-worker@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@parcel/service-worker/-/service-worker-2.3.2.tgz#c5d5ca876249fc39dbfd55e7f6be94645244cf5c" + integrity sha512-snBZYe8MV4suTtbQAABQ8OBWdccO07onxayReiDLUzTRffNB2V1ikLDYkngLMmpRAa1lp0bnB0KfvVX8jeLLOg== + "@parcel/source-map@^2.0.0": version "2.0.2" resolved "https://registry.yarnpkg.com/@parcel/source-map/-/source-map-2.0.2.tgz#9aa0b00518cee31d5634de6e9c924a5539b142c1" @@ -897,28 +895,6 @@ chrome-trace-event "^1.0.2" nullthrows "^1.1.1" -"@reach/observe-rect@^1.1.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2" - integrity sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ== - -"@reach/portal@^0.16.0": - version "0.16.2" - resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.16.2.tgz#ca83696215ee03acc2bb25a5ae5d8793eaaf2f64" - integrity sha512-9ur/yxNkuVYTIjAcfi46LdKUvH0uYZPfEp4usWcpt6PIp+WDF57F/5deMe/uGi/B/nfDweQu8VVwuMVrCb97JQ== - dependencies: - "@reach/utils" "0.16.0" - tiny-warning "^1.0.3" - tslib "^2.3.0" - -"@reach/utils@0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.16.0.tgz#5b0777cf16a7cab1ddd4728d5d02762df0ba84ce" - integrity sha512-PCggBet3qaQmwFNcmQ/GqHSefadAFyNCUekq9RrWoaU9hh/S4iaFgf2MBMdM47eQj5i/Bk0Mm07cP/XPFlkN+Q== - dependencies: - tiny-warning "^1.0.3" - tslib "^2.3.0" - "@swc/helpers@^0.2.11": version "0.2.14" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.2.14.tgz#20288c3627442339dd3d743c944f7043ee3590f0" @@ -1283,11 +1259,6 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -fast-equals@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-2.0.4.tgz#3add9410585e2d7364c2deeb6a707beadb24b927" - integrity sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w== - get-port@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119" @@ -1329,6 +1300,11 @@ htmlparser2@^7.1.1: domutils "^2.8.0" entities "^3.0.1" +idb@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/idb/-/idb-7.0.0.tgz#f349b418c128f625961147a7d6b0e4b526fd34ed" + integrity sha512-jSx0WOY9Nj+QzP6wX5e7g64jqh8ExtDs/IAuOrOEZCD/h6+0HqyrKsDMfdJc0hqhSvh0LsrwqrkDn+EtjjzSRA== + import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -1369,17 +1345,6 @@ json5@^2.2.0: dependencies: minimist "^1.2.5" -kbar@^0.1.0-beta.27: - version "0.1.0-beta.27" - resolved "https://registry.yarnpkg.com/kbar/-/kbar-0.1.0-beta.27.tgz#6fec637054599dc4c6aa5a0cfc4042a50b3e32d1" - integrity sha512-4knRJxDQqx3LUduhjuJh9EDGxnFpaQKjXt11UOsjKQ4ByXTTQpPjfAaKagVcTp9uVwEXGDhvGrsGbMfrI+6/Kg== - dependencies: - "@reach/portal" "^0.16.0" - fast-equals "^2.0.3" - match-sorter "^6.3.0" - react-virtual "^2.8.2" - tiny-invariant "^1.2.0" - lilconfig@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" @@ -1418,14 +1383,6 @@ loose-envify@^1.1.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -match-sorter@^6.3.0: - version "6.3.1" - resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda" - integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw== - dependencies: - "@babel/runtime" "^7.12.5" - remove-accents "0.4.2" - mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -1804,13 +1761,6 @@ react-refresh@^0.9.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf" integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ== -react-virtual@^2.8.2: - version "2.10.4" - resolved "https://registry.yarnpkg.com/react-virtual/-/react-virtual-2.10.4.tgz#08712f0acd79d7d6f7c4726f05651a13b24d8704" - integrity sha512-Ir6+oPQZTVHfa6+JL9M7cvMILstFZH/H3jqeYeKI4MSUX+rIruVwFC6nGVXw9wqAw8L0Kg2KvfXxI85OvYQdpQ== - dependencies: - "@reach/observe-rect" "^1.1.0" - react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -1819,16 +1769,11 @@ react@^17.0.2: loose-envify "^1.1.0" object-assign "^4.1.1" -regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: +regenerator-runtime@^0.13.7: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== -remove-accents@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" - integrity sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U= - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -1934,21 +1879,6 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tiny-invariant@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" - integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== - -tiny-warning@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" - integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== - -tslib@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== - type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"