From cfa2537fae0f19c24d27e4311bf4d64db1771baf Mon Sep 17 00:00:00 2001 From: maerzhase Date: Mon, 5 Sep 2022 09:34:42 +0200 Subject: [PATCH 1/5] feat(article): add drag n drop for blocks - fix #330 --- .../SlateEditor/RenderElements.module.scss | 21 ++++- .../NFTArticle/SlateEditor/RenderElements.tsx | 82 +++++++++++++++++-- 2 files changed, 96 insertions(+), 7 deletions(-) diff --git a/src/components/NFTArticle/SlateEditor/RenderElements.module.scss b/src/components/NFTArticle/SlateEditor/RenderElements.module.scss index 8c2e2940c..5d75c48a9 100644 --- a/src/components/NFTArticle/SlateEditor/RenderElements.module.scss +++ b/src/components/NFTArticle/SlateEditor/RenderElements.module.scss @@ -45,7 +45,6 @@ } &:not(.opened) { - &:hover, &.buttons_visible { .buttons { @@ -54,6 +53,24 @@ } } } + + &.insertAbove { + &::after { + top: 0; + } + } + + &.dragOver { + &::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 2px; + background-color: var(--color-secondary); + } + } } .add_block_wrapper { @@ -65,4 +82,4 @@ .add_block { transform: translateX(-50%); } -} \ No newline at end of file +} diff --git a/src/components/NFTArticle/SlateEditor/RenderElements.tsx b/src/components/NFTArticle/SlateEditor/RenderElements.tsx index c2c855286..c03ca6773 100644 --- a/src/components/NFTArticle/SlateEditor/RenderElements.tsx +++ b/src/components/NFTArticle/SlateEditor/RenderElements.tsx @@ -1,17 +1,16 @@ import style from "./RenderElements.module.scss" import cs from "classnames" import { ReactEditor, RenderElementProps, useSlateStatic } from "slate-react" -import React, { PropsWithChildren, useEffect, useMemo, useState } from "react" +import React, { PropsWithChildren, DragEvent, useMemo, useRef, useState } from "react" import { AddBlock } from "./UI/AddBlock" import { getArticleBlockDefinition } from "./Blocks" -import { Path, Transforms, Node } from "slate" +import { Path, Transforms, Node, } from "slate" import { BlockExtraMenu } from "./UI/BlockExtraMenu" import { BlockMenu } from "./UI/BlockMenu" import { TEditNodeFn, TEditNodeFnFactory } from "../../../types/ArticleEditor/Transforms" import { withStopPropagation } from "../../../utils/events" import { TAttributesEditorWrapper } from "../../../types/ArticleEditor/BlockDefinition"; - interface IEditableElementWrapperProps { element: any } @@ -35,7 +34,11 @@ function EditableElementWrapper({ const [showAddBlock, setShowAddBlock] = useState(false) const [showExtraMenu, setShowExtraMenu] = useState(false) const [showSettings, setShowSettings] = useState(false) + const [isDragOver, setIsDragOver] = useState(false) + const [isDragging, setIsDragging] = useState(false) + const [insertAbove, setInsertAbove] = useState(false) + const draggableRef = useRef(null); const editor = useSlateStatic() const path = ReactEditor.findPath(editor, element) @@ -70,6 +73,57 @@ function EditableElementWrapper({ } } + const handleStartDragging = () => { + if(!draggableRef.current) return; + (draggableRef.current as HTMLElement).setAttribute('draggable', 'true') + setIsDragging(true) + } + + const handleEndDragging = () => { + if(!draggableRef.current) return; + (draggableRef.current as HTMLElement).setAttribute('draggable', 'false') + setIsDragging(false) + } + + const handleDragStart = (e:DragEvent) => { + if (!isDragging) return; + const domElement = (e.target as HTMLElement).children[0] as HTMLElement + if (!domElement) return + ReactEditor.deselect(editor) + const fromPath = ReactEditor.findPath(editor, element) + e.dataTransfer.setDragImage(domElement, domElement.offsetWidth, 0); + e.dataTransfer.setData('text/plain', JSON.stringify(fromPath)); + } + + const handleDragOver = (e:DragEvent) => { + // For the first block we want to be able to drop elements above it + if(path === [0]){ + const {height, top} = (e.target as HTMLElement).getBoundingClientRect(); + if (e.clientY < (top + height/2)) { + setInsertAbove(true) + } else { + setInsertAbove(false) + } + } + setIsDragOver(true) + } + + const handleDragLeave = () => { + setIsDragOver(false) + } + + const handleDrop = (e:DragEvent) => { + e.preventDefault() + e.stopPropagation() + setIsDragOver(false) + const data = e.dataTransfer.getData('text/plain'); + if (!data) return; + const at = JSON.parse(e.dataTransfer.getData('text/plain')) as Path + const to = ReactEditor.findPath(editor, element) + const moveUp = !insertAbove && at[0] > to[0]; + Transforms.moveNodes(editor, {at, to: moveUp ? Path.next(to) : to}) + } + const deleteNode = () => { Transforms.removeNodes(editor, { at: path @@ -94,16 +148,25 @@ function EditableElementWrapper({ return factory(editor, element, path) }, [definition, editor, element, path]) + return (
{children}
- {definition.editAttributeComp ? ( + + {definition.editAttributeComp ? ( +
{showAddBlock && ( <> From 9405d5ccc9900281f3188ffb391d77e07bbd7e6b Mon Sep 17 00:00:00 2001 From: maerzhase Date: Mon, 5 Sep 2022 09:43:19 +0200 Subject: [PATCH 2/5] fix check for first path --- src/components/NFTArticle/SlateEditor/RenderElements.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/NFTArticle/SlateEditor/RenderElements.tsx b/src/components/NFTArticle/SlateEditor/RenderElements.tsx index c03ca6773..871a3a05a 100644 --- a/src/components/NFTArticle/SlateEditor/RenderElements.tsx +++ b/src/components/NFTArticle/SlateEditor/RenderElements.tsx @@ -97,7 +97,7 @@ function EditableElementWrapper({ const handleDragOver = (e:DragEvent) => { // For the first block we want to be able to drop elements above it - if(path === [0]){ + if(path[0] === 0){ const {height, top} = (e.target as HTMLElement).getBoundingClientRect(); if (e.clientY < (top + height/2)) { setInsertAbove(true) @@ -107,7 +107,7 @@ function EditableElementWrapper({ } setIsDragOver(true) } - + const handleDragLeave = () => { setIsDragOver(false) } From b3f09fdf149db38b0a4fc08e2e3dccceca937151 Mon Sep 17 00:00:00 2001 From: maerzhase Date: Mon, 5 Sep 2022 09:51:46 +0200 Subject: [PATCH 3/5] reset insertAbove on any element but first --- src/components/NFTArticle/SlateEditor/RenderElements.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/NFTArticle/SlateEditor/RenderElements.tsx b/src/components/NFTArticle/SlateEditor/RenderElements.tsx index 871a3a05a..0a8c71e2f 100644 --- a/src/components/NFTArticle/SlateEditor/RenderElements.tsx +++ b/src/components/NFTArticle/SlateEditor/RenderElements.tsx @@ -96,14 +96,17 @@ function EditableElementWrapper({ } const handleDragOver = (e:DragEvent) => { + const elemPath = ReactEditor.findPath(editor, element) // For the first block we want to be able to drop elements above it - if(path[0] === 0){ + if(elemPath[0] === 0){ const {height, top} = (e.target as HTMLElement).getBoundingClientRect(); if (e.clientY < (top + height/2)) { setInsertAbove(true) } else { setInsertAbove(false) } + } else if(insertAbove) { + setInsertAbove(false) } setIsDragOver(true) } From 1a878640b4be7d2950f08fc0c835b0d18ec78402 Mon Sep 17 00:00:00 2001 From: maerzhase Date: Mon, 5 Sep 2022 20:20:59 +0200 Subject: [PATCH 4/5] add buttons to move block up/down --- .../SlateEditor/RenderElements.module.scss | 1 + .../NFTArticle/SlateEditor/RenderElements.tsx | 48 ++++++++++++++++--- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/components/NFTArticle/SlateEditor/RenderElements.module.scss b/src/components/NFTArticle/SlateEditor/RenderElements.module.scss index 5d75c48a9..409392587 100644 --- a/src/components/NFTArticle/SlateEditor/RenderElements.module.scss +++ b/src/components/NFTArticle/SlateEditor/RenderElements.module.scss @@ -29,6 +29,7 @@ color: var(--color-white); } + &>div { width: 30px; } diff --git a/src/components/NFTArticle/SlateEditor/RenderElements.tsx b/src/components/NFTArticle/SlateEditor/RenderElements.tsx index 0a8c71e2f..cd6f01de9 100644 --- a/src/components/NFTArticle/SlateEditor/RenderElements.tsx +++ b/src/components/NFTArticle/SlateEditor/RenderElements.tsx @@ -122,9 +122,27 @@ function EditableElementWrapper({ const data = e.dataTransfer.getData('text/plain'); if (!data) return; const at = JSON.parse(e.dataTransfer.getData('text/plain')) as Path - const to = ReactEditor.findPath(editor, element) - const moveUp = !insertAbove && at[0] > to[0]; - Transforms.moveNodes(editor, {at, to: moveUp ? Path.next(to) : to}) + const targetPath = ReactEditor.findPath(editor, element) + const insertAfter = !insertAbove && at[0] > targetPath[0]; + Transforms.moveNodes(editor, {at, to: insertAfter ? Path.next(targetPath) : targetPath}) + } + + const handleMoveDown = () => { + const at = ReactEditor.findPath(editor, element) + if (at[0] === editor.children.length - 1) return + const to = Path.next(at); + Transforms.moveNodes(editor, {at, to}) + Transforms.select(editor, to) + Transforms.collapse(editor) + } + + const handleMoveUp = () => { + const at = ReactEditor.findPath(editor, element) + if (at[0] === 0) return + const to = Path.previous(at); + Transforms.moveNodes(editor, {at, to}) + Transforms.select(editor, to) + Transforms.collapse(editor) } const deleteNode = () => { @@ -158,6 +176,7 @@ function EditableElementWrapper({ onDragStart={handleDragStart} onDragOver={handleDragOver} onDragLeave={handleDragLeave} + onDragEnd={handleEndDragging} onDrop={handleDrop} className={cs(style.element_wrapper, { [style.opened]: showAddBlock || showExtraMenu, @@ -183,7 +202,8 @@ function EditableElementWrapper({ ):(
)} - + +
{showAddBlock && ( <> From 11ad6c2d4bb59ced2117c02831a194fc0e23cefc Mon Sep 17 00:00:00 2001 From: maerzhase Date: Mon, 26 Sep 2022 11:47:21 +0200 Subject: [PATCH 5/5] run lint:fix --- .../NFTArticle/SlateEditor/RenderElements.tsx | 137 +++++++++--------- 1 file changed, 72 insertions(+), 65 deletions(-) diff --git a/src/components/NFTArticle/SlateEditor/RenderElements.tsx b/src/components/NFTArticle/SlateEditor/RenderElements.tsx index 1f48f4015..677bd0211 100644 --- a/src/components/NFTArticle/SlateEditor/RenderElements.tsx +++ b/src/components/NFTArticle/SlateEditor/RenderElements.tsx @@ -1,10 +1,16 @@ import style from "./RenderElements.module.scss" import cs from "classnames" import { ReactEditor, RenderElementProps, useSlateStatic } from "slate-react" -import React, { PropsWithChildren, DragEvent, useMemo, useRef, useState } from "react" +import React, { + PropsWithChildren, + DragEvent, + useMemo, + useRef, + useState, +} from "react" import { AddBlock } from "./UI/AddBlock" import { getArticleBlockDefinition } from "./Blocks" -import { Path, Transforms, Node, } from "slate" +import { Path, Transforms, Node } from "slate" import { BlockExtraMenu } from "./UI/BlockExtraMenu" import { BlockMenu } from "./UI/BlockMenu" import { @@ -12,7 +18,7 @@ import { TEditNodeFnFactory, } from "../../../types/ArticleEditor/Transforms" import { withStopPropagation } from "../../../utils/events" -import { TAttributesEditorWrapper } from "../../../types/ArticleEditor/BlockDefinition"; +import { TAttributesEditorWrapper } from "../../../types/ArticleEditor/BlockDefinition" interface IEditableElementWrapperProps { element: any @@ -41,7 +47,7 @@ function EditableElementWrapper({ const [isDragging, setIsDragging] = useState(false) const [insertAbove, setInsertAbove] = useState(false) - const draggableRef = useRef(null); + const draggableRef = useRef(null) const editor = useSlateStatic() const path = ReactEditor.findPath(editor, element) @@ -77,38 +83,38 @@ function EditableElementWrapper({ } const handleStartDragging = () => { - if(!draggableRef.current) return; - (draggableRef.current as HTMLElement).setAttribute('draggable', 'true') + if (!draggableRef.current) return + ;(draggableRef.current as HTMLElement).setAttribute("draggable", "true") setIsDragging(true) } const handleEndDragging = () => { - if(!draggableRef.current) return; - (draggableRef.current as HTMLElement).setAttribute('draggable', 'false') + if (!draggableRef.current) return + ;(draggableRef.current as HTMLElement).setAttribute("draggable", "false") setIsDragging(false) } - const handleDragStart = (e:DragEvent) => { - if (!isDragging) return; + const handleDragStart = (e: DragEvent) => { + if (!isDragging) return const domElement = (e.target as HTMLElement).children[0] as HTMLElement if (!domElement) return ReactEditor.deselect(editor) const fromPath = ReactEditor.findPath(editor, element) - e.dataTransfer.setDragImage(domElement, domElement.offsetWidth, 0); - e.dataTransfer.setData('text/plain', JSON.stringify(fromPath)); + e.dataTransfer.setDragImage(domElement, domElement.offsetWidth, 0) + e.dataTransfer.setData("text/plain", JSON.stringify(fromPath)) } - const handleDragOver = (e:DragEvent) => { + const handleDragOver = (e: DragEvent) => { const elemPath = ReactEditor.findPath(editor, element) // For the first block we want to be able to drop elements above it - if(elemPath[0] === 0){ - const {height, top} = (e.target as HTMLElement).getBoundingClientRect(); - if (e.clientY < (top + height/2)) { - setInsertAbove(true) + if (elemPath[0] === 0) { + const { height, top } = (e.target as HTMLElement).getBoundingClientRect() + if (e.clientY < top + height / 2) { + setInsertAbove(true) } else { - setInsertAbove(false) + setInsertAbove(false) } - } else if(insertAbove) { + } else if (insertAbove) { setInsertAbove(false) } setIsDragOver(true) @@ -118,32 +124,35 @@ function EditableElementWrapper({ setIsDragOver(false) } - const handleDrop = (e:DragEvent) => { + const handleDrop = (e: DragEvent) => { e.preventDefault() e.stopPropagation() setIsDragOver(false) - const data = e.dataTransfer.getData('text/plain'); - if (!data) return; - const at = JSON.parse(e.dataTransfer.getData('text/plain')) as Path + const data = e.dataTransfer.getData("text/plain") + if (!data) return + const at = JSON.parse(e.dataTransfer.getData("text/plain")) as Path const targetPath = ReactEditor.findPath(editor, element) - const insertAfter = !insertAbove && at[0] > targetPath[0]; - Transforms.moveNodes(editor, {at, to: insertAfter ? Path.next(targetPath) : targetPath}) + const insertAfter = !insertAbove && at[0] > targetPath[0] + Transforms.moveNodes(editor, { + at, + to: insertAfter ? Path.next(targetPath) : targetPath, + }) } - const handleMoveDown = () => { - const at = ReactEditor.findPath(editor, element) + const handleMoveDown = () => { + const at = ReactEditor.findPath(editor, element) if (at[0] === editor.children.length - 1) return - const to = Path.next(at); - Transforms.moveNodes(editor, {at, to}) + const to = Path.next(at) + Transforms.moveNodes(editor, { at, to }) Transforms.select(editor, to) Transforms.collapse(editor) } - const handleMoveUp = () => { + const handleMoveUp = () => { const at = ReactEditor.findPath(editor, element) if (at[0] === 0) return - const to = Path.previous(at); - Transforms.moveNodes(editor, {at, to}) + const to = Path.previous(at) + Transforms.moveNodes(editor, { at, to }) Transforms.select(editor, to) Transforms.collapse(editor) } @@ -172,7 +181,6 @@ function EditableElementWrapper({ return factory(editor, element, path) }, [definition, editor, element, path]) - return (
{children}
- - {definition.editAttributeComp ? ( + {definition.editAttributeComp ? ( - - - + + +
{showAddBlock && ( <>