@byline/cli 0.1.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.
Files changed (251) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +23 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +72 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/doctor.d.ts +2 -0
  8. package/dist/commands/doctor.d.ts.map +1 -0
  9. package/dist/commands/doctor.js +36 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/init.d.ts +16 -0
  12. package/dist/commands/init.d.ts.map +1 -0
  13. package/dist/commands/init.js +76 -0
  14. package/dist/commands/init.js.map +1 -0
  15. package/dist/context.d.ts +38 -0
  16. package/dist/context.d.ts.map +1 -0
  17. package/dist/context.js +37 -0
  18. package/dist/context.js.map +1 -0
  19. package/dist/index.d.ts +5 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +4 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/lib/pg-url.d.ts +11 -0
  24. package/dist/lib/pg-url.d.ts.map +1 -0
  25. package/dist/lib/pg-url.js +22 -0
  26. package/dist/lib/pg-url.js.map +1 -0
  27. package/dist/manifest/deps.d.ts +29 -0
  28. package/dist/manifest/deps.d.ts.map +1 -0
  29. package/dist/manifest/deps.js +97 -0
  30. package/dist/manifest/deps.js.map +1 -0
  31. package/dist/manifest/env.d.ts +18 -0
  32. package/dist/manifest/env.d.ts.map +1 -0
  33. package/dist/manifest/env.js +38 -0
  34. package/dist/manifest/env.js.map +1 -0
  35. package/dist/phases/db-init.d.ts +3 -0
  36. package/dist/phases/db-init.d.ts.map +1 -0
  37. package/dist/phases/db-init.js +163 -0
  38. package/dist/phases/db-init.js.map +1 -0
  39. package/dist/phases/db.d.ts +11 -0
  40. package/dist/phases/db.d.ts.map +1 -0
  41. package/dist/phases/db.js +93 -0
  42. package/dist/phases/db.js.map +1 -0
  43. package/dist/phases/deps.d.ts +3 -0
  44. package/dist/phases/deps.d.ts.map +1 -0
  45. package/dist/phases/deps.js +115 -0
  46. package/dist/phases/deps.js.map +1 -0
  47. package/dist/phases/env.d.ts +3 -0
  48. package/dist/phases/env.d.ts.map +1 -0
  49. package/dist/phases/env.js +172 -0
  50. package/dist/phases/env.js.map +1 -0
  51. package/dist/phases/host.d.ts +3 -0
  52. package/dist/phases/host.d.ts.map +1 -0
  53. package/dist/phases/host.js +99 -0
  54. package/dist/phases/host.js.map +1 -0
  55. package/dist/phases/index.d.ts +7 -0
  56. package/dist/phases/index.d.ts.map +1 -0
  57. package/dist/phases/index.js +40 -0
  58. package/dist/phases/index.js.map +1 -0
  59. package/dist/phases/preflight.d.ts +4 -0
  60. package/dist/phases/preflight.d.ts.map +1 -0
  61. package/dist/phases/preflight.js +81 -0
  62. package/dist/phases/preflight.js.map +1 -0
  63. package/dist/phases/routes.d.ts +3 -0
  64. package/dist/phases/routes.d.ts.map +1 -0
  65. package/dist/phases/routes.js +145 -0
  66. package/dist/phases/routes.js.map +1 -0
  67. package/dist/phases/scaffold.d.ts +3 -0
  68. package/dist/phases/scaffold.d.ts.map +1 -0
  69. package/dist/phases/scaffold.js +113 -0
  70. package/dist/phases/scaffold.js.map +1 -0
  71. package/dist/phases/stub.d.ts +3 -0
  72. package/dist/phases/stub.d.ts.map +1 -0
  73. package/dist/phases/stub.js +25 -0
  74. package/dist/phases/stub.js.map +1 -0
  75. package/dist/phases/ui.d.ts +3 -0
  76. package/dist/phases/ui.d.ts.map +1 -0
  77. package/dist/phases/ui.js +93 -0
  78. package/dist/phases/ui.js.map +1 -0
  79. package/dist/phases/wire/index.d.ts +3 -0
  80. package/dist/phases/wire/index.d.ts.map +1 -0
  81. package/dist/phases/wire/index.js +67 -0
  82. package/dist/phases/wire/index.js.map +1 -0
  83. package/dist/phases/wire/root-tsx.d.ts +3 -0
  84. package/dist/phases/wire/root-tsx.d.ts.map +1 -0
  85. package/dist/phases/wire/root-tsx.js +57 -0
  86. package/dist/phases/wire/root-tsx.js.map +1 -0
  87. package/dist/phases/wire/server-ts.d.ts +3 -0
  88. package/dist/phases/wire/server-ts.d.ts.map +1 -0
  89. package/dist/phases/wire/server-ts.js +54 -0
  90. package/dist/phases/wire/server-ts.js.map +1 -0
  91. package/dist/phases/wire/shared.d.ts +34 -0
  92. package/dist/phases/wire/shared.d.ts.map +1 -0
  93. package/dist/phases/wire/shared.js +2 -0
  94. package/dist/phases/wire/shared.js.map +1 -0
  95. package/dist/phases/wire/start-ts.d.ts +3 -0
  96. package/dist/phases/wire/start-ts.d.ts.map +1 -0
  97. package/dist/phases/wire/start-ts.js +149 -0
  98. package/dist/phases/wire/start-ts.js.map +1 -0
  99. package/dist/phases/wire/tsconfig.d.ts +3 -0
  100. package/dist/phases/wire/tsconfig.d.ts.map +1 -0
  101. package/dist/phases/wire/tsconfig.js +105 -0
  102. package/dist/phases/wire/tsconfig.js.map +1 -0
  103. package/dist/phases/wire/vite-config.d.ts +3 -0
  104. package/dist/phases/wire/vite-config.d.ts.map +1 -0
  105. package/dist/phases/wire/vite-config.js +46 -0
  106. package/dist/phases/wire/vite-config.js.map +1 -0
  107. package/dist/prompts.d.ts +34 -0
  108. package/dist/prompts.d.ts.map +1 -0
  109. package/dist/prompts.js +49 -0
  110. package/dist/prompts.js.map +1 -0
  111. package/dist/runner.d.ts +5 -0
  112. package/dist/runner.d.ts.map +1 -0
  113. package/dist/runner.js +91 -0
  114. package/dist/runner.js.map +1 -0
  115. package/dist/state.d.ts +18 -0
  116. package/dist/state.d.ts.map +1 -0
  117. package/dist/state.js +68 -0
  118. package/dist/state.js.map +1 -0
  119. package/dist/templates/byline/admin.config.ts +41 -0
  120. package/dist/templates/byline/i18n.ts +47 -0
  121. package/dist/templates/byline/routes.ts +28 -0
  122. package/dist/templates/byline/seed.ts +19 -0
  123. package/dist/templates/byline/seeds/admin.ts +62 -0
  124. package/dist/templates/byline/server.config.ts +92 -0
  125. package/dist/templates/byline-examples/admin.config.ts +74 -0
  126. package/dist/templates/byline-examples/blocks/photo-block.ts +59 -0
  127. package/dist/templates/byline-examples/blocks/richtext-block.ts +35 -0
  128. package/dist/templates/byline-examples/collections/doc-example-flat-locale-all.ts +373 -0
  129. package/dist/templates/byline-examples/collections/doc-example-flat-locale-en.ts +283 -0
  130. package/dist/templates/byline-examples/collections/doc-example-tree-locale-all.ts +278 -0
  131. package/dist/templates/byline-examples/collections/doc-example-tree-locale-en.ts +205 -0
  132. package/dist/templates/byline-examples/collections/docs/admin.tsx +204 -0
  133. package/dist/templates/byline-examples/collections/docs/components/.gitkeep +0 -0
  134. package/dist/templates/byline-examples/collections/docs/components/feature-formatter.tsx +10 -0
  135. package/dist/templates/byline-examples/collections/docs/hooks/.gitkeep +0 -0
  136. package/dist/templates/byline-examples/collections/docs/index.ts +10 -0
  137. package/dist/templates/byline-examples/collections/docs/schema.ts +209 -0
  138. package/dist/templates/byline-examples/collections/docs-categories/admin.tsx +78 -0
  139. package/dist/templates/byline-examples/collections/docs-categories/components/.gitkeep +0 -0
  140. package/dist/templates/byline-examples/collections/docs-categories/hooks/.gitkeep +0 -0
  141. package/dist/templates/byline-examples/collections/docs-categories/index.ts +10 -0
  142. package/dist/templates/byline-examples/collections/docs-categories/schema.ts +33 -0
  143. package/dist/templates/byline-examples/collections/media/admin.tsx +188 -0
  144. package/dist/templates/byline-examples/collections/media/components/media-list-view.tsx +330 -0
  145. package/dist/templates/byline-examples/collections/media/components/media-thumbnail.tsx +63 -0
  146. package/dist/templates/byline-examples/collections/media/hooks/.gitkeep +0 -0
  147. package/dist/templates/byline-examples/collections/media/index.ts +10 -0
  148. package/dist/templates/byline-examples/collections/media/schema.ts +157 -0
  149. package/dist/templates/byline-examples/collections/news/admin.tsx +192 -0
  150. package/dist/templates/byline-examples/collections/news/components/.gitkeep +0 -0
  151. package/dist/templates/byline-examples/collections/news/hooks/.gitkeep +0 -0
  152. package/dist/templates/byline-examples/collections/news/index.ts +10 -0
  153. package/dist/templates/byline-examples/collections/news/schema.ts +91 -0
  154. package/dist/templates/byline-examples/collections/news-categories/admin.tsx +78 -0
  155. package/dist/templates/byline-examples/collections/news-categories/components/.gitkeep +0 -0
  156. package/dist/templates/byline-examples/collections/news-categories/hooks/.gitkeep +0 -0
  157. package/dist/templates/byline-examples/collections/news-categories/index.ts +10 -0
  158. package/dist/templates/byline-examples/collections/news-categories/schema.ts +33 -0
  159. package/dist/templates/byline-examples/collections/pages/admin.tsx +183 -0
  160. package/dist/templates/byline-examples/collections/pages/components/.gitkeep +0 -0
  161. package/dist/templates/byline-examples/collections/pages/hooks/.gitkeep +0 -0
  162. package/dist/templates/byline-examples/collections/pages/index.ts +10 -0
  163. package/dist/templates/byline-examples/collections/pages/schema.ts +96 -0
  164. package/dist/templates/byline-examples/components/length-indicator.tsx +138 -0
  165. package/dist/templates/byline-examples/components/pill.tsx +38 -0
  166. package/dist/templates/byline-examples/components/summary-length.tsx +39 -0
  167. package/dist/templates/byline-examples/fields/available-languages-field.ts +90 -0
  168. package/dist/templates/byline-examples/fields/lexical-richtext-compact.ts +88 -0
  169. package/dist/templates/byline-examples/i18n.ts +47 -0
  170. package/dist/templates/byline-examples/routes.ts +28 -0
  171. package/dist/templates/byline-examples/scripts/regenerate-media.ts +275 -0
  172. package/dist/templates/byline-examples/seed.ts +25 -0
  173. package/dist/templates/byline-examples/seeds/admin.ts +62 -0
  174. package/dist/templates/byline-examples/seeds/doc-categories.ts +71 -0
  175. package/dist/templates/byline-examples/seeds/docs.ts +293 -0
  176. package/dist/templates/byline-examples/seeds/news-categories.ts +71 -0
  177. package/dist/templates/byline-examples/server.config.ts +179 -0
  178. package/dist/templates/host/vite.config.ts +41 -0
  179. package/dist/templates/migrations/0000_condemned_kronos.sql +324 -0
  180. package/dist/templates/migrations/0001_sudden_phantom_reporter.sql +1 -0
  181. package/dist/templates/migrations/meta/0000_snapshot.json +2793 -0
  182. package/dist/templates/migrations/meta/0001_snapshot.json +2799 -0
  183. package/dist/templates/migrations/meta/_journal.json +20 -0
  184. package/dist/templates/routes/(byline)/admin/account/index.tsx +11 -0
  185. package/dist/templates/routes/(byline)/admin/collections/$collection/$id/api.tsx +16 -0
  186. package/dist/templates/routes/(byline)/admin/collections/$collection/$id/history.tsx +19 -0
  187. package/dist/templates/routes/(byline)/admin/collections/$collection/$id/index.tsx +16 -0
  188. package/dist/templates/routes/(byline)/admin/collections/$collection/create.tsx +11 -0
  189. package/dist/templates/routes/(byline)/admin/collections/$collection/index.tsx +11 -0
  190. package/dist/templates/routes/(byline)/admin/index.tsx +11 -0
  191. package/dist/templates/routes/(byline)/admin/permissions/index.tsx +11 -0
  192. package/dist/templates/routes/(byline)/admin/roles/$id/index.tsx +11 -0
  193. package/dist/templates/routes/(byline)/admin/roles/index.tsx +11 -0
  194. package/dist/templates/routes/(byline)/admin/route.tsx +11 -0
  195. package/dist/templates/routes/(byline)/admin/users/$id/index.tsx +11 -0
  196. package/dist/templates/routes/(byline)/admin/users/index.tsx +11 -0
  197. package/dist/templates/routes/(byline)/sign-in.tsx +11 -0
  198. package/dist/templates/ui-byline/blocks/photo-block/index.tsx +80 -0
  199. package/dist/templates/ui-byline/blocks/richtext-block/index.tsx +46 -0
  200. package/dist/templates/ui-byline/components/admonition/index.tsx +40 -0
  201. package/dist/templates/ui-byline/components/code/code-serializer.tsx +20 -0
  202. package/dist/templates/ui-byline/components/code/code.tsx +50 -0
  203. package/dist/templates/ui-byline/components/code/index.module.scss +137 -0
  204. package/dist/templates/ui-byline/components/code/index.ts +2 -0
  205. package/dist/templates/ui-byline/components/code/types.ts +5 -0
  206. package/dist/templates/ui-byline/components/code/utils.ts +20 -0
  207. package/dist/templates/ui-byline/components/heading-anchor/heading-anchor.tsx +69 -0
  208. package/dist/templates/ui-byline/components/heading-anchor/index.ts +1 -0
  209. package/dist/templates/ui-byline/components/heading-anchor/utils.ts +15 -0
  210. package/dist/templates/ui-byline/components/inline-image/index.tsx +109 -0
  211. package/dist/templates/ui-byline/components/layout/index.tsx +63 -0
  212. package/dist/templates/ui-byline/components/link/lang-link.tsx +70 -0
  213. package/dist/templates/ui-byline/components/link/link-field.tsx +298 -0
  214. package/dist/templates/ui-byline/components/link/link-lexical.tsx +191 -0
  215. package/dist/templates/ui-byline/components/list/index.ts +2 -0
  216. package/dist/templates/ui-byline/components/list/list-item.tsx +32 -0
  217. package/dist/templates/ui-byline/components/list/list.tsx +17 -0
  218. package/dist/templates/ui-byline/components/responsive-image/index.tsx +205 -0
  219. package/dist/templates/ui-byline/components/richtext-lexical/index.tsx +31 -0
  220. package/dist/templates/ui-byline/components/richtext-lexical/serialize/index.tsx +249 -0
  221. package/dist/templates/ui-byline/components/richtext-lexical/serialize/richtext-node-formats.ts +66 -0
  222. package/dist/templates/ui-byline/components/richtext-lexical/serialize/types.ts +48 -0
  223. package/dist/templates/ui-byline/components/richtext-lexical/serialize/utils.ts +15 -0
  224. package/dist/templates/ui-byline/components/table-cell/index.tsx +36 -0
  225. package/dist/templates/ui-byline/components/vimeo/index.tsx +21 -0
  226. package/dist/templates/ui-byline/components/youtube/index.tsx +22 -0
  227. package/dist/templates/ui-byline/render-blocks.tsx +71 -0
  228. package/dist/templates/ui-byline/types/i18n.ts +14 -0
  229. package/dist/templates/ui-byline/utils/image-sources.ts +102 -0
  230. package/dist/templates/ui-byline/utils/to-kebab-case.ts +5 -0
  231. package/dist/types.d.ts +54 -0
  232. package/dist/types.d.ts.map +1 -0
  233. package/dist/types.js +2 -0
  234. package/dist/types.js.map +1 -0
  235. package/dist/ui/diff.d.ts +4 -0
  236. package/dist/ui/diff.d.ts.map +1 -0
  237. package/dist/ui/diff.js +23 -0
  238. package/dist/ui/diff.js.map +1 -0
  239. package/dist/ui/grid.d.ts +7 -0
  240. package/dist/ui/grid.d.ts.map +1 -0
  241. package/dist/ui/grid.js +24 -0
  242. package/dist/ui/grid.js.map +1 -0
  243. package/dist/ui/logger.d.ts +14 -0
  244. package/dist/ui/logger.d.ts.map +1 -0
  245. package/dist/ui/logger.js +30 -0
  246. package/dist/ui/logger.js.map +1 -0
  247. package/dist/ui/snippet.d.ts +2 -0
  248. package/dist/ui/snippet.d.ts.map +1 -0
  249. package/dist/ui/snippet.js +7 -0
  250. package/dist/ui/snippet.js.map +1 -0
  251. package/package.json +69 -0
@@ -0,0 +1,191 @@
1
+ 'use client'
2
+
3
+ import type React from 'react'
4
+
5
+ import cx from 'classnames'
6
+
7
+ import { LangLink } from '@/ui/byline/components/link/lang-link'
8
+ import type { Locale } from '@/ui/byline/types/i18n'
9
+
10
+ // import { getPublicWebsiteUrl } from '@/utils/utils.framework.ts'
11
+
12
+ export interface LinkAttributes {
13
+ linkType?: 'custom' | 'internal'
14
+ newTab?: boolean
15
+ nofollow?: boolean
16
+ rel?: string
17
+ url?: string
18
+ doc?: {
19
+ value: string
20
+ relationTo: string
21
+ data: {
22
+ id: string
23
+ title: string
24
+ slug: string
25
+ area: string
26
+ collectionAlias: string
27
+ }
28
+ }
29
+ }
30
+
31
+ export type LinkType = 'internal' | 'custom'
32
+
33
+ export interface LinkLexicalProps {
34
+ attributes: LinkAttributes
35
+ lng: Locale
36
+ className?: string
37
+ onMouseEnter?: () => void
38
+ onMouseLeave?: () => void
39
+ children?: React.ReactNode
40
+ }
41
+
42
+ export function manageRel(input: string, action: 'add' | 'remove', value: string): string {
43
+ let result: string
44
+ let mutableInput = `${input}`
45
+ if (action === 'add') {
46
+ // if we somehow got out of sync - clean up
47
+ if (mutableInput.includes(value)) {
48
+ const re = new RegExp(value, 'g')
49
+ mutableInput = mutableInput.replace(re, '').trim()
50
+ }
51
+ mutableInput = mutableInput.trim()
52
+ result = mutableInput.length === 0 ? `${value}` : `${mutableInput} ${value}`
53
+ } else {
54
+ const re = new RegExp(value, 'g')
55
+ result = mutableInput.replace(re, '').trim()
56
+ }
57
+ return result
58
+ }
59
+
60
+ function getHref(args: LinkAttributes): string {
61
+ let href = ''
62
+ const publicWebsiteUrl = '/' // getPublicWebsiteUrl()
63
+ const { linkType, url } = args
64
+
65
+ if ((linkType === 'custom' || linkType === undefined) && url != null) {
66
+ href = url
67
+ } else if (
68
+ linkType === 'internal' &&
69
+ args.doc?.relationTo != null &&
70
+ args.doc?.data?.slug != null
71
+ ) {
72
+ const collection = args.doc.relationTo
73
+ const { slug, area, collectionAlias } = args.doc.data
74
+ if (collectionAlias != null) {
75
+ // The alias might be for the root
76
+ if (collectionAlias.length === 0) {
77
+ href = `/${slug}`
78
+ } else {
79
+ href = `/${collectionAlias}/${slug}`
80
+ }
81
+ } else {
82
+ href = `/${collection}/${slug}`
83
+ }
84
+
85
+ if (area != null && area.length > 0 && area !== 'root') {
86
+ href = `/${area}${href}`
87
+ }
88
+ }
89
+
90
+ const hrefIsLocal = ['tel:', 'mailto:', '/'].some((prefix) => href.startsWith(prefix))
91
+ if (!hrefIsLocal) {
92
+ try {
93
+ const objectURL = new URL(href)
94
+ if (objectURL.origin === publicWebsiteUrl) {
95
+ href = objectURL.href.replace(publicWebsiteUrl, '')
96
+ }
97
+ } catch (e) {
98
+ console.error(`Failed to format url: ${href}`, e) // eslint-disable-line no-console
99
+ }
100
+ }
101
+
102
+ return href
103
+ }
104
+
105
+ function getAdditionalProps(
106
+ args: LinkAttributes,
107
+ href: string
108
+ ): {
109
+ rel: string | undefined
110
+ target: string | undefined
111
+ } {
112
+ const additionalProps: {
113
+ rel: string | undefined
114
+ target: string | undefined
115
+ } = {
116
+ rel: undefined,
117
+ target: undefined,
118
+ }
119
+
120
+ let rel = ''
121
+ if (args.nofollow === true) rel = manageRel(rel, 'add', 'nofollow')
122
+ if (args.newTab === true) rel = manageRel(rel, 'add', 'noopener')
123
+ additionalProps.rel = rel
124
+
125
+ if (args.newTab === true) {
126
+ additionalProps.target = '_blank'
127
+ }
128
+
129
+ if (!href.startsWith('/')) {
130
+ additionalProps.target = '_blank'
131
+ }
132
+
133
+ if (additionalProps.rel == null || additionalProps.rel.length === 0) delete additionalProps.rel
134
+ if (additionalProps.target == null) delete additionalProps.target
135
+
136
+ return additionalProps
137
+ }
138
+
139
+ export function LinkLexicalSerializer({
140
+ attributes,
141
+ lng,
142
+ className,
143
+ onMouseEnter,
144
+ onMouseLeave,
145
+ children,
146
+ }: LinkLexicalProps): React.JSX.Element {
147
+ const href = getHref(attributes)
148
+ const additionalProps = getAdditionalProps(attributes, href)
149
+
150
+ if (href.startsWith('/')) {
151
+ return (
152
+ <LangLink
153
+ lng={lng}
154
+ to={href}
155
+ {...additionalProps}
156
+ className={cx(className, 'underline')}
157
+ onMouseEnter={onMouseEnter}
158
+ onMouseLeave={onMouseLeave}
159
+ >
160
+ {children}
161
+ </LangLink>
162
+ )
163
+ }
164
+ return (
165
+ <a
166
+ href={href}
167
+ {...additionalProps}
168
+ className={cx(className, 'underline')}
169
+ onMouseEnter={onMouseEnter}
170
+ onMouseLeave={onMouseLeave}
171
+ >
172
+ <span className="underline">{children}</span>
173
+ <span style={{ display: 'inline', whiteSpace: 'nowrap' }}>
174
+ &#x202F;
175
+ <svg
176
+ xmlns="http://www.w3.org/2000/svg"
177
+ className="fill-[#001827] dark:fill-gray-50"
178
+ style={{ display: 'inline' }}
179
+ focusable="false"
180
+ aria-hidden="true"
181
+ height="14px"
182
+ width="14px"
183
+ viewBox="0 0 24 24"
184
+ >
185
+ <path d="M0 0h24v24H0V0z" fill="none" />
186
+ <path d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" />
187
+ </svg>
188
+ </span>
189
+ </a>
190
+ )
191
+ }
@@ -0,0 +1,2 @@
1
+ export * from './list.tsx'
2
+ export * from './list-item.tsx'
@@ -0,0 +1,32 @@
1
+ 'use client'
2
+
3
+ import type React from 'react'
4
+
5
+ import type { SerializedLexicalNode } from '../richtext-lexical/serialize/types.ts'
6
+
7
+ export function ListItemSerializer({
8
+ node,
9
+ children,
10
+ }: {
11
+ node: SerializedLexicalNode
12
+ children: React.ReactNode
13
+ }): React.JSX.Element {
14
+ if (node?.checked != null) {
15
+ return (
16
+ <li
17
+ className={`not-prose component--list-item-checkbox ${
18
+ node.checked === true
19
+ ? 'component--list-item-checkbox-checked'
20
+ : 'component--list-item-checked-unchecked'
21
+ }`}
22
+ value={node?.value}
23
+ // aria-checked={node.checked === true ? 'true' : 'false'}
24
+ tabIndex={-1}
25
+ >
26
+ {children}
27
+ </li>
28
+ )
29
+ } else {
30
+ return <li value={node?.value}>{children}</li>
31
+ }
32
+ }
@@ -0,0 +1,17 @@
1
+ 'use client'
2
+
3
+ import type React from 'react'
4
+
5
+ import type { SerializedLexicalNode } from '../richtext-lexical/serialize/types.ts'
6
+
7
+ export function ListSerializer({
8
+ node,
9
+ children,
10
+ }: {
11
+ node: SerializedLexicalNode
12
+ children: React.ReactNode
13
+ }): React.JSX.Element {
14
+ type List = Extract<keyof React.JSX.IntrinsicElements, 'ul' | 'ol'>
15
+ const Tag = node?.tag as List
16
+ return <Tag className={node?.listType}>{children}</Tag>
17
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ 'use client'
10
+
11
+ import type { CSSProperties } from 'react'
12
+
13
+ import type { StoredFileValue } from '@byline/core'
14
+ import cx from 'classnames'
15
+
16
+ import {
17
+ getVariant,
18
+ getVariantSrcSet,
19
+ hasVariantFormat,
20
+ VARIANT_MIME,
21
+ type VariantFormat,
22
+ } from '@/ui/byline/utils/image-sources'
23
+
24
+ /**
25
+ * Responsive `<picture>` driven by Byline's image-field upload value.
26
+ *
27
+ * Collection-agnostic — the only data dependency is `StoredFileValue`,
28
+ * the per-field upload metadata that any `image` field carries. So a
29
+ * caller hands in `media.image` from a populated relation, an avatar's
30
+ * `image` field, an inline upload — whatever — and gets back AVIF /
31
+ * WebP `<source>` srcSets plus a sensible fallback.
32
+ *
33
+ * Source order is **AVIF first, WebP second** — browsers walk the
34
+ * sources in document order and pick the first one whose `type` they
35
+ * support. Modern browsers fetch the AVIF; older ones drop to WebP;
36
+ * pre-WebP browsers fall through to the `<img>` tag. Each `<source>`
37
+ * is only emitted when at least one variant in that format exists, so
38
+ * legacy media items that only have one of the formats still render
39
+ * correctly.
40
+ *
41
+ * `size` caps the variants included in the srcSet and provides a
42
+ * default `sizes` hint:
43
+ *
44
+ * - `large` — every variant; fills the viewport.
45
+ * - `medium` — caps at the `tablet` variant (≤ 1280w).
46
+ * - `small` — caps at the `mobile` variant (≤ 768w); ideal for list
47
+ * thumbnails inside a CSS grid.
48
+ */
49
+ export interface ResponsiveImageProps {
50
+ /** Upload value — typically `<doc>.image` from a populated relation. */
51
+ image: StoredFileValue | undefined | null
52
+ /** Width cap for variants in the srcSet. */
53
+ size?: 'large' | 'medium' | 'small'
54
+ /**
55
+ * Layout-context hint set by a parent that constrains the image's
56
+ * rendered width on desktop (e.g. a 50%-column block, a floated
57
+ * inline image). Reduces the default `sizes` hint so the browser
58
+ * picks a smaller variant from the srcSet — no visual change, just
59
+ * fewer downloaded bytes.
60
+ *
61
+ * Override entirely by passing `sizes` instead.
62
+ */
63
+ constrainedLayout?: boolean
64
+ /**
65
+ * Explicit CSS `sizes` hint passed to the `<source>`. Overrides the
66
+ * `size` / `constrainedLayout` defaults. Pass this when the rendered
67
+ * width does not match either heuristic.
68
+ */
69
+ sizes?: string
70
+ alt?: string
71
+ /** Applied to the wrapping `<picture>`. */
72
+ className?: string
73
+ /** Applied to the inner `<img>`. */
74
+ imgClassName?: string
75
+ /**
76
+ * Mobile bleed-to-edge — negative gutters on mobile, normal flow on
77
+ * desktop. Useful for hero/photo blocks rendered inside a constrained
78
+ * article layout.
79
+ */
80
+ bleedOnMobile?: boolean
81
+ loading?: 'lazy' | 'eager'
82
+ fetchPriority?: 'high' | 'low' | 'auto'
83
+ /**
84
+ * Variant-name fallback chain for the `<img src>`. The browser may
85
+ * paint this while the responsive srcSet resolves, and uses it as
86
+ * the absolute fallback. Default prefers the smallest variant
87
+ * available.
88
+ */
89
+ fallback?: string[]
90
+ style?: CSSProperties
91
+ }
92
+
93
+ /**
94
+ * Default `sizes` hints by `size` × `constrainedLayout`. The
95
+ * constrained column halves the desktop viewport portion; mobile
96
+ * always assumes the image fills the viewport (most layouts stack on
97
+ * narrow screens). Each cell is a CSS `sizes` string.
98
+ */
99
+ const SIZES_TABLE: Record<
100
+ 'large' | 'medium' | 'small',
101
+ Record<'default' | 'constrained', string>
102
+ > = {
103
+ large: {
104
+ default: '100vw',
105
+ constrained: '(min-width: 768px) 50vw, 100vw',
106
+ },
107
+ medium: {
108
+ default: '(min-width: 768px) 50vw, 100vw',
109
+ constrained: '(min-width: 768px) 25vw, 100vw',
110
+ },
111
+ small: {
112
+ default: '(min-width: 768px) 33vw, 50vw',
113
+ constrained: '(min-width: 768px) 20vw, 50vw',
114
+ },
115
+ }
116
+
117
+ const SRC_SET_CAP: Record<'large' | 'medium' | 'small', 'auto' | 'medium' | 'small'> = {
118
+ large: 'auto',
119
+ medium: 'medium',
120
+ small: 'small',
121
+ }
122
+
123
+ const DEFAULT_FALLBACK_CHAIN = ['thumbnail', 'card', 'mobile']
124
+
125
+ export function ResponsiveImage({
126
+ image,
127
+ size = 'large',
128
+ constrainedLayout = false,
129
+ sizes,
130
+ alt = '',
131
+ className,
132
+ imgClassName,
133
+ bleedOnMobile = false,
134
+ loading = 'lazy',
135
+ fetchPriority,
136
+ fallback = DEFAULT_FALLBACK_CHAIN,
137
+ style,
138
+ }: ResponsiveImageProps): React.JSX.Element | null {
139
+ if (image?.storageUrl == null) return null
140
+
141
+ const pictureClasses = cx(
142
+ 'flex not-prose overflow-hidden',
143
+ bleedOnMobile
144
+ ? '-ml-[18px] -mr-[18px] max-w-[calc(100%+36px)] sm:mx-0 sm:w-full'
145
+ : 'mx-0 w-full',
146
+ className
147
+ )
148
+
149
+ // SVG bypass — variants are not generated for SVG; render the original.
150
+ if (image.mimeType === 'image/svg+xml') {
151
+ return (
152
+ <picture className={pictureClasses}>
153
+ <img
154
+ className={cx('not-prose', imgClassName)}
155
+ style={style}
156
+ src={image.storageUrl}
157
+ alt={alt}
158
+ width={image.imageWidth ?? undefined}
159
+ height={image.imageHeight ?? undefined}
160
+ loading={loading}
161
+ fetchPriority={fetchPriority}
162
+ />
163
+ </picture>
164
+ )
165
+ }
166
+
167
+ const cap = SRC_SET_CAP[size]
168
+ // AVIF declared first so supporting browsers pick it; WebP follows
169
+ // for older clients. Each source is suppressed when the upload has
170
+ // no variants in that format.
171
+ const sourceFormats: VariantFormat[] = ['avif', 'webp']
172
+ const sources = sourceFormats
173
+ .filter((format) => hasVariantFormat(image, format))
174
+ .map((format) => ({ format, srcSet: getVariantSrcSet(image, format, cap) }))
175
+ .filter((entry) => entry.srcSet.length > 0)
176
+
177
+ const fallbackSrc =
178
+ fallback
179
+ .map((name) => getVariant(image, name)?.storageUrl)
180
+ .find((url): url is string => url != null) ?? image.storageUrl
181
+ const resolvedSizes = sizes ?? SIZES_TABLE[size][constrainedLayout ? 'constrained' : 'default']
182
+
183
+ return (
184
+ <picture className={pictureClasses}>
185
+ {sources.map(({ format, srcSet }) => (
186
+ <source
187
+ key={format}
188
+ srcSet={srcSet.join(', ')}
189
+ type={VARIANT_MIME[format]}
190
+ sizes={resolvedSizes}
191
+ />
192
+ ))}
193
+ <img
194
+ className={cx('not-prose', imgClassName)}
195
+ style={style}
196
+ src={fallbackSrc}
197
+ alt={alt}
198
+ width={image.imageWidth ?? undefined}
199
+ height={image.imageHeight ?? undefined}
200
+ loading={loading}
201
+ fetchPriority={fetchPriority}
202
+ />
203
+ </picture>
204
+ )
205
+ }
@@ -0,0 +1,31 @@
1
+ import { Serialize } from './serialize/index.tsx'
2
+ import type { Locale } from '@/ui/byline/types/i18n'
3
+
4
+ type RichTextIntrinsicProps = React.JSX.IntrinsicElements['div']
5
+ interface RichTextProps extends RichTextIntrinsicProps {
6
+ id?: string
7
+ lng: Locale
8
+ className?: string
9
+ wrapInDiv?: boolean
10
+ nodes: any
11
+ }
12
+
13
+ export const LexicalRichText = ({
14
+ nodes,
15
+ lng,
16
+ wrapInDiv = true,
17
+ }: RichTextProps): React.JSX.Element | null => {
18
+ if (nodes == null) {
19
+ return null
20
+ }
21
+
22
+ if (wrapInDiv) {
23
+ return (
24
+ <div className="editor-text">
25
+ <Serialize lng={lng} nodes={nodes} />
26
+ </div>
27
+ )
28
+ } else {
29
+ return <Serialize lng={lng} nodes={nodes} />
30
+ }
31
+ }