add global drag handle experiment

This commit is contained in:
Philipp Kühn 2021-03-27 11:41:55 +01:00
parent 0a238d9c12
commit 24d2f38021
4 changed files with 297 additions and 0 deletions

View File

@ -0,0 +1,158 @@
import { Extension } from '@tiptap/core'
import { NodeSelection, Plugin } from 'prosemirror-state'
import { serializeForClipboard } from 'prosemirror-view/src/clipboard'
function removeNode(node) {
node.parentNode.removeChild(node)
}
function absoluteRect(node) {
const data = node.getBoundingClientRect()
return {
top: data.top,
left: data.left,
width: data.width,
}
}
export default Extension.create({
addProseMirrorPlugins() {
function blockPosAtCoords(coords, view) {
const pos = view.posAtCoords(coords)
let node = view.domAtPos(pos.pos)
node = node.node
while (node && node.parentNode) {
if (node.parentNode?.classList?.contains('ProseMirror')) { // todo
break
}
node = node.parentNode
}
if (node && node.nodeType === 1) {
const desc = view.docView.nearestDesc(node, true)
if (!(!desc || desc === view.docView)) {
return desc.posBefore
}
}
return null
}
function dragStart(e, view) {
view.composing = true
if (!e.dataTransfer) {
return
}
const coords = { left: e.clientX + 50, top: e.clientY }
const pos = blockPosAtCoords(coords, view)
if (pos != null) {
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, pos)))
const slice = view.state.selection.content()
const { dom, text } = serializeForClipboard(view, slice)
e.dataTransfer.clearData()
e.dataTransfer.setData('text/html', dom.innerHTML)
e.dataTransfer.setData('text/plain', text)
const el = document.querySelector('.ProseMirror-selectednode')
e.dataTransfer?.setDragImage(el, 0, 0)
view.dragging = { slice, move: true }
}
}
let dropElement
const WIDTH = 28
return [
new Plugin({
view(editorView) {
const element = document.createElement('div')
element.draggable = 'true'
element.classList.add('global-drag-handle')
element.addEventListener('dragstart', e => dragStart(e, editorView))
dropElement = element
document.body.appendChild(dropElement)
return {
// update(view, prevState) {
// },
destroy() {
removeNode(dropElement)
dropElement = null
},
}
},
props: {
handleDrop(view, event, slice, moved) {
if (moved) {
// setTimeout(() => {
// console.log('remove selection')
// view.dispatch(view.state.tr.deleteSelection())
// }, 50)
}
},
// handlePaste() {
// alert(2)
// },
handleDOMEvents: {
// drop(view, event) {
// setTimeout(() => {
// const node = document.querySelector('.ProseMirror-hideselection')
// if (node) {
// node.classList.remove('ProseMirror-hideselection')
// }
// }, 50)
// },
mousemove(view, event) {
const coords = {
left: event.clientX + WIDTH + 50,
top: event.clientY,
}
const pos = view.posAtCoords(coords)
if (pos) {
let node = view.domAtPos(pos?.pos)
if (node) {
node = node.node
while (node && node.parentNode) {
if (node.parentNode?.classList?.contains('ProseMirror')) { // todo
break
}
node = node.parentNode
}
if (node instanceof Element) {
const cstyle = window.getComputedStyle(node)
const lineHeight = parseInt(cstyle.lineHeight, 10)
// const top = parseInt(cstyle.marginTop, 10) + parseInt(cstyle.paddingTop, 10)
const top = 0
const rect = absoluteRect(node)
const win = node.ownerDocument.defaultView
rect.top += win.pageYOffset + ((lineHeight - 24) / 2) + top
rect.left += win.pageXOffset
rect.width = `${WIDTH}px`
dropElement.style.left = `${-WIDTH + rect.left}px`
dropElement.style.top = `${rect.top}px`
}
}
}
},
},
},
}),
]
},
})

View File

@ -0,0 +1,133 @@
<template>
<div v-if="editor">
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent, defaultExtensions } from '@tiptap/vue-starter-kit'
import DragHandle from './DragHandle.js'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
...defaultExtensions(),
DragHandle,
],
content: `
<p>paragraph 1</p>
<p>paragraph 2</p>
<p>paragraph 3</p>
<ul>
<li>list item 1</li>
<li>list item 2</li>
</ul>
<pre>code</pre>
`,
onUpdate: () => {
console.log(this.editor.getHTML())
},
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
.global-drag-handle {
position: absolute;
&::after {
display: flex;
align-items: center;
justify-content: center;
width: 1rem;
height: 1.25rem;
content: '⠿';
font-weight: 700;
cursor: grab;
background:#0D0D0D10;
color: #0D0D0D50;
border-radius: 0.25rem;
}
}
</style>
<style lang="scss" scoped>
::v-deep {
.ProseMirror {
padding: 0 1rem;
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
code {
color: inherit;
background: none;
font-size: 0.8rem;
}
}
img {
max-width: 100%;
height: auto;
}
blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
margin: 2rem 0;
}
}
.ProseMirror-selectednode {
outline: 2px solid #70CFF8;
}
}
</style>

View File

@ -4,6 +4,7 @@ Congratulations! Youve found our playground with a list of experiments. Be aw
## New
* [Linter](/experiments/linter)
* [Multiple editors](/experiments/multiple-editors)
* [Global drag handle](/experiments/global-drag-handle)
* [@tiptap/extension-slash-command?](/experiments/commands)
* [@tiptap/extension-iframe?](/experiments/embeds)
* [@tiptap/extension-toggle-list?](/experiments/details)

View File

@ -0,0 +1,5 @@
# GlobalDragHandle
⚠️ Experiment
<demo name="Experiments/GlobalDragHandle" />