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/sortSelectedLines.js
|
||||||
packages/editor/CodeMirror/editorCommands/supportsCommand.js
|
packages/editor/CodeMirror/editorCommands/supportsCommand.js
|
||||||
packages/editor/CodeMirror/extensions/biDirectionalTextExtension.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/keyUpHandlerExtension.js
|
||||||
packages/editor/CodeMirror/extensions/links/ctrlClickLinksExtension.js
|
packages/editor/CodeMirror/extensions/links/ctrlClickLinksExtension.js
|
||||||
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.test.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/allLanguages.js
|
||||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/defaultLanguage.js
|
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/defaultLanguage.js
|
||||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/lookUpLanguage.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.test.js
|
||||||
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
|
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
|
||||||
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
|
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
|
||||||
|
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
|
||||||
packages/editor/CodeMirror/utils/setupVim.js
|
packages/editor/CodeMirror/utils/setupVim.js
|
||||||
packages/editor/ProseMirror/commands.test.js
|
packages/editor/ProseMirror/commands.test.js
|
||||||
packages/editor/ProseMirror/commands.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/sortSelectedLines.js
|
||||||
packages/editor/CodeMirror/editorCommands/supportsCommand.js
|
packages/editor/CodeMirror/editorCommands/supportsCommand.js
|
||||||
packages/editor/CodeMirror/extensions/biDirectionalTextExtension.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/keyUpHandlerExtension.js
|
||||||
packages/editor/CodeMirror/extensions/links/ctrlClickLinksExtension.js
|
packages/editor/CodeMirror/extensions/links/ctrlClickLinksExtension.js
|
||||||
packages/editor/CodeMirror/extensions/links/followLinkTooltipExtension.test.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/allLanguages.js
|
||||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/defaultLanguage.js
|
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/defaultLanguage.js
|
||||||
packages/editor/CodeMirror/utils/markdown/codeBlockLanguages/lookUpLanguage.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.test.js
|
||||||
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
|
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
|
||||||
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
|
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
|
||||||
|
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
|
||||||
packages/editor/CodeMirror/utils/setupVim.js
|
packages/editor/CodeMirror/utils/setupVim.js
|
||||||
packages/editor/ProseMirror/commands.test.js
|
packages/editor/ProseMirror/commands.test.js
|
||||||
packages/editor/ProseMirror/commands.js
|
packages/editor/ProseMirror/commands.js
|
||||||
|
@@ -39,6 +39,7 @@ import selectedNoteIdExtension, { setNoteIdEffect } from './extensions/selectedN
|
|||||||
import ctrlKeyStateClassExtension from './extensions/modifierKeyCssExtension';
|
import ctrlKeyStateClassExtension from './extensions/modifierKeyCssExtension';
|
||||||
import ctrlClickLinksExtension from './extensions/links/ctrlClickLinksExtension';
|
import ctrlClickLinksExtension from './extensions/links/ctrlClickLinksExtension';
|
||||||
import { RenderedContentContext } from './extensions/rendering/types';
|
import { RenderedContentContext } from './extensions/rendering/types';
|
||||||
|
import ctrlClickCheckboxExtension from './extensions/ctrlClickCheckboxExtension';
|
||||||
|
|
||||||
// Newer versions of CodeMirror by default use Chrome's EditContext API.
|
// Newer versions of CodeMirror by default use Chrome's EditContext API.
|
||||||
// While this might be stable enough for desktop use, it causes significant
|
// While this might be stable enough for desktop use, it causes significant
|
||||||
@@ -255,6 +256,7 @@ const createEditor = (
|
|||||||
ctrlClickLinksExtension(link => {
|
ctrlClickLinksExtension(link => {
|
||||||
props.onEvent({ kind: EditorEventType.FollowLink, link });
|
props.onEvent({ kind: EditorEventType.FollowLink, link });
|
||||||
}),
|
}),
|
||||||
|
ctrlClickCheckboxExtension(),
|
||||||
|
|
||||||
highlightSpecialChars(),
|
highlightSpecialChars(),
|
||||||
indentOnInput(),
|
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 openLink from './utils/openLink';
|
||||||
import getUrlAtPosition from './utils/getUrlAtPosition';
|
import getUrlAtPosition from './utils/getUrlAtPosition';
|
||||||
import { syntaxTree } from '@codemirror/language';
|
import { syntaxTree } from '@codemirror/language';
|
||||||
import { Prec } from '@codemirror/state';
|
import ctrlClickActionExtension from '../ctrlClickActionExtension';
|
||||||
|
|
||||||
|
|
||||||
type OnOpenLink = (url: string, view: EditorView)=> void;
|
type OnOpenLink = (url: string, view: EditorView)=> void;
|
||||||
|
|
||||||
|
|
||||||
const ctrlClickLinksExtension = (onOpenExternalLink: OnOpenLink) => {
|
const ctrlClickLinksExtension = (onOpenExternalLink: OnOpenLink) => {
|
||||||
return [
|
return [
|
||||||
modifierKeyCssExtension,
|
modifierKeyCssExtension,
|
||||||
@@ -19,27 +17,16 @@ const ctrlClickLinksExtension = (onOpenExternalLink: OnOpenLink) => {
|
|||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
Prec.high([
|
ctrlClickActionExtension((view: EditorView, event: MouseEvent) => {
|
||||||
EditorView.domEventHandlers({
|
const target = view.posAtCoords(event);
|
||||||
mousedown: (event: MouseEvent, view: EditorView) => {
|
const url = getUrlAtPosition(target, syntaxTree(view.state), view.state);
|
||||||
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;
|
|
||||||
|
|
||||||
// The default CodeMirror action for ctrl-click is to add another cursor
|
if (url) {
|
||||||
// to the document. If the user already has multiple cursors, assume that
|
openLink(url.url, view, onOpenExternalLink);
|
||||||
// the ctrl-click action is intended to add another.
|
return true;
|
||||||
if (url && !hasMultipleCursors) {
|
}
|
||||||
openLink(url.url, view, onOpenExternalLink);
|
return false;
|
||||||
event.preventDefault();
|
}),
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,30 +1,10 @@
|
|||||||
import { Decoration, EditorView, WidgetType } from '@codemirror/view';
|
import { Decoration, EditorView, WidgetType } from '@codemirror/view';
|
||||||
import { SyntaxNodeRef } from '@lezer/common';
|
import { SyntaxNodeRef } from '@lezer/common';
|
||||||
import makeReplaceExtension from './utils/makeInlineReplaceExtension';
|
import makeReplaceExtension from './utils/makeInlineReplaceExtension';
|
||||||
|
import toggleCheckboxAt from '../../utils/markdown/toggleCheckboxAt';
|
||||||
|
|
||||||
const checkboxClassName = 'cm-ext-checkbox-toggle';
|
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 {
|
class CheckboxWidget extends WidgetType {
|
||||||
public constructor(private checked: boolean, private depth: number, private label: string) {
|
public constructor(private checked: boolean, private depth: number, private label: string) {
|
||||||
@@ -58,7 +38,7 @@ class CheckboxWidget extends WidgetType {
|
|||||||
container.appendChild(checkbox);
|
container.appendChild(checkbox);
|
||||||
|
|
||||||
checkbox.oninput = () => {
|
checkbox.oninput = () => {
|
||||||
toggleCheckbox(view, view.posAtDOM(container));
|
toggleCheckboxAt(view.posAtDOM(container))(view);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.applyContainerClasses(container);
|
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