mirror of
https://github.com/fantasticit/think.git
synced 2026-10-30 22:56:06 +08:00
Merge pull request #290 from fantasticit/fix/20250211
fix: fix js error
This commit is contained in:
@@ -23,7 +23,7 @@ const nextConfig = semi({
|
||||
env: {
|
||||
SERVER_API_URL: config.client.apiUrl,
|
||||
COLLABORATION_API_URL: config.client.collaborationUrl,
|
||||
ENABLE_OSS_S3: config.oss.s3.enable,
|
||||
ENABLE_OSS_S3: config.oss.s3?.enable,
|
||||
DNS_PREFETCH: (config.client.dnsPrefetch || '').split(' '),
|
||||
SITE_URL: config.client.siteUrl,
|
||||
},
|
||||
|
||||
@@ -47,6 +47,28 @@ export const Ad = () => {
|
||||
const $form = useRef<FormApi>();
|
||||
const [currentCover, setCurrentCover] = useState('');
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
...columns,
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'id',
|
||||
render: (id) => {
|
||||
return (
|
||||
<Button
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
deleteAd(id);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}, [deleteAd]);
|
||||
|
||||
const setCover = useCallback((url) => {
|
||||
$form.current.setValue('cover', url);
|
||||
setCurrentCover(url);
|
||||
@@ -111,29 +133,7 @@ export const Ad = () => {
|
||||
<Form.Input label="链接" field="url" style={{ width: '100%' }} placeholder="请输入跳转链接"></Form.Input>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Table
|
||||
columns={[
|
||||
...columns,
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'id',
|
||||
render: (id) => {
|
||||
return (
|
||||
<Button
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
deleteAd(id);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
dataSource={data}
|
||||
pagination={false}
|
||||
/>
|
||||
<Table columns={columns} dataSource={data} pagination={false} />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -1,341 +1,396 @@
|
||||
import ReactDOM from 'react-dom';
|
||||
import type { Editor } from '@tiptap/core';
|
||||
import { Extension, findParentNodeClosestToPos } from '@tiptap/core';
|
||||
|
||||
import { Extension } from '@tiptap/core';
|
||||
import { safePos } from 'tiptap/prose-utils';
|
||||
import { ActiveNode, removePossibleTable, selectAncestorNodeByDom } from 'tiptap/prose-utils';
|
||||
import type { Node } from 'prosemirror-model';
|
||||
import { Fragment, Slice } from 'prosemirror-model';
|
||||
import { NodeSelection, Plugin, PluginKey, TextSelection } from 'prosemirror-state';
|
||||
import type { EditorView } from 'prosemirror-view';
|
||||
// @ts-expect-error
|
||||
import { __serializeForClipboard } from 'prosemirror-view';
|
||||
|
||||
import {
|
||||
NodeSelection,
|
||||
Plugin as PMPlugin,
|
||||
PluginKey as PMPluginKey,
|
||||
Selection,
|
||||
TextSelection,
|
||||
} from 'prosemirror-state';
|
||||
import { findParentNodeClosestToPos } from 'prosemirror-utils';
|
||||
import { __serializeForClipboard, Decoration, DecorationSet, EditorView } from 'prosemirror-view';
|
||||
function findNodeByUuid(editor: Editor, uuid: string) {
|
||||
let target = null;
|
||||
let pos = -1;
|
||||
|
||||
const DragablePluginKey = new PMPluginKey('dragable');
|
||||
|
||||
export const Dragable = Extension.create({
|
||||
name: 'dragable',
|
||||
|
||||
// @ts-ignore
|
||||
addProseMirrorPlugins() {
|
||||
let editorView: EditorView;
|
||||
let dragHandleDOM: HTMLElement;
|
||||
let activeNode: ActiveNode | null;
|
||||
let activeSelection: Selection | null;
|
||||
let dragging = false;
|
||||
let mouseleaveTimer = null;
|
||||
|
||||
const createDragHandleDOM = () => {
|
||||
const dom = document.createElement('div');
|
||||
dom.className = 'dragable';
|
||||
dom.draggable = true;
|
||||
dom.setAttribute('data-drag-handle', 'true');
|
||||
|
||||
return dom;
|
||||
};
|
||||
|
||||
const showDragHandleDOM = () => {
|
||||
dragHandleDOM?.classList?.add('show');
|
||||
dragHandleDOM?.classList?.remove('hide');
|
||||
};
|
||||
|
||||
const hideDragHandleDOM = () => {
|
||||
dragHandleDOM?.classList?.remove('show');
|
||||
dragHandleDOM?.classList?.remove('active');
|
||||
dragHandleDOM?.classList?.add('hide');
|
||||
};
|
||||
|
||||
const renderDragHandleDOM = (view: EditorView, referenceRectDOM: HTMLElement) => {
|
||||
const root = view.dom.parentElement;
|
||||
|
||||
if (!root) return;
|
||||
|
||||
const targetNodeRect = referenceRectDOM.getBoundingClientRect();
|
||||
const rootRect = root.getBoundingClientRect();
|
||||
const handleRect = dragHandleDOM.getBoundingClientRect();
|
||||
|
||||
let offsetX = -5;
|
||||
|
||||
if (referenceRectDOM.tagName === 'LI') {
|
||||
offsetX = referenceRectDOM.getAttribute('data-checked') ? -3 : -16;
|
||||
if (editor && !editor.isDestroyed) {
|
||||
editor.state.doc.descendants((node, p) => {
|
||||
if (node.attrs.uuid === uuid) {
|
||||
target = node;
|
||||
pos = p;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const left = targetNodeRect.left - rootRect.left - handleRect.width + offsetX;
|
||||
const top = targetNodeRect.top - rootRect.top + handleRect.height / 2 + root.scrollTop;
|
||||
return { node: target, pos };
|
||||
}
|
||||
|
||||
const offsetLeft = 0;
|
||||
export interface GlobalDragHandleOptions {
|
||||
dragHandleWidth: number;
|
||||
scrollTreshold: number;
|
||||
dragHandleSelector?: string;
|
||||
}
|
||||
|
||||
dragHandleDOM.style.left = `${left + offsetLeft}px`;
|
||||
dragHandleDOM.style.top = `${top - 2}px`;
|
||||
function absoluteRect(node: Element) {
|
||||
const data = node.getBoundingClientRect();
|
||||
|
||||
showDragHandleDOM();
|
||||
};
|
||||
return {
|
||||
top: data.top,
|
||||
left: data.left,
|
||||
width: data.width,
|
||||
};
|
||||
}
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (!activeNode) return null;
|
||||
function nodeDOMAtCoords(coords: { x: number; y: number }) {
|
||||
return document
|
||||
.elementsFromPoint(coords.x, coords.y)
|
||||
.find(
|
||||
(elem: Element) =>
|
||||
elem.parentElement?.matches?.('.ProseMirror') ||
|
||||
elem.matches(['li', 'p:not(:first-child)', 'pre', 'blockquote', 'h1, h2, h3, h4, h5, h6'].join(', '))
|
||||
);
|
||||
}
|
||||
|
||||
clearTimeout(mouseleaveTimer);
|
||||
showDragHandleDOM();
|
||||
};
|
||||
function nodePosAtDOM(node: Element, view: EditorView, options: GlobalDragHandleOptions) {
|
||||
const boundingRect = node.getBoundingClientRect();
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (!activeNode) return null;
|
||||
hideDragHandleDOM();
|
||||
};
|
||||
return view.posAtCoords({
|
||||
left: boundingRect.left + 50 + options.dragHandleWidth,
|
||||
top: boundingRect.top + 1,
|
||||
})?.inside;
|
||||
}
|
||||
|
||||
const handleMouseDown = () => {
|
||||
if (!activeNode) return null;
|
||||
function calcNodePos(pos: number, view: EditorView) {
|
||||
const $pos = view.state.doc.resolve(pos);
|
||||
if ($pos.depth > 1) return $pos.before($pos.depth);
|
||||
return pos;
|
||||
}
|
||||
|
||||
if (NodeSelection.isSelectable(activeNode.node)) {
|
||||
const nodeSelection = NodeSelection.create(editorView.state.doc, activeNode.$pos.pos - activeNode.offset);
|
||||
editorView.dispatch(editorView.state.tr.setSelection(nodeSelection));
|
||||
editorView.focus();
|
||||
activeSelection = nodeSelection;
|
||||
return nodeSelection;
|
||||
export function DragHandlePlugin(options: GlobalDragHandleOptions & { pluginKey: string; editor: Editor }) {
|
||||
const editor = options.editor;
|
||||
let listType = '';
|
||||
let dragHandleElement: HTMLElement | null = null;
|
||||
|
||||
let activeNode: { node: Node; pos: number } | null = null;
|
||||
|
||||
const handleDragStart = (event: DragEvent) => {
|
||||
const view = options.editor.view;
|
||||
|
||||
view.focus();
|
||||
|
||||
if (!event.dataTransfer) return;
|
||||
|
||||
const node = nodeDOMAtCoords({
|
||||
x: event.clientX + 50 + options.dragHandleWidth,
|
||||
y: event.clientY,
|
||||
});
|
||||
|
||||
if (!(node instanceof Element)) return;
|
||||
|
||||
if (activeNode && NodeSelection.isSelectable(activeNode.node)) {
|
||||
const selection = NodeSelection.create(view.state.doc, activeNode.pos);
|
||||
view.dispatch(view.state.tr.setSelection(selection));
|
||||
} else {
|
||||
let draggedNodePos = nodePosAtDOM(node, view, options);
|
||||
if (draggedNodePos == null || draggedNodePos < 0) return;
|
||||
draggedNodePos = calcNodePos(draggedNodePos, view);
|
||||
|
||||
const { from, to } = view.state.selection;
|
||||
const diff = from - to;
|
||||
|
||||
const fromSelectionPos = calcNodePos(from, view);
|
||||
let differentNodeSelected = false;
|
||||
|
||||
const nodePos = view.state.doc.resolve(fromSelectionPos);
|
||||
|
||||
// Check if nodePos points to the top level node
|
||||
if (nodePos.node().type.name === 'doc') {
|
||||
differentNodeSelected = true;
|
||||
} else {
|
||||
const nodeSelection = NodeSelection.create(view.state.doc, nodePos.before());
|
||||
|
||||
// Check if the node where the drag event started is part of the current selection
|
||||
differentNodeSelected = !(
|
||||
draggedNodePos + 1 >= nodeSelection.$from.pos && draggedNodePos <= nodeSelection.$to.pos
|
||||
);
|
||||
}
|
||||
let selection = view.state.selection;
|
||||
if (!differentNodeSelected && diff !== 0 && !(view.state.selection instanceof NodeSelection)) {
|
||||
const endSelection = NodeSelection.create(view.state.doc, to - 1);
|
||||
selection = TextSelection.create(view.state.doc, draggedNodePos, endSelection.$to.pos);
|
||||
} else {
|
||||
selection = NodeSelection.create(view.state.doc, draggedNodePos);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (!dragging) return;
|
||||
|
||||
dragging = false;
|
||||
activeSelection = null;
|
||||
activeNode = null;
|
||||
};
|
||||
|
||||
const handleDragStart = (event) => {
|
||||
dragging = true;
|
||||
if (event.dataTransfer && activeSelection) {
|
||||
const slice = activeSelection.content();
|
||||
event.dataTransfer.effectAllowed = 'copyMove';
|
||||
const { dom, text } = __serializeForClipboard(editorView, slice);
|
||||
event.dataTransfer.clearData();
|
||||
event.dataTransfer.setData('text/html', dom.innerHTML);
|
||||
event.dataTransfer.setData('text/plain', text);
|
||||
event.dataTransfer.setDragImage(activeNode?.el as any, 0, 0);
|
||||
|
||||
editorView.dragging = {
|
||||
slice,
|
||||
move: true,
|
||||
};
|
||||
// select complete table instead of just a row
|
||||
if ((selection as NodeSelection).node.type.name === 'tableRow') {
|
||||
const $pos = view.state.doc.resolve(selection.from);
|
||||
selection = NodeSelection.create(view.state.doc, $pos.before());
|
||||
}
|
||||
}
|
||||
};
|
||||
view.dispatch(view.state.tr.setSelection(selection));
|
||||
}
|
||||
|
||||
return [
|
||||
new PMPlugin({
|
||||
key: DragablePluginKey,
|
||||
view: (view) => {
|
||||
if (view.editable) {
|
||||
dragHandleDOM = createDragHandleDOM();
|
||||
dragHandleDOM.addEventListener('mouseenter', handleMouseEnter);
|
||||
dragHandleDOM.addEventListener('mouseleave', handleMouseLeave);
|
||||
dragHandleDOM.addEventListener('mousedown', handleMouseDown);
|
||||
dragHandleDOM.addEventListener('mouseup', handleMouseUp);
|
||||
dragHandleDOM.addEventListener('dragstart', handleDragStart);
|
||||
view.dom.parentNode?.appendChild(dragHandleDOM);
|
||||
view.dom.parentNode.style = 'position: relative;';
|
||||
// If the selected node is a list item, we need to save the type of the wrapping list e.g. OL or UL
|
||||
if (view.state.selection instanceof NodeSelection && view.state.selection.node.type.name === 'listItem') {
|
||||
listType = node.parentElement!.tagName.toUpperCase();
|
||||
}
|
||||
|
||||
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: true };
|
||||
};
|
||||
|
||||
const hideDragHandle = () => {
|
||||
if (dragHandleElement) {
|
||||
dragHandleElement.classList.add('hide');
|
||||
}
|
||||
activeNode = null;
|
||||
};
|
||||
|
||||
const showDragHandle = () => {
|
||||
if (dragHandleElement) {
|
||||
dragHandleElement.classList.remove('hide');
|
||||
}
|
||||
};
|
||||
|
||||
const onDragHandleDrag = (e: DragEvent) => {
|
||||
hideDragHandle();
|
||||
const scrollY = window.scrollY;
|
||||
if (e.clientY < options.scrollTreshold) {
|
||||
window.scrollTo({ top: scrollY - 30, behavior: 'smooth' });
|
||||
} else if (window.innerHeight - e.clientY < options.scrollTreshold) {
|
||||
window.scrollTo({ top: scrollY + 30, behavior: 'smooth' });
|
||||
}
|
||||
};
|
||||
|
||||
const showDragHandleAtDOM = (node: HTMLElement | undefined) => {
|
||||
const notDragging = node?.closest('.not-draggable');
|
||||
|
||||
if (!(node instanceof Element) || node.matches('ul, ol') || notDragging) {
|
||||
hideDragHandle();
|
||||
return;
|
||||
}
|
||||
|
||||
const compStyle = window.getComputedStyle(node);
|
||||
const parsedLineHeight = Number.parseInt(compStyle.lineHeight, 10);
|
||||
const lineHeight = Number.isNaN(parsedLineHeight) ? Number.parseInt(compStyle.fontSize) * 1.2 : parsedLineHeight;
|
||||
const paddingTop = Number.parseInt(compStyle.paddingTop, 10);
|
||||
|
||||
const rect = absoluteRect(node);
|
||||
rect.top += (lineHeight - 24) / 2;
|
||||
rect.top += paddingTop;
|
||||
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 - 12}px`;
|
||||
dragHandleElement.style.top = `${rect.top}px`;
|
||||
showDragHandle();
|
||||
};
|
||||
|
||||
const onEditorUpdate = () => {
|
||||
if (!editor.isEditable) {
|
||||
hideDragHandle();
|
||||
}
|
||||
};
|
||||
|
||||
// const onSelectionUpdate = () => {
|
||||
// if (!editor.isEditable)
|
||||
// return
|
||||
|
||||
// const maybeNode = findParentNodeClosestToPos(editor.state.selection.$anchor, node => !node.isInline)
|
||||
|
||||
// if (!maybeNode)
|
||||
// return
|
||||
|
||||
// const maybeDOM = findDomRefAtPos(maybeNode.pos, editor.view.domAtPos.bind(editor.view))
|
||||
|
||||
// if (maybeDOM) {
|
||||
// showDragHandleAtDOM(maybeDOM as HTMLElement)
|
||||
// activeNode = { node: maybeNode.node, pos: maybeNode.pos }
|
||||
// }
|
||||
// }
|
||||
|
||||
const callShowDraggableMenu = () => {
|
||||
if (!dragHandleElement) return;
|
||||
showDragHandle();
|
||||
};
|
||||
|
||||
return new Plugin({
|
||||
key: new PluginKey(options.pluginKey),
|
||||
view: (view) => {
|
||||
dragHandleElement = document.createElement('div');
|
||||
dragHandleElement.draggable = true;
|
||||
dragHandleElement.dataset.dragHandle = '';
|
||||
dragHandleElement.classList.add('drag-handle');
|
||||
dragHandleElement.addEventListener('dragstart', handleDragStart);
|
||||
dragHandleElement.addEventListener('drag', onDragHandleDrag);
|
||||
dragHandleElement.addEventListener('mousedown', callShowDraggableMenu);
|
||||
dragHandleElement.addEventListener('mouseout', hideDragHandle);
|
||||
|
||||
hideDragHandle();
|
||||
view?.dom?.parentElement?.appendChild(dragHandleElement);
|
||||
// editor.on('selectionUpdate', onSelectionUpdate)
|
||||
editor.on('update', onEditorUpdate);
|
||||
|
||||
return {
|
||||
destroy: () => {
|
||||
dragHandleElement?.removeEventListener('drag', onDragHandleDrag);
|
||||
dragHandleElement?.removeEventListener('dragstart', handleDragStart);
|
||||
dragHandleElement?.removeEventListener('mousedown', callShowDraggableMenu);
|
||||
dragHandleElement?.remove?.();
|
||||
dragHandleElement = null;
|
||||
// editor.off('selectionUpdate', onSelectionUpdate)
|
||||
editor.off('update', onEditorUpdate);
|
||||
},
|
||||
};
|
||||
},
|
||||
props: {
|
||||
handleDOMEvents: {
|
||||
mousemove: (view, event) => {
|
||||
if (!view.editable) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
update(view) {
|
||||
editorView = view;
|
||||
},
|
||||
destroy: () => {
|
||||
if (!dragHandleDOM) return;
|
||||
|
||||
clearTimeout(mouseleaveTimer);
|
||||
ReactDOM.unmountComponentAtNode(dragHandleDOM);
|
||||
dragHandleDOM.removeEventListener('mouseenter', handleMouseEnter);
|
||||
dragHandleDOM.removeEventListener('mouseleave', handleMouseLeave);
|
||||
dragHandleDOM.removeEventListener('mousedown', handleMouseDown);
|
||||
dragHandleDOM.removeEventListener('mouseup', handleMouseUp);
|
||||
dragHandleDOM.removeEventListener('dragstart', handleDragStart);
|
||||
dragHandleDOM.remove();
|
||||
},
|
||||
const coords = {
|
||||
x: event.clientX + 50 + options.dragHandleWidth,
|
||||
y: event.clientY,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// @ts-ignore
|
||||
handleDOMEvents: {
|
||||
drop: (view, event: DragEvent) => {
|
||||
if (!view.editable || !dragHandleDOM) return false;
|
||||
if (!activeSelection) return false;
|
||||
const maybePos = editor.view.posAtCoords({ left: coords.x, top: coords.y });
|
||||
let dom = nodeDOMAtCoords(coords) as HTMLElement | null;
|
||||
|
||||
const eventPos = view.posAtCoords({
|
||||
left: event.clientX,
|
||||
top: event.clientY,
|
||||
});
|
||||
while (dom && dom.nodeType === 3) {
|
||||
dom = dom.parentElement;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (activeSelection) {
|
||||
[
|
||||
'ProseMirror-selectednode',
|
||||
'ProseMirror-selectedblocknode-dragable',
|
||||
'ProseMirror-selectedblocknode-normal',
|
||||
].forEach((cls) => {
|
||||
(view.dom as HTMLElement).querySelectorAll(`.${cls}`).forEach((dom) => dom.classList.remove(cls));
|
||||
});
|
||||
const noneSelection = new TextSelection(
|
||||
view.state.doc.resolve(safePos(view.state, eventPos?.pos ?? 0))
|
||||
);
|
||||
view.dispatch(view.state.tr.setSelection(noneSelection));
|
||||
this.editor.commands.blur();
|
||||
if (dom?.closest(`[data-node-view-wrapper]`)) {
|
||||
dom = dom?.closest(`[data-node-view-wrapper]`);
|
||||
}
|
||||
|
||||
activeSelection = null;
|
||||
activeNode = null;
|
||||
}
|
||||
}, 100);
|
||||
if (dom?.closest(`.columns`)) {
|
||||
dom = dom?.closest(`.columns`);
|
||||
}
|
||||
|
||||
if (!eventPos) {
|
||||
return true;
|
||||
}
|
||||
if (dom?.closest(`table`)) {
|
||||
dom = dom?.closest(`table`);
|
||||
}
|
||||
|
||||
const maybeTitle = findParentNodeClosestToPos(
|
||||
view.state.doc.resolve(safePos(this.editor.state, eventPos.pos)),
|
||||
(node) => node.type.name === 'title'
|
||||
);
|
||||
if (!dom) {
|
||||
hideDragHandle();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 不允许在 title 处放置
|
||||
if (eventPos.pos === 0 || maybeTitle) {
|
||||
return true;
|
||||
}
|
||||
let maybeNode;
|
||||
|
||||
if (dragging) {
|
||||
const tr = removePossibleTable(view, event);
|
||||
|
||||
dragging = false;
|
||||
|
||||
if (tr) {
|
||||
view.dispatch(tr);
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (dom.getAttribute('data-uuid')) {
|
||||
maybeNode = findNodeByUuid(editor, dom.getAttribute('data-uuid')!);
|
||||
}
|
||||
|
||||
if (!maybeNode) {
|
||||
const p = editor.view.posAtDOM(dom, 1);
|
||||
if (!p || p < 0) {
|
||||
hideDragHandle();
|
||||
return false;
|
||||
},
|
||||
mousemove: (view, event) => {
|
||||
if (!view.editable || !dragHandleDOM) return false;
|
||||
}
|
||||
const maybeAfter = editor.state.doc.resolve(p)?.nodeBefore;
|
||||
|
||||
const coords = { left: event.clientX, top: event.clientY };
|
||||
const pos = view.posAtCoords(coords);
|
||||
|
||||
if (!pos || !pos.pos) return false;
|
||||
|
||||
let dom = view.nodeDOM(pos.pos) || view.domAtPos(pos.pos)?.node || event.target;
|
||||
|
||||
const maybeTaskItemOrListItem = findParentNodeClosestToPos(view.state.doc.resolve(pos.pos), (node) =>
|
||||
['taskItem', 'listItem'].some((name) => name === node.type.name)
|
||||
);
|
||||
|
||||
if (!dom) {
|
||||
if (dragging) return false;
|
||||
hideDragHandleDOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
while (dom && dom.nodeType === 3) {
|
||||
dom = dom.parentElement;
|
||||
}
|
||||
|
||||
// 选中列表项
|
||||
if (maybeTaskItemOrListItem) {
|
||||
while (dom && dom.tagName !== 'LI') {
|
||||
dom = dom.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
if (dom.tagName === 'LI') {
|
||||
if (dom?.parentElement?.childElementCount === 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 不允许选中整个列表
|
||||
if (dom.tagName === 'UL' || dom.tagName === 'OL') {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
let maybeReactRenderer: HTMLElement | null = dom;
|
||||
|
||||
while (maybeReactRenderer && !maybeReactRenderer.classList?.contains('react-renderer')) {
|
||||
maybeReactRenderer = maybeReactRenderer.parentElement;
|
||||
}
|
||||
|
||||
if (maybeReactRenderer && !maybeReactRenderer?.classList?.contains('node-columns')) {
|
||||
dom = maybeReactRenderer;
|
||||
}
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
|
||||
if (!(dom instanceof Element)) {
|
||||
if (dragging) return false;
|
||||
hideDragHandleDOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = selectAncestorNodeByDom(dom, view);
|
||||
|
||||
if (
|
||||
!result ||
|
||||
result.node.type.name === 'doc' ||
|
||||
result.node.type.name === 'title' ||
|
||||
result.node.type.name === 'tableOfContents'
|
||||
) {
|
||||
if (dragging) return false;
|
||||
hideDragHandleDOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
activeNode = result;
|
||||
renderDragHandleDOM(view, result.el);
|
||||
return false;
|
||||
},
|
||||
keydown: () => {
|
||||
if (!editorView.editable || !dragHandleDOM) return false;
|
||||
hideDragHandleDOM();
|
||||
return false;
|
||||
},
|
||||
mouseleave: () => {
|
||||
clearTimeout(mouseleaveTimer);
|
||||
mouseleaveTimer = setTimeout(() => {
|
||||
hideDragHandleDOM();
|
||||
}, 400);
|
||||
return false;
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
new PMPlugin({
|
||||
key: new PMPluginKey('AncestorDragablePluginFocusKey'),
|
||||
props: {
|
||||
decorations(state) {
|
||||
const usingActiveSelection = !!activeSelection;
|
||||
const selection = state.selection;
|
||||
|
||||
if (selection instanceof NodeSelection) {
|
||||
const { from, to } = selection;
|
||||
|
||||
return DecorationSet.create(state.doc, [
|
||||
Decoration.node(safePos(state, from), safePos(state, to), {
|
||||
class: usingActiveSelection
|
||||
? 'ProseMirror-selectedblocknode-dragable'
|
||||
: 'ProseMirror-selectedblocknode-normal',
|
||||
}),
|
||||
]);
|
||||
if (maybeAfter && !maybeAfter.isInline) {
|
||||
maybeNode = { node: maybeAfter, pos: p };
|
||||
}
|
||||
|
||||
return DecorationSet.empty;
|
||||
},
|
||||
if (!maybeNode && maybePos?.pos) {
|
||||
maybeNode = findParentNodeClosestToPos(editor.state.doc.resolve(maybePos?.pos), (node) => {
|
||||
return !node.isInline;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!maybeNode) return;
|
||||
|
||||
activeNode = { node: maybeNode.node!, pos: maybeNode.pos };
|
||||
showDragHandleAtDOM(dom as HTMLElement);
|
||||
},
|
||||
}),
|
||||
];
|
||||
keydown: () => {
|
||||
hideDragHandle();
|
||||
},
|
||||
mousewheel: () => {
|
||||
hideDragHandle();
|
||||
},
|
||||
dragstart: (view) => {
|
||||
view.dom.classList.add('dragging');
|
||||
},
|
||||
drop: (view, event) => {
|
||||
view.dom.classList.remove('dragging');
|
||||
hideDragHandle();
|
||||
let droppedNode: Node | null = null;
|
||||
const dropPos = view.posAtCoords({
|
||||
left: event.clientX,
|
||||
top: event.clientY,
|
||||
});
|
||||
|
||||
if (!dropPos) return;
|
||||
|
||||
if (view.state.selection instanceof NodeSelection) {
|
||||
droppedNode = view.state.selection.node;
|
||||
}
|
||||
|
||||
if (!droppedNode) return;
|
||||
|
||||
const resolvedPos = view.state.doc.resolve(dropPos.pos);
|
||||
const isDroppedInsideList = resolvedPos.parent.type.name === 'listItem';
|
||||
|
||||
if (
|
||||
view.state.selection instanceof NodeSelection &&
|
||||
view.state.selection.node.type.name === 'listItem' &&
|
||||
!isDroppedInsideList &&
|
||||
listType === 'OL'
|
||||
) {
|
||||
const text = droppedNode.textContent;
|
||||
if (!text) return;
|
||||
const paragraph = view.state.schema.nodes.paragraph?.createAndFill({}, view.state.schema.text(text));
|
||||
const listItem = view.state.schema.nodes.listItem?.createAndFill({}, paragraph);
|
||||
const newList = view.state.schema.nodes.orderedList?.createAndFill(null, listItem);
|
||||
const slice = new Slice(Fragment.from(newList), 0, 0);
|
||||
view.dragging = { slice, move: event.ctrlKey };
|
||||
}
|
||||
},
|
||||
dragend: (view) => {
|
||||
view.dom.classList.remove('dragging');
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const Dragable = Extension.create({
|
||||
name: 'drag',
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
dragHandleWidth: 20,
|
||||
scrollTreshold: 100,
|
||||
dragHandleSelector: 'drag-handle',
|
||||
};
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
this.editor.isEditable &&
|
||||
DragHandlePlugin({
|
||||
editor: this.editor,
|
||||
pluginKey: 'drag-handle',
|
||||
dragHandleWidth: this.options.dragHandleWidth,
|
||||
scrollTreshold: this.options.scrollTreshold,
|
||||
dragHandleSelector: this.options.dragHandleSelector,
|
||||
}),
|
||||
].filter(Boolean) as Plugin[];
|
||||
},
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ const reuploadImageAndUpdateSrc = async (img: HTMLImageElement) => {
|
||||
}
|
||||
|
||||
const blob = await resp.blob();
|
||||
const url = await uploadFile?.(blob);
|
||||
const url = await uploadFile?.(blob as File);
|
||||
|
||||
img.src = url;
|
||||
return true;
|
||||
@@ -150,87 +150,87 @@ export const Paste = Extension.create<IPasteOptions>({
|
||||
new Plugin({
|
||||
key: new PluginKey('paste'),
|
||||
props: {
|
||||
handlePaste: (view, event: ClipboardEvent) => {
|
||||
if (view.props.editable && !view.props.editable(view.state)) {
|
||||
return false;
|
||||
}
|
||||
// handlePaste: (view, event: ClipboardEvent) => {
|
||||
// if (view.props.editable && !view.props.editable(view.state)) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if (!event.clipboardData) return false;
|
||||
// if (!event.clipboardData) return false;
|
||||
|
||||
const files = Array.from(event.clipboardData.files);
|
||||
const text = event.clipboardData.getData('text/plain');
|
||||
const html = event.clipboardData.getData('text/html');
|
||||
const vscode = event.clipboardData.getData('vscode-editor-data');
|
||||
const node = event.clipboardData.getData('text/node');
|
||||
const markdownText = event.clipboardData.getData('text/markdown');
|
||||
const { state, dispatch } = view;
|
||||
// const files = Array.from(event.clipboardData.files);
|
||||
// const text = event.clipboardData.getData('text/plain');
|
||||
// const html = event.clipboardData.getData('text/html');
|
||||
// const vscode = event.clipboardData.getData('vscode-editor-data');
|
||||
// const node = event.clipboardData.getData('text/node');
|
||||
// const markdownText = event.clipboardData.getData('text/markdown');
|
||||
// const { state, dispatch } = view;
|
||||
|
||||
debug(() => {
|
||||
console.group('paste');
|
||||
console.log({ text, vscode, node, markdownText, files });
|
||||
console.log(html);
|
||||
console.groupEnd();
|
||||
});
|
||||
// debug(() => {
|
||||
// console.group('paste');
|
||||
// console.log({ text, vscode, node, markdownText, files });
|
||||
// console.log(html);
|
||||
// console.groupEnd();
|
||||
// });
|
||||
|
||||
// 直接复制节点
|
||||
if (node) {
|
||||
const json = safeJSONParse(node);
|
||||
const tr = view.state.tr;
|
||||
const selection = tr.selection;
|
||||
view.dispatch(tr.insert(selection.from - 1, view.state.schema.nodeFromJSON(json)).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
// // 直接复制节点
|
||||
// if (node) {
|
||||
// const json = safeJSONParse(node);
|
||||
// const tr = view.state.tr;
|
||||
// const selection = tr.selection;
|
||||
// view.dispatch(tr.insert(selection.from - 1, view.state.schema.nodeFromJSON(json)).scrollIntoView());
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// 链接
|
||||
if (isValidURL(text)) {
|
||||
if (!state.selection.empty) {
|
||||
toggleMark(this.editor.schema.marks.link, { href: text })(state, dispatch);
|
||||
return true;
|
||||
}
|
||||
// // 链接
|
||||
// if (isValidURL(text)) {
|
||||
// if (!state.selection.empty) {
|
||||
// toggleMark(this.editor.schema.marks.link, { href: text })(state, dispatch);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
const transaction = view.state.tr
|
||||
.insertText(text, state.selection.from, state.selection.to)
|
||||
.addMark(
|
||||
state.selection.from,
|
||||
state.selection.to + text.length,
|
||||
state.schema.marks.link.create({ href: text })
|
||||
);
|
||||
view.dispatch(transaction);
|
||||
return true;
|
||||
}
|
||||
// const transaction = view.state.tr
|
||||
// .insertText(text, state.selection.from, state.selection.to)
|
||||
// .addMark(
|
||||
// state.selection.from,
|
||||
// state.selection.to + text.length,
|
||||
// state.schema.marks.link.create({ href: text })
|
||||
// );
|
||||
// view.dispatch(transaction);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// 粘贴代码
|
||||
if (isInCode(view.state)) {
|
||||
event.preventDefault();
|
||||
view.dispatch(view.state.tr.insertText(text).scrollIntoView());
|
||||
return true;
|
||||
}
|
||||
// // 粘贴代码
|
||||
// if (isInCode(view.state)) {
|
||||
// event.preventDefault();
|
||||
// view.dispatch(view.state.tr.insertText(text).scrollIntoView());
|
||||
// return true;
|
||||
// }
|
||||
|
||||
const vscodeMeta = vscode ? JSON.parse(vscode) : undefined;
|
||||
const pasteCodeLanguage = vscodeMeta?.mode;
|
||||
// const vscodeMeta = vscode ? JSON.parse(vscode) : undefined;
|
||||
// const pasteCodeLanguage = vscodeMeta?.mode;
|
||||
|
||||
if (html.length > 0) {
|
||||
return htmlToProsemirror(editor, html);
|
||||
}
|
||||
// if (html.length > 0) {
|
||||
// return htmlToProsemirror(editor, html);
|
||||
// }
|
||||
|
||||
const { markdownToHTML } = extensionThis.options;
|
||||
// const { markdownToHTML } = extensionThis.options;
|
||||
|
||||
if ((markdownText || isMarkdown(text)) && markdownToHTML) {
|
||||
event.preventDefault();
|
||||
const html = markdownToHTML(normalizeMarkdown(markdownText || text));
|
||||
if (html && html.length) return htmlToProsemirror(editor, html, true);
|
||||
}
|
||||
// if ((markdownText || isMarkdown(text)) && markdownToHTML) {
|
||||
// event.preventDefault();
|
||||
// const html = markdownToHTML(normalizeMarkdown(markdownText || text));
|
||||
// if (html && html.length) return htmlToProsemirror(editor, html, true);
|
||||
// }
|
||||
|
||||
if (files.length) {
|
||||
event.preventDefault();
|
||||
files.forEach((file) => {
|
||||
handleFileEvent({ editor, file });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
// if (files.length) {
|
||||
// event.preventDefault();
|
||||
// files.forEach((file) => {
|
||||
// handleFileEvent({ editor, file });
|
||||
// });
|
||||
// return true;
|
||||
// }
|
||||
|
||||
return false;
|
||||
},
|
||||
// return false;
|
||||
// },
|
||||
handleDrop: (view, event: any) => {
|
||||
if (view.props.editable && !view.props.editable(view.state)) {
|
||||
return false;
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
[data-drag-handle] {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
display: inline;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: move;
|
||||
background-color: #fff;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-out;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='16' height='16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='3' y='1' width='3' height='3' rx='1.5' fill='%23111'/%3E%3Crect x='10' y='1' width='3' height='3' rx='1.5' fill='%23111'/%3E%3Crect x='3' y='6' width='3' height='3' rx='1.5' fill='%23111'/%3E%3Crect x='10' y='6' width='3' height='3' rx='1.5' fill='%23111'/%3E%3Crect x='3' y='11' width='3' height='3' rx='1.5' fill='%23111'/%3E%3Crect x='10' y='11' width='3' height='3' rx='1.5' fill='%23111'/%3E%3C/svg%3E");
|
||||
background-size: contain;
|
||||
background-position: center 0;
|
||||
.drag-handle {
|
||||
position: fixed;
|
||||
z-index: 50;
|
||||
width: 1.4rem;
|
||||
height: 1.4rem;
|
||||
cursor: grab;
|
||||
border: 1px solid var(--yunce-border-color);
|
||||
border-radius: var(--yunce-border-radius);
|
||||
opacity: 1;
|
||||
transition: opacity ease-in 0.2s;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(0, 0, 0, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E");
|
||||
background-size: calc(0.5em + 0.375rem) calc(0.5em + 0.375rem);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
|
||||
&.show {
|
||||
opacity: 0.3;
|
||||
&:hover {
|
||||
background-color: var(--yunce-hover-bg-color);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
background-color: var(--yunce-hover-bg-color);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
&.hide {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
/* 自定义节点选中 */
|
||||
--node-selected-border-color: rgb(0 101 255);
|
||||
--yunce-border-color: #dee0e3;
|
||||
--yunce-border-radius: 2px;
|
||||
}
|
||||
|
||||
body[theme-mode='dark'] {
|
||||
|
||||
Reference in New Issue
Block a user