@meowdown/embed 0.3.1 → 0.3.3

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.
@@ -1,5 +1,4 @@
1
1
  import { HostElement, State } from "@aria-ui/core";
2
- //#region src/fetch.d.ts
3
2
  /**
4
3
  * Loads the snapshot for `url`. Return `undefined` when nothing was found.
5
4
  */
@@ -18,5 +17,4 @@ interface FetchProps<T> {
18
17
  */
19
18
  resolver: Resolver<T> | null;
20
19
  }
21
- //#endregion
22
20
  export { Resolver as n, FetchProps as t };
@@ -1,7 +1,7 @@
1
1
  import { createSignal, useEffect } from "@aria-ui/core";
2
2
  import el from "crelt";
3
3
 
4
- //#region src/fetch.ts
4
+ // src/fetch.ts
5
5
  /**
6
6
  * Runs `resolver(url)` whenever `data` is `null` and both `url` and `resolver`
7
7
  * are set, ignoring results that arrive after the inputs changed or the host
@@ -10,6 +10,7 @@ import el from "crelt";
10
10
  * @internal
11
11
  */
12
12
  function useFetch(host, props, label) {
13
+ "use no memo";
13
14
  const fetched = createSignal(null);
14
15
  const pending = createSignal(false);
15
16
  useEffect(host, () => {
@@ -53,8 +54,7 @@ function isPromiseLike(value) {
53
54
  return typeof value === "object" && value !== null && typeof value.then === "function";
54
55
  }
55
56
 
56
- //#endregion
57
- //#region src/safe-url.ts
57
+ // src/safe-url.ts
58
58
  function getSafeUrl(value, protocols) {
59
59
  if (Array.from(value).some((character) => {
60
60
  return character.charCodeAt(0) <= 32 || character.charCodeAt(0) === 127;
@@ -68,8 +68,7 @@ function getSafeUrl(value, protocols) {
68
68
  }
69
69
  }
70
70
 
71
- //#endregion
72
- //#region src/render-link.ts
71
+ // src/render-link.ts
73
72
  function renderLink(content, destination) {
74
73
  const href = destination && getSafeUrl(destination);
75
74
  return href ? el("a", {
@@ -79,8 +78,7 @@ function renderLink(content, destination) {
79
78
  }, content) : content;
80
79
  }
81
80
 
82
- //#endregion
83
- //#region src/root.ts
81
+ // src/root.ts
84
82
  /**
85
83
  * The direct `<div data-root>` child that holds the rendered snapshot. Reused
86
84
  * across updates so other children the host appended survive; created on
@@ -96,11 +94,9 @@ function getRootContainer(host) {
96
94
  return container;
97
95
  }
98
96
 
99
- //#endregion
100
- //#region src/is-plain-click.ts
97
+ // src/is-plain-click.ts
101
98
  function isPlainClick(event) {
102
99
  return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
103
100
  }
104
101
 
105
- //#endregion
106
102
  export { useFetch as a, getSafeUrl as i, getRootContainer as n, renderLink as r, isPlainClick as t };
package/dist/x/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { n as Resolver, t as FetchProps } from "../fetch-Djnul03u.js";
1
+ import { n as Resolver, t as FetchProps } from "../fetch-gxdhOZP6.js";
2
2
  import { HostElement, State } from "@aria-ui/core";
3
3
  import { XPost, XPostMedia } from "@post-embed/types";
4
- //#region src/x/media-click.d.ts
5
4
  export declare const X_POST_MEDIA_CLICK = "meowdown-embed-media-click";
6
5
  interface XPostMediaClickDetail {
7
6
  /**
@@ -23,19 +22,13 @@ interface XPostMediaClickDetail {
23
22
  element: HTMLElement;
24
23
  }
25
24
  type XPostMediaClickEvent = CustomEvent<XPostMediaClickDetail>;
26
- //#endregion
27
- //#region src/x/x-post.d.ts
28
25
  interface XPostProps extends FetchProps<XPost> {
29
26
  mediaUrlProtocols: readonly string[] | null;
30
27
  }
31
28
  interface XPostElement extends HTMLElement, XPostProps {}
32
29
  /** @internal */
33
- export declare function useXPost(host: HostElement, props: State<XPostProps>): void;
34
- //#endregion
35
- //#region src/x/register.d.ts
30
+ export declare function setupXPost(host: HostElement, props: State<XPostProps>): void;
36
31
  export declare function registerXPost(name?: string): void;
37
- //#endregion
38
- //#region src/x/index.d.ts
39
32
  declare global {
40
33
  interface HTMLElementTagNameMap {
41
34
  'meowdown-embed-x': XPostElement;
@@ -44,5 +37,4 @@ declare global {
44
37
  'meowdown-embed-media-click': XPostMediaClickEvent;
45
38
  }
46
39
  }
47
- //#endregion
48
- export type { Resolver, XPostElement, XPostMediaClickDetail, XPostMediaClickEvent, XPostProps };
40
+ export { type Resolver, type XPostElement, type XPostMediaClickDetail, type XPostMediaClickEvent, type XPostProps, setupXPost as useXPost };
package/dist/x/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { a as useFetch, i as getSafeUrl, n as getRootContainer, r as renderLink, t as isPlainClick } from "../is-plain-click-QDpLdFP7.js";
1
+ import { a as useFetch, i as getSafeUrl, n as getRootContainer, r as renderLink, t as isPlainClick } from "../is-plain-click-Dbydr3nX.js";
2
2
  import { defineCustomElement, defineProps, registerCustomElement, useEffect } from "@aria-ui/core";
3
3
  import { parseXPost, parseXPostId } from "@post-embed/schema";
4
4
  import el from "crelt";
5
5
 
6
- //#region src/x/media-click.ts
6
+ // src/x/media-click.ts
7
7
  const X_POST_MEDIA_CLICK = "meowdown-embed-media-click";
8
8
  /**
9
9
  * Returns false when a listener called `preventDefault()`: the host shows the
@@ -18,8 +18,7 @@ function dispatchMediaClick(target, detail) {
18
18
  }));
19
19
  }
20
20
 
21
- //#endregion
22
- //#region src/x/render-author.ts
21
+ // src/x/render-author.ts
23
22
  function renderAuthor(author, protocols, date) {
24
23
  if (!author.name && !author.handle) return;
25
24
  const validHandle = /^\w{1,15}$/.test(author.handle);
@@ -37,8 +36,15 @@ function renderAuthor(author, protocols, date) {
37
36
  }) : void 0, el("div", { "data-author-details": "" }, el("div", { "data-author-name": "" }, el("bdi", {}, author.name || author.handle)), author.handle ? el("bdi", {}, renderLink(`@${author.handle}`, validHandle ? `https://x.com/${author.handle}` : void 0)) : void 0), date ? el("span", { "data-date": "" }, date) : void 0);
38
37
  }
39
38
 
40
- //#endregion
41
- //#region src/to-positive-number.ts
39
+ // ../../node_modules/.pnpm/@ocavue+utils@1.11.0/node_modules/@ocavue/utils/dist/index.js
40
+ /**
41
+ * Returns true if the given value is not null or undefined.
42
+ */
43
+ function isNotNullish(value) {
44
+ return value != null;
45
+ }
46
+
47
+ // src/to-positive-number.ts
42
48
  /**
43
49
  * The value rounded to an integer, or nothing unless it is a finite number
44
50
  * above zero.
@@ -47,8 +53,7 @@ function toPositiveNumber(value) {
47
53
  if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.round(value);
48
54
  }
49
55
 
50
- //#endregion
51
- //#region src/x/sort-video-sources.ts
56
+ // src/x/sort-video-sources.ts
52
57
  /**
53
58
  * Orders the sources the way a `<video>` should try them: it plays the first
54
59
  * `<source>` it supports, so MP4 files come before HLS playlists (which only
@@ -61,8 +66,7 @@ function sortVideoSources(sources) {
61
66
  });
62
67
  }
63
68
 
64
- //#endregion
65
- //#region src/x/render-media.ts
69
+ // src/x/render-media.ts
66
70
  function getSizeAttrs(media) {
67
71
  return {
68
72
  width: toPositiveNumber(media.width),
@@ -97,13 +101,13 @@ function getDisplayable(media, protocols) {
97
101
  url
98
102
  } : void 0;
99
103
  }
100
- const sources = sortVideoSources(media.sources.flatMap((source) => {
104
+ const sources = sortVideoSources(media.sources.map((source) => {
101
105
  const url = getSafeMediaUrl(source.url, protocols);
102
- return url ? [{
106
+ return url ? {
103
107
  ...source,
104
108
  url
105
- }] : [];
106
- }));
109
+ } : null;
110
+ }).filter(isNotNullish));
107
111
  if (sources.length === 0) return;
108
112
  const poster = media.poster && getSafeMediaUrl(media.poster, protocols);
109
113
  return {
@@ -231,8 +235,7 @@ function renderMedia(media, protocols, permalink) {
231
235
  }));
232
236
  }
233
237
 
234
- //#endregion
235
- //#region src/x/render-shared.ts
238
+ // src/x/render-shared.ts
236
239
  function getPermalink(post) {
237
240
  return /^\w{1,15}$/.test(post.author.handle) && /^\d+$/.test(post.id) ? `https://x.com/${post.author.handle}/status/${post.id}` : void 0;
238
241
  }
@@ -257,8 +260,7 @@ function renderDate(post) {
257
260
  }).format(date)), getPermalink(post));
258
261
  }
259
262
 
260
- //#endregion
261
- //#region src/x/render-post.ts
263
+ // src/x/render-post.ts
262
264
  function renderBody(post) {
263
265
  const permalink = getPermalink(post);
264
266
  return el("p", {
@@ -287,10 +289,9 @@ function renderPost(post, protocols = null) {
287
289
  return el("article", {}, el("div", { "data-content": "" }, renderAuthor(post.author, protocols, renderDate(post)), reply ? el("div", { "data-reply-to": "" }, renderLink(`Replying to @${reply.handle}`, replyUrl)) : void 0, renderBody(post), renderMedia(post.media, protocols, getPermalink(post)), post.quote ? renderQuoted(post.quote, protocols) : void 0, renderEdit(post)), el("details", { "data-show-more": "" }, el("summary", {})));
288
290
  }
289
291
 
290
- //#endregion
291
- //#region src/x/x-post.ts
292
+ // src/x/x-post.ts
292
293
  /** @internal */
293
- function useXPost(host, props) {
294
+ function setupXPost(host, props) {
294
295
  const { fetched, pending } = useFetch(host, props, "X post");
295
296
  useEffect(host, () => {
296
297
  host.dataset.meowdownEmbed = "x";
@@ -315,7 +316,7 @@ function renderFallback(pending, url) {
315
316
  "data-pending": ""
316
317
  } : { "data-fallback": "" }, el("header", { "data-author": "" }, el("bdi", {}, "X post")), el("p", { "data-body": "" }, pending ? "Loading this post…" : "This post is unavailable."), pending ? null : el("footer", { "data-footer": "" }, permalink ? renderLink("View on X", permalink) : "No saved post could be displayed."));
317
318
  }
318
- const XPost = defineCustomElement(useXPost, defineProps({
319
+ const XPost = defineCustomElement(setupXPost, defineProps({
319
320
  data: {
320
321
  default: null,
321
322
  attribute: false
@@ -334,11 +335,9 @@ const XPost = defineCustomElement(useXPost, defineProps({
334
335
  }
335
336
  }));
336
337
 
337
- //#endregion
338
- //#region src/x/register.ts
338
+ // src/x/register.ts
339
339
  function registerXPost(name = "meowdown-embed-x") {
340
340
  registerCustomElement(name, class extends XPost {});
341
341
  }
342
342
 
343
- //#endregion
344
- export { X_POST_MEDIA_CLICK, registerXPost, useXPost };
343
+ export { X_POST_MEDIA_CLICK, registerXPost, setupXPost, setupXPost as useXPost };
package/dist/x/theme.css CHANGED
@@ -51,6 +51,22 @@
51
51
  }
52
52
  }
53
53
 
54
+ @keyframes meowdown-embed-fade {
55
+ from,
56
+ to {
57
+ mask-image: linear-gradient(to bottom, #000 calc(100% - 3rem), transparent);
58
+ mask-size: 100% calc(var(--_body-limit) - var(--_show-more-height) - 2 * var(--_clip-gutter));
59
+ mask-repeat: no-repeat;
60
+ }
61
+ }
62
+
63
+ @keyframes meowdown-embed-show {
64
+ from,
65
+ to {
66
+ visibility: visible;
67
+ }
68
+ }
69
+
54
70
  /*
55
71
  * Hard reset. `all: revert` rolls every property on every box inside the card
56
72
  * back to the user-agent default, so a host rule on `p`, `a`, `img`,
@@ -216,6 +232,9 @@
216
232
  * when the card is too tall. Then the article gives up one row, which the
217
233
  * "Show more" toggle takes, so the whole card is exactly the limit. The
218
234
  * toggle is anchored to the card's padding box, out of the article's clip.
235
+ * The fade and the toggle run their own animations on the article's named
236
+ * timeline, which update on every scroll in every engine. Firefox applies
237
+ * a `style()` query on an animated value only after a full restyle.
219
238
  * Browsers without scroll-driven animations show the whole card. */
220
239
  & > article {
221
240
  /* Room inside the clip for focus rings at the article's edges. */
@@ -264,6 +283,7 @@
264
283
  margin-block-end: calc(var(--_overflowing) * var(--_show-more-height) - var(--_clip-gutter));
265
284
  animation: meowdown-embed-overflowing linear both;
266
285
  animation-timeline: scroll(self);
286
+ scroll-timeline: --meowdown-embed-clip block;
267
287
  }
268
288
  /* Opened, or holding keyboard focus that the clip could hide. */
269
289
  & > article:has(> [data-show-more][open]),
@@ -272,21 +292,20 @@
272
292
  overflow: visible;
273
293
  margin-block-end: calc(-1 * var(--_clip-gutter));
274
294
  }
275
- @container style(--_overflowing: 1) {
276
- /* On the content, not the article: a mask on the article would also
277
- * hide the toggle below it. */
278
- & > article > [data-content] {
279
- mask-image: linear-gradient(to bottom, #000 calc(100% - 3rem), transparent);
280
- mask-size: 100%
281
- calc(var(--_body-limit) - var(--_show-more-height) - 2 * var(--_clip-gutter));
282
- mask-repeat: no-repeat;
283
- }
284
- & > article > [data-show-more] {
285
- display: block;
286
- position: absolute;
287
- inset-inline-start: var(--meowdown-embed-padding, 1rem);
288
- bottom: var(--meowdown-embed-padding, 1rem);
289
- }
295
+ /* On the content, not the article: a mask on the article would also
296
+ * hide the toggle below it. */
297
+ & > article > [data-content] {
298
+ animation: meowdown-embed-fade linear both;
299
+ animation-timeline: --meowdown-embed-clip;
300
+ }
301
+ & > article > [data-show-more] {
302
+ display: block;
303
+ position: absolute;
304
+ inset-inline-start: var(--meowdown-embed-padding, 1rem);
305
+ bottom: var(--meowdown-embed-padding, 1rem);
306
+ visibility: hidden;
307
+ animation: meowdown-embed-show linear both;
308
+ animation-timeline: --meowdown-embed-clip;
290
309
  }
291
310
  /* Opened, "Show less" sticks to the bottom of the scrollport until the
292
311
  * card's end scrolls into view. */
@@ -294,6 +313,7 @@
294
313
  display: block;
295
314
  position: sticky;
296
315
  bottom: 0.75rem;
316
+ visibility: visible;
297
317
  z-index: 1;
298
318
  width: fit-content;
299
319
  margin: 0.5rem auto 0;
@@ -1,7 +1,6 @@
1
- import { n as Resolver, t as FetchProps } from "../fetch-Djnul03u.js";
1
+ import { n as Resolver, t as FetchProps } from "../fetch-gxdhOZP6.js";
2
2
  import { HostElement, State } from "@aria-ui/core";
3
3
  import { YouTubeVideo } from "@post-embed/types";
4
- //#region src/youtube/video-click.d.ts
5
4
  export declare const YOUTUBE_VIDEO_CLICK = "meowdown-embed-youtube-click";
6
5
  interface YouTubeVideoClickDetail {
7
6
  /**
@@ -28,8 +27,6 @@ interface YouTubeVideoClickDetail {
28
27
  element: HTMLElement;
29
28
  }
30
29
  type YouTubeVideoClickEvent = CustomEvent<YouTubeVideoClickDetail>;
31
- //#endregion
32
- //#region src/youtube/youtube-video.d.ts
33
30
  interface YouTubeVideoProps extends FetchProps<YouTubeVideo> {
34
31
  /**
35
32
  * `link` (default) opens the watch page; `inline` swaps in the YouTube
@@ -39,12 +36,8 @@ interface YouTubeVideoProps extends FetchProps<YouTubeVideo> {
39
36
  }
40
37
  interface YouTubeVideoElement extends HTMLElement, YouTubeVideoProps {}
41
38
  /** @internal */
42
- export declare function useYouTubeVideo(host: HostElement, props: State<YouTubeVideoProps>): void;
43
- //#endregion
44
- //#region src/youtube/register.d.ts
39
+ export declare function setupYouTubeVideo(host: HostElement, props: State<YouTubeVideoProps>): void;
45
40
  export declare function registerYouTubeVideo(name?: string): void;
46
- //#endregion
47
- //#region src/youtube/index.d.ts
48
41
  declare global {
49
42
  interface HTMLElementTagNameMap {
50
43
  'meowdown-embed-youtube': YouTubeVideoElement;
@@ -53,5 +46,4 @@ declare global {
53
46
  'meowdown-embed-youtube-click': YouTubeVideoClickEvent;
54
47
  }
55
48
  }
56
- //#endregion
57
- export type { Resolver, YouTubeVideoClickDetail, YouTubeVideoClickEvent, YouTubeVideoElement, YouTubeVideoProps };
49
+ export { type Resolver, type YouTubeVideoClickDetail, type YouTubeVideoClickEvent, type YouTubeVideoElement, type YouTubeVideoProps, setupYouTubeVideo as useYouTubeVideo };
@@ -1,9 +1,9 @@
1
- import { a as useFetch, i as getSafeUrl, n as getRootContainer, r as renderLink, t as isPlainClick } from "../is-plain-click-QDpLdFP7.js";
1
+ import { a as useFetch, i as getSafeUrl, n as getRootContainer, r as renderLink, t as isPlainClick } from "../is-plain-click-Dbydr3nX.js";
2
2
  import { defineCustomElement, defineProps, registerCustomElement, useEffect } from "@aria-ui/core";
3
3
  import { parseYouTubeVideo } from "@post-embed/schema";
4
4
  import el from "crelt";
5
5
 
6
- //#region src/youtube/parse-url.ts
6
+ // src/youtube/parse-url.ts
7
7
  const YOUTUBE_HOSTS = /^(?:www\.|m\.)?(?:youtube\.com|youtube-nocookie\.com)$/i;
8
8
  const YOUTU_BE_HOST = /^(?:www\.)?youtu\.be$/i;
9
9
  const VIDEO_ID = /^[\w-]{11}$/;
@@ -56,8 +56,7 @@ function getEmbedUrl(ref) {
56
56
  return `https://www.youtube-nocookie.com/embed/${ref.videoId}?${params}`;
57
57
  }
58
58
 
59
- //#endregion
60
- //#region src/youtube/video-click.ts
59
+ // src/youtube/video-click.ts
61
60
  const YOUTUBE_VIDEO_CLICK = "meowdown-embed-youtube-click";
62
61
  /**
63
62
  * Returns false when a listener called `preventDefault()`: the host plays the
@@ -72,8 +71,7 @@ function dispatchVideoClick(target, detail) {
72
71
  }));
73
72
  }
74
73
 
75
- //#endregion
76
- //#region src/youtube/render-video.ts
74
+ // src/youtube/render-video.ts
77
75
  function renderFrame(ref, title) {
78
76
  return el("iframe", {
79
77
  src: getEmbedUrl(ref),
@@ -144,10 +142,9 @@ function renderVideo(video, ref, playback) {
144
142
  }, title), video.author_name ? renderLink(el("span", { "data-author": "" }, video.author_name), video.author_url) : void 0));
145
143
  }
146
144
 
147
- //#endregion
148
- //#region src/youtube/youtube-video.ts
145
+ // src/youtube/youtube-video.ts
149
146
  /** @internal */
150
- function useYouTubeVideo(host, props) {
147
+ function setupYouTubeVideo(host, props) {
151
148
  const { fetched, pending } = useFetch(host, props, "YouTube video");
152
149
  useEffect(host, () => {
153
150
  host.dataset.meowdownEmbed = "youtube";
@@ -166,7 +163,7 @@ function renderFallback(pending) {
166
163
  "data-pending": ""
167
164
  } : { "data-fallback": "" }, el("div", { "data-details": "" }, el("span", { "data-title": "" }, "YouTube video")), el("p", { "data-body": "" }, pending ? "Loading this video…" : "This video is unavailable."), pending ? null : el("footer", { "data-footer": "" }, "No saved video could be displayed."));
168
165
  }
169
- const YouTubeVideoCustomElement = defineCustomElement(useYouTubeVideo, defineProps({
166
+ const YouTubeVideoCustomElement = defineCustomElement(setupYouTubeVideo, defineProps({
170
167
  data: {
171
168
  default: null,
172
169
  attribute: false
@@ -186,11 +183,9 @@ const YouTubeVideoCustomElement = defineCustomElement(useYouTubeVideo, definePro
186
183
  }
187
184
  }));
188
185
 
189
- //#endregion
190
- //#region src/youtube/register.ts
186
+ // src/youtube/register.ts
191
187
  function registerYouTubeVideo(name = "meowdown-embed-youtube") {
192
188
  registerCustomElement(name, class extends YouTubeVideoCustomElement {});
193
189
  }
194
190
 
195
- //#endregion
196
- export { YOUTUBE_VIDEO_CLICK, registerYouTubeVideo, useYouTubeVideo };
191
+ export { YOUTUBE_VIDEO_CLICK, registerYouTubeVideo, setupYouTubeVideo, setupYouTubeVideo as useYouTubeVideo };
@@ -41,7 +41,7 @@
41
41
  );
42
42
  --_muted: var(--meowdown-embed-muted-color, color-mix(in srgb, currentColor 58%, transparent));
43
43
  --_link: var(--meowdown-embed-link-color, #1d9bf0);
44
- --_radius: var(--meowdown-embed-radius, 1rem);
44
+ --_radius: var(--meowdown-embed-radius, 0.75rem);
45
45
  --_page: var(--meowdown-embed-background, Canvas);
46
46
  --_play: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 68 48'%3E%3Cpath d='M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55C3.97 2.33 2.27 4.81 1.48 7.74.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24S67.94 13.05 66.52 7.74z' fill='%23f00'/%3E%3Cpath d='M45 24 27 14v20z' fill='%23fff'/%3E%3C/svg%3E");
47
47
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/embed",
3
3
  "type": "module",
4
- "version": "0.3.1",
4
+ "version": "0.3.3",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -31,10 +31,10 @@
31
31
  "devDependencies": {
32
32
  "@meowdown/vitest": "0.0.0",
33
33
  "@ocavue/tsconfig": "^0.7.1",
34
- "@ocavue/utils": "^1.8.0",
35
- "@vitest/browser-playwright": "^5.0.1",
36
- "tsdown": "^0.23.0",
37
- "vitest": "^5.0.1"
34
+ "@ocavue/utils": "^1.11.0",
35
+ "@vitest/browser-playwright": "^5.0.5",
36
+ "tsdown": "^0.23.3",
37
+ "vitest": "^5.0.5"
38
38
  },
39
39
  "publishConfig": {
40
40
  "access": "public"