More tweaks

pull/119/head
Zef Hemel 2022-11-16 17:17:07 +01:00
parent 6cad99f097
commit 7fefc212a8
14 changed files with 182 additions and 187 deletions

View File

@ -198,6 +198,13 @@ functions:
description: Turn line into h3 header description: Turn line into h3 header
match: "^#*\\s*" match: "^#*\\s*"
replace: "### " replace: "### "
makeH4:
redirect: applyLineReplace
slashCommand:
name: h4
description: Turn line into h4 header
match: "^#*\\s*"
replace: "#### "
newPage: newPage:
path: ./page.ts:newPageCommand path: ./page.ts:newPageCommand

View File

@ -91,7 +91,6 @@ export function taskToggle(event: ClickEvent) {
export function previewTaskToggle(eventString: string) { export function previewTaskToggle(eventString: string) {
const [eventName, pos] = JSON.parse(eventString); const [eventName, pos] = JSON.parse(eventString);
if (eventName === "task") { if (eventName === "task") {
console.log("Gotta toggle a task at", pos);
return taskToggleAtPos(+pos); return taskToggleAtPos(+pos);
} }
} }
@ -107,9 +106,6 @@ async function toggleTaskMarker(node: ParseTree, moveToPos: number) {
to: node.to, to: node.to,
insert: changeTo, insert: changeTo,
}, },
selection: {
anchor: moveToPos,
},
}); });
const parentWikiLinks = collectNodesMatching( const parentWikiLinks = collectNodesMatching(

View File

@ -1,4 +1,6 @@
import type { ClickEvent } from "../../plug-api/app_event.ts";
import type { Extension } from "../deps.ts"; import type { Extension } from "../deps.ts";
import { Editor } from "../editor.tsx";
import { blockquotePlugin } from "./block_quote.ts"; import { blockquotePlugin } from "./block_quote.ts";
import { directivePlugin } from "./directive.ts"; import { directivePlugin } from "./directive.ts";
import { hideHeaderMarkPlugin, hideMarks } from "./hide_mark.ts"; import { hideHeaderMarkPlugin, hideMarks } from "./hide_mark.ts";
@ -9,15 +11,30 @@ import { tablePlugin } from "./table.ts";
import { taskListPlugin } from "./task.ts"; import { taskListPlugin } from "./task.ts";
import { cleanWikiLinkPlugin } from "./wiki_link.ts"; import { cleanWikiLinkPlugin } from "./wiki_link.ts";
export const cleanModePlugs = [ export function cleanModePlugins(editor: Editor) {
return [
goToLinkPlugin, goToLinkPlugin,
directivePlugin, directivePlugin,
blockquotePlugin, blockquotePlugin,
hideMarks(), hideMarks(),
hideHeaderMarkPlugin, hideHeaderMarkPlugin,
hideImageNodePlugin, hideImageNodePlugin,
taskListPlugin, taskListPlugin({
// TODO: Move this logic elsewhere?
onCheckboxClick: (pos) => {
const clickEvent: ClickEvent = {
page: editor.currentPage!,
altKey: false,
ctrlKey: false,
metaKey: false,
pos: pos,
};
// Propagate click event from checkbox
editor.dispatchAppEvent("page:click", clickEvent);
},
}),
listBulletPlugin, listBulletPlugin,
tablePlugin, tablePlugin,
cleanWikiLinkPlugin(), cleanWikiLinkPlugin(),
] as Extension[]; ] as Extension[];
}

View File

@ -6,19 +6,19 @@ import {
Decoration, Decoration,
DecorationSet, DecorationSet,
EditorView, EditorView,
syntaxTree,
ViewPlugin, ViewPlugin,
ViewUpdate, ViewUpdate,
} from "../deps.ts"; } from "../deps.ts";
import { checkRangeOverlap, invisibleDecoration } from "./util.ts"; import {
checkRangeOverlap,
invisibleDecoration,
iterateTreeInVisibleRanges,
} from "./util.ts";
function getLinkAnchor(view: EditorView) { function getLinkAnchor(view: EditorView) {
const widgets: any[] = []; const widgets: any[] = [];
for (const { from, to } of view.visibleRanges) { iterateTreeInVisibleRanges(view, {
syntaxTree(view.state).iterate({
from,
to,
enter: ({ type, from, to, node }) => { enter: ({ type, from, to, node }) => {
if (type.name !== "URL") return; if (type.name !== "URL") return;
const parent = node.parent; const parent = node.parent;
@ -31,16 +31,13 @@ function getLinkAnchor(view: EditorView) {
); );
if (!cursorOverlaps) { if (!cursorOverlaps) {
widgets.push( widgets.push(
...marks.map(({ from, to }) => ...marks.map(({ from, to }) => invisibleDecoration.range(from, to)),
invisibleDecoration.range(from, to)
),
invisibleDecoration.range(from, to), invisibleDecoration.range(from, to),
); );
} }
} }
}, },
}); });
}
return Decoration.set(widgets, true); return Decoration.set(widgets, true);
} }

View File

@ -65,6 +65,9 @@ class TablePlugin {
const firstLine = lines[0], lastLine = lines[lines.length - 1]; const firstLine = lines[0], lastLine = lines[lines.length - 1];
// In case of doubt, back out
if (!firstLine || !lastLine) return;
widgets.push(invisibleDecoration.range(firstLine.from, firstLine.to)); widgets.push(invisibleDecoration.range(firstLine.from, firstLine.to));
widgets.push(invisibleDecoration.range(lastLine.from, lastLine.to)); widgets.push(invisibleDecoration.range(lastLine.from, lastLine.to));

View File

@ -1,5 +1,4 @@
import { import {
ChangeSpec,
Decoration, Decoration,
DecorationSet, DecorationSet,
EditorView, EditorView,
@ -11,12 +10,13 @@ import {
} from "../deps.ts"; } from "../deps.ts";
import { isCursorInRange, iterateTreeInVisibleRanges } from "./util.ts"; import { isCursorInRange, iterateTreeInVisibleRanges } from "./util.ts";
/** // TODO: Find a nicer way to inject this on task handler into the class
* Plugin to add checkboxes in task lists. function TaskListsPluginFactory(onCheckboxClick: (pos: number) => void) {
*/ return class TaskListsPlugin {
class TaskListsPlugin {
decorations: DecorationSet = Decoration.none; decorations: DecorationSet = Decoration.none;
constructor(view: EditorView) { constructor(
view: EditorView,
) {
this.decorations = this.addCheckboxes(view); this.decorations = this.addCheckboxes(view);
} }
update(update: ViewUpdate) { update(update: ViewUpdate) {
@ -56,42 +56,51 @@ class TaskListsPlugin {
// Checkbox is checked if it has a 'x' in between the [] // Checkbox is checked if it has a 'x' in between the []
if ("xX".includes(checkbox[1])) checked = true; if ("xX".includes(checkbox[1])) checked = true;
const dec = Decoration.replace({ const dec = Decoration.replace({
widget: new CheckboxWidget(checked, from + nfrom + 1), widget: new CheckboxWidget(
checked,
from + nfrom + 1,
onCheckboxClick,
),
}); });
widgets.push(dec.range(from + nfrom, from + nto)); widgets.push(dec.range(from + nfrom, from + nto));
} }
}; };
} }
};
} }
/** /**
* Widget to render checkbox for a task list item. * Widget to render checkbox for a task list item.
*/ */
class CheckboxWidget extends WidgetType { class CheckboxWidget extends WidgetType {
constructor(public checked: boolean, readonly pos: number) { constructor(
public checked: boolean,
readonly pos: number,
readonly clickCallback: (pos: number) => void,
) {
super(); super();
} }
toDOM(view: EditorView): HTMLElement { toDOM(_view: EditorView): HTMLElement {
const wrap = document.createElement("span"); const wrap = document.createElement("span");
wrap.classList.add("sb-checkbox"); wrap.classList.add("sb-checkbox");
const checkbox = document.createElement("input"); const checkbox = document.createElement("input");
checkbox.type = "checkbox"; checkbox.type = "checkbox";
checkbox.checked = this.checked; checkbox.checked = this.checked;
checkbox.addEventListener("click", ({ target }) => { checkbox.addEventListener("click", (e) => {
const change: ChangeSpec = { // Let the click handler handle this
from: this.pos, e.stopPropagation();
to: this.pos + 1,
insert: this.checked ? " " : "x", this.clickCallback(this.pos);
};
view.dispatch({ changes: change });
this.checked = !this.checked;
(target as HTMLInputElement).checked = this.checked;
}); });
wrap.appendChild(checkbox); wrap.appendChild(checkbox);
return wrap; return wrap;
} }
} }
export const taskListPlugin = ViewPlugin.fromClass(TaskListsPlugin, { export function taskListPlugin(
{ onCheckboxClick }: { onCheckboxClick: (pos: number) => void },
) {
return ViewPlugin.fromClass(TaskListsPluginFactory(onCheckboxClick), {
decorations: (v) => v.decorations, decorations: (v) => v.decorations,
}); });
}

View File

@ -61,9 +61,10 @@ export function iterateTreeInVisibleRanges(
leave?(node: SyntaxNodeRef): void; leave?(node: SyntaxNodeRef): void;
}, },
) { ) {
for (const { from, to } of view.visibleRanges) { // for (const { from, to } of view.visibleRanges) {
syntaxTree(view.state).iterate({ ...iterateFns, from, to }); // syntaxTree(view.state).iterate({ ...iterateFns, from, to });
} // }
syntaxTree(view.state).iterate(iterateFns);
} }
/** /**

View File

@ -6,7 +6,6 @@ import {
ViewUpdate, ViewUpdate,
} from "../deps.ts"; } from "../deps.ts";
import { import {
checkRangeOverlap,
invisibleDecoration, invisibleDecoration,
isCursorInRange, isCursorInRange,
iterateTreeInVisibleRanges, iterateTreeInVisibleRanges,

View File

@ -135,7 +135,7 @@ export function FilterList({
searchBoxRef.current!.focus(); searchBoxRef.current!.focus();
} }
}} }}
onKeyDown={(e) => { onKeyUp={(e) => {
// console.log("Key up", / e); // console.log("Key up", / e);
if (onKeyPress) { if (onKeyPress) {
onKeyPress(e.key, text); onKeyPress(e.key, text);
@ -178,12 +178,13 @@ export function FilterList({
} }
break; break;
default: default:
setTimeout(() => {
updateFilter((e.target as any).value); updateFilter((e.target as any).value);
});
} }
e.stopPropagation(); e.stopPropagation();
}} }}
onKeyDown={(e) => {
e.stopPropagation();
}}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
/> />
</div> </div>

View File

@ -45,38 +45,12 @@ export function TopBar({
}) { }) {
const [theme, setTheme] = useState<string>(localStorage.theme ?? "light"); const [theme, setTheme] = useState<string>(localStorage.theme ?? "light");
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [editMode, setEditMode] = useState<boolean>(false);
const isMac = isMacLike(); const isMac = isMacLike();
useEffect(() => {
if (editMode) {
setTimeout(() => {
if (inputRef.current) {
console.log("Going to focus");
inputRef.current!.focus();
}
}, 0);
}
}, [editMode]);
return ( return (
<div id="sb-top"> <div id="sb-top">
{lhs} {lhs}
<div <div className="main">
className="main"
onClick={(e) => {
if (!editMode) {
setEditMode(true);
setTimeout(() => {
if (inputRef.current) {
console.log("Going to dispatch click event again");
inputRef.current!.dispatchEvent(e);
}
}, 100);
}
}}
>
<div className="inner"> <div className="inner">
<span <span
className={`sb-current-page ${ className={`sb-current-page ${
@ -87,16 +61,11 @@ export function TopBar({
: "sb-saved" : "sb-saved"
}`} }`}
> >
{editMode
? (
<input <input
type="text" type="text"
ref={inputRef} ref={inputRef}
value={pageName} value={pageName}
className="sb-edit-page-name" className="sb-edit-page-name"
onBlur={() => {
setEditMode(false);
}}
onKeyDown={(e) => { onKeyDown={(e) => {
console.log("Key press", e); console.log("Key press", e);
e.stopPropagation(); e.stopPropagation();
@ -104,12 +73,9 @@ export function TopBar({
e.preventDefault(); e.preventDefault();
const newName = (e.target as any).value; const newName = (e.target as any).value;
onRename(newName); onRename(newName);
setEditMode(false);
} }
}} }}
/> />
)
: pageName}
</span> </span>
{notifications.length > 0 && ( {notifications.length > 0 && (
<div className="sb-notifications"> <div className="sb-notifications">

View File

@ -99,7 +99,7 @@ import {
import { inlineImagesPlugin } from "./cm_plugins/inline_image.ts"; import { inlineImagesPlugin } from "./cm_plugins/inline_image.ts";
import { lineWrapper } from "./cm_plugins/line_wrapper.ts"; import { lineWrapper } from "./cm_plugins/line_wrapper.ts";
import { smartQuoteKeymap } from "./cm_plugins/smart_quotes.ts"; import { smartQuoteKeymap } from "./cm_plugins/smart_quotes.ts";
import { cleanModePlugs } from "./cm_plugins/clean.ts"; import { cleanModePlugins } from "./cm_plugins/clean.ts";
import customMarkdownStyle from "./style.ts"; import customMarkdownStyle from "./style.ts";
// Real-time collaboration // Real-time collaboration
@ -463,12 +463,13 @@ export class Editor {
drawSelection(), drawSelection(),
dropCursor(), dropCursor(),
indentOnInput(), indentOnInput(),
...cleanModePlugs, ...cleanModePlugins(this),
EditorView.lineWrapping, EditorView.lineWrapping,
lineWrapper([ lineWrapper([
{ selector: "ATXHeading1", class: "sb-line-h1" }, { selector: "ATXHeading1", class: "sb-line-h1" },
{ selector: "ATXHeading2", class: "sb-line-h2" }, { selector: "ATXHeading2", class: "sb-line-h2" },
{ selector: "ATXHeading3", class: "sb-line-h3" }, { selector: "ATXHeading3", class: "sb-line-h3" },
{ selector: "ATXHeading4", class: "sb-line-h4" },
{ selector: "ListItem", class: "sb-line-li", nesting: true }, { selector: "ListItem", class: "sb-line-li", nesting: true },
{ selector: "Blockquote", class: "sb-line-blockquote" }, { selector: "Blockquote", class: "sb-line-blockquote" },
{ selector: "Task", class: "sb-line-task" }, { selector: "Task", class: "sb-line-task" },
@ -510,23 +511,6 @@ export class Editor {
return true; return true;
}, },
}, },
{
key: "Ctrl-l",
mac: "Cmd-l",
run: (): boolean => {
this.editorView?.dispatch({
effects: [
EditorView.scrollIntoView(
this.editorView.state.selection.main.anchor,
{
y: "center",
},
),
],
});
return true;
},
},
]), ]),
EditorView.domEventHandlers({ EditorView.domEventHandlers({

View File

@ -20,7 +20,7 @@ export type SlashCommandHookT = {
slashCommand?: SlashCommandDef; slashCommand?: SlashCommandDef;
}; };
const slashCommandRegexp = /\/[\w\-]*/; const slashCommandRegexp = /([^\w]|^)\/[\w\-]*/;
export class SlashCommandHook implements Hook<SlashCommandHookT> { export class SlashCommandHook implements Hook<SlashCommandHookT> {
slashCommands = new Map<string, AppSlashCommand>(); slashCommands = new Map<string, AppSlashCommand>();

View File

@ -208,3 +208,8 @@
left: -20px; left: -20px;
} }
} }
.cm-scroller {
// Give some breathing space at the bottom of the screen
padding-bottom: 20em;
}

View File

@ -129,20 +129,25 @@
} }
.sb-header-inside.sb-line-h1 { .sb-header-inside.sb-line-h1 {
margin-left: -2ch; text-indent: -2ch;
} }
.sb-header-inside.sb-line-h2 { .sb-header-inside.sb-line-h2 {
margin-left: -3ch; text-indent: -3ch;
} }
.sb-header-inside.sb-line-h3 { .sb-header-inside.sb-line-h3 {
margin-left: -4ch; text-indent: -4ch;
}
.sb-header-inside.sb-line-h4 {
text-indent: -5ch;
} }
.sb-line-h1, .sb-line-h1,
.sb-line-h2, .sb-line-h2,
.sb-line-h3 { .sb-line-h3,
.sb-line-h4 {
// background-color: rgba(0, 30, 77, 0.5); // background-color: rgba(0, 30, 77, 0.5);
color: #333; color: #333;
font-weight: bold; font-weight: bold;
@ -151,7 +156,8 @@
.sb-line-h1 .sb-meta, .sb-line-h1 .sb-meta,
.sb-line-h2 .sb-meta, .sb-line-h2 .sb-meta,
.sb-line-h3 .sb-meta { .sb-line-h3 .sb-meta,
.sb-line-h4 .sb-meta {
color: #a1a1a0; color: #a1a1a0;
} }
@ -167,6 +173,10 @@
font-size: 1.1em; font-size: 1.1em;
} }
.sb-line-h4 {
font-size: 1em;
}
.sb-hashtag { .sb-hashtag {
color: blue; color: blue;
} }