Editor drag handle

* add darkmode border to navbar
This commit is contained in:
Philipinho
2024-04-23 01:32:18 +01:00
parent 6a57e3edb2
commit 2af1fe3c40
5 changed files with 11 additions and 226 deletions

View File

@ -6,7 +6,7 @@
padding-top: 0;
display: flex;
flex-direction: column;
/*border-right: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));*/
border-right: rem(1px) solid light-dark('', var(--mantine-color-dark-4));
}
.section {

View File

@ -1,223 +0,0 @@
import { Extension } from '@tiptap/core';
import { NodeSelection, Plugin } from '@tiptap/pm/state';
// @ts-ignore
import { __serializeForClipboard as serializeForClipboard, EditorView } from '@tiptap/pm/view';
export interface DragHandleOptions {
dragHandleWidth: number;
}
function removeNode(node) {
node.parentNode.removeChild(node);
}
function absoluteRect(node) {
const data = node.getBoundingClientRect();
return {
top: data.top,
left: data.left,
width: data.width,
};
}
function nodeDOMAtCoords(coords: { x: number; y: number }) {
return document
.elementsFromPoint(coords.x, coords.y)
.find(
(elem: HTMLElement) =>
elem.parentElement?.matches?.('.ProseMirror') ||
elem.matches(
[
'li',
'p:not(:first-child)',
'pre',
'blockquote',
'h1, h2, h3',
'[data-type=horizontalRule]',
].join(', '),
),
);
}
export function nodePosAtDOM(node: Element, view: EditorView) {
const boundingRect = node.getBoundingClientRect();
return view.posAtCoords({
left: boundingRect.left + 1,
top: boundingRect.top + 1,
})?.inside;
}
function DragHandle(options: DragHandleOptions) {
function handleDragStart(event: DragEvent, view: EditorView) {
view.focus();
if (!event.dataTransfer) return;
const node = nodeDOMAtCoords({
x: event.clientX + 50 + options.dragHandleWidth,
y: event.clientY,
});
if (!(node instanceof Element)) return;
const nodePos = nodePosAtDOM(node, view);
if (!nodePos) return;
view.dispatch(
view.state.tr.setSelection(
NodeSelection.create(view.state.doc, nodePos),
),
);
const slice = view.state.selection.content();
const { dom, text } = serializeForClipboard(view, slice);
event.dataTransfer.clearData();
event.dataTransfer.setData('text/html', dom.innerHTML);
event.dataTransfer.setData('text/plain', text);
event.dataTransfer.effectAllowed = 'copyMove';
event.dataTransfer.setDragImage(node, 0, 0);
view.dragging = { slice, move: event.ctrlKey };
}
function handleClick(event: MouseEvent, view: EditorView) {
view.focus();
view.dom.classList.remove('dragging');
const node = nodeDOMAtCoords({
x: event.clientX + 50 + options.dragHandleWidth,
y: event.clientY,
});
if (!(node instanceof Element)) return;
const nodePos = nodePosAtDOM(node, view);
if (!nodePos) return;
view.dispatch(
view.state.tr.setSelection(
NodeSelection.create(view.state.doc, nodePos),
),
);
}
let dragHandleElement: HTMLElement | null = null;
function hideDragHandle() {
if (dragHandleElement) {
dragHandleElement.classList.add('hidden');
}
}
function showDragHandle() {
if (dragHandleElement) {
dragHandleElement.classList.remove('hidden');
}
}
// @ts-ignore
return new Plugin({
view: (view) => {
dragHandleElement = document.createElement('div');
dragHandleElement.draggable = true;
dragHandleElement.dataset.dragHandle = '';
dragHandleElement.classList.add('drag-handle');
dragHandleElement.addEventListener('dragstart', (e) => {
handleDragStart(e, view);
});
dragHandleElement.addEventListener('click', (e) => {
handleClick(e, view);
});
hideDragHandle();
view?.dom?.parentElement?.appendChild(dragHandleElement);
return {
destroy: () => {
dragHandleElement?.remove?.();
dragHandleElement = null;
},
};
},
props: {
handleDOMEvents: {
mousemove: (view, event) => {
if (!view.editable) {
return;
}
const node = nodeDOMAtCoords({
x: event.clientX + 50 + options.dragHandleWidth,
y: event.clientY,
});
if (!(node instanceof Element)) {
hideDragHandle();
return;
}
const compStyle = window.getComputedStyle(node);
const lineHeight = parseInt(compStyle.lineHeight, 10);
const paddingTop = parseInt(compStyle.paddingTop, 10);
const rect = absoluteRect(node);
rect.top += (lineHeight - 24) / 2;
rect.top += paddingTop;
// Li markers
if (
node.matches('ul:not([data-type=taskList]) li, ol li')
) {
rect.left -= options.dragHandleWidth;
}
rect.width = options.dragHandleWidth;
if (!dragHandleElement) return;
dragHandleElement.style.left = `${rect.left - rect.width}px`;
dragHandleElement.style.top = `${rect.top}px`;
showDragHandle();
},
keydown: () => {
hideDragHandle();
},
mousewheel: () => {
hideDragHandle();
},
// dragging class is used for CSS
dragstart: (view) => {
view.dom.classList.add('dragging');
},
drop: (view) => {
view.dom.classList.remove('dragging');
},
dragend: (view) => {
view.dom.classList.remove('dragging');
},
},
},
});
}
export interface DragAndDropOptions {
}
// @ts-ignore
const DragAndDrop = Extension.create<DragAndDropOptions>({
name: 'dragAndDrop',
addProseMirrorPlugins() {
return [
DragHandle({
dragHandleWidth: 24,
}),
];
},
});
export default DragAndDrop;

View File

@ -9,7 +9,6 @@ import { Superscript } from "@tiptap/extension-superscript";
import SubScript from "@tiptap/extension-subscript";
import { Highlight } from "@tiptap/extension-highlight";
import { Typography } from "@tiptap/extension-typography";
import DragAndDrop from "@/features/editor/extensions/drag-handle";
import { TextStyle } from "@tiptap/extension-text-style";
import { Color } from "@tiptap/extension-color";
import SlashCommand from "@/features/editor/extensions/slash-command";
@ -17,6 +16,7 @@ import { Collaboration } from "@tiptap/extension-collaboration";
import { CollaborationCursor } from "@tiptap/extension-collaboration-cursor";
import { HocuspocusProvider } from "@hocuspocus/provider";
import { Comment, TrailingNode } from "@docmost/editor-ext";
import GlobalDragHandle from "tiptap-extension-global-drag-handle";
export const mainExtensions = [
StarterKit.configure({
@ -43,7 +43,7 @@ export const mainExtensions = [
}),
Typography,
TrailingNode,
DragAndDrop,
GlobalDragHandle,
TextStyle,
Color,
SlashCommand,

View File

@ -37,6 +37,7 @@
"@tiptap/starter-kit": "^2.2.4",
"@tiptap/suggestion": "^2.2.4",
"fractional-indexing-jittered": "^0.9.1",
"tiptap-extension-global-drag-handle": "^0.1.6",
"y-indexeddb": "^9.0.12",
"yjs": "^13.6.14"
},

7
pnpm-lock.yaml generated
View File

@ -101,6 +101,9 @@ importers:
fractional-indexing-jittered:
specifier: ^0.9.1
version: 0.9.1
tiptap-extension-global-drag-handle:
specifier: ^0.1.6
version: 0.1.6
y-indexeddb:
specifier: ^9.0.12
version: 9.0.12(yjs@13.6.14)
@ -11035,6 +11038,10 @@ packages:
'@popperjs/core': 2.11.8
dev: false
/tiptap-extension-global-drag-handle@0.1.6:
resolution: {integrity: sha512-kjRFd/glJGTHEglHPNWtML1iINuEBG4eWvWbNIuuXE2LbylFXYHKVgLvZgxilqXSBH/o+lKDEp7/iL3lKvnxGw==}
dev: false
/tmp@0.0.33:
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
engines: {node: '>=0.6.0'}