diff --git a/.gitignore b/.gitignore index eafef921..df803908 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ website_build data.db publish-data.db /index.json -.idea \ No newline at end of file +.idea +deno.lock \ No newline at end of file diff --git a/common/customtags.ts b/common/customtags.ts index 337bb094..a6eb52cb 100644 --- a/common/customtags.ts +++ b/common/customtags.ts @@ -1,5 +1,6 @@ import { Tag } from "./deps.ts"; +export const CommandLinkTag = Tag.define(); export const WikiLinkTag = Tag.define(); export const WikiLinkPageTag = Tag.define(); export const CodeInfoTag = Tag.define(); diff --git a/common/deps.ts b/common/deps.ts index 4e0b243d..20fb4346 100644 --- a/common/deps.ts +++ b/common/deps.ts @@ -41,7 +41,7 @@ export { TaskList, } from "@lezer/markdown"; -export type { SyntaxNode, Tree } from "@lezer/common"; +export type { NodeType, SyntaxNode, SyntaxNodeRef, Tree } from "@lezer/common"; export { searchKeymap } from "https://esm.sh/@codemirror/search@6.2.2?external=@codemirror/state,@codemirror/view"; export { @@ -65,6 +65,7 @@ export { EditorState, Range, SelectionRange, + StateField, Text, Transaction, } from "@codemirror/state"; @@ -72,6 +73,7 @@ export type { ChangeSpec, Extension, StateCommand } from "@codemirror/state"; export { defaultHighlightStyle, defineLanguageFacet, + foldedRanges, foldNodeProp, HighlightStyle, indentNodeProp, diff --git a/common/parser.ts b/common/parser.ts index 9911087e..ffcdf170 100644 --- a/common/parser.ts +++ b/common/parser.ts @@ -23,7 +23,10 @@ import { export const pageLinkRegex = /^\[\[([^\]]+)\]\]/; const WikiLink: MarkdownConfig = { - defineNodes: ["WikiLink", "WikiLinkPage"], + defineNodes: ["WikiLink", "WikiLinkPage", { + name: "WikiLinkMark", + style: t.processingInstruction, + }], parseInline: [ { name: "WikiLink", @@ -35,9 +38,51 @@ const WikiLink: MarkdownConfig = { ) { return -1; } + const endPos = pos + match[0].length; return cx.addElement( - cx.elt("WikiLink", pos, pos + match[0].length, [ - cx.elt("WikiLinkPage", pos + 2, pos + match[0].length - 2), + cx.elt("WikiLink", pos, endPos, [ + cx.elt("WikiLinkMark", pos, pos + 2), + cx.elt("WikiLinkPage", pos + 2, endPos - 2), + cx.elt("WikiLinkMark", endPos - 2, endPos), + ]), + ); + }, + after: "Emphasis", + }, + ], +}; + +const commandLinkRegex = /^\{\[([^\]]+)\]\}/; + +const CommandLink: MarkdownConfig = { + defineNodes: [ + { + name: "CommandLink", + style: { "CommandLink/...": ct.CommandLinkTag }, + }, + "CommandLinkName", + { + name: "CommandLinkMark", + style: t.processingInstruction, + }, + ], + parseInline: [ + { + name: "CommandLink", + parse(cx, next, pos) { + let match: RegExpMatchArray | null; + if ( + next != 123 /* '{' */ || + !(match = commandLinkRegex.exec(cx.slice(pos, cx.end))) + ) { + return -1; + } + const endPos = pos + match[0].length; + return cx.addElement( + cx.elt("CommandLink", pos, endPos, [ + cx.elt("CommandLinkMark", pos, pos + 2), + cx.elt("CommandLinkName", pos + 2, endPos - 2), + cx.elt("CommandLinkMark", endPos - 2, endPos), ]), ); }, @@ -167,6 +212,7 @@ export default function buildMarkdown(mdExtensions: MDExt[]): Language { return markdown({ extensions: [ WikiLink, + CommandLink, FrontMatter, TaskList, Comment, diff --git a/plugs/core/core.plug.yaml b/plugs/core/core.plug.yaml index b409872c..51354da5 100644 --- a/plugs/core/core.plug.yaml +++ b/plugs/core/core.plug.yaml @@ -12,11 +12,11 @@ syntax: - "h" regex: "https?:\\/\\/[-a-zA-Z0-9@:%._\\+~#=]{1,256}([-a-zA-Z0-9()@:%_\\+.~#?&=\\/]*)" className: sb-naked-url - CommandLink: - firstCharacters: - - "{" - regex: "\\{\\[[^\\]]+\\]\\}" - className: sb-command-link + # CommandLink: + # firstCharacters: + # - "{" + # regex: "\\{\\[[^\\]]+\\]\\}" + # className: sb-command-link NamedAnchor: firstCharacters: - "$" diff --git a/web/clean_mode.ts b/web/clean_mode.ts new file mode 100644 index 00000000..546ffabc --- /dev/null +++ b/web/clean_mode.ts @@ -0,0 +1,626 @@ +import { + ChangeSpec, + Decoration, + DecorationSet, + EditorState, + EditorView, + foldedRanges, + NodeType, + SyntaxNodeRef, + syntaxTree, + ViewPlugin, + ViewUpdate, + WidgetType, +} from "./deps.ts"; + +function getLinkAnchor(view: EditorView) { + const widgets: any[] = []; + + for (const { from, to } of view.visibleRanges) { + syntaxTree(view.state).iterate({ + from, + to, + enter: ({ type, from, to, node }) => { + if (type.name !== "URL") return; + const parent = node.parent; + const blackListedParents = ["Image"]; + if (parent && !blackListedParents.includes(parent.name)) { + const marks = parent.getChildren("LinkMark"); + const ranges = view.state.selection.ranges; + const cursorOverlaps = ranges.some(({ from, to }) => + checkRangeOverlap([from, to], [parent.from, parent.to]) + ); + if (!cursorOverlaps) { + widgets.push( + ...marks.map(({ from, to }) => + invisibleDecoration.range(from, to) + ), + invisibleDecoration.range(from, to), + ); + } + } + }, + }); + } + + return Decoration.set(widgets, true); +} + +export const goToLinkPlugin = ViewPlugin.fromClass( + class { + decorations: DecorationSet = Decoration.none; + constructor(view: EditorView) { + this.decorations = getLinkAnchor(view); + } + update(update: ViewUpdate) { + if ( + update.docChanged || + update.viewportChanged || + update.selectionSet + ) { + this.decorations = getLinkAnchor(update.view); + } + } + }, + { decorations: (v) => v.decorations }, +); + +class StartDirectiveWidget extends WidgetType { + constructor() { + super(); + } + toDOM(): HTMLElement { + const queryEl = document.createElement("div"); + queryEl.textContent = "start"; + queryEl.className = "sb-directive-start"; + console.log("Got dom", queryEl); + return queryEl; + } +} + +function getDirectives(view: EditorView) { + const widgets: any[] = []; + + for (const { from, to } of view.visibleRanges) { + syntaxTree(view.state).iterate({ + from, + to, + enter: ({ type, from, to }) => { + if (type.name !== "CommentBlock") { + return; + } + const text = view.state.sliceDoc(from, to); + if (/