@rimelight/ui 0.0.43 → 0.0.44

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 (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -12
  3. package/package.json +11 -14
  4. package/src/components/avatar/RLAAvatar.astro +4 -12
  5. package/src/components/badge/RLABadge.astro +2 -1
  6. package/src/components/badge/badge.theme.ts +0 -4
  7. package/src/components/banner/RLABanner.astro +1 -1
  8. package/src/components/banner/banner.theme.ts +1 -1
  9. package/src/components/breadcrumb/RLABreadcrumb.astro +2 -2
  10. package/src/components/button/RLAButton.astro +4 -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 +7 -4
  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 +1 -1
  68. package/src/components/modal/RLSModal.tsx +2 -2
  69. package/src/components/navigation-menu/RLANavigationMenu.astro +17 -21
  70. package/src/components/navigation-menu/navigation-menu.theme.ts +3 -7
  71. package/src/components/pagination/RLAPagination.astro +2 -2
  72. package/src/components/pagination/pagination.theme.ts +1 -1
  73. package/src/components/popover/RLAPopover.astro +39 -0
  74. package/src/components/progress/RLAProgress.astro +2 -0
  75. package/src/components/progress/progress.theme.ts +0 -4
  76. package/src/components/scroll-area/scroll-area.theme.ts +1 -1
  77. package/src/components/scroll-to-top/RLAScrollToTop.astro +6 -5
  78. package/src/components/scroll-to-top/RLVScrollToTop.vue +7 -7
  79. package/src/components/scroll-to-top/scroll-to-top.theme.ts +3 -7
  80. package/src/components/search/RLASearch.astro +3 -3
  81. package/src/components/select/RLASelect.astro +96 -528
  82. package/src/components/select/RLSSelect.tsx +68 -14
  83. package/src/components/select/RLVSelect.vue +57 -0
  84. package/src/components/select/select.theme.ts +11 -96
  85. package/src/components/shortcuts/RLAShortcuts.astro +9 -9
  86. package/src/components/sidebar/RLASidebar.astro +3 -3
  87. package/src/components/slideover/RLASlideover.astro +1 -1
  88. package/src/components/slider/RLASlider.astro +2 -1
  89. package/src/components/slider/slider.theme.ts +1 -5
  90. package/src/components/spinner/RLASpinner.astro +2 -1
  91. package/src/components/spinner/spinner.theme.ts +1 -5
  92. package/src/components/splitter/splitter.theme.ts +1 -1
  93. package/src/components/stepper/RLAStepper.astro +8 -8
  94. package/src/components/stepper/stepper.theme.ts +6 -7
  95. package/src/components/steps/RLASteps.astro +3 -3
  96. package/src/components/switch/RLASwitch.astro +2 -1
  97. package/src/components/switch/switch.theme.ts +0 -4
  98. package/src/components/table/table.theme.ts +1 -1
  99. package/src/components/table-of-contents/RLATableOfContents.astro +5 -5
  100. package/src/components/theme-selector/RLAThemeSelector.astro +16 -14
  101. package/src/components/theme-selector/theme-selector.theme.ts +2 -2
  102. package/src/components/toast/RLAToast.astro +6 -6
  103. package/src/components/toast/store.ts +1 -1
  104. package/src/components/tooltip/tooltip.theme.ts +1 -1
  105. package/src/icons.raw.ts +67 -0
  106. package/src/icons.ts +31 -64
  107. package/src/index.ts +19 -18
  108. package/src/preset.ts +110 -108
  109. package/src/resolver.ts +127 -15
  110. package/src/shortcuts.ts +103 -84
  111. package/src/stores.ts +22 -0
  112. package/src/types.ts +27 -9
  113. package/src/virtual.d.ts +4 -0
  114. package/src/components/editor/PageEditor.vue +0 -459
  115. package/src/components/editor/PageMention.vue +0 -87
  116. package/src/components/editor/PagePropertiesEditor.vue +0 -358
  117. package/src/components/editor/PagePropertiesRenderer.vue +0 -285
  118. package/src/components/editor/PageRenderer.vue +0 -155
  119. package/src/components/editor/PageSurround.vue +0 -130
  120. package/src/components/editor/PageVersionSelector.vue +0 -371
  121. package/src/components/editor/blocks/AddBlockModal.vue +0 -196
  122. package/src/components/editor/blocks/Block.vue +0 -142
  123. package/src/components/editor/blocks/BlockEditRenderer.vue +0 -26
  124. package/src/components/editor/blocks/BlockEditor.vue +0 -141
  125. package/src/components/editor/blocks/BlockViewRenderer.vue +0 -71
  126. package/src/components/editor/blocks/TextRenderer.vue +0 -31
  127. package/src/components/editor/blocks/editor/CalloutBlockEditor.vue +0 -53
  128. package/src/components/editor/blocks/editor/ImageBlockEditor.vue +0 -55
  129. package/src/components/editor/blocks/editor/ParagraphBlockEditor.vue +0 -31
  130. package/src/components/editor/blocks/editor/SectionBlockEditor.vue +0 -65
  131. package/src/components/editor/blocks/nodes/LinkNode.vue +0 -28
  132. package/src/components/editor/blocks/nodes/TextNode.vue +0 -22
  133. package/src/components/editor/blocks/renderer/CalloutBlockRenderer.vue +0 -65
  134. package/src/components/editor/blocks/renderer/ImageBlockRenderer.vue +0 -20
  135. package/src/components/editor/blocks/renderer/ParagraphBlockRenderer.vue +0 -21
  136. package/src/components/editor/blocks/renderer/SectionBlockRenderer.vue +0 -55
  137. package/src/components/editor/composables/injectionKeys.ts +0 -3
  138. package/src/components/editor/composables/useBlockEditor.ts +0 -261
  139. package/src/components/editor/composables/usePageEditor.ts +0 -97
  140. package/src/components/editor/modals/CreatePageModal.vue +0 -160
  141. package/src/components/editor/modals/DeletePageModal.vue +0 -116
  142. package/src/components/editor/modals/PageTreeModal.vue +0 -275
  143. package/src/components/editor/types/blocks.ts +0 -100
  144. package/src/components/editor/types/pages.ts +0 -89
  145. package/src/components/editor/types/schemas.ts +0 -36
  146. package/src/components/editor/utils/blocks.ts +0 -42
  147. package/src/components/editor/utils/editorHelpers.ts +0 -24
  148. package/src/components/editor/utils/page.ts +0 -219
  149. package/src/components/editor/utils/richTextHelpers.ts +0 -35
  150. package/src/components/editor/views/PageEditView.vue +0 -195
  151. package/src/components/editor/views/PageLiveView.vue +0 -90
  152. package/src/components/editor/views/PageReviewView.vue +0 -308
  153. package/src/components/icon/resolver.ts +0 -23
@@ -1,358 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, ref, reactive } from "vue"
3
- import type { Page } from "./types/pages"
4
- import type { Link } from "./types/schemas"
5
- import { scv, cx } from "css-variants"
6
- import { useI18n } from "./utils/editorHelpers"
7
- import { getLocalizedContent } from "./utils/page"
8
- import RLVButton from "../button/RLVButton.vue"
9
- import RLVInput from "../input/RLVInput.vue"
10
- import RLVFormField from "../form-field/RLVFormField.vue"
11
-
12
- export interface PagePropertiesEditorProps {
13
- onFetchPages?: () => Promise<Pick<Page, "title" | "slug" | "type" | "id">[]>
14
- }
15
-
16
- const { onFetchPages } = defineProps<PagePropertiesEditorProps>()
17
-
18
- const pagePropertiesEditorStyles = scv({
19
- slots: [
20
- "asideClass",
21
- "iconClass",
22
- "titleInputClass",
23
- "typeClass",
24
- "tagsClass",
25
- "imageClass",
26
- "groupButtonClass",
27
- "detailsClass",
28
- "fieldClass",
29
- "linksClass",
30
- "headerContent",
31
- "slugInput",
32
- "linksHeader",
33
- "linksTitle",
34
- "linksWrapper",
35
- "linkItem",
36
- "linkActions",
37
- "emptyLinks",
38
- "modalOverlay",
39
- "modalContent",
40
- "modalHeader",
41
- "modalBody",
42
- "modalGrid",
43
- "modalFooter"
44
- ],
45
- base: {
46
- asideClass:
47
- "flex flex-col gap-4 bg-neutral-50 dark:bg-neutral-900/50 p-4 rounded-xl border border-default w-full",
48
- iconClass: "rounded-full w-12 h-12 object-cover border border-default shadow-sm",
49
- titleInputClass: "w-full",
50
- typeClass: "text-xs text-dimmed uppercase tracking-wider text-center block mt-1",
51
- tagsClass: "flex flex-row flex-wrap gap-1 justify-center mt-2",
52
- imageClass: "w-full object-cover rounded-lg border border-default shadow-sm mt-4",
53
- groupButtonClass:
54
- "flex justify-between items-center w-full p-2.5 rounded-lg bg-neutral-100 dark:bg-neutral-800 text-left font-medium text-sm text-neutral-800 dark:text-neutral-200 hover:bg-neutral-200 dark:hover:bg-neutral-700 transition cursor-pointer mt-4",
55
- detailsClass:
56
- "p-3 flex flex-col gap-3 bg-white dark:bg-neutral-900 border border-default rounded-b-lg border-t-0 -mt-1",
57
- fieldClass: "w-full",
58
- linksClass: "flex flex-col gap-2 mt-4 pt-4 border-t border-default",
59
- headerContent: "flex flex-col gap-2 items-center text-center",
60
- slugInput:
61
- "w-full opacity-60 hover:opacity-100 focus-within:opacity-100 transition-opacity text-center text-xs font-mono bg-neutral-100 dark:bg-neutral-800/80 px-2 py-1 rounded border border-default",
62
- linksHeader: "flex items-center justify-between mb-2",
63
- linksTitle: "text-xs font-bold uppercase tracking-wider text-dimmed",
64
- linksWrapper: "flex flex-col gap-2",
65
- linkItem:
66
- "flex items-center justify-between p-2 rounded-lg border border-default bg-white dark:bg-neutral-900",
67
- linkActions: "flex items-center gap-1",
68
- emptyLinks: "text-xs text-dimmed italic",
69
- modalOverlay:
70
- "fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm",
71
- modalContent:
72
- "bg-white dark:bg-neutral-900 border border-default rounded-2xl w-full max-w-lg shadow-xl overflow-hidden flex flex-col",
73
- modalHeader: "flex items-center justify-between px-4 py-3 border-b border-default",
74
- modalBody: "flex flex-col gap-4 p-4",
75
- modalGrid: "grid grid-cols-2 gap-4",
76
- modalFooter: "flex justify-end gap-2 p-4 border-t border-default"
77
- }
78
- })
79
-
80
- const styles = computed(() => pagePropertiesEditorStyles())
81
-
82
- const page = defineModel<Page>({ required: true })
83
- const { t, locale } = useI18n()
84
-
85
- const sortedGroups = computed(() => {
86
- if (!page.value?.properties) return []
87
- return Object.entries(page.value.properties).toSorted((a, b) => {
88
- return (a[1] as any).order - (b[1] as any).order
89
- })
90
- })
91
-
92
- const getSortedFields = (fields: any) => {
93
- if (!fields) return []
94
- return Object.entries(fields).toSorted((a, b) => {
95
- return (a[1] as any).order - (b[1] as any).order
96
- })
97
- }
98
-
99
- const isLinkModalOpen = ref(false)
100
- const editingLinkIndex = ref<number | null>(null)
101
- const linkDraft = reactive<Partial<Link>>({
102
- label: "",
103
- to: "",
104
- icon: "",
105
- color: "neutral",
106
- variant: "link"
107
- })
108
-
109
- function openLinkModal(index: number | null = null) {
110
- editingLinkIndex.value = index
111
- if (index !== null && page.value.links?.[index]) {
112
- const link = page.value.links[index]!
113
- linkDraft.label = link.label
114
- linkDraft.to = link.to
115
- linkDraft.icon = link.icon
116
- linkDraft.color = link.color || "neutral"
117
- linkDraft.variant = link.variant || "link"
118
- } else {
119
- linkDraft.label = ""
120
- linkDraft.to = ""
121
- linkDraft.icon = ""
122
- linkDraft.color = "neutral"
123
- linkDraft.variant = "link"
124
- }
125
- isLinkModalOpen.value = true
126
- }
127
-
128
- function saveLink() {
129
- if (!linkDraft.label || !linkDraft.to) return
130
- if (!page.value.links) page.value.links = []
131
- const newLink: Link = {
132
- label: linkDraft.label,
133
- to: linkDraft.to!,
134
- icon: linkDraft.icon,
135
- color: (linkDraft.color as any) || "neutral",
136
- variant: (linkDraft.variant as any) || "link"
137
- }
138
- if (editingLinkIndex.value !== null) {
139
- page.value.links[editingLinkIndex.value] = newLink
140
- } else {
141
- page.value.links.push(newLink)
142
- }
143
- isLinkModalOpen.value = false
144
- }
145
-
146
- function removeLink(index: number) {
147
- if (page.value.links) {
148
- page.value.links.splice(index, 1)
149
- }
150
- }
151
-
152
- defineOptions({ name: "PagePropertiesEditor" })
153
- </script>
154
-
155
- <template>
156
- <aside :class="styles.asideClass">
157
- <div
158
- class="bg-white dark:bg-neutral-900 border border-default rounded-xl p-4 shadow-sm flex flex-col"
159
- >
160
- <div :class="styles.headerContent">
161
- <div class="relative group/icon mb-2">
162
- <img
163
- v-if="page.icon?.src"
164
- :src="page.icon?.src"
165
- :alt="page.icon?.alt || 'Icon'"
166
- :class="styles.iconClass"
167
- />
168
- <div
169
- v-else
170
- class="w-12 h-12 rounded-full bg-neutral-100 dark:bg-neutral-800 flex items-center justify-center border-2 border-dashed border-default"
171
- >
172
- <span class="i-lucide-image size-6 text-neutral-400" />
173
- </div>
174
- </div>
175
-
176
- <RLVInput
177
- v-model="page.title.en"
178
- placeholder="Enter page title..."
179
- class="w-full text-center font-bold text-lg"
180
- />
181
-
182
- <div class="mt-2 w-full">
183
- <RLVInput
184
- v-model="page.slug"
185
- placeholder="page-slug"
186
- class="w-full text-center text-xs font-mono opacity-80"
187
- />
188
- </div>
189
-
190
- <span :class="styles.typeClass">{{ page.type }}</span>
191
-
192
- <div v-if="page.tags?.length" :class="styles.tagsClass">
193
- <span
194
- v-for="(tag, index) in page.tags"
195
- :key="index"
196
- class="text-[10px] px-1.5 py-0.5 bg-neutral-100 dark:bg-neutral-800 rounded font-mono text-dimmed"
197
- >
198
- {{ getLocalizedContent(tag, locale) }}
199
- </span>
200
- </div>
201
-
202
- <div v-if="page.images?.length" class="w-full">
203
- <img
204
- v-if="page.images[0]"
205
- :src="page.images[0].src"
206
- :alt="page.images[0].alt || 'Page Image'"
207
- :class="styles.imageClass"
208
- />
209
- </div>
210
- </div>
211
-
212
- <div class="mt-4 space-y-2 w-full">
213
- <div v-for="[groupId, group] in sortedGroups" :key="groupId" class="w-full">
214
- <div :class="styles.groupButtonClass">
215
- <span>{{ getLocalizedContent((group as any).label, locale) }}</span>
216
- <span class="i-lucide-chevron-down size-4 opacity-60" />
217
- </div>
218
-
219
- <div :class="styles.detailsClass">
220
- <div
221
- v-for="[fieldKey, schema] in getSortedFields((group as any).fields)"
222
- :key="fieldKey"
223
- class="w-full"
224
- >
225
- <RLVFormField :label="getLocalizedContent((schema as any).label, locale)">
226
- <RLVInput
227
- v-if="(schema as any).type === 'text' || (schema as any).type === 'number'"
228
- v-model="(schema as any).defaultValue"
229
- placeholder="Type here..."
230
- class="w-full"
231
- />
232
-
233
- <select
234
- v-else-if="(schema as any).type === 'enum' && (schema as any).options"
235
- v-model="(schema as any).defaultValue"
236
- class="w-full bg-transparent border border-default rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary text-default bg-white dark:bg-neutral-900"
237
- >
238
- <option
239
- v-for="opt in (schema as any).options"
240
- :key="typeof opt === 'string' ? opt : opt.value"
241
- :value="typeof opt === 'string' ? opt : opt.value"
242
- >
243
- {{ typeof opt === "string" ? opt : getLocalizedContent(opt, locale) }}
244
- </option>
245
- </select>
246
-
247
- <div v-else class="text-xs text-dimmed italic">Complex property editor</div>
248
- </RLVFormField>
249
- </div>
250
- </div>
251
- </div>
252
- </div>
253
- </div>
254
-
255
- <div :class="styles.linksClass">
256
- <div :class="styles.linksHeader">
257
- <h6 :class="styles.linksTitle">
258
- {{ t("page_properties.links", "Links") }}
259
- </h6>
260
- <RLVButton
261
- leading-icon="i-lucide-plus"
262
- size="sm"
263
- variant="outline"
264
- color="neutral"
265
- class="!p-1"
266
- @click="openLinkModal()"
267
- />
268
- </div>
269
-
270
- <div v-if="page.links?.length" :class="styles.linksWrapper">
271
- <div v-for="(item, index) in page.links" :key="index" :class="styles.linkItem">
272
- <div class="flex items-center gap-2 text-sm text-neutral-800 dark:text-neutral-200">
273
- <span v-if="item.icon" :class="[item.icon, 'size-4 opacity-60']" />
274
- <span>{{ item.label }}</span>
275
- </div>
276
-
277
- <div :class="styles.linkActions">
278
- <RLVButton
279
- leading-icon="i-lucide-pencil"
280
- variant="ghost"
281
- color="neutral"
282
- class="!p-1"
283
- @click="openLinkModal(index)"
284
- />
285
- <RLVButton
286
- leading-icon="i-lucide-trash"
287
- variant="ghost"
288
- color="neutral"
289
- class="!p-1"
290
- @click="removeLink(index)"
291
- />
292
- </div>
293
- </div>
294
- </div>
295
-
296
- <p v-else :class="styles.emptyLinks">
297
- {{ t("page_properties.no_links", "No links added") }}
298
- </p>
299
- </div>
300
-
301
- <div v-if="isLinkModalOpen" :class="styles.modalOverlay" @click.self="isLinkModalOpen = false">
302
- <div :class="styles.modalContent">
303
- <div :class="styles.modalHeader">
304
- <h3 class="text-base font-semibold">
305
- {{
306
- editingLinkIndex !== null
307
- ? t("page_properties.edit_link", "Edit Link")
308
- : t("page_properties.add_link", "Add Link")
309
- }}
310
- </h3>
311
- <RLVButton
312
- color="neutral"
313
- variant="ghost"
314
- leading-leading-icon="i-lucide-x"
315
- class="!p-1.5"
316
- @click="isLinkModalOpen = false"
317
- />
318
- </div>
319
-
320
- <div :class="styles.modalBody">
321
- <RLVFormField :label="t('page_properties.label', 'Label')">
322
- <RLVInput
323
- v-model="linkDraft.label"
324
- placeholder="e.g. GitHub Repository"
325
- class="w-full"
326
- />
327
- </RLVFormField>
328
-
329
- <RLVFormField :label="t('page_properties.url', 'URL')">
330
- <RLVInput v-model="linkDraft.to" placeholder="https://github.com/..." class="w-full" />
331
- </RLVFormField>
332
-
333
- <RLVFormField :label="t('page_properties.icon', 'Icon Class')">
334
- <RLVInput v-model="linkDraft.icon" placeholder="i-lucide-github" class="w-full" />
335
- </RLVFormField>
336
- </div>
337
-
338
- <div :class="styles.modalFooter">
339
- <RLVButton
340
- color="neutral"
341
- variant="ghost"
342
- :label="t('common.cancel', 'Cancel')"
343
- @click="isLinkModalOpen = false"
344
- />
345
- <RLVButton
346
- color="primary"
347
- :label="
348
- editingLinkIndex !== null
349
- ? t('page_properties.update_link', 'Update')
350
- : t('page_properties.add_link', 'Add')
351
- "
352
- @click="saveLink"
353
- />
354
- </div>
355
- </div>
356
- </div>
357
- </aside>
358
- </template>
@@ -1,285 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from "vue"
3
- import type { Page } from "./types/pages"
4
- import { scv, cx } from "css-variants"
5
- import { useI18n } from "./utils/editorHelpers"
6
- import { getLocalizedContent } from "./utils/page"
7
- import RLVButton from "../button/RLVButton.vue"
8
- import PageMention from "./PageMention.vue"
9
-
10
- export interface PagePropertiesRendererProps {
11
- canEdit?: boolean
12
- editUrl?: string
13
- }
14
-
15
- const { canEdit = false, editUrl } = defineProps<PagePropertiesRendererProps>()
16
-
17
- const pagePropertiesRendererStyles = scv({
18
- slots: [
19
- "asideClass",
20
- "actionsClass",
21
- "iconClass",
22
- "titleClass",
23
- "typeClass",
24
- "tagsClass",
25
- "tabsClass",
26
- "imageClass",
27
- "groupButtonClass",
28
- "detailsClass",
29
- "fieldClass",
30
- "fieldLabelClass",
31
- "fieldValueClass",
32
- "listClass",
33
- "listItemClass",
34
- "pageArrayListClass",
35
- "pageArrayItemClass",
36
- "pageArrayBulletClass",
37
- "linksClass",
38
- "actionGroup",
39
- "headerContent",
40
- "imageWrapper"
41
- ],
42
- base: {
43
- asideClass:
44
- "flex flex-col gap-4 bg-neutral-50 dark:bg-neutral-900/50 p-4 rounded-xl border border-default w-full",
45
- actionsClass: "flex flex-row justify-between gap-2 mb-4",
46
- iconClass: "rounded-full w-12 h-12 object-cover border border-default shadow-sm",
47
- titleClass: "text-lg font-bold text-neutral-900 dark:text-neutral-100",
48
- typeClass: "text-xs text-dimmed uppercase tracking-wider",
49
- tagsClass: "flex flex-row flex-wrap gap-1 mt-2",
50
- tabsClass: "w-full mt-4",
51
- imageClass: "w-full object-cover rounded-lg border border-default shadow-sm",
52
- groupButtonClass:
53
- "flex justify-between items-center w-full p-2.5 rounded-lg bg-neutral-100 dark:bg-neutral-800 text-left font-medium text-sm text-neutral-800 dark:text-neutral-200 hover:bg-neutral-200 dark:hover:bg-neutral-700 transition cursor-pointer mt-4",
54
- detailsClass:
55
- "p-3 flex flex-col gap-2 bg-white dark:bg-neutral-900 border border-default rounded-b-lg border-t-0 -mt-1",
56
- fieldClass: "grid grid-cols-3 gap-2 items-baseline",
57
- fieldLabelClass: "text-xs font-semibold text-dimmed",
58
- fieldValueClass: "text-xs col-span-2 text-default",
59
- listClass: "flex flex-col list-disc list-inside",
60
- listItemClass: "font-medium",
61
- pageArrayListClass: "flex flex-col gap-y-1",
62
- pageArrayItemClass: "flex items-center gap-x-2",
63
- pageArrayBulletClass: "w-1 h-1 rounded-full bg-neutral-400 shrink-0",
64
- linksClass: "flex flex-col gap-2 mt-4 pt-4 border-t border-default",
65
- actionGroup: "flex flex-row gap-2",
66
- headerContent: "flex flex-col gap-2 items-center text-center",
67
- imageWrapper: "w-full mt-4"
68
- }
69
- })
70
-
71
- const styles = computed(() => pagePropertiesRendererStyles())
72
-
73
- const page = defineModel<Page>({ required: true })
74
- const { locale } = useI18n()
75
-
76
- const PREFERRED_GROUP_ORDER = ["identity", "characteristics", "inventory", "affiliations", "other"]
77
-
78
- const sortedGroups = computed(() => {
79
- if (!page.value?.properties) return []
80
- return Object.entries(page.value.properties).toSorted((a: any, b: any) => {
81
- const keyA = a[0].toLowerCase()
82
- const keyB = b[0].toLowerCase()
83
- const orderA = a[1]?.order ?? PREFERRED_GROUP_ORDER.indexOf(keyA)
84
- const orderB = b[1]?.order ?? PREFERRED_GROUP_ORDER.indexOf(keyB)
85
- const normA = orderA === -1 ? 999 : orderA
86
- const normB = orderB === -1 ? 999 : orderB
87
- return normA - normB
88
- })
89
- })
90
-
91
- const getGroupFields = (group: any) => {
92
- if (!group) return []
93
- const fieldsObj = group.fields || group
94
- return Object.entries(fieldsObj)
95
- .filter(([key]) => key !== "order" && key !== "label" && key !== "icon")
96
- .toSorted((a: any, b: any) => {
97
- const orderA = typeof a[1] === "object" && a[1]?.order !== undefined ? a[1].order : 999
98
- const orderB = typeof b[1] === "object" && b[1]?.order !== undefined ? b[1].order : 999
99
- return orderA - orderB
100
- })
101
- }
102
-
103
- const getFieldValue = (fieldVal: any) => {
104
- if (fieldVal === null || fieldVal === undefined) return null
105
- if (typeof fieldVal === "object" && !Array.isArray(fieldVal)) {
106
- if (fieldVal.defaultValue !== undefined) return fieldVal.defaultValue
107
- if (fieldVal.value !== undefined) return fieldVal.value
108
- }
109
- return fieldVal
110
- }
111
-
112
- const isFieldVisible = (fieldVal: any) => {
113
- const val = getFieldValue(fieldVal)
114
- if (val === null || val === undefined || val === "") return false
115
- if (Array.isArray(val) && val.length === 0) return false
116
- if (typeof val === "number") return true
117
- if (typeof val === "string") return val.trim().length > 0
118
- if (typeof val === "object") {
119
- const loc = getLocalizedContent(val, locale.value)
120
- if (loc && String(loc).trim().length > 0) return true
121
- if (val.slug || val.id || val.label) return true
122
- }
123
- return true
124
- }
125
-
126
- const shouldRenderGroup = (group: any) => {
127
- if (!group) return false
128
- const fields = getGroupFields(group)
129
- return fields.some(([_, val]) => isFieldVisible(val))
130
- }
131
-
132
- const formatTitleCase = (str: string) => {
133
- return str
134
- .replace(/([A-Z])/g, " $1")
135
- .replace(/[-_]/g, " ")
136
- .replace(/^./, (s) => s.toUpperCase())
137
- .trim()
138
- }
139
-
140
- const formatGroupLabel = (groupId: string, group: any) => {
141
- if (group?.label) return getLocalizedContent(group.label, locale.value)
142
- return formatTitleCase(groupId)
143
- }
144
-
145
- const formatFieldLabel = (fieldKey: string, schema: any) => {
146
- if (schema?.label) return getLocalizedContent(schema.label, locale.value)
147
- return formatTitleCase(fieldKey)
148
- }
149
-
150
- function isMentionObject(obj: any): boolean {
151
- if (!obj || typeof obj !== "object") return false
152
- return Boolean(obj.slug || obj.id || obj.pageId)
153
- }
154
-
155
- function getMentionPageId(obj: any): string {
156
- if (typeof obj === "string") return obj
157
- return String(obj.slug || obj.id || obj.pageId || "")
158
- }
159
-
160
- defineOptions({ name: "PagePropertiesRenderer" })
161
- </script>
162
-
163
- <template>
164
- <aside :class="styles.asideClass">
165
- <div :class="styles.actionsClass">
166
- <div :class="styles.actionGroup">
167
- <RLVButton variant="outline" color="neutral" leading-icon="i-lucide-share" class="!p-1.5" />
168
- <RLVButton variant="outline" color="neutral" leading-icon="i-lucide-link" class="!p-1.5" />
169
- </div>
170
- <div v-if="canEdit && editUrl" :class="styles.actionGroup">
171
- <RLVButton
172
- variant="outline"
173
- color="neutral"
174
- leading-icon="i-lucide-pencil"
175
- class="!p-1.5"
176
- :href="editUrl"
177
- />
178
- </div>
179
- </div>
180
-
181
- <div
182
- class="bg-white dark:bg-neutral-900 border border-default rounded-xl p-4 shadow-sm flex flex-col"
183
- >
184
- <div :class="styles.headerContent">
185
- <img
186
- v-if="page.icon?.src"
187
- :src="page.icon?.src"
188
- :alt="page.icon?.alt || 'Icon'"
189
- :class="styles.iconClass"
190
- />
191
- <h3 :class="styles.titleClass">
192
- {{ getLocalizedContent(page.title, locale) }}
193
- </h3>
194
- <span :class="styles.typeClass">{{ page.type }}</span>
195
- <div v-if="page.tags?.length" :class="styles.tagsClass">
196
- <span
197
- v-for="(tag, index) in page.tags"
198
- :key="index"
199
- class="text-[10px] px-1.5 py-0.5 bg-neutral-100 dark:bg-neutral-800 rounded font-mono text-dimmed"
200
- >
201
- {{ getLocalizedContent(tag, locale) }}
202
- </span>
203
- </div>
204
- <div v-if="page.images?.length" :class="styles.imageWrapper">
205
- <div v-if="page.images[0]">
206
- <img
207
- :src="page.images[0].src"
208
- :alt="page.images[0].alt || 'Page Image'"
209
- :class="styles.imageClass"
210
- />
211
- </div>
212
- </div>
213
- </div>
214
-
215
- <div class="mt-4 space-y-2">
216
- <div v-for="[groupId, group] in sortedGroups" :key="groupId">
217
- <div v-if="shouldRenderGroup(group)">
218
- <div :class="styles.groupButtonClass">
219
- <span>{{ formatGroupLabel(groupId, group) }}</span>
220
- <span class="i-lucide-chevron-down size-4 opacity-60" />
221
- </div>
222
-
223
- <dl :class="styles.detailsClass">
224
- <template v-for="[fieldKey, schema] in getGroupFields(group)" :key="fieldKey">
225
- <div
226
- v-if="isFieldVisible(schema)"
227
- class="grid grid-cols-3 gap-2 py-1 items-baseline"
228
- >
229
- <dt :class="styles.fieldLabelClass">
230
- {{ formatFieldLabel(fieldKey, schema) }}
231
- </dt>
232
- <dd :class="styles.fieldValueClass">
233
- <!-- Mention object -->
234
- <template v-if="isMentionObject(getFieldValue(schema))">
235
- <PageMention :page-id="getMentionPageId(getFieldValue(schema))" />
236
- </template>
237
-
238
- <!-- Array of values -->
239
- <ul v-else-if="Array.isArray(getFieldValue(schema))" :class="styles.listClass">
240
- <li v-for="(item, idx) in getFieldValue(schema)" :key="idx">
241
- <PageMention
242
- v-if="isMentionObject(item)"
243
- :page-id="getMentionPageId(item)"
244
- />
245
- <span v-else :class="styles.listItemClass">
246
- {{ getLocalizedContent(item, locale) }}
247
- </span>
248
- </li>
249
- </ul>
250
-
251
- <!-- Number -->
252
- <span v-else-if="typeof getFieldValue(schema) === 'number'">
253
- {{ getFieldValue(schema) }}
254
- </span>
255
-
256
- <!-- Standard text / localized object -->
257
- <span v-else>
258
- {{ getLocalizedContent(getFieldValue(schema), locale) }}
259
- </span>
260
- </dd>
261
- </div>
262
- </template>
263
- </dl>
264
- </div>
265
- </div>
266
- </div>
267
- </div>
268
-
269
- <div v-if="page.links?.length" :class="styles.linksClass">
270
- <h6 class="text-xs uppercase font-bold text-dimmed tracking-wider mb-2">Links</h6>
271
- <div class="flex flex-col gap-2">
272
- <a
273
- v-for="(linkItem, index) in page.links"
274
- :key="index"
275
- :href="linkItem.to"
276
- target="_blank"
277
- class="flex items-center gap-2 p-2 rounded-lg border border-default hover:bg-neutral-50 dark:hover:bg-neutral-800 transition text-sm text-neutral-800 dark:text-neutral-200"
278
- >
279
- <span v-if="linkItem.icon" :class="[linkItem.icon, 'size-4 shrink-0 opacity-60']" />
280
- <span>{{ linkItem.label }}</span>
281
- </a>
282
- </div>
283
- </div>
284
- </aside>
285
- </template>