@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.
- package/README.md +14 -12
- package/package.json +8 -10
- package/src/components/avatar/RLAAvatar.astro +4 -12
- package/src/components/badge/RLABadge.astro +2 -1
- package/src/components/badge/badge.theme.ts +0 -4
- package/src/components/banner/RLABanner.astro +1 -1
- package/src/components/banner/banner.theme.ts +1 -1
- package/src/components/breadcrumb/RLABreadcrumb.astro +2 -2
- package/src/components/button/RLAButton.astro +4 -1
- package/src/components/button/RLSButton.tsx +1 -1
- package/src/components/button/button.theme.ts +1 -5
- package/src/components/callout/RLACallout.astro +7 -5
- package/src/components/callout/callout.theme.ts +1 -5
- package/src/components/card/card.theme.ts +3 -3
- package/src/components/carousel/RLACarousel.astro +2 -2
- package/src/components/carousel/carousel.theme.ts +2 -2
- package/src/components/chart/RLAChart.astro +1 -1
- package/src/components/checkbox/RLACheckbox.astro +2 -1
- package/src/components/checkbox/checkbox.theme.ts +0 -4
- package/src/components/chip/RLAChip.astro +2 -1
- package/src/components/chip/chip.theme.ts +0 -4
- package/src/components/collapsible/RLACollapsible.astro +1 -1
- package/src/components/color-area/RLAColorArea.astro +1 -0
- package/src/components/color-area/color-area.theme.ts +1 -5
- package/src/components/color-field/RLAColorField.astro +1 -0
- package/src/components/color-field/color-field.theme.ts +1 -5
- package/src/components/color-slider/RLAColorSlider.astro +1 -0
- package/src/components/color-slider/color-slider.theme.ts +1 -5
- package/src/components/dashboard-group/RLADashboardGroup.astro +5 -7
- package/src/components/dashboard-group/dashboard-group.theme.ts +1 -1
- package/src/components/dashboard-group/dashboard-group.ts +1 -1
- package/src/components/dashboard-navbar/RLADashboardNavbar.astro +4 -4
- package/src/components/dashboard-navbar/dashboard-navbar.theme.ts +3 -3
- package/src/components/dashboard-navbar/dashboard-navbar.ts +1 -1
- package/src/components/dashboard-panel/RLADashboardPanel.astro +9 -8
- package/src/components/dashboard-panel/dashboard-panel.theme.ts +1 -1
- package/src/components/dashboard-sidebar/RLADashboardSidebar.astro +52 -20
- package/src/components/dashboard-sidebar/dashboard-sidebar.theme.ts +6 -5
- package/src/components/dashboard-sidebar-collapse/RLADashboardSidebarCollapse.astro +1 -0
- package/src/components/drawer/RLADrawer.astro +1 -1
- package/src/components/dropdown-menu/dropdown-menu.theme.ts +2 -2
- package/src/components/footer/RLAFooter.astro +2 -0
- package/src/components/footer/footer.theme.ts +0 -8
- package/src/components/gamepad/RLAGamepad.astro +1 -1
- package/src/components/head/UIHead.astro +1 -2
- package/src/components/header/header.theme.ts +1 -1
- package/src/components/icon/RLAIcon.astro +2 -5
- package/src/components/icon/RLSIcon.tsx +2 -2
- package/src/components/icon/RLVIcon.vue +18 -6
- package/src/components/icon/icon.theme.ts +1 -1
- package/src/components/image/image.theme.ts +1 -1
- package/src/components/input/RLAInput.astro +2 -1
- package/src/components/input/input.theme.ts +0 -4
- package/src/components/input-menu/RLAInputMenu.astro +2 -2
- package/src/components/input-menu/input-menu.theme.ts +1 -1
- package/src/components/input-rating/input-rating.theme.ts +2 -2
- package/src/components/kbd/RLAKbd.astro +2 -1
- package/src/components/kbd/kbd.theme.ts +0 -4
- package/src/components/keyboard/RLAKeyboard.astro +1 -1
- package/src/components/link/RLALink.astro +1 -0
- package/src/components/link/link.theme.ts +0 -4
- package/src/components/link-group/RLALinkGroup.astro +1 -1
- package/src/components/link-group/link-group.theme.ts +1 -10
- package/src/components/locale-selector/RLALocaleSelector.astro +4 -36
- package/src/components/locale-selector/locale-selector.theme.ts +3 -11
- package/src/components/logo/RLALogo.astro +1 -2
- package/src/components/modal/RLAModal.astro +8 -5
- package/src/components/modal/RLSModal.tsx +17 -12
- package/src/components/modal/modal.theme.ts +5 -4
- package/src/components/navigation-menu/RLANavigationMenu.astro +17 -21
- package/src/components/navigation-menu/navigation-menu.theme.ts +3 -7
- package/src/components/pagination/RLAPagination.astro +2 -2
- package/src/components/pagination/pagination.theme.ts +1 -1
- package/src/components/popover/RLAPopover.astro +39 -0
- package/src/components/progress/RLAProgress.astro +2 -0
- package/src/components/progress/progress.theme.ts +0 -4
- package/src/components/scroll-area/scroll-area.theme.ts +1 -1
- package/src/components/scroll-to-top/RLAScrollToTop.astro +6 -5
- package/src/components/scroll-to-top/RLVScrollToTop.vue +7 -7
- package/src/components/scroll-to-top/scroll-to-top.theme.ts +3 -7
- package/src/components/search/RLASearch.astro +3 -3
- package/src/components/select/RLASelect.astro +96 -528
- package/src/components/select/RLSSelect.tsx +68 -14
- package/src/components/select/RLVSelect.vue +57 -0
- package/src/components/select/select.theme.ts +11 -96
- package/src/components/shortcuts/RLAShortcuts.astro +9 -9
- package/src/components/sidebar/RLASidebar.astro +3 -3
- package/src/components/slideover/RLASlideover.astro +1 -1
- package/src/components/slider/RLASlider.astro +2 -1
- package/src/components/slider/slider.theme.ts +1 -5
- package/src/components/spinner/RLASpinner.astro +2 -1
- package/src/components/spinner/spinner.theme.ts +1 -5
- package/src/components/splitter/splitter.theme.ts +1 -1
- package/src/components/stepper/RLAStepper.astro +8 -8
- package/src/components/stepper/stepper.theme.ts +6 -7
- package/src/components/steps/RLASteps.astro +3 -3
- package/src/components/switch/RLASwitch.astro +2 -1
- package/src/components/switch/switch.theme.ts +0 -4
- package/src/components/table/table.theme.ts +1 -1
- package/src/components/table-of-contents/RLATableOfContents.astro +5 -5
- package/src/components/theme-selector/RLAThemeSelector.astro +16 -14
- package/src/components/theme-selector/theme-selector.theme.ts +2 -2
- package/src/components/toast/RLAToast.astro +6 -6
- package/src/components/toast/store.ts +1 -1
- package/src/components/tooltip/tooltip.theme.ts +1 -1
- package/src/icons.raw.ts +67 -0
- package/src/icons.ts +31 -64
- package/src/index.ts +19 -18
- package/src/preset.ts +110 -108
- package/src/resolver.ts +127 -15
- package/src/shortcuts.ts +103 -84
- package/src/stores.ts +22 -0
- package/src/types.ts +27 -9
- package/src/virtual.d.ts +4 -0
- package/src/components/editor/PageEditor.vue +0 -459
- package/src/components/editor/PageMention.vue +0 -87
- package/src/components/editor/PagePropertiesEditor.vue +0 -358
- package/src/components/editor/PagePropertiesRenderer.vue +0 -285
- package/src/components/editor/PageRenderer.vue +0 -155
- package/src/components/editor/PageSurround.vue +0 -130
- package/src/components/editor/PageVersionSelector.vue +0 -371
- package/src/components/editor/blocks/AddBlockModal.vue +0 -196
- package/src/components/editor/blocks/Block.vue +0 -142
- package/src/components/editor/blocks/BlockEditRenderer.vue +0 -26
- package/src/components/editor/blocks/BlockEditor.vue +0 -141
- package/src/components/editor/blocks/BlockViewRenderer.vue +0 -71
- package/src/components/editor/blocks/TextRenderer.vue +0 -31
- package/src/components/editor/blocks/editor/CalloutBlockEditor.vue +0 -53
- package/src/components/editor/blocks/editor/ImageBlockEditor.vue +0 -55
- package/src/components/editor/blocks/editor/ParagraphBlockEditor.vue +0 -31
- package/src/components/editor/blocks/editor/SectionBlockEditor.vue +0 -65
- package/src/components/editor/blocks/nodes/LinkNode.vue +0 -28
- package/src/components/editor/blocks/nodes/TextNode.vue +0 -22
- package/src/components/editor/blocks/renderer/CalloutBlockRenderer.vue +0 -65
- package/src/components/editor/blocks/renderer/ImageBlockRenderer.vue +0 -20
- package/src/components/editor/blocks/renderer/ParagraphBlockRenderer.vue +0 -21
- package/src/components/editor/blocks/renderer/SectionBlockRenderer.vue +0 -55
- package/src/components/editor/composables/injectionKeys.ts +0 -3
- package/src/components/editor/composables/useBlockEditor.ts +0 -261
- package/src/components/editor/composables/usePageEditor.ts +0 -97
- package/src/components/editor/modals/CreatePageModal.vue +0 -160
- package/src/components/editor/modals/DeletePageModal.vue +0 -116
- package/src/components/editor/modals/PageTreeModal.vue +0 -275
- package/src/components/editor/types/blocks.ts +0 -100
- package/src/components/editor/types/pages.ts +0 -89
- package/src/components/editor/types/schemas.ts +0 -36
- package/src/components/editor/utils/blocks.ts +0 -42
- package/src/components/editor/utils/editorHelpers.ts +0 -24
- package/src/components/editor/utils/page.ts +0 -219
- package/src/components/editor/utils/richTextHelpers.ts +0 -35
- package/src/components/editor/views/PageEditView.vue +0 -195
- package/src/components/editor/views/PageLiveView.vue +0 -90
- package/src/components/editor/views/PageReviewView.vue +0 -308
- 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>
|