create-ampless 1.0.0-alpha.90 → 1.0.0-alpha.92

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.
@@ -28,6 +28,7 @@ ampless はテーマとプラグインの両方を提供します。用途に合
28
28
  | 信頼できる副作用(S3 書き込み・外部 API 送信) | | ✓ (`writePublicAsset` + `trusted`) |
29
29
  | テーマに依存しない `<head>` / `<body>` 注入(アナリティクス・同意バナー) | | ✓ (`publicHead` / `publicBodyEnd`) |
30
30
  | 投稿単位の機械可読メタデータ(JSON-LD 等) | | ✓ (`schema` via `publicBodyForPost`) |
31
+ | 投稿本文の周囲の可視 HTML(reading-time、breadcrumb、share など) | | ✓ (`publicHtmlForPost`) |
31
32
  | 複数の ampless サイトで共有したいコード | | ✓ (npm パッケージとして公開) |
32
33
 
33
34
  判断の目安:
@@ -67,6 +68,7 @@ ampless プラグインは `AmplessPlugin` オブジェクトを返す TypeScrip
67
68
  | `publicHead(ctx)` | root layout の `<head>` | 同期 (async layout から呼ばれる) | 1 |
68
69
  | `publicBodyEnd(ctx)` | root layout の `<body>` 末尾 | 同期 | 1 |
69
70
  | `publicBodyForPost(post, ctx)` | テーマの post ページテンプレート(投稿単位) | 同期 | 4 |
71
+ | `publicHtmlForPost(post, ctx)` | テーマの post ページテンプレート(投稿単位、可視 HTML) | 同期 | 6d |
70
72
  | `ogImage` | `/og/[slug]` ルート | リクエスト時、公開 Lambda 内 | 既存 |
71
73
  | `hooks` | trust_level に応じた processor Lambda | 非同期、SQS イベントで起動 | 既存 |
72
74
  | `settings.public` | `/admin/plugins` フォーム | 宣言的なマニフェスト | 2 |
@@ -181,6 +183,7 @@ interface AmplessPlugin {
181
183
  publicHead?(ctx): readonly PublicHeadDescriptor[]
182
184
  publicBodyEnd?(ctx): readonly PublicBodyDescriptor[]
183
185
  publicBodyForPost?(post: Post, ctx): readonly PublicPostBodyDescriptor[]
186
+ publicHtmlForPost?(post: Post, ctx): readonly PublicPostHtmlDescriptor[]
184
187
  ogImage?: OgImageConfig
185
188
  settings?: { public?: readonly PluginSettingField[] }
186
189
  }
@@ -309,6 +312,7 @@ SSR がデッドラインなしでブロックします。ネットワーク呼
309
312
  | `publicHead(ctx)` | `PublicHeadDescriptor[]` | 解析ローダー、フォント、jsonld、hreflang |
310
313
  | `publicBodyEnd(ctx)` | `PublicBodyDescriptor[]` | GTM no-script フレーム、チャットウィジェット、末尾スニペット |
311
314
  | `publicBodyForPost(post, ctx)` | `PublicPostBodyDescriptor[]` | 投稿単位の body 注入 — JSON-LD 構造化データ。テーマの post ページテンプレートが render する |
315
+ | `publicHtmlForPost(post, ctx)` | `PublicPostHtmlDescriptor[]` | 投稿単位の可視 HTML を `beforeContent` / `afterContent` に注入 — reading-time バッジ、breadcrumb、share リンク等。body は runtime が `sanitize-html` の厳格 allowlist で sanitize |
312
316
 
313
317
  `ctx` オブジェクトの中身:
314
318
 
@@ -402,6 +406,21 @@ TypeScript としてサイトと同一の Node プロセスで動きます。des
402
406
 
403
407
  `meta` / `link` を除いている理由:投稿単位のメタデータは Next.js `generateMetadata()` 経由の `metadata()` サーフェスが担い、フレームワークの deduplication・streaming と統合されている。`publicBodyForPost` は `generateMetadata` が生成できない構造化データ(`<script type="application/ld+json">`)のためだけに存在する。
404
408
 
409
+ ### `PublicPostHtmlDescriptor`(Phase 6d)
410
+
411
+ `publicHtmlForPost` は `PublicPostHtmlDescriptor[]` を返します:
412
+
413
+ ```ts
414
+ {
415
+ type: 'html',
416
+ id: 'display', // plugin-local 短識別子(≤ 64 文字、制御文字不可)
417
+ position: 'beforeContent' | 'afterContent',
418
+ body: '<p class="reading-time">約 3 分で読めます</p>',
419
+ }
420
+ ```
421
+
422
+ runtime は `body` を `sanitize-html` の厳格 allowlist で sanitize し(詳しい allowlist と drop 対象は上の `publicHtmlForPost` 例を参照)、結果を `<div data-ampless-plugin="${namespace}" data-ampless-position="${position}">` で wrap します。テーマは `pages/post.tsx` で `{html.beforeContent}` / `{html.afterContent}` を embed するだけで、plugin の出力に対して `dangerouslySetInnerHTML` を書きません。
423
+
405
424
  ### JSON-LD 自動 escape
406
425
 
407
426
  `scriptType === 'application/ld+json'` のとき、runtime は描画前に **`body` 文字列を自動 escape** する — `<` → `<`、`>` → `>`、`&` → `&`、U+2028 → ` `、U+2029 → ` `。この処理は `inlineScript` を受け付ける 3 つのサーフェス(`publicHead` / `publicBodyEnd` / `publicBodyForPost`)すべてで行われる。プラグイン作者は生の JSON 文字列を返せばよく、自前で escape しなくてよい。
@@ -470,6 +489,60 @@ export default function schemaJsonldPlugin() {
470
489
 
471
490
  テーマの `pages/post.tsx` が `ampless.publicBodyForPost(post)` を呼び、返された descriptor を描画します。runtime は自動 escape した body を持つ `<script type="application/ld+json">` 要素をページに挿入します。
472
491
 
492
+ ### `publicHtmlForPost` 例(Phase 6d)
493
+
494
+ **可視 HTML** を post の周囲に出したいとき(reading-time バッジ、breadcrumb、share リンク、micro-format 注釈など)は `publicHtmlForPost` を使います。runtime が body を sanitize したうえで `beforeContent` / `afterContent` スロットに embed するので、テーマ側で `dangerouslySetInnerHTML` を書く必要はありません。
495
+
496
+ ```typescript
497
+ import { definePlugin } from 'ampless'
498
+
499
+ export default function readingTimePlugin() {
500
+ return definePlugin({
501
+ name: 'reading-time',
502
+ apiVersion: 1,
503
+ trust_level: 'untrusted',
504
+ capabilities: ['publicHtmlForPost'],
505
+ publicHtmlForPost(post, _ctx) {
506
+ const words = countWords(post)
507
+ const minutes = Math.max(1, Math.round(words / 200))
508
+ return [{
509
+ type: 'html',
510
+ id: 'display',
511
+ position: 'beforeContent',
512
+ body: `<p class="reading-time" data-words="${words}" data-minutes="${minutes}">約 ${minutes} 分で読めます</p>`,
513
+ }]
514
+ },
515
+ })
516
+ }
517
+ ```
518
+
519
+ テーマの `pages/post.tsx` は `const html = await ampless.publicHtmlForPost(post)` を 1 回呼び、スロットを embed します:
520
+
521
+ ```tsx
522
+ {postBody} {/* publicBodyForPost — JSON-LD */}
523
+ {html.beforeContent} {/* publicHtmlForPost — beforeContent スロット */}
524
+ <div className="prose" dangerouslySetInnerHTML={{ __html: renderBody(post) }} />
525
+ {html.afterContent} {/* publicHtmlForPost — afterContent スロット */}
526
+ ```
527
+
528
+ **スロット位置**(v1): `'beforeContent'` / `'afterContent'` の 2 つ。
529
+
530
+ **Sanitizer(厳格、trust level に関わらず同一):**
531
+
532
+ - 許可タグ: `p` · `span` · `strong` · `em` · `a` · `code` · `br` · `ul` · `ol` · `li`
533
+ - 許可グローバル属性: `class` · `data-words` · `data-minutes` · `data-ampless-*`
534
+ - 許可 `<a>` 属性: `href` · `rel` · `target`。`target="_blank"` のとき sanitizer が `rel="noopener noreferrer"` を自動付与
535
+ - `href` で許可するスキーム: `http` / `https`。相対 URL (`./path` / `../path` / `/path` / `#anchor`) は素通り。`javascript:` / `data:` / `mailto:` / `tel:` / `vbscript:` は drop
536
+ - drop されるタグ・属性: `<img>` · `<iframe>` · `<video>` · `<audio>` · `<object>` · `<embed>` · `<form>` · `<style>` · インライン `style` · 全 event handler (`on*`)
537
+
538
+ allowlist 外のタグが必要になった場合は issue を立ててください。allowlist は設計上拡張するものであって、escape hatch ではありません。
539
+
540
+ **`id` は plugin-local。** 短い識別子(例: `'display'`)を使います。runtime が React `key` および wrapper `<div>` の `data-ampless-plugin` / `data-ampless-position` 属性を組むときに `${instanceId ?? name}:${id}` で resolve するので、plugin 作者が自前で namespace を埋め込む必要はありません。validator は `id` が空、制御文字を含む、64 文字超のいずれかなら descriptor を drop します。
541
+
542
+ **dedupe は position ごと。** 1 つの plugin instance が `beforeContent` と `afterContent` の両方に同じ `id` を返すのは OK(dedupe スコープが独立)。同じ position に同じ `id` を 2 回返すと最初の 1 件を残して 2 件目を warn 付きで drop します。
543
+
544
+ **複数 instance。** distinct な `instanceId` を持つ 2 つの `reading-time` instance(例: `reading-time-en` / `reading-time-jp`)は、同じ position に `id: 'display'` を返しても両方残ります(namespace が違うため)。
545
+
473
546
  ### クライアントサイドの DOM 操作はしない
474
547
 
475
548
  `publicHead` または `publicBodyEnd` から返したインラインスクリプトは、React がページを hydrate する前、HTML のパース中に実行されます。**React が管理するサブツリー内の見える DOM を操作してはいけません** — hydration が走ると React は仮想 DOM と合わないツリーを検出し、`Hydration failed because the server rendered HTML didn't match the client` エラーを投げてサブツリーをゼロから再生成します。挿入したノードは消えてしまいます。
@@ -488,7 +561,7 @@ React 19 はさらに、クライアントコンポーネントのレンダー
488
561
  - テーマが描画した要素のクラス / 属性 / テキストコンテンツの変更
489
562
  - クライアントサイドで `#post-body` のような要素を読み取って投稿単位の HTML を挿入する — 現在 `publicHead`-for-post に相当するサーフェスはなく、サーバーレンダリング済みのサブツリーをクライアントサイドで書き換えると hydration と競合します
490
563
 
491
- 投稿単位の見える出力には、`publicBodyForPost` 経由で JSON-LD を返す(§6 の `PublicPostBodyDescriptor` 参照)か、テーマ自身のテンプレートを使ってください。サーバーサイドでの投稿単位 HTML 注入はロードマップに載っています。
564
+ 投稿単位の見える出力には `publicHtmlForPost` を使ってください(Phase 6d — 上の例と §6 の `PublicPostHtmlDescriptor` 参照)。runtime が post 本文の周囲の固定スロットにサーバーサイド HTML を出すので、hydration と競合しません。
492
565
 
493
566
  ---
494
567
 
@@ -35,6 +35,7 @@ your code where future-you (and other site authors) will look for it.
35
35
  | Trusted side effects (S3 writes, external API push) | | ✓ (`writePublicAsset` + `trusted`) |
36
36
  | Theme-independent `<head>` / `<body>` injection (analytics, consent) | | ✓ (`publicHead` / `publicBodyEnd`) |
37
37
  | Per-post machine-readable metadata (JSON-LD, etc.) | | ✓ (`schema` via `publicBodyForPost`) |
38
+ | Per-post visible HTML around the body (reading-time, breadcrumb, share) | | ✓ (`publicHtmlForPost`) |
38
39
  | Code you want to share across multiple ampless sites | | ✓ (publish as npm package) |
39
40
 
40
41
  Rule of thumb:
@@ -88,6 +89,7 @@ surfaces:
88
89
  | `publicHead(ctx)` | root layout `<head>` | sync (called from async layout) | 1 |
89
90
  | `publicBodyEnd(ctx)` | root layout end of `<body>` | sync | 1 |
90
91
  | `publicBodyForPost(post, ctx)` | theme post page template (per-post) | sync | 4 |
92
+ | `publicHtmlForPost(post, ctx)` | theme post page template (per-post, visible HTML) | sync | 6d |
91
93
  | `ogImage` | `/og/[slug]` route | request-time, in public Lambda | Existing |
92
94
  | `hooks` | trust_level-matched processor Lambda | async, on SQS event | Existing |
93
95
  | `settings.public` | `/admin/plugins` form | declarative manifest | 2 |
@@ -210,6 +212,7 @@ interface AmplessPlugin {
210
212
  publicHead?(ctx): readonly PublicHeadDescriptor[]
211
213
  publicBodyEnd?(ctx): readonly PublicBodyDescriptor[]
212
214
  publicBodyForPost?(post: Post, ctx): readonly PublicPostBodyDescriptor[]
215
+ publicHtmlForPost?(post: Post, ctx): readonly PublicPostHtmlDescriptor[]
213
216
  ogImage?: OgImageConfig
214
217
  settings?: { public?: readonly PluginSettingField[] }
215
218
  }
@@ -380,6 +383,7 @@ elevated AWS access.
380
383
  | `publicHead(ctx)` | `PublicHeadDescriptor[]` | Analytics loader, fonts, jsonld, hreflang |
381
384
  | `publicBodyEnd(ctx)` | `PublicBodyDescriptor[]` | GTM no-script frame, chat widgets, tail snippets |
382
385
  | `publicBodyForPost(post, ctx)` | `PublicPostBodyDescriptor[]` | Per-post body injection — JSON-LD structured data; rendered by the theme's post page template |
386
+ | `publicHtmlForPost(post, ctx)` | `PublicPostHtmlDescriptor[]` | Per-post visible HTML at `beforeContent` / `afterContent` — reading-time badge, breadcrumb, share links. Bodies are sanitized by the runtime under a strict `sanitize-html` allowlist |
383
387
 
384
388
  The `ctx` object carries:
385
389
 
@@ -483,6 +487,27 @@ behaviour. `publicBodyForPost` exists solely for the structured data
483
487
  use-case (`<script type="application/ld+json">`) that `generateMetadata`
484
488
  cannot produce.
485
489
 
490
+ ### `PublicPostHtmlDescriptor` (Phase 6d)
491
+
492
+ `publicHtmlForPost` returns `PublicPostHtmlDescriptor[]`:
493
+
494
+ ```ts
495
+ {
496
+ type: 'html',
497
+ id: 'display', // plugin-local short identifier (≤ 64 chars, no control chars)
498
+ position: 'beforeContent' | 'afterContent',
499
+ body: '<p class="reading-time">~3 min read</p>',
500
+ }
501
+ ```
502
+
503
+ The runtime sanitizes `body` with `sanitize-html` under a strict
504
+ allowlist (see the `publicHtmlForPost` example above for the full
505
+ allowlist + dropped-tag list) and wraps the result in
506
+ `<div data-ampless-plugin="${namespace}" data-ampless-position="${position}">`.
507
+ Themes embed `{html.beforeContent}` / `{html.afterContent}` in
508
+ `pages/post.tsx`; they never call `dangerouslySetInnerHTML` on plugin
509
+ output themselves.
510
+
486
511
  ### JSON-LD auto-escape
487
512
 
488
513
  When `scriptType === 'application/ld+json'`, the runtime **automatically
@@ -574,6 +599,80 @@ and renders the returned descriptors. The runtime inserts a
574
599
  `<script type="application/ld+json">` element with the auto-escaped
575
600
  body into the page.
576
601
 
602
+ ### `publicHtmlForPost` example (Phase 6d)
603
+
604
+ Use `publicHtmlForPost` when you need to render **visible HTML**
605
+ around a post — reading-time badge, breadcrumb, share links,
606
+ micro-format annotations, etc. The runtime sanitizes the body before
607
+ rendering and embeds the result at the `beforeContent` or
608
+ `afterContent` slot, so themes never call `dangerouslySetInnerHTML`
609
+ on plugin output.
610
+
611
+ ```typescript
612
+ import { definePlugin } from 'ampless'
613
+
614
+ export default function readingTimePlugin() {
615
+ return definePlugin({
616
+ name: 'reading-time',
617
+ apiVersion: 1,
618
+ trust_level: 'untrusted',
619
+ capabilities: ['publicHtmlForPost'],
620
+ publicHtmlForPost(post, _ctx) {
621
+ const words = countWords(post)
622
+ const minutes = Math.max(1, Math.round(words / 200))
623
+ return [{
624
+ type: 'html',
625
+ id: 'display',
626
+ position: 'beforeContent',
627
+ body: `<p class="reading-time" data-words="${words}" data-minutes="${minutes}">~${minutes} min read</p>`,
628
+ }]
629
+ },
630
+ })
631
+ }
632
+ ```
633
+
634
+ The theme's `pages/post.tsx` calls
635
+ `const html = await ampless.publicHtmlForPost(post)` once and embeds
636
+ the slots:
637
+
638
+ ```tsx
639
+ {postBody} {/* publicBodyForPost — JSON-LD */}
640
+ {html.beforeContent} {/* publicHtmlForPost — beforeContent slot */}
641
+ <div className="prose" dangerouslySetInnerHTML={{ __html: renderBody(post) }} />
642
+ {html.afterContent} {/* publicHtmlForPost — afterContent slot */}
643
+ ```
644
+
645
+ **Slot positions** (v1): `'beforeContent'` and `'afterContent'`.
646
+
647
+ **Sanitizer (strict, identical across trust levels):**
648
+
649
+ - Allowed tags: `p` · `span` · `strong` · `em` · `a` · `code` · `br` · `ul` · `ol` · `li`
650
+ - Allowed global attributes: `class` · `data-words` · `data-minutes` · `data-ampless-*`
651
+ - Allowed `<a>` attributes: `href` · `rel` · `target`. `target="_blank"` triggers auto-injection of `rel="noopener noreferrer"`.
652
+ - Allowed URL schemes on `href`: `http` / `https`. Relative URLs (`./path`, `../path`, `/path`, `#anchor`) pass through. `javascript:` / `data:` / `mailto:` / `tel:` / `vbscript:` are dropped.
653
+ - Dropped tags / attributes: `<img>` · `<iframe>` · `<video>` · `<audio>` · `<object>` · `<embed>` · `<form>` · `<style>` · inline `style` · all event handlers (`on*`).
654
+
655
+ If you need a tag outside this list, open an issue — the allowlist
656
+ expands by design, not by escape hatch.
657
+
658
+ **`id` is plugin-local.** Use a short identifier (e.g. `'display'`).
659
+ The runtime resolves it to `${instanceId ?? name}:${id}` when building
660
+ the React `key` and the wrapper `<div>`'s `data-ampless-plugin` /
661
+ `data-ampless-position` attributes. Do not embed your own namespace
662
+ in `id`. The validator drops descriptors whose `id` is empty,
663
+ contains control characters, or exceeds 64 characters.
664
+
665
+ **Dedupe is per-position.** Returning the same `id` to both
666
+ `beforeContent` and `afterContent` from a single plugin instance is
667
+ fine — they live in independent dedupe scopes. Returning the same
668
+ `id` twice to the same position keeps only the first occurrence and
669
+ warns.
670
+
671
+ **Multiple instances.** Two `reading-time` plugin instances with
672
+ distinct `instanceId` (e.g. `reading-time-en` / `reading-time-jp`)
673
+ can both emit `id: 'display'` to the same position; the runtime
674
+ keeps both because the namespaces differ.
675
+
577
676
  ### Client-side DOM mutation: don't
578
677
 
579
678
  Inline scripts you return from `publicHead` or `publicBodyEnd` execute
@@ -612,10 +711,10 @@ something like `document.body.append(myNewElement)` may not even fire.
612
711
  any client-side rewrite of a server-rendered subtree races against
613
712
  hydration
614
713
 
615
- For visible per-post output, return JSON-LD via `publicBodyForPost`
616
- (see §6's `PublicPostBodyDescriptor`) or stick with the theme's own
617
- templates. A future ampless capability for server-side per-post HTML
618
- injection is on the roadmap.
714
+ For visible per-post output, use `publicHtmlForPost` (Phase 6d — see
715
+ the example above and §6's `PublicPostHtmlDescriptor`). The runtime
716
+ emits server-side HTML at fixed slots around the post body, so
717
+ nothing races against hydration.
619
718
 
620
719
  ---
621
720
 
@@ -25,20 +25,20 @@
25
25
  "@tiptap/pm": "^3.23.6",
26
26
  "@tiptap/react": "^3.23.6",
27
27
  "@tiptap/starter-kit": "^3.23.6",
28
- "@ampless/plugin-analytics-ga4": "^0.2.0-alpha.11",
29
- "@ampless/plugin-cookie-consent": "^0.1.0-alpha.2",
30
- "@ampless/plugin-gtm": "^0.2.0-alpha.10",
31
- "@ampless/plugin-og-image": "^0.2.0-alpha.27",
32
- "@ampless/plugin-plausible": "^0.2.0-alpha.10",
33
- "@ampless/plugin-rss": "^0.2.0-alpha.27",
34
- "@ampless/plugin-schema-jsonld": "^0.1.1-alpha.6",
35
- "@ampless/plugin-seo": "^0.2.0-alpha.27",
36
- "@ampless/plugin-webhook": "^0.2.0-alpha.27",
37
- "@ampless/admin": "^1.0.0-alpha.56",
38
- "@ampless/backend": "^1.0.0-alpha.44",
39
- "@ampless/runtime": "^1.0.0-alpha.35",
28
+ "@ampless/plugin-analytics-ga4": "^0.2.0-alpha.12",
29
+ "@ampless/plugin-cookie-consent": "^0.1.0-alpha.3",
30
+ "@ampless/plugin-gtm": "^0.2.0-alpha.11",
31
+ "@ampless/plugin-og-image": "^0.2.0-alpha.28",
32
+ "@ampless/plugin-plausible": "^0.2.0-alpha.11",
33
+ "@ampless/plugin-rss": "^0.2.0-alpha.28",
34
+ "@ampless/plugin-schema-jsonld": "^0.1.1-alpha.7",
35
+ "@ampless/plugin-seo": "^0.2.0-alpha.28",
36
+ "@ampless/plugin-webhook": "^0.2.0-alpha.28",
37
+ "@ampless/admin": "^1.0.0-alpha.57",
38
+ "@ampless/backend": "^1.0.0-alpha.45",
39
+ "@ampless/runtime": "^1.0.0-alpha.36",
40
40
  "@digital-go-jp/tailwind-theme-plugin": "^0.3.4",
41
- "ampless": "^1.0.0-alpha.27",
41
+ "ampless": "^1.0.0-alpha.28",
42
42
  "aws-amplify": "^6.17.0",
43
43
  "class-variance-authority": "^0.7.1",
44
44
  "clsx": "^2.1.1",
@@ -39,6 +39,7 @@ export default async function BlogPost({ params }: PostCtx) {
39
39
  if (!post) notFound()
40
40
 
41
41
  const postBody = await ampless.publicBodyForPost(post)
42
+ const html = await ampless.publicHtmlForPost(post)
42
43
 
43
44
  const defaultLightbox = settings.media.imageDisplay === 'lightbox'
44
45
  const maxWidth = settings.media.imageMaxWidth ?? '100%'
@@ -79,6 +80,8 @@ export default async function BlogPost({ params }: PostCtx) {
79
80
 
80
81
  {postBody}
81
82
 
83
+ {html.beforeContent}
84
+
82
85
  <div
83
86
  id="post-body"
84
87
  className="prose prose-neutral dark:prose-invert max-w-none [&_img]:max-w-[var(--ampless-img-max-width)] [&_img]:mx-auto"
@@ -86,6 +89,8 @@ export default async function BlogPost({ params }: PostCtx) {
86
89
  dangerouslySetInnerHTML={{ __html: renderBody(post) }}
87
90
  />
88
91
 
92
+ {html.afterContent}
93
+
89
94
  <TagList tags={post.tags} className="mt-8 border-t pt-6" />
90
95
 
91
96
  <footer className="mt-6 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-gray-500">
@@ -29,6 +29,7 @@ export default async function CorporatePost({ params }: PostCtx) {
29
29
  if (!post) notFound()
30
30
 
31
31
  const postBody = await ampless.publicBodyForPost(post)
32
+ const html = await ampless.publicHtmlForPost(post)
32
33
 
33
34
  const defaultLightbox = settings.media.imageDisplay === 'lightbox'
34
35
  const maxWidth = settings.media.imageMaxWidth ?? '100%'
@@ -68,6 +69,8 @@ export default async function CorporatePost({ params }: PostCtx) {
68
69
 
69
70
  {postBody}
70
71
 
72
+ {html.beforeContent}
73
+
71
74
  <div
72
75
  id="post-body"
73
76
  className="prose prose-neutral dark:prose-invert max-w-none [&_img]:max-w-[var(--ampless-img-max-width)] [&_img]:mx-auto"
@@ -75,6 +78,8 @@ export default async function CorporatePost({ params }: PostCtx) {
75
78
  dangerouslySetInnerHTML={{ __html: renderBody(post) }}
76
79
  />
77
80
 
81
+ {html.afterContent}
82
+
78
83
  <TagList tags={post.tags} className="mt-8 border-t pt-6" />
79
84
  </article>
80
85
 
@@ -29,6 +29,7 @@ export default async function DadsPost({ params }: PostCtx) {
29
29
  if (!post) notFound()
30
30
 
31
31
  const postBody = await ampless.publicBodyForPost(post)
32
+ const html = await ampless.publicHtmlForPost(post)
32
33
 
33
34
  const defaultLightbox = settings.media.imageDisplay === 'lightbox'
34
35
  const maxWidth = settings.media.imageMaxWidth ?? '100%'
@@ -73,6 +74,8 @@ export default async function DadsPost({ params }: PostCtx) {
73
74
 
74
75
  {postBody}
75
76
 
77
+ {html.beforeContent}
78
+
76
79
  <div
77
80
  id="post-body"
78
81
  className="prose prose-neutral dark:prose-invert max-w-none [&_a]:text-[var(--primary)] [&_a]:underline-offset-4 [&_img]:max-w-[var(--ampless-img-max-width)] [&_img]:mx-auto"
@@ -80,6 +83,8 @@ export default async function DadsPost({ params }: PostCtx) {
80
83
  dangerouslySetInnerHTML={{ __html: renderBody(post) }}
81
84
  />
82
85
 
86
+ {html.afterContent}
87
+
83
88
  <TagList tags={post.tags} className="mt-10 border-t pt-6" />
84
89
  </article>
85
90
 
@@ -33,6 +33,7 @@ export default async function DocsPost({ params }: PostCtx) {
33
33
  if (!post) notFound()
34
34
 
35
35
  const postBody = await ampless.publicBodyForPost(post)
36
+ const html = await ampless.publicHtmlForPost(post)
36
37
 
37
38
  const defaultLightbox = settings.media.imageDisplay === 'lightbox'
38
39
  const maxWidth = settings.media.imageMaxWidth ?? '100%'
@@ -70,6 +71,8 @@ export default async function DocsPost({ params }: PostCtx) {
70
71
 
71
72
  {postBody}
72
73
 
74
+ {html.beforeContent}
75
+
73
76
  <div
74
77
  id="post-body"
75
78
  className="prose prose-neutral dark:prose-invert max-w-none [&_img]:max-w-[var(--ampless-img-max-width)] [&_img]:mx-auto"
@@ -77,6 +80,8 @@ export default async function DocsPost({ params }: PostCtx) {
77
80
  dangerouslySetInnerHTML={{ __html: renderBody(post) }}
78
81
  />
79
82
 
83
+ {html.afterContent}
84
+
80
85
  <TagList tags={post.tags} className="mt-10 border-t pt-6" />
81
86
  </article>
82
87
  </main>
@@ -29,6 +29,7 @@ export default async function LandingPost({ params }: PostCtx) {
29
29
  if (!post) notFound()
30
30
 
31
31
  const postBody = await ampless.publicBodyForPost(post)
32
+ const html = await ampless.publicHtmlForPost(post)
32
33
 
33
34
  const defaultLightbox = settings.media.imageDisplay === 'lightbox'
34
35
  const maxWidth = settings.media.imageMaxWidth ?? '100%'
@@ -70,6 +71,8 @@ export default async function LandingPost({ params }: PostCtx) {
70
71
 
71
72
  {postBody}
72
73
 
74
+ {html.beforeContent}
75
+
73
76
  <div
74
77
  id="post-body"
75
78
  className="prose prose-neutral dark:prose-invert max-w-none [&_img]:max-w-[var(--ampless-img-max-width)] [&_img]:mx-auto"
@@ -77,6 +80,8 @@ export default async function LandingPost({ params }: PostCtx) {
77
80
  dangerouslySetInnerHTML={{ __html: renderBody(post) }}
78
81
  />
79
82
 
83
+ {html.afterContent}
84
+
80
85
  <TagList tags={post.tags} className="mt-10 border-t pt-6" />
81
86
  </article>
82
87
 
@@ -26,6 +26,7 @@ export default async function MinimalPost({ params }: PostCtx) {
26
26
  if (!post) notFound()
27
27
 
28
28
  const postBody = await ampless.publicBodyForPost(post)
29
+ const html = await ampless.publicHtmlForPost(post)
29
30
 
30
31
  const defaultLightbox = settings.media.imageDisplay === 'lightbox'
31
32
  const maxWidth = settings.media.imageMaxWidth ?? '100%'
@@ -51,6 +52,8 @@ export default async function MinimalPost({ params }: PostCtx) {
51
52
 
52
53
  {postBody}
53
54
 
55
+ {html.beforeContent}
56
+
54
57
  <div
55
58
  id="post-body"
56
59
  className="prose prose-neutral dark:prose-invert max-w-none [&_img]:max-w-[var(--ampless-img-max-width)] [&_img]:mx-auto"
@@ -58,6 +61,8 @@ export default async function MinimalPost({ params }: PostCtx) {
58
61
  dangerouslySetInnerHTML={{ __html: renderBody(post) }}
59
62
  />
60
63
 
64
+ {html.afterContent}
65
+
61
66
  <TagList tags={post.tags} className="mt-8 border-t pt-6" />
62
67
  </article>
63
68
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ampless",
3
- "version": "1.0.0-alpha.90",
3
+ "version": "1.0.0-alpha.92",
4
4
  "description": "Create a new ampless project",
5
5
  "license": "MIT",
6
6
  "type": "module",