@brett_lamy/docstream-editor 0.1.0 → 0.3.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.
@@ -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
- if (!rect) return null
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
- const style: React.CSSProperties = {
226
- position: "fixed",
227
- left: rect.left,
228
- top: rect.bottom + 6,
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 className="slash-menu" style={style}>
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 const SlashMenu = Extension.create({
256
- name: "slashMenu",
257
- addProseMirrorPlugins() {
258
- let renderer: ReactRenderer<MenuHandle, MenuProps> | null = null
259
- return [
260
- Suggestion<SlashItem>({
261
- editor: this.editor,
262
- char: "/",
263
- startOfLine: false,
264
- items: ({ query }) =>
265
- SLASH_ITEMS.filter((item) =>
266
- `${item.title} ${item.keywords}`.toLowerCase().includes(query.toLowerCase())
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"