@nuasite/cms 0.26.0 → 0.28.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/README.md +103 -0
- package/dist/editor.js +25765 -25462
- package/package.json +1 -1
- package/src/collection-scanner.ts +152 -12
- package/src/dev-middleware.ts +7 -0
- package/src/editor/components/fields.tsx +8 -2
- package/src/editor/components/frontmatter-fields.tsx +13 -3
- package/src/editor/components/link-edit-popover.tsx +232 -0
- package/src/editor/components/markdown-editor-overlay.tsx +37 -0
- package/src/editor/components/markdown-inline-editor.tsx +25 -52
- package/src/editor/components/mdx-block-view.tsx +151 -41
- package/src/editor/hooks/useLinkPopover.ts +64 -0
- package/src/editor/milkdown-mdx-plugin.tsx +5 -0
- package/src/editor/milkdown-utils.ts +21 -0
- package/src/field-types.ts +109 -27
- package/src/index.ts +21 -8
- package/src/pages/component-preview.astro +56 -0
- package/src/types.ts +18 -0
- package/src/vite-plugin.ts +9 -0
package/src/index.ts
CHANGED
|
@@ -119,10 +119,17 @@ export default function nuaCms(options: NuaCmsOptions = {}): AstroIntegration {
|
|
|
119
119
|
return {
|
|
120
120
|
name: '@nuasite/cms',
|
|
121
121
|
hooks: {
|
|
122
|
-
'astro:config:setup': async ({ updateConfig, command, injectScript, logger }) => {
|
|
122
|
+
'astro:config:setup': async ({ updateConfig, command, injectScript, injectRoute, logger }) => {
|
|
123
123
|
// CMS is only needed during dev — skip all setup during build
|
|
124
124
|
if (command !== 'dev') return
|
|
125
125
|
|
|
126
|
+
// Inject dev-only component preview route (prerender:false → SSR with query param access)
|
|
127
|
+
injectRoute({
|
|
128
|
+
pattern: '/_nua/preview',
|
|
129
|
+
entrypoint: new URL('./pages/component-preview.astro', import.meta.url).pathname,
|
|
130
|
+
prerender: false,
|
|
131
|
+
})
|
|
132
|
+
|
|
126
133
|
// --- CMS Marker setup ---
|
|
127
134
|
idCounter.value = 0
|
|
128
135
|
manifestWriter.reset()
|
|
@@ -200,13 +207,17 @@ export default function nuaCms(options: NuaCmsOptions = {}): AstroIntegration {
|
|
|
200
207
|
injectScript(
|
|
201
208
|
'page',
|
|
202
209
|
`
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
+
if (window.location.pathname.startsWith('/_nua/')) {
|
|
211
|
+
// Skip CMS editor on internal preview pages
|
|
212
|
+
} else {
|
|
213
|
+
${configScript}
|
|
214
|
+
if (!document.querySelector('script[data-nuasite-cms]')) {
|
|
215
|
+
const s = document.createElement('script');
|
|
216
|
+
s.type = 'module';
|
|
217
|
+
s.src = ${JSON.stringify(editorSrc)};
|
|
218
|
+
s.dataset.nuasiteCms = '';
|
|
219
|
+
document.head.appendChild(s);
|
|
220
|
+
}
|
|
210
221
|
}
|
|
211
222
|
`,
|
|
212
223
|
)
|
|
@@ -347,6 +358,7 @@ async function mergeRedirects(dir: URL, logger: { info: (msg: string) => void })
|
|
|
347
358
|
}
|
|
348
359
|
|
|
349
360
|
export { n } from './field-types'
|
|
361
|
+
export type { DateHints, ImageHints, NumberHints, TextareaHints, TextHints } from './field-types'
|
|
350
362
|
export { createContemberStorageAdapter as contemberMedia } from './media/contember'
|
|
351
363
|
export { createLocalStorageAdapter as localMedia } from './media/local'
|
|
352
364
|
export { createS3StorageAdapter as s3Media } from './media/s3'
|
|
@@ -382,6 +394,7 @@ export type {
|
|
|
382
394
|
ComponentProp,
|
|
383
395
|
ContentConstraints,
|
|
384
396
|
FieldDefinition,
|
|
397
|
+
FieldHints,
|
|
385
398
|
FieldType,
|
|
386
399
|
ImageMetadata,
|
|
387
400
|
JsonLdEntry,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
// @ts-ignore — virtual module provided by CMS vite plugin
|
|
3
|
+
import { components } from 'virtual:cms-component-preview'
|
|
4
|
+
|
|
5
|
+
const componentFile = Astro.url.searchParams.get('file')
|
|
6
|
+
const childrenMarkdown = Astro.url.searchParams.get('children')
|
|
7
|
+
|
|
8
|
+
let props: Record<string, unknown> = {}
|
|
9
|
+
const propsParam = Astro.url.searchParams.get('props')
|
|
10
|
+
if (propsParam) {
|
|
11
|
+
try {
|
|
12
|
+
props = JSON.parse(propsParam)
|
|
13
|
+
} catch {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const loader = componentFile ? components[componentFile] : undefined
|
|
17
|
+
const Component = loader ? (await loader()).default : null
|
|
18
|
+
|
|
19
|
+
// Render markdown children to HTML
|
|
20
|
+
let childrenHtml = ''
|
|
21
|
+
if (childrenMarkdown) {
|
|
22
|
+
try {
|
|
23
|
+
const { unified } = await import('unified')
|
|
24
|
+
const { default: remarkParse } = await import('remark-parse')
|
|
25
|
+
const { default: remarkRehype } = await import('remark-rehype')
|
|
26
|
+
const { default: rehypeStringify } = await import('rehype-stringify')
|
|
27
|
+
const result = await unified().use(remarkParse).use(remarkRehype).use(rehypeStringify).process(childrenMarkdown)
|
|
28
|
+
childrenHtml = String(result)
|
|
29
|
+
} catch {
|
|
30
|
+
childrenHtml = childrenMarkdown
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Import the project's global CSS so components render with correct styles.
|
|
35
|
+
// Glob matches common CSS entry points — Vite resolves relative to project root.
|
|
36
|
+
const globalStyles = import.meta.glob(['/src/styles/**/*.css', '/src/styles/**/*.scss'], { eager: true })
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<html>
|
|
40
|
+
<head>
|
|
41
|
+
<meta charset="utf-8" />
|
|
42
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
43
|
+
<style>
|
|
44
|
+
html, body { margin: 0; padding: 0; background: white; }
|
|
45
|
+
body { padding: 8px; }
|
|
46
|
+
</style>
|
|
47
|
+
</head>
|
|
48
|
+
<body>
|
|
49
|
+
{Component
|
|
50
|
+
? childrenHtml
|
|
51
|
+
? <Component {...props}><Fragment set:html={childrenHtml} /></Component>
|
|
52
|
+
: <Component {...props} />
|
|
53
|
+
: <p style="color:#888;font-size:13px;margin:0">No preview available</p>
|
|
54
|
+
}
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
package/src/types.ts
CHANGED
|
@@ -242,6 +242,18 @@ export type FieldType =
|
|
|
242
242
|
| 'object'
|
|
243
243
|
| 'reference'
|
|
244
244
|
|
|
245
|
+
/** Editor hints for enhanced field rendering (extracted from `n.*()` options in content config) */
|
|
246
|
+
export interface FieldHints {
|
|
247
|
+
min?: number | string
|
|
248
|
+
max?: number | string
|
|
249
|
+
step?: number
|
|
250
|
+
placeholder?: string
|
|
251
|
+
maxLength?: number
|
|
252
|
+
minLength?: number
|
|
253
|
+
rows?: number
|
|
254
|
+
accept?: string
|
|
255
|
+
}
|
|
256
|
+
|
|
245
257
|
/** Definition of a single field in a collection's schema */
|
|
246
258
|
export interface FieldDefinition {
|
|
247
259
|
/** Field name as it appears in frontmatter */
|
|
@@ -270,6 +282,8 @@ export interface FieldDefinition {
|
|
|
270
282
|
hidden?: boolean
|
|
271
283
|
/** Source field name this field is derived from (e.g. categoryHref derived from category) */
|
|
272
284
|
derivedFrom?: string
|
|
285
|
+
/** Editor hints for enhanced field rendering */
|
|
286
|
+
hints?: FieldHints
|
|
273
287
|
}
|
|
274
288
|
|
|
275
289
|
/** Per-entry metadata for collection browsing */
|
|
@@ -304,6 +318,10 @@ export interface CollectionDefinition {
|
|
|
304
318
|
fileExtension: 'md' | 'mdx' | 'json' | 'yaml' | 'yml'
|
|
305
319
|
/** Per-entry metadata for browsing */
|
|
306
320
|
entries?: CollectionEntryInfo[]
|
|
321
|
+
/** Frontmatter field name to sort entries by (detected from `.orderBy()` in content config) */
|
|
322
|
+
orderBy?: string
|
|
323
|
+
/** Sort direction for orderBy field */
|
|
324
|
+
orderDirection?: 'asc' | 'desc'
|
|
307
325
|
}
|
|
308
326
|
|
|
309
327
|
/** Manifest metadata for versioning and conflict detection */
|
package/src/vite-plugin.ts
CHANGED
|
@@ -28,6 +28,9 @@ export function createVitePlugin(context: VitePluginContext): Plugin[] {
|
|
|
28
28
|
if (id === '/@cms/components' || id === 'virtual:cms-components') {
|
|
29
29
|
return '\0virtual:cms-components'
|
|
30
30
|
}
|
|
31
|
+
if (id === 'virtual:cms-component-preview') {
|
|
32
|
+
return '\0virtual:cms-component-preview'
|
|
33
|
+
}
|
|
31
34
|
},
|
|
32
35
|
load(id) {
|
|
33
36
|
if (id === '\0virtual:cms-manifest') {
|
|
@@ -36,6 +39,12 @@ export function createVitePlugin(context: VitePluginContext): Plugin[] {
|
|
|
36
39
|
if (id === '\0virtual:cms-components') {
|
|
37
40
|
return `export default ${JSON.stringify(componentDefinitions)};`
|
|
38
41
|
}
|
|
42
|
+
if (id === '\0virtual:cms-component-preview') {
|
|
43
|
+
const entries = Object.values(componentDefinitions).map(
|
|
44
|
+
(def) => ` ${JSON.stringify(def.file)}: () => import(${JSON.stringify('/' + def.file)})`,
|
|
45
|
+
)
|
|
46
|
+
return `export const components = {\n${entries.join(',\n')}\n};`
|
|
47
|
+
}
|
|
39
48
|
},
|
|
40
49
|
}
|
|
41
50
|
|