dineway 0.1.3 → 0.1.4

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.
Files changed (191) hide show
  1. package/package.json +6 -3
  2. package/src/astro/routes/PluginRegistry.tsx +21 -0
  3. package/src/astro/routes/admin.astro +83 -0
  4. package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
  5. package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
  6. package/src/astro/routes/api/admin/api-tokens/[id].ts +40 -0
  7. package/src/astro/routes/api/admin/api-tokens/index.ts +68 -0
  8. package/src/astro/routes/api/admin/bylines/[id]/index.ts +87 -0
  9. package/src/astro/routes/api/admin/bylines/index.ts +72 -0
  10. package/src/astro/routes/api/admin/comments/[id]/status.ts +120 -0
  11. package/src/astro/routes/api/admin/comments/[id].ts +64 -0
  12. package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
  13. package/src/astro/routes/api/admin/comments/counts.ts +30 -0
  14. package/src/astro/routes/api/admin/comments/index.ts +46 -0
  15. package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +91 -0
  16. package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
  17. package/src/astro/routes/api/admin/oauth-clients/[id].ts +110 -0
  18. package/src/astro/routes/api/admin/oauth-clients/index.ts +71 -0
  19. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +39 -0
  20. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +39 -0
  21. package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
  22. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +48 -0
  23. package/src/astro/routes/api/admin/plugins/[id]/update.ts +59 -0
  24. package/src/astro/routes/api/admin/plugins/index.ts +32 -0
  25. package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +62 -0
  26. package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
  27. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +64 -0
  28. package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
  29. package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
  30. package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
  31. package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +62 -0
  32. package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
  33. package/src/astro/routes/api/admin/users/[id]/disable.ts +69 -0
  34. package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
  35. package/src/astro/routes/api/admin/users/[id]/index.ts +146 -0
  36. package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
  37. package/src/astro/routes/api/admin/users/index.ts +66 -0
  38. package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
  39. package/src/astro/routes/api/auth/invite/accept.ts +52 -0
  40. package/src/astro/routes/api/auth/invite/complete.ts +86 -0
  41. package/src/astro/routes/api/auth/invite/index.ts +99 -0
  42. package/src/astro/routes/api/auth/logout.ts +40 -0
  43. package/src/astro/routes/api/auth/magic-link/send.ts +89 -0
  44. package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
  45. package/src/astro/routes/api/auth/me.ts +60 -0
  46. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +221 -0
  47. package/src/astro/routes/api/auth/oauth/[provider].ts +120 -0
  48. package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
  49. package/src/astro/routes/api/auth/passkey/index.ts +54 -0
  50. package/src/astro/routes/api/auth/passkey/options.ts +84 -0
  51. package/src/astro/routes/api/auth/passkey/register/options.ts +88 -0
  52. package/src/astro/routes/api/auth/passkey/register/verify.ts +119 -0
  53. package/src/astro/routes/api/auth/passkey/verify.ts +68 -0
  54. package/src/astro/routes/api/auth/signup/complete.ts +87 -0
  55. package/src/astro/routes/api/auth/signup/request.ts +77 -0
  56. package/src/astro/routes/api/auth/signup/verify.ts +53 -0
  57. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +311 -0
  58. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
  59. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +54 -0
  60. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +61 -0
  61. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +33 -0
  62. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
  63. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +56 -0
  64. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +54 -0
  65. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
  66. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +105 -0
  67. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +140 -0
  68. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +30 -0
  69. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +56 -0
  70. package/src/astro/routes/api/content/[collection]/[id].ts +137 -0
  71. package/src/astro/routes/api/content/[collection]/index.ts +59 -0
  72. package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
  73. package/src/astro/routes/api/dashboard.ts +32 -0
  74. package/src/astro/routes/api/dev/emails.ts +36 -0
  75. package/src/astro/routes/api/import/probe.ts +47 -0
  76. package/src/astro/routes/api/import/wordpress/analyze.ts +531 -0
  77. package/src/astro/routes/api/import/wordpress/execute.ts +296 -0
  78. package/src/astro/routes/api/import/wordpress/media.ts +338 -0
  79. package/src/astro/routes/api/import/wordpress/prepare.ts +181 -0
  80. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +393 -0
  81. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
  82. package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
  83. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +357 -0
  84. package/src/astro/routes/api/manifest.ts +63 -0
  85. package/src/astro/routes/api/mcp.ts +124 -0
  86. package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
  87. package/src/astro/routes/api/media/[id].ts +145 -0
  88. package/src/astro/routes/api/media/file/[...key].ts +79 -0
  89. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +86 -0
  90. package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
  91. package/src/astro/routes/api/media/providers/index.ts +30 -0
  92. package/src/astro/routes/api/media/upload-url.ts +137 -0
  93. package/src/astro/routes/api/media.ts +202 -0
  94. package/src/astro/routes/api/menus/[name]/items.ts +87 -0
  95. package/src/astro/routes/api/menus/[name]/reorder.ts +33 -0
  96. package/src/astro/routes/api/menus/[name].ts +65 -0
  97. package/src/astro/routes/api/menus/index.ts +47 -0
  98. package/src/astro/routes/api/oauth/authorize.ts +417 -0
  99. package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
  100. package/src/astro/routes/api/oauth/device/code.ts +55 -0
  101. package/src/astro/routes/api/oauth/device/token.ts +69 -0
  102. package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
  103. package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
  104. package/src/astro/routes/api/oauth/token.ts +184 -0
  105. package/src/astro/routes/api/openapi.json.ts +32 -0
  106. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +92 -0
  107. package/src/astro/routes/api/redirects/404s/index.ts +72 -0
  108. package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
  109. package/src/astro/routes/api/redirects/[id].ts +84 -0
  110. package/src/astro/routes/api/redirects/index.ts +52 -0
  111. package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
  112. package/src/astro/routes/api/revisions/[revisionId]/restore.ts +62 -0
  113. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +76 -0
  114. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +52 -0
  115. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +32 -0
  116. package/src/astro/routes/api/schema/collections/[slug]/index.ts +80 -0
  117. package/src/astro/routes/api/schema/collections/index.ts +47 -0
  118. package/src/astro/routes/api/schema/index.ts +109 -0
  119. package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
  120. package/src/astro/routes/api/schema/orphans/index.ts +26 -0
  121. package/src/astro/routes/api/search/enable.ts +64 -0
  122. package/src/astro/routes/api/search/index.ts +51 -0
  123. package/src/astro/routes/api/search/rebuild.ts +72 -0
  124. package/src/astro/routes/api/search/stats.ts +35 -0
  125. package/src/astro/routes/api/search/suggest.ts +49 -0
  126. package/src/astro/routes/api/sections/[slug].ts +84 -0
  127. package/src/astro/routes/api/sections/index.ts +52 -0
  128. package/src/astro/routes/api/settings/email.ts +150 -0
  129. package/src/astro/routes/api/settings.ts +67 -0
  130. package/src/astro/routes/api/setup/admin-verify.ts +102 -0
  131. package/src/astro/routes/api/setup/admin.ts +96 -0
  132. package/src/astro/routes/api/setup/dev-bypass.ts +200 -0
  133. package/src/astro/routes/api/setup/dev-reset.ts +40 -0
  134. package/src/astro/routes/api/setup/index.ts +127 -0
  135. package/src/astro/routes/api/setup/status.ts +122 -0
  136. package/src/astro/routes/api/snapshot.ts +76 -0
  137. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +95 -0
  138. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +69 -0
  139. package/src/astro/routes/api/taxonomies/index.ts +59 -0
  140. package/src/astro/routes/api/themes/preview.ts +78 -0
  141. package/src/astro/routes/api/typegen.ts +114 -0
  142. package/src/astro/routes/api/well-known/auth.ts +69 -0
  143. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +45 -0
  144. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +38 -0
  145. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +72 -0
  146. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +127 -0
  147. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +80 -0
  148. package/src/astro/routes/api/widget-areas/[name].ts +87 -0
  149. package/src/astro/routes/api/widget-areas/index.ts +99 -0
  150. package/src/astro/routes/api/widget-components.ts +22 -0
  151. package/src/astro/routes/robots.txt.ts +81 -0
  152. package/src/astro/routes/sitemap-[collection].xml.ts +104 -0
  153. package/src/astro/routes/sitemap.xml.ts +92 -0
  154. package/src/components/Break.astro +45 -0
  155. package/src/components/Button.astro +71 -0
  156. package/src/components/Buttons.astro +49 -0
  157. package/src/components/Code.astro +59 -0
  158. package/src/components/Columns.astro +59 -0
  159. package/src/components/CommentForm.astro +315 -0
  160. package/src/components/Comments.astro +232 -0
  161. package/src/components/Cover.astro +128 -0
  162. package/src/components/DinewayBodyEnd.astro +32 -0
  163. package/src/components/DinewayBodyStart.astro +32 -0
  164. package/src/components/DinewayHead.astro +53 -0
  165. package/src/components/DinewayImage.astro +178 -0
  166. package/src/components/DinewayMedia.astro +167 -0
  167. package/src/components/Embed.astro +128 -0
  168. package/src/components/File.astro +122 -0
  169. package/src/components/Gallery.astro +93 -0
  170. package/src/components/HtmlBlock.astro +33 -0
  171. package/src/components/Image.astro +178 -0
  172. package/src/components/InlineEditor.astro +27 -0
  173. package/src/components/InlinePortableTextEditor.tsx +1937 -0
  174. package/src/components/LiveSearch.astro +614 -0
  175. package/src/components/PortableText.astro +51 -0
  176. package/src/components/Pullquote.astro +51 -0
  177. package/src/components/Table.astro +108 -0
  178. package/src/components/WidgetArea.astro +22 -0
  179. package/src/components/WidgetRenderer.astro +72 -0
  180. package/src/components/index.ts +116 -0
  181. package/src/components/marks/Link.astro +31 -0
  182. package/src/components/marks/StrikeThrough.astro +7 -0
  183. package/src/components/marks/Subscript.astro +7 -0
  184. package/src/components/marks/Superscript.astro +7 -0
  185. package/src/components/marks/Underline.astro +7 -0
  186. package/src/components/widgets/Archives.astro +65 -0
  187. package/src/components/widgets/Categories.astro +35 -0
  188. package/src/components/widgets/RecentPosts.astro +51 -0
  189. package/src/components/widgets/Search.astro +18 -0
  190. package/src/components/widgets/Tags.astro +38 -0
  191. package/src/ui.ts +75 -0
@@ -0,0 +1,128 @@
1
+ ---
2
+ import { sanitizeHref } from "../utils/url.js";
3
+ import { sanitizeContent } from "../utils/sanitize.js";
4
+
5
+ /**
6
+ * Portable Text Embed block component
7
+ *
8
+ * Renders embeds from YouTube, Vimeo, Twitter, etc.
9
+ */
10
+ export interface Props {
11
+ node: {
12
+ _type: "embed";
13
+ _key: string;
14
+ url: string;
15
+ provider?: string;
16
+ html?: string;
17
+ caption?: string;
18
+ };
19
+ }
20
+
21
+ const { node } = Astro.props;
22
+
23
+ if (!node?.url) {
24
+ return null;
25
+ }
26
+
27
+ const { url: rawUrl, provider, html, caption } = node;
28
+ const url = sanitizeHref(rawUrl);
29
+
30
+ const YOUTUBE_ID_PATTERN = /(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
31
+ const VIMEO_ID_PATTERN = /vimeo\.com\/(\d+)/;
32
+
33
+ // Extract video ID for common providers
34
+ function getYouTubeId(input: string): string | null {
35
+ const match = input.match(YOUTUBE_ID_PATTERN);
36
+ return match?.[1] || null;
37
+ }
38
+
39
+ function getVimeoId(input: string): string | null {
40
+ const match = input.match(VIMEO_ID_PATTERN);
41
+ return match?.[1] || null;
42
+ }
43
+
44
+ const youtubeId = getYouTubeId(url);
45
+ const vimeoId = getVimeoId(url);
46
+
47
+ // Check if this is a self-hosted video or audio
48
+ const isSelfHostedVideo = provider === "video";
49
+ const isSelfHostedAudio = provider === "audio";
50
+ ---
51
+
52
+ <figure class="dineway-embed">
53
+ {
54
+ isSelfHostedVideo ? (
55
+ <div class="dineway-embed-video">
56
+ <video controls preload="metadata">
57
+ <source src={url} />
58
+ Your browser does not support the video element.
59
+ </video>
60
+ </div>
61
+ ) : isSelfHostedAudio ? (
62
+ <div class="dineway-embed-audio">
63
+ <audio controls preload="metadata">
64
+ <source src={url} />
65
+ Your browser does not support the audio element.
66
+ </audio>
67
+ </div>
68
+ ) : youtubeId ? (
69
+ <div class="dineway-embed-video">
70
+ <iframe
71
+ src={`https://www.youtube.com/embed/${youtubeId}`}
72
+ title="YouTube video"
73
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
74
+ allowfullscreen
75
+ />
76
+ </div>
77
+ ) : vimeoId ? (
78
+ <div class="dineway-embed-video">
79
+ <iframe
80
+ src={`https://player.vimeo.com/video/${vimeoId}`}
81
+ title="Vimeo video"
82
+ allow="autoplay; fullscreen; picture-in-picture"
83
+ allowfullscreen
84
+ />
85
+ </div>
86
+ ) : html ? (
87
+ <div class="dineway-embed-html" set:html={sanitizeContent(html)} />
88
+ ) : (
89
+ <a href={url} target="_blank" rel="noopener noreferrer">
90
+ {url}
91
+ </a>
92
+ )
93
+ }
94
+ {caption && <figcaption>{caption}</figcaption>}
95
+ </figure>
96
+
97
+ <style>
98
+ .dineway-embed {
99
+ margin: 1.5rem 0;
100
+ }
101
+ .dineway-embed-video {
102
+ position: relative;
103
+ padding-bottom: 56.25%; /* 16:9 */
104
+ height: 0;
105
+ overflow: hidden;
106
+ }
107
+ .dineway-embed-video iframe,
108
+ .dineway-embed-video video {
109
+ position: absolute;
110
+ top: 0;
111
+ left: 0;
112
+ width: 100%;
113
+ height: 100%;
114
+ border: 0;
115
+ }
116
+ .dineway-embed-audio {
117
+ width: 100%;
118
+ }
119
+ .dineway-embed-audio audio {
120
+ width: 100%;
121
+ }
122
+ .dineway-embed figcaption {
123
+ font-size: 0.875rem;
124
+ color: #666;
125
+ margin-top: 0.5rem;
126
+ text-align: center;
127
+ }
128
+ </style>
@@ -0,0 +1,122 @@
1
+ ---
2
+ import { sanitizeHref } from "../utils/url.js";
3
+
4
+ /**
5
+ * Portable Text File block component
6
+ *
7
+ * Renders a downloadable file link from WordPress imports.
8
+ */
9
+ export interface Props {
10
+ node: {
11
+ _type: "file";
12
+ _key: string;
13
+ url: string;
14
+ filename?: string;
15
+ showDownloadButton?: boolean;
16
+ };
17
+ }
18
+
19
+ const { node } = Astro.props;
20
+ const { url: rawUrl, filename, showDownloadButton = true } = node ?? {};
21
+ const url = sanitizeHref(rawUrl);
22
+
23
+ // Extract filename from URL if not provided
24
+ const displayName = filename || url?.split("/").pop()?.split("?")[0] || "Download";
25
+ ---
26
+
27
+ <div class="dineway-file">
28
+ <a href={url} class="dineway-file__link" download={filename}>
29
+ <svg
30
+ class="dineway-file__icon"
31
+ xmlns="http://www.w3.org/2000/svg"
32
+ viewBox="0 0 24 24"
33
+ fill="none"
34
+ stroke="currentColor"
35
+ stroke-width="2"
36
+ stroke-linecap="round"
37
+ stroke-linejoin="round"
38
+ >
39
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
40
+ <polyline points="14 2 14 8 20 8"></polyline>
41
+ </svg>
42
+ <span class="dineway-file__name">{displayName}</span>
43
+ </a>
44
+ {
45
+ showDownloadButton && (
46
+ <a href={url} class="dineway-file__download" download={filename} aria-label="Download file">
47
+ <svg
48
+ xmlns="http://www.w3.org/2000/svg"
49
+ viewBox="0 0 24 24"
50
+ fill="none"
51
+ stroke="currentColor"
52
+ stroke-width="2"
53
+ stroke-linecap="round"
54
+ stroke-linejoin="round"
55
+ >
56
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
57
+ <polyline points="7 10 12 15 17 10" />
58
+ <line x1="12" y1="15" x2="12" y2="3" />
59
+ </svg>
60
+ </a>
61
+ )
62
+ }
63
+ </div>
64
+
65
+ <style>
66
+ .dineway-file {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 0.75rem;
70
+ padding: 1rem;
71
+ margin: 1rem 0;
72
+ background-color: var(--dineway-file-bg, #f5f5f5);
73
+ border-radius: 4px;
74
+ border: 1px solid var(--dineway-file-border, #e0e0e0);
75
+ }
76
+
77
+ .dineway-file__link {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 0.5rem;
81
+ flex: 1;
82
+ text-decoration: none;
83
+ color: var(--dineway-file-color, #333);
84
+ }
85
+
86
+ .dineway-file__link:hover {
87
+ text-decoration: underline;
88
+ }
89
+
90
+ .dineway-file__icon {
91
+ width: 1.5rem;
92
+ height: 1.5rem;
93
+ flex-shrink: 0;
94
+ color: var(--dineway-file-icon-color, #666);
95
+ }
96
+
97
+ .dineway-file__name {
98
+ font-weight: 500;
99
+ word-break: break-all;
100
+ }
101
+
102
+ .dineway-file__download {
103
+ display: flex;
104
+ align-items: center;
105
+ justify-content: center;
106
+ width: 2.5rem;
107
+ height: 2.5rem;
108
+ background-color: var(--dineway-button-bg, #0073aa);
109
+ color: var(--dineway-button-color, #fff);
110
+ border-radius: 4px;
111
+ transition: background-color 0.2s;
112
+ }
113
+
114
+ .dineway-file__download:hover {
115
+ background-color: var(--dineway-button-bg-hover, #005177);
116
+ }
117
+
118
+ .dineway-file__download svg {
119
+ width: 1.25rem;
120
+ height: 1.25rem;
121
+ }
122
+ </style>
@@ -0,0 +1,93 @@
1
+ ---
2
+ /**
3
+ * Portable Text Gallery block component
4
+ *
5
+ * Renders image galleries from WordPress imports.
6
+ * Uses Astro's Image component for optimization when dimensions are available.
7
+ */
8
+ import { Image as AstroImage } from "astro:assets";
9
+
10
+ export interface Props {
11
+ node: {
12
+ _type: "gallery";
13
+ _key: string;
14
+ images: Array<{
15
+ _type: "image";
16
+ _key: string;
17
+ asset: {
18
+ _ref: string;
19
+ url?: string;
20
+ };
21
+ alt?: string;
22
+ caption?: string;
23
+ width?: number;
24
+ height?: number;
25
+ }>;
26
+ columns?: number;
27
+ };
28
+ }
29
+
30
+ const { node } = Astro.props;
31
+ const images = node?.images ?? [];
32
+ const columns = node?.columns ?? 3;
33
+
34
+ if (!images.length) {
35
+ return null;
36
+ }
37
+ ---
38
+
39
+ <div class="dineway-gallery" style={`--columns: ${columns}`}>
40
+ {
41
+ images.map((image) => {
42
+ const src =
43
+ image.asset.url ||
44
+ `/_dineway/api/media/file/${image.asset._ref}`;
45
+ const hasSize = image.width && image.height;
46
+ return (
47
+ <figure class="dineway-gallery-item">
48
+ {hasSize ? (
49
+ <AstroImage
50
+ src={src}
51
+ alt={image.alt || ""}
52
+ width={image.width!}
53
+ height={image.height!}
54
+ layout="constrained"
55
+ />
56
+ ) : (
57
+ <img src={src} alt={image.alt || ""} loading="lazy" decoding="async" />
58
+ )}
59
+ {image.caption && <figcaption>{image.caption}</figcaption>}
60
+ </figure>
61
+ );
62
+ })
63
+ }
64
+ </div>
65
+
66
+ <style>
67
+ .dineway-gallery {
68
+ display: grid;
69
+ grid-template-columns: repeat(var(--columns, 3), 1fr);
70
+ gap: 1rem;
71
+ margin: 1.5rem 0;
72
+ }
73
+ .dineway-gallery-item {
74
+ margin: 0;
75
+ }
76
+ .dineway-gallery-item img {
77
+ width: 100%;
78
+ height: auto;
79
+ object-fit: cover;
80
+ aspect-ratio: 1;
81
+ }
82
+ .dineway-gallery-item figcaption {
83
+ font-size: 0.75rem;
84
+ color: #666;
85
+ margin-top: 0.25rem;
86
+ text-align: center;
87
+ }
88
+ @media (max-width: 640px) {
89
+ .dineway-gallery {
90
+ grid-template-columns: repeat(2, 1fr);
91
+ }
92
+ }
93
+ </style>
@@ -0,0 +1,33 @@
1
+ ---
2
+ /**
3
+ * Portable Text HTML block component
4
+ *
5
+ * Renders raw HTML blocks that couldn't be converted to Portable Text.
6
+ * Sanitizes HTML to prevent XSS attacks.
7
+ */
8
+ import { sanitizeContent } from "../utils/sanitize.js";
9
+
10
+ export interface Props {
11
+ node: {
12
+ _type: "htmlBlock";
13
+ _key: string;
14
+ html: string;
15
+ };
16
+ }
17
+
18
+ const { node } = Astro.props;
19
+
20
+ if (!node?.html) {
21
+ return null;
22
+ }
23
+
24
+ const sanitized = sanitizeContent(node.html);
25
+ ---
26
+
27
+ <div class="dineway-html-block" set:html={sanitized} />
28
+
29
+ <style>
30
+ .dineway-html-block {
31
+ margin: 1.5rem 0;
32
+ }
33
+ </style>
@@ -0,0 +1,178 @@
1
+ ---
2
+ /**
3
+ * Portable Text Image block component
4
+ *
5
+ * Renders image blocks from WordPress imports and Dineway media.
6
+ * Uses the provider's getSrc function for responsive srcset generation.
7
+ */
8
+ import type { ImageEmbed } from "../media/types.js";
9
+ import { getMediaProvider } from "../media/provider-loader.js";
10
+ // Standard responsive breakpoints
11
+ const BREAKPOINTS = [640, 750, 828, 960, 1080, 1280, 1600, 1920];
12
+
13
+ export interface Props {
14
+ node: {
15
+ _type: "image";
16
+ _key: string;
17
+ asset: {
18
+ _ref: string;
19
+ url?: string;
20
+ /** Provider ID for external media (e.g., "image-cdn") */
21
+ provider?: string;
22
+ /** Provider metadata (blurhash, dominantColor, etc.) */
23
+ meta?: Record<string, unknown>;
24
+ };
25
+ alt?: string;
26
+ caption?: string;
27
+ /** Original image width */
28
+ width?: number;
29
+ /** Original image height */
30
+ height?: number;
31
+ /** Display width for this instance (overrides original) */
32
+ displayWidth?: number;
33
+ /** Display height for this instance (overrides original) */
34
+ displayHeight?: number;
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Generate srcset using provider's getSrc function
40
+ */
41
+ function generateSrcset(
42
+ getSrc: NonNullable<ImageEmbed["getSrc"]>,
43
+ maxWidth: number,
44
+ aspectRatio?: number,
45
+ ): string {
46
+ return BREAKPOINTS.filter((w) => w <= maxWidth * 2)
47
+ .map((w) => {
48
+ const h = aspectRatio ? Math.round(w / aspectRatio) : undefined;
49
+ return `${getSrc({ width: w, height: h })} ${w}w`;
50
+ })
51
+ .join(", ");
52
+ }
53
+
54
+ const { node } = Astro.props;
55
+
56
+ if (!node?.asset) {
57
+ return null;
58
+ }
59
+
60
+ const { asset, alt = "", caption, width, height, displayWidth, displayHeight } = node;
61
+
62
+ // Calculate aspect ratio from original dimensions
63
+ const aspectRatio = width && height ? width / height : undefined;
64
+
65
+ // Calculate render dimensions
66
+ let renderWidth: number | undefined;
67
+ let renderHeight: number | undefined;
68
+
69
+ if (displayWidth && displayHeight) {
70
+ renderWidth = displayWidth;
71
+ renderHeight = displayHeight;
72
+ } else if (displayWidth && aspectRatio) {
73
+ renderWidth = displayWidth;
74
+ renderHeight = Math.round(displayWidth / aspectRatio);
75
+ } else if (displayHeight && aspectRatio) {
76
+ renderWidth = Math.round(displayHeight * aspectRatio);
77
+ renderHeight = displayHeight;
78
+ } else {
79
+ renderWidth = width;
80
+ renderHeight = height;
81
+ }
82
+
83
+ // Get the image source URL and srcset
84
+ let src = "";
85
+ let srcset: string | undefined;
86
+ let sizes: string | undefined;
87
+ const providerId = asset.provider;
88
+
89
+ if (providerId && providerId !== "local") {
90
+ // External provider
91
+ const provider = await getMediaProvider(providerId);
92
+ if (provider) {
93
+ try {
94
+ const mediaValue = {
95
+ provider: providerId,
96
+ id: asset._ref,
97
+ width: renderWidth,
98
+ height: renderHeight,
99
+ alt,
100
+ };
101
+ const result = provider.getEmbed(mediaValue, {
102
+ width: renderWidth,
103
+ height: renderHeight,
104
+ });
105
+ const embed = result instanceof Promise ? await result : result;
106
+ if (embed.type === "image") {
107
+ src = embed.src;
108
+
109
+ // Generate srcset if provider supports dynamic sizing
110
+ if (embed.getSrc) {
111
+ const maxWidth = renderWidth || 1200;
112
+ const ar = renderWidth && renderHeight ? renderWidth / renderHeight : aspectRatio;
113
+ srcset = generateSrcset(embed.getSrc, maxWidth, ar);
114
+ sizes = renderWidth
115
+ ? `(min-width: ${renderWidth}px) ${renderWidth}px, 100vw`
116
+ : "100vw";
117
+ }
118
+ }
119
+ } catch (error) {
120
+ console.warn(`Failed to get embed for image ${asset._ref}:`, error);
121
+ }
122
+ }
123
+ }
124
+
125
+ // Fallback for local provider — prefer stored URL (includes storage key with extension),
126
+ // fall back to _ref (bare ULID, works if media file endpoint supports ID lookup)
127
+ if (!src) {
128
+ src = asset.url || `/_dineway/api/media/file/${asset._ref}`;
129
+ }
130
+
131
+ // Build placeholder background style
132
+ const blurhash = asset.meta?.blurhash as string | undefined;
133
+ const dominantColor = asset.meta?.dominantColor as string | undefined;
134
+
135
+ let placeholderStyle = "";
136
+ if (blurhash) {
137
+ const { blurhashToImageCssString } = await import("@unpic/placeholder");
138
+ placeholderStyle = blurhashToImageCssString(blurhash);
139
+ } else if (dominantColor) {
140
+ placeholderStyle = `background-color: ${dominantColor};`;
141
+ }
142
+
143
+ const baseStyle = aspectRatio
144
+ ? `aspect-ratio: ${aspectRatio}; max-width: 100%; height: auto;`
145
+ : "max-width: 100%; height: auto;";
146
+ const imgStyle = placeholderStyle ? `${baseStyle} ${placeholderStyle}` : baseStyle;
147
+ ---
148
+
149
+ <figure class="dineway-image">
150
+ <img
151
+ src={src}
152
+ srcset={srcset}
153
+ sizes={sizes}
154
+ alt={alt}
155
+ width={renderWidth}
156
+ height={renderHeight}
157
+ loading="lazy"
158
+ decoding="async"
159
+ style={imgStyle}
160
+ />
161
+ {caption && <figcaption>{caption}</figcaption>}
162
+ </figure>
163
+
164
+ <style>
165
+ .dineway-image {
166
+ margin: 1.5rem 0;
167
+ }
168
+ .dineway-image img {
169
+ max-width: 100%;
170
+ height: auto;
171
+ }
172
+ .dineway-image figcaption {
173
+ font-size: 0.875rem;
174
+ color: #666;
175
+ margin-top: 0.5rem;
176
+ text-align: center;
177
+ }
178
+ </style>
@@ -0,0 +1,27 @@
1
+ ---
2
+ /**
3
+ * Wrapper for the inline Portable Text editor.
4
+ *
5
+ * This exists as a separate .astro component so that the static import
6
+ * of the React component is visible to Astro's build pipeline — required
7
+ * for `client:load` to find a matching renderer.
8
+ */
9
+ import { InlinePortableTextEditor } from "./InlinePortableTextEditor";
10
+
11
+ export interface Props {
12
+ value: unknown[];
13
+ collection: string;
14
+ entryId: string;
15
+ field: string;
16
+ }
17
+
18
+ const { value, collection, entryId, field } = Astro.props;
19
+ ---
20
+
21
+ <InlinePortableTextEditor
22
+ client:only="react"
23
+ value={value}
24
+ collection={collection}
25
+ entryId={entryId}
26
+ field={field}
27
+ />