react-embed-docs 0.1.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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +422 -0
  3. package/dist/client/components/Breadcrumbs.d.ts +21 -0
  4. package/dist/client/components/Breadcrumbs.d.ts.map +1 -0
  5. package/dist/client/components/Breadcrumbs.js +123 -0
  6. package/dist/client/components/DocsLayout.d.ts +20 -0
  7. package/dist/client/components/DocsLayout.d.ts.map +1 -0
  8. package/dist/client/components/DocsLayout.js +387 -0
  9. package/dist/client/components/DocumentContent.d.ts +5 -0
  10. package/dist/client/components/DocumentContent.d.ts.map +1 -0
  11. package/dist/client/components/DocumentContent.js +15 -0
  12. package/dist/client/components/DocumentEdit.d.ts +6 -0
  13. package/dist/client/components/DocumentEdit.d.ts.map +1 -0
  14. package/dist/client/components/DocumentEdit.js +153 -0
  15. package/dist/client/components/DocumentList.d.ts +5 -0
  16. package/dist/client/components/DocumentList.d.ts.map +1 -0
  17. package/dist/client/components/DocumentList.js +39 -0
  18. package/dist/client/components/DocumentProvider.d.ts +42 -0
  19. package/dist/client/components/DocumentProvider.d.ts.map +1 -0
  20. package/dist/client/components/DocumentProvider.js +47 -0
  21. package/dist/client/components/DocumentView.d.ts +6 -0
  22. package/dist/client/components/DocumentView.d.ts.map +1 -0
  23. package/dist/client/components/DocumentView.js +58 -0
  24. package/dist/client/components/DragOverlayItem.d.ts +5 -0
  25. package/dist/client/components/DragOverlayItem.d.ts.map +1 -0
  26. package/dist/client/components/DragOverlayItem.js +9 -0
  27. package/dist/client/components/EmojiPicker.d.ts +8 -0
  28. package/dist/client/components/EmojiPicker.d.ts.map +1 -0
  29. package/dist/client/components/EmojiPicker.js +48 -0
  30. package/dist/client/components/ExportButton.d.ts +22 -0
  31. package/dist/client/components/ExportButton.d.ts.map +1 -0
  32. package/dist/client/components/ExportButton.js +97 -0
  33. package/dist/client/components/Layout.d.ts +7 -0
  34. package/dist/client/components/Layout.d.ts.map +1 -0
  35. package/dist/client/components/Layout.js +172 -0
  36. package/dist/client/components/ReactEmbedDocs.d.ts +8 -0
  37. package/dist/client/components/ReactEmbedDocs.d.ts.map +1 -0
  38. package/dist/client/components/ReactEmbedDocs.js +8 -0
  39. package/dist/client/components/SearchInput.d.ts +2 -0
  40. package/dist/client/components/SearchInput.d.ts.map +1 -0
  41. package/dist/client/components/SearchInput.js +7 -0
  42. package/dist/client/components/Sidebar.d.ts +10 -0
  43. package/dist/client/components/Sidebar.d.ts.map +1 -0
  44. package/dist/client/components/Sidebar.js +176 -0
  45. package/dist/client/components/SortableTreeItem.d.ts +13 -0
  46. package/dist/client/components/SortableTreeItem.d.ts.map +1 -0
  47. package/dist/client/components/SortableTreeItem.js +24 -0
  48. package/dist/client/components/VersionHistory.d.ts +14 -0
  49. package/dist/client/components/VersionHistory.d.ts.map +1 -0
  50. package/dist/client/components/VersionHistory.js +102 -0
  51. package/dist/client/hooks/useCollaboration.d.ts +99 -0
  52. package/dist/client/hooks/useCollaboration.d.ts.map +1 -0
  53. package/dist/client/hooks/useCollaboration.js +180 -0
  54. package/dist/client/hooks/useDocsQuery.d.ts +84 -0
  55. package/dist/client/hooks/useDocsQuery.d.ts.map +1 -0
  56. package/dist/client/hooks/useDocsQuery.js +241 -0
  57. package/dist/client/hooks/useExport.d.ts +31 -0
  58. package/dist/client/hooks/useExport.d.ts.map +1 -0
  59. package/dist/client/hooks/useExport.js +66 -0
  60. package/dist/client/hooks/useFileUpload.d.ts +44 -0
  61. package/dist/client/hooks/useFileUpload.d.ts.map +1 -0
  62. package/dist/client/hooks/useFileUpload.js +193 -0
  63. package/dist/client/hooks/useSystemTheme.d.ts +2 -0
  64. package/dist/client/hooks/useSystemTheme.d.ts.map +1 -0
  65. package/dist/client/hooks/useSystemTheme.js +19 -0
  66. package/dist/client/hooks/useVersions.d.ts +105 -0
  67. package/dist/client/hooks/useVersions.d.ts.map +1 -0
  68. package/dist/client/hooks/useVersions.js +129 -0
  69. package/dist/client/index.d.ts +23 -0
  70. package/dist/client/index.d.ts.map +1 -0
  71. package/dist/client/index.js +18 -0
  72. package/dist/client/lib/blocknoteTheme.d.ts +13 -0
  73. package/dist/client/lib/blocknoteTheme.d.ts.map +1 -0
  74. package/dist/client/lib/blocknoteTheme.js +76 -0
  75. package/dist/client/lib/path.d.ts +8 -0
  76. package/dist/client/lib/path.d.ts.map +1 -0
  77. package/dist/client/lib/path.js +30 -0
  78. package/dist/client/providers/DocumentProvider.d.ts +1 -0
  79. package/dist/client/providers/DocumentProvider.d.ts.map +1 -0
  80. package/dist/client/providers/DocumentProvider.js +1 -0
  81. package/dist/server/CollaborationService.d.ts +134 -0
  82. package/dist/server/CollaborationService.d.ts.map +1 -0
  83. package/dist/server/CollaborationService.js +307 -0
  84. package/dist/server/DocsService.d.ts +115 -0
  85. package/dist/server/DocsService.d.ts.map +1 -0
  86. package/dist/server/DocsService.js +512 -0
  87. package/dist/server/ExportService.d.ts +106 -0
  88. package/dist/server/ExportService.d.ts.map +1 -0
  89. package/dist/server/ExportService.js +501 -0
  90. package/dist/server/FilesService.d.ts +44 -0
  91. package/dist/server/FilesService.d.ts.map +1 -0
  92. package/dist/server/FilesService.js +78 -0
  93. package/dist/server/VersioningService.d.ts +112 -0
  94. package/dist/server/VersioningService.d.ts.map +1 -0
  95. package/dist/server/VersioningService.js +264 -0
  96. package/dist/server/db.d.ts +7 -0
  97. package/dist/server/db.d.ts.map +1 -0
  98. package/dist/server/db.js +22 -0
  99. package/dist/server/index.d.ts +55 -0
  100. package/dist/server/index.d.ts.map +1 -0
  101. package/dist/server/index.js +36 -0
  102. package/dist/server/routes.d.ts +9 -0
  103. package/dist/server/routes.d.ts.map +1 -0
  104. package/dist/server/routes.js +483 -0
  105. package/dist/server/schema.d.ts +587 -0
  106. package/dist/server/schema.d.ts.map +1 -0
  107. package/dist/server/schema.js +126 -0
  108. package/dist/shared/types.d.ts +314 -0
  109. package/dist/shared/types.d.ts.map +1 -0
  110. package/dist/shared/types.js +48 -0
  111. package/drizzle/migrations/0000_gray_monster_badoon.sql +88 -0
  112. package/drizzle/migrations/meta/0000_snapshot.json +574 -0
  113. package/drizzle/migrations/meta/_journal.json +13 -0
  114. package/package.json +109 -0
  115. package/styles/docs.css +981 -0
@@ -0,0 +1,42 @@
1
+ import { ReactNode } from 'react';
2
+ import type { DocumentSummary } from '../../shared/types.js';
3
+ import { Params } from '../lib/path.js';
4
+ /**
5
+ * Theme type for the document provider
6
+ */
7
+ export type DocumentTheme = 'light' | 'dark';
8
+ /**
9
+ * Document context value interface
10
+ */
11
+ export interface DocumentContextValue {
12
+ /** Current theme */
13
+ theme: DocumentTheme;
14
+ path: string;
15
+ locale: string;
16
+ params: Params;
17
+ onCreate: () => void;
18
+ onOpen: (path: string) => void;
19
+ onEdit: (slug: string) => void;
20
+ onDelete: (doc: DocumentSummary) => void;
21
+ }
22
+ /**
23
+ * Props for DocumentProvider component
24
+ */
25
+ export interface DocumentProviderProps {
26
+ children: ReactNode;
27
+ theme?: DocumentTheme;
28
+ params: Params;
29
+ path: string;
30
+ onNavigate?: (path: string) => void;
31
+ locale?: 'ru' | 'en';
32
+ }
33
+ /**
34
+ * Provider component for document context
35
+ */
36
+ export declare function DocumentProvider({ children, params, theme, path, onNavigate, locale, }: DocumentProviderProps): JSX.Element;
37
+ /**
38
+ * Hook to access document context
39
+ * @throws Error if used outside of DocumentProvider
40
+ */
41
+ export declare function useDocument(): DocumentContextValue;
42
+ //# sourceMappingURL=DocumentProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocumentProvider.d.ts","sourceRoot":"","sources":["../../../src/client/components/DocumentProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,SAAS,EAA2B,MAAM,OAAO,CAAA;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEvC;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,CAAA;AAE5C;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,oBAAoB;IACpB,KAAK,EAAE,aAAa,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,QAAQ,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;CACzC;AAOD;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,SAAS,CAAA;IACnB,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;CACrB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,KAAe,EACf,IAAI,EACJ,UAAU,EACV,MAAa,GACd,EAAE,qBAAqB,GAAG,GAAG,CAAC,OAAO,CAyCrC;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,oBAAoB,CAQlD"}
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext } from 'react';
4
+ /**
5
+ * Document context
6
+ */
7
+ const DocumentContext = createContext(null);
8
+ /**
9
+ * Provider component for document context
10
+ */
11
+ export function DocumentProvider({ children, params, theme = 'light', path, onNavigate, locale = 'en', }) {
12
+ console.log('params', params);
13
+ const onOpen = useCallback((path) => {
14
+ onNavigate?.(path);
15
+ }, [onNavigate]);
16
+ const onEdit = useCallback((slug) => {
17
+ onNavigate?.(`${slug}/edit`);
18
+ }, [onNavigate]);
19
+ const onCreate = useCallback(() => {
20
+ onNavigate?.(`${path}/new`);
21
+ }, [onNavigate]);
22
+ const onDelete = useCallback((doc) => {
23
+ // alert
24
+ }, []);
25
+ const value = {
26
+ theme,
27
+ path,
28
+ locale,
29
+ params,
30
+ onCreate,
31
+ onEdit,
32
+ onOpen,
33
+ onDelete,
34
+ };
35
+ return (_jsx(DocumentContext.Provider, { value: value, children: children }));
36
+ }
37
+ /**
38
+ * Hook to access document context
39
+ * @throws Error if used outside of DocumentProvider
40
+ */
41
+ export function useDocument() {
42
+ const context = useContext(DocumentContext);
43
+ if (context === null) {
44
+ throw new Error('useDocument must be used within a DocumentProvider');
45
+ }
46
+ return context;
47
+ }
@@ -0,0 +1,6 @@
1
+ import '@blocknote/mantine/style.css';
2
+ interface DocumentViewProps {
3
+ }
4
+ export declare function DocumentView({}: DocumentViewProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=DocumentView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocumentView.d.ts","sourceRoot":"","sources":["../../../src/client/components/DocumentView.tsx"],"names":[],"mappings":"AAIA,OAAO,8BAA8B,CAAA;AASrC,UAAU,iBAAiB;CAAG;AAU9B,wBAAgB,YAAY,CAAC,EAAE,EAAE,iBAAiB,2CA0HjD"}
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { BlockNoteView } from '@blocknote/mantine';
4
+ import '@blocknote/mantine/style.css';
5
+ import { useCreateBlockNote } from '@blocknote/react';
6
+ import { Clock, Edit, Loader2, User } from 'lucide-react';
7
+ import { useCallback, useEffect, useState } from 'react';
8
+ import { useDocumentQuery } from '../hooks/useDocsQuery.js';
9
+ import { blockNoteTheme } from '../lib/blocknoteTheme.js';
10
+ import { useDocument } from './DocumentProvider.js';
11
+ import { ExportButton } from './ExportButton.js';
12
+ // Default content when document has no content
13
+ const getDefaultContent = () => [
14
+ {
15
+ type: 'paragraph',
16
+ content: '',
17
+ },
18
+ ];
19
+ export function DocumentView({}) {
20
+ const { params, onCreate, onEdit, theme } = useDocument();
21
+ const { data: doc, isLoading, error } = useDocumentQuery(params.documentSlug);
22
+ const [hasLoaded, setHasLoaded] = useState(false);
23
+ // Creates a new editor instance in read-only mode
24
+ const editor = useCreateBlockNote({
25
+ initialContent: getDefaultContent(),
26
+ });
27
+ const handleEdit = useCallback(() => {
28
+ if (!doc)
29
+ return;
30
+ onEdit(params.slugs.join('/') + '/' + doc.slug);
31
+ }, [doc, onEdit]);
32
+ // Load document content when available
33
+ useEffect(() => {
34
+ if (doc?.content && editor && !hasLoaded) {
35
+ try {
36
+ const content = typeof doc.content === 'string'
37
+ ? JSON.parse(doc.content)
38
+ : doc.content;
39
+ if (Array.isArray(content) && content.length > 0) {
40
+ editor.replaceBlocks(editor.document, content);
41
+ }
42
+ }
43
+ catch (e) {
44
+ console.error('Failed to parse document content:', e);
45
+ }
46
+ setHasLoaded(true);
47
+ }
48
+ }, [doc, editor, hasLoaded]);
49
+ if (isLoading) {
50
+ return (_jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: _jsx(Loader2, { className: "h-8 w-8 animate-spin text-gray-400" }) }));
51
+ }
52
+ if (error || !doc) {
53
+ return (_jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: _jsx("p", { className: "text-red-500", children: "Failed to load document. Please try again." }) }));
54
+ }
55
+ return (_jsxs("div", { className: "max-w-[80%] mx-auto", children: [_jsxs("div", { className: "mb-8", children: [_jsxs("div", { className: "flex items-start justify-between gap-4 mb-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [doc.emoji && (_jsx("span", { className: "text-5xl leading-none", role: "img", "aria-label": "document emoji", children: doc.emoji })), _jsx("h1", { className: "text-3xl font-bold", children: doc.title })] }), _jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [doc && (_jsx(ExportButton, { documentId: doc.id, documentTitle: doc.title })), _jsxs("button", { onClick: handleEdit, className: "px-3 py-1.5 text-sm bg-secondary text-white rounded-md hover:bg-primary flex items-center gap-2 transition-colors", children: [_jsx(Edit, { className: "h-4 w-4" }), "Edit"] })] })] }), _jsxs("div", { className: "flex items-center gap-6 text-sm text-gray-500", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(User, { className: "h-4 w-4" }), _jsxs("span", { children: ["Author ID: ", doc.authorId] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Clock, { className: "h-4 w-4" }), _jsxs("span", { children: ["Updated", ' ', doc.updatedAt
56
+ ? new Date(doc.updatedAt).toLocaleDateString()
57
+ : 'N/A'] })] })] })] }), doc.cover && (_jsx("div", { className: "mb-8 -mx-4 sm:-mx-8 lg:-mx-12", children: _jsx("div", { className: "relative w-full max-h-80 overflow-hidden", children: _jsx("img", { src: doc.cover, alt: doc.title, className: "w-full h-full object-cover" }) }) })), _jsx("div", { children: _jsx(BlockNoteView, { editor: editor, editable: false, theme: blockNoteTheme[theme], className: "[&_.bn-editor]:p-0 [&_.bn-editor]:px-0 [&_.bn-container]:max-w-none [&_.bn-editor]:!px-0" }) })] }));
58
+ }
@@ -0,0 +1,5 @@
1
+ import { FlattenedItem } from '../../shared/types';
2
+ export declare function DragOverlayItem({ doc }: {
3
+ doc: FlattenedItem;
4
+ }): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=DragOverlayItem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DragOverlayItem.d.ts","sourceRoot":"","sources":["../../../src/client/components/DragOverlayItem.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,wBAAgB,eAAe,CAAC,EAAE,GAAG,EAAE,EAAE;IAAE,GAAG,EAAE,aAAa,CAAA;CAAE,2CAyB9D"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { FileTextIcon, FolderOpenIcon } from 'lucide-react';
3
+ // Drag Overlay Item - shown while dragging
4
+ export function DragOverlayItem({ doc }) {
5
+ return (_jsxs("div", { className: [
6
+ 'flex items-center gap-1 w-full px-2 py-1.5 text-sm rounded-md shadow-lg',
7
+ 'bg-white dark:bg-zinc-800 border border-border opacity-90 cursor-grabbing',
8
+ ].join(' '), style: { paddingLeft: doc.depth * 12 + 8 }, children: [_jsx("div", { className: "w-5" }), doc.emoji ? (_jsx("span", { className: "text-base shrink-0", children: doc.emoji })) : doc.hasChildren ? (_jsx(FolderOpenIcon, { className: "h-4 w-4 shrink-0 text-muted-foreground" })) : (_jsx(FileTextIcon, { className: "h-4 w-4 shrink-0 text-muted-foreground" })), _jsx("span", { className: "truncate flex-1 font-medium", children: doc.title })] }));
9
+ }
@@ -0,0 +1,8 @@
1
+ interface EmojiPickerProps {
2
+ value: string | null | undefined;
3
+ onChange: (emoji: string) => void;
4
+ className?: string;
5
+ }
6
+ export declare function EmojiPicker({ value, onChange, className }: EmojiPickerProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=EmojiPicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmojiPicker.d.ts","sourceRoot":"","sources":["../../../src/client/components/EmojiPicker.tsx"],"names":[],"mappings":"AAIA,UAAU,gBAAgB;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAgBD,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,gBAAgB,2CAoE3E"}
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useRef, useEffect } from 'react';
4
+ // Popular emojis for documents
5
+ const POPULAR_EMOJIS = [
6
+ '📄', '📝', '📊', '📈', '📉', '📑', '📋', '📁', '📂', '🗂️',
7
+ '📦', '📮', '📯', '📰', '📱', '💻', '🖥️', '📀', '💿', '📼',
8
+ '🌸', '🌺', '🌻', '🌼', '🌷', '🌹', '🌵', '🌲', '🌳', '🌴',
9
+ '🍏', '🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🫐',
10
+ '⚽', '🏀', '🏈', '⚾', '🥎', '🎾', '🏐', '🏉', '🥏', '🎱',
11
+ '🚗', '🚕', '🚙', '🚌', '🚎', '🏎️', '🚓', '🚑', '🚒', '🚐',
12
+ '❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '❣️',
13
+ '⭐', '🌟', '✨', '💫', '⚡', '🔥', '💥', '☄️', '☀️', '🌤️',
14
+ '🔴', '🟠', '🟡', '🟢', '🔵', '🟣', '⚫', '⚪', '🟤', '🔘',
15
+ '✅', '❌', '⭕', '🚫', '💯', '💢', '♨️', '🚷', '🚯', '🚳',
16
+ ];
17
+ export function EmojiPicker({ value, onChange, className }) {
18
+ const [isOpen, setIsOpen] = useState(false);
19
+ const dropdownRef = useRef(null);
20
+ // Close dropdown when clicking outside
21
+ useEffect(() => {
22
+ function handleClickOutside(event) {
23
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
24
+ setIsOpen(false);
25
+ }
26
+ }
27
+ if (isOpen) {
28
+ document.addEventListener('mousedown', handleClickOutside);
29
+ }
30
+ return () => {
31
+ document.removeEventListener('mousedown', handleClickOutside);
32
+ };
33
+ }, [isOpen]);
34
+ return (_jsxs("div", { ref: dropdownRef, className: "relative", children: [_jsx("button", { onClick: () => setIsOpen(!isOpen), className: [
35
+ 'h-10 w-10 rounded-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900',
36
+ 'hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-center text-xl transition-colors',
37
+ className,
38
+ ].join(' '), children: value || '📄' }), isOpen && (_jsxs("div", { className: "absolute top-full left-0 mt-1 z-50 w-64 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-md shadow-lg p-2", children: [_jsx("div", { className: "grid grid-cols-8 gap-1", children: POPULAR_EMOJIS.map((emoji) => (_jsx("button", { onClick: () => {
39
+ onChange(emoji);
40
+ setIsOpen(false);
41
+ }, className: [
42
+ 'h-8 w-8 rounded hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center justify-center text-lg transition-colors',
43
+ value === emoji && 'bg-gray-100 dark:bg-gray-800',
44
+ ].join(' '), children: emoji }, emoji))) }), value && (_jsx("button", { onClick: () => {
45
+ onChange('');
46
+ setIsOpen(false);
47
+ }, className: "mt-2 w-full py-1.5 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-800 rounded transition-colors", children: "Remove emoji" }))] }))] }));
48
+ }
@@ -0,0 +1,22 @@
1
+ export interface ExportButtonProps {
2
+ documentId: string;
3
+ documentTitle?: string;
4
+ baseUrl?: string;
5
+ className?: string;
6
+ buttonClassName?: string;
7
+ menuClassName?: string;
8
+ }
9
+ /**
10
+ * Export button with dropdown menu for different formats
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * <ExportButton
15
+ * documentId="doc-123"
16
+ * documentTitle="My Document"
17
+ * baseUrl="/api"
18
+ * />
19
+ * ```
20
+ */
21
+ export declare function ExportButton({ documentId, documentTitle, baseUrl, className, buttonClassName, menuClassName, }: ExportButtonProps): import("react/jsx-runtime").JSX.Element;
22
+ //# sourceMappingURL=ExportButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExportButton.d.ts","sourceRoot":"","sources":["../../../src/client/components/ExportButton.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAyCD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,EAC3B,UAAU,EACV,aAAa,EACb,OAAO,EACP,SAAc,EACd,eAAoB,EACpB,aAAkB,GACnB,EAAE,iBAAiB,2CAoHnB"}
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useRef, useEffect } from 'react';
3
+ import { Download, FileText, FileType, FileCode, File } from 'lucide-react';
4
+ import { useExport } from '../hooks/useExport.js';
5
+ const EXPORT_OPTIONS = [
6
+ {
7
+ format: 'docx',
8
+ label: 'Microsoft Word',
9
+ description: 'Editable document format',
10
+ icon: _jsx(FileText, { className: "w-4 h-4 text-blue-600" }),
11
+ extension: 'docx',
12
+ },
13
+ {
14
+ format: 'pdf',
15
+ label: 'PDF Document',
16
+ description: 'Print-ready format',
17
+ icon: _jsx(FileType, { className: "w-4 h-4 text-red-600" }),
18
+ extension: 'pdf',
19
+ },
20
+ {
21
+ format: 'html',
22
+ label: 'HTML Page',
23
+ description: 'Web page format',
24
+ icon: _jsx(FileCode, { className: "w-4 h-4 text-orange-600" }),
25
+ extension: 'html',
26
+ },
27
+ {
28
+ format: 'markdown',
29
+ label: 'Markdown',
30
+ description: 'Plain text with formatting',
31
+ icon: _jsx(File, { className: "w-4 h-4 text-gray-600" }),
32
+ extension: 'md',
33
+ },
34
+ ];
35
+ /**
36
+ * Export button with dropdown menu for different formats
37
+ *
38
+ * @example
39
+ * ```tsx
40
+ * <ExportButton
41
+ * documentId="doc-123"
42
+ * documentTitle="My Document"
43
+ * baseUrl="/api"
44
+ * />
45
+ * ```
46
+ */
47
+ export function ExportButton({ documentId, documentTitle, baseUrl, className = '', buttonClassName = '', menuClassName = '', }) {
48
+ const [isOpen, setIsOpen] = useState(false);
49
+ const menuRef = useRef(null);
50
+ const { exportDocument, isExporting } = useExport({ baseUrl });
51
+ // Close menu when clicking outside
52
+ useEffect(() => {
53
+ function handleClickOutside(event) {
54
+ if (menuRef.current && !menuRef.current.contains(event.target)) {
55
+ setIsOpen(false);
56
+ }
57
+ }
58
+ document.addEventListener('mousedown', handleClickOutside);
59
+ return () => document.removeEventListener('mousedown', handleClickOutside);
60
+ }, []);
61
+ const handleExport = async (format) => {
62
+ setIsOpen(false);
63
+ const baseName = documentTitle
64
+ ? `${documentTitle.replace(/[^a-zA-Z0-9\s-]/g, '').replace(/\s+/g, '-')}`
65
+ : 'document';
66
+ const extension = format === 'markdown' ? 'md' : format;
67
+ const defaultFilename = `${baseName}.${extension}`;
68
+ try {
69
+ await exportDocument(documentId, format, defaultFilename);
70
+ }
71
+ catch (error) {
72
+ const message = error instanceof Error ? error.message : 'Export failed';
73
+ // Show alert for now - could be replaced with a toast notification
74
+ if (message.includes('docx') || message.includes('puppeteer') || message.includes('playwright')) {
75
+ alert(`Missing dependency: ${message}\n\nInstall with:\n${format === 'docx' ? 'npm install docx' : 'npm install puppeteer'}`);
76
+ }
77
+ else {
78
+ alert(`Export failed: ${message}`);
79
+ }
80
+ }
81
+ };
82
+ return (_jsxs("div", { className: `relative inline-block ${className}`, ref: menuRef, children: [_jsxs("button", { onClick: () => setIsOpen(!isOpen), disabled: isExporting, className: `
83
+ inline-flex items-center gap-2 px-3 py-1.5
84
+ bg-white border border-gray-300 rounded-md
85
+ text-sm font-medium text-gray-700
86
+ hover:bg-gray-50 hover:border-gray-400
87
+ focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1
88
+ disabled:opacity-50 disabled:cursor-not-allowed
89
+ transition-colors
90
+ ${buttonClassName}
91
+ `, children: [_jsx(Download, { className: `w-4 h-4 ${isExporting ? 'animate-bounce' : ''}` }), _jsx("span", { children: "Export" }), _jsx("svg", { className: `w-4 h-4 transition-transform ${isOpen ? 'rotate-180' : ''}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })] }), isOpen && (_jsx("div", { className: `
92
+ absolute right-0 mt-2 w-64
93
+ bg-white border border-gray-200 rounded-lg shadow-lg
94
+ z-50 overflow-hidden
95
+ ${menuClassName}
96
+ `, children: _jsxs("div", { className: "py-1", children: [_jsx("div", { className: "px-3 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider", children: "Export as" }), EXPORT_OPTIONS.map((option) => (_jsxs("button", { onClick: () => handleExport(option.format), disabled: isExporting, className: "\n w-full flex items-start gap-3 px-3 py-2.5\n text-left hover:bg-gray-50\n focus:outline-none focus:bg-gray-50\n disabled:opacity-50 disabled:cursor-not-allowed\n transition-colors\n ", children: [_jsx("div", { className: "flex-shrink-0 mt-0.5", children: option.icon }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium text-gray-900", children: option.label }), _jsx("div", { className: "text-xs text-gray-500", children: option.description })] }), _jsxs("div", { className: "flex-shrink-0 text-xs text-gray-400 uppercase", children: [".", option.extension] })] }, option.format)))] }) }))] }));
97
+ }
@@ -0,0 +1,7 @@
1
+ interface DocsLayoutProps {
2
+ children: React.ReactNode;
3
+ userAvatar?: React.ReactNode;
4
+ }
5
+ export declare function Layout({ children, userAvatar }: DocsLayoutProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=Layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Layout.d.ts","sourceRoot":"","sources":["../../../src/client/components/Layout.tsx"],"names":[],"mappings":"AAgBA,UAAU,eAAe;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B;AAmJD,wBAAgB,MAAM,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,eAAe,2CA8J/D"}
@@ -0,0 +1,172 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { FileText, PanelLeftCloseIcon, PanelLeftOpenIcon, Plus, Search, } from 'lucide-react';
4
+ import { useEffect, useRef, useState } from 'react';
5
+ import { useDocumentsQuery } from '../hooks/useDocsQuery.js';
6
+ import { Breadcrumbs } from './Breadcrumbs.js';
7
+ import { useDocument } from './DocumentProvider.js';
8
+ import { Sidebar } from './Sidebar.js';
9
+ // Simple debounce hook
10
+ function useDebounce(value, delay) {
11
+ const [debouncedValue, setDebouncedValue] = useState(value);
12
+ useEffect(() => {
13
+ const timer = setTimeout(() => {
14
+ setDebouncedValue(value);
15
+ }, delay);
16
+ return () => clearTimeout(timer);
17
+ }, [value, delay]);
18
+ return debouncedValue;
19
+ }
20
+ // Build breadcrumb path for a document
21
+ function buildBreadcrumb(doc, allDocs) {
22
+ const parts = [doc.title];
23
+ let current = doc;
24
+ while (current.parentId) {
25
+ const parent = allDocs.find((d) => d.id === current.parentId);
26
+ if (!parent)
27
+ break;
28
+ parts.unshift(parent.title);
29
+ current = parent;
30
+ }
31
+ return parts.join(' / ');
32
+ }
33
+ // Build full path from root to document (for URL navigation)
34
+ function buildDocumentPath(docId, allDocs, basePath) {
35
+ const pathParts = [];
36
+ const visited = new Set();
37
+ let currentId = docId;
38
+ while (currentId) {
39
+ // Prevent infinite loops from circular references
40
+ if (visited.has(currentId))
41
+ break;
42
+ visited.add(currentId);
43
+ const doc = allDocs.find((d) => d.id === currentId);
44
+ if (!doc)
45
+ break;
46
+ pathParts.unshift(doc.slug || doc.id);
47
+ currentId = doc.parentId ?? null;
48
+ }
49
+ if (pathParts.length === 0)
50
+ return basePath;
51
+ return `${basePath}/${pathParts.join('/')}`;
52
+ }
53
+ // Get all ancestor IDs of a document (for auto-expanding)
54
+ function getAncestorIds(docId, allDocs) {
55
+ const ancestors = [];
56
+ const visited = new Set();
57
+ let currentId = docId;
58
+ while (currentId) {
59
+ if (visited.has(currentId))
60
+ break;
61
+ visited.add(currentId);
62
+ const doc = allDocs.find((d) => d.id === currentId);
63
+ if (!doc)
64
+ break;
65
+ if (doc.parentId) {
66
+ ancestors.push(doc.parentId);
67
+ currentId = doc.parentId;
68
+ }
69
+ else {
70
+ break;
71
+ }
72
+ }
73
+ return ancestors;
74
+ }
75
+ // Extract text snippets from BlockNote content
76
+ function extractTextSnippets(content, query) {
77
+ if (!content || typeof content !== 'object')
78
+ return [];
79
+ const snippets = [];
80
+ const queryLower = query.toLowerCase();
81
+ const extractFromBlock = (block) => {
82
+ if (!block || typeof block !== 'object')
83
+ return '';
84
+ const b = block;
85
+ // Handle text content
86
+ if (b.content && Array.isArray(b.content)) {
87
+ return b.content
88
+ .map((c) => {
89
+ if (typeof c === 'string')
90
+ return c;
91
+ if (c && typeof c === 'object') {
92
+ const contentItem = c;
93
+ if (contentItem.text && typeof contentItem.text === 'string') {
94
+ return contentItem.text;
95
+ }
96
+ }
97
+ return '';
98
+ })
99
+ .join(' ');
100
+ }
101
+ // Handle children
102
+ if (b.children && Array.isArray(b.children)) {
103
+ return b.children.map(extractFromBlock).join(' ');
104
+ }
105
+ return '';
106
+ };
107
+ const contentObj = content;
108
+ if (contentObj.content && Array.isArray(contentObj.content)) {
109
+ for (const block of contentObj.content) {
110
+ const text = extractFromBlock(block);
111
+ if (text.toLowerCase().includes(queryLower)) {
112
+ // Find the position of the match
113
+ const matchIndex = text.toLowerCase().indexOf(queryLower);
114
+ const start = Math.max(0, matchIndex - 20);
115
+ const end = Math.min(text.length, matchIndex + query.length + 60);
116
+ let snippet = text.slice(start, end);
117
+ // Add ellipsis if truncated
118
+ if (start > 0)
119
+ snippet = '...' + snippet;
120
+ if (end < text.length)
121
+ snippet = snippet + '...';
122
+ snippets.push(snippet);
123
+ if (snippets.length >= 2)
124
+ break; // Limit to 2 snippets
125
+ }
126
+ }
127
+ }
128
+ return snippets;
129
+ }
130
+ export function Layout({ children, userAvatar }) {
131
+ const { onOpen, onCreate, params } = useDocument();
132
+ const [expandedFolders, setExpandedFolders] = useState(new Set());
133
+ const [isSidebarOpen, setIsSidebarOpen] = useState(true);
134
+ // Header search state
135
+ const [headerSearchQuery, setHeaderSearchQuery] = useState('');
136
+ const [showSearchResults, setShowSearchResults] = useState(false);
137
+ const debouncedSearchQuery = useDebounce(headerSearchQuery, 300);
138
+ const searchInputRef = useRef(null);
139
+ const { data } = useDocumentsQuery();
140
+ const documents = data?.documents ?? [];
141
+ // Search query for header search
142
+ const { data: searchResultsData } = useDocumentsQuery(debouncedSearchQuery.length > 0 ? { search: debouncedSearchQuery } : {});
143
+ const searchResults = searchResultsData?.documents ?? [];
144
+ // Handle click outside to close search results
145
+ useEffect(() => {
146
+ const handleClickOutside = (event) => {
147
+ if (searchInputRef.current &&
148
+ !searchInputRef.current.contains(event.target)) {
149
+ setShowSearchResults(false);
150
+ }
151
+ };
152
+ document.addEventListener('mousedown', handleClickOutside);
153
+ return () => document.removeEventListener('mousedown', handleClickOutside);
154
+ }, []);
155
+ return (_jsxs("div", { className: "flex h-screen w-full", children: [_jsx(Sidebar, { isOpen: isSidebarOpen, onToggle: setIsSidebarOpen, documents: documents }), _jsxs("main", { className: "flex-1 flex flex-col min-w-0 overflow-hidden", children: [params.mode === 'view' && (_jsxs("header", { className: "h-16 border-b border-border flex items-center justify-between px-6 shrink-0", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("button", { onClick: () => setIsSidebarOpen(!isSidebarOpen), className: "p-2 hover:bg-primary rounded-md transition-colors text-muted-foreground", title: isSidebarOpen ? 'Close sidebar' : 'Open sidebar', children: isSidebarOpen ? (_jsx(PanelLeftCloseIcon, { className: "h-6 w-6" })) : (_jsx(PanelLeftOpenIcon, { className: "h-6 w-6" })) }), _jsx(Breadcrumbs, { homeLabel: "Documents" })] }), _jsxs("div", { className: "flex items-center gap-4 py-2", children: [_jsxs("div", { ref: searchInputRef, className: "relative w-64", children: [_jsx(Search, { className: "absolute left-2 top-2.5 h-4 w-4 z-10" }), _jsx("input", { type: "text", placeholder: "Search documentation...", className: "w-full pl-8 pr-3 py-2 text-sm border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent", value: headerSearchQuery, onChange: (e) => {
156
+ setHeaderSearchQuery(e.target.value);
157
+ setShowSearchResults(e.target.value.length > 0);
158
+ }, onFocus: () => {
159
+ if (headerSearchQuery.length > 0) {
160
+ setShowSearchResults(true);
161
+ }
162
+ } }), showSearchResults && headerSearchQuery.length > 0 && (_jsx("div", { className: "absolute top-full left-0 right-0 mt-1 bg-background border border-border rounded-md shadow-2xl max-h-80 overflow-y-auto z-50", children: searchResults.length === 0 ? (_jsxs("div", { className: "px-4 py-3 text-sm text-gray-500", children: ["No results found for \"", headerSearchQuery, "\""] })) : (_jsx("div", { className: "py-1", children: searchResults.map((doc) => {
163
+ const breadcrumb = doc.parentId
164
+ ? buildBreadcrumb(doc, documents)
165
+ : null;
166
+ return (_jsx("button", { onClick: () => {
167
+ onOpen(doc.id);
168
+ setHeaderSearchQuery('');
169
+ setShowSearchResults(false);
170
+ }, className: "w-full px-3 py-2 text-left hover:bg-primary hover:text-primary-foreground transition-colors", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("div", { className: "shrink-0 mt-0.5", children: doc.emoji ? (_jsx("span", { className: "text-base", children: doc.emoji })) : (_jsx(FileText, { className: "h-4 w-4 text-muted-foreground" })) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium text-sm truncate", children: doc.title }), breadcrumb && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: breadcrumb }))] })] }) }, doc.id));
171
+ }) })) }))] }), _jsxs("button", { onClick: onCreate, className: "px-4 py-2 bg-secondary text-white text-sm rounded-md hover:bg-gray-800 flex items-center gap-2 transition-colors", children: [_jsx(Plus, { className: "h-4 w-4" }), "Create"] }), userAvatar] })] })), _jsx("div", { className: "flex-1 overflow-auto p-6", children: children })] })] }));
172
+ }
@@ -0,0 +1,8 @@
1
+ export interface ReactEmbedDocsProps {
2
+ path?: string;
3
+ theme?: 'light' | 'dark';
4
+ locale?: 'ru' | 'en';
5
+ onNavigate?: (path: string) => void;
6
+ }
7
+ export declare function ReactEmbedDocs(props: ReactEmbedDocsProps): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=ReactEmbedDocs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactEmbedDocs.d.ts","sourceRoot":"","sources":["../../../src/client/components/ReactEmbedDocs.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACxB,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;IACpB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,2CAcxD"}
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { processPath } from '../lib/path.js';
3
+ import { DocumentContent } from './DocumentContent.js';
4
+ import { DocumentProvider } from './DocumentProvider.js';
5
+ import { Layout } from './Layout.js';
6
+ export function ReactEmbedDocs(props) {
7
+ return (_jsx(DocumentProvider, { theme: props.theme, path: props.path ?? '', locale: props.locale, onNavigate: props.onNavigate, params: processPath(props.path), children: _jsx(Layout, { children: _jsx(DocumentContent, {}) }) }));
8
+ }
@@ -0,0 +1,2 @@
1
+ export declare function SearchInput(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=SearchInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchInput.d.ts","sourceRoot":"","sources":["../../../src/client/components/SearchInput.tsx"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,4CAc1B"}
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { SearchIcon } from 'lucide-react';
3
+ import { useState } from 'react';
4
+ export function SearchInput() {
5
+ const [searchQuery, setSearchQuery] = useState('');
6
+ return (_jsxs("div", { className: "relative", children: [_jsx(SearchIcon, { className: "absolute left-2 top-2.5 h-4 w-4 text-gray-400" }), _jsx("input", { type: "text", placeholder: "Search docs...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "w-full pl-8 pr-3 py-2 text-sm border border-gray-300 dark:border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" })] }));
7
+ }
@@ -0,0 +1,10 @@
1
+ import { DocumentSummary } from '../../shared/types';
2
+ interface IProps {
3
+ isOpen: boolean;
4
+ currentDocId?: string | null;
5
+ documents: DocumentSummary[];
6
+ onToggle: (isOpen: boolean) => void;
7
+ }
8
+ export declare function Sidebar({ isOpen, onToggle, documents, currentDocId }: IProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=Sidebar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sidebar.d.ts","sourceRoot":"","sources":["../../../src/client/components/Sidebar.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAE,eAAe,EAAiB,MAAM,oBAAoB,CAAA;AAMnE,UAAU,MAAM;IACd,MAAM,EAAE,OAAO,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,SAAS,EAAE,eAAe,EAAE,CAAA;IAC5B,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;CACpC;AACD,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,MAAM,2CA0Q5E"}