@nuasite/cms 0.18.0 → 0.19.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 (62) hide show
  1. package/dist/editor.js +44697 -26834
  2. package/package.json +23 -21
  3. package/src/build-processor.ts +4 -1
  4. package/src/collection-scanner.ts +425 -48
  5. package/src/dev-middleware.ts +26 -203
  6. package/src/editor/api.ts +1 -22
  7. package/src/editor/components/ai-chat.tsx +3 -3
  8. package/src/editor/components/ai-tooltip.tsx +2 -1
  9. package/src/editor/components/block-editor.tsx +13 -108
  10. package/src/editor/components/collections-browser.tsx +168 -205
  11. package/src/editor/components/component-card.tsx +49 -0
  12. package/src/editor/components/confirm-dialog.tsx +34 -47
  13. package/src/editor/components/create-page-modal.tsx +529 -101
  14. package/src/editor/components/delete-page-dialog.tsx +100 -0
  15. package/src/editor/components/fields.tsx +175 -0
  16. package/src/editor/components/frontmatter-fields.tsx +281 -70
  17. package/src/editor/components/frontmatter-sidebar.tsx +223 -0
  18. package/src/editor/components/highlight-overlay.ts +3 -2
  19. package/src/editor/components/markdown-editor-overlay.tsx +131 -85
  20. package/src/editor/components/markdown-inline-editor.tsx +74 -5
  21. package/src/editor/components/mdx-block-view.tsx +102 -0
  22. package/src/editor/components/mdx-component-picker.tsx +123 -0
  23. package/src/editor/components/mdx-props-editor.tsx +94 -0
  24. package/src/editor/components/media-library.tsx +373 -100
  25. package/src/editor/components/modal-shell.tsx +87 -0
  26. package/src/editor/components/prop-editor.tsx +52 -0
  27. package/src/editor/components/redirect-countdown.tsx +3 -1
  28. package/src/editor/components/redirects-manager.tsx +269 -0
  29. package/src/editor/components/reference-picker.tsx +203 -0
  30. package/src/editor/components/seo-editor.tsx +285 -303
  31. package/src/editor/components/toast/toast-container.tsx +2 -1
  32. package/src/editor/components/toolbar.tsx +177 -46
  33. package/src/editor/constants.ts +26 -0
  34. package/src/editor/editor.ts +112 -0
  35. package/src/editor/fetch.ts +62 -0
  36. package/src/editor/index.tsx +19 -1
  37. package/src/editor/markdown-api.ts +105 -156
  38. package/src/editor/milkdown-mdx-plugin.tsx +269 -0
  39. package/src/editor/signals.ts +206 -13
  40. package/src/editor/types.ts +52 -1
  41. package/src/handlers/api-routes.ts +251 -0
  42. package/src/handlers/component-ops.ts +2 -18
  43. package/src/handlers/markdown-ops.ts +202 -47
  44. package/src/handlers/page-ops.ts +229 -0
  45. package/src/handlers/redirect-ops.ts +163 -0
  46. package/src/handlers/source-writer.ts +157 -1
  47. package/src/html-processor.ts +14 -2
  48. package/src/index.ts +76 -2
  49. package/src/manifest-writer.ts +19 -1
  50. package/src/media/contember.ts +2 -1
  51. package/src/media/local.ts +66 -28
  52. package/src/media/project-images.ts +81 -0
  53. package/src/media/s3.ts +32 -11
  54. package/src/media/types.ts +24 -2
  55. package/src/shared.ts +27 -0
  56. package/src/source-finder/collection-finder.ts +219 -41
  57. package/src/source-finder/index.ts +7 -1
  58. package/src/source-finder/search-index.ts +178 -36
  59. package/src/source-finder/snippet-utils.ts +423 -3
  60. package/src/tsconfig.json +0 -2
  61. package/src/types.ts +111 -2
  62. package/src/utils.ts +40 -4
@@ -0,0 +1,123 @@
1
+ import { useState } from 'preact/hooks'
2
+ import { getComponentDefinitions } from '../manifest'
3
+ import { manifest, mdxComponentPickerOpen } from '../signals'
4
+ import { ComponentCard, getDefaultProps } from './component-card'
5
+ import { CancelButton, ModalBackdrop, ModalHeader } from './modal-shell'
6
+ import { PropEditor } from './prop-editor'
7
+
8
+ export interface MdxComponentPickerProps {
9
+ onInsert: (componentName: string, props: Record<string, string>) => void
10
+ }
11
+
12
+ export function MdxComponentPicker({ onInsert }: MdxComponentPickerProps) {
13
+ const isOpen = mdxComponentPickerOpen.value
14
+ const [selectedComponent, setSelectedComponent] = useState<string | null>(null)
15
+ const [propValues, setPropValues] = useState<Record<string, string>>({})
16
+ const [searchQuery, setSearchQuery] = useState('')
17
+
18
+ if (!isOpen) return null
19
+
20
+ const componentDefinitions = getComponentDefinitions(manifest.value)
21
+
22
+ const resetSelection = () => {
23
+ setSelectedComponent(null)
24
+ setPropValues({})
25
+ }
26
+
27
+ const close = () => {
28
+ mdxComponentPickerOpen.value = false
29
+ resetSelection()
30
+ setSearchQuery('')
31
+ }
32
+
33
+ const handleSelectComponent = (name: string) => {
34
+ const def = componentDefinitions[name]
35
+ if (!def) return
36
+ setSelectedComponent(name)
37
+ setPropValues(getDefaultProps(def))
38
+ }
39
+
40
+ const handleConfirmInsert = () => {
41
+ if (selectedComponent) {
42
+ onInsert(selectedComponent, propValues)
43
+ close()
44
+ }
45
+ }
46
+
47
+ const mdxAllowList = manifest.value?.mdxComponents
48
+ const filteredDefs = Object.values(componentDefinitions).filter((def) => {
49
+ if (mdxAllowList && !mdxAllowList.includes(def.name)) return false
50
+ return !searchQuery || def.name.toLowerCase().includes(searchQuery.toLowerCase())
51
+ || def.description?.toLowerCase().includes(searchQuery.toLowerCase())
52
+ })
53
+
54
+ return (
55
+ <ModalBackdrop onClose={close} maxWidth="max-w-md" extraClass="max-h-[80vh] flex flex-col overflow-hidden">
56
+ {selectedComponent
57
+ ? (
58
+ <>
59
+ <ModalHeader title={`Configure ${selectedComponent}`} onBack={resetSelection} onClose={close} />
60
+ <div class="p-5 overflow-y-auto flex-1">
61
+ <div class="px-4 py-3 bg-white/10 rounded-cms-md mb-4 text-[13px] text-white">
62
+ Inserting <strong>{selectedComponent}</strong> at cursor position
63
+ </div>
64
+ {(() => {
65
+ const selectedDef = componentDefinitions[selectedComponent]
66
+ if (!selectedDef || selectedDef.props.length === 0) {
67
+ return (
68
+ <div class="text-white/50 text-[13px]">
69
+ This component has no configurable props.
70
+ </div>
71
+ )
72
+ }
73
+ return selectedDef.props.map((prop) => (
74
+ <PropEditor
75
+ key={prop.name}
76
+ prop={prop}
77
+ value={propValues[prop.name] || ''}
78
+ onChange={(value) => setPropValues((prev) => ({ ...prev, [prop.name]: value }))}
79
+ />
80
+ ))
81
+ })()}
82
+ </div>
83
+ <div class="px-5 py-4 border-t border-white/10 flex gap-2 justify-end">
84
+ <CancelButton onClick={resetSelection} label="Back" />
85
+ <button
86
+ onClick={handleConfirmInsert}
87
+ class="px-4 py-2.5 bg-cms-primary text-cms-primary-text rounded-cms-pill cursor-pointer hover:bg-cms-primary-hover transition-all font-medium"
88
+ >
89
+ Insert
90
+ </button>
91
+ </div>
92
+ </>
93
+ )
94
+ : (
95
+ <>
96
+ <ModalHeader title="Insert Component" onClose={close} />
97
+ <div class="px-5 pt-4">
98
+ <input
99
+ type="text"
100
+ value={searchQuery}
101
+ onInput={(e) => setSearchQuery((e.target as HTMLInputElement).value)}
102
+ placeholder="Search components..."
103
+ class="w-full px-4 py-2.5 bg-white/10 border border-white/20 text-[13px] text-white placeholder:text-white/40 outline-none focus:border-white/40 focus:ring-1 focus:ring-white/10 transition-all rounded-cms-md"
104
+ />
105
+ </div>
106
+ <div class="p-5 overflow-y-auto flex-1">
107
+ {filteredDefs.length === 0
108
+ ? (
109
+ <div class="text-center text-white/50 py-8">
110
+ {searchQuery ? 'No components match your search.' : 'No components available.'}
111
+ </div>
112
+ )
113
+ : (
114
+ <div class="flex flex-col gap-2">
115
+ {filteredDefs.map((def) => <ComponentCard key={def.name} def={def} onClick={() => handleSelectComponent(def.name)} />)}
116
+ </div>
117
+ )}
118
+ </div>
119
+ </>
120
+ )}
121
+ </ModalBackdrop>
122
+ )
123
+ }
@@ -0,0 +1,94 @@
1
+ import { useEffect, useState } from 'preact/hooks'
2
+ import { clampPanelPosition, Z_INDEX } from '../constants'
3
+ import { getComponentDefinition } from '../manifest'
4
+ import { closeMdxPropsEditor, manifest, mdxPropsEditorState } from '../signals'
5
+ import { MdxComponentIcon } from './mdx-block-view'
6
+ import { CancelButton, CloseButton } from './modal-shell'
7
+ import { PropEditor } from './prop-editor'
8
+
9
+ export function MdxPropsEditor({
10
+ onUpdateProps,
11
+ }: {
12
+ onUpdateProps: (nodePos: number, props: Record<string, string>) => void
13
+ }) {
14
+ const state = mdxPropsEditorState.value
15
+ const isVisible = state.isOpen && state.componentName !== null && state.nodePos !== null
16
+
17
+ const definition = isVisible ? getComponentDefinition(manifest.value, state.componentName!) : undefined
18
+ const [propValues, setPropValues] = useState<Record<string, string>>(state.props)
19
+
20
+ useEffect(() => {
21
+ if (isVisible) setPropValues(state.props)
22
+ }, [state.nodePos, state.componentName, state.props, isVisible])
23
+
24
+ if (!isVisible) return null
25
+
26
+ const panelStyle = state.cursorPos ? clampPanelPosition(state.cursorPos, 360) : {}
27
+
28
+ const handleSave = () => {
29
+ if (state.nodePos !== null) {
30
+ onUpdateProps(state.nodePos, propValues)
31
+ closeMdxPropsEditor()
32
+ }
33
+ }
34
+
35
+ return (
36
+ <>
37
+ <div
38
+ data-cms-ui
39
+ onClick={closeMdxPropsEditor}
40
+ style={{ zIndex: Z_INDEX.SELECTION }}
41
+ class="fixed inset-0"
42
+ />
43
+
44
+ <div
45
+ data-cms-ui
46
+ onClick={(e: MouseEvent) => e.stopPropagation()}
47
+ class="fixed w-90 bg-cms-dark shadow-[0_8px_32px_rgba(0,0,0,0.4)] font-sans text-sm overflow-hidden flex flex-col rounded-cms-xl border border-white/10"
48
+ style={{ ...panelStyle, zIndex: Z_INDEX.MODAL }}
49
+ >
50
+ <div class="px-5 py-4 flex justify-between items-center border-b border-white/10">
51
+ <div class="flex items-center gap-2">
52
+ <MdxComponentIcon />
53
+ <span class="font-semibold text-white">{state.componentName}</span>
54
+ </div>
55
+ <CloseButton onClick={closeMdxPropsEditor} />
56
+ </div>
57
+
58
+ <div class="p-5 overflow-y-auto flex-1">
59
+ {definition
60
+ ? (
61
+ definition.props.map((prop) => (
62
+ <PropEditor
63
+ key={prop.name}
64
+ prop={prop}
65
+ value={propValues[prop.name] || ''}
66
+ onChange={(value) => setPropValues((prev) => ({ ...prev, [prop.name]: value }))}
67
+ />
68
+ ))
69
+ )
70
+ : (
71
+ <div class="text-white/50 text-[13px]">
72
+ <div class="mb-3">Unknown component — props not editable.</div>
73
+ <div class="font-mono text-[11px] text-white/30 bg-white/5 p-3 rounded-cms-md break-all">
74
+ {Object.entries(propValues).map(([k, v]) => <div key={k}>{k}="{v}"</div>)}
75
+ </div>
76
+ </div>
77
+ )}
78
+ </div>
79
+
80
+ {definition && (
81
+ <div class="px-5 py-4 border-t border-white/10 flex gap-2 justify-end">
82
+ <CancelButton onClick={closeMdxPropsEditor} />
83
+ <button
84
+ onClick={handleSave}
85
+ class="px-4 py-2.5 bg-cms-primary text-cms-primary-text rounded-cms-pill cursor-pointer hover:bg-cms-primary-hover transition-all font-medium"
86
+ >
87
+ Save
88
+ </button>
89
+ </div>
90
+ )}
91
+ </div>
92
+ </>
93
+ )
94
+ }