astro-awesomeness 0.7.3 → 0.8.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/{index-BPru3twJ.d.ts → index-BuHeGVBM.d.ts} +7 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.js +2 -2
- package/dist/{lib-WSV3WGQr.js → lib-8VQ_znvk.js} +10 -3
- package/dist/lib-8VQ_znvk.js.map +1 -0
- package/package.json +1 -1
- package/src/astro/tag-list.astro +3 -3
- package/src/lib/index.ts +1 -0
- package/src/lib/tag-url.test.ts +23 -0
- package/src/lib/tag-url.ts +11 -0
- package/dist/lib-WSV3WGQr.js.map +0 -1
|
@@ -39,9 +39,13 @@ declare const readingTime: (input: string) => {
|
|
|
39
39
|
//#endregion
|
|
40
40
|
//#region src/lib/slugify.d.ts
|
|
41
41
|
declare const slugify: (input: string) => string;
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/lib/tag-url.d.ts
|
|
44
|
+
declare const DEFAULT_TAG_BASE = "/tag";
|
|
45
|
+
declare const tagUrl: (tag: string, base?: string) => string;
|
|
42
46
|
declare namespace index_d_exports {
|
|
43
|
-
export { PostLike, PostParams, PostUrlBuilder, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, slugify };
|
|
47
|
+
export { DEFAULT_TAG_BASE, PostLike, PostParams, PostUrlBuilder, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, slugify, tagUrl };
|
|
44
48
|
}
|
|
45
49
|
//#endregion
|
|
46
|
-
export {
|
|
47
|
-
//# sourceMappingURL=index-
|
|
50
|
+
export { readingTime as a, PostUrlBuilder as c, formatDate as d, cn as f, slugify as i, createPostUrl as l, DEFAULT_TAG_BASE as n, PostLike as o, tagUrl as r, PostParams as s, index_d_exports as t, getRelatedPosts as u };
|
|
51
|
+
//# sourceMappingURL=index-BuHeGVBM.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { Author, Post, Tag, t as index_d_exports } from "./content/index.js";
|
|
2
|
-
import { t as index_d_exports$1 } from "./index-
|
|
2
|
+
import { t as index_d_exports$1 } from "./index-BuHeGVBM.js";
|
|
3
3
|
export { type Author, type Post, type Tag, index_d_exports as content, index_d_exports$1 as lib };
|
package/dist/index.js
CHANGED
package/dist/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { type PostLike, type PostParams, type PostUrlBuilder, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, slugify };
|
|
1
|
+
import { a as readingTime, c as PostUrlBuilder, d as formatDate, f as cn, i as slugify, l as createPostUrl, n as DEFAULT_TAG_BASE, o as PostLike, r as tagUrl, s as PostParams, u as getRelatedPosts } from "../index-BuHeGVBM.js";
|
|
2
|
+
export { DEFAULT_TAG_BASE, type PostLike, type PostParams, type PostUrlBuilder, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, slugify, tagUrl };
|
package/dist/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { t as cn } from "../cn-DmzdyV07.js";
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as createPostUrl, c as formatDate, i as readingTime, n as DEFAULT_TAG_BASE, o as slugify, r as tagUrl, s as getRelatedPosts } from "../lib-8VQ_znvk.js";
|
|
3
3
|
|
|
4
|
-
export { cn, createPostUrl, formatDate, getRelatedPosts, readingTime, slugify };
|
|
4
|
+
export { DEFAULT_TAG_BASE, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, slugify, tagUrl };
|
|
@@ -89,17 +89,24 @@ const readingTime = (input) => {
|
|
|
89
89
|
};
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/lib/tag-url.ts
|
|
94
|
+
const DEFAULT_TAG_BASE = "/tag";
|
|
95
|
+
const tagUrl = (tag, base = DEFAULT_TAG_BASE) => `${base.replace(/* @__PURE__ */ new RegExp("\\/+$", "v"), "")}/${slugify(tag)}/`;
|
|
96
|
+
|
|
92
97
|
//#endregion
|
|
93
98
|
//#region src/lib/index.ts
|
|
94
99
|
var lib_exports = /* @__PURE__ */ __exportAll({
|
|
100
|
+
DEFAULT_TAG_BASE: () => DEFAULT_TAG_BASE,
|
|
95
101
|
cn: () => cn,
|
|
96
102
|
createPostUrl: () => createPostUrl,
|
|
97
103
|
formatDate: () => formatDate,
|
|
98
104
|
getRelatedPosts: () => getRelatedPosts,
|
|
99
105
|
readingTime: () => readingTime,
|
|
100
|
-
slugify: () => slugify
|
|
106
|
+
slugify: () => slugify,
|
|
107
|
+
tagUrl: () => tagUrl
|
|
101
108
|
});
|
|
102
109
|
|
|
103
110
|
//#endregion
|
|
104
|
-
export {
|
|
105
|
-
//# sourceMappingURL=lib-
|
|
111
|
+
export { createPostUrl as a, formatDate as c, readingTime as i, DEFAULT_TAG_BASE as n, slugify as o, tagUrl as r, getRelatedPosts as s, lib_exports as t };
|
|
112
|
+
//# sourceMappingURL=lib-8VQ_znvk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib-8VQ_znvk.js","names":[],"sources":["../src/lib/format-date.ts","../src/lib/get-related-posts.ts","../src/lib/slugify.ts","../src/lib/post-url.ts","../src/lib/reading-time.ts","../src/lib/tag-url.ts","../src/lib/index.ts"],"sourcesContent":["const defaultOptions: Intl.DateTimeFormatOptions = {\n day: \"numeric\",\n month: \"long\",\n timeZone: \"UTC\",\n year: \"numeric\",\n};\n\nconst formatterCache = new Map<string, Intl.DateTimeFormat>();\n\nconst getFormatter = (key: string, locale: string, options: Intl.DateTimeFormatOptions) => {\n let formatter = formatterCache.get(key);\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(locale, options);\n formatterCache.set(key, formatter);\n }\n return formatter;\n};\n\nconst formatDate = (date: Date, locale: string = \"en-US\", options?: Intl.DateTimeFormatOptions) => {\n // Default-option calls (every blog passes only (date, locale)) key on locale\n // alone, skipping the JSON.stringify the custom-option path needs.\n if (!options) {\n return getFormatter(locale, locale, defaultOptions).format(date);\n }\n const merged = { ...defaultOptions, ...options };\n return getFormatter(`${locale}:${JSON.stringify(merged)}`, locale, merged).format(date);\n};\n\nexport { formatDate };\n","type HasTags = { data: { tags: Array<string> }; id: string };\n\nconst getRelatedPosts = <T extends HasTags>(current: T, all: Array<T>, n: number) => {\n const currentTagSet = new Set(current.data.tags);\n const scored: Array<{ post: T; score: number }> = [];\n\n for (const post of all) {\n if (post.id === current.id) {\n continue;\n }\n let score = 0;\n for (const tag of post.data.tags) {\n if (currentTagSet.has(tag)) {\n score++;\n }\n }\n scored.push({ post, score });\n }\n\n return scored\n .toSorted((x, y) => y.score - x.score)\n .slice(0, n)\n .map(({ post }) => post);\n};\n\nexport { getRelatedPosts };\n","const slugify = (input: string) =>\n input\n .normalize(\"NFD\")\n .replaceAll(/[̀-ͯ]/gv, \"\")\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s\\-]/gv, \"\")\n .trim()\n .replaceAll(/[\\s\\-]+/gv, \"-\")\n .replaceAll(/^-+|-+$/gv, \"\");\n\nexport { slugify };\n","import { slugify } from \"./slugify\";\n\ntype PostLike = {\n data: {\n categories?: Array<string>;\n seo?: { canonical_url?: string };\n };\n id: string;\n};\n\ntype PostParams = { category: string; slug: string };\n\ntype PostUrlBuilder = {\n postParams: (post: PostLike) => PostParams;\n postUrl: (post: PostLike) => string;\n};\n\n// Per-blog URL builder. Each blog supplies its own pt-BR -> slug map\n// (`category-slugs.json`) and the catch-all category used when the post has\n// no categories. The canonical_url short-circuit lets WordPress migrations\n// keep their original URL pair while everything else falls back to a slugified\n// derived category + Astro entry id.\nconst createPostUrl = (\n categorySlugMap: Record<string, string>,\n defaultCategory: string,\n): PostUrlBuilder => {\n const postParams = (post: PostLike): PostParams => {\n const canonical = post.data.seo?.canonical_url;\n if (canonical !== undefined && canonical !== \"\") {\n try {\n const segments = new URL(canonical).pathname.split(\"/\").filter(Boolean);\n const category = segments.at(-2);\n const slug = segments.at(-1);\n if (category !== undefined && slug !== undefined) {\n return { category, slug };\n }\n } catch {\n // Malformed canonical_url; fall through to category-derived logic.\n }\n }\n const first = post.data.categories?.at(0);\n const category =\n first !== undefined && first !== \"\"\n ? (categorySlugMap[first] ?? slugify(first))\n : defaultCategory;\n return { category, slug: post.id };\n };\n\n const postUrl = (post: PostLike): string => {\n const { category, slug } = postParams(post);\n return `/${category}/${slug}/`;\n };\n\n return { postParams, postUrl };\n};\n\nexport type { PostLike, PostParams, PostUrlBuilder };\nexport { createPostUrl };\n","const WORDS_PER_MINUTE = 200;\n\nconst readingTime = (input: string) => {\n const stripped = input\n .replaceAll(/<[^>]*>/gv, \"\")\n .replaceAll(/[#*_`>\\-]/gv, \" \")\n .replaceAll(/\\s+/gv, \" \")\n .trim();\n const words = stripped.length === 0 ? 0 : stripped.split(\" \").length;\n const minutes = Math.max(1, Math.ceil(words / WORDS_PER_MINUTE));\n return { minutes, words };\n};\n\nexport { readingTime };\n","import { slugify } from \"./slugify\";\n\nconst DEFAULT_TAG_BASE = \"/tag\";\n\n// The trailing slash is not cosmetic. @astrojs/sitemap emits directory-style\n// URLs, so a chip pointing at /tag/foo names a URL that is absent from the\n// sitemap and only reachable through a redirect.\nconst tagUrl = (tag: string, base: string = DEFAULT_TAG_BASE): string =>\n `${base.replace(/\\/+$/v, \"\")}/${slugify(tag)}/`;\n\nexport { DEFAULT_TAG_BASE, tagUrl };\n","export { cn } from \"./cn\";\nexport { formatDate } from \"./format-date\";\nexport { getRelatedPosts } from \"./get-related-posts\";\nexport type { PostLike, PostParams, PostUrlBuilder } from \"./post-url\";\nexport { createPostUrl } from \"./post-url\";\nexport { readingTime } from \"./reading-time\";\nexport { slugify } from \"./slugify\";\nexport { DEFAULT_TAG_BASE, tagUrl } from \"./tag-url\";\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/astro/tag-list.astro
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_TAG_BASE, tagUrl } from "../lib/tag-url";
|
|
3
3
|
|
|
4
4
|
type Props = {
|
|
5
5
|
tags: Array<string>;
|
|
@@ -7,13 +7,13 @@ type Props = {
|
|
|
7
7
|
class?: string;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
const { tags, base =
|
|
10
|
+
const { tags, base = DEFAULT_TAG_BASE, class: className = "" } = Astro.props;
|
|
11
11
|
---
|
|
12
12
|
<ul class:list={["flex flex-wrap gap-2", className]}>
|
|
13
13
|
{tags.map((tag) => (
|
|
14
14
|
<li>
|
|
15
15
|
<a
|
|
16
|
-
href={
|
|
16
|
+
href={tagUrl(tag, base)}
|
|
17
17
|
class="border-border bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground inline-flex items-center rounded-md border px-2 py-0.5 text-xs transition-colors"
|
|
18
18
|
>
|
|
19
19
|
{tag}
|
package/src/lib/index.ts
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_TAG_BASE, tagUrl } from "./tag-url";
|
|
4
|
+
|
|
5
|
+
describe("tagUrl", () => {
|
|
6
|
+
it("defaults to the /tag base every consuming blog routes on", () => {
|
|
7
|
+
expect(DEFAULT_TAG_BASE).toBe("/tag");
|
|
8
|
+
expect(tagUrl("Arquitetura")).toBe("/tag/arquitetura/");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("ends every url with a slash so it matches the sitemap entry", () => {
|
|
12
|
+
expect(tagUrl("design")).toMatch(/\/$/v);
|
|
13
|
+
expect(tagUrl("design", "/etiqueta")).toBe("/etiqueta/design/");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("does not double the separator when the base already ends in a slash", () => {
|
|
17
|
+
expect(tagUrl("design", "/tag/")).toBe("/tag/design/");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("slugifies accents and spaces the same way the route params do", () => {
|
|
21
|
+
expect(tagUrl("Área Externa")).toBe("/tag/area-externa/");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { slugify } from "./slugify";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_TAG_BASE = "/tag";
|
|
4
|
+
|
|
5
|
+
// The trailing slash is not cosmetic. @astrojs/sitemap emits directory-style
|
|
6
|
+
// URLs, so a chip pointing at /tag/foo names a URL that is absent from the
|
|
7
|
+
// sitemap and only reachable through a redirect.
|
|
8
|
+
const tagUrl = (tag: string, base: string = DEFAULT_TAG_BASE): string =>
|
|
9
|
+
`${base.replace(/\/+$/v, "")}/${slugify(tag)}/`;
|
|
10
|
+
|
|
11
|
+
export { DEFAULT_TAG_BASE, tagUrl };
|
package/dist/lib-WSV3WGQr.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lib-WSV3WGQr.js","names":[],"sources":["../src/lib/format-date.ts","../src/lib/get-related-posts.ts","../src/lib/slugify.ts","../src/lib/post-url.ts","../src/lib/reading-time.ts","../src/lib/index.ts"],"sourcesContent":["const defaultOptions: Intl.DateTimeFormatOptions = {\n day: \"numeric\",\n month: \"long\",\n timeZone: \"UTC\",\n year: \"numeric\",\n};\n\nconst formatterCache = new Map<string, Intl.DateTimeFormat>();\n\nconst getFormatter = (key: string, locale: string, options: Intl.DateTimeFormatOptions) => {\n let formatter = formatterCache.get(key);\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(locale, options);\n formatterCache.set(key, formatter);\n }\n return formatter;\n};\n\nconst formatDate = (date: Date, locale: string = \"en-US\", options?: Intl.DateTimeFormatOptions) => {\n // Default-option calls (every blog passes only (date, locale)) key on locale\n // alone, skipping the JSON.stringify the custom-option path needs.\n if (!options) {\n return getFormatter(locale, locale, defaultOptions).format(date);\n }\n const merged = { ...defaultOptions, ...options };\n return getFormatter(`${locale}:${JSON.stringify(merged)}`, locale, merged).format(date);\n};\n\nexport { formatDate };\n","type HasTags = { data: { tags: Array<string> }; id: string };\n\nconst getRelatedPosts = <T extends HasTags>(current: T, all: Array<T>, n: number) => {\n const currentTagSet = new Set(current.data.tags);\n const scored: Array<{ post: T; score: number }> = [];\n\n for (const post of all) {\n if (post.id === current.id) {\n continue;\n }\n let score = 0;\n for (const tag of post.data.tags) {\n if (currentTagSet.has(tag)) {\n score++;\n }\n }\n scored.push({ post, score });\n }\n\n return scored\n .toSorted((x, y) => y.score - x.score)\n .slice(0, n)\n .map(({ post }) => post);\n};\n\nexport { getRelatedPosts };\n","const slugify = (input: string) =>\n input\n .normalize(\"NFD\")\n .replaceAll(/[̀-ͯ]/gv, \"\")\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s\\-]/gv, \"\")\n .trim()\n .replaceAll(/[\\s\\-]+/gv, \"-\")\n .replaceAll(/^-+|-+$/gv, \"\");\n\nexport { slugify };\n","import { slugify } from \"./slugify\";\n\ntype PostLike = {\n data: {\n categories?: Array<string>;\n seo?: { canonical_url?: string };\n };\n id: string;\n};\n\ntype PostParams = { category: string; slug: string };\n\ntype PostUrlBuilder = {\n postParams: (post: PostLike) => PostParams;\n postUrl: (post: PostLike) => string;\n};\n\n// Per-blog URL builder. Each blog supplies its own pt-BR -> slug map\n// (`category-slugs.json`) and the catch-all category used when the post has\n// no categories. The canonical_url short-circuit lets WordPress migrations\n// keep their original URL pair while everything else falls back to a slugified\n// derived category + Astro entry id.\nconst createPostUrl = (\n categorySlugMap: Record<string, string>,\n defaultCategory: string,\n): PostUrlBuilder => {\n const postParams = (post: PostLike): PostParams => {\n const canonical = post.data.seo?.canonical_url;\n if (canonical !== undefined && canonical !== \"\") {\n try {\n const segments = new URL(canonical).pathname.split(\"/\").filter(Boolean);\n const category = segments.at(-2);\n const slug = segments.at(-1);\n if (category !== undefined && slug !== undefined) {\n return { category, slug };\n }\n } catch {\n // Malformed canonical_url; fall through to category-derived logic.\n }\n }\n const first = post.data.categories?.at(0);\n const category =\n first !== undefined && first !== \"\"\n ? (categorySlugMap[first] ?? slugify(first))\n : defaultCategory;\n return { category, slug: post.id };\n };\n\n const postUrl = (post: PostLike): string => {\n const { category, slug } = postParams(post);\n return `/${category}/${slug}/`;\n };\n\n return { postParams, postUrl };\n};\n\nexport type { PostLike, PostParams, PostUrlBuilder };\nexport { createPostUrl };\n","const WORDS_PER_MINUTE = 200;\n\nconst readingTime = (input: string) => {\n const stripped = input\n .replaceAll(/<[^>]*>/gv, \"\")\n .replaceAll(/[#*_`>\\-]/gv, \" \")\n .replaceAll(/\\s+/gv, \" \")\n .trim();\n const words = stripped.length === 0 ? 0 : stripped.split(\" \").length;\n const minutes = Math.max(1, Math.ceil(words / WORDS_PER_MINUTE));\n return { minutes, words };\n};\n\nexport { readingTime };\n","export { cn } from \"./cn\";\nexport { formatDate } from \"./format-date\";\nexport { getRelatedPosts } from \"./get-related-posts\";\nexport type { PostLike, PostParams, PostUrlBuilder } from \"./post-url\";\nexport { createPostUrl } from \"./post-url\";\nexport { readingTime } from \"./reading-time\";\nexport { slugify } from \"./slugify\";\n"],"mappings":";;;;AAAA,MAAM,iBAA6C;CACjD,KAAK;CACL,OAAO;CACP,UAAU;CACV,MAAM;AACR;AAEA,MAAM,iCAAiB,IAAI,IAAiC;AAE5D,MAAM,gBAAgB,KAAa,QAAgB,YAAwC;CACzF,IAAI,YAAY,eAAe,IAAI,GAAG;CACtC,IAAI,CAAC,WAAW;EACd,YAAY,IAAI,KAAK,eAAe,QAAQ,OAAO;EACnD,eAAe,IAAI,KAAK,SAAS;CACnC;CACA,OAAO;AACT;AAEA,MAAM,cAAc,MAAY,SAAiB,SAAS,YAAyC;CAGjG,IAAI,CAAC,SACH,OAAO,aAAa,QAAQ,QAAQ,cAAc,CAAC,CAAC,OAAO,IAAI;CAEjE,MAAM,SAAS;EAAE,GAAG;EAAgB,GAAG;CAAQ;CAC/C,OAAO,aAAa,GAAG,OAAO,GAAG,KAAK,UAAU,MAAM,KAAK,QAAQ,MAAM,CAAC,CAAC,OAAO,IAAI;AACxF;;;;ACxBA,MAAM,mBAAsC,SAAY,KAAe,MAAc;CACnF,MAAM,gBAAgB,IAAI,IAAI,QAAQ,KAAK,IAAI;CAC/C,MAAM,SAA4C,CAAC;CAEnD,KAAK,MAAM,QAAQ,KAAK;EACtB,IAAI,KAAK,OAAO,QAAQ,IACtB;EAEF,IAAI,QAAQ;EACZ,KAAK,MAAM,OAAO,KAAK,KAAK,MAC1B,IAAI,cAAc,IAAI,GAAG,GACvB;EAGJ,OAAO,KAAK;GAAE;GAAM;EAAM,CAAC;CAC7B;CAEA,OAAO,OACJ,UAAU,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CACrC,MAAM,GAAG,CAAC,CAAC,CACX,KAAK,EAAE,WAAW,IAAI;AAC3B;;;;ACvBA,MAAM,WAAW,UACf,MACG,UAAU,KAAK,CAAC,CAChB,2BAAW,wBAAQ,GAAG,EAAE,CAAC,CACzB,YAAY,CAAC,CACb,2BAAW,kCAAgB,GAAG,EAAE,CAAC,CACjC,KAAK,CAAC,CACN,2BAAW,4BAAU,GAAG,GAAG,CAAC,CAC5B,2BAAW,0BAAU,GAAG,EAAE;;;;ACc/B,MAAM,iBACJ,iBACA,oBACmB;CACnB,MAAM,cAAc,SAA+B;EACjD,MAAM,YAAY,KAAK,KAAK,KAAK;EACjC,IAAI,cAAc,UAAa,cAAc,IAC3C,IAAI;GACF,MAAM,WAAW,IAAI,IAAI,SAAS,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;GACtE,MAAM,WAAW,SAAS,GAAG,EAAE;GAC/B,MAAM,OAAO,SAAS,GAAG,EAAE;GAC3B,IAAI,aAAa,UAAa,SAAS,QACrC,OAAO;IAAE;IAAU;GAAK;EAE5B,QAAQ,CAER;EAEF,MAAM,QAAQ,KAAK,KAAK,YAAY,GAAG,CAAC;EAKxC,OAAO;GAAE,UAHP,UAAU,UAAa,UAAU,KAC5B,gBAAgB,UAAU,QAAQ,KAAK,IACxC;GACa,MAAM,KAAK;EAAG;CACnC;CAEA,MAAM,WAAW,SAA2B;EAC1C,MAAM,EAAE,UAAU,SAAS,WAAW,IAAI;EAC1C,OAAO,IAAI,SAAS,GAAG,KAAK;CAC9B;CAEA,OAAO;EAAE;EAAY;CAAQ;AAC/B;;;;ACtDA,MAAM,mBAAmB;AAEzB,MAAM,eAAe,UAAkB;CACrC,MAAM,WAAW,MACd,2BAAW,0BAAU,GAAG,EAAE,CAAC,CAC3B,2BAAW,6BAAY,GAAG,GAAG,CAAC,CAC9B,2BAAW,uBAAM,GAAG,GAAG,CAAC,CACxB,KAAK;CACR,MAAM,QAAQ,SAAS,WAAW,IAAI,IAAI,SAAS,MAAM,GAAG,CAAC,CAAC;CAE9D,OAAO;EAAE,SADO,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,gBAAgB,CAC/C;EAAG;CAAM;AAC1B"}
|