@meowdown/embed 0.2.0 → 0.3.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/x/index.js CHANGED
@@ -259,14 +259,14 @@ function renderDate(post) {
259
259
 
260
260
  //#endregion
261
261
  //#region src/x/render-post.ts
262
- function renderBody(post, collapseBlankLines = false) {
262
+ function renderBody(post) {
263
263
  const permalink = getPermalink(post);
264
264
  return el("p", {
265
265
  "data-body": "",
266
266
  dir: "auto",
267
267
  lang: post.lang || void 0
268
268
  }, el("span", { "data-text": "" }, post.body.map((segment) => {
269
- return segment.type === "link" ? renderLink(segment.text, segment.url) : segment.text.split(collapseBlankLines ? /\n+/ : "\n").map((line, index) => {
269
+ return segment.type === "link" ? renderLink(segment.text, segment.url) : segment.text.split("\n").map((line, index) => {
270
270
  return [index ? el("br", {}) : void 0, line];
271
271
  });
272
272
  })), post.truncated && permalink ? [" ", renderLink("Show more", permalink)] : void 0);
@@ -279,12 +279,12 @@ function renderQuoted(post, protocols) {
279
279
  return el("article", {
280
280
  "data-quoted": "",
281
281
  "aria-label": "Quoted post"
282
- }, renderAuthor(post.author, protocols, renderDate(post)), renderBody(post, true), renderMedia(post.media, protocols, getPermalink(post)), renderEdit(post));
282
+ }, renderAuthor(post.author, protocols, renderDate(post)), renderBody(post), renderMedia(post.media, protocols, getPermalink(post)), renderEdit(post));
283
283
  }
284
284
  function renderPost(post, protocols = null) {
285
285
  const reply = post.replyTo;
286
286
  const replyUrl = reply && /^\w{1,15}$/.test(reply.handle) && /^\d+$/.test(reply.id) ? `https://x.com/${reply.handle}/status/${reply.id}` : void 0;
287
- return el("article", {}, 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));
287
+ 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
288
  }
289
289
 
290
290
  //#endregion
package/dist/x/theme.css CHANGED
@@ -33,6 +33,24 @@
33
33
  inherits: true;
34
34
  }
35
35
 
36
+ @property --meowdown-embed-max-height {
37
+ syntax: '*';
38
+ inherits: true;
39
+ }
40
+
41
+ @property --_overflowing {
42
+ syntax: '<integer>';
43
+ inherits: true;
44
+ initial-value: 0;
45
+ }
46
+
47
+ @keyframes meowdown-embed-overflowing {
48
+ from,
49
+ to {
50
+ --_overflowing: 1;
51
+ }
52
+ }
53
+
36
54
  /*
37
55
  * Hard reset. `all: revert` rolls every property on every box inside the card
38
56
  * back to the user-agent default, so a host rule on `p`, `a`, `img`,
@@ -58,12 +76,14 @@
58
76
  --_link: var(--meowdown-embed-link-color, #1d9bf0);
59
77
  --_tint: color-mix(in srgb, currentColor 5%, transparent);
60
78
  --_media-height: 7.5rem;
79
+ --_show-more-height: 2rem;
80
+ --_max-width: 28rem;
61
81
 
62
82
  display: block;
63
83
  /* As wide as the content, between 18rem and 28rem: a short text post stays
64
84
  * small, while long text and media fill the full width. */
65
85
  width: fit-content;
66
- max-width: min(28rem, 100%);
86
+ max-width: min(var(--_max-width), 100%);
67
87
  font: inherit;
68
88
  color: var(--meowdown-embed-color, inherit);
69
89
  text-decoration-skip-ink: none;
@@ -86,6 +106,11 @@
86
106
  [data-meowdown-embed='x'] [data-root] {
87
107
  display: block;
88
108
  position: relative;
109
+ /* The host's percentage `max-width` counts as `none` when a shrink-to-fit
110
+ * parent (an inline-block editor wrapper) measures the card, so long text
111
+ * would widen that parent to its own limit. A fixed cap here keeps the
112
+ * card's intrinsic width at 28rem. */
113
+ max-width: var(--_max-width);
89
114
  color: var(--meowdown-embed-color, inherit);
90
115
  background: var(--meowdown-embed-background, transparent);
91
116
  border: 1px solid var(--_hairline);
@@ -184,6 +209,111 @@
184
209
  color: inherit;
185
210
  }
186
211
 
212
+ /* Max height. The article is clipped to `--_body-limit`, the tallest
213
+ * article that keeps the card within `--meowdown-embed-max-height`. As a
214
+ * scroll container it has a scroll timeline only while the clip hides
215
+ * content, so the animation on that timeline sets `--_overflowing` exactly
216
+ * when the card is too tall. Then the article gives up one row, which the
217
+ * "Show more" toggle takes, so the whole card is exactly the limit. The
218
+ * toggle is anchored to the card's padding box, out of the article's clip.
219
+ * Browsers without scroll-driven animations show the whole card. */
220
+ & > article {
221
+ /* Room inside the clip for focus rings at the article's edges. */
222
+ --_clip-gutter: 4px;
223
+ --_body-limit: calc(
224
+ var(--meowdown-embed-max-height, 256px) - 2 * var(--meowdown-embed-padding, 1rem) - 2px + 2 *
225
+ var(--_clip-gutter)
226
+ );
227
+ }
228
+ & > article > [data-show-more] {
229
+ display: none;
230
+ }
231
+ & > article > [data-show-more] > summary {
232
+ display: block;
233
+ width: fit-content;
234
+ font-size: 0.9375em;
235
+ line-height: var(--_show-more-height);
236
+ color: var(--_link);
237
+ list-style: none;
238
+ cursor: pointer;
239
+ }
240
+ & > article > [data-show-more] > summary::-webkit-details-marker {
241
+ display: none;
242
+ }
243
+ & > article > [data-show-more] > summary::before {
244
+ content: 'Show more';
245
+ }
246
+ & > article > [data-show-more][open] > summary::before {
247
+ content: 'Show less';
248
+ }
249
+ & > article > [data-show-more] > summary:hover {
250
+ text-decoration: underline;
251
+ text-underline-offset: 0.15em;
252
+ }
253
+ & > article > [data-show-more] > summary:focus-visible {
254
+ outline: 2px solid var(--_link);
255
+ outline-offset: 2px;
256
+ border-radius: 4px;
257
+ }
258
+ @supports (animation-timeline: scroll()) {
259
+ & > article {
260
+ max-height: calc(var(--_body-limit) - var(--_overflowing) * var(--_show-more-height));
261
+ overflow: hidden;
262
+ padding: var(--_clip-gutter);
263
+ margin: calc(-1 * var(--_clip-gutter));
264
+ margin-block-end: calc(var(--_overflowing) * var(--_show-more-height) - var(--_clip-gutter));
265
+ animation: meowdown-embed-overflowing linear both;
266
+ animation-timeline: scroll(self);
267
+ }
268
+ /* Opened, or holding keyboard focus that the clip could hide. */
269
+ & > article:has(> [data-show-more][open]),
270
+ & > article:has(:focus-visible:not(summary)) {
271
+ max-height: none;
272
+ overflow: visible;
273
+ margin-block-end: calc(-1 * var(--_clip-gutter));
274
+ }
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
+ }
290
+ }
291
+ /* Opened, "Show less" sticks to the bottom of the scrollport until the
292
+ * card's end scrolls into view. */
293
+ & > article > [data-show-more][open] {
294
+ display: block;
295
+ position: sticky;
296
+ bottom: 0.75rem;
297
+ z-index: 1;
298
+ width: fit-content;
299
+ margin: 0.5rem auto 0;
300
+ }
301
+ & > article > [data-show-more][open] > summary {
302
+ padding: 0.375rem 0.875rem;
303
+ border: 1px solid var(--_hairline);
304
+ border-radius: 999px;
305
+ background: var(--meowdown-embed-background, Canvas);
306
+ box-shadow: 0 2px 8px rgb(0 0 0 / 12%);
307
+ font-size: 0.875em;
308
+ font-weight: 500;
309
+ line-height: normal;
310
+ }
311
+ & > article > [data-show-more][open] > summary:hover {
312
+ text-decoration: none;
313
+ background: color-mix(in srgb, var(--_link) 8%, var(--meowdown-embed-background, Canvas));
314
+ }
315
+ }
316
+
187
317
  /* Body */
188
318
  & [data-reply-to] {
189
319
  display: block;
@@ -353,11 +483,6 @@
353
483
  align-self: center;
354
484
  }
355
485
  & [data-quoted] [data-body] {
356
- display: -webkit-box;
357
- -webkit-box-orient: vertical;
358
- -webkit-line-clamp: 3;
359
- line-clamp: 3;
360
- overflow: hidden;
361
486
  margin-block-start: 0.25rem;
362
487
  font-size: 0.9375em;
363
488
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/embed",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.3.1",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",