@uniweb/kit 0.19.11 → 0.19.13
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/kit",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.13",
|
|
4
4
|
"description": "Standard component library for Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
"shiki": "^3.0.0",
|
|
44
44
|
"tailwind-merge": "^3.6.0",
|
|
45
45
|
"temml": "^0.13.2",
|
|
46
|
+
"@uniweb/semantic-parser": "^1.5.1",
|
|
46
47
|
"@uniweb/core": "^0.35.0",
|
|
47
48
|
"@uniweb/projections": "^0.7.11",
|
|
48
|
-
"@uniweb/
|
|
49
|
-
"@uniweb/
|
|
50
|
-
"@uniweb/semantic-parser": "^1.4.1"
|
|
49
|
+
"@uniweb/schemas": "^0.9.4",
|
|
50
|
+
"@uniweb/scene": "^0.1.3"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"react": "^19.0.0",
|
|
@@ -131,13 +131,17 @@ export function FileLogo({ filename, size = '24', className, ...props }) {
|
|
|
131
131
|
const ext = getExtension(filename)
|
|
132
132
|
const iconType = FILE_ICONS[ext] || 'default'
|
|
133
133
|
const icon = ICONS[iconType]
|
|
134
|
+
// A number or a string of digits is pixels. React appends `px` to a number and
|
|
135
|
+
// to nothing else, so the default `'24'` rendered `width:24` — invalid CSS the
|
|
136
|
+
// browser drops — until 2026-09-29. Any other string (`'1.5em'`) is kept as written.
|
|
137
|
+
const dimension = /^\d+(\.\d+)?$/.test(String(size)) ? `${size}px` : size
|
|
134
138
|
|
|
135
139
|
return (
|
|
136
140
|
<svg
|
|
137
141
|
viewBox="0 0 24 24"
|
|
138
142
|
fill="currentColor"
|
|
139
143
|
className={cn('inline-block', className)}
|
|
140
|
-
style={{ width:
|
|
144
|
+
style={{ width: dimension, height: dimension }}
|
|
141
145
|
aria-hidden="true"
|
|
142
146
|
{...props}
|
|
143
147
|
>
|
|
@@ -54,6 +54,7 @@ import { Image } from '../../components/Image/index.js'
|
|
|
54
54
|
import { Media } from '../../components/Media/index.js'
|
|
55
55
|
import { Icon } from '../../components/Icon/index.js'
|
|
56
56
|
import { Link } from '../../components/Link/index.js'
|
|
57
|
+
import { FileLogo } from '../../components/FileLogo/index.js'
|
|
57
58
|
import { Code } from '../Section/renderers/Code.jsx'
|
|
58
59
|
import { Divider } from '../Section/renderers/Divider.jsx'
|
|
59
60
|
|
|
@@ -98,7 +99,6 @@ export const NOT_RENDERED = {
|
|
|
98
99
|
'inset_ref nodes, and they render nothing.',
|
|
99
100
|
form: 'an editor node; its data reaches components as content.data[schemaId]',
|
|
100
101
|
'card-group': 'an editor node, deprecated — maps to content.data[cardType]',
|
|
101
|
-
'document-group': 'an editor node — its documents reach content.links',
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
@@ -114,6 +114,11 @@ export const NOT_RENDERED = {
|
|
|
114
114
|
* internal links. Until 2026-07-31 nothing emitted it, so an authored id
|
|
115
115
|
* existed only inside the registry and never reached the document.
|
|
116
116
|
*/
|
|
117
|
+
/** A file's name from its address — the last path segment, without a query or fragment. */
|
|
118
|
+
function fileNameOf(url) {
|
|
119
|
+
return String(url || '').split(/[?#]/)[0].split('/').pop() || ''
|
|
120
|
+
}
|
|
121
|
+
|
|
117
122
|
function authoredId(element) {
|
|
118
123
|
return element?.attrs?.id ?? element?.id ?? undefined
|
|
119
124
|
}
|
|
@@ -312,6 +317,36 @@ export function SequenceElement({ element, block, components }) {
|
|
|
312
317
|
)
|
|
313
318
|
}
|
|
314
319
|
|
|
320
|
+
// A file — a PDF — with an optional preview: one link to the file, holding
|
|
321
|
+
// the preview (or a file badge, when there is none) and the document's name.
|
|
322
|
+
// The author and description describe the RESOURCE, so they sit in the
|
|
323
|
+
// caption, and the preview's alt is empty — the name labels the link. <Link>
|
|
324
|
+
// treats a file address as a download and applies the base path. Until
|
|
325
|
+
// 2026-09-29 the parser delivered a document as an image, and this drew
|
|
326
|
+
// `<img src="report.pdf">`.
|
|
327
|
+
case 'document': {
|
|
328
|
+
// `file` is the file's own name — the parser reads it from the address, and a build
|
|
329
|
+
// that copies the file under a hashed name stamps the one it had; the address is
|
|
330
|
+
// the fallback for content parsed before documents carried one (2026-09-29).
|
|
331
|
+
const { url, alt, caption, preview, author, description, name: file } = element.attrs || {}
|
|
332
|
+
const name = caption || alt || file || fileNameOf(url)
|
|
333
|
+
return (
|
|
334
|
+
<figure id={authoredId(element)} data-kind="document">
|
|
335
|
+
<Link to={url}>
|
|
336
|
+
{preview ? <Image src={preview} alt="" /> : <FileLogo filename={file || url} />}
|
|
337
|
+
<span>{name}</span>
|
|
338
|
+
</Link>
|
|
339
|
+
{(author || description) && (
|
|
340
|
+
<figcaption>
|
|
341
|
+
{author && <span>{author}</span>}
|
|
342
|
+
{author && description && ' — '}
|
|
343
|
+
{description && <span>{description}</span>}
|
|
344
|
+
</figcaption>
|
|
345
|
+
)}
|
|
346
|
+
</figure>
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
315
350
|
// Spread rather than cherry-pick: a markdown video carries poster and the
|
|
316
351
|
// playback flags, and <Media> reads all of them.
|
|
317
352
|
case 'video':
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Visual Component
|
|
3
3
|
*
|
|
4
|
-
* Renders the first non-empty
|
|
5
|
-
*
|
|
4
|
+
* Renders one visual: a `content.media` item by its kind, or the first non-empty of
|
|
5
|
+
* the candidates it is handed (inset, then video, then image).
|
|
6
6
|
*
|
|
7
7
|
* @module @uniweb/kit/styled/Visual
|
|
8
8
|
*/
|
|
@@ -13,9 +13,19 @@ import { Image } from "../../components/Image/index.js";
|
|
|
13
13
|
import { getChildBlockRenderer } from "../../utils/index.js";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Renders
|
|
16
|
+
* Renders one visual.
|
|
17
|
+
*
|
|
18
|
+
* ⭐ `media` is a `content.media` item — an image, a video or an embedded component,
|
|
19
|
+
* tagged with its `kind` — rendered by that kind. Handed the whole `content.media`,
|
|
20
|
+
* it renders the first item: a slot of one is the first thing the author placed in
|
|
21
|
+
* it, whatever its kind. An embedded component needs `block`, whose insets it is
|
|
22
|
+
* one of.
|
|
23
|
+
*
|
|
24
|
+
* Without `media`, the first non-empty candidate: inset, then video, then image.
|
|
17
25
|
*
|
|
18
26
|
* @param {Object} props
|
|
27
|
+
* @param {Object|Object[]} [props.media] - A `content.media` item, or the list (its first item)
|
|
28
|
+
* @param {Object} [props.block] - The section's block — resolves an embedded component
|
|
19
29
|
* @param {Object} [props.inset] - Inset Block instance (from block.insets or block.getInset())
|
|
20
30
|
* @param {Object} [props.video] - Video object with src property
|
|
21
31
|
* @param {Object} [props.image] - Image object with src and alt properties
|
|
@@ -23,24 +33,25 @@ import { getChildBlockRenderer } from "../../utils/index.js";
|
|
|
23
33
|
* @param {React.ReactNode} [props.fallback] - Fallback when no visual is found
|
|
24
34
|
*
|
|
25
35
|
* @example
|
|
26
|
-
* //
|
|
27
|
-
* <Visual
|
|
36
|
+
* // The slot of one — the author's first image, video or component
|
|
37
|
+
* <Visual media={content.media} block={block} className="rounded-lg" />
|
|
28
38
|
*
|
|
29
39
|
* @example
|
|
30
|
-
* //
|
|
31
|
-
* <Visual
|
|
40
|
+
* // Every item of a gallery, in the author's order
|
|
41
|
+
* {content.media.map((item, i) => <Visual key={i} media={item} block={block} />)}
|
|
32
42
|
*
|
|
33
43
|
* @example
|
|
34
|
-
* //
|
|
35
|
-
* <Visual image={content.images[
|
|
44
|
+
* // Try inset first, fall back to video, then image
|
|
45
|
+
* <Visual inset={block.insets[0]} video={content.videos[0]} image={content.images[0]} />
|
|
36
46
|
*/
|
|
37
|
-
export function Visual({ inset, video, image, className, fallback = null }) {
|
|
38
|
-
if (
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
return className ? <div className={className}>{rendered}</div> : rendered;
|
|
47
|
+
export function Visual({ media, block, inset, video, image, className, fallback = null }) {
|
|
48
|
+
if (media !== undefined) {
|
|
49
|
+
const item = Array.isArray(media) ? media[0] : media;
|
|
50
|
+
return renderItem(item, block, className) ?? fallback;
|
|
42
51
|
}
|
|
43
52
|
|
|
53
|
+
if (inset) return renderInset(inset, className);
|
|
54
|
+
|
|
44
55
|
if (video) {
|
|
45
56
|
return <Media {...video} className={className} />;
|
|
46
57
|
}
|
|
@@ -52,3 +63,30 @@ export function Visual({ inset, video, image, className, fallback = null }) {
|
|
|
52
63
|
|
|
53
64
|
return fallback;
|
|
54
65
|
}
|
|
66
|
+
|
|
67
|
+
/** A `content.media` item, rendered by its kind — or null when it cannot be. */
|
|
68
|
+
function renderItem(item, block, className) {
|
|
69
|
+
if (!item || typeof item !== "object") return null;
|
|
70
|
+
const { kind, ...attrs } = item;
|
|
71
|
+
switch (kind) {
|
|
72
|
+
case "image": {
|
|
73
|
+
const { contentType, viewType, contentId, imgPos, ...imageProps } = attrs;
|
|
74
|
+
return <Image {...imageProps} className={className} />;
|
|
75
|
+
}
|
|
76
|
+
case "video":
|
|
77
|
+
return <Media {...attrs} className={className} />;
|
|
78
|
+
case "inset": {
|
|
79
|
+
const insetBlock = attrs.refId ? block?.getInset?.(attrs.refId) : null;
|
|
80
|
+
return insetBlock ? renderInset(insetBlock, className) : null;
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function renderInset(inset, className) {
|
|
88
|
+
const Renderer = getChildBlockRenderer();
|
|
89
|
+
if (!Renderer) return null;
|
|
90
|
+
const rendered = <Renderer blocks={[inset]} />;
|
|
91
|
+
return className ? <div className={className}>{rendered}</div> : rendered;
|
|
92
|
+
}
|