@rimelight/ui 0.0.43 → 0.0.45

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 (154) hide show
  1. package/README.md +14 -12
  2. package/package.json +8 -10
  3. package/src/components/avatar/RLAAvatar.astro +4 -12
  4. package/src/components/badge/RLABadge.astro +2 -1
  5. package/src/components/badge/badge.theme.ts +0 -4
  6. package/src/components/banner/RLABanner.astro +1 -1
  7. package/src/components/banner/banner.theme.ts +1 -1
  8. package/src/components/breadcrumb/RLABreadcrumb.astro +2 -2
  9. package/src/components/button/RLAButton.astro +4 -1
  10. package/src/components/button/RLSButton.tsx +1 -1
  11. package/src/components/button/button.theme.ts +1 -5
  12. package/src/components/callout/RLACallout.astro +7 -5
  13. package/src/components/callout/callout.theme.ts +1 -5
  14. package/src/components/card/card.theme.ts +3 -3
  15. package/src/components/carousel/RLACarousel.astro +2 -2
  16. package/src/components/carousel/carousel.theme.ts +2 -2
  17. package/src/components/chart/RLAChart.astro +1 -1
  18. package/src/components/checkbox/RLACheckbox.astro +2 -1
  19. package/src/components/checkbox/checkbox.theme.ts +0 -4
  20. package/src/components/chip/RLAChip.astro +2 -1
  21. package/src/components/chip/chip.theme.ts +0 -4
  22. package/src/components/collapsible/RLACollapsible.astro +1 -1
  23. package/src/components/color-area/RLAColorArea.astro +1 -0
  24. package/src/components/color-area/color-area.theme.ts +1 -5
  25. package/src/components/color-field/RLAColorField.astro +1 -0
  26. package/src/components/color-field/color-field.theme.ts +1 -5
  27. package/src/components/color-slider/RLAColorSlider.astro +1 -0
  28. package/src/components/color-slider/color-slider.theme.ts +1 -5
  29. package/src/components/dashboard-group/RLADashboardGroup.astro +5 -7
  30. package/src/components/dashboard-group/dashboard-group.theme.ts +1 -1
  31. package/src/components/dashboard-group/dashboard-group.ts +1 -1
  32. package/src/components/dashboard-navbar/RLADashboardNavbar.astro +4 -4
  33. package/src/components/dashboard-navbar/dashboard-navbar.theme.ts +3 -3
  34. package/src/components/dashboard-navbar/dashboard-navbar.ts +1 -1
  35. package/src/components/dashboard-panel/RLADashboardPanel.astro +9 -8
  36. package/src/components/dashboard-panel/dashboard-panel.theme.ts +1 -1
  37. package/src/components/dashboard-sidebar/RLADashboardSidebar.astro +52 -20
  38. package/src/components/dashboard-sidebar/dashboard-sidebar.theme.ts +6 -5
  39. package/src/components/dashboard-sidebar-collapse/RLADashboardSidebarCollapse.astro +1 -0
  40. package/src/components/drawer/RLADrawer.astro +1 -1
  41. package/src/components/dropdown-menu/dropdown-menu.theme.ts +2 -2
  42. package/src/components/footer/RLAFooter.astro +2 -0
  43. package/src/components/footer/footer.theme.ts +0 -8
  44. package/src/components/gamepad/RLAGamepad.astro +1 -1
  45. package/src/components/head/UIHead.astro +1 -2
  46. package/src/components/header/header.theme.ts +1 -1
  47. package/src/components/icon/RLAIcon.astro +2 -5
  48. package/src/components/icon/RLSIcon.tsx +2 -2
  49. package/src/components/icon/RLVIcon.vue +18 -6
  50. package/src/components/icon/icon.theme.ts +1 -1
  51. package/src/components/image/image.theme.ts +1 -1
  52. package/src/components/input/RLAInput.astro +2 -1
  53. package/src/components/input/input.theme.ts +0 -4
  54. package/src/components/input-menu/RLAInputMenu.astro +2 -2
  55. package/src/components/input-menu/input-menu.theme.ts +1 -1
  56. package/src/components/input-rating/input-rating.theme.ts +2 -2
  57. package/src/components/kbd/RLAKbd.astro +2 -1
  58. package/src/components/kbd/kbd.theme.ts +0 -4
  59. package/src/components/keyboard/RLAKeyboard.astro +1 -1
  60. package/src/components/link/RLALink.astro +1 -0
  61. package/src/components/link/link.theme.ts +0 -4
  62. package/src/components/link-group/RLALinkGroup.astro +1 -1
  63. package/src/components/link-group/link-group.theme.ts +1 -10
  64. package/src/components/locale-selector/RLALocaleSelector.astro +4 -36
  65. package/src/components/locale-selector/locale-selector.theme.ts +3 -11
  66. package/src/components/logo/RLALogo.astro +1 -2
  67. package/src/components/modal/RLAModal.astro +8 -5
  68. package/src/components/modal/RLSModal.tsx +17 -12
  69. package/src/components/modal/modal.theme.ts +5 -4
  70. package/src/components/navigation-menu/RLANavigationMenu.astro +17 -21
  71. package/src/components/navigation-menu/navigation-menu.theme.ts +3 -7
  72. package/src/components/pagination/RLAPagination.astro +2 -2
  73. package/src/components/pagination/pagination.theme.ts +1 -1
  74. package/src/components/popover/RLAPopover.astro +39 -0
  75. package/src/components/progress/RLAProgress.astro +2 -0
  76. package/src/components/progress/progress.theme.ts +0 -4
  77. package/src/components/scroll-area/scroll-area.theme.ts +1 -1
  78. package/src/components/scroll-to-top/RLAScrollToTop.astro +6 -5
  79. package/src/components/scroll-to-top/RLVScrollToTop.vue +7 -7
  80. package/src/components/scroll-to-top/scroll-to-top.theme.ts +3 -7
  81. package/src/components/search/RLASearch.astro +3 -3
  82. package/src/components/select/RLASelect.astro +96 -528
  83. package/src/components/select/RLSSelect.tsx +68 -14
  84. package/src/components/select/RLVSelect.vue +57 -0
  85. package/src/components/select/select.theme.ts +11 -96
  86. package/src/components/shortcuts/RLAShortcuts.astro +9 -9
  87. package/src/components/sidebar/RLASidebar.astro +3 -3
  88. package/src/components/slideover/RLASlideover.astro +1 -1
  89. package/src/components/slider/RLASlider.astro +2 -1
  90. package/src/components/slider/slider.theme.ts +1 -5
  91. package/src/components/spinner/RLASpinner.astro +2 -1
  92. package/src/components/spinner/spinner.theme.ts +1 -5
  93. package/src/components/splitter/splitter.theme.ts +1 -1
  94. package/src/components/stepper/RLAStepper.astro +8 -8
  95. package/src/components/stepper/stepper.theme.ts +6 -7
  96. package/src/components/steps/RLASteps.astro +3 -3
  97. package/src/components/switch/RLASwitch.astro +2 -1
  98. package/src/components/switch/switch.theme.ts +0 -4
  99. package/src/components/table/table.theme.ts +1 -1
  100. package/src/components/table-of-contents/RLATableOfContents.astro +5 -5
  101. package/src/components/theme-selector/RLAThemeSelector.astro +16 -14
  102. package/src/components/theme-selector/theme-selector.theme.ts +2 -2
  103. package/src/components/toast/RLAToast.astro +6 -6
  104. package/src/components/toast/store.ts +1 -1
  105. package/src/components/tooltip/tooltip.theme.ts +1 -1
  106. package/src/icons.raw.ts +67 -0
  107. package/src/icons.ts +31 -64
  108. package/src/index.ts +19 -18
  109. package/src/preset.ts +110 -108
  110. package/src/resolver.ts +127 -15
  111. package/src/shortcuts.ts +103 -84
  112. package/src/stores.ts +22 -0
  113. package/src/types.ts +27 -9
  114. package/src/virtual.d.ts +4 -0
  115. package/src/components/editor/PageEditor.vue +0 -459
  116. package/src/components/editor/PageMention.vue +0 -87
  117. package/src/components/editor/PagePropertiesEditor.vue +0 -358
  118. package/src/components/editor/PagePropertiesRenderer.vue +0 -285
  119. package/src/components/editor/PageRenderer.vue +0 -155
  120. package/src/components/editor/PageSurround.vue +0 -130
  121. package/src/components/editor/PageVersionSelector.vue +0 -371
  122. package/src/components/editor/blocks/AddBlockModal.vue +0 -196
  123. package/src/components/editor/blocks/Block.vue +0 -142
  124. package/src/components/editor/blocks/BlockEditRenderer.vue +0 -26
  125. package/src/components/editor/blocks/BlockEditor.vue +0 -141
  126. package/src/components/editor/blocks/BlockViewRenderer.vue +0 -71
  127. package/src/components/editor/blocks/TextRenderer.vue +0 -31
  128. package/src/components/editor/blocks/editor/CalloutBlockEditor.vue +0 -53
  129. package/src/components/editor/blocks/editor/ImageBlockEditor.vue +0 -55
  130. package/src/components/editor/blocks/editor/ParagraphBlockEditor.vue +0 -31
  131. package/src/components/editor/blocks/editor/SectionBlockEditor.vue +0 -65
  132. package/src/components/editor/blocks/nodes/LinkNode.vue +0 -28
  133. package/src/components/editor/blocks/nodes/TextNode.vue +0 -22
  134. package/src/components/editor/blocks/renderer/CalloutBlockRenderer.vue +0 -65
  135. package/src/components/editor/blocks/renderer/ImageBlockRenderer.vue +0 -20
  136. package/src/components/editor/blocks/renderer/ParagraphBlockRenderer.vue +0 -21
  137. package/src/components/editor/blocks/renderer/SectionBlockRenderer.vue +0 -55
  138. package/src/components/editor/composables/injectionKeys.ts +0 -3
  139. package/src/components/editor/composables/useBlockEditor.ts +0 -261
  140. package/src/components/editor/composables/usePageEditor.ts +0 -97
  141. package/src/components/editor/modals/CreatePageModal.vue +0 -160
  142. package/src/components/editor/modals/DeletePageModal.vue +0 -116
  143. package/src/components/editor/modals/PageTreeModal.vue +0 -275
  144. package/src/components/editor/types/blocks.ts +0 -100
  145. package/src/components/editor/types/pages.ts +0 -89
  146. package/src/components/editor/types/schemas.ts +0 -36
  147. package/src/components/editor/utils/blocks.ts +0 -42
  148. package/src/components/editor/utils/editorHelpers.ts +0 -24
  149. package/src/components/editor/utils/page.ts +0 -219
  150. package/src/components/editor/utils/richTextHelpers.ts +0 -35
  151. package/src/components/editor/views/PageEditView.vue +0 -195
  152. package/src/components/editor/views/PageLiveView.vue +0 -90
  153. package/src/components/editor/views/PageReviewView.vue +0 -308
  154. package/src/components/icon/resolver.ts +0 -23
package/src/stores.ts ADDED
@@ -0,0 +1,22 @@
1
+ export interface Atom<T> {
2
+ get: () => T
3
+ set: (next: T) => void
4
+ subscribe: (fn: (val: T) => void) => () => void
5
+ }
6
+
7
+ export function atom<T>(initial: T): Atom<T> {
8
+ let val = initial
9
+ const subs = new Set<(v: T) => void>()
10
+ return {
11
+ get: () => val,
12
+ set: (next: T) => {
13
+ val = next
14
+ subs.forEach((fn) => fn(val))
15
+ },
16
+ subscribe: (fn: (v: T) => void) => {
17
+ subs.add(fn)
18
+ fn(val)
19
+ return () => subs.delete(fn)
20
+ }
21
+ }
22
+ }
package/src/types.ts CHANGED
@@ -1,12 +1,36 @@
1
1
  import type { UserConfig } from "unocss"
2
2
  import type { RimelightIcons } from "./icons"
3
- import type { P } from "./preset"
3
+ import type { PALETTES } from "./preset"
4
4
 
5
5
  export type Theme = "light" | "dark"
6
- export type BuiltinThemeColor = keyof typeof P
6
+ export type BuiltinThemeColor = keyof typeof PALETTES
7
7
  export type ThemeColor = BuiltinThemeColor | (string & {})
8
8
 
9
+ export type ClassValue =
10
+ | string
11
+ | number
12
+ | boolean
13
+ | undefined
14
+ | null
15
+ | { [key: string]: any }
16
+ | ClassValue[]
17
+
18
+ export interface ThemeConfig<S extends string = string> {
19
+ slots?: readonly S[] | S[]
20
+ base?: Partial<Record<S, ClassValue>>
21
+ variants?: Record<string, any>
22
+ compoundVariants?: Array<
23
+ Record<string, any> & {
24
+ classNames?: Partial<Record<S, ClassValue>>
25
+ class?: Partial<Record<S, ClassValue>>
26
+ }
27
+ >
28
+ defaultVariants?: Record<string, any>
29
+ }
30
+
9
31
  export type ColorModeInput =
32
+ | [chroma: number, hue: number]
33
+ | readonly [chroma: number, hue: number]
10
34
  | Record<string, string>
11
35
  | { light?: Record<string, string>; dark?: Record<string, string> }
12
36
 
@@ -42,13 +66,7 @@ export interface UIConfig extends PresetOptions {
42
66
  * Global component configuration overrides.
43
67
  */
44
68
  components?: {
45
- themes?: Record<
46
- string,
47
- {
48
- slots?: Record<string, string | ((base: string) => string) | undefined>
49
- [key: string]: unknown
50
- }
51
- >
69
+ themes?: Record<string, ThemeConfig<string>>
52
70
  }
53
71
  /**
54
72
  * Custom UnoCSS configuration overrides.
@@ -0,0 +1,4 @@
1
+ declare module "virtual:rimelight/ui" {
2
+ import type { UIConfig } from "./types"
3
+ export default {} as UIConfig
4
+ }
@@ -1,459 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, computed, watch, provide } from "vue"
3
- import type { Page, PageSurround, PageDefinition, PageVersion } from "./types/pages"
4
- import { scv, cx } from "css-variants"
5
- import { useI18n } from "./utils/editorHelpers"
6
- import { usePageEditor } from "./composables/usePageEditor"
7
- import { getLocalizedContent, syncPageWithDefinition, dehydratePageProperties } from "./utils/page"
8
- import PagePropertiesEditor from "./PagePropertiesEditor.vue"
9
- import PageRenderer from "./PageRenderer.vue"
10
- import BlockEditor from "./blocks/BlockEditor.vue"
11
- import PageSurroundComponent from "./PageSurround.vue"
12
- import DeletePageModal from "./modals/DeletePageModal.vue"
13
- import PageTreeModal from "./modals/PageTreeModal.vue"
14
- import RLVButton from "../button/RLVButton.vue"
15
- import RLVInput from "../input/RLVInput.vue"
16
- import PageVersionSelector from "./PageVersionSelector.vue"
17
-
18
- export interface PageEditorProps {
19
- isSaving: boolean
20
- isViewingVersion?: boolean
21
- useSurround?: boolean
22
- surround?: PageSurround | null
23
- surroundStatus?: "idle" | "pending" | "success" | "error"
24
- resolvePage: (id: string) => Promise<Pick<Page, "title" | "icon" | "slug">>
25
- pageDefinitions?: Record<string, PageDefinition>
26
- onDeletePage?: (id: string) => Promise<void>
27
- onFetchPages?: () => Promise<Pick<Page, "title" | "slug" | "type" | "id">[]>
28
- onNavigateToPage?: (slug: string) => void
29
- onViewPage?: (slug: string) => void
30
- basePath?: string
31
- isAdmin?: boolean
32
- }
33
-
34
- const {
35
- isSaving,
36
- isViewingVersion = false,
37
- useSurround = false,
38
- surroundStatus = "idle",
39
- surround = null,
40
- resolvePage,
41
- onDeletePage,
42
- onFetchPages,
43
- onNavigateToPage,
44
- onViewPage,
45
- basePath,
46
- isAdmin = false,
47
- pageDefinitions
48
- } = defineProps<PageEditorProps>()
49
- provide("page-resolver", resolvePage)
50
-
51
- export interface PageEditorEmits {
52
- "save": [value: Page]
53
- "version-navigate": [version: PageVersion]
54
- "version-approved": [version: PageVersion]
55
- "version-rejected": [version: PageVersion]
56
- "version-reverted": [version: PageVersion]
57
- "publish": [value: Page]
58
- "unpublish": [value: Page]
59
- }
60
- const emit = defineEmits<PageEditorEmits>()
61
-
62
- const pageEditorStyles = scv({
63
- slots: [
64
- "header",
65
- "headerGroup",
66
- "splitContainer",
67
- "editorColumn",
68
- "container",
69
- "grid",
70
- "toc",
71
- "properties",
72
- "contentWrapper",
73
- "banner",
74
- "icon",
75
- "title",
76
- "surroundSkeleton",
77
- "skeleton",
78
- "metadata",
79
- "resizer",
80
- "previewColumn",
81
- "headerText",
82
- "headerHighLight",
83
- "headerSeparator",
84
- "titleWrapper",
85
- "titleInput",
86
- "descriptionInput"
87
- ],
88
- base: {
89
- header:
90
- "h-12 w-full bg-neutral-100 dark:bg-neutral-800 flex items-center justify-between px-4 border-b border-default",
91
- headerGroup: "flex items-center gap-2",
92
- splitContainer: "flex w-full min-h-0",
93
- editorColumn: "relative overflow-y-auto p-6 flex-1 w-full min-w-0 max-w-7xl mx-auto",
94
- container: "flex flex-col py-8 w-full",
95
- grid: "grid grid-cols-1 lg:grid-cols-24 gap-8 items-start w-full",
96
- toc: "hidden lg:flex lg:col-span-4 sticky top-20 self-start",
97
- properties: "order-1 lg:order-2 lg:col-span-6",
98
- contentWrapper: "order-2 lg:order-1 lg:col-span-14 flex flex-col gap-6 w-full min-w-0",
99
- banner: "rounded-xl w-full object-cover max-h-[220px]",
100
- icon: "rounded-full w-12 h-12 object-cover border border-default",
101
- title: "",
102
- surroundSkeleton: "grid grid-cols-1 gap-4 sm:grid-cols-2 mt-8",
103
- skeleton: "h-48 w-full rounded-xl bg-neutral-100 dark:bg-neutral-800 animate-pulse",
104
- metadata:
105
- "flex flex-col gap-1 text-xs text-dimmed p-4 bg-neutral-50 dark:bg-neutral-900/40 rounded-xl border border-default mt-8",
106
- resizer:
107
- "sticky flex flex-col items-center justify-center w-6 cursor-col-resize group px-1 py-16 self-start",
108
- previewColumn: "sticky self-start overflow-y-auto min-h-0",
109
- headerText: "text-xs text-neutral-500 dark:text-neutral-400 font-medium whitespace-nowrap",
110
- headerHighLight: "text-neutral-900 dark:text-white font-semibold",
111
- headerSeparator: "w-px h-4 bg-neutral-200 dark:bg-neutral-700 mx-2",
112
- titleWrapper: "flex flex-row gap-2 items-center w-full",
113
- titleInput: "text-4xl font-bold p-0 focus:ring-0 shadow-none border-0 bg-transparent w-full",
114
- descriptionInput:
115
- "p-0 text-lg text-dimmed focus:ring-0 shadow-none border-0 bg-transparent w-full"
116
- }
117
- })
118
-
119
- const styles = computed(() => pageEditorStyles())
120
-
121
- const page = defineModel<Page>({ required: true })
122
- const versionId = defineModel<string | null>("currentVersionId", { default: null })
123
- const { undo, redo, canUndo, canRedo, captureSnapshot, resetHistory, pauseHistory, resumeHistory } =
124
- usePageEditor(page)
125
- const { t, locale } = useI18n()
126
-
127
- const showPreview = ref(false)
128
- const editorWidth = ref(50)
129
- const isDeleteModalOpen = ref(false)
130
- const isDeleting = ref(false)
131
- const isPageTreeModalOpen = ref(false)
132
- const isFetchingTree = ref(false)
133
- const treePages = ref<Pick<Page, "title" | "slug">[]>([])
134
-
135
- const currentDefinition = computed(() => {
136
- if (!page.value?.type || !pageDefinitions) return null
137
- return pageDefinitions[page.value.type]
138
- })
139
-
140
- const previousPage = computed(() => surround?.previous)
141
- const nextPage = computed(() => surround?.next)
142
- const hasSurround = computed(() => !!(surround?.previous || surround?.next))
143
-
144
- defineOptions({ name: "PageEditor" })
145
-
146
- watch(
147
- [() => page.value?.id, () => versionId.value, () => page.value?.type, currentDefinition],
148
- () => {
149
- if (page.value && currentDefinition.value) {
150
- syncPageWithDefinition(page.value, currentDefinition.value)
151
- if (!page.value.description) {
152
- page.value.description = { en: "" }
153
- }
154
- }
155
- },
156
- { immediate: true }
157
- )
158
-
159
- const handleViewPage = () => {
160
- if (onViewPage) {
161
- onViewPage(page.value.slug)
162
- } else if (typeof window !== "undefined") {
163
- window.location.href = `/${page.value.slug}`
164
- }
165
- }
166
-
167
- const handleSave = () => {
168
- if (currentDefinition.value) {
169
- syncPageWithDefinition(page.value, currentDefinition.value)
170
- }
171
- const dataToPersist = JSON.parse(JSON.stringify(page.value))
172
- dataToPersist.properties = dehydratePageProperties(dataToPersist.properties)
173
- emit("save", dataToPersist)
174
- }
175
-
176
- const handlePublishToggle = () => {
177
- if (currentDefinition.value) {
178
- syncPageWithDefinition(page.value, currentDefinition.value)
179
- }
180
- const dataToPersist = JSON.parse(JSON.stringify(page.value))
181
- dataToPersist.properties = dehydratePageProperties(dataToPersist.properties)
182
- if (page.value.postedAt) {
183
- emit("unpublish", dataToPersist)
184
- } else {
185
- emit("publish", dataToPersist)
186
- }
187
- }
188
-
189
- const handleDeleteConfirm = async () => {
190
- if (!onDeletePage || !page.value.id) return
191
- try {
192
- isDeleting.value = true
193
- await onDeletePage(page.value.id)
194
- isDeleteModalOpen.value = false
195
- } catch (err) {
196
- console.error("Failed to delete page:", err)
197
- } finally {
198
- isDeleting.value = false
199
- }
200
- }
201
-
202
- const handleOpenTree = async () => {
203
- if (!onFetchPages) return
204
- isFetchingTree.value = true
205
- try {
206
- treePages.value = await onFetchPages()
207
- isPageTreeModalOpen.value = true
208
- } catch (e) {
209
- console.error("Failed to fetch pages for tree", e)
210
- } finally {
211
- isFetchingTree.value = false
212
- }
213
- }
214
-
215
- const handleTreeNavigate = (slug: string) => {
216
- if (onNavigateToPage) {
217
- onNavigateToPage(slug)
218
- }
219
- }
220
-
221
- const removeBanner = () => {
222
- page.value.banner = undefined
223
- }
224
-
225
- defineExpose({ undo, redo, canUndo, canRedo, resetHistory })
226
- </script>
227
-
228
- <template>
229
- <div class="flex flex-col h-screen overflow-hidden">
230
- <div :class="styles.header">
231
- <div :class="styles.headerGroup">
232
- <RLVButton
233
- leading-icon="i-lucide-list-tree"
234
- variant="ghost"
235
- color="neutral"
236
- class="!p-1.5"
237
- :loading="isFetchingTree"
238
- :disabled="!onFetchPages"
239
- @click="handleOpenTree"
240
- />
241
- <span :class="styles.headerText">
242
- {{ t("common.editing", "Editing") }}:
243
- <span :class="styles.headerHighLight">{{ getLocalizedContent(page.title, locale) }}</span>
244
- </span>
245
- <span :class="styles.headerSeparator" />
246
- <PageVersionSelector
247
- v-if="page.id"
248
- v-model:current-version-id="versionId"
249
- :page-id="page.id"
250
- :is-admin="isAdmin"
251
- @version-selected="(v: any) => emit('version-navigate', v)"
252
- @version-approved="(v: any) => emit('version-approved', v)"
253
- @version-rejected="(v: any) => emit('version-rejected', v)"
254
- @version-reverted="(v: any) => emit('version-reverted', v)"
255
- />
256
- <RLVButton
257
- leading-icon="i-lucide-rotate-ccw"
258
- variant="ghost"
259
- color="neutral"
260
- class="!p-1.5"
261
- :disabled="!canUndo"
262
- @click="undo"
263
- />
264
- <RLVButton
265
- leading-icon="i-lucide-rotate-cw"
266
- variant="ghost"
267
- color="neutral"
268
- class="!p-1.5"
269
- :disabled="!canRedo"
270
- @click="redo"
271
- />
272
- </div>
273
-
274
- <div :class="styles.headerGroup">
275
- <RLVButton
276
- leading-icon="i-lucide-external-link"
277
- :label="t('page_editor.view_page', 'View Page')"
278
- variant="ghost"
279
- color="neutral"
280
- @click="handleViewPage"
281
- />
282
- <RLVButton
283
- :leading-icon="showPreview ? 'i-lucide-eye-off' : 'i-lucide-eye'"
284
- :label="t('page_editor.preview', 'Preview')"
285
- variant="ghost"
286
- color="neutral"
287
- @click="showPreview = !showPreview"
288
- />
289
- <span :class="styles.headerSeparator" />
290
- <slot name="header-actions" />
291
- <RLVButton
292
- leading-icon="i-lucide-globe"
293
- :label="
294
- page.postedAt
295
- ? t('page_editor.unpublish', 'Unpublish')
296
- : t('page_editor.publish', 'Publish')
297
- "
298
- color="neutral"
299
- variant="outline"
300
- :loading="isSaving"
301
- @click="handlePublishToggle"
302
- />
303
- <RLVButton
304
- leading-icon="i-lucide-save"
305
- :label="t('page_editor.save', 'Save')"
306
- color="primary"
307
- :loading="isSaving"
308
- @click="handleSave"
309
- />
310
- <RLVButton
311
- leading-icon="i-lucide-trash-2"
312
- variant="ghost"
313
- color="danger"
314
- class="!p-1.5"
315
- @click="isDeleteModalOpen = true"
316
- />
317
- </div>
318
- </div>
319
-
320
- <div :class="styles.splitContainer">
321
- <div
322
- :class="styles.editorColumn"
323
- :style="{ width: showPreview ? `${editorWidth}%` : '100%' }"
324
- >
325
- <div :class="styles.container">
326
- <div :class="styles.grid">
327
- <PagePropertiesEditor
328
- v-model="page"
329
- :on-fetch-pages="onFetchPages"
330
- :class="styles.properties"
331
- />
332
-
333
- <div :class="styles.contentWrapper">
334
- <div
335
- class="w-full relative group/banner aspect-[5/1] rounded-xl overflow-hidden bg-neutral-100 dark:bg-neutral-800 border border-default mb-4 flex items-center justify-center"
336
- >
337
- <img
338
- v-if="page.banner?.src"
339
- :src="page.banner?.src"
340
- :alt="page.banner?.alt || 'Banner'"
341
- class="absolute inset-0 size-full object-cover"
342
- />
343
- <div
344
- v-else
345
- class="flex flex-col items-center justify-center text-dimmed gap-1 opacity-50 hover:opacity-100 transition duration-150 cursor-pointer"
346
- >
347
- <span class="i-lucide-image size-8" />
348
- <span class="text-xs uppercase font-bold tracking-wider">
349
- {{ t("page_editor.add_banner", "Add Banner") }}
350
- </span>
351
- </div>
352
-
353
- <div
354
- class="absolute top-4 right-4 flex gap-2 opacity-0 group-hover/banner:opacity-100 transition"
355
- >
356
- <RLVButton
357
- v-if="page.banner?.src"
358
- leading-icon="i-lucide-trash"
359
- variant="solid"
360
- color="danger"
361
- class="!p-1.5"
362
- @click="removeBanner"
363
- />
364
- </div>
365
- </div>
366
-
367
- <div class="border-b border-default pb-4">
368
- <RLVInput
369
- v-model="page.title.en"
370
- placeholder="Page Title"
371
- class="w-full text-4xl font-bold bg-transparent border-0 focus:ring-0 focus:outline-none p-0"
372
- />
373
- <textarea
374
- v-if="page.description"
375
- v-model="page.description.en"
376
- placeholder="Add a description..."
377
- rows="2"
378
- class="w-full text-lg text-dimmed bg-transparent border-0 focus:ring-0 focus:outline-none p-0 resize-none mt-2"
379
- />
380
- </div>
381
-
382
- <BlockEditor
383
- v-model="page.blocks"
384
- @start="pauseHistory"
385
- @mutation="
386
- () => {
387
- resumeHistory()
388
- captureSnapshot()
389
- }
390
- "
391
- />
392
-
393
- <template v-if="useSurround">
394
- <div v-if="surroundStatus === 'pending'" :class="styles.surroundSkeleton">
395
- <div :class="styles.skeleton" />
396
- <div :class="styles.skeleton" />
397
- </div>
398
-
399
- <PageSurroundComponent
400
- v-else-if="surroundStatus === 'success' && hasSurround"
401
- :pageType="page.type"
402
- :previousTitle="getLocalizedContent(previousPage?.title, locale)"
403
- :previousDescription="getLocalizedContent(previousPage?.description, locale)"
404
- :previousTo="previousPage?.slug ? `/${previousPage.slug}` : undefined"
405
- :nextTitle="getLocalizedContent(nextPage?.title, locale)"
406
- :nextDescription="getLocalizedContent(nextPage?.description, locale)"
407
- :nextTo="nextPage?.slug ? `/${nextPage.slug}` : undefined"
408
- />
409
-
410
- <div :class="styles.metadata">
411
- <h6 class="font-bold">Metadata</h6>
412
- <span>ID: {{ page.id }}</span>
413
- <span v-if="page.createdAt">
414
- Created: {{ new Date(page.createdAt).toLocaleString() }}
415
- </span>
416
- <span v-if="page.updatedAt">
417
- Updated: {{ new Date(page.updatedAt).toLocaleString() }}
418
- </span>
419
- </div>
420
- </template>
421
- </div>
422
- </div>
423
- </div>
424
- </div>
425
-
426
- <div
427
- v-if="showPreview"
428
- :class="styles.previewColumn"
429
- :style="{
430
- width: `${100 - editorWidth}%`,
431
- height: 'calc(100vh - 48px)'
432
- }"
433
- >
434
- <PageRenderer
435
- v-model="page"
436
- :resolve-page="resolvePage"
437
- :use-surround="useSurround"
438
- :surround="surround"
439
- :surround-status="surroundStatus"
440
- />
441
- </div>
442
- </div>
443
- </div>
444
-
445
- <DeletePageModal
446
- v-model:open="isDeleteModalOpen"
447
- :loading="isDeleting"
448
- :page-title="getLocalizedContent(page.title, locale)"
449
- @confirm="handleDeleteConfirm"
450
- />
451
-
452
- <PageTreeModal
453
- v-model:open="isPageTreeModalOpen"
454
- :pages="treePages"
455
- :base-path="basePath"
456
- :loading="isFetchingTree"
457
- @navigate="handleTreeNavigate"
458
- />
459
- </template>
@@ -1,87 +0,0 @@
1
- <script setup lang="ts">
2
- import { inject, ref, watch, computed } from "vue"
3
- import { getLocalizedContent } from "./utils/page"
4
- import { useI18n } from "./utils/editorHelpers"
5
- import type { Page } from "./types/pages"
6
- import { scv, cx } from "css-variants"
7
-
8
- export interface PageMentionProps {
9
- pageId: string
10
- fallbackLabel?: string
11
- }
12
-
13
- const { pageId, fallbackLabel } = defineProps<PageMentionProps>()
14
-
15
- const pageMentionStyles = scv({
16
- slots: ["linkClass", "iconClass", "textClass", "skeletonClass", "unresolvedText"],
17
- base: {
18
- linkClass:
19
- "inline-flex items-baseline gap-1 align-middle text-primary hover:underline transition-colors font-medium text-inherit",
20
- iconClass: "w-4 h-4 rounded-full object-cover shrink-0",
21
- textClass: "truncate font-medium capitalize",
22
- skeletonClass:
23
- "h-3 w-24 rounded bg-neutral-200 dark:bg-neutral-800 animate-pulse inline-block align-middle",
24
- unresolvedText: "text-xs text-dimmed italic"
25
- }
26
- })
27
-
28
- const styles = computed(() => pageMentionStyles())
29
-
30
- const { locale } = useI18n()
31
- const resolver =
32
- inject<(id: string) => Promise<Pick<Page, "title" | "icon" | "slug">>>("page-resolver")
33
- const linkedPage = ref<Pick<Page, "title" | "icon" | "slug"> | null>(null)
34
- const status = ref<"idle" | "pending" | "success" | "error">("idle")
35
-
36
- const cleanPath = computed(() => (pageId || "").replace(/^\//, ""))
37
-
38
- const loadPage = async () => {
39
- if (!pageId) return
40
- status.value = "pending"
41
- if (!resolver) {
42
- status.value = "error"
43
- return
44
- }
45
- try {
46
- const res = await resolver(pageId)
47
- if (res && res.slug) {
48
- linkedPage.value = res
49
- status.value = "success"
50
- } else {
51
- status.value = "error"
52
- }
53
- } catch (e) {
54
- status.value = "error"
55
- }
56
- }
57
-
58
- watch(() => pageId, loadPage, { immediate: true })
59
-
60
- defineOptions({ name: "PageMention" })
61
- </script>
62
-
63
- <template>
64
- <a
65
- v-if="linkedPage && linkedPage.slug"
66
- :href="`/${linkedPage.slug.replace(/^\//, '')}`"
67
- :class="styles.linkClass"
68
- >
69
- <img
70
- v-if="linkedPage.icon?.src"
71
- :src="linkedPage.icon.src"
72
- :alt="linkedPage.icon.alt || 'Icon'"
73
- :class="styles.iconClass"
74
- />
75
- <span :class="styles.textClass">
76
- {{ getLocalizedContent(linkedPage.title, locale) }}
77
- </span>
78
- </a>
79
-
80
- <div v-else-if="status === 'pending'" :class="styles.skeletonClass" />
81
-
82
- <a v-else-if="cleanPath" :href="`/${cleanPath}`" :class="styles.linkClass">
83
- <span :class="styles.textClass">
84
- {{ fallbackLabel || cleanPath.split("/").pop()?.replace(/-/g, " ") }}
85
- </span>
86
- </a>
87
- </template>