@bygghammar/cms-platform 0.76.0 → 0.77.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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 0.77.0
|
|
2
|
+
|
|
3
|
+
- Global Sections list: preview thumbnails now use the section's first block type
|
|
4
|
+
and show a text snippet from the saved content (including Rich text `html`),
|
|
5
|
+
instead of a blank generic placeholder. Multi-block sections show a count badge.
|
|
6
|
+
- Shared thumbnail: text detection reads `html`, `body`, `lead`, `subtitle` and
|
|
7
|
+
`heading`, with locale fallback and HTML stripped to plain text.
|
|
8
|
+
|
|
1
9
|
## 0.76.0
|
|
2
10
|
|
|
3
11
|
- Rich text editor colours: swatches are now derived from the consuming site's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bygghammar/cms-platform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.77.0",
|
|
4
4
|
"description": "Shared CMS platform: blocks, block types, i18n core, UI primitives and design-token contract used by every Bygghammar/Voyara site.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -27,6 +27,24 @@ export type GlobalSectionsListDeps = {
|
|
|
27
27
|
previewSrc: (key: string) => string;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
/** Saved sections store an array of block instances; shapes may vary by age. */
|
|
31
|
+
function blockList(blocks: unknown): Record<string, unknown>[] {
|
|
32
|
+
if (!Array.isArray(blocks)) return [];
|
|
33
|
+
return blocks.filter((b): b is Record<string, unknown> => !!b && typeof b === "object");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function blockCount(blocks: unknown): number {
|
|
37
|
+
return blockList(blocks).length;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Block type of the first block, so the thumbnail can draw its wireframe. */
|
|
41
|
+
function firstBlockType(blocks: unknown): string | undefined {
|
|
42
|
+
const first = blockList(blocks)[0];
|
|
43
|
+
const type = first?.["type"];
|
|
44
|
+
return typeof type === "string" && type ? type : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
30
48
|
/**
|
|
31
49
|
* Library screen listing global sections — reusable groups of blocks that update
|
|
32
50
|
* everywhere at once. All data access and navigation is host-supplied.
|
|
@@ -98,7 +116,12 @@ export function GlobalSectionsListView({ deps }: { deps: GlobalSectionsListDeps
|
|
|
98
116
|
onClick={() => setPreview({ key: g.key, name: g.name })}
|
|
99
117
|
className="group relative block"
|
|
100
118
|
>
|
|
101
|
-
<DataThumb data={g.blocks} />
|
|
119
|
+
<DataThumb data={g.blocks} blockType={firstBlockType(g.blocks)} />
|
|
120
|
+
{blockCount(g.blocks) > 1 && (
|
|
121
|
+
<span className="absolute bottom-1 right-1 rounded bg-ink/70 px-1 text-[0.55rem] leading-4 text-white">
|
|
122
|
+
{blockCount(g.blocks)}
|
|
123
|
+
</span>
|
|
124
|
+
)}
|
|
102
125
|
<span className="absolute inset-0 hidden items-center justify-center bg-ink/50 text-white group-hover:flex">
|
|
103
126
|
<Eye className="size-4" />
|
|
104
127
|
</span>
|
|
@@ -64,6 +64,42 @@ function findImage(node: unknown, depth = 0): string | null {
|
|
|
64
64
|
return null;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
const ENTITIES: Record<string, string> = {
|
|
68
|
+
" ": " ",
|
|
69
|
+
"&": "&",
|
|
70
|
+
"<": "<",
|
|
71
|
+
">": ">",
|
|
72
|
+
""": '"',
|
|
73
|
+
"'": "'",
|
|
74
|
+
"'": "'",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** Plain-text snippet from sanitized editor HTML (no DOM needed, SSR-safe). */
|
|
78
|
+
function htmlToText(html: string): string {
|
|
79
|
+
const withBreaks = html.replace(/<(br|\/p|\/h[1-6]|\/li|\/div)[^>]*>/gi, " ");
|
|
80
|
+
const stripped = withBreaks.replace(/<[^>]*>/g, "");
|
|
81
|
+
const decoded = stripped
|
|
82
|
+
.replace(/ |&|<|>|"|'|'/gi, (m) => ENTITIES[m.toLowerCase()] ?? m)
|
|
83
|
+
.replace(/&#(\d+);/g, (_, code: string) => String.fromCodePoint(Number(code)));
|
|
84
|
+
return decoded.replace(/\s+/g, " ").trim();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Text keys, in the order a block would show them. */
|
|
88
|
+
const TEXT_KEYS = ["title", "heading", "name", "label", "eyebrow", "html", "body", "lead", "subtitle"];
|
|
89
|
+
|
|
90
|
+
/** Picks a string out of a plain string or a localized map (active locale first). */
|
|
91
|
+
function localizedString(value: unknown): string | null {
|
|
92
|
+
if (typeof value === "string") return value.trim() ? value : null;
|
|
93
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
94
|
+
const map = value as Record<string, unknown>;
|
|
95
|
+
const ordered = ["sv", "en", ...Object.keys(map)];
|
|
96
|
+
for (const locale of ordered) {
|
|
97
|
+
const candidate = map[locale];
|
|
98
|
+
if (typeof candidate === "string" && candidate.trim()) return candidate;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
67
103
|
function findText(node: unknown, depth = 0): string | null {
|
|
68
104
|
if (node == null || depth > 4) return null;
|
|
69
105
|
if (Array.isArray(node)) {
|
|
@@ -75,13 +111,16 @@ function findText(node: unknown, depth = 0): string | null {
|
|
|
75
111
|
}
|
|
76
112
|
if (typeof node !== "object") return null;
|
|
77
113
|
const obj = node as Record<string, unknown>;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
114
|
+
const nested = obj["props"];
|
|
115
|
+
if (nested && typeof nested === "object") {
|
|
116
|
+
const found = findText(nested, depth + 1);
|
|
117
|
+
if (found) return found;
|
|
118
|
+
}
|
|
119
|
+
for (const key of TEXT_KEYS) {
|
|
120
|
+
const raw = localizedString(obj[key]);
|
|
121
|
+
if (!raw) continue;
|
|
122
|
+
const text = key === "html" || /<[a-z][^>]*>/i.test(raw) ? htmlToText(raw) : raw.trim();
|
|
123
|
+
if (text) return text.length > 120 ? `${text.slice(0, 120)}…` : text;
|
|
85
124
|
}
|
|
86
125
|
return null;
|
|
87
126
|
}
|
|
@@ -372,7 +411,7 @@ export function DataThumb({
|
|
|
372
411
|
|
|
373
412
|
return (
|
|
374
413
|
<div className="relative shrink-0 border border-ink/10 bg-white overflow-hidden" style={style}>
|
|
375
|
-
{text
|
|
414
|
+
{text ? (
|
|
376
415
|
<div className="flex size-full flex-col justify-center gap-1.5 p-2">
|
|
377
416
|
<div className="h-1 w-6 bg-sea/60" />
|
|
378
417
|
<div className="text-[0.5rem] leading-tight text-ink/70 line-clamp-2 font-serif">
|