@imjp/writenex-astro 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 (141) hide show
  1. package/README.md +539 -0
  2. package/dist/chunk-5PM6EQE5.js +151 -0
  3. package/dist/chunk-5PM6EQE5.js.map +1 -0
  4. package/dist/chunk-7XU5X6CW.js +1331 -0
  5. package/dist/chunk-7XU5X6CW.js.map +1 -0
  6. package/dist/chunk-AAOQHQPU.js +574 -0
  7. package/dist/chunk-AAOQHQPU.js.map +1 -0
  8. package/dist/chunk-CF2XXJFF.js +1410 -0
  9. package/dist/chunk-CF2XXJFF.js.map +1 -0
  10. package/dist/chunk-CRPZUUDU.js +52 -0
  11. package/dist/chunk-CRPZUUDU.js.map +1 -0
  12. package/dist/chunk-CYLDJ3HZ.js +310 -0
  13. package/dist/chunk-CYLDJ3HZ.js.map +1 -0
  14. package/dist/chunk-KIKIPIFA.js +1 -0
  15. package/dist/chunk-KIKIPIFA.js.map +1 -0
  16. package/dist/chunk-XNTQTTJU.js +145 -0
  17. package/dist/chunk-XNTQTTJU.js.map +1 -0
  18. package/dist/client/index.css +2 -0
  19. package/dist/client/index.css.map +1 -0
  20. package/dist/client/index.js +375 -0
  21. package/dist/client/index.js.map +1 -0
  22. package/dist/client/styles.css +584 -0
  23. package/dist/client/variables.css +304 -0
  24. package/dist/config/index.d.ts +54 -0
  25. package/dist/config/index.js +38 -0
  26. package/dist/config/index.js.map +1 -0
  27. package/dist/config-BmEdBDo_.d.ts +220 -0
  28. package/dist/content-BWR52vD-.d.ts +64 -0
  29. package/dist/discovery/index.d.ts +310 -0
  30. package/dist/discovery/index.js +38 -0
  31. package/dist/discovery/index.js.map +1 -0
  32. package/dist/errors-C0iYiDTv.d.ts +107 -0
  33. package/dist/filesystem/index.d.ts +1292 -0
  34. package/dist/filesystem/index.js +203 -0
  35. package/dist/filesystem/index.js.map +1 -0
  36. package/dist/image-FP7w5ZIs.d.ts +47 -0
  37. package/dist/index.d.ts +64 -0
  38. package/dist/index.js +151 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/loader-55LWCXHA.js +12 -0
  41. package/dist/loader-55LWCXHA.js.map +1 -0
  42. package/dist/loader-CrdnaAWR.d.ts +327 -0
  43. package/dist/server/index.d.ts +357 -0
  44. package/dist/server/index.js +37 -0
  45. package/dist/server/index.js.map +1 -0
  46. package/package.json +94 -0
  47. package/src/client/App.tsx +900 -0
  48. package/src/client/components/ConfigPanel/ConfigPanel.css +553 -0
  49. package/src/client/components/ConfigPanel/ConfigPanel.tsx +396 -0
  50. package/src/client/components/ConfigPanel/index.ts +6 -0
  51. package/src/client/components/CreateContentModal/CreateContentModal.css +327 -0
  52. package/src/client/components/CreateContentModal/CreateContentModal.tsx +216 -0
  53. package/src/client/components/CreateContentModal/index.ts +7 -0
  54. package/src/client/components/Editor/Editor.css +885 -0
  55. package/src/client/components/Editor/Editor.tsx +484 -0
  56. package/src/client/components/Editor/ImageDialog.css +344 -0
  57. package/src/client/components/Editor/ImageDialog.tsx +367 -0
  58. package/src/client/components/Editor/LinkDialog.css +326 -0
  59. package/src/client/components/Editor/LinkDialog.tsx +332 -0
  60. package/src/client/components/Editor/index.ts +6 -0
  61. package/src/client/components/FrontmatterForm/FrontmatterForm.css +468 -0
  62. package/src/client/components/FrontmatterForm/FrontmatterForm.tsx +914 -0
  63. package/src/client/components/FrontmatterForm/index.ts +7 -0
  64. package/src/client/components/Header/Header.css +300 -0
  65. package/src/client/components/Header/Header.tsx +300 -0
  66. package/src/client/components/Header/index.ts +7 -0
  67. package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.css +239 -0
  68. package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.tsx +151 -0
  69. package/src/client/components/KeyboardShortcuts/index.ts +6 -0
  70. package/src/client/components/LazyEditor.tsx +75 -0
  71. package/src/client/components/LiveRegion/LiveRegion.css +19 -0
  72. package/src/client/components/LiveRegion/LiveRegion.tsx +60 -0
  73. package/src/client/components/LiveRegion/index.ts +7 -0
  74. package/src/client/components/SearchReplace/SearchReplacePanel.css +300 -0
  75. package/src/client/components/SearchReplace/SearchReplacePanel.tsx +332 -0
  76. package/src/client/components/SearchReplace/index.ts +7 -0
  77. package/src/client/components/SelectCollectionModal/SelectCollectionModal.css +308 -0
  78. package/src/client/components/SelectCollectionModal/SelectCollectionModal.tsx +223 -0
  79. package/src/client/components/SelectCollectionModal/index.ts +7 -0
  80. package/src/client/components/Sidebar/Sidebar.css +570 -0
  81. package/src/client/components/Sidebar/Sidebar.tsx +617 -0
  82. package/src/client/components/Sidebar/index.ts +7 -0
  83. package/src/client/components/SkipLink/SkipLink.css +51 -0
  84. package/src/client/components/SkipLink/SkipLink.tsx +67 -0
  85. package/src/client/components/SkipLink/index.ts +7 -0
  86. package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.css +233 -0
  87. package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.tsx +160 -0
  88. package/src/client/components/UnsavedChangesModal/index.ts +1 -0
  89. package/src/client/components/VersionHistory/DiffViewer.css +430 -0
  90. package/src/client/components/VersionHistory/DiffViewer.tsx +383 -0
  91. package/src/client/components/VersionHistory/VersionActions.css +318 -0
  92. package/src/client/components/VersionHistory/VersionActions.tsx +277 -0
  93. package/src/client/components/VersionHistory/VersionHistoryPanel.css +369 -0
  94. package/src/client/components/VersionHistory/VersionHistoryPanel.tsx +469 -0
  95. package/src/client/components/VersionHistory/index.ts +9 -0
  96. package/src/client/context/ApiContext.tsx +154 -0
  97. package/src/client/context/ThemeContext.tsx +172 -0
  98. package/src/client/hooks/useAnnounce.ts +201 -0
  99. package/src/client/hooks/useApi.ts +374 -0
  100. package/src/client/hooks/useArrowNavigation.ts +286 -0
  101. package/src/client/hooks/useAutosave.ts +241 -0
  102. package/src/client/hooks/useFocusTrap.ts +178 -0
  103. package/src/client/hooks/useKeyboardShortcuts.ts +203 -0
  104. package/src/client/hooks/useSearch.ts +206 -0
  105. package/src/client/hooks/useVersionHistory.ts +451 -0
  106. package/src/client/index.tsx +70 -0
  107. package/src/client/styles.css +584 -0
  108. package/src/client/utils/focus.ts +57 -0
  109. package/src/client/utils/openInEditor.ts +130 -0
  110. package/src/client/variables.css +304 -0
  111. package/src/config/defaults.ts +109 -0
  112. package/src/config/index.ts +32 -0
  113. package/src/config/loader.ts +174 -0
  114. package/src/config/schema.ts +161 -0
  115. package/src/core/constants.ts +39 -0
  116. package/src/core/errors.ts +739 -0
  117. package/src/core/index.ts +11 -0
  118. package/src/discovery/collections.ts +216 -0
  119. package/src/discovery/index.ts +33 -0
  120. package/src/discovery/patterns.ts +702 -0
  121. package/src/discovery/schema.ts +453 -0
  122. package/src/filesystem/images.ts +798 -0
  123. package/src/filesystem/index.ts +107 -0
  124. package/src/filesystem/reader.ts +452 -0
  125. package/src/filesystem/version-config.ts +390 -0
  126. package/src/filesystem/versions.ts +1339 -0
  127. package/src/filesystem/watcher.ts +226 -0
  128. package/src/filesystem/writer.ts +540 -0
  129. package/src/index.ts +61 -0
  130. package/src/integration.ts +228 -0
  131. package/src/server/assets.ts +254 -0
  132. package/src/server/cache.ts +355 -0
  133. package/src/server/index.ts +33 -0
  134. package/src/server/middleware.ts +209 -0
  135. package/src/server/routes.ts +1428 -0
  136. package/src/types/api.ts +61 -0
  137. package/src/types/config.ts +134 -0
  138. package/src/types/content.ts +64 -0
  139. package/src/types/image.ts +48 -0
  140. package/src/types/index.ts +58 -0
  141. package/src/types/version.ts +117 -0
@@ -0,0 +1,396 @@
1
+ /**
2
+ * @fileoverview Configuration panel modal
3
+ *
4
+ * Modal component displaying Writenex Astro configuration settings,
5
+ * including image settings, editor settings, and discovered collections.
6
+ * Includes focus trap for accessibility compliance.
7
+ *
8
+ * @module @writenex/astro/client/components/ConfigPanel
9
+ */
10
+
11
+ import { useCallback, useEffect, useRef, useState } from "react";
12
+ import {
13
+ X,
14
+ Settings,
15
+ Folder,
16
+ Image,
17
+ Info,
18
+ ExternalLink,
19
+ Copy,
20
+ Check,
21
+ ChevronDown,
22
+ } from "lucide-react";
23
+ import { useFocusTrap } from "../../hooks/useFocusTrap";
24
+ import type { Collection, WritenexClientConfig } from "../../hooks/useApi";
25
+ import { useSharedApi } from "../../context/ApiContext";
26
+ import {
27
+ openInEditor,
28
+ getAvailableEditors,
29
+ getPreferredEditor,
30
+ setPreferredEditor,
31
+ type EditorType,
32
+ } from "../../utils/openInEditor";
33
+ import "./ConfigPanel.css";
34
+
35
+ /**
36
+ * Props for the ConfigPanel component
37
+ */
38
+ interface ConfigPanelProps {
39
+ /** Current configuration */
40
+ config: WritenexClientConfig | null;
41
+ /** Discovered collections */
42
+ collections: Collection[];
43
+ /** Whether the modal is open */
44
+ isOpen: boolean;
45
+ /** Callback to close the modal */
46
+ onClose: () => void;
47
+ }
48
+
49
+ /**
50
+ * Configuration panel modal component
51
+ *
52
+ * @component
53
+ * @example
54
+ * ```tsx
55
+ * <ConfigPanel
56
+ * config={config}
57
+ * collections={collections}
58
+ * isOpen={showConfig}
59
+ * onClose={() => setShowConfig(false)}
60
+ * />
61
+ * ```
62
+ */
63
+ export function ConfigPanel({
64
+ config,
65
+ collections,
66
+ isOpen,
67
+ onClose,
68
+ }: ConfigPanelProps): React.ReactElement | null {
69
+ const api = useSharedApi();
70
+ const triggerRef = useRef<HTMLElement | null>(null);
71
+ const [configPath, setConfigPath] = useState<string | null>(null);
72
+ const [hasConfigFile, setHasConfigFile] = useState(false);
73
+ const [copied, setCopied] = useState(false);
74
+ const [selectedEditor, setSelectedEditor] =
75
+ useState<EditorType>(getPreferredEditor());
76
+ const [showEditorDropdown, setShowEditorDropdown] = useState(false);
77
+
78
+ // Fetch config path when modal opens
79
+ useEffect(() => {
80
+ if (isOpen) {
81
+ api
82
+ .getConfigPath()
83
+ .then((data) => {
84
+ setConfigPath(data.configPath);
85
+ setHasConfigFile(data.hasConfigFile);
86
+ })
87
+ .catch(() => {
88
+ setConfigPath(null);
89
+ setHasConfigFile(false);
90
+ });
91
+ }
92
+ }, [isOpen, api]);
93
+
94
+ // Store the trigger element when modal opens
95
+ useEffect(() => {
96
+ if (isOpen) {
97
+ triggerRef.current = document.activeElement as HTMLElement;
98
+ }
99
+ }, [isOpen]);
100
+
101
+ // Focus trap for accessibility
102
+ const { containerRef } = useFocusTrap({
103
+ enabled: isOpen,
104
+ onEscape: onClose,
105
+ returnFocusTo: triggerRef.current,
106
+ });
107
+
108
+ const handleOpenInEditor = useCallback(() => {
109
+ if (configPath) {
110
+ openInEditor(configPath, selectedEditor);
111
+ setPreferredEditor(selectedEditor);
112
+ }
113
+ }, [configPath, selectedEditor]);
114
+
115
+ const handleCopyPath = useCallback(() => {
116
+ if (configPath) {
117
+ navigator.clipboard.writeText(configPath).then(() => {
118
+ setCopied(true);
119
+ setTimeout(() => setCopied(false), 2000);
120
+ });
121
+ }
122
+ }, [configPath]);
123
+
124
+ const handleEditorSelect = useCallback((editor: EditorType) => {
125
+ setSelectedEditor(editor);
126
+ setPreferredEditor(editor);
127
+ setShowEditorDropdown(false);
128
+ }, []);
129
+
130
+ if (!isOpen) return null;
131
+
132
+ const handleOverlayClick = (e: React.MouseEvent) => {
133
+ if (e.target === e.currentTarget) onClose();
134
+ };
135
+
136
+ return (
137
+ <div className="wn-config-overlay" onClick={handleOverlayClick}>
138
+ <div
139
+ ref={containerRef}
140
+ className="wn-config-modal"
141
+ role="dialog"
142
+ aria-modal="true"
143
+ aria-labelledby="config-panel-title"
144
+ >
145
+ {/* Header */}
146
+ <div className="wn-config-header">
147
+ <h2 id="config-panel-title" className="wn-config-title">
148
+ <Settings size={16} />
149
+ Configuration
150
+ </h2>
151
+ <button
152
+ className="wn-config-close"
153
+ onClick={onClose}
154
+ title="Close"
155
+ aria-label="Close configuration panel"
156
+ >
157
+ <X size={16} />
158
+ </button>
159
+ </div>
160
+
161
+ {/* Content */}
162
+ <div className="wn-config-content">
163
+ {/* Configuration File - Primary action at top */}
164
+ <section className="wn-config-section">
165
+ <div className="wn-config-help">
166
+ <h3 className="wn-config-help-title">Configuration File</h3>
167
+ {hasConfigFile && configPath ? (
168
+ <>
169
+ <p className="wn-config-help-text">
170
+ Configuration loaded from{" "}
171
+ <code className="wn-config-help-code">
172
+ {configPath.split("/").pop()}
173
+ </code>
174
+ </p>
175
+ <div className="wn-config-actions">
176
+ <div className="wn-config-editor-select">
177
+ <button
178
+ className="wn-config-btn wn-config-btn--primary"
179
+ onClick={handleOpenInEditor}
180
+ title={`Open in ${getAvailableEditors().find((e) => e.name === selectedEditor)?.displayName}`}
181
+ >
182
+ <ExternalLink size={14} />
183
+ Open in Editor
184
+ </button>
185
+ <button
186
+ className="wn-config-btn wn-config-btn--dropdown"
187
+ onClick={() =>
188
+ setShowEditorDropdown(!showEditorDropdown)
189
+ }
190
+ aria-label="Select editor"
191
+ aria-expanded={showEditorDropdown}
192
+ >
193
+ <ChevronDown size={14} />
194
+ </button>
195
+ {showEditorDropdown && (
196
+ <div className="wn-config-dropdown">
197
+ {getAvailableEditors().map((editor) => (
198
+ <button
199
+ key={editor.name}
200
+ className={`wn-config-dropdown-item ${selectedEditor === editor.name ? "wn-config-dropdown-item--active" : ""}`}
201
+ onClick={() =>
202
+ handleEditorSelect(editor.name as EditorType)
203
+ }
204
+ >
205
+ {editor.displayName}
206
+ </button>
207
+ ))}
208
+ </div>
209
+ )}
210
+ </div>
211
+ <button
212
+ className="wn-config-btn wn-config-btn--secondary"
213
+ onClick={handleCopyPath}
214
+ title="Copy file path"
215
+ >
216
+ {copied ? <Check size={14} /> : <Copy size={14} />}
217
+ {copied ? "Copied" : "Copy Path"}
218
+ </button>
219
+ </div>
220
+ </>
221
+ ) : (
222
+ <p className="wn-config-help-text">
223
+ No configuration file found. Using default settings.
224
+ </p>
225
+ )}
226
+ </div>
227
+ </section>
228
+
229
+ {/* Image Settings */}
230
+ <section className="wn-config-section">
231
+ <h3 className="wn-config-section-title">
232
+ <Image size={14} />
233
+ Image Settings
234
+ </h3>
235
+ <div className="wn-config-items">
236
+ <ConfigItem
237
+ label="Strategy"
238
+ value={config?.images?.strategy ?? "colocated"}
239
+ valueClass={getStrategyClass(config?.images?.strategy)}
240
+ description={getStrategyDescription(config?.images?.strategy)}
241
+ />
242
+ {config?.images?.publicPath && (
243
+ <ConfigItem
244
+ label="Public Path"
245
+ value={config.images.publicPath}
246
+ />
247
+ )}
248
+ {config?.images?.storagePath && (
249
+ <ConfigItem
250
+ label="Storage Path"
251
+ value={config.images.storagePath}
252
+ />
253
+ )}
254
+ </div>
255
+ </section>
256
+
257
+ {/* Editor Settings */}
258
+ <section className="wn-config-section">
259
+ <h3 className="wn-config-section-title">
260
+ <Info size={14} />
261
+ Editor Settings
262
+ </h3>
263
+ <div className="wn-config-items">
264
+ <ConfigItem
265
+ label="Autosave"
266
+ value={
267
+ config?.editor?.autosave !== false ? "Enabled" : "Disabled"
268
+ }
269
+ valueClass={
270
+ config?.editor?.autosave !== false
271
+ ? "wn-config-item-value--emerald"
272
+ : "wn-config-item-value--muted"
273
+ }
274
+ />
275
+ <ConfigItem
276
+ label="Interval"
277
+ value={`${(config?.editor?.autosaveInterval ?? 3000) / 1000}s`}
278
+ />
279
+ </div>
280
+ </section>
281
+
282
+ {/* Collections */}
283
+ <section className="wn-config-section">
284
+ <h3 className="wn-config-section-title">
285
+ <Folder size={14} />
286
+ Collections ({collections.length})
287
+ </h3>
288
+ {collections.length === 0 ? (
289
+ <p className="wn-config-empty">No collections discovered</p>
290
+ ) : (
291
+ <div className="wn-config-collections">
292
+ {collections.map((col) => (
293
+ <CollectionCard key={col.name} collection={col} />
294
+ ))}
295
+ </div>
296
+ )}
297
+ </section>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ );
302
+ }
303
+
304
+ /**
305
+ * Get CSS class for strategy value
306
+ */
307
+ function getStrategyClass(strategy?: string): string {
308
+ switch (strategy) {
309
+ case "colocated":
310
+ return "wn-config-item-value--violet";
311
+ case "public":
312
+ return "wn-config-item-value--emerald";
313
+ default:
314
+ return "wn-config-item-value--amber";
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Get description for strategy value
320
+ */
321
+ function getStrategyDescription(strategy?: string): string {
322
+ switch (strategy) {
323
+ case "colocated":
324
+ return "Images stored alongside content files";
325
+ case "public":
326
+ return "Images stored in public folder";
327
+ default:
328
+ return "Custom storage path";
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Config item component
334
+ */
335
+ function ConfigItem({
336
+ label,
337
+ value,
338
+ valueClass = "",
339
+ description,
340
+ }: {
341
+ label: string;
342
+ value: string;
343
+ valueClass?: string;
344
+ description?: string;
345
+ }): React.ReactElement {
346
+ return (
347
+ <div className="wn-config-item">
348
+ <span className="wn-config-item-label">{label}</span>
349
+ <div>
350
+ <span className={`wn-config-item-value ${valueClass}`}>{value}</span>
351
+ {description && (
352
+ <p className="wn-config-item-description">{description}</p>
353
+ )}
354
+ </div>
355
+ </div>
356
+ );
357
+ }
358
+
359
+ /**
360
+ * Collection card component
361
+ */
362
+ function CollectionCard({
363
+ collection,
364
+ }: {
365
+ collection: Collection;
366
+ }): React.ReactElement {
367
+ return (
368
+ <div className="wn-config-collection">
369
+ <div className="wn-config-collection-header">
370
+ <span className="wn-config-collection-name">{collection.name}</span>
371
+ <span className="wn-config-collection-count">
372
+ {collection.count} items
373
+ </span>
374
+ </div>
375
+ <div className="wn-config-collection-details">
376
+ <p className="wn-config-collection-detail">
377
+ <span>Path:</span> {collection.path}
378
+ </p>
379
+ <p className="wn-config-collection-detail">
380
+ <span>Pattern:</span> {collection.filePattern}
381
+ </p>
382
+ {collection.schema && (
383
+ <p className="wn-config-collection-detail wn-config-collection-detail--blue">
384
+ <span>Schema:</span> {Object.keys(collection.schema).length} fields
385
+ detected
386
+ </p>
387
+ )}
388
+ {collection.previewUrl && (
389
+ <p className="wn-config-collection-detail wn-config-collection-detail--violet">
390
+ <span>Preview:</span> {collection.previewUrl}
391
+ </p>
392
+ )}
393
+ </div>
394
+ </div>
395
+ );
396
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @fileoverview ConfigPanel component exports
3
+ * @module @writenex/astro/client/components/ConfigPanel
4
+ */
5
+
6
+ export { ConfigPanel } from "./ConfigPanel";