inkpen 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.rubocop.yml +8 -0
- data/.yardopts +11 -0
- data/CLAUDE.md +141 -0
- data/README.md +409 -0
- data/Rakefile +19 -0
- data/app/assets/javascripts/inkpen/controllers/editor_controller.js +2050 -0
- data/app/assets/javascripts/inkpen/controllers/sticky_toolbar_controller.js +667 -0
- data/app/assets/javascripts/inkpen/controllers/toolbar_controller.js +693 -0
- data/app/assets/javascripts/inkpen/export/html.js +637 -0
- data/app/assets/javascripts/inkpen/export/index.js +30 -0
- data/app/assets/javascripts/inkpen/export/markdown.js +697 -0
- data/app/assets/javascripts/inkpen/export/pdf.js +372 -0
- data/app/assets/javascripts/inkpen/extensions/advanced_table.js +640 -0
- data/app/assets/javascripts/inkpen/extensions/block_commands.js +300 -0
- data/app/assets/javascripts/inkpen/extensions/block_gutter.js +338 -0
- data/app/assets/javascripts/inkpen/extensions/callout.js +303 -0
- data/app/assets/javascripts/inkpen/extensions/columns.js +403 -0
- data/app/assets/javascripts/inkpen/extensions/database.js +990 -0
- data/app/assets/javascripts/inkpen/extensions/document_section.js +352 -0
- data/app/assets/javascripts/inkpen/extensions/drag_handle.js +407 -0
- data/app/assets/javascripts/inkpen/extensions/embed.js +629 -0
- data/app/assets/javascripts/inkpen/extensions/enhanced_image.js +566 -0
- data/app/assets/javascripts/inkpen/extensions/export_commands.js +271 -0
- data/app/assets/javascripts/inkpen/extensions/file_attachment.js +593 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/index.js +58 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/inkpen_table.js +638 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/inkpen_table_cell.js +100 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/inkpen_table_header.js +100 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/table_constants.js +152 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/table_helpers.js +254 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/table_menu.js +282 -0
- data/app/assets/javascripts/inkpen/extensions/preformatted.js +239 -0
- data/app/assets/javascripts/inkpen/extensions/section.js +281 -0
- data/app/assets/javascripts/inkpen/extensions/section_title.js +126 -0
- data/app/assets/javascripts/inkpen/extensions/slash_commands.js +439 -0
- data/app/assets/javascripts/inkpen/extensions/table_of_contents.js +474 -0
- data/app/assets/javascripts/inkpen/extensions/toggle_block.js +332 -0
- data/app/assets/javascripts/inkpen/index.js +87 -0
- data/app/assets/stylesheets/inkpen/advanced_table.css +514 -0
- data/app/assets/stylesheets/inkpen/animations.css +626 -0
- data/app/assets/stylesheets/inkpen/block_gutter.css +265 -0
- data/app/assets/stylesheets/inkpen/callout.css +359 -0
- data/app/assets/stylesheets/inkpen/columns.css +314 -0
- data/app/assets/stylesheets/inkpen/database.css +658 -0
- data/app/assets/stylesheets/inkpen/document_section.css +305 -0
- data/app/assets/stylesheets/inkpen/drag_drop.css +220 -0
- data/app/assets/stylesheets/inkpen/editor.css +652 -0
- data/app/assets/stylesheets/inkpen/embed.css +468 -0
- data/app/assets/stylesheets/inkpen/enhanced_image.css +453 -0
- data/app/assets/stylesheets/inkpen/export.css +499 -0
- data/app/assets/stylesheets/inkpen/file_attachment.css +347 -0
- data/app/assets/stylesheets/inkpen/footnotes.css +136 -0
- data/app/assets/stylesheets/inkpen/inkpen_table.css +608 -0
- data/app/assets/stylesheets/inkpen/preformatted.css +215 -0
- data/app/assets/stylesheets/inkpen/search_replace.css +58 -0
- data/app/assets/stylesheets/inkpen/section.css +236 -0
- data/app/assets/stylesheets/inkpen/slash_menu.css +252 -0
- data/app/assets/stylesheets/inkpen/sticky_toolbar.css +314 -0
- data/app/assets/stylesheets/inkpen/toc.css +386 -0
- data/app/assets/stylesheets/inkpen/toggle.css +260 -0
- data/app/helpers/inkpen/editor_helper.rb +114 -0
- data/app/views/inkpen/_editor.html.erb +139 -0
- data/config/importmap.rb +170 -0
- data/docs/.DS_Store +0 -0
- data/docs/CHANGELOG.md +571 -0
- data/docs/FEATURES.md +436 -0
- data/docs/ROADMAP.md +3029 -0
- data/docs/VISION.md +235 -0
- data/docs/extensions/INKPEN_TABLE.md +482 -0
- data/docs/thinking/CORRECTED_NO_VUE.md +756 -0
- data/docs/thinking/EXECUTIVE_SUMMARY.md +403 -0
- data/docs/thinking/INKPEN_CODE_SAMPLES.md +1479 -0
- data/docs/thinking/INKPEN_MASTER_GUIDE.md +891 -0
- data/docs/thinking/README_START_HERE.md +341 -0
- data/lib/inkpen/configuration.rb +175 -0
- data/lib/inkpen/editor.rb +204 -0
- data/lib/inkpen/engine.rb +32 -0
- data/lib/inkpen/extensions/base.rb +109 -0
- data/lib/inkpen/extensions/code_block_syntax.rb +177 -0
- data/lib/inkpen/extensions/document_section.rb +111 -0
- data/lib/inkpen/extensions/forced_document.rb +183 -0
- data/lib/inkpen/extensions/mention.rb +155 -0
- data/lib/inkpen/extensions/preformatted.rb +111 -0
- data/lib/inkpen/extensions/section.rb +139 -0
- data/lib/inkpen/extensions/slash_commands.rb +100 -0
- data/lib/inkpen/extensions/table.rb +182 -0
- data/lib/inkpen/extensions/task_list.rb +145 -0
- data/lib/inkpen/sticky_toolbar.rb +157 -0
- data/lib/inkpen/toolbar.rb +145 -0
- data/lib/inkpen/version.rb +5 -0
- data/lib/inkpen.rb +101 -0
- data/sig/inkpen.rbs +4 -0
- metadata +165 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { Extension } from "@tiptap/core"
|
|
2
|
+
import { Plugin, PluginKey } from "@tiptap/pm/state"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Drag Handle Extension for TipTap
|
|
6
|
+
*
|
|
7
|
+
* Enables drag and drop reordering of blocks. Works in conjunction with
|
|
8
|
+
* the BlockGutter extension which provides the visual drag handles.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Visual drop indicator showing insertion point
|
|
12
|
+
* - Smooth block movement with ProseMirror transactions
|
|
13
|
+
* - Edge scrolling when dragging near viewport edges
|
|
14
|
+
* - Keyboard shortcut for moving blocks (Cmd+Shift+Arrow)
|
|
15
|
+
*
|
|
16
|
+
* @since 0.3.2
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const DRAG_HANDLE_KEY = new PluginKey("dragHandle")
|
|
20
|
+
|
|
21
|
+
export const DragHandle = Extension.create({
|
|
22
|
+
name: "dragHandle",
|
|
23
|
+
|
|
24
|
+
addOptions() {
|
|
25
|
+
return {
|
|
26
|
+
// Scroll speed when dragging near edges
|
|
27
|
+
scrollSpeed: 10,
|
|
28
|
+
// Distance from edge to trigger scrolling
|
|
29
|
+
scrollThreshold: 80,
|
|
30
|
+
// Class for drop indicator
|
|
31
|
+
dropIndicatorClass: "inkpen-drop-indicator",
|
|
32
|
+
// Callback when block is moved
|
|
33
|
+
onBlockMoved: null
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
addKeyboardShortcuts() {
|
|
38
|
+
return {
|
|
39
|
+
"Mod-Shift-ArrowUp": () => this.editor.commands.moveBlockUp(),
|
|
40
|
+
"Mod-Shift-ArrowDown": () => this.editor.commands.moveBlockDown()
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
addCommands() {
|
|
45
|
+
return {
|
|
46
|
+
moveBlockUp: () => ({ state, dispatch }) => {
|
|
47
|
+
return this.moveBlock(state, dispatch, -1)
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
moveBlockDown: () => ({ state, dispatch }) => {
|
|
51
|
+
return this.moveBlock(state, dispatch, 1)
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
moveBlockToPosition: (fromPos, toPos) => ({ state, dispatch }) => {
|
|
55
|
+
return this.moveBlockTo(state, dispatch, fromPos, toPos)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
moveBlock(state, dispatch, direction) {
|
|
61
|
+
const { selection, doc } = state
|
|
62
|
+
const $from = selection.$from
|
|
63
|
+
|
|
64
|
+
// Find the current block
|
|
65
|
+
const depth = $from.depth
|
|
66
|
+
if (depth === 0) return false
|
|
67
|
+
|
|
68
|
+
const blockStart = $from.before(depth)
|
|
69
|
+
const blockEnd = $from.after(depth)
|
|
70
|
+
const node = doc.nodeAt(blockStart)
|
|
71
|
+
|
|
72
|
+
if (!node) return false
|
|
73
|
+
|
|
74
|
+
// Find sibling block
|
|
75
|
+
let targetPos
|
|
76
|
+
if (direction < 0) {
|
|
77
|
+
// Moving up - find previous sibling
|
|
78
|
+
const $blockStart = doc.resolve(blockStart)
|
|
79
|
+
if ($blockStart.nodeBefore) {
|
|
80
|
+
targetPos = blockStart - $blockStart.nodeBefore.nodeSize
|
|
81
|
+
} else {
|
|
82
|
+
return false // Already at top
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
// Moving down - find next sibling
|
|
86
|
+
const $blockEnd = doc.resolve(blockEnd)
|
|
87
|
+
if ($blockEnd.nodeAfter) {
|
|
88
|
+
targetPos = blockEnd + $blockEnd.nodeAfter.nodeSize
|
|
89
|
+
} else {
|
|
90
|
+
return false // Already at bottom
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (dispatch) {
|
|
95
|
+
const tr = state.tr
|
|
96
|
+
|
|
97
|
+
// Delete from original position
|
|
98
|
+
tr.delete(blockStart, blockEnd)
|
|
99
|
+
|
|
100
|
+
// Adjust target position after deletion
|
|
101
|
+
const adjustedPos = targetPos > blockStart ? targetPos - node.nodeSize : targetPos
|
|
102
|
+
|
|
103
|
+
// Insert at new position
|
|
104
|
+
tr.insert(adjustedPos, node)
|
|
105
|
+
|
|
106
|
+
// Set selection to moved block
|
|
107
|
+
const newPos = adjustedPos + 1
|
|
108
|
+
tr.setSelection(state.selection.constructor.near(tr.doc.resolve(newPos)))
|
|
109
|
+
|
|
110
|
+
dispatch(tr.scrollIntoView())
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return true
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
moveBlockTo(state, dispatch, fromPos, toPos) {
|
|
117
|
+
const { doc } = state
|
|
118
|
+
const node = doc.nodeAt(fromPos)
|
|
119
|
+
|
|
120
|
+
if (!node) return false
|
|
121
|
+
|
|
122
|
+
// Don't move if positions are the same
|
|
123
|
+
if (fromPos === toPos) return false
|
|
124
|
+
|
|
125
|
+
const nodeSize = node.nodeSize
|
|
126
|
+
|
|
127
|
+
if (dispatch) {
|
|
128
|
+
const tr = state.tr
|
|
129
|
+
|
|
130
|
+
// Delete from original position
|
|
131
|
+
tr.delete(fromPos, fromPos + nodeSize)
|
|
132
|
+
|
|
133
|
+
// Adjust target position after deletion
|
|
134
|
+
const adjustedPos = toPos > fromPos ? toPos - nodeSize : toPos
|
|
135
|
+
|
|
136
|
+
// Insert at new position
|
|
137
|
+
tr.insert(adjustedPos, node)
|
|
138
|
+
|
|
139
|
+
dispatch(tr.scrollIntoView())
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return true
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
addProseMirrorPlugins() {
|
|
146
|
+
const extension = this
|
|
147
|
+
const options = this.options
|
|
148
|
+
|
|
149
|
+
return [
|
|
150
|
+
new Plugin({
|
|
151
|
+
key: DRAG_HANDLE_KEY,
|
|
152
|
+
|
|
153
|
+
state: {
|
|
154
|
+
init() {
|
|
155
|
+
return {
|
|
156
|
+
isDragging: false,
|
|
157
|
+
draggedPos: null,
|
|
158
|
+
dropPos: null
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
apply(tr, value) {
|
|
162
|
+
const meta = tr.getMeta(DRAG_HANDLE_KEY)
|
|
163
|
+
if (meta) {
|
|
164
|
+
return { ...value, ...meta }
|
|
165
|
+
}
|
|
166
|
+
return value
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
props: {
|
|
171
|
+
handleDOMEvents: {
|
|
172
|
+
dragover(view, event) {
|
|
173
|
+
// Check if this is a block drag (from our gutter)
|
|
174
|
+
const blockData = event.dataTransfer.types.includes("application/inkpen-block")
|
|
175
|
+
if (!blockData) return false
|
|
176
|
+
|
|
177
|
+
event.preventDefault()
|
|
178
|
+
event.dataTransfer.dropEffect = "move"
|
|
179
|
+
|
|
180
|
+
// Find drop position
|
|
181
|
+
const coords = { left: event.clientX, top: event.clientY }
|
|
182
|
+
const dropInfo = extension.findDropPosition(view, coords)
|
|
183
|
+
|
|
184
|
+
if (dropInfo) {
|
|
185
|
+
extension.showDropIndicator(view, dropInfo)
|
|
186
|
+
|
|
187
|
+
// Edge scrolling
|
|
188
|
+
extension.handleEdgeScroll(view, event.clientY)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return true
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
dragleave(view, event) {
|
|
195
|
+
// Only hide if leaving the editor entirely
|
|
196
|
+
const relatedTarget = event.relatedTarget
|
|
197
|
+
if (!view.dom.contains(relatedTarget)) {
|
|
198
|
+
extension.hideDropIndicator(view)
|
|
199
|
+
}
|
|
200
|
+
return false
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
drop(view, event) {
|
|
204
|
+
const blockDataStr = event.dataTransfer.getData("application/inkpen-block")
|
|
205
|
+
if (!blockDataStr) return false
|
|
206
|
+
|
|
207
|
+
event.preventDefault()
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const blockData = JSON.parse(blockDataStr)
|
|
211
|
+
const fromPos = blockData.pos
|
|
212
|
+
|
|
213
|
+
// Find drop position
|
|
214
|
+
const coords = { left: event.clientX, top: event.clientY }
|
|
215
|
+
const dropInfo = extension.findDropPosition(view, coords)
|
|
216
|
+
|
|
217
|
+
if (dropInfo && dropInfo.pos !== fromPos) {
|
|
218
|
+
// Execute the move
|
|
219
|
+
const node = view.state.doc.nodeAt(fromPos)
|
|
220
|
+
if (node) {
|
|
221
|
+
extension.moveBlockTo(
|
|
222
|
+
view.state,
|
|
223
|
+
view.dispatch.bind(view),
|
|
224
|
+
fromPos,
|
|
225
|
+
dropInfo.insertPos
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
// Callback
|
|
229
|
+
options.onBlockMoved?.(fromPos, dropInfo.insertPos, node)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
} catch (e) {
|
|
233
|
+
console.error("Drop failed:", e)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
extension.cleanup(view)
|
|
237
|
+
return true
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
dragend(view) {
|
|
241
|
+
extension.cleanup(view)
|
|
242
|
+
return false
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
view() {
|
|
248
|
+
return {
|
|
249
|
+
destroy() {
|
|
250
|
+
// Clean up any lingering indicators
|
|
251
|
+
document.querySelectorAll(`.${options.dropIndicatorClass}`).forEach(el => el.remove())
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
]
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
findDropPosition(view, coords) {
|
|
260
|
+
const { state } = view
|
|
261
|
+
const { doc } = state
|
|
262
|
+
|
|
263
|
+
// Get position from coordinates
|
|
264
|
+
const posInfo = view.posAtCoords(coords)
|
|
265
|
+
if (!posInfo) return null
|
|
266
|
+
|
|
267
|
+
// Resolve to find the block boundary
|
|
268
|
+
const $pos = doc.resolve(posInfo.pos)
|
|
269
|
+
|
|
270
|
+
// Find the top-level block
|
|
271
|
+
let blockPos = null
|
|
272
|
+
let insertBefore = true
|
|
273
|
+
|
|
274
|
+
// Walk up to find the top-level block
|
|
275
|
+
for (let d = $pos.depth; d > 0; d--) {
|
|
276
|
+
const node = $pos.node(d)
|
|
277
|
+
const parent = $pos.node(d - 1)
|
|
278
|
+
|
|
279
|
+
// Check if this is a direct child of the document
|
|
280
|
+
if (parent.type.name === "doc") {
|
|
281
|
+
blockPos = $pos.before(d)
|
|
282
|
+
break
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// If no block found, use document structure
|
|
287
|
+
if (blockPos === null) {
|
|
288
|
+
// Find the nearest block by iterating through document
|
|
289
|
+
let closestPos = 0
|
|
290
|
+
let closestDistance = Infinity
|
|
291
|
+
|
|
292
|
+
doc.forEach((node, pos) => {
|
|
293
|
+
const nodeCoords = view.coordsAtPos(pos)
|
|
294
|
+
const distance = Math.abs(nodeCoords.top - coords.top)
|
|
295
|
+
|
|
296
|
+
if (distance < closestDistance) {
|
|
297
|
+
closestDistance = distance
|
|
298
|
+
closestPos = pos
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
blockPos = closestPos
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Determine if we should insert before or after this block
|
|
306
|
+
const blockCoords = view.coordsAtPos(blockPos)
|
|
307
|
+
const node = doc.nodeAt(blockPos)
|
|
308
|
+
const nodeHeight = node ? view.coordsAtPos(blockPos + node.nodeSize).top - blockCoords.top : 20
|
|
309
|
+
const midpoint = blockCoords.top + nodeHeight / 2
|
|
310
|
+
|
|
311
|
+
insertBefore = coords.top < midpoint
|
|
312
|
+
|
|
313
|
+
// Calculate the actual insertion position
|
|
314
|
+
let insertPos
|
|
315
|
+
if (insertBefore) {
|
|
316
|
+
insertPos = blockPos
|
|
317
|
+
} else {
|
|
318
|
+
insertPos = blockPos + (node?.nodeSize || 0)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
pos: blockPos,
|
|
323
|
+
insertPos,
|
|
324
|
+
insertBefore,
|
|
325
|
+
coords: insertBefore ? blockCoords : { ...blockCoords, top: blockCoords.top + nodeHeight }
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
showDropIndicator(view, dropInfo) {
|
|
330
|
+
const { dropIndicatorClass } = this.options
|
|
331
|
+
|
|
332
|
+
// Remove existing indicator
|
|
333
|
+
this.hideDropIndicator(view)
|
|
334
|
+
|
|
335
|
+
// Create indicator element
|
|
336
|
+
const indicator = document.createElement("div")
|
|
337
|
+
indicator.className = dropIndicatorClass
|
|
338
|
+
|
|
339
|
+
// Position the indicator
|
|
340
|
+
const editorRect = view.dom.getBoundingClientRect()
|
|
341
|
+
const wrapperRect = view.dom.parentElement?.getBoundingClientRect() || editorRect
|
|
342
|
+
|
|
343
|
+
indicator.style.position = "absolute"
|
|
344
|
+
indicator.style.left = "0"
|
|
345
|
+
indicator.style.right = "0"
|
|
346
|
+
indicator.style.top = `${dropInfo.coords.top - wrapperRect.top}px`
|
|
347
|
+
|
|
348
|
+
// Append to editor wrapper
|
|
349
|
+
const wrapper = view.dom.parentElement
|
|
350
|
+
if (wrapper) {
|
|
351
|
+
wrapper.appendChild(indicator)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Store reference for cleanup
|
|
355
|
+
this.dropIndicator = indicator
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
hideDropIndicator() {
|
|
359
|
+
if (this.dropIndicator) {
|
|
360
|
+
this.dropIndicator.remove()
|
|
361
|
+
this.dropIndicator = null
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
handleEdgeScroll(view, clientY) {
|
|
366
|
+
const { scrollThreshold, scrollSpeed } = this.options
|
|
367
|
+
const viewportHeight = window.innerHeight
|
|
368
|
+
|
|
369
|
+
// Cancel any existing scroll animation
|
|
370
|
+
if (this.scrollAnimationId) {
|
|
371
|
+
cancelAnimationFrame(this.scrollAnimationId)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Check if near top or bottom edge
|
|
375
|
+
if (clientY < scrollThreshold) {
|
|
376
|
+
// Scroll up
|
|
377
|
+
const intensity = 1 - clientY / scrollThreshold
|
|
378
|
+
this.animateScroll(-scrollSpeed * intensity)
|
|
379
|
+
} else if (clientY > viewportHeight - scrollThreshold) {
|
|
380
|
+
// Scroll down
|
|
381
|
+
const intensity = 1 - (viewportHeight - clientY) / scrollThreshold
|
|
382
|
+
this.animateScroll(scrollSpeed * intensity)
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
animateScroll(delta) {
|
|
387
|
+
window.scrollBy(0, delta)
|
|
388
|
+
|
|
389
|
+
// Continue scrolling while dragging
|
|
390
|
+
this.scrollAnimationId = requestAnimationFrame(() => {
|
|
391
|
+
// This will be cancelled when drag ends or mouse moves away from edge
|
|
392
|
+
})
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
cleanup(view) {
|
|
396
|
+
this.hideDropIndicator(view)
|
|
397
|
+
|
|
398
|
+
if (this.scrollAnimationId) {
|
|
399
|
+
cancelAnimationFrame(this.scrollAnimationId)
|
|
400
|
+
this.scrollAnimationId = null
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
view.dom.classList.remove("is-block-dragging")
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
export default DragHandle
|