@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.
- package/LICENSE +373 -0
- package/README.md +23 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +72 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +36 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +16 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +76 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/context.d.ts +38 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +37 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/pg-url.d.ts +11 -0
- package/dist/lib/pg-url.d.ts.map +1 -0
- package/dist/lib/pg-url.js +22 -0
- package/dist/lib/pg-url.js.map +1 -0
- package/dist/manifest/deps.d.ts +29 -0
- package/dist/manifest/deps.d.ts.map +1 -0
- package/dist/manifest/deps.js +97 -0
- package/dist/manifest/deps.js.map +1 -0
- package/dist/manifest/env.d.ts +18 -0
- package/dist/manifest/env.d.ts.map +1 -0
- package/dist/manifest/env.js +38 -0
- package/dist/manifest/env.js.map +1 -0
- package/dist/phases/db-init.d.ts +3 -0
- package/dist/phases/db-init.d.ts.map +1 -0
- package/dist/phases/db-init.js +163 -0
- package/dist/phases/db-init.js.map +1 -0
- package/dist/phases/db.d.ts +11 -0
- package/dist/phases/db.d.ts.map +1 -0
- package/dist/phases/db.js +93 -0
- package/dist/phases/db.js.map +1 -0
- package/dist/phases/deps.d.ts +3 -0
- package/dist/phases/deps.d.ts.map +1 -0
- package/dist/phases/deps.js +115 -0
- package/dist/phases/deps.js.map +1 -0
- package/dist/phases/env.d.ts +3 -0
- package/dist/phases/env.d.ts.map +1 -0
- package/dist/phases/env.js +172 -0
- package/dist/phases/env.js.map +1 -0
- package/dist/phases/host.d.ts +3 -0
- package/dist/phases/host.d.ts.map +1 -0
- package/dist/phases/host.js +99 -0
- package/dist/phases/host.js.map +1 -0
- package/dist/phases/index.d.ts +7 -0
- package/dist/phases/index.d.ts.map +1 -0
- package/dist/phases/index.js +40 -0
- package/dist/phases/index.js.map +1 -0
- package/dist/phases/preflight.d.ts +4 -0
- package/dist/phases/preflight.d.ts.map +1 -0
- package/dist/phases/preflight.js +81 -0
- package/dist/phases/preflight.js.map +1 -0
- package/dist/phases/routes.d.ts +3 -0
- package/dist/phases/routes.d.ts.map +1 -0
- package/dist/phases/routes.js +145 -0
- package/dist/phases/routes.js.map +1 -0
- package/dist/phases/scaffold.d.ts +3 -0
- package/dist/phases/scaffold.d.ts.map +1 -0
- package/dist/phases/scaffold.js +113 -0
- package/dist/phases/scaffold.js.map +1 -0
- package/dist/phases/stub.d.ts +3 -0
- package/dist/phases/stub.d.ts.map +1 -0
- package/dist/phases/stub.js +25 -0
- package/dist/phases/stub.js.map +1 -0
- package/dist/phases/ui.d.ts +3 -0
- package/dist/phases/ui.d.ts.map +1 -0
- package/dist/phases/ui.js +93 -0
- package/dist/phases/ui.js.map +1 -0
- package/dist/phases/wire/index.d.ts +3 -0
- package/dist/phases/wire/index.d.ts.map +1 -0
- package/dist/phases/wire/index.js +67 -0
- package/dist/phases/wire/index.js.map +1 -0
- package/dist/phases/wire/root-tsx.d.ts +3 -0
- package/dist/phases/wire/root-tsx.d.ts.map +1 -0
- package/dist/phases/wire/root-tsx.js +57 -0
- package/dist/phases/wire/root-tsx.js.map +1 -0
- package/dist/phases/wire/server-ts.d.ts +3 -0
- package/dist/phases/wire/server-ts.d.ts.map +1 -0
- package/dist/phases/wire/server-ts.js +54 -0
- package/dist/phases/wire/server-ts.js.map +1 -0
- package/dist/phases/wire/shared.d.ts +34 -0
- package/dist/phases/wire/shared.d.ts.map +1 -0
- package/dist/phases/wire/shared.js +2 -0
- package/dist/phases/wire/shared.js.map +1 -0
- package/dist/phases/wire/start-ts.d.ts +3 -0
- package/dist/phases/wire/start-ts.d.ts.map +1 -0
- package/dist/phases/wire/start-ts.js +149 -0
- package/dist/phases/wire/start-ts.js.map +1 -0
- package/dist/phases/wire/tsconfig.d.ts +3 -0
- package/dist/phases/wire/tsconfig.d.ts.map +1 -0
- package/dist/phases/wire/tsconfig.js +105 -0
- package/dist/phases/wire/tsconfig.js.map +1 -0
- package/dist/phases/wire/vite-config.d.ts +3 -0
- package/dist/phases/wire/vite-config.d.ts.map +1 -0
- package/dist/phases/wire/vite-config.js +46 -0
- package/dist/phases/wire/vite-config.js.map +1 -0
- package/dist/prompts.d.ts +34 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +49 -0
- package/dist/prompts.js.map +1 -0
- package/dist/runner.d.ts +5 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +91 -0
- package/dist/runner.js.map +1 -0
- package/dist/state.d.ts +18 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +68 -0
- package/dist/state.js.map +1 -0
- package/dist/templates/byline/admin.config.ts +41 -0
- package/dist/templates/byline/i18n.ts +47 -0
- package/dist/templates/byline/routes.ts +28 -0
- package/dist/templates/byline/seed.ts +19 -0
- package/dist/templates/byline/seeds/admin.ts +62 -0
- package/dist/templates/byline/server.config.ts +92 -0
- package/dist/templates/byline-examples/admin.config.ts +74 -0
- package/dist/templates/byline-examples/blocks/photo-block.ts +59 -0
- package/dist/templates/byline-examples/blocks/richtext-block.ts +35 -0
- package/dist/templates/byline-examples/collections/doc-example-flat-locale-all.ts +373 -0
- package/dist/templates/byline-examples/collections/doc-example-flat-locale-en.ts +283 -0
- package/dist/templates/byline-examples/collections/doc-example-tree-locale-all.ts +278 -0
- package/dist/templates/byline-examples/collections/doc-example-tree-locale-en.ts +205 -0
- package/dist/templates/byline-examples/collections/docs/admin.tsx +204 -0
- package/dist/templates/byline-examples/collections/docs/components/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/docs/components/feature-formatter.tsx +10 -0
- package/dist/templates/byline-examples/collections/docs/hooks/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/docs/index.ts +10 -0
- package/dist/templates/byline-examples/collections/docs/schema.ts +209 -0
- package/dist/templates/byline-examples/collections/docs-categories/admin.tsx +78 -0
- package/dist/templates/byline-examples/collections/docs-categories/components/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/docs-categories/hooks/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/docs-categories/index.ts +10 -0
- package/dist/templates/byline-examples/collections/docs-categories/schema.ts +33 -0
- package/dist/templates/byline-examples/collections/media/admin.tsx +188 -0
- package/dist/templates/byline-examples/collections/media/components/media-list-view.tsx +330 -0
- package/dist/templates/byline-examples/collections/media/components/media-thumbnail.tsx +63 -0
- package/dist/templates/byline-examples/collections/media/hooks/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/media/index.ts +10 -0
- package/dist/templates/byline-examples/collections/media/schema.ts +157 -0
- package/dist/templates/byline-examples/collections/news/admin.tsx +192 -0
- package/dist/templates/byline-examples/collections/news/components/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/news/hooks/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/news/index.ts +10 -0
- package/dist/templates/byline-examples/collections/news/schema.ts +91 -0
- package/dist/templates/byline-examples/collections/news-categories/admin.tsx +78 -0
- package/dist/templates/byline-examples/collections/news-categories/components/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/news-categories/hooks/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/news-categories/index.ts +10 -0
- package/dist/templates/byline-examples/collections/news-categories/schema.ts +33 -0
- package/dist/templates/byline-examples/collections/pages/admin.tsx +183 -0
- package/dist/templates/byline-examples/collections/pages/components/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/pages/hooks/.gitkeep +0 -0
- package/dist/templates/byline-examples/collections/pages/index.ts +10 -0
- package/dist/templates/byline-examples/collections/pages/schema.ts +96 -0
- package/dist/templates/byline-examples/components/length-indicator.tsx +138 -0
- package/dist/templates/byline-examples/components/pill.tsx +38 -0
- package/dist/templates/byline-examples/components/summary-length.tsx +39 -0
- package/dist/templates/byline-examples/fields/available-languages-field.ts +90 -0
- package/dist/templates/byline-examples/fields/lexical-richtext-compact.ts +88 -0
- package/dist/templates/byline-examples/i18n.ts +47 -0
- package/dist/templates/byline-examples/routes.ts +28 -0
- package/dist/templates/byline-examples/scripts/regenerate-media.ts +275 -0
- package/dist/templates/byline-examples/seed.ts +25 -0
- package/dist/templates/byline-examples/seeds/admin.ts +62 -0
- package/dist/templates/byline-examples/seeds/doc-categories.ts +71 -0
- package/dist/templates/byline-examples/seeds/docs.ts +293 -0
- package/dist/templates/byline-examples/seeds/news-categories.ts +71 -0
- package/dist/templates/byline-examples/server.config.ts +179 -0
- package/dist/templates/host/vite.config.ts +41 -0
- package/dist/templates/migrations/0000_condemned_kronos.sql +324 -0
- package/dist/templates/migrations/0001_sudden_phantom_reporter.sql +1 -0
- package/dist/templates/migrations/meta/0000_snapshot.json +2793 -0
- package/dist/templates/migrations/meta/0001_snapshot.json +2799 -0
- package/dist/templates/migrations/meta/_journal.json +20 -0
- package/dist/templates/routes/(byline)/admin/account/index.tsx +11 -0
- package/dist/templates/routes/(byline)/admin/collections/$collection/$id/api.tsx +16 -0
- package/dist/templates/routes/(byline)/admin/collections/$collection/$id/history.tsx +19 -0
- package/dist/templates/routes/(byline)/admin/collections/$collection/$id/index.tsx +16 -0
- package/dist/templates/routes/(byline)/admin/collections/$collection/create.tsx +11 -0
- package/dist/templates/routes/(byline)/admin/collections/$collection/index.tsx +11 -0
- package/dist/templates/routes/(byline)/admin/index.tsx +11 -0
- package/dist/templates/routes/(byline)/admin/permissions/index.tsx +11 -0
- package/dist/templates/routes/(byline)/admin/roles/$id/index.tsx +11 -0
- package/dist/templates/routes/(byline)/admin/roles/index.tsx +11 -0
- package/dist/templates/routes/(byline)/admin/route.tsx +11 -0
- package/dist/templates/routes/(byline)/admin/users/$id/index.tsx +11 -0
- package/dist/templates/routes/(byline)/admin/users/index.tsx +11 -0
- package/dist/templates/routes/(byline)/sign-in.tsx +11 -0
- package/dist/templates/ui-byline/blocks/photo-block/index.tsx +80 -0
- package/dist/templates/ui-byline/blocks/richtext-block/index.tsx +46 -0
- package/dist/templates/ui-byline/components/admonition/index.tsx +40 -0
- package/dist/templates/ui-byline/components/code/code-serializer.tsx +20 -0
- package/dist/templates/ui-byline/components/code/code.tsx +50 -0
- package/dist/templates/ui-byline/components/code/index.module.scss +137 -0
- package/dist/templates/ui-byline/components/code/index.ts +2 -0
- package/dist/templates/ui-byline/components/code/types.ts +5 -0
- package/dist/templates/ui-byline/components/code/utils.ts +20 -0
- package/dist/templates/ui-byline/components/heading-anchor/heading-anchor.tsx +69 -0
- package/dist/templates/ui-byline/components/heading-anchor/index.ts +1 -0
- package/dist/templates/ui-byline/components/heading-anchor/utils.ts +15 -0
- package/dist/templates/ui-byline/components/inline-image/index.tsx +109 -0
- package/dist/templates/ui-byline/components/layout/index.tsx +63 -0
- package/dist/templates/ui-byline/components/link/lang-link.tsx +70 -0
- package/dist/templates/ui-byline/components/link/link-field.tsx +298 -0
- package/dist/templates/ui-byline/components/link/link-lexical.tsx +191 -0
- package/dist/templates/ui-byline/components/list/index.ts +2 -0
- package/dist/templates/ui-byline/components/list/list-item.tsx +32 -0
- package/dist/templates/ui-byline/components/list/list.tsx +17 -0
- package/dist/templates/ui-byline/components/responsive-image/index.tsx +205 -0
- package/dist/templates/ui-byline/components/richtext-lexical/index.tsx +31 -0
- package/dist/templates/ui-byline/components/richtext-lexical/serialize/index.tsx +249 -0
- package/dist/templates/ui-byline/components/richtext-lexical/serialize/richtext-node-formats.ts +66 -0
- package/dist/templates/ui-byline/components/richtext-lexical/serialize/types.ts +48 -0
- package/dist/templates/ui-byline/components/richtext-lexical/serialize/utils.ts +15 -0
- package/dist/templates/ui-byline/components/table-cell/index.tsx +36 -0
- package/dist/templates/ui-byline/components/vimeo/index.tsx +21 -0
- package/dist/templates/ui-byline/components/youtube/index.tsx +22 -0
- package/dist/templates/ui-byline/render-blocks.tsx +71 -0
- package/dist/templates/ui-byline/types/i18n.ts +14 -0
- package/dist/templates/ui-byline/utils/image-sources.ts +102 -0
- package/dist/templates/ui-byline/utils/to-kebab-case.ts +5 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/diff.d.ts +4 -0
- package/dist/ui/diff.d.ts.map +1 -0
- package/dist/ui/diff.js +23 -0
- package/dist/ui/diff.js.map +1 -0
- package/dist/ui/grid.d.ts +7 -0
- package/dist/ui/grid.d.ts.map +1 -0
- package/dist/ui/grid.js +24 -0
- package/dist/ui/grid.js.map +1 -0
- package/dist/ui/logger.d.ts +14 -0
- package/dist/ui/logger.d.ts.map +1 -0
- package/dist/ui/logger.js +30 -0
- package/dist/ui/logger.js.map +1 -0
- package/dist/ui/snippet.d.ts +2 -0
- package/dist/ui/snippet.d.ts.map +1 -0
- package/dist/ui/snippet.js +7 -0
- package/dist/ui/snippet.js.map +1 -0
- 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
|
+
 
|
|
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,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
|
+
}
|