This commit is contained in:
Zef Hemel
2022-03-01 16:57:20 +01:00
parent 5cd5feeafa
commit 9b1b950c41
23 changed files with 636 additions and 290 deletions
+5 -1
View File
@@ -1,6 +1,7 @@
import { AppCommand } from "../types";
import { isMacLike } from "../util";
import { FilterList, Option } from "./filter";
import { faPersonRunning } from "@fortawesome/free-solid-svg-icons";
export function CommandPalette({
commands,
@@ -20,9 +21,12 @@ export function CommandPalette({
console.log("Commands", options);
return (
<FilterList
placeholder="Enter command to run"
label="Run"
placeholder="Command"
options={options}
allowNew={false}
icon={faPersonRunning}
helpText="Start typing the command name to filter results, press <code>Return</code> to run."
onSelect={(opt) => {
if (opt) {
onTrigger(commands.get(opt.name));
+52 -36
View File
@@ -1,4 +1,6 @@
import React, { useEffect, useRef, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconDefinition } from "@fortawesome/free-solid-svg-icons";
export interface Option {
name: string;
@@ -16,17 +18,23 @@ function magicSorter(a: Option, b: Option): number {
export function FilterList({
placeholder,
options,
label,
onSelect,
onKeyPress,
allowNew = false,
helpText = "",
icon,
newHint,
}: {
placeholder: string;
options: Option[];
label: string;
onKeyPress?: (key: string, currentText: string) => void;
onSelect: (option: Option | undefined) => void;
allowNew?: boolean;
helpText: string;
newHint?: string;
icon?: IconDefinition;
}) {
const searchBoxRef = useRef<HTMLInputElement>(null);
const [text, setText] = useState("");
@@ -81,40 +89,45 @@ export function FilterList({
};
}, []);
const returEl = (
<div className="filter-container">
<input
type="text"
value={text}
placeholder={placeholder}
ref={searchBoxRef}
onChange={filter}
onKeyDown={(e: React.KeyboardEvent) => {
console.log("Key up", e.key);
if (onKeyPress) {
onKeyPress(e.key, text);
}
switch (e.key) {
case "ArrowUp":
setSelectionOption(Math.max(0, selectedOption - 1));
break;
case "ArrowDown":
setSelectionOption(
Math.min(matchingOptions.length - 1, selectedOption + 1)
);
break;
case "Enter":
onSelect(matchingOptions[selectedOption]);
e.preventDefault();
break;
case "Escape":
onSelect(undefined);
break;
}
}}
className="input"
/>
const returnEl = (
<div className="filter-box">
<div className="header">
<label>{label}</label>
<input
type="text"
value={text}
placeholder={placeholder}
ref={searchBoxRef}
onChange={filter}
onKeyDown={(e: React.KeyboardEvent) => {
console.log("Key up", e.key);
if (onKeyPress) {
onKeyPress(e.key, text);
}
switch (e.key) {
case "ArrowUp":
setSelectionOption(Math.max(0, selectedOption - 1));
break;
case "ArrowDown":
setSelectionOption(
Math.min(matchingOptions.length - 1, selectedOption + 1)
);
break;
case "Enter":
onSelect(matchingOptions[selectedOption]);
e.preventDefault();
break;
case "Escape":
onSelect(undefined);
break;
}
}}
/>
</div>
<div
className="help-text"
dangerouslySetInnerHTML={{ __html: helpText }}
></div>
<div className="result-list">
{matchingOptions && matchingOptions.length > 0
? matchingOptions.map((option, idx) => (
@@ -132,7 +145,10 @@ export function FilterList({
onSelect(option);
}}
>
<span className="user-name">{option.name}</span>
<span className="icon">
{icon && <FontAwesomeIcon icon={icon} />}
</span>
<span className="name">{option.name}</span>
{option.hint && <span className="hint">{option.hint}</span>}
</div>
))
@@ -147,5 +163,5 @@ export function FilterList({
});
});
return returEl;
return returnEl;
}
+8 -3
View File
@@ -1,4 +1,6 @@
import { PageMeta } from "../types";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFileLines } from "@fortawesome/free-solid-svg-icons";
export function NavigationBar({
currentPage,
@@ -8,9 +10,12 @@ export function NavigationBar({
onClick: () => void;
}) {
return (
<div id="top">
<div className="current-page" onClick={onClick}>
» {currentPage?.name}
<div id="top" onClick={onClick}>
<div className="inner">
<span className="icon">
<FontAwesomeIcon icon={faFileLines} />
</span>
<span className="current-page">{currentPage?.name}</span>
</div>
</div>
);
+5 -1
View File
@@ -1,5 +1,6 @@
import { PageMeta } from "../types";
import { FilterList, Option } from "./filter";
import { faFileLines } from "@fortawesome/free-solid-svg-icons";
export function PageNavigator({
allPages,
@@ -28,9 +29,12 @@ export function PageNavigator({
}
return (
<FilterList
placeholder=""
placeholder="Page"
label="Open"
options={options}
icon={faFileLines}
allowNew={true}
helpText="Start typing the page name to filter results, press <code>Return</code> to open."
newHint="Create page"
onSelect={(opt) => {
onNavigate(opt?.name);
+24 -18
View File
@@ -57,6 +57,7 @@ import {
import { safeRun } from "./util";
import { Indexer } from "./indexer";
import { IPageNavigator, PathPageNavigator } from "./navigator";
import { smartQuoteKeymap } from "./smart_quotes";
class PageState {
editorState: EditorState;
@@ -123,7 +124,7 @@ export class Editor implements AppEventDispatcher {
}
async loadPlugins() {
const system = new BrowserSystem("plugin");
const system = new BrowserSystem("/plugin");
system.registerSyscalls(
dbSyscalls,
editorSyscalls(this),
@@ -222,6 +223,7 @@ export class Editor implements AppEventDispatcher {
{ selector: "FencedCode", class: "line-fenced-code" },
]),
keymap.of([
...smartQuoteKeymap,
...closeBracketsKeymap,
...standardKeymap,
...searchKeymap,
@@ -248,8 +250,8 @@ export class Editor implements AppEventDispatcher {
},
},
{
key: "Ctrl-e",
mac: "Cmd-e",
key: "Ctrl-k",
mac: "Cmd-k",
run: (target): boolean => {
this.viewDispatch({ type: "start-navigate" });
return true;
@@ -302,7 +304,6 @@ export class Editor implements AppEventDispatcher {
ctx: CompletionContext
): Promise<CompletionResult | null> {
let allCompletionResults = await this.dispatchAppEvent("editor:complete");
// console.log("All results", allCompletionResults);
if (allCompletionResults.length === 1) {
return allCompletionResults[0];
} else if (allCompletionResults.length > 1) {
@@ -368,13 +369,14 @@ export class Editor implements AppEventDispatcher {
console.log("Page not modified, skipping saving");
return;
}
// Write to file system
let text = editorState.sliceDoc();
let pageMeta = await this.space.writePage(this.currentPage.name, text);
// Write to the space
const pageName = this.currentPage.name;
const text = editorState.sliceDoc();
let pageMeta = await this.space.writePage(pageName, text);
// Update in open page cache
this.openPages.set(
this.currentPage.name,
pageName,
new PageState(editorState, this.editorView!.scrollDOM.scrollTop, pageMeta)
);
@@ -422,10 +424,10 @@ export class Editor implements AppEventDispatcher {
console.log("File changed on disk, reloading");
let pageData = await this.space.readPage(currentPageName);
this.openPages.set(
newPageMeta.name,
currentPageName,
new PageState(this.createEditorState(pageData.text), 0, newPageMeta)
);
await this.loadPage(currentPageName);
await this.loadPage(currentPageName, false);
}
}
@@ -433,11 +435,11 @@ export class Editor implements AppEventDispatcher {
this.editorView!.focus();
}
async navigate(name: string) {
await this.pageNavigator.navigate(name);
navigate(name: string) {
this.pageNavigator.navigate(name);
}
async loadPage(pageName: string) {
async loadPage(pageName: string, checkNewVersion: boolean = true) {
let pageState = this.openPages.get(pageName);
if (!pageState) {
let pageData = await this.space.readPage(pageName);
@@ -447,11 +449,8 @@ export class Editor implements AppEventDispatcher {
pageData.meta
);
this.openPages.set(pageName, pageState!);
} else {
// Loaded page from in-mory cache, let's async see if this page hasn't been updated
this.checkForNewVersion(pageState.meta).catch((e) => {
console.error("Failed to check for new version");
});
// Freshly loaded, no need to check for a new version either way
checkNewVersion = false;
}
this.editorView!.setState(pageState!.editorState);
this.editorView!.scrollDOM.scrollTop = pageState!.scrollTop;
@@ -470,6 +469,13 @@ export class Editor implements AppEventDispatcher {
) {
await this.indexPage(pageState.editorState.sliceDoc(), pageState.meta);
}
if (checkNewVersion) {
// Loaded page from in-memory cache, let's async see if this page hasn't been updated
this.checkForNewVersion(pageState.meta).catch((e) => {
console.error("Failed to check for new version");
});
}
}
ViewComponent(): React.ReactElement {
+2 -2
View File
@@ -43,8 +43,8 @@ self.addEventListener("message", (event) => {
let data = messageEvent.data;
switch (data.type) {
case "boot":
console.log("Booting", `./${data.prefix}/function/${data.name}`);
importScripts(`./${data.prefix}/function/${data.name}`);
console.log("Booting", `${data.prefix}/function/${data.name}`);
importScripts(`${data.prefix}/function/${data.name}`);
self.postMessage({
type: "inited",
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

+2 -1
View File
@@ -3,8 +3,9 @@
<head>
<meta charset="utf-8" />
<title>Page</title>
<link rel="stylesheet" href="styles.css" />
<link rel="stylesheet" href="styles/main.scss" />
<script type="module" src="boot.ts"></script>
<link rel="manifest" href="manifest.json" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
</head>
+17
View File
@@ -0,0 +1,17 @@
{
"short_name": "Nugget",
"name": "Nugget",
"icons": [
{
"src": "./images/logo.png",
"type": "image/png",
"sizes": "1024x1024"
}
],
"capture_links": "new-client",
"start_url": "/start",
"display": "standalone",
"scope": "/",
"theme_color": "#000",
"description": "Note taking for winners"
}
+4 -2
View File
@@ -17,7 +17,6 @@ function decodePageUrl(url: string): string {
export class PathPageNavigator implements IPageNavigator {
navigationResolve?: (value: undefined) => void;
async navigate(page: string) {
console.log("Pushing state", page);
window.history.pushState({ page: page }, page, `/${encodePageUrl(page)}`);
window.dispatchEvent(new PopStateEvent("popstate"));
await new Promise<undefined>((resolve) => {
@@ -27,7 +26,10 @@ export class PathPageNavigator implements IPageNavigator {
}
subscribe(pageLoadCallback: (pageName: string) => Promise<void>): void {
const cb = () => {
console.log("State popped", this.getCurrentPage());
const gotoPage = this.getCurrentPage();
if (!gotoPage) {
return;
}
safeRun(async () => {
await pageLoadCallback(this.getCurrentPage());
if (this.navigationResolve) {
+1
View File
@@ -46,6 +46,7 @@ export class BrowserSystem extends System {
new URL("../plugin_sw.ts", import.meta.url),
{
type: "module",
scope: "/",
}
);
+1 -1
View File
@@ -18,7 +18,7 @@ export class FunctionWorker {
// type: "classic",
// });
let worker = window.Worker;
this.worker = new worker("function_worker.js");
this.worker = new worker("/function_worker.js");
// console.log("Starting worker", this.worker);
this.worker.onmessage = this.onmessage.bind(this);
+1 -1
View File
@@ -4,7 +4,7 @@ export default function reducer(
state: AppViewState,
action: Action
): AppViewState {
console.log("Got action", action);
// console.log("Got action", action);
switch (action.type) {
case "page-loaded":
return {
+35
View File
@@ -0,0 +1,35 @@
import { KeyBinding } from "@codemirror/view";
// TODO: Add support for selection (put quotes around or create blockquote block?)
function keyBindingForQuote(
quote: string,
left: string,
right: string
): KeyBinding {
return {
key: quote,
run: (target): boolean => {
let cursorPos = target.state.selection.main.from;
let chBefore = target.state.sliceDoc(cursorPos - 1, cursorPos);
let quote = right;
if (/\W/.exec(chBefore) && !/[!\?,\.\-=“]/.exec(chBefore)) {
quote = left;
}
target.dispatch({
changes: {
insert: quote,
from: cursorPos,
},
selection: {
anchor: cursorPos + 1,
},
});
return true;
},
};
}
export const smartQuoteKeymap: KeyBinding[] = [
keyBindingForQuote('"', "“", "”"),
keyBindingForQuote("'", "", ""),
];
+1
View File
@@ -10,6 +10,7 @@ export interface Space {
export class HttpRemoteSpace implements Space {
url: string;
constructor(url: string) {
this.url = url;
}
-221
View File
@@ -1,221 +0,0 @@
:root {
--ident: 18px;
/* --editor-font: "Avenir"; */
--editor-font: "Menlo";
--top-bar-bg: rgb(41, 41, 41);
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#top {
height: 40px;
background-color: var(--top-bar-bg);
position: absolute;
top: 0;
left: 0;
right: 0;
color: #eee;
}
#bottom {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 25px;
background-color: var(--top-bar-bg);
color: #eee;
margin: 0;
padding: 5px;
font-family: var(--editor-font);
text-align: right;
}
#editor {
position: absolute;
top: 40px;
bottom: 40px;
left: 0;
right: 0;
overflow-y: hidden;
}
.cm-editor {
width: 100%;
height: 100%;
font-size: var(--ident);
}
@media only screen and (max-width: 800px) {
.cm-editor .cm-content {
margin: 0 10px !important;
}
}
.cm-editor .cm-content {
font-family: var(--editor-font);
margin: auto;
max-width: 800px;
}
.cm-editor .cm-selectionBackground {
background-color: #d7e1f6 !important;
}
.cm-editor .h1 {
font-size: 1.5em;
color: #fff;
font-weight: bold;
}
.cm-editor .cm-line.line-h1 {
display: block;
background-color: rgba(0, 15, 52, 0.6);
}
.cm-editor .h1.meta {
color: orange;
}
.cm-editor .h2 {
font-size: 1.2em;
color: #fff;
font-weight: bold;
}
.cm-editor .cm-line.line-h2 {
display: block;
background-color: rgba(0, 15, 52, 0.6);
}
.cm-editor .h2.meta {
color: orange;
}
/* .cm-editor .cm-line {
padding-left: 50px;
max-width: 800px;
} */
.cm-editor .line-code {
background-color: #efefef;
margin-left: 30px;
}
.cm-editor .line-fenced-code {
background-color: #efefef;
}
.cm-editor .meta {
color: #650007;
}
.cm-editor .line-blockquote {
background-color: #eee;
color: #676767;
text-indent: calc(-1 * (var(--ident) + 3px));
padding-left: var(--ident);
}
.cm-editor .emphasis {
font-style: italic;
}
.cm-editor .strong {
font-weight: 900;
}
.cm-editor .link:not(.meta, .url) {
color: #0330cb;
text-decoration: underline;
}
.cm-editor .link.url {
color: #7e7d7d;
}
.cm-editor .url:not(.link) {
color: #0330cb;
text-decoration: underline;
}
.cm-editor .wiki-link-page {
color: #0330cb;
text-decoration: underline;
}
.cm-editor .wiki-link {
color: #808080;
}
.cm-editor .mention {
color: gray;
}
.cm-editor .tag {
color: #8d8d8d;
}
.cm-editor .line-li {
text-indent: calc(-1 * var(--ident) - 3px);
margin-left: var(--ident);
}
.cm-editor .task-marker {
background-color: #ddd;
}
.current-page {
font-family: var(--editor-font);
margin-left: 10px;
margin-top: 10px;
font-weight: bold;
}
.filter-container {
font-family: var(--editor-font);
display: block;
border: #333 1px solid;
z-index: 1000;
position: absolute;
left: 25px;
top: 10px;
right: 10px;
}
.filter-container .result-list {
max-height: 200px;
overflow-y: scroll;
background-color: white;
}
.filter-container input {
font-family: var(--editor-font);
width: 100%;
/* border: 1px #333 solid; */
background-color: var(--top-bar-bg);
color: #eee;
border: 0;
padding: 3px;
outline: 0;
font-weight: bold;
}
.filter-container .option,
.filter-container .selected-option {
padding: 3px 3px 3px 3px;
cursor: pointer;
}
.filter-container .selected-option {
background-color: #b1b1b1;
}
.filter-container .hint {
float: right;
margin-right: 10px;
color: #333;
}
+120
View File
@@ -0,0 +1,120 @@
.cm-editor {
width: 100%;
height: 100%;
font-size: var(--ident);
.cm-content {
font-family: var(--editor-font);
margin: auto;
max-width: 800px;
}
.cm-selectionBackground {
background-color: #d7e1f6 !important;
}
.h1 {
font-size: 1.5em;
color: #fff;
font-weight: bold;
}
.cm-line.line-h1 {
display: block;
background-color: rgba(0, 15, 52, 0.6);
}
.h1.meta {
color: orange;
}
.h2 {
font-size: 1.2em;
color: #fff;
font-weight: bold;
}
.cm-line.line-h2 {
display: block;
background-color: rgba(0, 15, 52, 0.6);
}
.h2.meta {
color: orange;
}
/* Color list item this way */
.line-li .meta {
color: rgb(0, 123, 19);
}
/* Then undo other meta */
.line-li .meta ~ .meta {
color: #650007;
}
.line-code {
background-color: #efefef;
margin-left: 30px;
}
.line-fenced-code {
background-color: #efefef;
}
.meta {
color: #650007;
}
.line-blockquote {
background-color: #eee;
color: #676767;
text-indent: calc(-1 * (var(--ident) + 3px));
padding-left: var(--ident);
}
.emphasis {
font-style: italic;
}
.strong {
font-weight: 900;
}
.link:not(.meta, .url) {
color: #0330cb;
text-decoration: underline;
}
.link.url {
color: #7e7d7d;
}
.url:not(.link) {
color: #0330cb;
text-decoration: underline;
}
.wiki-link-page {
color: #0330cb;
text-decoration: underline;
}
.wiki-link {
color: #808080;
}
.mention {
color: gray;
}
.tag {
color: #8d8d8d;
}
.line-li {
text-indent: calc(-1 * var(--ident) - 3px);
margin-left: var(--ident);
}
.task-marker {
background-color: #ddd;
}
}
+87
View File
@@ -0,0 +1,87 @@
.filter-box {
position: absolute;
font-family: var(--ui-font);
margin: auto;
width: 500px;
height: 600px;
background-color: #fff;
left: 0;
right: 0;
top: 0;
bottom: 0;
max-height: 290px;
overflow: auto;
z-index: 100;
border: rgb(103, 103, 103) 1px solid;
border-radius: 8px;
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
label {
color: var(--highlight-color);
}
.header {
border-bottom: 1px rgb(108, 108, 108) solid;
padding: 13px 10px 10px 10px;
}
.help-text {
background-color: #eee;
border-bottom: 1px rgb(108, 108, 108) solid;
padding: 5px;
color: #555;
}
.result-list {
max-height: 200px;
overflow-y: scroll;
background-color: white;
.icon {
padding: 0 8px 0 5px;
}
.name {
padding-top: -3px;
}
}
input {
font-family: "Arial";
background: transparent;
color: #000;
border: 0;
padding: 3px;
outline: 0;
font-size: 1em;
}
input::placeholder {
color: rgb(199, 199, 199);
font-weight: normal;
}
.option,
.selected-option {
padding: 8px;
cursor: pointer;
}
.selected-option {
background-color: var(--highlight-color);
color: #eee;
}
.option .hint,
.selected-option .hint {
float: right;
margin-right: 0;
margin-top: -4px;
padding-left: 5px;
padding-right: 5px;
padding-top: 3px;
padding-bottom: 3px;
color: #eee;
background-color: #212476;
border-radius: 5px;
}
}
+77
View File
@@ -0,0 +1,77 @@
@use "editor.scss";
@use "filter_box.scss";
:root {
--ident: 18px;
/* --editor-font: "Avenir"; */
--editor-font: "Menlo";
--ui-font: "Arial";
--top-bar-bg: rgb(41, 41, 41);
--highlight-color: #464cfc;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#top {
height: 55px;
position: absolute;
top: 0;
left: 0;
right: 0;
background-color: rgb(213, 213, 213);
border-bottom: rgb(193, 193, 193) 1px solid;
color: rgb(55, 55, 55);
.inner {
padding-top: 12px;
max-width: 800px;
font-size: 28px;
margin: auto;
}
.current-page {
font-family: var(--ui-font);
font-weight: bold;
}
.icon {
padding-left: 5px;
padding-right: 10px;
}
}
#bottom {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 20px;
background-color: rgb(232, 232, 232);
color: rgb(79, 78, 78);
border-top: rgb(186, 186, 186) 1px solid;
margin: 0;
padding: 5px 10px;
font-family: var(--ui-font);
font-size: 0.9em;
text-align: right;
}
#editor {
position: absolute;
top: 60px;
bottom: 30px;
left: 0;
right: 0;
overflow-y: hidden;
}
@media only screen and (max-width: 800px) {
.cm-editor .cm-content {
margin: 0 10px !important;
}
}