mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37cabc5440 | |||
| 0106d7152c | |||
| 28c81d3a47 | |||
| 857878701a | |||
| cca4e256e0 |
@@ -0,0 +1,172 @@
|
||||
import { NodeSelection, Plugin } from "prosemirror-state";
|
||||
import { EditorView, __serializeForClipboard } from "prosemirror-view";
|
||||
import Extension from "@shared/editor/lib/Extension";
|
||||
import {
|
||||
absoluteRect,
|
||||
nodeDOMAtCoords,
|
||||
nodePosAtDOM,
|
||||
} from "@shared/editor/lib/position";
|
||||
|
||||
export type DragHandleOptions = {
|
||||
/**
|
||||
* The width of the drag handle
|
||||
*/
|
||||
dragHandleWidth: number;
|
||||
};
|
||||
|
||||
function DragHandle(options: DragHandleOptions) {
|
||||
let dragHandleElement: HTMLElement | null = null;
|
||||
|
||||
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 === undefined || nodePos < 0) {
|
||||
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))
|
||||
);
|
||||
}
|
||||
|
||||
function hideDragHandle() {
|
||||
if (dragHandleElement) {
|
||||
dragHandleElement.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function showDragHandle() {
|
||||
if (dragHandleElement) {
|
||||
dragHandleElement.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
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 style = window.getComputedStyle(node);
|
||||
const lineHeight = parseInt(style.lineHeight, 10);
|
||||
const paddingTop = parseInt(style.paddingTop, 10);
|
||||
const rect = absoluteRect(node);
|
||||
|
||||
rect.top += (lineHeight - 24) / 2;
|
||||
rect.top += paddingTop;
|
||||
if (node.matches("ul:not(.checkbox_list) 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,
|
||||
dragstart: (view) => {
|
||||
view.dom.classList.add("dragging");
|
||||
},
|
||||
drop: (view) => {
|
||||
view.dom.classList.remove("dragging");
|
||||
},
|
||||
dragend: (view) => {
|
||||
view.dom.classList.remove("dragging");
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default class DragAndDrop extends Extension {
|
||||
get plugins() {
|
||||
return [DragHandle({ dragHandleWidth: 24 })];
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import Editor, { Props as EditorProps } from "~/components/Editor";
|
||||
import Flex from "~/components/Flex";
|
||||
import BlockMenuExtension from "~/editor/extensions/BlockMenu";
|
||||
import ClipboardTextSerializer from "~/editor/extensions/ClipboardTextSerializer";
|
||||
import DragAndDrop from "~/editor/extensions/DragAndDrop";
|
||||
import EmojiMenuExtension from "~/editor/extensions/EmojiMenu";
|
||||
import FindAndReplaceExtension from "~/editor/extensions/FindAndReplace";
|
||||
import HoverPreviewsExtension from "~/editor/extensions/HoverPreviews";
|
||||
@@ -45,6 +46,7 @@ const extensions = [
|
||||
MentionMenuExtension,
|
||||
FindAndReplaceExtension,
|
||||
HoverPreviewsExtension,
|
||||
DragAndDrop,
|
||||
// Order these default key handlers last
|
||||
PreventTab,
|
||||
Keys,
|
||||
|
||||
Vendored
+8
@@ -11,4 +11,12 @@ declare module "prosemirror-view" {
|
||||
plainText: boolean,
|
||||
$context: ResolvedPos
|
||||
);
|
||||
|
||||
export function __serializeForClipboard(
|
||||
view: EditorView,
|
||||
slice: Slice
|
||||
): {
|
||||
dom: Element;
|
||||
text: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable no-irregular-whitespace */
|
||||
import { lighten, transparentize } from "polished";
|
||||
import styled, { DefaultTheme, css, keyframes } from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import { EditorStyleHelper } from "../styles/EditorStyleHelper";
|
||||
import { videoStyle } from "./Video";
|
||||
|
||||
@@ -257,6 +258,65 @@ const findAndReplaceStyle = () => css`
|
||||
}
|
||||
`;
|
||||
|
||||
const dragHandleStyle = (props: Props) => css`
|
||||
.drag-handle,
|
||||
.block-menu-trigger {
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
opacity: 1;
|
||||
transition: opacity ease-in 100ms;
|
||||
border-radius: 4px;
|
||||
color: ${props.theme.textSecondary};
|
||||
background-color: ${props.theme.background};
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJjdXJyZW50Q29sb3IiPgo8cmVjdCB4PSI4IiB5PSI3IiB3aWR0aD0iMyIgaGVpZ2h0PSIyIiByeD0iMSIvPgo8cmVjdCB4PSI4IiB5PSIxMSIgd2lkdGg9IjMiIGhlaWdodD0iMiIgcng9IjEiLz4KPHJlY3QgeD0iOCIgeT0iMTUiIHdpZHRoPSIzIiBoZWlnaHQ9IjIiIHJ4PSIxIi8+CjxyZWN0IHg9IjEzIiB5PSI3IiB3aWR0aD0iMyIgaGVpZ2h0PSIyIiByeD0iMSIvPgo8cmVjdCB4PSIxMyIgeT0iMTEiIHdpZHRoPSIzIiBoZWlnaHQ9IjIiIHJ4PSIxIi8+CjxyZWN0IHg9IjEzIiB5PSIxNSIgd2lkdGg9IjMiIGhlaWdodD0iMiIgcng9IjEiLz4KPC9zdmc+);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
z-index: 50;
|
||||
cursor: grab;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: ${props.theme.text};
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
display: none;
|
||||
}
|
||||
|
||||
${breakpoint("tablet")`
|
||||
display: block;
|
||||
pointer-events: auto;
|
||||
`}
|
||||
}
|
||||
|
||||
.block-menu-trigger {
|
||||
background: none;
|
||||
outline: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: var(--pointer);
|
||||
background: ${props.theme.background};
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: ${props.theme.text};
|
||||
background: ${props.theme.secondaryBackground};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const emailStyle = (props: Props) => css`
|
||||
.attachment {
|
||||
display: block;
|
||||
@@ -613,9 +673,16 @@ iframe.embed {
|
||||
caret-color: transparent;
|
||||
}
|
||||
|
||||
.ProseMirror-selectednode {
|
||||
outline: 2px solid
|
||||
${props.readOnly ? "transparent" : props.theme.selected};
|
||||
.ProseMirror:not(.dragging) .ProseMirror-selectednode {
|
||||
outline: 4px solid ${
|
||||
props.readOnly ? "transparent" : transparentize(0.9, props.theme.accent)
|
||||
};
|
||||
border-radius: 4px;
|
||||
background-color: ${
|
||||
props.readOnly ? "transparent" : transparentize(0.9, props.theme.accent)
|
||||
};
|
||||
transition: background-color 100ms;
|
||||
box-shadow: none;
|
||||
|
||||
@media print {
|
||||
outline: none;
|
||||
@@ -628,17 +695,6 @@ li.ProseMirror-selectednode {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
li.ProseMirror-selectednode:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: ${props.rtl ? "-2px" : "-32px"};
|
||||
right: ${props.rtl ? "-32px" : "-2px"};
|
||||
top: -2px;
|
||||
bottom: -2px;
|
||||
border: 2px solid ${props.theme.selected};
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
img.ProseMirror-separator {
|
||||
display: inline;
|
||||
border: none !important;
|
||||
@@ -772,7 +828,7 @@ h6:not(.placeholder):before {
|
||||
opacity: 0;
|
||||
user-select: none;
|
||||
background: ${props.theme.background};
|
||||
margin-${props.rtl ? "right" : "left"}: -26px;
|
||||
margin: 0 4px;
|
||||
flex-direction: ${props.rtl ? "row-reverse" : "row"};
|
||||
display: none;
|
||||
position: relative;
|
||||
@@ -1021,8 +1077,8 @@ a:hover {
|
||||
|
||||
ul,
|
||||
ol {
|
||||
margin: ${props.rtl ? "0 -26px 0 0.1em" : "0 0.1em 0 -26px"};
|
||||
padding: ${props.rtl ? "0 48px 0 0" : "0 0 0 48px"};
|
||||
margin: 0;
|
||||
padding: ${props.rtl ? "0 22px 0 0" : "0 0 0 22px"};
|
||||
}
|
||||
|
||||
ol ol {
|
||||
@@ -1035,8 +1091,7 @@ ol ol ol {
|
||||
|
||||
ul.checkbox_list {
|
||||
padding: 0;
|
||||
margin-left: ${props.rtl ? "0" : "-24px"};
|
||||
margin-right: ${props.rtl ? "-24px" : "0"};
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul li,
|
||||
@@ -1056,52 +1111,12 @@ ol li {
|
||||
ul.checkbox_list > li {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding-${props.rtl ? "right" : "left"}: 24px;
|
||||
}
|
||||
|
||||
ul.checkbox_list > li.checked > div > p {
|
||||
color: ${props.theme.textTertiary};
|
||||
}
|
||||
|
||||
ul li::before,
|
||||
ol li::before {
|
||||
background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3QgeD0iOCIgeT0iNyIgd2lkdGg9IjMiIGhlaWdodD0iMiIgcng9IjEiIGZpbGw9IiM0RTVDNkUiLz4KPHJlY3QgeD0iOCIgeT0iMTEiIHdpZHRoPSIzIiBoZWlnaHQ9IjIiIHJ4PSIxIiBmaWxsPSIjNEU1QzZFIi8+CjxyZWN0IHg9IjgiIHk9IjE1IiB3aWR0aD0iMyIgaGVpZ2h0PSIyIiByeD0iMSIgZmlsbD0iIzRFNUM2RSIvPgo8cmVjdCB4PSIxMyIgeT0iNyIgd2lkdGg9IjMiIGhlaWdodD0iMiIgcng9IjEiIGZpbGw9IiM0RTVDNkUiLz4KPHJlY3QgeD0iMTMiIHk9IjExIiB3aWR0aD0iMyIgaGVpZ2h0PSIyIiByeD0iMSIgZmlsbD0iIzRFNUM2RSIvPgo8cmVjdCB4PSIxMyIgeT0iMTUiIHdpZHRoPSIzIiBoZWlnaHQ9IjIiIHJ4PSIxIiBmaWxsPSIjNEU1QzZFIi8+Cjwvc3ZnPgo=") no-repeat;
|
||||
background-position: 0 2px;
|
||||
content: "";
|
||||
display: ${props.readOnly ? "none" : "inline-block"};
|
||||
cursor: grab;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
position: absolute;
|
||||
${props.rtl ? "right" : "left"}: -40px;
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease-in-out;
|
||||
}
|
||||
|
||||
ul li[draggable=true]::before,
|
||||
ol li[draggable=true]::before {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
ul > li.counter-2::before,
|
||||
ol li.counter-2::before {
|
||||
${props.rtl ? "right" : "left"}: -50px;
|
||||
}
|
||||
|
||||
ul > li.hovering::before,
|
||||
ol li.hovering::before {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
ul li.ProseMirror-selectednode::after,
|
||||
ol li.ProseMirror-selectednode::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul.checkbox_list > li::before {
|
||||
${props.rtl ? "right" : "left"}: 0;
|
||||
}
|
||||
|
||||
ul.checkbox_list li .checkbox {
|
||||
display: inline-block;
|
||||
cursor: var(--pointer);
|
||||
@@ -1664,40 +1679,6 @@ table {
|
||||
border-right: ${EditorStyleHelper.padding}px solid ${props.theme.background};
|
||||
}
|
||||
|
||||
.block-menu-trigger {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
display: ${props.readOnly ? "none" : "inline"};
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: ${props.theme.textSecondary};
|
||||
background: none;
|
||||
position: absolute;
|
||||
transition: color 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||
opacity 150ms ease-in-out;
|
||||
outline: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin-top: 1px;
|
||||
margin-${props.rtl ? "right" : "left"}: -28px;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
cursor: var(--pointer);
|
||||
color: ${props.theme.text};
|
||||
background: ${props.theme.secondaryBackground};
|
||||
}
|
||||
}
|
||||
|
||||
.ProseMirror[contenteditable="true"]:focus-within,
|
||||
.ProseMirror-focused .block-menu-trigger,
|
||||
.block-menu-trigger:active,
|
||||
.block-menu-trigger:focus {
|
||||
opacity: 1;
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
.ProseMirror-gapcursor {
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
@@ -1755,6 +1736,7 @@ del[data-operation-index] {
|
||||
.placeholder:before,
|
||||
.block-menu-trigger,
|
||||
.heading-actions,
|
||||
.drag-handle,
|
||||
button.show-source-button,
|
||||
h1:not(.placeholder):before,
|
||||
h2:not(.placeholder):before,
|
||||
@@ -1796,6 +1778,7 @@ const EditorContainer = styled.div<Props>`
|
||||
${codeMarkCursor}
|
||||
${codeBlockStyle}
|
||||
${findAndReplaceStyle}
|
||||
${dragHandleStyle}
|
||||
${emailStyle}
|
||||
`;
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { EditorView } from "prosemirror-view";
|
||||
|
||||
export function absoluteRect(node: Element) {
|
||||
const data = node.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
top: data.top,
|
||||
left: data.left,
|
||||
width: data.width,
|
||||
};
|
||||
}
|
||||
|
||||
export 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(", ")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function nodePosAtDOM(node: Element, view: EditorView) {
|
||||
const boundingRect = node.getBoundingClientRect();
|
||||
|
||||
return view.posAtCoords({
|
||||
left: boundingRect.left + 1,
|
||||
top: boundingRect.top + 1,
|
||||
})?.inside;
|
||||
}
|
||||
@@ -77,6 +77,14 @@ export default class Heading extends Node {
|
||||
|
||||
return [
|
||||
`h${node.attrs.level + (this.options.offset || 0)}`,
|
||||
|
||||
[
|
||||
"span",
|
||||
{
|
||||
class: "heading-content",
|
||||
},
|
||||
0,
|
||||
],
|
||||
[
|
||||
"span",
|
||||
{
|
||||
@@ -87,13 +95,6 @@ export default class Heading extends Node {
|
||||
},
|
||||
...(anchor ? [anchor, fold] : []),
|
||||
],
|
||||
[
|
||||
"span",
|
||||
{
|
||||
class: "heading-content",
|
||||
},
|
||||
0,
|
||||
],
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,19 +4,10 @@ import {
|
||||
sinkListItem,
|
||||
liftListItem,
|
||||
} from "prosemirror-schema-list";
|
||||
import {
|
||||
Transaction,
|
||||
EditorState,
|
||||
Plugin,
|
||||
TextSelection,
|
||||
Command,
|
||||
} from "prosemirror-state";
|
||||
import { DecorationSet, Decoration } from "prosemirror-view";
|
||||
import { TextSelection, Command } from "prosemirror-state";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import { findParentNodeClosestToPos } from "../queries/findParentNode";
|
||||
import getParentListItem from "../queries/getParentListItem";
|
||||
import isInList from "../queries/isInList";
|
||||
import isList from "../queries/isList";
|
||||
import Node from "./Node";
|
||||
|
||||
export default class ListItem extends Node {
|
||||
@@ -34,165 +25,6 @@ export default class ListItem extends Node {
|
||||
};
|
||||
}
|
||||
|
||||
get plugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
state: {
|
||||
init() {
|
||||
return DecorationSet.empty;
|
||||
},
|
||||
apply: (
|
||||
tr: Transaction,
|
||||
set: DecorationSet,
|
||||
oldState: EditorState,
|
||||
newState: EditorState
|
||||
) => {
|
||||
const action = tr.getMeta("li");
|
||||
if (!action && !tr.docChanged) {
|
||||
return set;
|
||||
}
|
||||
|
||||
// Adjust decoration positions to changes made by the transaction
|
||||
set = set.map(tr.mapping, tr.doc);
|
||||
|
||||
switch (action?.event) {
|
||||
case "mouseover": {
|
||||
const result = findParentNodeClosestToPos(
|
||||
newState.doc.resolve(action.pos),
|
||||
(node) =>
|
||||
node.type.name === this.name ||
|
||||
node.type.name === "checkbox_item"
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
return set;
|
||||
}
|
||||
|
||||
const list = findParentNodeClosestToPos(
|
||||
newState.doc.resolve(action.pos),
|
||||
(node) => isList(node, this.editor.schema)
|
||||
);
|
||||
|
||||
if (!list) {
|
||||
return set;
|
||||
}
|
||||
|
||||
const start = list.node.attrs.order || 1;
|
||||
|
||||
let listItemNumber = 0;
|
||||
list.node.content.forEach((li, _, index) => {
|
||||
if (li === result.node) {
|
||||
listItemNumber = index;
|
||||
}
|
||||
});
|
||||
|
||||
const counterLength = String(start + listItemNumber).length;
|
||||
|
||||
return set.add(tr.doc, [
|
||||
Decoration.node(
|
||||
result.pos,
|
||||
result.pos + result.node.nodeSize,
|
||||
{
|
||||
class: `hovering`,
|
||||
},
|
||||
{
|
||||
hover: true,
|
||||
}
|
||||
),
|
||||
Decoration.node(
|
||||
result.pos,
|
||||
result.pos + result.node.nodeSize,
|
||||
{
|
||||
class: `counter-${counterLength}`,
|
||||
}
|
||||
),
|
||||
]);
|
||||
}
|
||||
case "mouseout": {
|
||||
const result = findParentNodeClosestToPos(
|
||||
newState.doc.resolve(action.pos),
|
||||
(node) =>
|
||||
node.type.name === this.name ||
|
||||
node.type.name === "checkbox_item"
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
return set;
|
||||
}
|
||||
|
||||
return set.remove(
|
||||
set.find(
|
||||
result.pos,
|
||||
result.pos + result.node.nodeSize,
|
||||
(spec) => spec.hover
|
||||
)
|
||||
);
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
return set;
|
||||
},
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
return this.getState(state);
|
||||
},
|
||||
handleDOMEvents: {
|
||||
mouseover: (view, event) => {
|
||||
const { state, dispatch } = view;
|
||||
const target = event.target as HTMLElement;
|
||||
const li = target?.closest("li");
|
||||
|
||||
if (!li) {
|
||||
return false;
|
||||
}
|
||||
if (!view.dom.contains(li)) {
|
||||
return false;
|
||||
}
|
||||
const pos = view.posAtDOM(li, 0);
|
||||
if (!pos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
state.tr.setMeta("li", {
|
||||
event: "mouseover",
|
||||
pos,
|
||||
})
|
||||
);
|
||||
return false;
|
||||
},
|
||||
mouseout: (view, event) => {
|
||||
const { state, dispatch } = view;
|
||||
const target = event.target as HTMLElement;
|
||||
const li = target?.closest("li");
|
||||
|
||||
if (!li) {
|
||||
return false;
|
||||
}
|
||||
if (!view.dom.contains(li)) {
|
||||
return false;
|
||||
}
|
||||
const pos = view.posAtDOM(li, 0);
|
||||
if (!pos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
state.tr.setMeta("li", {
|
||||
event: "mouseout",
|
||||
pos,
|
||||
})
|
||||
);
|
||||
return false;
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
commands({ type }: { type: NodeType }) {
|
||||
return {
|
||||
indentList: () => sinkListItem(type),
|
||||
|
||||
Reference in New Issue
Block a user