Change selection behaviour (#904)
* Change selection behaviour * turn mouseup/mousedown events into click * Remove `drawSelection()` * Add custom cursorpull/917/head
parent
09e7f1f906
commit
5c28b1a73f
|
@ -6,7 +6,6 @@ import {
|
||||||
decoratorStateField,
|
decoratorStateField,
|
||||||
invisibleDecoration,
|
invisibleDecoration,
|
||||||
isCursorInRange,
|
isCursorInRange,
|
||||||
shouldRenderAsCode,
|
|
||||||
} from "./util.ts";
|
} from "./util.ts";
|
||||||
import { MarkdownWidget } from "./markdown_widget.ts";
|
import { MarkdownWidget } from "./markdown_widget.ts";
|
||||||
import { IFrameWidget } from "./iframe_widget.ts";
|
import { IFrameWidget } from "./iframe_widget.ts";
|
||||||
|
@ -18,7 +17,7 @@ export function fencedCodePlugin(editor: Client) {
|
||||||
syntaxTree(state).iterate({
|
syntaxTree(state).iterate({
|
||||||
enter({ from, to, name, node }) {
|
enter({ from, to, name, node }) {
|
||||||
if (name === "FencedCode") {
|
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
|
// Don't render the widget if the cursor is inside the fenced code
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,6 @@ export class LinkWidget extends WidgetType {
|
||||||
|
|
||||||
// Mouse handling
|
// Mouse handling
|
||||||
anchor.addEventListener("click", (e) => {
|
anchor.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
anchor.addEventListener("mouseup", (e) => {
|
|
||||||
if (e.button !== 0) {
|
if (e.button !== 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -57,6 +53,13 @@ export class LinkWidget extends WidgetType {
|
||||||
anchor.href = this.options.href || "#";
|
anchor.href = this.options.href || "#";
|
||||||
return anchor;
|
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 {
|
export class HtmlWidget extends WidgetType {
|
||||||
|
@ -89,10 +92,8 @@ export function decoratorStateField(
|
||||||
},
|
},
|
||||||
|
|
||||||
update(value: DecorationSet, tr: Transaction) {
|
update(value: DecorationSet, tr: Transaction) {
|
||||||
// if (tr.docChanged || tr.selection) {
|
if (tr.isUserEvent("select.pointer")) return value;
|
||||||
return stateToDecoratorMapper(tr.state);
|
return stateToDecoratorMapper(tr.state);
|
||||||
// }
|
|
||||||
// return value;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
provide: (f) => EditorView.decorations.from(f),
|
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.
|
* Decoration to simply hide anything.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -86,9 +86,11 @@ export function cleanWikiLinkPlugin(client: Client) {
|
||||||
callback: (e) => {
|
callback: (e) => {
|
||||||
if (e.altKey) {
|
if (e.altKey) {
|
||||||
// Move cursor into the link
|
// Move cursor into the link
|
||||||
return client.editorView.dispatch({
|
client.editorView.dispatch({
|
||||||
selection: { anchor: from + firstMark.length },
|
selection: { anchor: from + firstMark.length },
|
||||||
});
|
});
|
||||||
|
client.focus();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// Dispatch click event to navigate there without moving the cursor
|
// Dispatch click event to navigate there without moving the cursor
|
||||||
const clickEvent: ClickEvent = {
|
const clickEvent: ClickEvent = {
|
||||||
|
|
|
@ -19,14 +19,15 @@ import {
|
||||||
LanguageSupport,
|
LanguageSupport,
|
||||||
syntaxHighlighting,
|
syntaxHighlighting,
|
||||||
} from "@codemirror/language";
|
} from "@codemirror/language";
|
||||||
import { EditorState } from "@codemirror/state";
|
import { EditorSelection, EditorState } from "@codemirror/state";
|
||||||
import {
|
import {
|
||||||
drawSelection,
|
|
||||||
dropCursor,
|
dropCursor,
|
||||||
EditorView,
|
EditorView,
|
||||||
highlightSpecialChars,
|
highlightSpecialChars,
|
||||||
KeyBinding,
|
KeyBinding,
|
||||||
keymap,
|
keymap,
|
||||||
|
layer,
|
||||||
|
RectangleMarker,
|
||||||
ViewPlugin,
|
ViewPlugin,
|
||||||
ViewUpdate,
|
ViewUpdate,
|
||||||
} from "@codemirror/view";
|
} from "@codemirror/view";
|
||||||
|
@ -115,7 +116,6 @@ export function createEditorState(
|
||||||
codeCopyPlugin(client),
|
codeCopyPlugin(client),
|
||||||
highlightSpecialChars(),
|
highlightSpecialChars(),
|
||||||
history(),
|
history(),
|
||||||
drawSelection(),
|
|
||||||
dropCursor(),
|
dropCursor(),
|
||||||
codeFolding({
|
codeFolding({
|
||||||
placeholderText: "…",
|
placeholderText: "…",
|
||||||
|
@ -124,8 +124,46 @@ export function createEditorState(
|
||||||
...cleanModePlugins(client),
|
...cleanModePlugins(client),
|
||||||
EditorView.lineWrapping,
|
EditorView.lineWrapping,
|
||||||
plugLinter(client),
|
plugLinter(client),
|
||||||
// lintGutter(),
|
// Taken from https://github.com/codemirror/view/blob/main/src/draw-selection.ts
|
||||||
// gutters(),
|
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),
|
postScriptPrefacePlugin(client),
|
||||||
lineWrapper([
|
lineWrapper([
|
||||||
{ selector: "ATXHeading1", class: "sb-line-h1" },
|
{ selector: "ATXHeading1", class: "sb-line-h1" },
|
||||||
|
@ -200,7 +238,7 @@ export function createEditorState(
|
||||||
touchCount = 0;
|
touchCount = 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
mousedown: (event: MouseEvent, view: EditorView) => {
|
click: (event: MouseEvent, view: EditorView) => {
|
||||||
const pos = view.posAtCoords(event);
|
const pos = view.posAtCoords(event);
|
||||||
if (event.button !== 0) {
|
if (event.button !== 0) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -60,6 +60,12 @@ export class MainUI {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
globalThis.addEventListener("mouseup", (_) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
client.editorView.dispatch({});
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewComponent() {
|
ViewComponent() {
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
color: var(--root-color);
|
color: var(--root-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-cursor {
|
.cm-cursor,
|
||||||
|
.cm-dropCursor {
|
||||||
border-left: 1.2px solid var(--editor-caret-color) !important;
|
border-left: 1.2px solid var(--editor-caret-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,9 +175,10 @@
|
||||||
#sb-editor {
|
#sb-editor {
|
||||||
.cm-content {
|
.cm-content {
|
||||||
font-family: var(--editor-font);
|
font-family: var(--editor-font);
|
||||||
|
caret-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-selectionBackground {
|
::selection {
|
||||||
background-color: var(--editor-selection-background-color);
|
background-color: var(--editor-selection-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -401,6 +401,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-line-code-outside .sb-code-info {
|
.sb-line-code-outside .sb-code-info {
|
||||||
|
&::selection {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
float: right;
|
float: right;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
|
@ -408,6 +412,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-code-copy-button {
|
.sb-code-copy-button {
|
||||||
|
&::selection {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
float: right;
|
float: right;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 0 3px;
|
margin: 0 3px;
|
||||||
|
|
Loading…
Reference in New Issue