chaingrow-blog 0.2.2 → 0.7.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/README.md +617 -139
- package/dist/blog-cards-D1j7ZqHK.d.ts +123 -0
- package/dist/blog-cards-DPfcmUUD.d.cts +123 -0
- package/dist/chunk-4EKET2DQ.js +34 -0
- package/dist/chunk-4EKET2DQ.js.map +1 -0
- package/dist/chunk-BXEQ6QKM.js +636 -0
- package/dist/chunk-BXEQ6QKM.js.map +1 -0
- package/dist/chunk-K3HNYPEP.js +176 -0
- package/dist/chunk-K3HNYPEP.js.map +1 -0
- package/dist/index.cjs +237 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -3
- package/dist/index.d.ts +20 -3
- package/dist/index.js +101 -3
- package/dist/index.js.map +1 -1
- package/dist/next/index.cjs +682 -11
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +168 -2
- package/dist/next/index.d.ts +168 -2
- package/dist/next/index.js +14 -42
- package/dist/next/index.js.map +1 -1
- package/dist/next/webhook.d.cts +1 -1
- package/dist/next/webhook.d.ts +1 -1
- package/dist/react/index.cjs +721 -37
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +189 -31
- package/dist/react/index.d.ts +189 -31
- package/dist/react/index.js +265 -65
- package/dist/react/index.js.map +1 -1
- package/dist/types-DvkCm1rh.d.cts +114 -0
- package/dist/types-DvkCm1rh.d.ts +114 -0
- package/package.json +1 -1
- package/dist/chunk-OTLGKSYD.js +0 -74
- package/dist/chunk-OTLGKSYD.js.map +0 -1
- package/dist/types-CoPD0ejB.d.cts +0 -53
- package/dist/types-CoPD0ejB.d.ts +0 -53
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { B as Blog, F as FaqItem, a as Block, E as EmbeddedMedia } from './types-DvkCm1rh.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Consumer-supplied extras for BlogPosting. Everything is optional — the
|
|
5
|
+
* SDK doesn't know the customer's base URL, organisation name, or author
|
|
6
|
+
* identity, so it just omits what it isn't told. Schema.org treats
|
|
7
|
+
* `author`, `publisher`, and `url` as recommended but not required.
|
|
8
|
+
*/
|
|
9
|
+
type BlogJsonLdOptions = {
|
|
10
|
+
/** Absolute URL of the blog page (e.g. `https://example.com/blog/my-post`). */
|
|
11
|
+
url?: string;
|
|
12
|
+
author?: {
|
|
13
|
+
name: string;
|
|
14
|
+
type?: 'Person' | 'Organization';
|
|
15
|
+
url?: string;
|
|
16
|
+
};
|
|
17
|
+
publisher?: {
|
|
18
|
+
name: string;
|
|
19
|
+
logo?: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Build a Schema.org `BlogPosting` JSON-LD object from a chaingrow Blog.
|
|
24
|
+
* Fields that aren't present on the Blog (description, og_image, …) are
|
|
25
|
+
* omitted entirely so the resulting JSON has no `undefined` junk.
|
|
26
|
+
*/
|
|
27
|
+
declare function buildBlogPostingSchema(blog: Blog, opts?: BlogJsonLdOptions): Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Build a Schema.org `FAQPage` JSON-LD object from chaingrow's `{question, answer}[]`
|
|
30
|
+
* pairs. Returns `null` for empty input so callers can skip it with a single
|
|
31
|
+
* `if (faqSchema) items.push(faqSchema)` / `filter(Boolean)`.
|
|
32
|
+
*/
|
|
33
|
+
declare function buildFAQPageSchema(faq: FaqItem[] | null | undefined): Record<string, unknown> | null;
|
|
34
|
+
/**
|
|
35
|
+
* Combines BlogPosting + FAQPage + any LLM-generated `structured_data.jsonLd`
|
|
36
|
+
* extras into a flat array ready for `@graph`. Tolerant of missing/malformed
|
|
37
|
+
* `structured_data` — if the backend ever sends something unexpected we just
|
|
38
|
+
* skip it rather than crashing the consumer's page.
|
|
39
|
+
*/
|
|
40
|
+
declare function buildBlogJsonLd(blog: Blog, opts?: BlogJsonLdOptions): Record<string, unknown>[];
|
|
41
|
+
/** Schema-org context string — exposed so `<JsonLdGraph>` and any
|
|
42
|
+
* bring-your-own renderer use the same value. */
|
|
43
|
+
declare const SCHEMA_ORG_CONTEXT: "https://schema.org";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Compute the block-index insertion point for each media item when
|
|
47
|
+
* distributing `mediaCount` attachments across `blockCount` blocks.
|
|
48
|
+
*
|
|
49
|
+
* Formula: media `i` goes at insertion index `floor(i * blockCount / mediaCount)`.
|
|
50
|
+
* An insertion index of `0` means "before block 0" (top of body); `blockCount`
|
|
51
|
+
* means "after the last block".
|
|
52
|
+
*
|
|
53
|
+
* Examples (blockCount = 10):
|
|
54
|
+
* mediaCount = 1 → [0] (single media at top)
|
|
55
|
+
* mediaCount = 2 → [0, 5] (top + middle)
|
|
56
|
+
* mediaCount = 3 → [0, 3, 6] (top, 1/3, 2/3)
|
|
57
|
+
* mediaCount = 4 → [0, 2, 5, 7] (every quarter)
|
|
58
|
+
*
|
|
59
|
+
* When `blockCount` is 0 every media falls at position 0 and stacks at the
|
|
60
|
+
* top. When two media round to the same position they both land there and
|
|
61
|
+
* render consecutively; this is deterministic but not always visually ideal
|
|
62
|
+
* for very small `blockCount / mediaCount` ratios — assume content has
|
|
63
|
+
* enough blocks to spread across.
|
|
64
|
+
*/
|
|
65
|
+
declare function distributeMediaPositions(blockCount: number, mediaCount: number): number[];
|
|
66
|
+
/** One interleaved slot in a blog's body — either a block or a media item. */
|
|
67
|
+
type InterleavedSlot = {
|
|
68
|
+
kind: 'block';
|
|
69
|
+
block: Block;
|
|
70
|
+
index: number;
|
|
71
|
+
} | {
|
|
72
|
+
kind: 'media';
|
|
73
|
+
media: EmbeddedMedia;
|
|
74
|
+
index: number;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Walk `blocks` once, splicing in `media` at the positions returned by
|
|
78
|
+
* `distributeMediaPositions`. Returns a flat array of typed slots that
|
|
79
|
+
* `<BlogRenderer>` can map over directly. Exposed so consumers who need a
|
|
80
|
+
* custom layout (e.g. wrapping each section in a `<section>`) can use the
|
|
81
|
+
* same placement logic without re-implementing it.
|
|
82
|
+
*/
|
|
83
|
+
declare function interleaveMedia(blocks: Block[], media: EmbeddedMedia[] | undefined): InterleavedSlot[];
|
|
84
|
+
|
|
85
|
+
type BlogCard = {
|
|
86
|
+
id: string;
|
|
87
|
+
href: string;
|
|
88
|
+
title: string;
|
|
89
|
+
description: string | null;
|
|
90
|
+
og_image: string | null;
|
|
91
|
+
published_at: string | null;
|
|
92
|
+
language?: string;
|
|
93
|
+
};
|
|
94
|
+
type ToBlogCardOptions = {
|
|
95
|
+
/** URL prefix for the generated `href`. Defaults to `/blog`. */
|
|
96
|
+
pathPrefix?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Full control over the generated `href` — takes precedence over
|
|
99
|
+
* `pathPrefix`. Use this when the route depends on more than a static
|
|
100
|
+
* prefix, e.g. locale-prefixed routes:
|
|
101
|
+
*
|
|
102
|
+
* ```ts
|
|
103
|
+
* hrefFor: (blog) =>
|
|
104
|
+
* blog.language === 'de' ? `/blog/${blog.slug}` : `/${blog.language}/blog/${blog.slug}`
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
hrefFor?: (blog: Blog) => string;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Project a chaingrow `Blog` into the small `BlogCard` shape used by
|
|
111
|
+
* `<BlogIndex>` and `mergeBlogs`. Hybrid integrators can map their own
|
|
112
|
+
* legacy posts into the same shape (`extra` prop on `<BlogIndex>`) and
|
|
113
|
+
* the two sources render through the same Card slot.
|
|
114
|
+
*/
|
|
115
|
+
declare function toBlogCard(blog: Blog, opts?: ToBlogCardOptions): BlogCard;
|
|
116
|
+
/**
|
|
117
|
+
* Merge a list of chaingrow cards with extra (legacy) cards and return a
|
|
118
|
+
* single list sorted newest-first by `published_at`. Cards without a
|
|
119
|
+
* `published_at` sort to the end.
|
|
120
|
+
*/
|
|
121
|
+
declare function mergeBlogs(chaingrow: BlogCard[], extra?: BlogCard[]): BlogCard[];
|
|
122
|
+
|
|
123
|
+
export { type BlogCard as B, type InterleavedSlot as I, SCHEMA_ORG_CONTEXT as S, type ToBlogCardOptions as T, type BlogJsonLdOptions as a, buildBlogJsonLd as b, buildBlogPostingSchema as c, buildFAQPageSchema as d, distributeMediaPositions as e, interleaveMedia as i, mergeBlogs as m, toBlogCard as t };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { B as Blog, F as FaqItem, a as Block, E as EmbeddedMedia } from './types-DvkCm1rh.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Consumer-supplied extras for BlogPosting. Everything is optional — the
|
|
5
|
+
* SDK doesn't know the customer's base URL, organisation name, or author
|
|
6
|
+
* identity, so it just omits what it isn't told. Schema.org treats
|
|
7
|
+
* `author`, `publisher`, and `url` as recommended but not required.
|
|
8
|
+
*/
|
|
9
|
+
type BlogJsonLdOptions = {
|
|
10
|
+
/** Absolute URL of the blog page (e.g. `https://example.com/blog/my-post`). */
|
|
11
|
+
url?: string;
|
|
12
|
+
author?: {
|
|
13
|
+
name: string;
|
|
14
|
+
type?: 'Person' | 'Organization';
|
|
15
|
+
url?: string;
|
|
16
|
+
};
|
|
17
|
+
publisher?: {
|
|
18
|
+
name: string;
|
|
19
|
+
logo?: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Build a Schema.org `BlogPosting` JSON-LD object from a chaingrow Blog.
|
|
24
|
+
* Fields that aren't present on the Blog (description, og_image, …) are
|
|
25
|
+
* omitted entirely so the resulting JSON has no `undefined` junk.
|
|
26
|
+
*/
|
|
27
|
+
declare function buildBlogPostingSchema(blog: Blog, opts?: BlogJsonLdOptions): Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Build a Schema.org `FAQPage` JSON-LD object from chaingrow's `{question, answer}[]`
|
|
30
|
+
* pairs. Returns `null` for empty input so callers can skip it with a single
|
|
31
|
+
* `if (faqSchema) items.push(faqSchema)` / `filter(Boolean)`.
|
|
32
|
+
*/
|
|
33
|
+
declare function buildFAQPageSchema(faq: FaqItem[] | null | undefined): Record<string, unknown> | null;
|
|
34
|
+
/**
|
|
35
|
+
* Combines BlogPosting + FAQPage + any LLM-generated `structured_data.jsonLd`
|
|
36
|
+
* extras into a flat array ready for `@graph`. Tolerant of missing/malformed
|
|
37
|
+
* `structured_data` — if the backend ever sends something unexpected we just
|
|
38
|
+
* skip it rather than crashing the consumer's page.
|
|
39
|
+
*/
|
|
40
|
+
declare function buildBlogJsonLd(blog: Blog, opts?: BlogJsonLdOptions): Record<string, unknown>[];
|
|
41
|
+
/** Schema-org context string — exposed so `<JsonLdGraph>` and any
|
|
42
|
+
* bring-your-own renderer use the same value. */
|
|
43
|
+
declare const SCHEMA_ORG_CONTEXT: "https://schema.org";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Compute the block-index insertion point for each media item when
|
|
47
|
+
* distributing `mediaCount` attachments across `blockCount` blocks.
|
|
48
|
+
*
|
|
49
|
+
* Formula: media `i` goes at insertion index `floor(i * blockCount / mediaCount)`.
|
|
50
|
+
* An insertion index of `0` means "before block 0" (top of body); `blockCount`
|
|
51
|
+
* means "after the last block".
|
|
52
|
+
*
|
|
53
|
+
* Examples (blockCount = 10):
|
|
54
|
+
* mediaCount = 1 → [0] (single media at top)
|
|
55
|
+
* mediaCount = 2 → [0, 5] (top + middle)
|
|
56
|
+
* mediaCount = 3 → [0, 3, 6] (top, 1/3, 2/3)
|
|
57
|
+
* mediaCount = 4 → [0, 2, 5, 7] (every quarter)
|
|
58
|
+
*
|
|
59
|
+
* When `blockCount` is 0 every media falls at position 0 and stacks at the
|
|
60
|
+
* top. When two media round to the same position they both land there and
|
|
61
|
+
* render consecutively; this is deterministic but not always visually ideal
|
|
62
|
+
* for very small `blockCount / mediaCount` ratios — assume content has
|
|
63
|
+
* enough blocks to spread across.
|
|
64
|
+
*/
|
|
65
|
+
declare function distributeMediaPositions(blockCount: number, mediaCount: number): number[];
|
|
66
|
+
/** One interleaved slot in a blog's body — either a block or a media item. */
|
|
67
|
+
type InterleavedSlot = {
|
|
68
|
+
kind: 'block';
|
|
69
|
+
block: Block;
|
|
70
|
+
index: number;
|
|
71
|
+
} | {
|
|
72
|
+
kind: 'media';
|
|
73
|
+
media: EmbeddedMedia;
|
|
74
|
+
index: number;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Walk `blocks` once, splicing in `media` at the positions returned by
|
|
78
|
+
* `distributeMediaPositions`. Returns a flat array of typed slots that
|
|
79
|
+
* `<BlogRenderer>` can map over directly. Exposed so consumers who need a
|
|
80
|
+
* custom layout (e.g. wrapping each section in a `<section>`) can use the
|
|
81
|
+
* same placement logic without re-implementing it.
|
|
82
|
+
*/
|
|
83
|
+
declare function interleaveMedia(blocks: Block[], media: EmbeddedMedia[] | undefined): InterleavedSlot[];
|
|
84
|
+
|
|
85
|
+
type BlogCard = {
|
|
86
|
+
id: string;
|
|
87
|
+
href: string;
|
|
88
|
+
title: string;
|
|
89
|
+
description: string | null;
|
|
90
|
+
og_image: string | null;
|
|
91
|
+
published_at: string | null;
|
|
92
|
+
language?: string;
|
|
93
|
+
};
|
|
94
|
+
type ToBlogCardOptions = {
|
|
95
|
+
/** URL prefix for the generated `href`. Defaults to `/blog`. */
|
|
96
|
+
pathPrefix?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Full control over the generated `href` — takes precedence over
|
|
99
|
+
* `pathPrefix`. Use this when the route depends on more than a static
|
|
100
|
+
* prefix, e.g. locale-prefixed routes:
|
|
101
|
+
*
|
|
102
|
+
* ```ts
|
|
103
|
+
* hrefFor: (blog) =>
|
|
104
|
+
* blog.language === 'de' ? `/blog/${blog.slug}` : `/${blog.language}/blog/${blog.slug}`
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
hrefFor?: (blog: Blog) => string;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Project a chaingrow `Blog` into the small `BlogCard` shape used by
|
|
111
|
+
* `<BlogIndex>` and `mergeBlogs`. Hybrid integrators can map their own
|
|
112
|
+
* legacy posts into the same shape (`extra` prop on `<BlogIndex>`) and
|
|
113
|
+
* the two sources render through the same Card slot.
|
|
114
|
+
*/
|
|
115
|
+
declare function toBlogCard(blog: Blog, opts?: ToBlogCardOptions): BlogCard;
|
|
116
|
+
/**
|
|
117
|
+
* Merge a list of chaingrow cards with extra (legacy) cards and return a
|
|
118
|
+
* single list sorted newest-first by `published_at`. Cards without a
|
|
119
|
+
* `published_at` sort to the end.
|
|
120
|
+
*/
|
|
121
|
+
declare function mergeBlogs(chaingrow: BlogCard[], extra?: BlogCard[]): BlogCard[];
|
|
122
|
+
|
|
123
|
+
export { type BlogCard as B, type InterleavedSlot as I, SCHEMA_ORG_CONTEXT as S, type ToBlogCardOptions as T, type BlogJsonLdOptions as a, buildBlogJsonLd as b, buildBlogPostingSchema as c, buildFAQPageSchema as d, distributeMediaPositions as e, interleaveMedia as i, mergeBlogs as m, toBlogCard as t };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/blog-cards.ts
|
|
2
|
+
var normalizePathPrefix = (s) => {
|
|
3
|
+
let p = s.trim();
|
|
4
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
5
|
+
while (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
|
6
|
+
return p;
|
|
7
|
+
};
|
|
8
|
+
function toBlogCard(blog, opts = {}) {
|
|
9
|
+
const prefix = normalizePathPrefix(opts.pathPrefix ?? "/blog");
|
|
10
|
+
return {
|
|
11
|
+
id: blog.id,
|
|
12
|
+
href: opts.hrefFor ? opts.hrefFor(blog) : `${prefix}/${blog.slug}`,
|
|
13
|
+
title: blog.title,
|
|
14
|
+
description: blog.description,
|
|
15
|
+
og_image: blog.og_image,
|
|
16
|
+
published_at: blog.published_at,
|
|
17
|
+
language: blog.language
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function mergeBlogs(chaingrow, extra = []) {
|
|
21
|
+
const combined = [...chaingrow, ...extra];
|
|
22
|
+
combined.sort((a, b) => {
|
|
23
|
+
const ta = a.published_at ? Date.parse(a.published_at) : 0;
|
|
24
|
+
const tb = b.published_at ? Date.parse(b.published_at) : 0;
|
|
25
|
+
return tb - ta;
|
|
26
|
+
});
|
|
27
|
+
return combined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
toBlogCard,
|
|
32
|
+
mergeBlogs
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=chunk-4EKET2DQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/blog-cards.ts"],"sourcesContent":["import type { Blog } from './types';\n\nexport type BlogCard = {\n id: string;\n href: string;\n title: string;\n description: string | null;\n og_image: string | null;\n published_at: string | null;\n language?: string;\n};\n\nexport type ToBlogCardOptions = {\n /** URL prefix for the generated `href`. Defaults to `/blog`. */\n pathPrefix?: string;\n /**\n * Full control over the generated `href` — takes precedence over\n * `pathPrefix`. Use this when the route depends on more than a static\n * prefix, e.g. locale-prefixed routes:\n *\n * ```ts\n * hrefFor: (blog) =>\n * blog.language === 'de' ? `/blog/${blog.slug}` : `/${blog.language}/blog/${blog.slug}`\n * ```\n */\n hrefFor?: (blog: Blog) => string;\n};\n\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Project a chaingrow `Blog` into the small `BlogCard` shape used by\n * `<BlogIndex>` and `mergeBlogs`. Hybrid integrators can map their own\n * legacy posts into the same shape (`extra` prop on `<BlogIndex>`) and\n * the two sources render through the same Card slot.\n */\nexport function toBlogCard(blog: Blog, opts: ToBlogCardOptions = {}): BlogCard {\n const prefix = normalizePathPrefix(opts.pathPrefix ?? '/blog');\n return {\n id: blog.id,\n href: opts.hrefFor ? opts.hrefFor(blog) : `${prefix}/${blog.slug}`,\n title: blog.title,\n description: blog.description,\n og_image: blog.og_image,\n published_at: blog.published_at,\n language: blog.language,\n };\n}\n\n/**\n * Merge a list of chaingrow cards with extra (legacy) cards and return a\n * single list sorted newest-first by `published_at`. Cards without a\n * `published_at` sort to the end.\n */\nexport function mergeBlogs(\n chaingrow: BlogCard[],\n extra: BlogCard[] = [],\n): BlogCard[] {\n const combined = [...chaingrow, ...extra];\n combined.sort((a, b) => {\n const ta = a.published_at ? Date.parse(a.published_at) : 0;\n const tb = b.published_at ? Date.parse(b.published_at) : 0;\n return tb - ta;\n });\n return combined;\n}\n"],"mappings":";AA4BA,IAAM,sBAAsB,CAAC,MAAsB;AACjD,MAAI,IAAI,EAAE,KAAK;AACf,MAAI,CAAC,EAAE,WAAW,GAAG,EAAG,KAAI,MAAM;AAClC,SAAO,EAAE,SAAS,KAAK,EAAE,SAAS,GAAG,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AACzD,SAAO;AACT;AAQO,SAAS,WAAW,MAAY,OAA0B,CAAC,GAAa;AAC7E,QAAM,SAAS,oBAAoB,KAAK,cAAc,OAAO;AAC7D,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,MAAM,KAAK,UAAU,KAAK,QAAQ,IAAI,IAAI,GAAG,MAAM,IAAI,KAAK,IAAI;AAAA,IAChE,OAAO,KAAK;AAAA,IACZ,aAAa,KAAK;AAAA,IAClB,UAAU,KAAK;AAAA,IACf,cAAc,KAAK;AAAA,IACnB,UAAU,KAAK;AAAA,EACjB;AACF;AAOO,SAAS,WACd,WACA,QAAoB,CAAC,GACT;AACZ,QAAM,WAAW,CAAC,GAAG,WAAW,GAAG,KAAK;AACxC,WAAS,KAAK,CAAC,GAAG,MAAM;AACtB,UAAM,KAAK,EAAE,eAAe,KAAK,MAAM,EAAE,YAAY,IAAI;AACzD,UAAM,KAAK,EAAE,eAAe,KAAK,MAAM,EAAE,YAAY,IAAI;AACzD,WAAO,KAAK;AAAA,EACd,CAAC;AACD,SAAO;AACT;","names":[]}
|