create-starlight-cms 0.9.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/LICENSE +21 -0
- package/README.md +18 -0
- package/THIRD_PARTY_NOTICES.md +4 -0
- package/bin/index.js +147 -0
- package/package.json +46 -0
- package/template/.nvmrc +1 -0
- package/template/LICENSE +21 -0
- package/template/README.md +68 -0
- package/template/THIRD_PARTY_NOTICES.md +12 -0
- package/template/_gitignore +14 -0
- package/template/astro.config.mjs +26 -0
- package/template/migrations/0001_schema.sql +73 -0
- package/template/migrations/0002_publish_delivery.sql +16 -0
- package/template/package-lock.json.template +9123 -0
- package/template/package.json +89 -0
- package/template/scripts/build-admin.mjs +38 -0
- package/template/scripts/check-linux-bindings.mjs +18 -0
- package/template/scripts/dev.mjs +94 -0
- package/template/src/admin/admin.scss +182 -0
- package/template/src/admin/app.ts +130 -0
- package/template/src/admin/client.tsx +684 -0
- package/template/src/admin/components/tiptap-extension/node-background-extension.ts +150 -0
- package/template/src/admin/components/tiptap-icons/align-center-icon.tsx +38 -0
- package/template/src/admin/components/tiptap-icons/align-justify-icon.tsx +38 -0
- package/template/src/admin/components/tiptap-icons/align-left-icon.tsx +38 -0
- package/template/src/admin/components/tiptap-icons/align-right-icon.tsx +38 -0
- package/template/src/admin/components/tiptap-icons/arrow-left-icon.tsx +24 -0
- package/template/src/admin/components/tiptap-icons/arrow-right-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/ban-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/blockquote-icon.tsx +44 -0
- package/template/src/admin/components/tiptap-icons/bold-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/case-sensitive-icon.tsx +32 -0
- package/template/src/admin/components/tiptap-icons/check-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/chevron-down-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/chevron-up-icon.tsx +22 -0
- package/template/src/admin/components/tiptap-icons/close-icon.tsx +24 -0
- package/template/src/admin/components/tiptap-icons/code-block-icon.tsx +38 -0
- package/template/src/admin/components/tiptap-icons/code2-icon.tsx +32 -0
- package/template/src/admin/components/tiptap-icons/corner-down-left-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/external-link-icon.tsx +28 -0
- package/template/src/admin/components/tiptap-icons/heading-five-icon.tsx +28 -0
- package/template/src/admin/components/tiptap-icons/heading-four-icon.tsx +28 -0
- package/template/src/admin/components/tiptap-icons/heading-icon.tsx +24 -0
- package/template/src/admin/components/tiptap-icons/heading-one-icon.tsx +28 -0
- package/template/src/admin/components/tiptap-icons/heading-six-icon.tsx +30 -0
- package/template/src/admin/components/tiptap-icons/heading-three-icon.tsx +36 -0
- package/template/src/admin/components/tiptap-icons/heading-two-icon.tsx +28 -0
- package/template/src/admin/components/tiptap-icons/highlighter-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/image-plus-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/italic-icon.tsx +24 -0
- package/template/src/admin/components/tiptap-icons/link-icon.tsx +28 -0
- package/template/src/admin/components/tiptap-icons/list-icon.tsx +56 -0
- package/template/src/admin/components/tiptap-icons/list-ordered-icon.tsx +56 -0
- package/template/src/admin/components/tiptap-icons/list-todo-icon.tsx +50 -0
- package/template/src/admin/components/tiptap-icons/moon-star-icon.tsx +30 -0
- package/template/src/admin/components/tiptap-icons/redo2-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/search-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/strike-icon.tsx +28 -0
- package/template/src/admin/components/tiptap-icons/subscript-icon.tsx +38 -0
- package/template/src/admin/components/tiptap-icons/sun-icon.tsx +58 -0
- package/template/src/admin/components/tiptap-icons/superscript-icon.tsx +38 -0
- package/template/src/admin/components/tiptap-icons/trash-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/underline-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/undo2-icon.tsx +26 -0
- package/template/src/admin/components/tiptap-icons/whole-word-icon.tsx +36 -0
- package/template/src/admin/components/tiptap-node/blockquote-node/blockquote-node.scss +37 -0
- package/template/src/admin/components/tiptap-node/code-block-node/code-block-node.scss +54 -0
- package/template/src/admin/components/tiptap-node/heading-node/heading-node.scss +45 -0
- package/template/src/admin/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts +14 -0
- package/template/src/admin/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss +25 -0
- package/template/src/admin/components/tiptap-node/image-node/image-node.scss +35 -0
- package/template/src/admin/components/tiptap-node/image-upload-node/image-upload-node-extension.ts +161 -0
- package/template/src/admin/components/tiptap-node/image-upload-node/image-upload-node.scss +249 -0
- package/template/src/admin/components/tiptap-node/image-upload-node/image-upload-node.tsx +554 -0
- package/template/src/admin/components/tiptap-node/image-upload-node/index.tsx +1 -0
- package/template/src/admin/components/tiptap-node/list-node/list-node.scss +208 -0
- package/template/src/admin/components/tiptap-node/paragraph-node/paragraph-node.scss +217 -0
- package/template/src/admin/components/tiptap-templates/simple/NOTICE.md +14 -0
- package/template/src/admin/components/tiptap-templates/simple/data/content.json +477 -0
- package/template/src/admin/components/tiptap-templates/simple/simple-editor.scss +97 -0
- package/template/src/admin/components/tiptap-templates/simple/simple-editor.tsx +348 -0
- package/template/src/admin/components/tiptap-templates/simple/theme-toggle.tsx +44 -0
- package/template/src/admin/components/tiptap-ui/blockquote-button/blockquote-button.tsx +122 -0
- package/template/src/admin/components/tiptap-ui/blockquote-button/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/blockquote-button/use-blockquote.ts +273 -0
- package/template/src/admin/components/tiptap-ui/code-block-button/code-block-button.tsx +122 -0
- package/template/src/admin/components/tiptap-ui/code-block-button/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/code-block-button/use-code-block.ts +283 -0
- package/template/src/admin/components/tiptap-ui/color-highlight-button/color-highlight-button.scss +49 -0
- package/template/src/admin/components/tiptap-ui/color-highlight-button/color-highlight-button.tsx +170 -0
- package/template/src/admin/components/tiptap-ui/color-highlight-button/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/color-highlight-button/use-color-highlight.ts +380 -0
- package/template/src/admin/components/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx +231 -0
- package/template/src/admin/components/tiptap-ui/color-highlight-popover/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui/heading-button/heading-button.tsx +124 -0
- package/template/src/admin/components/tiptap-ui/heading-button/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/heading-button/use-heading.ts +348 -0
- package/template/src/admin/components/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +128 -0
- package/template/src/admin/components/tiptap-ui/heading-dropdown-menu/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/heading-dropdown-menu/use-heading-dropdown-menu.ts +132 -0
- package/template/src/admin/components/tiptap-ui/image-upload-button/image-upload-button.tsx +130 -0
- package/template/src/admin/components/tiptap-ui/image-upload-button/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/image-upload-button/use-image-upload.ts +197 -0
- package/template/src/admin/components/tiptap-ui/link-popover/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/link-popover/link-popover.scss +16 -0
- package/template/src/admin/components/tiptap-ui/link-popover/link-popover.tsx +315 -0
- package/template/src/admin/components/tiptap-ui/link-popover/use-link-popover.ts +297 -0
- package/template/src/admin/components/tiptap-ui/list-button/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/list-button/list-button.tsx +120 -0
- package/template/src/admin/components/tiptap-ui/list-button/use-list.ts +353 -0
- package/template/src/admin/components/tiptap-ui/list-dropdown-menu/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +126 -0
- package/template/src/admin/components/tiptap-ui/list-dropdown-menu/use-list-dropdown-menu.ts +220 -0
- package/template/src/admin/components/tiptap-ui/mark-button/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/mark-button/mark-button.tsx +122 -0
- package/template/src/admin/components/tiptap-ui/mark-button/use-mark.ts +219 -0
- package/template/src/admin/components/tiptap-ui/search-and-replace/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/search-and-replace/search-and-replace.scss +300 -0
- package/template/src/admin/components/tiptap-ui/search-and-replace/search-and-replace.tsx +718 -0
- package/template/src/admin/components/tiptap-ui/search-and-replace/use-search-and-replace.ts +528 -0
- package/template/src/admin/components/tiptap-ui/text-align-button/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/text-align-button/text-align-button.tsx +144 -0
- package/template/src/admin/components/tiptap-ui/text-align-button/use-text-align.ts +237 -0
- package/template/src/admin/components/tiptap-ui/undo-redo-button/index.tsx +2 -0
- package/template/src/admin/components/tiptap-ui/undo-redo-button/undo-redo-button.tsx +125 -0
- package/template/src/admin/components/tiptap-ui/undo-redo-button/use-undo-redo.ts +200 -0
- package/template/src/admin/components/tiptap-ui-primitive/badge/badge-colors.scss +395 -0
- package/template/src/admin/components/tiptap-ui-primitive/badge/badge-group.scss +16 -0
- package/template/src/admin/components/tiptap-ui-primitive/badge/badge.scss +99 -0
- package/template/src/admin/components/tiptap-ui-primitive/badge/badge.tsx +44 -0
- package/template/src/admin/components/tiptap-ui-primitive/badge/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/button/button-colors.scss +613 -0
- package/template/src/admin/components/tiptap-ui-primitive/button/button.scss +390 -0
- package/template/src/admin/components/tiptap-ui-primitive/button/button.tsx +158 -0
- package/template/src/admin/components/tiptap-ui-primitive/button/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/button-group/button-group.scss +89 -0
- package/template/src/admin/components/tiptap-ui-primitive/button-group/button-group.tsx +72 -0
- package/template/src/admin/components/tiptap-ui-primitive/button-group/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/card/card.scss +74 -0
- package/template/src/admin/components/tiptap-ui-primitive/card/card.tsx +79 -0
- package/template/src/admin/components/tiptap-ui-primitive/card/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.scss +152 -0
- package/template/src/admin/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.tsx +262 -0
- package/template/src/admin/components/tiptap-ui-primitive/dropdown-menu/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/input/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/input/input.scss +41 -0
- package/template/src/admin/components/tiptap-ui-primitive/input/input.tsx +22 -0
- package/template/src/admin/components/tiptap-ui-primitive/input-group/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/input-group/input-group.scss +225 -0
- package/template/src/admin/components/tiptap-ui-primitive/input-group/input-group.tsx +135 -0
- package/template/src/admin/components/tiptap-ui-primitive/popover/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/popover/popover.scss +64 -0
- package/template/src/admin/components/tiptap-ui-primitive/popover/popover.tsx +35 -0
- package/template/src/admin/components/tiptap-ui-primitive/separator/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/separator/separator.scss +23 -0
- package/template/src/admin/components/tiptap-ui-primitive/separator/separator.tsx +28 -0
- package/template/src/admin/components/tiptap-ui-primitive/spacer/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/spacer/spacer.tsx +24 -0
- package/template/src/admin/components/tiptap-ui-primitive/switch/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/switch/switch.scss +86 -0
- package/template/src/admin/components/tiptap-ui-primitive/switch/switch.tsx +29 -0
- package/template/src/admin/components/tiptap-ui-primitive/textarea/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/textarea/textarea.scss +42 -0
- package/template/src/admin/components/tiptap-ui-primitive/textarea/textarea.tsx +16 -0
- package/template/src/admin/components/tiptap-ui-primitive/toolbar/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/toolbar/toolbar.scss +98 -0
- package/template/src/admin/components/tiptap-ui-primitive/toolbar/toolbar.tsx +203 -0
- package/template/src/admin/components/tiptap-ui-primitive/tooltip/index.tsx +1 -0
- package/template/src/admin/components/tiptap-ui-primitive/tooltip/tooltip.scss +43 -0
- package/template/src/admin/components/tiptap-ui-primitive/tooltip/tooltip.tsx +241 -0
- package/template/src/admin/hooks/use-composed-ref.ts +46 -0
- package/template/src/admin/hooks/use-cursor-visibility.ts +69 -0
- package/template/src/admin/hooks/use-element-rect.ts +166 -0
- package/template/src/admin/hooks/use-is-breakpoint.ts +35 -0
- package/template/src/admin/hooks/use-menu-navigation.ts +204 -0
- package/template/src/admin/hooks/use-scrolling.ts +75 -0
- package/template/src/admin/hooks/use-throttled-callback.ts +48 -0
- package/template/src/admin/hooks/use-tiptap-editor.ts +69 -0
- package/template/src/admin/hooks/use-unmount.ts +21 -0
- package/template/src/admin/hooks/use-window-size.ts +93 -0
- package/template/src/admin/html.ts +17 -0
- package/template/src/admin/lib/tiptap-utils.ts +652 -0
- package/template/src/admin/navigation-tree.tsx +82 -0
- package/template/src/admin/preview.ts +73 -0
- package/template/src/admin/styles/_cms-theme.scss +58 -0
- package/template/src/admin/styles/_keyframe-animations.scss +91 -0
- package/template/src/admin/styles/_variables.scss +298 -0
- package/template/src/assets/favicon.svg +1 -0
- package/template/src/assets/logo.svg +1 -0
- package/template/src/astro-env.d.ts +1 -0
- package/template/src/content.config.ts +12 -0
- package/template/src/db/bootstrap.ts +36 -0
- package/template/src/db/client.ts +7 -0
- package/template/src/db/schema.ts +45 -0
- package/template/src/documents/service.ts +178 -0
- package/template/src/documents/validation.ts +9 -0
- package/template/src/env.ts +9 -0
- package/template/src/index.ts +10 -0
- package/template/src/locales.ts +10 -0
- package/template/src/media/service.ts +120 -0
- package/template/src/navigation/service.ts +171 -0
- package/template/src/pages/[locale]/cms-preview-shell.astro +12 -0
- package/template/src/pages/cms-preview-shell.astro +5 -0
- package/template/src/publish/deploy-hook.ts +42 -0
- package/template/src/publish/record.ts +20 -0
- package/template/src/publish/service.ts +135 -0
- package/template/src/scss.d.ts +5 -0
- package/template/src/site.config.ts +7 -0
- package/template/src/sql.d.ts +4 -0
- package/template/src/starlight/cms-origin.ts +4 -0
- package/template/src/starlight/loader.ts +21 -0
- package/template/src/starlight/preview-render.ts +162 -0
- package/template/src/starlight/preview-shell.astro +14 -0
- package/template/src/starlight/render.ts +59 -0
- package/template/src/starlight/schema.ts +34 -0
- package/template/src/starlight/sidebar.ts +35 -0
- package/template/src/starlight/snapshot.ts +47 -0
- package/template/src/starlight/source.ts +26 -0
- package/template/src/starlight/tiptap.ts +29 -0
- package/template/src/styles/starlight.css +12 -0
- package/template/test-files/content.test.template.ts +82 -0
- package/template/test-files/deploy-hook.test.template.ts +32 -0
- package/template/test-files/media.test.template.ts +39 -0
- package/template/test-files/publishing.integration.test.template.ts +156 -0
- package/template/tsconfig.admin.json +18 -0
- package/template/tsconfig.json +23 -0
- package/template/worker-configuration.d.ts +15389 -0
- package/template/wrangler.jsonc +26 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Editor } from "@tiptap/react"
|
|
2
|
+
import { useWindowSize } from "@/hooks/use-window-size"
|
|
3
|
+
import { useBodyRect } from "@/hooks/use-element-rect"
|
|
4
|
+
import { useEffect } from "react"
|
|
5
|
+
|
|
6
|
+
export interface CursorVisibilityOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The Tiptap editor instance
|
|
9
|
+
*/
|
|
10
|
+
editor?: Editor | null
|
|
11
|
+
/**
|
|
12
|
+
* Reference to the toolbar element that may obscure the cursor
|
|
13
|
+
*/
|
|
14
|
+
overlayHeight?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Custom hook that ensures the cursor remains visible when typing in a Tiptap editor.
|
|
19
|
+
* Automatically scrolls the window when the cursor would be hidden by the toolbar.
|
|
20
|
+
*
|
|
21
|
+
* @param options.editor The Tiptap editor instance
|
|
22
|
+
* @param options.overlayHeight Toolbar height to account for
|
|
23
|
+
* @returns The bounding rect of the body
|
|
24
|
+
*/
|
|
25
|
+
export function useCursorVisibility({
|
|
26
|
+
editor,
|
|
27
|
+
overlayHeight = 0,
|
|
28
|
+
}: CursorVisibilityOptions) {
|
|
29
|
+
const { height: windowHeight } = useWindowSize()
|
|
30
|
+
const rect = useBodyRect({
|
|
31
|
+
enabled: true,
|
|
32
|
+
throttleMs: 100,
|
|
33
|
+
useResizeObserver: true,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const ensureCursorVisibility = () => {
|
|
38
|
+
if (!editor) return
|
|
39
|
+
|
|
40
|
+
const { state, view } = editor
|
|
41
|
+
if (!view.hasFocus()) return
|
|
42
|
+
|
|
43
|
+
// Get current cursor position coordinates
|
|
44
|
+
const { from } = state.selection
|
|
45
|
+
const cursorCoords = view.coordsAtPos(from)
|
|
46
|
+
|
|
47
|
+
if (windowHeight < rect.height && cursorCoords) {
|
|
48
|
+
const availableSpace = windowHeight - cursorCoords.top
|
|
49
|
+
|
|
50
|
+
// If the cursor is hidden behind the overlay or offscreen, scroll it into view
|
|
51
|
+
if (availableSpace < overlayHeight) {
|
|
52
|
+
const targetCursorY = Math.max(windowHeight / 2, overlayHeight)
|
|
53
|
+
const currentScrollY = window.scrollY
|
|
54
|
+
const cursorAbsoluteY = cursorCoords.top + currentScrollY
|
|
55
|
+
const newScrollY = cursorAbsoluteY - targetCursorY
|
|
56
|
+
|
|
57
|
+
window.scrollTo({
|
|
58
|
+
top: Math.max(0, newScrollY),
|
|
59
|
+
behavior: "smooth",
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
ensureCursorVisibility()
|
|
66
|
+
}, [editor, overlayHeight, windowHeight, rect.height])
|
|
67
|
+
|
|
68
|
+
return rect
|
|
69
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from "react"
|
|
4
|
+
import { useThrottledCallback } from "@/hooks/use-throttled-callback"
|
|
5
|
+
|
|
6
|
+
export type RectState = Omit<DOMRect, "toJSON">
|
|
7
|
+
|
|
8
|
+
export interface ElementRectOptions {
|
|
9
|
+
/**
|
|
10
|
+
* The element to track. Can be an Element, ref, or selector string.
|
|
11
|
+
* Defaults to document.body if not provided.
|
|
12
|
+
*/
|
|
13
|
+
element?: Element | React.RefObject<Element> | string | null
|
|
14
|
+
/**
|
|
15
|
+
* Whether to enable rect tracking
|
|
16
|
+
*/
|
|
17
|
+
enabled?: boolean
|
|
18
|
+
/**
|
|
19
|
+
* Throttle delay in milliseconds for rect updates
|
|
20
|
+
*/
|
|
21
|
+
throttleMs?: number
|
|
22
|
+
/**
|
|
23
|
+
* Whether to use ResizeObserver for more accurate tracking
|
|
24
|
+
*/
|
|
25
|
+
useResizeObserver?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const initialRect: RectState = {
|
|
29
|
+
x: 0,
|
|
30
|
+
y: 0,
|
|
31
|
+
width: 0,
|
|
32
|
+
height: 0,
|
|
33
|
+
top: 0,
|
|
34
|
+
right: 0,
|
|
35
|
+
bottom: 0,
|
|
36
|
+
left: 0,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const isSSR = typeof window === "undefined"
|
|
40
|
+
const hasResizeObserver = !isSSR && typeof ResizeObserver !== "undefined"
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Helper function to check if code is running on client side
|
|
44
|
+
*/
|
|
45
|
+
const isClientSide = (): boolean => !isSSR
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Custom hook that tracks an element's bounding rectangle and updates on resize, scroll, etc.
|
|
49
|
+
*
|
|
50
|
+
* @param options Configuration options for element rect tracking
|
|
51
|
+
* @returns The current bounding rectangle of the element
|
|
52
|
+
*/
|
|
53
|
+
export function useElementRect({
|
|
54
|
+
element,
|
|
55
|
+
enabled = true,
|
|
56
|
+
throttleMs = 100,
|
|
57
|
+
useResizeObserver = true,
|
|
58
|
+
}: ElementRectOptions = {}): RectState {
|
|
59
|
+
const [rect, setRect] = useState<RectState>(initialRect)
|
|
60
|
+
|
|
61
|
+
const getTargetElement = useCallback((): Element | null => {
|
|
62
|
+
if (!enabled || !isClientSide()) return null
|
|
63
|
+
|
|
64
|
+
if (!element) {
|
|
65
|
+
return document.body
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof element === "string") {
|
|
69
|
+
return document.querySelector(element)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if ("current" in element) {
|
|
73
|
+
return element.current
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return element
|
|
77
|
+
}, [element, enabled])
|
|
78
|
+
|
|
79
|
+
const updateRect = useThrottledCallback(
|
|
80
|
+
() => {
|
|
81
|
+
if (!enabled || !isClientSide()) return
|
|
82
|
+
|
|
83
|
+
const targetElement = getTargetElement()
|
|
84
|
+
if (!targetElement) {
|
|
85
|
+
setRect(initialRect)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const newRect = targetElement.getBoundingClientRect()
|
|
90
|
+
setRect({
|
|
91
|
+
x: newRect.x,
|
|
92
|
+
y: newRect.y,
|
|
93
|
+
width: newRect.width,
|
|
94
|
+
height: newRect.height,
|
|
95
|
+
top: newRect.top,
|
|
96
|
+
right: newRect.right,
|
|
97
|
+
bottom: newRect.bottom,
|
|
98
|
+
left: newRect.left,
|
|
99
|
+
})
|
|
100
|
+
},
|
|
101
|
+
throttleMs,
|
|
102
|
+
[enabled, getTargetElement],
|
|
103
|
+
{ leading: true, trailing: true }
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (!enabled || !isClientSide()) {
|
|
108
|
+
setRect(initialRect)
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const targetElement = getTargetElement()
|
|
113
|
+
if (!targetElement) return
|
|
114
|
+
|
|
115
|
+
updateRect()
|
|
116
|
+
|
|
117
|
+
const cleanup: (() => void)[] = []
|
|
118
|
+
|
|
119
|
+
if (useResizeObserver && hasResizeObserver) {
|
|
120
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
121
|
+
window.requestAnimationFrame(updateRect)
|
|
122
|
+
})
|
|
123
|
+
resizeObserver.observe(targetElement)
|
|
124
|
+
cleanup.push(() => resizeObserver.disconnect())
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const handleUpdate = () => updateRect()
|
|
128
|
+
|
|
129
|
+
window.addEventListener("scroll", handleUpdate, true)
|
|
130
|
+
window.addEventListener("resize", handleUpdate, true)
|
|
131
|
+
|
|
132
|
+
cleanup.push(() => {
|
|
133
|
+
window.removeEventListener("scroll", handleUpdate)
|
|
134
|
+
window.removeEventListener("resize", handleUpdate)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
return () => {
|
|
138
|
+
cleanup.forEach((fn) => fn())
|
|
139
|
+
setRect(initialRect)
|
|
140
|
+
}
|
|
141
|
+
}, [enabled, getTargetElement, updateRect, useResizeObserver])
|
|
142
|
+
|
|
143
|
+
return rect
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Convenience hook for tracking document.body rect
|
|
148
|
+
*/
|
|
149
|
+
export function useBodyRect(
|
|
150
|
+
options: Omit<ElementRectOptions, "element"> = {}
|
|
151
|
+
): RectState {
|
|
152
|
+
return useElementRect({
|
|
153
|
+
...options,
|
|
154
|
+
element: isClientSide() ? document.body : null,
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Convenience hook for tracking a ref element's rect
|
|
160
|
+
*/
|
|
161
|
+
export function useRefRect<T extends Element>(
|
|
162
|
+
ref: React.RefObject<T>,
|
|
163
|
+
options: Omit<ElementRectOptions, "element"> = {}
|
|
164
|
+
): RectState {
|
|
165
|
+
return useElementRect({ ...options, element: ref })
|
|
166
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useEffect, useState } from "react"
|
|
2
|
+
|
|
3
|
+
type BreakpointMode = "min" | "max"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook to detect whether the current viewport matches a given breakpoint rule.
|
|
7
|
+
* Example:
|
|
8
|
+
* useIsBreakpoint("max", 768) // true when width < 768
|
|
9
|
+
* useIsBreakpoint("min", 1024) // true when width >= 1024
|
|
10
|
+
*/
|
|
11
|
+
export function useIsBreakpoint(
|
|
12
|
+
mode: BreakpointMode = "max",
|
|
13
|
+
breakpoint = 768
|
|
14
|
+
) {
|
|
15
|
+
const [matches, setMatches] = useState<boolean | undefined>(undefined)
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const query =
|
|
19
|
+
mode === "min"
|
|
20
|
+
? `(min-width: ${breakpoint}px)`
|
|
21
|
+
: `(max-width: ${breakpoint - 1}px)`
|
|
22
|
+
|
|
23
|
+
const mql = window.matchMedia(query)
|
|
24
|
+
const onChange = (e: MediaQueryListEvent) => setMatches(e.matches)
|
|
25
|
+
|
|
26
|
+
// Set initial value
|
|
27
|
+
setMatches(mql.matches)
|
|
28
|
+
|
|
29
|
+
// Add listener
|
|
30
|
+
mql.addEventListener("change", onChange)
|
|
31
|
+
return () => mql.removeEventListener("change", onChange)
|
|
32
|
+
}, [mode, breakpoint])
|
|
33
|
+
|
|
34
|
+
return !!matches
|
|
35
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { Editor } from "@tiptap/react"
|
|
2
|
+
import { useEffect, useState } from "react"
|
|
3
|
+
|
|
4
|
+
type Orientation = "horizontal" | "vertical" | "both"
|
|
5
|
+
|
|
6
|
+
interface MenuNavigationOptions<T> {
|
|
7
|
+
/**
|
|
8
|
+
* The Tiptap editor instance, if using with a Tiptap editor.
|
|
9
|
+
*/
|
|
10
|
+
editor?: Editor | null
|
|
11
|
+
/**
|
|
12
|
+
* Reference to the container element for handling keyboard events.
|
|
13
|
+
*/
|
|
14
|
+
containerRef?: React.RefObject<HTMLElement | null>
|
|
15
|
+
/**
|
|
16
|
+
* Search query that affects the selected item.
|
|
17
|
+
*/
|
|
18
|
+
query?: string
|
|
19
|
+
/**
|
|
20
|
+
* Array of items to navigate through.
|
|
21
|
+
*/
|
|
22
|
+
items: T[]
|
|
23
|
+
/**
|
|
24
|
+
* Callback fired when an item is selected.
|
|
25
|
+
*/
|
|
26
|
+
onSelect?: (item: T) => void
|
|
27
|
+
/**
|
|
28
|
+
* Callback fired when the menu should close.
|
|
29
|
+
*/
|
|
30
|
+
onClose?: () => void
|
|
31
|
+
/**
|
|
32
|
+
* The navigation orientation of the menu.
|
|
33
|
+
* @default "vertical"
|
|
34
|
+
*/
|
|
35
|
+
orientation?: Orientation
|
|
36
|
+
/**
|
|
37
|
+
* Whether to automatically select the first item when the menu opens.
|
|
38
|
+
* @default true
|
|
39
|
+
*/
|
|
40
|
+
autoSelectFirstItem?: boolean
|
|
41
|
+
/**
|
|
42
|
+
* Whether Tab/Shift+Tab should loop through the items.
|
|
43
|
+
* Disable this for ARIA toolbars, where Tab must leave the toolbar.
|
|
44
|
+
* @default true
|
|
45
|
+
*/
|
|
46
|
+
loopOnTab?: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Hook that implements keyboard navigation for dropdown menus and command palettes.
|
|
51
|
+
*
|
|
52
|
+
* Handles arrow keys, tab, home/end, enter for selection, and escape to close.
|
|
53
|
+
* Works with both Tiptap editors and regular DOM elements.
|
|
54
|
+
*
|
|
55
|
+
* @param options - Configuration options for the menu navigation
|
|
56
|
+
* @returns Object containing the selected index and a setter function
|
|
57
|
+
*/
|
|
58
|
+
export function useMenuNavigation<T>({
|
|
59
|
+
editor,
|
|
60
|
+
containerRef,
|
|
61
|
+
query,
|
|
62
|
+
items,
|
|
63
|
+
onSelect,
|
|
64
|
+
onClose,
|
|
65
|
+
orientation = "vertical",
|
|
66
|
+
autoSelectFirstItem = true,
|
|
67
|
+
loopOnTab = true,
|
|
68
|
+
}: MenuNavigationOptions<T>) {
|
|
69
|
+
const [selectedIndex, setSelectedIndex] = useState<number>(
|
|
70
|
+
autoSelectFirstItem ? 0 : -1
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const handleKeyboardNavigation = (event: KeyboardEvent) => {
|
|
75
|
+
if (!items.length) return false
|
|
76
|
+
|
|
77
|
+
const moveNext = () =>
|
|
78
|
+
setSelectedIndex((currentIndex) => {
|
|
79
|
+
if (currentIndex === -1) return 0
|
|
80
|
+
return (currentIndex + 1) % items.length
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const movePrev = () =>
|
|
84
|
+
setSelectedIndex((currentIndex) => {
|
|
85
|
+
if (currentIndex === -1) return items.length - 1
|
|
86
|
+
return (currentIndex - 1 + items.length) % items.length
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
switch (event.key) {
|
|
90
|
+
case "ArrowUp": {
|
|
91
|
+
if (orientation === "horizontal") return false
|
|
92
|
+
event.preventDefault()
|
|
93
|
+
movePrev()
|
|
94
|
+
return true
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case "ArrowDown": {
|
|
98
|
+
if (orientation === "horizontal") return false
|
|
99
|
+
event.preventDefault()
|
|
100
|
+
moveNext()
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
case "ArrowLeft": {
|
|
105
|
+
if (orientation === "vertical") return false
|
|
106
|
+
event.preventDefault()
|
|
107
|
+
movePrev()
|
|
108
|
+
return true
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
case "ArrowRight": {
|
|
112
|
+
if (orientation === "vertical") return false
|
|
113
|
+
event.preventDefault()
|
|
114
|
+
moveNext()
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
case "Tab": {
|
|
119
|
+
if (!loopOnTab) return false
|
|
120
|
+
event.preventDefault()
|
|
121
|
+
if (event.shiftKey) {
|
|
122
|
+
movePrev()
|
|
123
|
+
} else {
|
|
124
|
+
moveNext()
|
|
125
|
+
}
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
case "Home": {
|
|
130
|
+
event.preventDefault()
|
|
131
|
+
setSelectedIndex(0)
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case "End": {
|
|
136
|
+
event.preventDefault()
|
|
137
|
+
setSelectedIndex(items.length - 1)
|
|
138
|
+
return true
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
case "Enter": {
|
|
142
|
+
if (event.isComposing) return false
|
|
143
|
+
event.preventDefault()
|
|
144
|
+
if (selectedIndex !== -1 && items[selectedIndex]) {
|
|
145
|
+
onSelect?.(items[selectedIndex])
|
|
146
|
+
}
|
|
147
|
+
return true
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
case "Escape": {
|
|
151
|
+
if (!onClose) return false
|
|
152
|
+
event.preventDefault()
|
|
153
|
+
onClose?.()
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
default:
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let targetElement: HTMLElement | null = null
|
|
163
|
+
|
|
164
|
+
if (editor) {
|
|
165
|
+
targetElement = editor.view.dom
|
|
166
|
+
} else if (containerRef?.current) {
|
|
167
|
+
targetElement = containerRef.current
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (targetElement) {
|
|
171
|
+
targetElement.addEventListener("keydown", handleKeyboardNavigation, true)
|
|
172
|
+
|
|
173
|
+
return () => {
|
|
174
|
+
targetElement?.removeEventListener(
|
|
175
|
+
"keydown",
|
|
176
|
+
handleKeyboardNavigation,
|
|
177
|
+
true
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return undefined
|
|
183
|
+
}, [
|
|
184
|
+
editor,
|
|
185
|
+
containerRef,
|
|
186
|
+
items,
|
|
187
|
+
selectedIndex,
|
|
188
|
+
onSelect,
|
|
189
|
+
onClose,
|
|
190
|
+
orientation,
|
|
191
|
+
loopOnTab,
|
|
192
|
+
])
|
|
193
|
+
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
if (query) {
|
|
196
|
+
setSelectedIndex(autoSelectFirstItem ? 0 : -1)
|
|
197
|
+
}
|
|
198
|
+
}, [query, autoSelectFirstItem])
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
selectedIndex: items.length ? selectedIndex : undefined,
|
|
202
|
+
setSelectedIndex,
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { RefObject } from "react"
|
|
2
|
+
import { useEffect, useState } from "react"
|
|
3
|
+
|
|
4
|
+
type ScrollTarget = RefObject<HTMLElement> | Window | null | undefined
|
|
5
|
+
type EventTargetWithScroll = Window | HTMLElement | Document
|
|
6
|
+
|
|
7
|
+
interface UseScrollingOptions {
|
|
8
|
+
debounce?: number
|
|
9
|
+
fallbackToDocument?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useScrolling(
|
|
13
|
+
target?: ScrollTarget,
|
|
14
|
+
options: UseScrollingOptions = {}
|
|
15
|
+
): boolean {
|
|
16
|
+
const { debounce = 150, fallbackToDocument = true } = options
|
|
17
|
+
const [isScrolling, setIsScrolling] = useState(false)
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
// Resolve element or window
|
|
21
|
+
const element: EventTargetWithScroll =
|
|
22
|
+
target && typeof Window !== "undefined" && target instanceof Window
|
|
23
|
+
? target
|
|
24
|
+
: ((target as RefObject<HTMLElement>)?.current ?? window)
|
|
25
|
+
|
|
26
|
+
// Mobile: fallback to document when using window
|
|
27
|
+
const eventTarget: EventTargetWithScroll =
|
|
28
|
+
fallbackToDocument &&
|
|
29
|
+
element === window &&
|
|
30
|
+
typeof document !== "undefined"
|
|
31
|
+
? document
|
|
32
|
+
: element
|
|
33
|
+
|
|
34
|
+
const on = (
|
|
35
|
+
el: EventTargetWithScroll,
|
|
36
|
+
event: string,
|
|
37
|
+
handler: EventListener
|
|
38
|
+
) => el.addEventListener(event, handler, true)
|
|
39
|
+
|
|
40
|
+
const off = (
|
|
41
|
+
el: EventTargetWithScroll,
|
|
42
|
+
event: string,
|
|
43
|
+
handler: EventListener
|
|
44
|
+
) => el.removeEventListener(event, handler)
|
|
45
|
+
|
|
46
|
+
let timeout: ReturnType<typeof setTimeout>
|
|
47
|
+
const supportsScrollEnd = element === window && "onscrollend" in window
|
|
48
|
+
|
|
49
|
+
const handleScroll: EventListener = () => {
|
|
50
|
+
if (!isScrolling) setIsScrolling(true)
|
|
51
|
+
|
|
52
|
+
if (!supportsScrollEnd) {
|
|
53
|
+
clearTimeout(timeout)
|
|
54
|
+
timeout = setTimeout(() => setIsScrolling(false), debounce)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const handleScrollEnd: EventListener = () => setIsScrolling(false)
|
|
59
|
+
|
|
60
|
+
on(eventTarget, "scroll", handleScroll)
|
|
61
|
+
if (supportsScrollEnd) {
|
|
62
|
+
on(eventTarget, "scrollend", handleScrollEnd)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return () => {
|
|
66
|
+
off(eventTarget, "scroll", handleScroll)
|
|
67
|
+
if (supportsScrollEnd) {
|
|
68
|
+
off(eventTarget, "scrollend", handleScrollEnd)
|
|
69
|
+
}
|
|
70
|
+
clearTimeout(timeout)
|
|
71
|
+
}
|
|
72
|
+
}, [target, debounce, fallbackToDocument, isScrolling])
|
|
73
|
+
|
|
74
|
+
return isScrolling
|
|
75
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import throttle from "lodash.throttle"
|
|
2
|
+
|
|
3
|
+
import { useUnmount } from "@/hooks/use-unmount"
|
|
4
|
+
import { useMemo } from "react"
|
|
5
|
+
|
|
6
|
+
interface ThrottleSettings {
|
|
7
|
+
leading?: boolean | undefined
|
|
8
|
+
trailing?: boolean | undefined
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const defaultOptions: ThrottleSettings = {
|
|
12
|
+
leading: false,
|
|
13
|
+
trailing: true,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A hook that returns a throttled callback function.
|
|
18
|
+
*
|
|
19
|
+
* @param fn The function to throttle
|
|
20
|
+
* @param wait The time in ms to wait before calling the function
|
|
21
|
+
* @param dependencies The dependencies to watch for changes
|
|
22
|
+
* @param options The throttle options
|
|
23
|
+
*/
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
+
export function useThrottledCallback<T extends (...args: any[]) => any>(
|
|
26
|
+
fn: T,
|
|
27
|
+
wait = 250,
|
|
28
|
+
dependencies: React.DependencyList = [],
|
|
29
|
+
options: ThrottleSettings = defaultOptions
|
|
30
|
+
): {
|
|
31
|
+
(this: ThisParameterType<T>, ...args: Parameters<T>): ReturnType<T>
|
|
32
|
+
cancel: () => void
|
|
33
|
+
flush: () => void
|
|
34
|
+
} {
|
|
35
|
+
const handler = useMemo(
|
|
36
|
+
() => throttle<T>(fn, wait, options),
|
|
37
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
38
|
+
dependencies
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
useUnmount(() => {
|
|
42
|
+
handler.cancel()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return handler
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default useThrottledCallback
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Editor } from "@tiptap/react"
|
|
2
|
+
import { useCurrentEditor, useEditorState } from "@tiptap/react"
|
|
3
|
+
import { useEffect, useState } from "react"
|
|
4
|
+
|
|
5
|
+
function getActivePageEditor(editor: Editor): Editor | null {
|
|
6
|
+
const storage = editor.storage as unknown as Record<string, unknown>
|
|
7
|
+
const pages = storage.pages as { activeEditor?: Editor | null } | undefined
|
|
8
|
+
if (!pages || !("activeEditor" in pages)) return null
|
|
9
|
+
return pages.activeEditor ?? null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useTiptapEditor(providedEditor?: Editor | null): {
|
|
13
|
+
editor: Editor | null
|
|
14
|
+
editorState?: Editor["state"]
|
|
15
|
+
canCommand?: Editor["can"]
|
|
16
|
+
} {
|
|
17
|
+
const { editor: coreEditor } = useCurrentEditor()
|
|
18
|
+
const mainEditor = providedEditor ?? coreEditor
|
|
19
|
+
|
|
20
|
+
const [storageEditor, setStorageEditor] = useState<Editor | null>(null)
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!mainEditor) {
|
|
24
|
+
setStorageEditor(null)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const updateHandler = () =>
|
|
29
|
+
setStorageEditor(getActivePageEditor(mainEditor))
|
|
30
|
+
|
|
31
|
+
updateHandler()
|
|
32
|
+
|
|
33
|
+
mainEditor.on("update", updateHandler)
|
|
34
|
+
mainEditor.on("selectionUpdate", updateHandler)
|
|
35
|
+
|
|
36
|
+
return () => {
|
|
37
|
+
mainEditor.off("update", updateHandler)
|
|
38
|
+
mainEditor.off("selectionUpdate", updateHandler)
|
|
39
|
+
}
|
|
40
|
+
}, [mainEditor])
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!storageEditor) return
|
|
44
|
+
|
|
45
|
+
const handleDestroy = () => setStorageEditor(null)
|
|
46
|
+
|
|
47
|
+
storageEditor.on("destroy", handleDestroy)
|
|
48
|
+
return () => {
|
|
49
|
+
storageEditor.off("destroy", handleDestroy)
|
|
50
|
+
}
|
|
51
|
+
}, [storageEditor])
|
|
52
|
+
|
|
53
|
+
const editorState = useEditorState({
|
|
54
|
+
editor: storageEditor ?? mainEditor,
|
|
55
|
+
selector(context) {
|
|
56
|
+
if (!context.editor) {
|
|
57
|
+
return { editor: null, editorState: undefined, canCommand: undefined }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
editor: context.editor,
|
|
62
|
+
editorState: context.editor.state,
|
|
63
|
+
canCommand: context.editor.can,
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
return editorState ?? { editor: null }
|
|
69
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useRef, useEffect } from "react"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook that executes a callback when the component unmounts.
|
|
5
|
+
*
|
|
6
|
+
* @param callback Function to be called on component unmount
|
|
7
|
+
*/
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
export const useUnmount = (callback: (...args: Array<any>) => any) => {
|
|
10
|
+
const ref = useRef(callback)
|
|
11
|
+
ref.current = callback
|
|
12
|
+
|
|
13
|
+
useEffect(
|
|
14
|
+
() => () => {
|
|
15
|
+
ref.current()
|
|
16
|
+
},
|
|
17
|
+
[]
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default useUnmount
|