Fixes #164: Rewrote all CM view plugins to statefields
This commit is contained in:
+67
-92
@@ -1,104 +1,79 @@
|
||||
import { ClickEvent } from "../../plug-api/app_event.ts";
|
||||
import {
|
||||
Decoration,
|
||||
DecorationSet,
|
||||
EditorView,
|
||||
ViewPlugin,
|
||||
ViewUpdate,
|
||||
} from "../deps.ts";
|
||||
import { Decoration, syntaxTree } from "../deps.ts";
|
||||
import { Editor } from "../editor.tsx";
|
||||
import {
|
||||
decoratorStateField,
|
||||
invisibleDecoration,
|
||||
isCursorInRange,
|
||||
iterateTreeInVisibleRanges,
|
||||
} from "./util.ts";
|
||||
import { LinkWidget } from "./util.ts";
|
||||
|
||||
export function linkPlugin(editor: Editor) {
|
||||
return ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations: DecorationSet = Decoration.none;
|
||||
constructor(readonly view: EditorView) {
|
||||
this.decorations = this.calculateDecorations();
|
||||
}
|
||||
calculateDecorations() {
|
||||
const widgets: any[] = [];
|
||||
const view = this.view;
|
||||
return decoratorStateField((state) => {
|
||||
const widgets: any[] = [];
|
||||
|
||||
iterateTreeInVisibleRanges(this.view, {
|
||||
enter: ({ type, from, to }) => {
|
||||
if (type.name !== "Link") {
|
||||
return;
|
||||
}
|
||||
// Adding 2 on each side due to [[ and ]] that are outside the WikiLinkPage node
|
||||
if (isCursorInRange(view.state, [from, to])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = view.state.sliceDoc(from, to);
|
||||
// Links are of the form [hell](https://example.com)
|
||||
const [anchorPart, linkPart] = text.split("]("); // Not pretty
|
||||
if (!linkPart) {
|
||||
// Invalid link
|
||||
return;
|
||||
}
|
||||
const cleanAnchor = anchorPart.substring(1); // cut off the initial [
|
||||
const cleanLink = linkPart.substring(0, linkPart.length - 1); // cut off the final )
|
||||
|
||||
// Hide the whole thing
|
||||
widgets.push(
|
||||
invisibleDecoration.range(
|
||||
from,
|
||||
to,
|
||||
),
|
||||
);
|
||||
|
||||
widgets.push(
|
||||
Decoration.widget({
|
||||
widget: new LinkWidget(
|
||||
{
|
||||
text: cleanAnchor,
|
||||
title: `Click to visit ${cleanLink}`,
|
||||
cssClass: "sb-link",
|
||||
href: cleanLink,
|
||||
callback: (e) => {
|
||||
if (e.altKey) {
|
||||
// Move cursor into the link, approximate location
|
||||
return view.dispatch({
|
||||
selection: { anchor: from + 1 },
|
||||
});
|
||||
}
|
||||
// Dispatch click event to navigate there without moving the cursor
|
||||
const clickEvent: ClickEvent = {
|
||||
page: editor.currentPage!,
|
||||
ctrlKey: e.ctrlKey,
|
||||
metaKey: e.metaKey,
|
||||
altKey: e.altKey,
|
||||
pos: from,
|
||||
};
|
||||
editor.dispatchAppEvent("page:click", clickEvent).catch(
|
||||
console.error,
|
||||
);
|
||||
},
|
||||
},
|
||||
),
|
||||
}).range(from),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return Decoration.set(widgets, true);
|
||||
}
|
||||
update(update: ViewUpdate) {
|
||||
if (
|
||||
update.docChanged ||
|
||||
update.viewportChanged ||
|
||||
update.selectionSet
|
||||
) {
|
||||
this.decorations = this.calculateDecorations();
|
||||
syntaxTree(state).iterate({
|
||||
enter: ({ type, from, to }) => {
|
||||
if (type.name !== "Link") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ decorations: (v) => v.decorations },
|
||||
);
|
||||
// Adding 2 on each side due to [[ and ]] that are outside the WikiLinkPage node
|
||||
if (isCursorInRange(state, [from, to])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = state.sliceDoc(from, to);
|
||||
// Links are of the form [hell](https://example.com)
|
||||
const [anchorPart, linkPart] = text.split("]("); // Not pretty
|
||||
if (!linkPart) {
|
||||
// Invalid link
|
||||
return;
|
||||
}
|
||||
const cleanAnchor = anchorPart.substring(1); // cut off the initial [
|
||||
const cleanLink = linkPart.substring(0, linkPart.length - 1); // cut off the final )
|
||||
|
||||
// Hide the whole thing
|
||||
widgets.push(
|
||||
invisibleDecoration.range(
|
||||
from,
|
||||
to,
|
||||
),
|
||||
);
|
||||
|
||||
widgets.push(
|
||||
Decoration.widget({
|
||||
widget: new LinkWidget(
|
||||
{
|
||||
text: cleanAnchor,
|
||||
title: `Click to visit ${cleanLink}`,
|
||||
cssClass: "sb-link",
|
||||
href: cleanLink,
|
||||
callback: (e) => {
|
||||
if (e.altKey) {
|
||||
// Move cursor into the link, approximate location
|
||||
return editor.editorView!.dispatch({
|
||||
selection: { anchor: from + 1 },
|
||||
});
|
||||
}
|
||||
// Dispatch click event to navigate there without moving the cursor
|
||||
const clickEvent: ClickEvent = {
|
||||
page: editor.currentPage!,
|
||||
ctrlKey: e.ctrlKey,
|
||||
metaKey: e.metaKey,
|
||||
altKey: e.altKey,
|
||||
pos: from,
|
||||
};
|
||||
editor.dispatchAppEvent("page:click", clickEvent).catch(
|
||||
console.error,
|
||||
);
|
||||
},
|
||||
},
|
||||
),
|
||||
}).range(from),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return Decoration.set(widgets, true);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user