fixed indent/outdent
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { Extension } from '@tiptap/core';
|
||||
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
||||
|
||||
/**
|
||||
* MobileIndent Extension
|
||||
*
|
||||
* Provides mobile-friendly indent/outdent by:
|
||||
* 1. Converting a space at the start of a line into an indent action (Tab)
|
||||
* 2. Converting a backspace at the start of an indented list item into an outdent action (Shift+Tab)
|
||||
*/
|
||||
export const MobileIndent = Extension.create({
|
||||
name: 'mobileIndent',
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
const editor = this.editor;
|
||||
|
||||
return [
|
||||
new Plugin({
|
||||
key: new PluginKey('mobileIndent'),
|
||||
props: {
|
||||
handleTextInput(view, _from, _to, text) {
|
||||
// Only care about space
|
||||
if (text !== ' ') return false;
|
||||
|
||||
const { state } = view;
|
||||
const { $from } = state.selection;
|
||||
|
||||
// Check if we're at the start of a text block (position 0 of the parent)
|
||||
// $from.parentOffset is the offset within the current parent node
|
||||
if ($from.parentOffset !== 0) return false;
|
||||
|
||||
// Check if we're in a list item (taskItem or listItem)
|
||||
const parentNode = $from.node(-1);
|
||||
if (!parentNode) return false;
|
||||
|
||||
const parentType = parentNode.type.name;
|
||||
const isListItem = parentType === 'taskItem' || parentType === 'listItem';
|
||||
|
||||
if (!isListItem) return false;
|
||||
|
||||
// Sink the list item (indent)
|
||||
const sunk = editor.chain().sinkListItem('taskItem').run() ||
|
||||
editor.chain().sinkListItem('listItem').run();
|
||||
|
||||
// Return true to prevent the space from being inserted
|
||||
return sunk;
|
||||
},
|
||||
|
||||
handleKeyDown(view, event) {
|
||||
// Only care about Backspace
|
||||
if (event.key !== 'Backspace') return false;
|
||||
|
||||
const { state } = view;
|
||||
const { $from, empty } = state.selection;
|
||||
|
||||
// Only handle if selection is collapsed (cursor, not a range)
|
||||
if (!empty) return false;
|
||||
|
||||
// Check if we're at the start of a text block
|
||||
if ($from.parentOffset !== 0) return false;
|
||||
|
||||
// Check if we're in a list item
|
||||
const parentNode = $from.node(-1);
|
||||
if (!parentNode) return false;
|
||||
|
||||
const parentType = parentNode.type.name;
|
||||
const isListItem = parentType === 'taskItem' || parentType === 'listItem';
|
||||
|
||||
if (!isListItem) return false;
|
||||
|
||||
// Check if this list item is nested (has a parent list that is itself in a list item)
|
||||
// We only want to lift if we are nested
|
||||
const grandparent = $from.node(-2);
|
||||
const greatGrandparent = $from.node(-3);
|
||||
|
||||
// If grandparent is a list and greatGrandparent is also a list item, we're nested
|
||||
const grandparentIsList = grandparent?.type.name === 'taskList' || grandparent?.type.name === 'bulletList' || grandparent?.type.name === 'orderedList';
|
||||
const greatGrandparentIsListItem = greatGrandparent?.type.name === 'taskItem' || greatGrandparent?.type.name === 'listItem';
|
||||
|
||||
if (!grandparentIsList || !greatGrandparentIsListItem) return false;
|
||||
|
||||
// Lift the list item (outdent)
|
||||
const lifted = editor.chain().liftListItem('taskItem').run() ||
|
||||
editor.chain().liftListItem('listItem').run();
|
||||
|
||||
if (lifted) {
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user