@brett_lamy/docstream-editor 0.1.0 → 0.3.0
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.
- package/README.md +220 -0
- package/package.json +9 -7
- package/src/editor/Editor.tsx +65 -37
- package/src/editor/ReactDemoEditor.tsx +108 -0
- package/src/editor/convert.ts +10 -2
- package/src/editor/extensions.ts +74 -0
- package/src/editor/nodes.tsx +31 -3
- package/src/editor/slash-menu.tsx +59 -24
- package/src/index.ts +10 -0
- package/src/styles.css +658 -1
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
forwardRef,
|
|
3
3
|
useEffect,
|
|
4
4
|
useImperativeHandle,
|
|
5
|
+
useLayoutEffect,
|
|
5
6
|
useRef,
|
|
6
7
|
useState,
|
|
7
8
|
} from "react"
|
|
@@ -219,17 +220,41 @@ const SlashMenuView = forwardRef<MenuHandle, MenuProps>(function SlashMenuView(
|
|
|
219
220
|
},
|
|
220
221
|
}))
|
|
221
222
|
|
|
223
|
+
const menuRef = useRef<HTMLDivElement>(null)
|
|
222
224
|
const rect = clientRect?.()
|
|
223
|
-
|
|
225
|
+
const [pos, setPos] = useState<{ left: number; top: number }>(() => ({
|
|
226
|
+
left: rect ? rect.left : 0,
|
|
227
|
+
top: rect ? rect.bottom + 6 : 0,
|
|
228
|
+
}))
|
|
224
229
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
// Position below the cursor, but flip above when there isn't room below
|
|
231
|
+
// (e.g. triggered near the bottom of the viewport). Measured after render.
|
|
232
|
+
useLayoutEffect(() => {
|
|
233
|
+
const r = clientRect?.()
|
|
234
|
+
const el = menuRef.current
|
|
235
|
+
if (!r || !el) return
|
|
236
|
+
const margin = 6
|
|
237
|
+
const menuH = el.offsetHeight
|
|
238
|
+
const menuW = el.offsetWidth
|
|
239
|
+
const spaceBelow = window.innerHeight - r.bottom
|
|
240
|
+
let top = r.bottom + margin
|
|
241
|
+
if (spaceBelow < menuH + margin && r.top - margin > menuH) {
|
|
242
|
+
top = r.top - menuH - margin
|
|
243
|
+
}
|
|
244
|
+
top = Math.max(margin, Math.min(top, window.innerHeight - menuH - margin))
|
|
245
|
+
let left = Math.min(r.left, window.innerWidth - menuW - margin)
|
|
246
|
+
left = Math.max(margin, left)
|
|
247
|
+
if (left !== pos.left || top !== pos.top) setPos({ left, top })
|
|
248
|
+
}, [clientRect, items, pos.left, pos.top])
|
|
249
|
+
|
|
250
|
+
if (!rect) return null
|
|
230
251
|
|
|
231
252
|
return (
|
|
232
|
-
<div
|
|
253
|
+
<div
|
|
254
|
+
ref={menuRef}
|
|
255
|
+
className="slash-menu"
|
|
256
|
+
style={{ position: "fixed", left: pos.left, top: pos.top }}
|
|
257
|
+
>
|
|
233
258
|
{items.length === 0 && <div className="slash-empty">No matching blocks</div>}
|
|
234
259
|
{items.map((item, i) => (
|
|
235
260
|
<button
|
|
@@ -252,19 +277,26 @@ const SlashMenuView = forwardRef<MenuHandle, MenuProps>(function SlashMenuView(
|
|
|
252
277
|
)
|
|
253
278
|
})
|
|
254
279
|
|
|
255
|
-
export
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
280
|
+
export type { SlashItem }
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Build a slash-command extension. Pass a custom `items` list to override the
|
|
284
|
+
* default GitBook blocks. The default instance is exported as `SlashMenu`.
|
|
285
|
+
*/
|
|
286
|
+
export function createSlashMenu(items: SlashItem[] = SLASH_ITEMS) {
|
|
287
|
+
return Extension.create({
|
|
288
|
+
name: "slashMenu",
|
|
289
|
+
addProseMirrorPlugins() {
|
|
290
|
+
let renderer: ReactRenderer<MenuHandle, MenuProps> | null = null
|
|
291
|
+
return [
|
|
292
|
+
Suggestion<SlashItem>({
|
|
293
|
+
editor: this.editor,
|
|
294
|
+
char: "/",
|
|
295
|
+
startOfLine: false,
|
|
296
|
+
items: ({ query }) =>
|
|
297
|
+
items.filter((item) =>
|
|
298
|
+
`${item.title} ${item.keywords}`.toLowerCase().includes(query.toLowerCase())
|
|
299
|
+
),
|
|
268
300
|
command: ({ editor, range, props }) => {
|
|
269
301
|
editor.chain().focus().deleteRange(range as Range).run()
|
|
270
302
|
props.run(editor as Editor)
|
|
@@ -302,8 +334,11 @@ export const SlashMenu = Extension.create({
|
|
|
302
334
|
renderer?.destroy()
|
|
303
335
|
renderer = null
|
|
304
336
|
},
|
|
337
|
+
}),
|
|
305
338
|
}),
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
}
|
|
339
|
+
]
|
|
340
|
+
},
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export const SlashMenu = createSlashMenu()
|
package/src/index.ts
CHANGED
|
@@ -2,3 +2,13 @@ export { GitbookEditor } from "./editor/Editor"
|
|
|
2
2
|
export type { GitbookEditorProps } from "./editor/Editor"
|
|
3
3
|
export { astToTiptap, tiptapToAst } from "./editor/convert"
|
|
4
4
|
export type { PMNode } from "./editor/convert"
|
|
5
|
+
|
|
6
|
+
// Building blocks for composing a custom editor (e.g. with Yjs collaboration
|
|
7
|
+
// or a bespoke slash menu) instead of the batteries-included GitbookEditor.
|
|
8
|
+
export { createGitbookExtensions, getGitbookSchema, GbTable } from "./editor/extensions"
|
|
9
|
+
export type { GitbookExtensionOptions } from "./editor/extensions"
|
|
10
|
+
export { gitbookNodes, GbCodeBlock } from "./editor/nodes"
|
|
11
|
+
export { SlashMenu, createSlashMenu, SLASH_ITEMS } from "./editor/slash-menu"
|
|
12
|
+
export type { SlashItem } from "./editor/slash-menu"
|
|
13
|
+
export { ReactDemoEditor } from "./editor/ReactDemoEditor"
|
|
14
|
+
export type { ReactDemoEditorProps } from "./editor/ReactDemoEditor"
|