@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/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
- ${configScript}
204
- if (!document.querySelector('script[data-nuasite-cms]')) {
205
- const s = document.createElement('script');
206
- s.type = 'module';
207
- s.src = ${JSON.stringify(editorSrc)};
208
- s.dataset.nuasiteCms = '';
209
- document.head.appendChild(s);
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 */
@@ -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