diff --git a/web/cm_plugins/fenced_code.ts b/web/cm_plugins/fenced_code.ts index 6577a7aa..d95ffd15 100644 --- a/web/cm_plugins/fenced_code.ts +++ b/web/cm_plugins/fenced_code.ts @@ -6,7 +6,6 @@ import { decoratorStateField, invisibleDecoration, isCursorInRange, - shouldRenderAsCode, } from "./util.ts"; import { MarkdownWidget } from "./markdown_widget.ts"; import { IFrameWidget } from "./iframe_widget.ts"; @@ -18,7 +17,7 @@ export function fencedCodePlugin(editor: Client) { syntaxTree(state).iterate({ enter({ from, to, name, node }) { if (name === "FencedCode") { - if (shouldRenderAsCode(state, [from, to])) { + if (isCursorInRange(state, [from, to])) { // Don't render the widget if the cursor is inside the fenced code return; } diff --git a/web/cm_plugins/util.ts b/web/cm_plugins/util.ts index b7a53c17..d9211300 100644 --- a/web/cm_plugins/util.ts +++ b/web/cm_plugins/util.ts @@ -24,10 +24,6 @@ export class LinkWidget extends WidgetType { // Mouse handling anchor.addEventListener("click", (e) => { - e.preventDefault(); - e.stopPropagation(); - }); - anchor.addEventListener("mouseup", (e) => { if (e.button !== 0) { return; } @@ -57,6 +53,13 @@ export class LinkWidget extends WidgetType { anchor.href = this.options.href || "#"; return anchor; } + + eq(other: WidgetType): boolean { + return other instanceof LinkWidget && + this.options.text === other.options.text && + this.options.href === other.options.href && + this.options.title === other.options.title; + } } export class HtmlWidget extends WidgetType { @@ -89,10 +92,8 @@ export function decoratorStateField( }, update(value: DecorationSet, tr: Transaction) { - // if (tr.docChanged || tr.selection) { + if (tr.isUserEvent("select.pointer")) return value; return stateToDecoratorMapper(tr.state); - // } - // return value; }, provide: (f) => EditorView.decorations.from(f), @@ -161,21 +162,6 @@ export function isCursorInRange(state: EditorState, range: [number, number]) { ); } -export function shouldRenderAsCode( - state: EditorState, - range: [number, number], -) { - const mainSelection = state.selection.main; - // When the selection is empty, we need to check if the cursor is inside the fenced code - if (mainSelection.empty) { - return checkRangeOverlap(range, [mainSelection.from, mainSelection.to]); - } else { - // If the selection is encompassing the fenced code we render as code, or vice versa - return checkRangeSubset([mainSelection.from, mainSelection.to], range) || - checkRangeSubset(range, [mainSelection.from, mainSelection.to]); - } -} - /** * Decoration to simply hide anything. */ diff --git a/web/cm_plugins/wiki_link.ts b/web/cm_plugins/wiki_link.ts index fb4c1fb4..42c19e6c 100644 --- a/web/cm_plugins/wiki_link.ts +++ b/web/cm_plugins/wiki_link.ts @@ -86,9 +86,11 @@ export function cleanWikiLinkPlugin(client: Client) { callback: (e) => { if (e.altKey) { // Move cursor into the link - return client.editorView.dispatch({ + client.editorView.dispatch({ selection: { anchor: from + firstMark.length }, }); + client.focus(); + return; } // Dispatch click event to navigate there without moving the cursor const clickEvent: ClickEvent = { diff --git a/web/editor_state.ts b/web/editor_state.ts index da4bf98d..0c0d3f57 100644 --- a/web/editor_state.ts +++ b/web/editor_state.ts @@ -19,14 +19,15 @@ import { LanguageSupport, syntaxHighlighting, } from "@codemirror/language"; -import { EditorState } from "@codemirror/state"; +import { EditorSelection, EditorState } from "@codemirror/state"; import { - drawSelection, dropCursor, EditorView, highlightSpecialChars, KeyBinding, keymap, + layer, + RectangleMarker, ViewPlugin, ViewUpdate, } from "@codemirror/view"; @@ -115,7 +116,6 @@ export function createEditorState( codeCopyPlugin(client), highlightSpecialChars(), history(), - drawSelection(), dropCursor(), codeFolding({ placeholderText: "…", @@ -124,8 +124,46 @@ export function createEditorState( ...cleanModePlugins(client), EditorView.lineWrapping, plugLinter(client), - // lintGutter(), - // gutters(), + // Taken from https://github.com/codemirror/view/blob/main/src/draw-selection.ts + layer({ + above: true, + markers(view) { + const safari = /Apple Computer/.test(navigator.vendor); + const ios = safari && + (/Mobile\/\w+/.test(navigator.userAgent) || + navigator.maxTouchPoints > 2); + + const { state } = view; + const cursors = []; + for (const r of state.selection.ranges) { + const prim = r == state.selection.main; + if (!r.empty || !prim || !ios) { + const className = prim + ? "cm-cursor cm-cursor-primary" + : "cm-cursor cm-cursor-secondary"; + const cursor = r.empty + ? r + : EditorSelection.cursor(r.head, r.head > r.anchor ? -1 : 1); + for ( + const piece of RectangleMarker.forRange(view, className, cursor) + ) cursors.push(piece); + } + } + return cursors; + }, + update(update, dom) { + if (update.transactions.some((tr) => tr.selection)) { + dom.style.animationName = dom.style.animationName == "cm-blink" + ? "cm-blink2" + : "cm-blink"; + } + return update.docChanged || update.selectionSet; + }, + mount(dom) { + dom.style.animationDuration = "1200ms"; + }, + class: "cm-cursorLayer", + }), postScriptPrefacePlugin(client), lineWrapper([ { selector: "ATXHeading1", class: "sb-line-h1" }, @@ -200,7 +238,7 @@ export function createEditorState( touchCount = 0; }, - mousedown: (event: MouseEvent, view: EditorView) => { + click: (event: MouseEvent, view: EditorView) => { const pos = view.posAtCoords(event); if (event.button !== 0) { return; diff --git a/web/editor_ui.tsx b/web/editor_ui.tsx index 2c63e425..e25abfde 100644 --- a/web/editor_ui.tsx +++ b/web/editor_ui.tsx @@ -60,6 +60,12 @@ export class MainUI { }); } }); + + globalThis.addEventListener("mouseup", (_) => { + setTimeout(() => { + client.editorView.dispatch({}); + }) + }); } ViewComponent() { diff --git a/web/styles/colors.scss b/web/styles/colors.scss index 4bdee6a8..78350b21 100644 --- a/web/styles/colors.scss +++ b/web/styles/colors.scss @@ -4,7 +4,8 @@ color: var(--root-color); } -.cm-cursor { +.cm-cursor, +.cm-dropCursor { border-left: 1.2px solid var(--editor-caret-color) !important; } @@ -174,9 +175,10 @@ #sb-editor { .cm-content { font-family: var(--editor-font); + caret-color: transparent; } - .cm-selectionBackground { + ::selection { background-color: var(--editor-selection-background-color); } diff --git a/web/styles/editor.scss b/web/styles/editor.scss index ba90b503..2b630356 100644 --- a/web/styles/editor.scss +++ b/web/styles/editor.scss @@ -401,6 +401,10 @@ } .sb-line-code-outside .sb-code-info { + &::selection { + background-color: transparent; + } + display: block; float: right; font-size: 90%; @@ -408,6 +412,10 @@ } .sb-code-copy-button { + &::selection { + background-color: transparent; + } + float: right; cursor: pointer; margin: 0 3px;