@wikex/admin-kit 0.2.0 → 0.2.2

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 (72) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/admin/before-login.d.ts +5 -0
  3. package/dist/admin/before-login.js +27 -0
  4. package/dist/admin/brand.d.ts +19 -0
  5. package/dist/admin/brand.js +16 -0
  6. package/dist/admin/header.d.ts +5 -0
  7. package/dist/admin/header.js +96 -0
  8. package/dist/admin/logout-button.d.ts +8 -0
  9. package/dist/admin/logout-button.js +8 -0
  10. package/dist/admin/nav-client.d.ts +16 -0
  11. package/dist/admin/nav-client.js +258 -0
  12. package/dist/admin/nav.d.ts +16 -0
  13. package/dist/admin/nav.js +31 -0
  14. package/dist/admin/theme-toggle.d.ts +5 -0
  15. package/dist/admin/theme-toggle.js +8 -0
  16. package/dist/admin/view-site.d.ts +5 -0
  17. package/dist/admin/view-site.js +11 -0
  18. package/dist/chunk-7FEACJUN.js +151 -0
  19. package/dist/chunk-BUD6BMFV.js +117 -0
  20. package/dist/chunk-GMPR6LHH.js +2063 -0
  21. package/dist/chunk-GZ2B5BTH.js +39 -0
  22. package/dist/chunk-JJLHSZ3T.js +40 -0
  23. package/dist/chunk-LC2CP5SR.js +13 -0
  24. package/dist/chunk-SAI5SWZA.js +29 -0
  25. package/dist/chunk-SM6CCKQQ.js +62 -0
  26. package/dist/chunk-VK4EFLWH.js +119 -0
  27. package/dist/chunk-X7VETFVD.js +52 -0
  28. package/dist/index.d.ts +28 -0
  29. package/dist/index.js +104 -0
  30. package/dist/live-preview.d.ts +118 -0
  31. package/dist/live-preview.js +20 -0
  32. package/dist/project-ClCmpyeX.d.ts +54 -0
  33. package/dist/project.d.ts +1 -0
  34. package/dist/project.js +8 -0
  35. package/dist/rich-text-client.d.ts +5 -0
  36. package/dist/rich-text-client.js +166 -0
  37. package/dist/rich-text.d.ts +5 -0
  38. package/dist/rich-text.js +6 -0
  39. package/dist/runtimeConfig-BwjQepcW.d.ts +30 -0
  40. package/dist/starter-client.d.ts +56 -0
  41. package/dist/starter-client.js +26 -0
  42. package/dist/starter.d.ts +39 -0
  43. package/dist/starter.js +62 -0
  44. package/dist/text-style-runtime.d.ts +5 -0
  45. package/dist/text-style-runtime.js +74 -0
  46. package/dist/text-styles.d.ts +86 -0
  47. package/dist/text-styles.js +22 -0
  48. package/package.json +24 -19
  49. package/src/TextStyleRuntime.tsx +0 -90
  50. package/src/admin/BeforeLogin.tsx +0 -19
  51. package/src/admin/Brand.tsx +0 -74
  52. package/src/admin/Header.tsx +0 -87
  53. package/src/admin/LogoutButton.tsx +0 -41
  54. package/src/admin/Nav.tsx +0 -35
  55. package/src/admin/NavClient.tsx +0 -288
  56. package/src/admin/ThemeToggle.tsx +0 -31
  57. package/src/admin/ViewSite.tsx +0 -11
  58. package/src/admin/useAdminBrand.ts +0 -81
  59. package/src/adminExperience.ts +0 -117
  60. package/src/index.ts +0 -11
  61. package/src/live-preview/GlobalInlineInspector.tsx +0 -334
  62. package/src/live-preview/LivePreviewEditor.tsx +0 -2088
  63. package/src/live-preview/TextStyleInspector.tsx +0 -289
  64. package/src/live-preview/index.ts +0 -23
  65. package/src/live-preview/runtimeConfig.ts +0 -52
  66. package/src/plugin.ts +0 -70
  67. package/src/project.ts +0 -82
  68. package/src/rich-text/client.tsx +0 -202
  69. package/src/rich-text/index.ts +0 -10
  70. package/src/starter/client.tsx +0 -22
  71. package/src/starter.ts +0 -95
  72. package/src/textStyles.ts +0 -161
@@ -1,202 +0,0 @@
1
- 'use client'
2
-
3
- import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
4
- import { $forEachSelectedTextNode } from '@lexical/selection'
5
- import type { ToolbarGroupItem } from '@payloadcms/richtext-lexical'
6
- import { createClientFeature } from '@payloadcms/richtext-lexical/client'
7
- import {
8
- $getNodeByKey,
9
- $getSelection,
10
- $getState,
11
- $isRangeSelection,
12
- $isTextNode,
13
- $setSelection,
14
- $setState,
15
- createState,
16
- type LexicalEditor,
17
- type RangeSelection,
18
- TextNode,
19
- } from 'lexical'
20
- import { useCallback, useEffect, useRef, useState } from 'react'
21
-
22
- import {
23
- getTextStyleCSS,
24
- normalizeTextStyleValue,
25
- TEXT_FONT_OPTIONS,
26
- TEXT_LINE_HEIGHT_OPTIONS,
27
- type TextFontFamily,
28
- type TextLineHeight,
29
- } from '../textStyles'
30
-
31
- const fontFamilyState = createState('fontFamily', {
32
- parse: (value) => normalizeTextStyleValue({ fontFamily: value }).fontFamily,
33
- })
34
- const lineHeightState = createState('lineHeight', {
35
- parse: (value) => normalizeTextStyleValue({ lineHeight: value }).lineHeight,
36
- })
37
-
38
- type TypographyValue = {
39
- fontFamily: TextFontFamily
40
- lineHeight: TextLineHeight
41
- }
42
-
43
- const DEFAULT_TYPOGRAPHY: TypographyValue = {
44
- fontFamily: 'default',
45
- lineHeight: 'default',
46
- }
47
-
48
- const readSelectedTypography = (editor: LexicalEditor): TypographyValue =>
49
- editor.getEditorState().read(() => {
50
- const selection = $getSelection()
51
- if (!$isRangeSelection(selection)) return DEFAULT_TYPOGRAPHY
52
-
53
- const textNode = selection.getNodes().find($isTextNode)
54
- if (!textNode) return DEFAULT_TYPOGRAPHY
55
-
56
- return {
57
- fontFamily: $getState(textNode, fontFamilyState) ?? 'default',
58
- lineHeight: $getState(textNode, lineHeightState) ?? 'default',
59
- }
60
- })
61
-
62
- const updateSelectedTypography = (
63
- editor: LexicalEditor,
64
- property: keyof TypographyValue,
65
- value: TextFontFamily | TextLineHeight,
66
- savedSelection: null | RangeSelection,
67
- ) => {
68
- editor.update(() => {
69
- if (!$isRangeSelection($getSelection()) && savedSelection) {
70
- $setSelection(savedSelection.clone())
71
- }
72
-
73
- $forEachSelectedTextNode((textNode) => {
74
- if (property === 'fontFamily') {
75
- const fontFamily = value as TextFontFamily
76
- $setState(textNode, fontFamilyState, fontFamily === 'default' ? undefined : fontFamily)
77
- } else {
78
- const lineHeight = value as TextLineHeight
79
- $setState(textNode, lineHeightState, lineHeight === 'default' ? undefined : lineHeight)
80
- }
81
- })
82
- })
83
- }
84
-
85
- const TypographyToolbarItem = ({ editor }: { editor: LexicalEditor }) => {
86
- const [value, setValue] = useState<TypographyValue>(DEFAULT_TYPOGRAPHY)
87
- const savedSelection = useRef<null | RangeSelection>(null)
88
-
89
- const captureSelection = useCallback(() => {
90
- editor.getEditorState().read(() => {
91
- const selection = $getSelection()
92
- if ($isRangeSelection(selection)) savedSelection.current = selection.clone()
93
- })
94
- }, [editor])
95
-
96
- useEffect(() => {
97
- const syncValue = () => {
98
- captureSelection()
99
- setValue(readSelectedTypography(editor))
100
- }
101
-
102
- syncValue()
103
- return editor.registerUpdateListener(syncValue)
104
- }, [captureSelection, editor])
105
-
106
- return (
107
- <div
108
- aria-label="Kiểu chữ"
109
- className="wikex-typography-selects"
110
- onMouseDown={(event) => {
111
- event.stopPropagation()
112
- captureSelection()
113
- }}
114
- >
115
- <select
116
- aria-label="Phông chữ"
117
- className="wikex-minimal-select wikex-minimal-select--font"
118
- onChange={(event) => {
119
- const fontFamily = event.target.value as TextFontFamily
120
- setValue((current) => ({ ...current, fontFamily }))
121
- updateSelectedTypography(editor, 'fontFamily', fontFamily, savedSelection.current)
122
- }}
123
- title="Phông chữ"
124
- value={value.fontFamily}
125
- >
126
- {TEXT_FONT_OPTIONS.map((option) => (
127
- <option key={option.value} value={option.value}>
128
- {option.label}
129
- </option>
130
- ))}
131
- </select>
132
- <select
133
- aria-label="Chiều cao dòng"
134
- className="wikex-minimal-select wikex-minimal-select--line-height"
135
- onChange={(event) => {
136
- const lineHeight = event.target.value as TextLineHeight
137
- setValue((current) => ({ ...current, lineHeight }))
138
- updateSelectedTypography(editor, 'lineHeight', lineHeight, savedSelection.current)
139
- }}
140
- title="Chiều cao dòng"
141
- value={value.lineHeight}
142
- >
143
- {TEXT_LINE_HEIGHT_OPTIONS.map((option) => (
144
- <option key={option.value} value={option.value}>
145
- {option.label}
146
- </option>
147
- ))}
148
- </select>
149
- </div>
150
- )
151
- }
152
-
153
- const TypographyStatePlugin = () => {
154
- const [editor] = useLexicalComposerContext()
155
-
156
- useEffect(
157
- () =>
158
- editor.registerMutationListener(TextNode, (mutatedNodes) => {
159
- editor.getEditorState().read(() => {
160
- for (const [nodeKey, mutation] of mutatedNodes) {
161
- if (mutation === 'destroyed') continue
162
-
163
- const node = $getNodeByKey(nodeKey)
164
- const element = editor.getElementByKey(nodeKey)
165
- if (!$isTextNode(node) || !element) continue
166
-
167
- const style = getTextStyleCSS({
168
- fontFamily: $getState(node, fontFamilyState),
169
- lineHeight: $getState(node, lineHeightState),
170
- })
171
-
172
- if (style.fontFamily) element.style.fontFamily = style.fontFamily
173
- else element.style.removeProperty('font-family')
174
-
175
- if (style.lineHeight) element.style.lineHeight = style.lineHeight
176
- else element.style.removeProperty('line-height')
177
- }
178
- })
179
- }),
180
- [editor],
181
- )
182
-
183
- return null
184
- }
185
-
186
- const toolbarItem: ToolbarGroupItem = {
187
- Component: TypographyToolbarItem,
188
- key: 'wikex-typography-controls',
189
- }
190
-
191
- const typographyToolbarGroup = {
192
- items: [toolbarItem],
193
- key: 'wikexTypography',
194
- order: 34,
195
- type: 'buttons' as const,
196
- }
197
-
198
- export const RichTextTypographyFeatureClient = createClientFeature({
199
- plugins: [{ Component: TypographyStatePlugin, position: 'normal' }],
200
- toolbarFixed: { groups: [typographyToolbarGroup] },
201
- toolbarInline: { groups: [typographyToolbarGroup] },
202
- })
@@ -1,10 +0,0 @@
1
- import { createServerFeature } from '@payloadcms/richtext-lexical'
2
-
3
- const TypographyFeature = createServerFeature({
4
- feature: {
5
- ClientFeature: '@wikex/admin-kit/rich-text/client#RichTextTypographyFeatureClient',
6
- },
7
- key: 'wikexTypography',
8
- })
9
-
10
- export const RichTextTypographyFeature = () => TypographyFeature()
@@ -1,22 +0,0 @@
1
- 'use client'
2
-
3
- import { configureAdminBrand } from '../admin/Brand'
4
- import LivePreviewEditor, { configureLivePreview } from '../live-preview'
5
- import type { WikexStarter } from '../starter'
6
-
7
- type LivePreviewConfig = WikexStarter['livePreview'] & {
8
- getVisualEditorRoute: WikexStarter['getVisualEditorRoute']
9
- }
10
-
11
- /** Configure and return the shared client-side Live Preview provider for a Wikex starter. */
12
- export const createWikexLivePreviewAdapter = ({
13
- brand,
14
- getVisualEditorRoute,
15
- pageBlocks,
16
- postBlocks,
17
- }: LivePreviewConfig) => {
18
- configureAdminBrand({ logo: { alt: brand.name, ...brand.logo }, siteName: brand.name })
19
- configureLivePreview({ getVisualEditorRoute, pageBlocks, postBlocks })
20
-
21
- return LivePreviewEditor
22
- }
package/src/starter.ts DELETED
@@ -1,95 +0,0 @@
1
- import { createContentModule, type ContentModuleOptions } from '@wikex/content-kit'
2
- import { adminExperiencePlugin } from './adminExperience'
3
- import type { BlockPickerOption, VisualEditorRoute } from './live-preview/runtimeConfig'
4
- import { createVisualEditorRouteResolver, defineWikexProject } from './project'
5
- import { wikexAdminPlugin } from './plugin'
6
-
7
- export type WikexStarterBrand = {
8
- description: string
9
- logo: {
10
- height: number
11
- url: string
12
- width: number
13
- }
14
- name: string
15
- }
16
-
17
- export type WikexStarterOptions = {
18
- brand: WikexStarterBrand
19
- content: ContentModuleOptions
20
- globals?: Readonly<Record<string, 'content' | 'footer' | 'header'>>
21
- id: string
22
- modules?: readonly string[]
23
- provider: string
24
- }
25
-
26
- export type WikexStarter = {
27
- collections: ReturnType<typeof createContentModule>['collections']
28
- getVisualEditorRoute: (pathname: string) => VisualEditorRoute | null
29
- livePreview: {
30
- brand: WikexStarterBrand
31
- pageBlocks: readonly BlockPickerOption[]
32
- postBlocks: readonly BlockPickerOption[]
33
- }
34
- plugins: ReturnType<typeof createContentModule>['plugins']
35
- project: ReturnType<typeof defineWikexProject>
36
- }
37
-
38
- const getLabel = (block: { labels?: unknown; slug: string }) => {
39
- if (typeof block.labels === 'object' && block.labels && 'singular' in block.labels) {
40
- const singular = block.labels.singular
41
- if (typeof singular === 'string') return singular
42
- }
43
- return block.slug
44
- }
45
-
46
- const isStarterBlock = (block: unknown): block is { labels?: unknown; slug: string } =>
47
- typeof block === 'object' && block !== null && 'slug' in block && typeof block.slug === 'string'
48
-
49
- const toBlockPickerOptions = (blocks: unknown): readonly BlockPickerOption[] => {
50
- if (!Array.isArray(blocks)) return []
51
- return blocks.filter(isStarterBlock).map((block) => {
52
- const label = getLabel(block)
53
- return { description: label, label, payloadLabel: label, slug: block.slug }
54
- })
55
- }
56
-
57
- /**
58
- * One factory for the conventional Wikex Payload setup. The consuming project still owns
59
- * its visual block components and frontend renderer, but no longer has to wire plugins,
60
- * content schema, branding and preview routing independently.
61
- */
62
- export const createWikexStarter = (options: WikexStarterOptions): WikexStarter => {
63
- const seo = options.content.seo === undefined ? { siteName: options.brand.name } : options.content.seo
64
- const content = createContentModule({ ...options.content, seo })
65
- const collections = [content.pageSlug, content.postSlug].filter(
66
- (slug): slug is string => Boolean(slug),
67
- )
68
- const getVisualEditorRoute = createVisualEditorRouteResolver({
69
- collections,
70
- globals: options.globals,
71
- })
72
- const project = defineWikexProject({
73
- brand: options.brand,
74
- id: options.id,
75
- modules: options.modules ?? ['website', 'blog'],
76
- })
77
- const pageBlocks = toBlockPickerOptions(
78
- options.content.page === false ? undefined : options.content.page?.blocks,
79
- )
80
- const postBlocks = toBlockPickerOptions(
81
- options.content.post === false ? undefined : options.content.post?.blocks,
82
- )
83
-
84
- return {
85
- collections: content.collections,
86
- getVisualEditorRoute,
87
- livePreview: { brand: options.brand, pageBlocks, postBlocks },
88
- plugins: [
89
- adminExperiencePlugin,
90
- ...content.plugins,
91
- wikexAdminPlugin({ components: { provider: options.provider } }),
92
- ],
93
- project,
94
- }
95
- }
package/src/textStyles.ts DELETED
@@ -1,161 +0,0 @@
1
- export const TEXT_FONT_OPTIONS = [
2
- { css: '', label: 'Mặc định', value: 'default' },
3
- { css: 'var(--font-manrope), sans-serif', label: 'Manrope', value: 'manrope' },
4
- { css: 'var(--font-inter), sans-serif', label: 'Inter', value: 'inter' },
5
- { css: 'var(--font-roboto), sans-serif', label: 'Roboto', value: 'roboto' },
6
- { css: 'var(--font-montserrat), sans-serif', label: 'Montserrat', value: 'montserrat' },
7
- { css: 'var(--font-lora), serif', label: 'Lora', value: 'lora' },
8
- {
9
- css: 'var(--font-playfair-display), serif',
10
- label: 'Playfair Display',
11
- value: 'playfair-display',
12
- },
13
- ] as const
14
-
15
- export const TEXT_LINE_HEIGHT_OPTIONS = [
16
- { css: '', label: 'Mặc định', value: 'default' },
17
- { css: '0.9', label: 'Rất chặt · 0.9', value: '0.9' },
18
- { css: '1', label: 'Chặt · 1.0', value: '1' },
19
- { css: '1.15', label: 'Gọn · 1.15', value: '1.15' },
20
- { css: '1.35', label: 'Tiêu chuẩn · 1.35', value: '1.35' },
21
- { css: '1.5', label: 'Thoáng · 1.5', value: '1.5' },
22
- { css: '1.7', label: 'Rộng · 1.7', value: '1.7' },
23
- ] as const
24
-
25
- export type TextFontFamily = (typeof TEXT_FONT_OPTIONS)[number]['value']
26
- export type TextLineHeight = (typeof TEXT_LINE_HEIGHT_OPTIONS)[number]['value']
27
-
28
- export type TextStyleValue = {
29
- fontFamily?: TextFontFamily
30
- lineHeight?: TextLineHeight
31
- /** Percentage of the containing block, written by the canvas resize handles. */
32
- width?: number
33
- /** Percentage offset from the containing block's left content edge. */
34
- widthOffset?: number
35
- }
36
-
37
- export type TextStyleMap = Record<string, TextStyleValue>
38
-
39
- export const EDITABLE_INTERACTIVE_SELECTOR = [
40
- 'a',
41
- 'button',
42
- 'input:not([type="hidden"])',
43
- 'select',
44
- 'textarea',
45
- '[role="button"]',
46
- '[role="link"]',
47
- '[tabindex]:not([tabindex="-1"])',
48
- ].join(',')
49
-
50
- export const EDITABLE_PREVIEW_SELECTOR = [
51
- 'h1',
52
- 'h2',
53
- 'h3',
54
- 'h4',
55
- 'h5',
56
- 'h6',
57
- 'p',
58
- 'li',
59
- 'span',
60
- 'strong',
61
- EDITABLE_INTERACTIVE_SELECTOR,
62
- ].join(',')
63
-
64
- export const isSyntheticElementStyleKey = (value: string) => value.startsWith('@element:')
65
-
66
- export const getEditableElementStyleKey = (element: HTMLElement) => {
67
- const scopedRoot = element.closest<HTMLElement>('[data-wikex-scope]')
68
- const root = scopedRoot ?? element.ownerDocument.body
69
- const scope = scopedRoot?.dataset.wikexScope || 'body'
70
- const segments: string[] = []
71
- let current: HTMLElement | null = element
72
-
73
- while (current && current !== root) {
74
- const parentElement: HTMLElement | null = current.parentElement
75
- if (!parentElement) break
76
-
77
- const tagName = current.tagName.toLowerCase()
78
- const sameTagSiblings = Array.from(parentElement.children).filter(
79
- (sibling) => sibling.tagName === current?.tagName,
80
- )
81
- const index = sameTagSiblings.indexOf(current) + 1
82
- segments.push(`${tagName}:nth-of-type(${Math.max(index, 1)})`)
83
- current = parentElement
84
- }
85
-
86
- return `@element:${scope}:${segments.reverse().join('>')}`
87
- }
88
-
89
- const fontValues = new Set<string>(TEXT_FONT_OPTIONS.map((option) => option.value))
90
- const lineHeightValues = new Set<string>(TEXT_LINE_HEIGHT_OPTIONS.map((option) => option.value))
91
-
92
- const isRecord = (value: unknown): value is Record<string, unknown> =>
93
- Boolean(value) && typeof value === 'object' && !Array.isArray(value)
94
-
95
- export const normalizeTextStyleValue = (value: unknown): TextStyleValue => {
96
- if (!isRecord(value)) return {}
97
-
98
- const fontFamily =
99
- typeof value.fontFamily === 'string' && fontValues.has(value.fontFamily)
100
- ? (value.fontFamily as TextFontFamily)
101
- : undefined
102
- const lineHeight =
103
- typeof value.lineHeight === 'string' && lineHeightValues.has(value.lineHeight)
104
- ? (value.lineHeight as TextLineHeight)
105
- : undefined
106
- const legacyWidth =
107
- typeof value.width === 'string' && /^\d+(?:\.\d+)?%$/.test(value.width)
108
- ? Number.parseFloat(value.width)
109
- : undefined
110
- const widthValue = typeof value.width === 'number' ? value.width : legacyWidth
111
- const width =
112
- typeof widthValue === 'number' && Number.isFinite(widthValue)
113
- ? Math.min(100, Math.max(5, widthValue))
114
- : undefined
115
- const widthOffset =
116
- typeof value.widthOffset === 'number' && Number.isFinite(value.widthOffset)
117
- ? Math.min(width ? 100 - width : 95, Math.max(0, value.widthOffset))
118
- : undefined
119
-
120
- return { fontFamily, lineHeight, width, widthOffset }
121
- }
122
-
123
- export const normalizeTextStyleMap = (value: unknown): TextStyleMap => {
124
- if (!isRecord(value)) return {}
125
-
126
- return Object.fromEntries(
127
- Object.entries(value).map(([fieldPath, fieldStyle]) => [
128
- fieldPath,
129
- normalizeTextStyleValue(fieldStyle),
130
- ]),
131
- )
132
- }
133
-
134
- export const getTextStyleCSS = (
135
- value: TextStyleValue,
136
- ): {
137
- boxSizing?: 'border-box'
138
- fontFamily?: string
139
- lineHeight?: string
140
- marginLeft?: string
141
- marginRight?: string
142
- maxWidth?: string
143
- width?: string
144
- } => {
145
- const fontFamily = TEXT_FONT_OPTIONS.find((option) => option.value === value.fontFamily)?.css
146
- const lineHeight = TEXT_LINE_HEIGHT_OPTIONS.find(
147
- (option) => option.value === value.lineHeight,
148
- )?.css
149
- const width = typeof value.width === 'number' ? `${value.width}%` : undefined
150
- const widthOffset = typeof value.widthOffset === 'number' ? `${value.widthOffset}%` : undefined
151
-
152
- return {
153
- boxSizing: width ? 'border-box' : undefined,
154
- fontFamily: fontFamily || undefined,
155
- lineHeight: lineHeight || undefined,
156
- marginLeft: width ? widthOffset || '0%' : undefined,
157
- marginRight: width ? 'auto' : undefined,
158
- maxWidth: width ? 'none' : undefined,
159
- width,
160
- }
161
- }