@crombolo/delivery-client 0.37.0 → 0.38.1

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/index.d.ts CHANGED
@@ -119,11 +119,87 @@ export interface ApiBlockGrid<Content = ApiElement, Settings = ApiElement> {
119
119
  gridColumns: number;
120
120
  items: ApiBlockGridItem<Content, Settings>[];
121
121
  }
122
- /** Rich text: its markup, with links already this site's URLs. */
123
- export interface ApiRichText {
124
- markup: string;
125
- blocks: unknown[];
122
+ /** Text in a rich-text tree. */
123
+ export interface RichTextTextElement {
124
+ $type: 'RichTextTextElement';
125
+ tag: '#text';
126
+ text: string;
127
+ }
128
+ /** A tag in a rich-text tree, its `data-*` attributes without their prefix. */
129
+ export interface RichTextGenericElement<Tag extends string = string, Attributes = RichTextAttributes> {
130
+ $type: 'RichTextGenericElement';
131
+ tag: Tag;
132
+ attributes: Attributes;
133
+ elements: RichTextElement[];
134
+ }
135
+ export type RichTextAttributes = Record<string, unknown>;
136
+ /**
137
+ * A link: to a page, with its `route` and no `href`; to media, with `href` its
138
+ * URL; or anywhere else, with `href` as written. A local link to nothing has
139
+ * neither.
140
+ */
141
+ export interface RichTextLinkAttributes extends RichTextAttributes {
142
+ href?: string;
143
+ target?: string;
144
+ title?: string;
145
+ destinationId?: string;
146
+ destinationType?: string;
147
+ linkType?: 'Content' | 'Media';
148
+ route?: ApiRoute;
149
+ }
150
+ /** An image; from the media library, `src` is its URL with the crop query kept. */
151
+ export interface RichTextImageAttributes extends RichTextAttributes {
152
+ src?: string;
153
+ alt?: string;
154
+ width?: string;
155
+ height?: string;
156
+ }
157
+ /** Where a block sits: `content-id` names its entry in the value's `blocks`. */
158
+ export interface RichTextBlockAttributes extends RichTextAttributes {
159
+ 'content-id': string;
160
+ }
161
+ /** The tags whose attributes are known. */
162
+ export interface RichTextTags {
163
+ a: RichTextGenericElement<'a', RichTextLinkAttributes>;
164
+ img: RichTextGenericElement<'img', RichTextImageAttributes>;
165
+ 'umb-rte-block': RichTextGenericElement<'umb-rte-block', RichTextBlockAttributes>;
166
+ 'umb-rte-block-inline': RichTextGenericElement<'umb-rte-block-inline', RichTextBlockAttributes>;
167
+ }
168
+ export type RichTextBlockElement = RichTextTags['umb-rte-block' | 'umb-rte-block-inline'];
169
+ /** Any element below the root. */
170
+ export type RichTextElement = RichTextGenericElement | RichTextTextElement;
171
+ /**
172
+ * A rich-text value: Umbraco's `RichTextOutputAsJson` tree, the only form the
173
+ * Delivery API answers rich text in (story 216).
174
+ */
175
+ export interface RichTextValue<Content = ApiElement, Settings = ApiElement> {
176
+ tag: '#root';
177
+ attributes: RichTextAttributes;
178
+ elements: RichTextElement[];
179
+ blocks: ApiBlockListItem<Content, Settings>[];
180
+ }
181
+ /**
182
+ * What `walkRichText` makes of each node, given what it made of the node's
183
+ * children. A tag with no visitor of its own goes to `element`; a block to
184
+ * `block` when there is one, with its entry in `blocks`.
185
+ */
186
+ export interface RichTextVisitors<Result, Content = ApiElement, Settings = ApiElement> {
187
+ text(node: RichTextTextElement): Result;
188
+ element(node: RichTextGenericElement, children: Result[]): Result;
189
+ block?(item: ApiBlockListItem<Content, Settings> | undefined, node: RichTextBlockElement): Result;
190
+ tags?: {
191
+ [Tag in keyof RichTextTags]?: (node: RichTextTags[Tag], children: Result[]) => Result;
192
+ } & {
193
+ [tag: string]: ((node: never, children: Result[]) => Result) | undefined;
194
+ };
126
195
  }
196
+ /**
197
+ * Text or an attribute from a rich-text tree as characters: the tree keeps
198
+ * entities as the CMS stored them, as Umbraco's does. Unknown names are left.
199
+ */
200
+ export declare function decodeEntities(text: string): string;
201
+ /** A rich-text tree folded through `visitors`, children first: what each top-level node made. */
202
+ export declare function walkRichText<Result, Content = ApiElement, Settings = ApiElement>(tree: RichTextValue<Content, Settings>, visitors: RichTextVisitors<Result, Content, Settings>): Result[];
127
203
  export interface ApiColor {
128
204
  value: string;
129
205
  label: string;
package/dist/index.js CHANGED
@@ -1,4 +1,63 @@
1
1
  // packages/delivery-client/src/index.ts
2
+ var ENTITIES = {
3
+ amp: "&",
4
+ lt: "<",
5
+ gt: ">",
6
+ quot: '"',
7
+ apos: "'",
8
+ nbsp: " ",
9
+ shy: "­",
10
+ copy: "©",
11
+ reg: "®",
12
+ trade: "™",
13
+ hellip: "…",
14
+ mdash: "—",
15
+ ndash: "–",
16
+ lsquo: "‘",
17
+ rsquo: "’",
18
+ ldquo: "“",
19
+ rdquo: "”",
20
+ laquo: "«",
21
+ raquo: "»",
22
+ bull: "•",
23
+ middot: "·",
24
+ deg: "°",
25
+ times: "×",
26
+ divide: "÷",
27
+ euro: "€",
28
+ pound: "£"
29
+ };
30
+ function decodeEntities(text) {
31
+ if (!text.includes("&"))
32
+ return text;
33
+ return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+\d*);/gi, (whole, name) => {
34
+ if (name[0] !== "#")
35
+ return ENTITIES[name] ?? ENTITIES[name.toLowerCase()] ?? whole;
36
+ const code = name[1] === "x" || name[1] === "X" ? Number.parseInt(name.slice(2), 16) : Number(name.slice(1));
37
+ return code > 0 && code <= 1114111 ? String.fromCodePoint(code) : whole;
38
+ });
39
+ }
40
+ var BLOCK_TAGS = new Set(["umb-rte-block", "umb-rte-block-inline"]);
41
+ function walkRichText(tree, visitors) {
42
+ const blocks = new Map(tree.blocks.flatMap((item) => {
43
+ const id = item.content?.id;
44
+ return typeof id === "string" ? [[id.toLowerCase(), item]] : [];
45
+ }));
46
+ const visit = (node) => {
47
+ if (node.tag === "#text")
48
+ return visitors.text(node);
49
+ const element = node;
50
+ if (visitors.block && BLOCK_TAGS.has(element.tag)) {
51
+ const block = element;
52
+ const id = String(block.attributes["content-id"] ?? "").toLowerCase();
53
+ return visitors.block(blocks.get(id), block);
54
+ }
55
+ const children = element.elements.map(visit);
56
+ const own = visitors.tags?.[element.tag];
57
+ return own ? own(element, children) : visitors.element(element, children);
58
+ };
59
+ return tree.elements.map(visit);
60
+ }
2
61
  function isExpanded(reference) {
3
62
  return Object.keys(reference.properties).length > 0;
4
63
  }
@@ -157,6 +216,8 @@ export {
157
216
  DeliveryError,
158
217
  contentKeysIn,
159
218
  createDeliveryClient,
219
+ decodeEntities,
160
220
  isExpanded,
161
- memoryConditionalStore
221
+ memoryConditionalStore,
222
+ walkRichText
162
223
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crombolo/delivery-client",
3
- "version": "0.37.0",
3
+ "version": "0.38.1",
4
4
  "description": "A typed client for a crombolo site's Delivery API, for apps in the browser, Node or Bun. No dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "Crombolo",