You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-24 20:19:10 +02:00
Desktop,Mobile: Markdown editor: Toggle checkboxes on ctrl-click (#12927)
This commit is contained in:
@@ -1004,6 +1004,8 @@ packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
|
||||
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
|
||||
packages/editor/CodeMirror/editorCommands/supportsCommand.js
|
||||
packages/editor/CodeMirror/extensions/biDirectionalTextExtension.js
|
||||
packages/editor/CodeMirror/extensions/ctrlClickActionExtension.js
|
||||
packages/editor/CodeMirror/extensions/ctrlClickCheckboxExtension.js
|
||||
packages/editor/CodeMirror/extensions/keyUpHandlerExtension.js
|
||||
packages/editor/CodeMirror/extensions/links/ctrlClickLinksExtension.js
|
||||
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.test.js
|
||||
@@ -1074,9 +1076,11 @@ packages/editor/CodeMirror/utils/isInSyntaxNode.js
|
||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/allLanguages.js
|
||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/defaultLanguage.js
|
||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/lookUpLanguage.js
|
||||
packages/editor/CodeMirror/utils/markdown/getCheckboxAtPosition.js
|
||||
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.test.js
|
||||
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
|
||||
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
|
||||
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
|
||||
packages/editor/CodeMirror/utils/setupVim.js
|
||||
packages/editor/ProseMirror/commands.test.js
|
||||
packages/editor/ProseMirror/commands.js
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -977,6 +977,8 @@ packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
|
||||
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
|
||||
packages/editor/CodeMirror/editorCommands/supportsCommand.js
|
||||
packages/editor/CodeMirror/extensions/biDirectionalTextExtension.js
|
||||
packages/editor/CodeMirror/extensions/ctrlClickActionExtension.js
|
||||
packages/editor/CodeMirror/extensions/ctrlClickCheckboxExtension.js
|
||||
packages/editor/CodeMirror/extensions/keyUpHandlerExtension.js
|
||||
packages/editor/CodeMirror/extensions/links/ctrlClickLinksExtension.js
|
||||
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.test.js
|
||||
@@ -1047,9 +1049,11 @@ packages/editor/CodeMirror/utils/isInSyntaxNode.js
|
||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/allLanguages.js
|
||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/defaultLanguage.js
|
||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/lookUpLanguage.js
|
||||
packages/editor/CodeMirror/utils/markdown/getCheckboxAtPosition.js
|
||||
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.test.js
|
||||
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
|
||||
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
|
||||
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
|
||||
packages/editor/CodeMirror/utils/setupVim.js
|
||||
packages/editor/ProseMirror/commands.test.js
|
||||
packages/editor/ProseMirror/commands.js
|
||||
|
@@ -39,6 +39,7 @@ import selectedNoteIdExtension, { setNoteIdEffect } from './extensions/selectedN
|
||||
import ctrlKeyStateClassExtension from './extensions/modifierKeyCssExtension';
|
||||
import ctrlClickLinksExtension from './extensions/links/ctrlClickLinksExtension';
|
||||
import { RenderedContentContext } from './extensions/rendering/types';
|
||||
import ctrlClickCheckboxExtension from './extensions/ctrlClickCheckboxExtension';
|
||||
|
||||
// Newer versions of CodeMirror by default use Chrome's EditContext API.
|
||||
// While this might be stable enough for desktop use, it causes significant
|
||||
@@ -255,6 +256,7 @@ const createEditor = (
|
||||
ctrlClickLinksExtension(link => {
|
||||
props.onEvent({ kind: EditorEventType.FollowLink, link });
|
||||
}),
|
||||
ctrlClickCheckboxExtension(),
|
||||
|
||||
highlightSpecialChars(),
|
||||
indentOnInput(),
|
||||
|
@@ -0,0 +1,33 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { Prec } from '@codemirror/state';
|
||||
|
||||
const hasMultipleCursors = (view: EditorView) => {
|
||||
return view.state.selection.ranges.length > 1;
|
||||
};
|
||||
|
||||
type OnCtrlClick = (view: EditorView, event: MouseEvent)=> boolean;
|
||||
|
||||
const ctrlClickActionExtension = (onCtrlClick: OnCtrlClick) => {
|
||||
return [
|
||||
Prec.high([
|
||||
EditorView.domEventHandlers({
|
||||
mousedown: (event: MouseEvent, view: EditorView) => {
|
||||
const hasModifier = event.ctrlKey || event.metaKey;
|
||||
// The default CodeMirror action for ctrl-click is to add another cursor
|
||||
// to the document. If the user already has multiple cursors, assume that
|
||||
// the ctrl-click action is intended to add another.
|
||||
if (hasModifier && !hasMultipleCursors(view)) {
|
||||
const handled = onCtrlClick(view, event);
|
||||
if (handled) {
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}),
|
||||
]),
|
||||
];
|
||||
};
|
||||
|
||||
export default ctrlClickActionExtension;
|
@@ -0,0 +1,30 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import modifierKeyCssExtension from './modifierKeyCssExtension';
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
import getCheckboxAtPosition from '../utils/markdown/getCheckboxAtPosition';
|
||||
import toggleCheckboxAt from '../utils/markdown/toggleCheckboxAt';
|
||||
import ctrlClickActionExtension from './ctrlClickActionExtension';
|
||||
|
||||
|
||||
const ctrlClickCheckboxExtension = () => {
|
||||
return [
|
||||
modifierKeyCssExtension,
|
||||
EditorView.theme({
|
||||
'&.-ctrl-or-cmd-pressed .cm-taskMarker': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
}),
|
||||
ctrlClickActionExtension((view, event) => {
|
||||
const target = view.posAtCoords(event);
|
||||
const taskMarker = getCheckboxAtPosition(target, syntaxTree(view.state));
|
||||
|
||||
if (taskMarker) {
|
||||
toggleCheckboxAt(target)(view);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
];
|
||||
};
|
||||
|
||||
export default ctrlClickCheckboxExtension;
|
@@ -4,12 +4,10 @@ import modifierKeyCssExtension from '../modifierKeyCssExtension';
|
||||
import openLink from './utils/openLink';
|
||||
import getUrlAtPosition from './utils/getUrlAtPosition';
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
import { Prec } from '@codemirror/state';
|
||||
|
||||
import ctrlClickActionExtension from '../ctrlClickActionExtension';
|
||||
|
||||
type OnOpenLink = (url: string, view: EditorView)=> void;
|
||||
|
||||
|
||||
const ctrlClickLinksExtension = (onOpenExternalLink: OnOpenLink) => {
|
||||
return [
|
||||
modifierKeyCssExtension,
|
||||
@@ -19,27 +17,16 @@ const ctrlClickLinksExtension = (onOpenExternalLink: OnOpenLink) => {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
}),
|
||||
Prec.high([
|
||||
EditorView.domEventHandlers({
|
||||
mousedown: (event: MouseEvent, view: EditorView) => {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
const target = view.posAtCoords(event);
|
||||
const url = getUrlAtPosition(target, syntaxTree(view.state), view.state);
|
||||
const hasMultipleCursors = view.state.selection.ranges.length > 1;
|
||||
ctrlClickActionExtension((view: EditorView, event: MouseEvent) => {
|
||||
const target = view.posAtCoords(event);
|
||||
const url = getUrlAtPosition(target, syntaxTree(view.state), view.state);
|
||||
|
||||
// The default CodeMirror action for ctrl-click is to add another cursor
|
||||
// to the document. If the user already has multiple cursors, assume that
|
||||
// the ctrl-click action is intended to add another.
|
||||
if (url && !hasMultipleCursors) {
|
||||
openLink(url.url, view, onOpenExternalLink);
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}),
|
||||
]),
|
||||
if (url) {
|
||||
openLink(url.url, view, onOpenExternalLink);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
];
|
||||
};
|
||||
|
||||
|
@@ -1,30 +1,10 @@
|
||||
import { Decoration, EditorView, WidgetType } from '@codemirror/view';
|
||||
import { SyntaxNodeRef } from '@lezer/common';
|
||||
import makeReplaceExtension from './utils/makeInlineReplaceExtension';
|
||||
import toggleCheckboxAt from '../../utils/markdown/toggleCheckboxAt';
|
||||
|
||||
const checkboxClassName = 'cm-ext-checkbox-toggle';
|
||||
|
||||
const toggleCheckbox = (view: EditorView, linePos: number) => {
|
||||
if (linePos >= view.state.doc.length) {
|
||||
// Position out of range
|
||||
return false;
|
||||
}
|
||||
|
||||
const line = view.state.doc.lineAt(linePos);
|
||||
const checkboxMarkup = line.text.match(/\[(x|\s)\]/);
|
||||
if (!checkboxMarkup) {
|
||||
// Couldn't find the checkbox
|
||||
return false;
|
||||
}
|
||||
|
||||
const isChecked = checkboxMarkup[0] === '[x]';
|
||||
const checkboxPos = checkboxMarkup.index! + line.from;
|
||||
|
||||
view.dispatch({
|
||||
changes: [{ from: checkboxPos, to: checkboxPos + 3, insert: isChecked ? '[ ]' : '[x]' }],
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
class CheckboxWidget extends WidgetType {
|
||||
public constructor(private checked: boolean, private depth: number, private label: string) {
|
||||
@@ -58,7 +38,7 @@ class CheckboxWidget extends WidgetType {
|
||||
container.appendChild(checkbox);
|
||||
|
||||
checkbox.oninput = () => {
|
||||
toggleCheckbox(view, view.posAtDOM(container));
|
||||
toggleCheckboxAt(view.posAtDOM(container))(view);
|
||||
};
|
||||
|
||||
this.applyContainerClasses(container);
|
||||
|
@@ -0,0 +1,21 @@
|
||||
import { Tree } from '@lezer/common';
|
||||
|
||||
const getCheckboxAtPosition = (pos: number, tree: Tree) => {
|
||||
let iterator = tree.resolveStack(pos);
|
||||
|
||||
while (true) {
|
||||
if (iterator.node.name === 'TaskMarker') {
|
||||
return iterator.node;
|
||||
}
|
||||
|
||||
if (!iterator.next) {
|
||||
break;
|
||||
} else {
|
||||
iterator = iterator.next;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default getCheckboxAtPosition;
|
@@ -0,0 +1,26 @@
|
||||
import { Command, EditorView } from '@codemirror/view';
|
||||
|
||||
const toggleCheckbox = (linePos: number): Command => (target: EditorView) => {
|
||||
const state = target.state;
|
||||
if (linePos >= state.doc.length) {
|
||||
// Position out of range
|
||||
return false;
|
||||
}
|
||||
|
||||
const line = state.doc.lineAt(linePos);
|
||||
const checkboxMarkup = line.text.match(/\[(x|\s)\]/);
|
||||
if (!checkboxMarkup) {
|
||||
// Couldn't find the checkbox
|
||||
return false;
|
||||
}
|
||||
|
||||
const isChecked = checkboxMarkup[0] === '[x]';
|
||||
const checkboxPos = checkboxMarkup.index! + line.from;
|
||||
|
||||
target.dispatch({
|
||||
changes: [{ from: checkboxPos, to: checkboxPos + 3, insert: isChecked ? '[ ]' : '[x]' }],
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
export default toggleCheckbox;
|
Reference in New Issue
Block a user