1
0
This commit is contained in:
Zef Hemel 2022-03-03 10:35:32 +01:00
parent 9b1b950c41
commit f73acae41a
20 changed files with 571 additions and 570 deletions

1
plugins/.gitignore vendored
View File

@ -1 +0,0 @@
dist

View File

@ -1,7 +1,7 @@
DENO_BUNDLE=deno run --allow-read --allow-write --unstable bundle.ts --debug DENO_BUNDLE=deno run --allow-read --allow-write --unstable bundle.ts --debug
build: * build: *
mkdir -p dist mkdir -p dist
$(DENO_BUNDLE) core/core.plugin.json dist/core.plugin.json $(DENO_BUNDLE) core/core.plugin.json ../webapp/src/generated/core.plugin.json
entr: entr:
ls core/* | entr make ls core/* | entr make

View File

@ -1,8 +1,5 @@
{ {
"commands": { "commands": {
"Count Words": {
"invoke": "word_count_command"
},
"Navigate To page": { "Navigate To page": {
"invoke": "linkNavigate", "invoke": "linkNavigate",
"key": "Ctrl-Enter", "key": "Ctrl-Enter",
@ -68,9 +65,6 @@
"taskToggle": { "taskToggle": {
"path": "./task.ts:taskToggle" "path": "./task.ts:taskToggle"
}, },
"word_count_command": {
"path": "./word_count_command.ts:wordCount"
},
"insertToday": { "insertToday": {
"path": "./dates.ts:insertToday" "path": "./dates.ts:insertToday"
}, },

View File

@ -29,9 +29,15 @@ export async function deletePage() {
} }
export async function renamePage() { export async function renamePage() {
// console.log("HELLO WORLD");
const pageMeta = await syscall("editor.getCurrentPage"); const pageMeta = await syscall("editor.getCurrentPage");
const oldName = pageMeta.name; const oldName = pageMeta.name;
const newName = await syscall("editor.prompt", `Rename ${oldName} to:`); console.log("Old name is", oldName);
const newName = await syscall(
"editor.prompt",
`Rename ${oldName} to:`,
oldName
);
if (!newName) { if (!newName) {
return; return;
} }

View File

@ -7,7 +7,7 @@
"license": "MIT", "license": "MIT",
"browserslist": "> 0.5%, last 2 versions, not dead", "browserslist": "> 0.5%, last 2 versions, not dead",
"scripts": { "scripts": {
"start": "cp src/function_worker.js dist/ && parcel", "start": "mkdir -p dist && cp src/function_worker.js dist/ && parcel",
"build": "parcel build && cp src/function_worker.js dist/", "build": "parcel build && cp src/function_worker.js dist/",
"clean": "rm -rf dist", "clean": "rm -rf dist",
"check-watch": "tsc --noEmit --watch" "check-watch": "tsc --noEmit --watch"

View File

@ -2,7 +2,14 @@ import { PageMeta } from "../types";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFileLines } from "@fortawesome/free-solid-svg-icons"; import { faFileLines } from "@fortawesome/free-solid-svg-icons";
export function NavigationBar({ function prettyName(s: string | undefined): string {
if (!s) {
return "";
}
return s.replaceAll("/", " / ");
}
export function TopBar({
currentPage, currentPage,
onClick, onClick,
}: { }: {
@ -15,7 +22,7 @@ export function NavigationBar({
<span className="icon"> <span className="icon">
<FontAwesomeIcon icon={faFileLines} /> <FontAwesomeIcon icon={faFileLines} />
</span> </span>
<span className="current-page">{currentPage?.name}</span> <span className="current-page">{prettyName(currentPage?.name)}</span>
</div> </div>
</div> </div>
); );

View File

@ -6,3 +6,5 @@ export const TagTag = Tag.define();
export const MentionTag = Tag.define(); export const MentionTag = Tag.define();
export const TaskTag = Tag.define(); export const TaskTag = Tag.define();
export const TaskMarkerTag = Tag.define(); export const TaskMarkerTag = Tag.define();
export const CommentTag = Tag.define();
export const CommentMarkerTag = Tag.define();

View File

@ -8,7 +8,6 @@ import {
import { closeBrackets, closeBracketsKeymap } from "@codemirror/closebrackets"; import { closeBrackets, closeBracketsKeymap } from "@codemirror/closebrackets";
import { indentWithTab, standardKeymap } from "@codemirror/commands"; import { indentWithTab, standardKeymap } from "@codemirror/commands";
import { history, historyKeymap } from "@codemirror/history"; import { history, historyKeymap } from "@codemirror/history";
import { indentOnInput, syntaxTree } from "@codemirror/language";
import { bracketMatching } from "@codemirror/matchbrackets"; import { bracketMatching } from "@codemirror/matchbrackets";
import { searchKeymap } from "@codemirror/search"; import { searchKeymap } from "@codemirror/search";
import { EditorState, StateField, Transaction } from "@codemirror/state"; import { EditorState, StateField, Transaction } from "@codemirror/state";
@ -20,24 +19,31 @@ import {
KeyBinding, KeyBinding,
keymap, keymap,
} from "@codemirror/view"; } from "@codemirror/view";
import React, { useEffect, useReducer } from "react"; import React, { useEffect, useReducer } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import coreManifest from "../../plugins/dist/core.plugin.json"; import coreManifest from "./generated/core.plugin.json";
// @ts-ignore
window.coreManifest = coreManifest;
import { AppEvent, AppEventDispatcher, ClickEvent } from "./app_event";
import * as commands from "./commands"; import * as commands from "./commands";
import { CommandPalette } from "./components/command_palette"; import { CommandPalette } from "./components/command_palette";
import { NavigationBar } from "./components/navigation_bar";
import { PageNavigator } from "./components/page_navigator"; import { PageNavigator } from "./components/page_navigator";
import { StatusBar } from "./components/status_bar"; import { StatusBar } from "./components/status_bar";
import { Space } from "./space"; import { TopBar } from "./components/top_bar";
import { Indexer } from "./indexer";
import { lineWrapper } from "./lineWrapper"; import { lineWrapper } from "./lineWrapper";
import { markdown } from "./markdown"; import { markdown } from "./markdown";
import { IPageNavigator, PathPageNavigator } from "./navigator";
import customMarkDown from "./parser"; import customMarkDown from "./parser";
import { BrowserSystem } from "./plugins/browser_system"; import { BrowserSystem } from "./plugins/browser_system";
import { Manifest, slashCommandRegexp } from "./plugins/types"; import { Plugin } from "./plugins/runtime";
import { slashCommandRegexp } from "./plugins/types";
import reducer from "./reducer"; import reducer from "./reducer";
import { smartQuoteKeymap } from "./smart_quotes";
import { Space } from "./space";
import customMarkdownStyle from "./style"; import customMarkdownStyle from "./style";
import dbSyscalls from "./syscalls/db.localstorage"; import dbSyscalls from "./syscalls/db.localstorage";
import { Plugin } from "./plugins/runtime";
import editorSyscalls from "./syscalls/editor.browser"; import editorSyscalls from "./syscalls/editor.browser";
import indexerSyscalls from "./syscalls/indexer.native"; import indexerSyscalls from "./syscalls/indexer.native";
import spaceSyscalls from "./syscalls/space.native"; import spaceSyscalls from "./syscalls/space.native";
@ -48,16 +54,7 @@ import {
initialViewState, initialViewState,
PageMeta, PageMeta,
} from "./types"; } from "./types";
import {
AppEvent,
AppEventDispatcher,
ClickEvent,
IndexEvent,
} from "./app_event";
import { safeRun } from "./util"; import { safeRun } from "./util";
import { Indexer } from "./indexer";
import { IPageNavigator, PathPageNavigator } from "./navigator";
import { smartQuoteKeymap } from "./smart_quotes";
class PageState { class PageState {
editorState: EditorState; editorState: EditorState;
@ -203,7 +200,7 @@ export class Editor implements AppEventDispatcher {
history(), history(),
drawSelection(), drawSelection(),
dropCursor(), dropCursor(),
indentOnInput(), // indentOnInput(),
customMarkdownStyle, customMarkdownStyle,
bracketMatching(), bracketMatching(),
closeBrackets(), closeBrackets(),
@ -217,10 +214,12 @@ export class Editor implements AppEventDispatcher {
lineWrapper([ lineWrapper([
{ selector: "ATXHeading1", class: "line-h1" }, { selector: "ATXHeading1", class: "line-h1" },
{ selector: "ATXHeading2", class: "line-h2" }, { selector: "ATXHeading2", class: "line-h2" },
{ selector: "ListItem", class: "line-li" }, { selector: "ATXHeading3", class: "line-h3" },
{ selector: "ListItem", class: "line-li", nesting: true },
{ selector: "Blockquote", class: "line-blockquote" }, { selector: "Blockquote", class: "line-blockquote" },
{ selector: "CodeBlock", class: "line-code" }, { selector: "CodeBlock", class: "line-code" },
{ selector: "FencedCode", class: "line-fenced-code" }, { selector: "FencedCode", class: "line-fenced-code" },
{ selector: "Comment", class: "line-comment" },
]), ]),
keymap.of([ keymap.of([
...smartQuoteKeymap, ...smartQuoteKeymap,
@ -535,7 +534,7 @@ export class Editor implements AppEventDispatcher {
commands={viewState.commands} commands={viewState.commands}
/> />
)} )}
<NavigationBar <TopBar
currentPage={viewState.currentPage} currentPage={viewState.currentPage}
onClick={() => { onClick={() => {
dispatch({ type: "start-navigate" }); dispatch({ type: "start-navigate" });

File diff suppressed because one or more lines are too long

View File

@ -12,10 +12,12 @@ import { Range } from "@codemirror/rangeset";
interface WrapElement { interface WrapElement {
selector: string; selector: string;
class: string; class: string;
nesting?: boolean;
} }
function wrapLines(view: EditorView, wrapElements: WrapElement[]) { function wrapLines(view: EditorView, wrapElements: WrapElement[]) {
let widgets: Range<Decoration>[] = []; let widgets: Range<Decoration>[] = [];
let elementStack: string[] = [];
for (let { from, to } of view.visibleRanges) { for (let { from, to } of view.visibleRanges) {
const doc = view.state.doc; const doc = view.state.doc;
syntaxTree(view.state).iterate({ syntaxTree(view.state).iterate({
@ -25,12 +27,19 @@ function wrapLines(view: EditorView, wrapElements: WrapElement[]) {
const bodyText = doc.sliceString(from, to); const bodyText = doc.sliceString(from, to);
for (let wrapElement of wrapElements) { for (let wrapElement of wrapElements) {
if (type.name == wrapElement.selector) { if (type.name == wrapElement.selector) {
if (wrapElement.nesting) {
elementStack.push(type.name);
}
const bodyText = doc.sliceString(from, to); const bodyText = doc.sliceString(from, to);
let idx = from; let idx = from;
for (let line of bodyText.split("\n")) { for (let line of bodyText.split("\n")) {
let cls = wrapElement.class;
if (wrapElement.nesting) {
cls = `${cls} ${cls}-${elementStack.length}`;
}
widgets.push( widgets.push(
Decoration.line({ Decoration.line({
class: wrapElement.class, class: cls,
}).range(doc.lineAt(idx).from) }).range(doc.lineAt(idx).from)
); );
idx += line.length + 1; idx += line.length + 1;
@ -38,7 +47,13 @@ function wrapLines(view: EditorView, wrapElements: WrapElement[]) {
} }
} }
}, },
leave(type, from: number, to: number) {}, leave(type, from: number, to: number) {
for (let wrapElement of wrapElements) {
if (type.name == wrapElement.selector && wrapElement.nesting) {
elementStack.pop();
}
}
},
}); });
} }
// Widgets have to be sorted by `from` in ascending order // Widgets have to be sorted by `from` in ascending order

View File

@ -1,234 +0,0 @@
import { EditorSelection } from "@codemirror/state";
import { syntaxTree } from "@codemirror/language";
import { markdownLanguage } from "./markdown";
function nodeStart(node, doc) {
return doc.sliceString(node.from, node.from + 50);
}
class Context {
constructor(node, from, to, spaceBefore, spaceAfter, type, item) {
this.node = node;
this.from = from;
this.to = to;
this.spaceBefore = spaceBefore;
this.spaceAfter = spaceAfter;
this.type = type;
this.item = item;
}
blank(trailing = true) {
let result = this.spaceBefore;
if (this.node.name == "Blockquote")
result += ">";
else
for (let i = this.to - this.from - result.length - this.spaceAfter.length; i > 0; i--)
result += " ";
return result + (trailing ? this.spaceAfter : "");
}
marker(doc, add) {
let number = this.node.name == "OrderedList" ? String((+itemNumber(this.item, doc)[2] + add)) : "";
return this.spaceBefore + number + this.type + this.spaceAfter;
}
}
function getContext(node, line, doc) {
let nodes = [];
for (let cur = node; cur && cur.name != "Document"; cur = cur.parent) {
if (cur.name == "ListItem" || cur.name == "Blockquote")
nodes.push(cur);
}
let context = [], pos = 0;
for (let i = nodes.length - 1; i >= 0; i--) {
let node = nodes[i], match, start = pos;
if (node.name == "Blockquote" && (match = /^[ \t]*>( ?)/.exec(line.slice(pos)))) {
pos += match[0].length;
context.push(new Context(node, start, pos, "", match[1], ">", null));
}
else if (node.name == "ListItem" && node.parent.name == "OrderedList" &&
(match = /^([ \t]*)\d+([.)])([ \t]*)/.exec(nodeStart(node, doc)))) {
let after = match[3], len = match[0].length;
if (after.length >= 4) {
after = after.slice(0, after.length - 4);
len -= 4;
}
pos += len;
context.push(new Context(node.parent, start, pos, match[1], after, match[2], node));
}
else if (node.name == "ListItem" && node.parent.name == "BulletList" &&
(match = /^([ \t]*)([-+*])([ \t]+)/.exec(nodeStart(node, doc)))) {
let after = match[3], len = match[0].length;
if (after.length > 4) {
after = after.slice(0, after.length - 4);
len -= 4;
}
pos += len;
context.push(new Context(node.parent, start, pos, match[1], after, match[2], node));
}
}
return context;
}
function itemNumber(item, doc) {
return /^(\s*)(\d+)(?=[.)])/.exec(doc.sliceString(item.from, item.from + 10));
}
function renumberList(after, doc, changes, offset = 0) {
for (let prev = -1, node = after;;) {
if (node.name == "ListItem") {
let m = itemNumber(node, doc);
let number = +m[2];
if (prev >= 0) {
if (number != prev + 1)
return;
changes.push({ from: node.from + m[1].length, to: node.from + m[0].length, insert: String(prev + 2 + offset) });
}
prev = number;
}
let next = node.nextSibling;
if (!next)
break;
node = next;
}
}
/// This command, when invoked in Markdown context with cursor
/// selection(s), will create a new line with the markup for
/// blockquotes and lists that were active on the old line. If the
/// cursor was directly after the end of the markup for the old line,
/// trailing whitespace and list markers are removed from that line.
///
/// The command does nothing in non-Markdown context, so it should
/// not be used as the only binding for Enter (even in a Markdown
/// document, HTML and code regions might use a different language).
export const insertNewlineContinueMarkup = ({ state, dispatch }) => {
let tree = syntaxTree(state), { doc } = state;
let dont = null, changes = state.changeByRange(range => {
if (!range.empty || !markdownLanguage.isActiveAt(state, range.from))
return dont = { range };
let pos = range.from, line = doc.lineAt(pos);
let context = getContext(tree.resolveInner(pos, -1), line.text, doc);
while (context.length && context[context.length - 1].from > pos - line.from)
context.pop();
if (!context.length)
return dont = { range };
let inner = context[context.length - 1];
if (inner.to - inner.spaceAfter.length > pos - line.from)
return dont = { range };
let emptyLine = pos >= (inner.to - inner.spaceAfter.length) && !/\S/.test(line.text.slice(inner.to));
// Empty line in list
if (inner.item && emptyLine) {
// First list item or blank line before: delete a level of markup
if (inner.node.firstChild.to >= pos ||
line.from > 0 && !/[^\s>]/.test(doc.lineAt(line.from - 1).text)) {
let next = context.length > 1 ? context[context.length - 2] : null;
let delTo, insert = "";
if (next && next.item) { // Re-add marker for the list at the next level
delTo = line.from + next.from;
insert = next.marker(doc, 1);
}
else {
delTo = line.from + (next ? next.to : 0);
}
let changes = [{ from: delTo, to: pos, insert }];
if (inner.node.name == "OrderedList")
renumberList(inner.item, doc, changes, -2);
if (next && next.node.name == "OrderedList")
renumberList(next.item, doc, changes);
return { range: EditorSelection.cursor(delTo + insert.length), changes };
}
else { // Move this line down
let insert = "";
for (let i = 0, e = context.length - 2; i <= e; i++)
insert += context[i].blank(i < e);
insert += state.lineBreak;
return { range: EditorSelection.cursor(pos + insert.length), changes: { from: line.from, insert } };
}
}
if (inner.node.name == "Blockquote" && emptyLine && line.from) {
let prevLine = doc.lineAt(line.from - 1), quoted = />\s*$/.exec(prevLine.text);
// Two aligned empty quoted lines in a row
if (quoted && quoted.index == inner.from) {
let changes = state.changes([{ from: prevLine.from + quoted.index, to: prevLine.to },
{ from: line.from + inner.from, to: line.to }]);
return { range: range.map(changes), changes };
}
}
let changes = [];
if (inner.node.name == "OrderedList")
renumberList(inner.item, doc, changes);
let insert = state.lineBreak;
let continued = inner.item && inner.item.from < line.from;
// If not dedented
if (!continued || /^[\s\d.)\-+*>]*/.exec(line.text)[0].length >= inner.to) {
for (let i = 0, e = context.length - 1; i <= e; i++)
insert += i == e && !continued ? context[i].marker(doc, 1) : context[i].blank();
}
let from = pos;
while (from > line.from && /\s/.test(line.text.charAt(from - line.from - 1)))
from--;
changes.push({ from, to: pos, insert });
return { range: EditorSelection.cursor(from + insert.length), changes };
});
if (dont)
return false;
dispatch(state.update(changes, { scrollIntoView: true, userEvent: "input" }));
return true;
};
function isMark(node) {
return node.name == "QuoteMark" || node.name == "ListMark";
}
function contextNodeForDelete(tree, pos) {
let node = tree.resolveInner(pos, -1), scan = pos;
if (isMark(node)) {
scan = node.from;
node = node.parent;
}
for (let prev; prev = node.childBefore(scan);) {
if (isMark(prev)) {
scan = prev.from;
}
else if (prev.name == "OrderedList" || prev.name == "BulletList") {
node = prev.lastChild;
scan = node.to;
}
else {
break;
}
}
return node;
}
/// This command will, when invoked in a Markdown context with the
/// cursor directly after list or blockquote markup, delete one level
/// of markup. When the markup is for a list, it will be replaced by
/// spaces on the first invocation (a further invocation will delete
/// the spaces), to make it easy to continue a list.
///
/// When not after Markdown block markup, this command will return
/// false, so it is intended to be bound alongside other deletion
/// commands, with a higher precedence than the more generic commands.
export const deleteMarkupBackward = ({ state, dispatch }) => {
let tree = syntaxTree(state);
let dont = null, changes = state.changeByRange(range => {
let pos = range.from, { doc } = state;
if (range.empty && markdownLanguage.isActiveAt(state, range.from)) {
let line = doc.lineAt(pos);
let context = getContext(contextNodeForDelete(tree, pos), line.text, doc);
if (context.length) {
let inner = context[context.length - 1];
let spaceEnd = inner.to - inner.spaceAfter.length + (inner.spaceAfter ? 1 : 0);
// Delete extra trailing space after markup
if (pos - line.from > spaceEnd && !/\S/.test(line.text.slice(spaceEnd, pos - line.from)))
return { range: EditorSelection.cursor(line.from + spaceEnd),
changes: { from: line.from + spaceEnd, to: pos } };
if (pos - line.from == spaceEnd) {
let start = line.from + inner.from;
// Replace a list item marker with blank space
if (inner.item && inner.node.from < inner.item.from && /\S/.test(line.text.slice(inner.from, inner.to)))
return { range, changes: { from: start, to: line.from + inner.to, insert: inner.blank() } };
// Delete one level of indentation
if (start < pos)
return { range: EditorSelection.cursor(start), changes: { from: start, to: pos } };
}
}
}
return dont = { range };
});
if (dont)
return false;
dispatch(state.update(changes, { scrollIntoView: true, userEvent: "delete" }));
return true;
};

View File

@ -1,10 +1,15 @@
import {StateCommand, Text, EditorSelection, ChangeSpec} from "@codemirror/state" import {
import {syntaxTree} from "@codemirror/language" StateCommand,
import {SyntaxNode, Tree} from "@lezer/common" Text,
import {markdownLanguage} from "./markdown" EditorSelection,
ChangeSpec,
} from "@codemirror/state";
import { syntaxTree } from "@codemirror/language";
import { SyntaxNode, Tree } from "@lezer/common";
import { markdownLanguage } from "./markdown";
function nodeStart(node: SyntaxNode, doc: Text) { function nodeStart(node: SyntaxNode, doc: Text) {
return doc.sliceString(node.from, node.from + 50) return doc.sliceString(node.from, node.from + 50);
} }
class Context { class Context {
@ -19,65 +24,126 @@ class Context {
) {} ) {}
blank(trailing: boolean = true) { blank(trailing: boolean = true) {
let result = this.spaceBefore let result = this.spaceBefore;
if (this.node.name == "Blockquote") result += ">" if (this.node.name == "Blockquote") {
else for (let i = this.to - this.from - result.length - this.spaceAfter.length; i > 0; i--) result += " " result += ">";
return result + (trailing ? this.spaceAfter : "") } else if (this.node.name == "Comment") {
result += "%%";
} else
for (
let i = this.to - this.from - result.length - this.spaceAfter.length;
i > 0;
i--
)
result += " ";
return result + (trailing ? this.spaceAfter : "");
} }
marker(doc: Text, add: number) { marker(doc: Text, add: number) {
let number = this.node.name == "OrderedList" ? String((+itemNumber(this.item!, doc)[2] + add)) : "" let number =
return this.spaceBefore + number + this.type + this.spaceAfter this.node.name == "OrderedList"
? String(+itemNumber(this.item!, doc)[2] + add)
: "";
return this.spaceBefore + number + this.type + this.spaceAfter;
} }
} }
function getContext(node: SyntaxNode, line: string, doc: Text) { function getContext(node: SyntaxNode, line: string, doc: Text) {
let nodes = [] let nodes = [];
for (let cur: SyntaxNode | null = node; cur && cur.name != "Document"; cur = cur.parent) { for (
if (cur.name == "ListItem" || cur.name == "Blockquote") let cur: SyntaxNode | null = node;
nodes.push(cur) cur && cur.name != "Document";
cur = cur.parent
) {
if (
cur.name == "ListItem" ||
cur.name == "Blockquote" ||
cur.name == "Comment"
)
nodes.push(cur);
} }
let context = [], pos = 0 let context = [],
pos = 0;
for (let i = nodes.length - 1; i >= 0; i--) { for (let i = nodes.length - 1; i >= 0; i--) {
let node = nodes[i], match, start = pos let node = nodes[i],
if (node.name == "Blockquote" && (match = /^[ \t]*>( ?)/.exec(line.slice(pos)))) { match,
pos += match[0].length start = pos;
context.push(new Context(node, start, pos, "", match[1], ">", null)) if (
} else if (node.name == "ListItem" && node.parent!.name == "OrderedList" && node.name == "Blockquote" &&
(match = /^([ \t]*)\d+([.)])([ \t]*)/.exec(nodeStart(node, doc)))) { (match = /^[ \t]*>( ?)/.exec(line.slice(pos)))
let after = match[3], len = match[0].length ) {
if (after.length >= 4) { after = after.slice(0, after.length - 4); len -= 4 } pos += match[0].length;
pos += len context.push(new Context(node, start, pos, "", match[1], ">", null));
context.push(new Context(node.parent!, start, pos, match[1], after, match[2], node)) } else if (
} else if (node.name == "ListItem" && node.parent!.name == "BulletList" && node.name == "Comment" &&
(match = /^([ \t]*)([-+*])([ \t]+)/.exec(nodeStart(node, doc)))) { (match = /^[ \t]*%%( ?)/.exec(line.slice(pos)))
let after = match[3], len = match[0].length ) {
if (after.length > 4) { after = after.slice(0, after.length - 4); len -= 4 } pos += match[0].length;
pos += len context.push(new Context(node, start, pos, "", match[1], "%%", null));
context.push(new Context(node.parent!, start, pos, match[1], after, match[2], node)) } else if (
node.name == "ListItem" &&
node.parent!.name == "OrderedList" &&
(match = /^([ \t]*)\d+([.)])([ \t]*)/.exec(nodeStart(node, doc)))
) {
let after = match[3],
len = match[0].length;
if (after.length >= 4) {
after = after.slice(0, after.length - 4);
len -= 4;
}
pos += len;
context.push(
new Context(node.parent!, start, pos, match[1], after, match[2], node)
);
} else if (
node.name == "ListItem" &&
node.parent!.name == "BulletList" &&
(match = /^([ \t]*)([-+*])([ \t]+)/.exec(nodeStart(node, doc)))
) {
let after = match[3],
len = match[0].length;
if (after.length > 4) {
after = after.slice(0, after.length - 4);
len -= 4;
}
pos += len;
context.push(
new Context(node.parent!, start, pos, match[1], after, match[2], node)
);
} }
} }
return context return context;
} }
function itemNumber(item: SyntaxNode, doc: Text) { function itemNumber(item: SyntaxNode, doc: Text) {
return /^(\s*)(\d+)(?=[.)])/.exec(doc.sliceString(item.from, item.from + 10))! return /^(\s*)(\d+)(?=[.)])/.exec(
doc.sliceString(item.from, item.from + 10)
)!;
} }
function renumberList(after: SyntaxNode, doc: Text, changes: ChangeSpec[], offset = 0) { function renumberList(
for (let prev = -1, node = after;;) { after: SyntaxNode,
doc: Text,
changes: ChangeSpec[],
offset = 0
) {
for (let prev = -1, node = after; ; ) {
if (node.name == "ListItem") { if (node.name == "ListItem") {
let m = itemNumber(node, doc) let m = itemNumber(node, doc);
let number = +m[2] let number = +m[2];
if (prev >= 0) { if (prev >= 0) {
if (number != prev + 1) return if (number != prev + 1) return;
changes.push({from: node.from + m[1].length, to: node.from + m[0].length, insert: String(prev + 2 + offset)}) changes.push({
from: node.from + m[1].length,
to: node.from + m[0].length,
insert: String(prev + 2 + offset),
});
} }
prev = number prev = number;
} }
let next = node.nextSibling let next = node.nextSibling;
if (!next) break if (!next) break;
node = next node = next;
} }
} }
@ -90,93 +156,149 @@ function renumberList(after: SyntaxNode, doc: Text, changes: ChangeSpec[], offse
/// The command does nothing in non-Markdown context, so it should /// The command does nothing in non-Markdown context, so it should
/// not be used as the only binding for Enter (even in a Markdown /// not be used as the only binding for Enter (even in a Markdown
/// document, HTML and code regions might use a different language). /// document, HTML and code regions might use a different language).
export const insertNewlineContinueMarkup: StateCommand = ({state, dispatch}) => { export const insertNewlineContinueMarkup: StateCommand = ({
let tree = syntaxTree(state), {doc} = state state,
let dont = null, changes = state.changeByRange(range => { dispatch,
if (!range.empty || !markdownLanguage.isActiveAt(state, range.from)) return dont = {range} }) => {
let pos = range.from, line = doc.lineAt(pos) let tree = syntaxTree(state),
let context = getContext(tree.resolveInner(pos, -1), line.text, doc) { doc } = state;
while (context.length && context[context.length - 1].from > pos - line.from) context.pop() let dont = null,
if (!context.length) return dont = {range} changes = state.changeByRange((range) => {
let inner = context[context.length - 1] if (!range.empty || !markdownLanguage.isActiveAt(state, range.from))
if (inner.to - inner.spaceAfter.length > pos - line.from) return dont = {range} return (dont = { range });
let pos = range.from,
line = doc.lineAt(pos);
let context = getContext(tree.resolveInner(pos, -1), line.text, doc);
while (
context.length &&
context[context.length - 1].from > pos - line.from
)
context.pop();
if (!context.length) return (dont = { range });
let inner = context[context.length - 1];
if (inner.to - inner.spaceAfter.length > pos - line.from)
return (dont = { range });
let emptyLine = pos >= (inner.to - inner.spaceAfter.length) && !/\S/.test(line.text.slice(inner.to)) let emptyLine =
// Empty line in list pos >= inner.to - inner.spaceAfter.length &&
if (inner.item && emptyLine) { !/\S/.test(line.text.slice(inner.to));
// First list item or blank line before: delete a level of markup // Empty line in list
if (inner.node.firstChild!.to >= pos || if (inner.item && emptyLine) {
line.from > 0 && !/[^\s>]/.test(doc.lineAt(line.from - 1).text)) { // First list item or blank line before: delete a level of markup
let next = context.length > 1 ? context[context.length - 2] : null if (
let delTo, insert = "" inner.node.firstChild!.to >= pos ||
if (next && next.item) { // Re-add marker for the list at the next level (line.from > 0 && !/[^\s>]/.test(doc.lineAt(line.from - 1).text))
delTo = line.from + next.from ) {
insert = next.marker(doc, 1) let next = context.length > 1 ? context[context.length - 2] : null;
let delTo,
insert = "";
if (next && next.item) {
// Re-add marker for the list at the next level
delTo = line.from + next.from;
insert = next.marker(doc, 1);
} else {
delTo = line.from + (next ? next.to : 0);
}
let changes: ChangeSpec[] = [{ from: delTo, to: pos, insert }];
if (inner.node.name == "OrderedList")
renumberList(inner.item!, doc, changes, -2);
if (next && next.node.name == "OrderedList")
renumberList(next.item!, doc, changes);
return {
range: EditorSelection.cursor(delTo + insert.length),
changes,
};
} else { } else {
delTo = line.from + (next ? next.to : 0) // Move this line down
let insert = "";
for (let i = 0, e = context.length - 2; i <= e; i++)
insert += context[i].blank(i < e);
insert += state.lineBreak;
return {
range: EditorSelection.cursor(pos + insert.length),
changes: { from: line.from, insert },
};
} }
let changes: ChangeSpec[] = [{from: delTo, to: pos, insert}]
if (inner.node.name == "OrderedList") renumberList(inner.item!, doc, changes, -2)
if (next && next.node.name == "OrderedList") renumberList(next.item!, doc, changes)
return {range: EditorSelection.cursor(delTo + insert.length), changes}
} else { // Move this line down
let insert = ""
for (let i = 0, e = context.length - 2; i <= e; i++) insert += context[i].blank(i < e)
insert += state.lineBreak
return {range: EditorSelection.cursor(pos + insert.length), changes: {from: line.from, insert}}
} }
}
if (inner.node.name == "Blockquote" && emptyLine && line.from) { if (inner.node.name == "Blockquote" && emptyLine && line.from) {
let prevLine = doc.lineAt(line.from - 1), quoted = />\s*$/.exec(prevLine.text) let prevLine = doc.lineAt(line.from - 1),
// Two aligned empty quoted lines in a row quoted = />\s*$/.exec(prevLine.text);
if (quoted && quoted.index == inner.from) { // Two aligned empty quoted lines in a row
let changes = state.changes([{from: prevLine.from + quoted.index, to: prevLine.to}, if (quoted && quoted.index == inner.from) {
{from: line.from + inner.from, to: line.to}]) let changes = state.changes([
return {range: range.map(changes), changes} { from: prevLine.from + quoted.index, to: prevLine.to },
{ from: line.from + inner.from, to: line.to },
]);
return { range: range.map(changes), changes };
}
} }
}
let changes: ChangeSpec[] = [] if (inner.node.name == "Comment" && emptyLine && line.from) {
if (inner.node.name == "OrderedList") renumberList(inner.item!, doc, changes) let prevLine = doc.lineAt(line.from - 1),
let insert = state.lineBreak commented = /%%\s*$/.exec(prevLine.text);
let continued = inner.item && inner.item.from < line.from // Two aligned empty quoted lines in a row
// If not dedented if (commented && commented.index == inner.from) {
if (!continued || /^[\s\d.)\-+*>]*/.exec(line.text)![0].length >= inner.to) { let changes = state.changes([
for (let i = 0, e = context.length - 1; i <= e; i++) { from: prevLine.from + commented.index, to: prevLine.to },
insert += i == e && !continued ? context[i].marker(doc, 1) : context[i].blank() { from: line.from + inner.from, to: line.to },
} ]);
let from = pos return { range: range.map(changes), changes };
while (from > line.from && /\s/.test(line.text.charAt(from - line.from - 1))) from-- }
changes.push({from, to: pos, insert}) }
return {range: EditorSelection.cursor(from + insert.length), changes}
}) let changes: ChangeSpec[] = [];
if (dont) return false if (inner.node.name == "OrderedList")
dispatch(state.update(changes, {scrollIntoView: true, userEvent: "input"})) renumberList(inner.item!, doc, changes);
return true let insert = state.lineBreak;
} let continued = inner.item && inner.item.from < line.from;
// If not dedented
if (
!continued ||
/^[\s\d.)\-+*>]*/.exec(line.text)![0].length >= inner.to
) {
for (let i = 0, e = context.length - 1; i <= e; i++)
insert +=
i == e && !continued
? context[i].marker(doc, 1)
: context[i].blank();
}
let from = pos;
while (
from > line.from &&
/\s/.test(line.text.charAt(from - line.from - 1))
)
from--;
changes.push({ from, to: pos, insert });
return { range: EditorSelection.cursor(from + insert.length), changes };
});
if (dont) return false;
dispatch(state.update(changes, { scrollIntoView: true, userEvent: "input" }));
return true;
};
function isMark(node: SyntaxNode) { function isMark(node: SyntaxNode) {
return node.name == "QuoteMark" || node.name == "ListMark" return node.name == "QuoteMark" || node.name == "ListMark";
} }
function contextNodeForDelete(tree: Tree, pos: number) { function contextNodeForDelete(tree: Tree, pos: number) {
let node = tree.resolveInner(pos, -1), scan = pos let node = tree.resolveInner(pos, -1),
scan = pos;
if (isMark(node)) { if (isMark(node)) {
scan = node.from scan = node.from;
node = node.parent! node = node.parent!;
} }
for (let prev; prev = node.childBefore(scan);) { for (let prev; (prev = node.childBefore(scan)); ) {
if (isMark(prev)) { if (isMark(prev)) {
scan = prev.from scan = prev.from;
} else if (prev.name == "OrderedList" || prev.name == "BulletList") { } else if (prev.name == "OrderedList" || prev.name == "BulletList") {
node = prev.lastChild! node = prev.lastChild!;
scan = node.to scan = node.to;
} else { } else {
break break;
} }
} }
return node return node;
} }
/// This command will, when invoked in a Markdown context with the /// This command will, when invoked in a Markdown context with the
@ -188,34 +310,62 @@ function contextNodeForDelete(tree: Tree, pos: number) {
/// When not after Markdown block markup, this command will return /// When not after Markdown block markup, this command will return
/// false, so it is intended to be bound alongside other deletion /// false, so it is intended to be bound alongside other deletion
/// commands, with a higher precedence than the more generic commands. /// commands, with a higher precedence than the more generic commands.
export const deleteMarkupBackward: StateCommand = ({state, dispatch}) => { export const deleteMarkupBackward: StateCommand = ({ state, dispatch }) => {
let tree = syntaxTree(state) let tree = syntaxTree(state);
let dont = null, changes = state.changeByRange(range => { let dont = null,
let pos = range.from, {doc} = state changes = state.changeByRange((range) => {
if (range.empty && markdownLanguage.isActiveAt(state, range.from)) { let pos = range.from,
let line = doc.lineAt(pos) { doc } = state;
let context = getContext(contextNodeForDelete(tree, pos), line.text, doc) if (range.empty && markdownLanguage.isActiveAt(state, range.from)) {
if (context.length) { let line = doc.lineAt(pos);
let inner = context[context.length - 1] let context = getContext(
let spaceEnd = inner.to - inner.spaceAfter.length + (inner.spaceAfter ? 1 : 0) contextNodeForDelete(tree, pos),
// Delete extra trailing space after markup line.text,
if (pos - line.from > spaceEnd && !/\S/.test(line.text.slice(spaceEnd, pos - line.from))) doc
return {range: EditorSelection.cursor(line.from + spaceEnd), );
changes: {from: line.from + spaceEnd, to: pos}} if (context.length) {
if (pos - line.from == spaceEnd) { let inner = context[context.length - 1];
let start = line.from + inner.from let spaceEnd =
// Replace a list item marker with blank space inner.to - inner.spaceAfter.length + (inner.spaceAfter ? 1 : 0);
if (inner.item && inner.node.from < inner.item.from && /\S/.test(line.text.slice(inner.from, inner.to))) // Delete extra trailing space after markup
return {range, changes: {from: start, to: line.from + inner.to, insert: inner.blank()}} if (
// Delete one level of indentation pos - line.from > spaceEnd &&
if (start < pos) !/\S/.test(line.text.slice(spaceEnd, pos - line.from))
return {range: EditorSelection.cursor(start), changes: {from: start, to: pos}} )
return {
range: EditorSelection.cursor(line.from + spaceEnd),
changes: { from: line.from + spaceEnd, to: pos },
};
if (pos - line.from == spaceEnd) {
let start = line.from + inner.from;
// Replace a list item marker with blank space
if (
inner.item &&
inner.node.from < inner.item.from &&
/\S/.test(line.text.slice(inner.from, inner.to))
)
return {
range,
changes: {
from: start,
to: line.from + inner.to,
insert: inner.blank(),
},
};
// Delete one level of indentation
if (start < pos)
return {
range: EditorSelection.cursor(start),
changes: { from: start, to: pos },
};
}
} }
} }
} return (dont = { range });
return dont = {range} });
}) if (dont) return false;
if (dont) return false dispatch(
dispatch(state.update(changes, {scrollIntoView: true, userEvent: "delete"})) state.update(changes, { scrollIntoView: true, userEvent: "delete" })
return true );
} return true;
};

View File

@ -1,37 +0,0 @@
import { Prec } from "@codemirror/state";
import { keymap } from "@codemirror/view";
import { LanguageSupport } from "@codemirror/language";
import { MarkdownParser, parseCode } from "@lezer/markdown";
import { html } from "@codemirror/lang-html";
import { commonmarkLanguage, markdownLanguage, mkLang, getCodeParser } from "./markdown";
import { insertNewlineContinueMarkup, deleteMarkupBackward } from "./commands";
export { commonmarkLanguage, markdownLanguage, insertNewlineContinueMarkup, deleteMarkupBackward };
/// A small keymap with Markdown-specific bindings. Binds Enter to
/// [`insertNewlineContinueMarkup`](#lang-markdown.insertNewlineContinueMarkup)
/// and Backspace to
/// [`deleteMarkupBackward`](#lang-markdown.deleteMarkupBackward).
export const markdownKeymap = [
{ key: "Enter", run: insertNewlineContinueMarkup },
{ key: "Backspace", run: deleteMarkupBackward }
];
const htmlNoMatch = html({ matchClosingTags: false });
/// Markdown language support.
export function markdown(config = {}) {
let { codeLanguages, defaultCodeLanguage, addKeymap = true, base: { parser } = commonmarkLanguage } = config;
if (!(parser instanceof MarkdownParser))
throw new RangeError("Base parser provided to `markdown` should be a Markdown parser");
let extensions = config.extensions ? [config.extensions] : [];
let support = [htmlNoMatch.support], defaultCode;
if (defaultCodeLanguage instanceof LanguageSupport) {
support.push(defaultCodeLanguage.support);
defaultCode = defaultCodeLanguage.language;
}
else if (defaultCodeLanguage) {
defaultCode = defaultCodeLanguage;
}
let codeParser = codeLanguages || defaultCode ? getCodeParser(codeLanguages || [], defaultCode) : undefined;
extensions.push(parseCode({ codeParser, htmlParser: htmlNoMatch.language.parser }));
if (addKeymap)
support.push(Prec.high(keymap.of(markdownKeymap)));
return new LanguageSupport(mkLang(parser.configure(extensions)), support);
}

View File

@ -1,75 +0,0 @@
import { Language, defineLanguageFacet, languageDataProp, foldNodeProp, indentNodeProp, LanguageDescription, ParseContext } from "@codemirror/language";
import { styleTags, tags as t } from "@codemirror/highlight";
import { parser as baseParser, GFM, Subscript, Superscript, Emoji } from "@lezer/markdown";
const data = defineLanguageFacet({ block: { open: "<!--", close: "-->" } });
export const commonmark = baseParser.configure({
props: [
styleTags({
"Blockquote/...": t.quote,
HorizontalRule: t.contentSeparator,
"ATXHeading1/... SetextHeading1/...": t.heading1,
"ATXHeading2/... SetextHeading2/...": t.heading2,
"ATXHeading3/...": t.heading3,
"ATXHeading4/...": t.heading4,
"ATXHeading5/...": t.heading5,
"ATXHeading6/...": t.heading6,
"Comment CommentBlock": t.comment,
Escape: t.escape,
Entity: t.character,
"Emphasis/...": t.emphasis,
"StrongEmphasis/...": t.strong,
"Link/... Image/...": t.link,
"OrderedList/... BulletList/...": t.list,
// "CodeBlock/... FencedCode/...": t.blockComment,
"InlineCode CodeText": t.monospace,
URL: t.url,
"HeaderMark HardBreak QuoteMark ListMark LinkMark EmphasisMark CodeMark": t.processingInstruction,
"CodeInfo LinkLabel": t.labelName,
LinkTitle: t.string,
Paragraph: t.content
}),
foldNodeProp.add(type => {
if (!type.is("Block") || type.is("Document"))
return undefined;
return (tree, state) => ({ from: state.doc.lineAt(tree.from).to, to: tree.to });
}),
indentNodeProp.add({
Document: () => null
}),
languageDataProp.add({
Document: data
})
]
});
export function mkLang(parser) {
return new Language(data, parser, parser.nodeSet.types.find(t => t.name == "Document"));
}
/// Language support for strict CommonMark.
export const commonmarkLanguage = mkLang(commonmark);
const extended = commonmark.configure([GFM, Subscript, Superscript, Emoji, {
props: [
styleTags({
"TableDelimiter SubscriptMark SuperscriptMark StrikethroughMark": t.processingInstruction,
"TableHeader/...": t.heading,
"Strikethrough/...": t.strikethrough,
TaskMarker: t.atom,
Task: t.list,
Emoji: t.character,
"Subscript Superscript": t.special(t.content),
TableCell: t.content
})
]
}]);
/// Language support for [GFM](https://github.github.com/gfm/) plus
/// subscript, superscript, and emoji syntax.
export const markdownLanguage = mkLang(extended);
export function getCodeParser(languages, defaultLanguage) {
return (info) => {
let found = info && LanguageDescription.matchLanguageName(languages, info, true);
if (!found)
return defaultLanguage ? defaultLanguage.parser : null;
if (found.support)
return found.support.language.parser;
return ParseContext.getSkippingParser(found.load());
};
}

View File

@ -1,5 +1,11 @@
import { styleTags, tags as t } from "@codemirror/highlight"; import { styleTags, tags as t } from "@codemirror/highlight";
import { MarkdownConfig, TaskList } from "@lezer/markdown"; import {
MarkdownConfig,
TaskList,
BlockContext,
LeafBlock,
LeafBlockParser,
} from "@lezer/markdown";
import { commonmark, mkLang } from "./markdown/markdown"; import { commonmark, mkLang } from "./markdown/markdown";
import * as ct from "./customtags"; import * as ct from "./customtags";
import { pageLinkRegex } from "./constant"; import { pageLinkRegex } from "./constant";
@ -77,6 +83,35 @@ const UnmarkedUrl: MarkdownConfig = {
], ],
}; };
class CommentParser implements LeafBlockParser {
nextLine() {
return false;
}
finish(cx: BlockContext, leaf: LeafBlock) {
cx.addLeafElement(
leaf,
cx.elt("Comment", leaf.start, leaf.start + leaf.content.length, [
// cx.elt("CommentMarker", leaf.start, leaf.start + 3),
...cx.parser.parseInline(leaf.content.slice(3), leaf.start + 3),
])
);
return true;
}
}
export const Comment: MarkdownConfig = {
defineNodes: [{ name: "Comment", block: true }],
parseBlock: [
{
name: "Comment",
leaf(cx, leaf) {
return /^%%\s/.test(leaf.content) ? new CommentParser() : null;
},
after: "SetextHeading",
},
],
};
const TagLink: MarkdownConfig = { const TagLink: MarkdownConfig = {
defineNodes: ["TagLink"], defineNodes: ["TagLink"],
parseInline: [ parseInline: [
@ -102,6 +137,7 @@ const WikiMarkdown = commonmark.configure([
TagLink, TagLink,
TaskList, TaskList,
UnmarkedUrl, UnmarkedUrl,
Comment,
{ {
props: [ props: [
styleTags({ styleTags({
@ -112,6 +148,8 @@ const WikiMarkdown = commonmark.configure([
Task: ct.TaskTag, Task: ct.TaskTag,
TaskMarker: ct.TaskMarkerTag, TaskMarker: ct.TaskMarkerTag,
Url: t.url, Url: t.url,
Comment: ct.CommentTag,
// CommentMarker: ct.CommentMarkerTag,
}), }),
], ],
}, },

View File

@ -4,6 +4,7 @@ import * as ct from "./customtags";
export default HighlightStyle.define([ export default HighlightStyle.define([
{ tag: t.heading1, class: "h1" }, { tag: t.heading1, class: "h1" },
{ tag: t.heading2, class: "h2" }, { tag: t.heading2, class: "h2" },
{ tag: t.heading3, class: "h3" },
{ tag: t.link, class: "link" }, { tag: t.link, class: "link" },
{ tag: t.meta, class: "meta" }, { tag: t.meta, class: "meta" },
{ tag: t.quote, class: "quote" }, { tag: t.quote, class: "quote" },
@ -15,6 +16,8 @@ export default HighlightStyle.define([
{ tag: ct.MentionTag, class: "mention" }, { tag: ct.MentionTag, class: "mention" },
{ tag: ct.TaskTag, class: "task" }, { tag: ct.TaskTag, class: "task" },
{ tag: ct.TaskMarkerTag, class: "task-marker" }, { tag: ct.TaskMarkerTag, class: "task-marker" },
{ tag: ct.CommentTag, class: "comment" },
{ tag: ct.CommentMarkerTag, class: "comment-marker" },
{ tag: t.emphasis, class: "emphasis" }, { tag: t.emphasis, class: "emphasis" },
{ tag: t.strong, class: "strong" }, { tag: t.strong, class: "strong" },
{ tag: t.atom, class: "atom" }, { tag: t.atom, class: "atom" },

View File

@ -13,34 +13,29 @@
background-color: #d7e1f6 !important; background-color: #d7e1f6 !important;
} }
.h1 { .line-h1,
.line-h2,
.line-h3 {
background-color: rgba(0, 15, 52, 0.6);
color: #fff;
font-weight: bold;
padding: 2px 2px;
.meta {
color: orange;
}
}
.line-h1 {
font-size: 1.5em; font-size: 1.5em;
color: #fff;
font-weight: bold;
} }
.cm-line.line-h1 { .line-h2 {
display: block;
background-color: rgba(0, 15, 52, 0.6);
}
.h1.meta {
color: orange;
}
.h2 {
font-size: 1.2em; font-size: 1.2em;
color: #fff;
font-weight: bold;
} }
.cm-line.line-h2 { .line-h3 {
display: block; font-size: 1.1em;
background-color: rgba(0, 15, 52, 0.6);
}
.h2.meta {
color: orange;
} }
/* Color list item this way */ /* Color list item this way */
@ -66,10 +61,10 @@
} }
.line-blockquote { .line-blockquote {
background-color: #eee; background-color: rgba(220, 220, 220, 0.5);
color: #676767; color: #676767;
text-indent: calc(-1 * (var(--ident) + 3px)); text-indent: -2ch;
padding-left: var(--ident); padding-left: 2ch;
} }
.emphasis { .emphasis {
@ -88,6 +83,7 @@
.link.url { .link.url {
color: #7e7d7d; color: #7e7d7d;
} }
.url:not(.link) { .url:not(.link) {
color: #0330cb; color: #0330cb;
text-decoration: underline; text-decoration: underline;
@ -109,12 +105,40 @@
color: #8d8d8d; color: #8d8d8d;
} }
.line-li { .code {
text-indent: calc(-1 * var(--ident) - 3px); background-color: #efefef;
margin-left: var(--ident); }
.line-li-1 {
text-indent: -2ch;
padding-left: 2ch;
}
.line-li-1.line-li-2 {
text-indent: -4ch;
padding-left: 4ch;
}
.line-li-1.line-li-2.line-li-3 {
text-indent: -6ch;
padding-left: 6ch;
}
.line-li-1.line-li-2.line-li-3.line-li-4 {
text-indent: -8ch;
padding-left: 8ch;
}
.line-li-1.line-li-2.line-li-3.line-li-4.line-li-5 {
text-indent: -10ch;
padding-left: 10ch;
} }
.task-marker { .task-marker {
background-color: #ddd; background-color: #ddd;
} }
.line-comment {
background-color: rgba(255, 255, 0, 0.5);
}
} }

View File

@ -16,13 +16,29 @@
border-radius: 8px; border-radius: 8px;
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
label {
color: var(--highlight-color);
}
.header { .header {
border-bottom: 1px rgb(108, 108, 108) solid; border-bottom: 1px rgb(108, 108, 108) solid;
padding: 13px 10px 10px 10px; padding: 13px 10px 10px 10px;
display: flex;
label {
color: var(--highlight-color);
margin: 3px;
}
input {
font-family: "Arial";
background: transparent;
color: #000;
border: 0;
padding: 3px;
outline: 0;
font-size: 1em;
flex-grow: 100;
}
input::placeholder {
color: rgb(199, 199, 199);
font-weight: normal;
}
} }
.help-text { .help-text {
@ -45,21 +61,6 @@
} }
} }
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, .option,
.selected-option { .selected-option {
padding: 8px; padding: 8px;

View File

@ -63,7 +63,7 @@ body {
#editor { #editor {
position: absolute; position: absolute;
top: 60px; top: 55px;
bottom: 30px; bottom: 30px;
left: 0; left: 0;
right: 0; right: 0;

View File

@ -127,7 +127,7 @@ export default (editor: Editor) => ({
"editor.dispatch": (change: Transaction) => { "editor.dispatch": (change: Transaction) => {
editor.editorView!.dispatch(change); editor.editorView!.dispatch(change);
}, },
"editor.prompt": (message: string): string | null => { "editor.prompt": (message: string, defaultValue = ""): string | null => {
return prompt(message); return prompt(message, defaultValue);
}, },
}); });