@nuasite/cms 0.39.1 → 0.40.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/dist/editor.js +14575 -13938
- package/package.json +1 -1
- package/src/build-processor.ts +1 -1
- package/src/collection-scanner.ts +49 -2
- package/src/dev-middleware.ts +1 -1
- package/src/editor/components/attribute-editor.tsx +0 -1
- package/src/editor/components/bg-image-overlay.tsx +7 -8
- package/src/editor/components/block-editor.tsx +12 -12
- package/src/editor/components/collections-browser.tsx +10 -10
- package/src/editor/components/create-page-modal.tsx +18 -18
- package/src/editor/components/delete-page-dialog.tsx +4 -3
- package/src/editor/components/field-utils.ts +54 -0
- package/src/editor/components/fields.tsx +254 -72
- package/src/editor/components/frontmatter-fields.tsx +135 -54
- package/src/editor/components/frontmatter-sidebar.tsx +55 -58
- package/src/editor/components/link-edit-popover.tsx +10 -5
- package/src/editor/components/markdown-editor-overlay.tsx +100 -39
- package/src/editor/components/markdown-inline-editor.tsx +58 -26
- package/src/editor/components/mdx-block-view.tsx +4 -4
- package/src/editor/components/mdx-component-picker.tsx +2 -2
- package/src/editor/components/media-library.tsx +19 -18
- package/src/editor/components/modal-shell.tsx +16 -3
- package/src/editor/components/prop-editor.tsx +15 -18
- package/src/editor/components/redirects-manager.tsx +42 -35
- package/src/editor/components/reference-picker.tsx +5 -4
- package/src/editor/components/seo-editor.tsx +36 -27
- package/src/editor/components/toolbar.tsx +50 -33
- package/src/editor/dom.ts +13 -2
- package/src/editor/editor.ts +7 -6
- package/src/editor/hooks/useBlockEditorHandlers.ts +7 -6
- package/src/editor/index.tsx +7 -6
- package/src/editor/signals.ts +44 -13
- package/src/editor/strings.ts +123 -0
- package/src/editor/styles.css +75 -2
- package/src/editor/types.ts +8 -0
- package/src/index.ts +6 -0
- package/src/source-finder/image-finder.ts +1 -1
- package/src/source-finder/search-index.ts +12 -4
- package/src/source-finder/snippet-utils.ts +4 -1
- package/src/types.ts +4 -0
package/src/editor/styles.css
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
/* Border & Radius */
|
|
32
32
|
--color-cms-border: rgba(0, 0, 0, 0.08);
|
|
33
|
+
--radius-cms-xs: 5px;
|
|
33
34
|
--radius-cms-sm: 8px;
|
|
34
35
|
--radius-cms-md: 16px;
|
|
35
36
|
--radius-cms-lg: 24px;
|
|
@@ -58,6 +59,10 @@
|
|
|
58
59
|
line-height: 1.5;
|
|
59
60
|
-webkit-font-smoothing: antialiased;
|
|
60
61
|
-moz-osx-font-smoothing: grayscale;
|
|
62
|
+
/* Render all native form chrome (date picker indicator, scrollbars, autofill,
|
|
63
|
+
default controls) in dark-mode colors. Replaces a webkit-only `filter: invert(1)`
|
|
64
|
+
workaround that didn't cover Firefox. */
|
|
65
|
+
color-scheme: dark;
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
.cms-root *,
|
|
@@ -66,6 +71,33 @@
|
|
|
66
71
|
box-sizing: border-box;
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
.cms-root button:not(:disabled) {
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.cms-root select {
|
|
79
|
+
/* -moz-appearance: none dropped — Firefox 70+ honors the standard appearance property. */
|
|
80
|
+
appearance: none;
|
|
81
|
+
-webkit-appearance: none;
|
|
82
|
+
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff66' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
|
|
83
|
+
background-repeat: no-repeat;
|
|
84
|
+
background-position: right 0.75rem center;
|
|
85
|
+
background-size: 1rem;
|
|
86
|
+
padding-right: 2rem;
|
|
87
|
+
/* appearance: none strips the native click affordance — restore it explicitly. */
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.cms-root input[type="number"] {
|
|
92
|
+
-moz-appearance: textfield;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.cms-root input[type="number"]::-webkit-inner-spin-button,
|
|
96
|
+
.cms-root input[type="number"]::-webkit-outer-spin-button {
|
|
97
|
+
-webkit-appearance: none;
|
|
98
|
+
margin: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
69
101
|
/* Custom animations for CMS */
|
|
70
102
|
@keyframes spin {
|
|
71
103
|
from { transform: rotate(0deg); }
|
|
@@ -242,14 +274,55 @@
|
|
|
242
274
|
|
|
243
275
|
.milkdown-dark .ProseMirror h1 {
|
|
244
276
|
font-size: 1.75em;
|
|
245
|
-
border-bottom:
|
|
246
|
-
padding-bottom: 0
|
|
277
|
+
border-bottom: none;
|
|
278
|
+
padding-bottom: 0;
|
|
247
279
|
}
|
|
248
280
|
|
|
249
281
|
.milkdown-dark .ProseMirror h2 {
|
|
250
282
|
font-size: 1.5em;
|
|
251
283
|
}
|
|
252
284
|
|
|
285
|
+
.milkdown-editor .ProseMirror h1,
|
|
286
|
+
.milkdown-editor .ProseMirror h2,
|
|
287
|
+
.milkdown-editor .ProseMirror h3,
|
|
288
|
+
.milkdown-editor .ProseMirror h4,
|
|
289
|
+
.milkdown-editor .ProseMirror h5,
|
|
290
|
+
.milkdown-editor .ProseMirror h6 {
|
|
291
|
+
position: relative;
|
|
292
|
+
padding-right: 3em;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.milkdown-editor .ProseMirror h1::after,
|
|
296
|
+
.milkdown-editor .ProseMirror h2::after,
|
|
297
|
+
.milkdown-editor .ProseMirror h3::after,
|
|
298
|
+
.milkdown-editor .ProseMirror h4::after,
|
|
299
|
+
.milkdown-editor .ProseMirror h5::after,
|
|
300
|
+
.milkdown-editor .ProseMirror h6::after {
|
|
301
|
+
position: absolute;
|
|
302
|
+
right: 0;
|
|
303
|
+
top: 50%;
|
|
304
|
+
transform: translateY(-50%);
|
|
305
|
+
padding: 0.15em 0.6em;
|
|
306
|
+
font-size: 0.55em;
|
|
307
|
+
font-weight: 600;
|
|
308
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
309
|
+
color: var(--color-cms-primary);
|
|
310
|
+
background: rgba(223, 255, 64, 0.12);
|
|
311
|
+
border: 1px solid rgba(223, 255, 64, 0.3);
|
|
312
|
+
border-radius: 9999px;
|
|
313
|
+
letter-spacing: 0.05em;
|
|
314
|
+
user-select: none;
|
|
315
|
+
pointer-events: none;
|
|
316
|
+
line-height: 1.3;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.milkdown-editor .ProseMirror h1::after { content: "H1"; }
|
|
320
|
+
.milkdown-editor .ProseMirror h2::after { content: "H2"; }
|
|
321
|
+
.milkdown-editor .ProseMirror h3::after { content: "H3"; }
|
|
322
|
+
.milkdown-editor .ProseMirror h4::after { content: "H4"; }
|
|
323
|
+
.milkdown-editor .ProseMirror h5::after { content: "H5"; }
|
|
324
|
+
.milkdown-editor .ProseMirror h6::after { content: "H6"; }
|
|
325
|
+
|
|
253
326
|
.milkdown-dark .ProseMirror h3 {
|
|
254
327
|
font-size: 1.25em;
|
|
255
328
|
}
|
package/src/editor/types.ts
CHANGED
|
@@ -54,6 +54,14 @@ export interface CmsConfig {
|
|
|
54
54
|
features?: CmsFeatures
|
|
55
55
|
/** Maximum upload size in bytes for media uploads (injected by the integration). */
|
|
56
56
|
maxUploadSize?: number
|
|
57
|
+
/**
|
|
58
|
+
* Describes the host site's color theme. The CMS draws editor chrome and outlines
|
|
59
|
+
* in a contrasting color so they read clearly against the page being edited.
|
|
60
|
+
* - 'light' — host is light → CMS uses dark outlines
|
|
61
|
+
* - 'dark' — host is dark → CMS uses light outlines
|
|
62
|
+
* - 'auto' (default) — detect from `prefers-color-scheme` and the page background
|
|
63
|
+
*/
|
|
64
|
+
siteTheme?: 'auto' | 'light' | 'dark'
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
export interface ComponentProp {
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,12 @@ export interface NuaCmsOptions extends CmsMarkerOptions {
|
|
|
34
34
|
theme?: Record<string, string>
|
|
35
35
|
themePreset?: string
|
|
36
36
|
features?: CmsFeatures
|
|
37
|
+
/**
|
|
38
|
+
* Describes the host site's color theme. The CMS draws editor chrome and outlines
|
|
39
|
+
* in a contrasting color. 'auto' (default) detects via prefers-color-scheme and
|
|
40
|
+
* the page's computed background.
|
|
41
|
+
*/
|
|
42
|
+
siteTheme?: 'auto' | 'light' | 'dark'
|
|
37
43
|
}
|
|
38
44
|
/**
|
|
39
45
|
* Proxy /_nua/cms requests to this target URL during dev.
|
|
@@ -151,7 +151,7 @@ export async function findImageSourceLocation(
|
|
|
151
151
|
imageSrc: string,
|
|
152
152
|
imageSrcSet?: string,
|
|
153
153
|
pageFiles?: readonly string[],
|
|
154
|
-
preferredLocation?: { file
|
|
154
|
+
preferredLocation?: { file?: string; line?: number; srcOccurrence?: number },
|
|
155
155
|
): Promise<SourceLocation | undefined> {
|
|
156
156
|
// Use index if available (much faster)
|
|
157
157
|
if (isSearchIndexInitialized()) {
|
|
@@ -1436,7 +1436,7 @@ function extractPathname(src: string): string {
|
|
|
1436
1436
|
export function findInImageIndex(
|
|
1437
1437
|
imageSrc: string,
|
|
1438
1438
|
pageFiles?: readonly string[],
|
|
1439
|
-
preferredLocation?: { file
|
|
1439
|
+
preferredLocation?: { file?: string; line?: number; srcOccurrence?: number },
|
|
1440
1440
|
): SourceLocation | undefined {
|
|
1441
1441
|
const index = getImageSearchIndex()
|
|
1442
1442
|
|
|
@@ -1446,7 +1446,7 @@ export function findInImageIndex(
|
|
|
1446
1446
|
const candidates = decoded && decoded !== imageSrc ? [imageSrc, decoded] : [imageSrc]
|
|
1447
1447
|
// Astro stamps absolute paths in `data-astro-source-file`; the index uses
|
|
1448
1448
|
// project-relative paths. Normalize before comparing.
|
|
1449
|
-
const preferredFile = preferredLocation ? toProjectRelativePath(preferredLocation.file) : undefined
|
|
1449
|
+
const preferredFile = preferredLocation?.file ? toProjectRelativePath(preferredLocation.file) : undefined
|
|
1450
1450
|
const preferredLine = preferredLocation?.line
|
|
1451
1451
|
const preferredOccurrence = preferredLocation?.srcOccurrence
|
|
1452
1452
|
|
|
@@ -1454,13 +1454,21 @@ export function findInImageIndex(
|
|
|
1454
1454
|
let occurrenceCounter = 0
|
|
1455
1455
|
let occurrenceMatch: SourceLocation | undefined
|
|
1456
1456
|
let lineMatch: SourceLocation | undefined
|
|
1457
|
+
const pageSet = pageFiles?.length ? new Set(pageFiles) : undefined
|
|
1458
|
+
// Scope occurrence counting: if preferredFile is set, only that file.
|
|
1459
|
+
// Otherwise fall back to pageFiles. Otherwise count across the whole index.
|
|
1460
|
+
const occurrenceScope = (file: string): boolean => {
|
|
1461
|
+
if (preferredFile) return file === preferredFile
|
|
1462
|
+
if (pageSet) return pageSet.has(file)
|
|
1463
|
+
return true
|
|
1464
|
+
}
|
|
1457
1465
|
for (const entry of index) {
|
|
1458
1466
|
if (!candidates.includes(entry.src)) continue
|
|
1459
|
-
if (
|
|
1467
|
+
if (occurrenceScope(entry.file)) {
|
|
1460
1468
|
if (preferredOccurrence !== undefined && occurrenceCounter++ === preferredOccurrence) {
|
|
1461
1469
|
occurrenceMatch ??= imageEntryToLocation(entry)
|
|
1462
1470
|
}
|
|
1463
|
-
if (preferredLine !== undefined && entry.line === preferredLine) {
|
|
1471
|
+
if (preferredLine !== undefined && entry.line === preferredLine && (!preferredFile || entry.file === preferredFile)) {
|
|
1464
1472
|
lineMatch ??= imageEntryToLocation(entry)
|
|
1465
1473
|
}
|
|
1466
1474
|
}
|
|
@@ -751,7 +751,10 @@ export async function enhanceManifestWithSourceSnippets(
|
|
|
751
751
|
}
|
|
752
752
|
|
|
753
753
|
// ── Non-collection images: find via search index / AST ──
|
|
754
|
-
|
|
754
|
+
// Always pass preferredLocation when srcOccurrence is set, even if
|
|
755
|
+
// entry.sourcePath is missing — html-processor populates srcOccurrence
|
|
756
|
+
// independently of Astro's source attribution.
|
|
757
|
+
const preferredLocation = entry.sourcePath || entry.imageMetadata.srcOccurrence !== undefined
|
|
755
758
|
? {
|
|
756
759
|
file: entry.sourcePath,
|
|
757
760
|
line: entry.sourceLine,
|
package/src/types.ts
CHANGED
|
@@ -300,6 +300,10 @@ export interface FieldDefinition {
|
|
|
300
300
|
hints?: FieldHints
|
|
301
301
|
/** True when the field uses Astro's `image()` schema (entry-relative paths through astro:assets). */
|
|
302
302
|
astroImage?: boolean
|
|
303
|
+
/** Semantic role used by the editor UI to position special fields without name matching.
|
|
304
|
+
* - `publish-toggle`: boolean controlling whether the entry is published (e.g. `draft`, `isDraft`, `published`).
|
|
305
|
+
* - `publish-date`: the publish/release date field (e.g. `date`, `publishDate`, `publishedAt`). */
|
|
306
|
+
role?: 'publish-toggle' | 'publish-date'
|
|
303
307
|
}
|
|
304
308
|
|
|
305
309
|
/** Per-entry metadata for collection browsing */
|