1
0

Something working

This commit is contained in:
Zef Hemel 2022-02-21 11:27:30 +01:00
parent af59d394ae
commit cbe0677f93
10 changed files with 393 additions and 56 deletions

View File

@ -1,3 +1,4 @@
{
"editor.formatOnSave": true
"editor.formatOnSave": true,
"editor.tabSize": 2
}

View File

@ -1,3 +1,5 @@
## Lots of
Content #here
@zef.hemel

View File

@ -7,9 +7,11 @@ For the rest of you, if you are not a parent, have no plan to be, or have absolu
“So, do I need your permission to stop reading now? I will decide that myself, thank you very much!”
Thank you for sharing that perspective. However, in your case specifically, I have to insist you keep reading. Its kids with your independent mindset that were trying to raise here. Although, perhaps you already know how to do that. Get in touch.
Thank you for sharing that perspective. However, in your case specifically, I have to insist you keep reading. Its kids with your independent mindset that were trying to raise here. Although, perhaps you already know how to do that. Get in touch.
----
Hello there
-----
Before we start, why would you even listen to me? Lets [[be honest]] here, I have no relevant credentials beyond being a dad myself. However, has that ever stopped anybody from doing anything? As any conspiracy theorist would say: you dont need to take my word for it, do your own research — Im just asking questions!

View File

@ -1 +1 @@
Sappie
# Sappie

View File

@ -21,6 +21,7 @@
"@codemirror/lang-markdown": "^0.19.6",
"@codemirror/state": "^0.19.7",
"@codemirror/view": "^0.19.42",
"kbar": "^0.1.0-beta.27",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}

View File

@ -5,8 +5,7 @@ import { history, historyKeymap } from "@codemirror/history";
import { indentOnInput } from "@codemirror/language";
import { bracketMatching } from "@codemirror/matchbrackets";
import { searchKeymap } from "@codemirror/search";
import { EditorState, StateField } from "@codemirror/state";
import { EditorState, StateField, Transaction } from "@codemirror/state";
import {
drawSelection,
dropCursor,
@ -14,48 +13,101 @@ import {
highlightSpecialChars,
keymap,
} from "@codemirror/view";
import React, { useEffect, useReducer, useRef } from "react";
import ReactDOM from "react-dom";
import * as commands from "./commands";
import { markdown } from "./markdown";
import { HttpFileSystem } from "./fs";
import { lineWrapper } from "./lineWrapper";
import { markdown } from "./markdown";
import customMarkDown from "./parser";
import customMarkdownStyle from "./style";
import { HttpFileSystem } from "./fs";
import ReactDOM from "react-dom";
import { MutableRefObject, useEffect, useRef, useState } from "react";
import { FilterList } from "./components/filter";
const fs = new HttpFileSystem("http://localhost:2222/fs");
type AppState = {
currentNote: string;
type NoteMeta = {
name: string;
};
type AppViewState = {
currentNote: string;
isSaved: boolean;
isFiltering: boolean;
allNotes: NoteMeta[];
};
const initialViewState = {
currentNote: "",
isSaved: false,
isFiltering: false,
allNotes: [],
};
type Action =
| { type: "loaded"; name: string }
| { type: "saved" }
| { type: "start-navigate" }
| { type: "stop-navigate" }
| { type: "updated" }
| { type: "notes-list"; notes: NoteMeta[] };
function reducer(state: AppViewState, action: Action): AppViewState {
switch (action.type) {
case "loaded":
return {
...state,
currentNote: action.name,
isSaved: true,
};
case "saved":
return {
...state,
isSaved: true,
};
case "updated":
return {
...state,
isSaved: false,
};
case "start-navigate":
return {
...state,
isFiltering: true,
};
case "stop-navigate":
return {
...state,
isFiltering: false,
};
case "notes-list":
return {
...state,
allNotes: action.notes,
};
}
}
class Editor {
view: EditorView;
currentNote: string;
dispatch: React.Dispatch<Action>;
constructor(parent: Element, currentNote: string, text: string) {
constructor(
parent: Element,
currentNote: string,
text: string,
dispatch: React.Dispatch<Action>
) {
this.view = new EditorView({
state: this.createEditorState(text),
parent: parent,
});
this.currentNote = currentNote;
this.dispatch = dispatch;
}
load(name: string, text: string) {
this.currentNote = name;
this.view.setState(this.createEditorState(text));
}
async save() {
await fs.writeNote(this.currentNote, this.view.state.sliceDoc());
}
focus() {
this.view.focus();
}
private createEditorState(text: string): EditorState {
createEditorState(text: string): EditorState {
return EditorState.create({
doc: text,
extensions: [
@ -107,67 +159,150 @@ class Editor {
return true;
},
},
{
key: "Ctrl-p",
mac: "Cmd-p",
run: (target): boolean => {
this.dispatch({ type: "start-navigate" });
return true;
},
},
]),
EditorView.domEventHandlers({
click: (event: MouseEvent, view: EditorView) => {
if (event.metaKey || event.ctrlKey) {
console.log("Navigate click");
let coords = view.posAtCoords(event);
console.log(
"Coords",
view.state.doc.sliceString(coords!, coords! + 1)
);
return false;
}
},
click: this.click.bind(this),
}),
markdown({
base: customMarkDown,
}),
StateField.define({
create: () => null,
update: (value, transaction) => {
if (transaction.docChanged) {
console.log("Something changed");
}
return null;
},
update: this.update.bind(this),
}),
],
});
}
update(value: null, transaction: Transaction): null {
if (transaction.docChanged) {
console.log("Something changed");
this.dispatch({
type: "updated",
});
}
return null;
}
load(name: string, text: string) {
this.currentNote = name;
this.view.setState(this.createEditorState(text));
}
click(event: MouseEvent, view: EditorView) {
if (event.metaKey || event.ctrlKey) {
console.log("Navigate click");
let coords = view.posAtCoords(event);
console.log("Coords", view.state.doc.sliceString(coords!, coords! + 1));
return false;
}
}
async save() {
await fs.writeNote(this.currentNote, this.view.state.sliceDoc());
this.dispatch({ type: "saved" });
}
focus() {
this.view.focus();
}
}
function TopBar({ editor }: { editor: Editor | null }) {
function TopBar({
currentNote,
isSaved,
isFiltering,
allNotes,
onNavigate,
}: {
currentNote: string;
isSaved: boolean;
isFiltering: boolean;
allNotes: NoteMeta[];
onNavigate: (note: string) => void;
}) {
return (
<div id="top">
This is the top bar, do something cool: {editor?.currentNote}
<span className="current-note">{currentNote}</span>
{isSaved ? "" : "*"}
{isFiltering ? (
<FilterList
initialText=""
options={allNotes}
onSelect={(opt) => {
console.log("Selected", opt);
onNavigate(opt.name);
}}
></FilterList>
) : null}
</div>
);
}
function App() {
let editor: Editor | null;
function AppView() {
const editorRef = useRef<HTMLDivElement>(null);
const [editor, setEditor] = useState<Editor | null>(null);
const [appState, dispatch] = useReducer(reducer, initialViewState);
useEffect(() => {
let editor = new Editor(editorRef.current!, "", "");
editor = new Editor(editorRef.current!, "", "", dispatch);
editor.focus();
// @ts-ignore
window.editor = editor;
fs.readNote("start").then((text) => {
editor.load("start", text);
editor!.load("start", text);
dispatch({
type: "loaded",
name: "start",
});
});
setEditor(editor);
}, []);
useEffect(() => {
fs.listNotes()
.then((notes) => {
dispatch({
type: "notes-list",
notes: notes,
});
})
.catch((e) => console.error(e));
}, []);
return (
<>
<TopBar editor={editor} />
<TopBar
currentNote={appState.currentNote}
isSaved={appState.isSaved}
isFiltering={appState.isFiltering}
allNotes={appState.allNotes}
onNavigate={(note) => {
dispatch({ type: "stop-navigate" });
editor!.focus();
fs.readNote(note).then((text) => {
editor!.load(note, text);
dispatch({
type: "loaded",
name: note,
});
});
}}
/>
<div id="editor" ref={editorRef}></div>
<div id="bottom">Bottom</div>
</>
);
}
ReactDOM.render(<App />, document.body);
ReactDOM.render(<AppView />, document.body);

View File

@ -0,0 +1,92 @@
import React, { useEffect, useRef, useState } from "react";
type Option = {
name: string;
};
export function FilterList({
initialText,
options,
onSelect,
}: {
initialText: string;
options: Option[];
onSelect: (option: Option) => void;
}) {
const searchBoxRef = useRef<HTMLInputElement>(null);
const [text, setText] = useState(initialText);
const [matchingOptions, setMatchingOptions] = useState(options);
const [selectedOption, setSelectionOption] = useState(0);
const filter = (e: React.ChangeEvent<HTMLInputElement>) => {
const keyword = e.target.value.toLowerCase();
if (keyword) {
const results = options.filter((option) => {
return option.name.toLowerCase().indexOf(keyword) !== -1;
});
setMatchingOptions(results);
} else {
setMatchingOptions(options);
}
setText(keyword);
setSelectionOption(0);
};
useEffect(() => {
searchBoxRef.current!.focus();
}, []);
return (
<div className="filter-container">
<input
type="search"
value={text}
ref={searchBoxRef}
onChange={filter}
onKeyDown={(e: React.KeyboardEvent) => {
console.log("Key up", e.key);
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;
}
}}
className="input"
placeholder="Filter"
/>
<div className="result-list">
{matchingOptions && matchingOptions.length > 0 ? (
matchingOptions.map((option, idx) => (
<li
key={"" + idx}
className={selectedOption === idx ? "selected-option" : "option"}
onMouseOver={(e) => {
setSelectionOption(idx);
}}
onClick={(e) => {
onSelect(option);
e.preventDefault();
}}
>
<span className="user-name">{option.name}</span>
</li>
))
) : (
<h1>No results found!</h1>
)}
</div>
</div>
);
}

View File

@ -4,7 +4,7 @@ export interface NoteMeta {
}
export interface FileSystem {
listNotes(): Promise<NoteMeta>;
listNotes(): Promise<NoteMeta[]>;
readNote(name: string): Promise<string>;
writeNote(name: string, text: string): Promise<void>;
}
@ -15,7 +15,7 @@ export class HttpFileSystem implements FileSystem {
constructor(url: string) {
this.url = url;
}
async listNotes(): Promise<NoteMeta> {
async listNotes(): Promise<NoteMeta[]> {
let req = await fetch(this.url, {
method: 'GET'
});

View File

@ -130,3 +130,27 @@ body {
text-indent: calc(-1 * var(--ident) - 3px);
margin-left: var(--ident);
}
reach-portal input {
background-color: #fff;
border: 0;
}
reach-portal > div > div {
background-color: #fff;
border: #000 1px solid;
padding: 5px;
}
.filter-container {
background-color: white;
display: block;
position: absolute;
left: 10px;
top: 10px;
z-index: 1000;
}
.filter-container .selected-option {
background-color: #520130;
}

View File

@ -23,6 +23,13 @@
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"
@ -890,6 +897,28 @@
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"
@ -1254,6 +1283,11 @@ 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"
@ -1335,6 +1369,17 @@ 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"
@ -1373,6 +1418,14 @@ 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"
@ -1751,6 +1804,13 @@ 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"
@ -1759,11 +1819,16 @@ react@^17.0.2:
loose-envify "^1.1.0"
object-assign "^4.1.1"
regenerator-runtime@^0.13.7:
regenerator-runtime@^0.13.4, 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"
@ -1869,6 +1934,21 @@ 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"