@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,195 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, ref, watch, nextTick } from "vue"
|
|
3
|
-
import type { Page, PageVersion, PageDefinition } from "../types/pages"
|
|
4
|
-
import { getPageResolutionPath } from "../utils/page"
|
|
5
|
-
import PageEditor from "../PageEditor.vue"
|
|
6
|
-
|
|
7
|
-
export interface PageEditViewProps {
|
|
8
|
-
lookupPath?: string
|
|
9
|
-
fetchUrl?: string
|
|
10
|
-
cacheKey?: string
|
|
11
|
-
pageDefinitions: Record<string, PageDefinition>
|
|
12
|
-
baseUrl: string
|
|
13
|
-
hierarchyPath?: string
|
|
14
|
-
isAdmin?: boolean
|
|
15
|
-
backUrl?: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const {
|
|
19
|
-
lookupPath,
|
|
20
|
-
fetchUrl,
|
|
21
|
-
cacheKey,
|
|
22
|
-
pageDefinitions,
|
|
23
|
-
baseUrl,
|
|
24
|
-
hierarchyPath,
|
|
25
|
-
isAdmin = false,
|
|
26
|
-
backUrl
|
|
27
|
-
} = defineProps<PageEditViewProps>()
|
|
28
|
-
|
|
29
|
-
const resolvedFetchUrl = computed(() => getPageResolutionPath(lookupPath || fetchUrl || ""))
|
|
30
|
-
const isSaving = ref(false)
|
|
31
|
-
const currentVersionId = ref<string | null>(null)
|
|
32
|
-
const editorRef = ref<{
|
|
33
|
-
resetHistory: () => void
|
|
34
|
-
} | null>(null)
|
|
35
|
-
const localPage = ref<Page | null>(null)
|
|
36
|
-
const pageData = ref<Page | null>(null)
|
|
37
|
-
const pageStatus = ref<"idle" | "pending" | "success" | "error">("idle")
|
|
38
|
-
|
|
39
|
-
const fetchPageDetails = async () => {
|
|
40
|
-
if (!resolvedFetchUrl.value) return
|
|
41
|
-
pageStatus.value = "pending"
|
|
42
|
-
try {
|
|
43
|
-
const res = await fetch(resolvedFetchUrl.value)
|
|
44
|
-
if (!res.ok) throw new Error("Failed to fetch")
|
|
45
|
-
const data = await res.json()
|
|
46
|
-
pageData.value = data
|
|
47
|
-
localPage.value = JSON.parse(JSON.stringify(data))
|
|
48
|
-
pageStatus.value = "success"
|
|
49
|
-
} catch (err) {
|
|
50
|
-
pageStatus.value = "error"
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
watch(() => resolvedFetchUrl.value, fetchPageDetails, { immediate: true })
|
|
55
|
-
|
|
56
|
-
const resolvePage = async (idOrSlug: string): Promise<Pick<Page, "title" | "icon" | "slug">> => {
|
|
57
|
-
const url = getPageResolutionPath(idOrSlug)
|
|
58
|
-
if (!url) throw new Error(`[Editor] Invalid page reference: ${idOrSlug}`)
|
|
59
|
-
const res = await fetch(`${url}?select=title,icon,slug`)
|
|
60
|
-
if (!res.ok) throw new Error(`[Editor] Page not found: ${idOrSlug}`)
|
|
61
|
-
return await res.json()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const fetchPages = async () => {
|
|
65
|
-
const types = Object.keys(pageDefinitions).join(",")
|
|
66
|
-
const prefix = (hierarchyPath ?? baseUrl).replace(/^\/|\/$/g, "")
|
|
67
|
-
const res = await fetch(
|
|
68
|
-
`/api/pages/list?select=title,slug,type,id&types=${types}&prefix=${prefix}`
|
|
69
|
-
)
|
|
70
|
-
if (!res.ok) throw new Error("Failed to fetch page list")
|
|
71
|
-
return await res.json()
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const handleSave = async (updatedPage: Page): Promise<void> => {
|
|
75
|
-
if (!updatedPage.id) return
|
|
76
|
-
isSaving.value = true
|
|
77
|
-
try {
|
|
78
|
-
await fetch(`/api/pages/id/${updatedPage.id}`, {
|
|
79
|
-
method: "PUT",
|
|
80
|
-
headers: { "Content-Type": "application/json" },
|
|
81
|
-
body: JSON.stringify(updatedPage)
|
|
82
|
-
})
|
|
83
|
-
await fetchPageDetails()
|
|
84
|
-
await nextTick()
|
|
85
|
-
editorRef.value?.resetHistory()
|
|
86
|
-
} catch (e) {
|
|
87
|
-
console.error(e)
|
|
88
|
-
} finally {
|
|
89
|
-
isSaving.value = false
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const handlePublish = async (updatedPage: Page): Promise<void> => {
|
|
94
|
-
if (!updatedPage.id) return
|
|
95
|
-
isSaving.value = true
|
|
96
|
-
try {
|
|
97
|
-
await fetch(`/api/pages/id/${updatedPage.id}/publish`, {
|
|
98
|
-
method: "POST"
|
|
99
|
-
})
|
|
100
|
-
const base = baseUrl.replace(/\/$/, "")
|
|
101
|
-
const cleanBase = base.replace(/^\//, "")
|
|
102
|
-
const slugToUse = updatedPage.slug.startsWith(cleanBase)
|
|
103
|
-
? updatedPage.slug
|
|
104
|
-
: `${cleanBase}/${updatedPage.slug}`
|
|
105
|
-
if (typeof window !== "undefined") {
|
|
106
|
-
window.location.href = `/${slugToUse}`
|
|
107
|
-
}
|
|
108
|
-
} catch (e) {
|
|
109
|
-
console.error(e)
|
|
110
|
-
} finally {
|
|
111
|
-
isSaving.value = false
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const handleUnpublish = async (updatedPage: Page): Promise<void> => {
|
|
116
|
-
if (!updatedPage.id) return
|
|
117
|
-
isSaving.value = true
|
|
118
|
-
try {
|
|
119
|
-
await fetch(`/api/pages/id/${updatedPage.id}/unpublish`, {
|
|
120
|
-
method: "POST"
|
|
121
|
-
})
|
|
122
|
-
await fetchPageDetails()
|
|
123
|
-
} catch (e) {
|
|
124
|
-
console.error(e)
|
|
125
|
-
} finally {
|
|
126
|
-
isSaving.value = false
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const handleDelete = async (id: string) => {
|
|
131
|
-
try {
|
|
132
|
-
await fetch(`/api/pages/id/${id}`, {
|
|
133
|
-
method: "DELETE"
|
|
134
|
-
})
|
|
135
|
-
const dest = backUrl || baseUrl
|
|
136
|
-
if (typeof window !== "undefined") {
|
|
137
|
-
window.location.href = dest
|
|
138
|
-
}
|
|
139
|
-
} catch (e) {
|
|
140
|
-
console.error(e)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const handleVersionNavigate = (version: PageVersion) => {
|
|
145
|
-
if (!version?.id || !localPage.value?.slug) return
|
|
146
|
-
const base = baseUrl.replace(/\/$/, "")
|
|
147
|
-
const cleanBase = base.replace(/^\//, "")
|
|
148
|
-
const slugToUse = localPage.value.slug.startsWith(cleanBase)
|
|
149
|
-
? localPage.value.slug
|
|
150
|
-
: `${cleanBase}/${localPage.value.slug}`
|
|
151
|
-
if (typeof window !== "undefined") {
|
|
152
|
-
window.location.href = `/${slugToUse}/review?version=${version.id}`
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const handleNavigateToPage = (slug: string) => {
|
|
157
|
-
const base = baseUrl.replace(/\/$/, "")
|
|
158
|
-
const cleanBase = base.replace(/^\//, "")
|
|
159
|
-
const slugToUse = slug.startsWith(cleanBase) ? slug : `${cleanBase}/${slug}`
|
|
160
|
-
if (typeof window !== "undefined") {
|
|
161
|
-
window.location.href = `/${slugToUse}/edit`
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
defineOptions({ name: "PageEditView" })
|
|
166
|
-
</script>
|
|
167
|
-
|
|
168
|
-
<template>
|
|
169
|
-
<div v-if="pageStatus === 'pending'" class="h-full w-full flex items-center justify-center p-12">
|
|
170
|
-
<span class="i-lucide-loader-2 size-12 animate-spin text-neutral-400" />
|
|
171
|
-
</div>
|
|
172
|
-
<div v-else-if="pageStatus === 'error' || !localPage" class="p-8 text-center">
|
|
173
|
-
<h3 class="text-lg font-bold text-red-600">Page Not Found</h3>
|
|
174
|
-
<p class="text-neutral-500 mt-2">The requested page could not be loaded.</p>
|
|
175
|
-
</div>
|
|
176
|
-
<template v-else-if="localPage && localPage.id">
|
|
177
|
-
<PageEditor
|
|
178
|
-
ref="editorRef"
|
|
179
|
-
v-model="localPage"
|
|
180
|
-
v-model:current-version-id="currentVersionId"
|
|
181
|
-
:is-saving="isSaving"
|
|
182
|
-
:is-admin="isAdmin"
|
|
183
|
-
:base-path="(hierarchyPath ?? baseUrl).replace(/^\/|\/$/g, '')"
|
|
184
|
-
:page-definitions="pageDefinitions"
|
|
185
|
-
:resolve-page="resolvePage"
|
|
186
|
-
:on-fetch-pages="fetchPages"
|
|
187
|
-
:on-delete-page="handleDelete"
|
|
188
|
-
:on-navigate-to-page="handleNavigateToPage"
|
|
189
|
-
@save="handleSave"
|
|
190
|
-
@publish="handlePublish"
|
|
191
|
-
@unpublish="handleUnpublish"
|
|
192
|
-
@version-navigate="handleVersionNavigate"
|
|
193
|
-
/>
|
|
194
|
-
</template>
|
|
195
|
-
</template>
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, ref, watch } from "vue"
|
|
3
|
-
import type { Page } from "../types/pages"
|
|
4
|
-
import { getPageResolutionPath } from "../utils/page"
|
|
5
|
-
import PageRenderer from "../PageRenderer.vue"
|
|
6
|
-
|
|
7
|
-
export interface PageLiveViewProps {
|
|
8
|
-
lookupPath?: string
|
|
9
|
-
fetchUrl?: string
|
|
10
|
-
cacheKey?: string
|
|
11
|
-
baseUrl: string
|
|
12
|
-
canEdit?: boolean
|
|
13
|
-
}
|
|
14
|
-
const {
|
|
15
|
-
lookupPath,
|
|
16
|
-
fetchUrl,
|
|
17
|
-
cacheKey,
|
|
18
|
-
baseUrl,
|
|
19
|
-
canEdit = false
|
|
20
|
-
} = defineProps<PageLiveViewProps>()
|
|
21
|
-
|
|
22
|
-
const resolvedFetchUrl = computed(() => getPageResolutionPath(lookupPath || fetchUrl || ""))
|
|
23
|
-
const page = ref<Page | null>(null)
|
|
24
|
-
const pageStatus = ref<"idle" | "pending" | "success" | "error">("idle")
|
|
25
|
-
|
|
26
|
-
const fetchPage = async () => {
|
|
27
|
-
if (!resolvedFetchUrl.value) return
|
|
28
|
-
pageStatus.value = "pending"
|
|
29
|
-
try {
|
|
30
|
-
const res = await fetch(resolvedFetchUrl.value)
|
|
31
|
-
if (!res.ok) throw new Error("Failed to fetch page")
|
|
32
|
-
const data = await res.json()
|
|
33
|
-
page.value = data
|
|
34
|
-
pageStatus.value = "success"
|
|
35
|
-
} catch (err) {
|
|
36
|
-
pageStatus.value = "error"
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
watch(() => resolvedFetchUrl.value, fetchPage, { immediate: true })
|
|
41
|
-
|
|
42
|
-
const resolvePage = async (idOrSlug: string) => {
|
|
43
|
-
if (!idOrSlug) return null
|
|
44
|
-
const clean = idOrSlug.replace(/^\//, "")
|
|
45
|
-
try {
|
|
46
|
-
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(clean)
|
|
47
|
-
const url = isUuid ? `/api/pages/id/${clean}` : `/api/pages/find/${clean}`
|
|
48
|
-
const res = await fetch(url)
|
|
49
|
-
if (res.ok) return await res.json()
|
|
50
|
-
const res2 = await fetch(`/api/pages/find/${clean}`)
|
|
51
|
-
if (res2.ok) return await res2.json()
|
|
52
|
-
return null
|
|
53
|
-
} catch (err) {
|
|
54
|
-
console.warn("[PageLiveView resolvePage Error]:", err)
|
|
55
|
-
return null
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const editUrl = computed(() => {
|
|
60
|
-
const base = baseUrl.replace(/\/$/, "")
|
|
61
|
-
if (page.value?.slug) {
|
|
62
|
-
const slug = page.value.slug
|
|
63
|
-
const cleanBase = base.replace(/^\//, "")
|
|
64
|
-
if (slug.startsWith(cleanBase)) {
|
|
65
|
-
return `/${slug}/edit`
|
|
66
|
-
}
|
|
67
|
-
return `${base}/${slug}/edit`
|
|
68
|
-
}
|
|
69
|
-
return `${base}/edit`
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
defineOptions({ name: "PageLiveView" })
|
|
73
|
-
</script>
|
|
74
|
-
|
|
75
|
-
<template>
|
|
76
|
-
<div v-if="pageStatus === 'pending'" class="h-full w-full flex items-center justify-center p-12">
|
|
77
|
-
<span class="i-lucide-loader-2 size-12 animate-spin text-neutral-400" />
|
|
78
|
-
</div>
|
|
79
|
-
<div v-else-if="pageStatus === 'error' || !page" class="p-8 text-center">
|
|
80
|
-
<h3 class="text-lg font-bold text-red-600">Page Not Found</h3>
|
|
81
|
-
<p class="text-neutral-500 mt-2">The requested page could not be located.</p>
|
|
82
|
-
</div>
|
|
83
|
-
<PageRenderer
|
|
84
|
-
v-else
|
|
85
|
-
v-model="page"
|
|
86
|
-
:resolve-page="resolvePage"
|
|
87
|
-
:can-edit="canEdit"
|
|
88
|
-
:edit-url="editUrl"
|
|
89
|
-
/>
|
|
90
|
-
</template>
|
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { ref, computed, watch } from "vue"
|
|
3
|
-
import type { Page, PageVersion, PageDefinition } from "../types/pages"
|
|
4
|
-
import {
|
|
5
|
-
convertVersionToPage,
|
|
6
|
-
syncPageWithDefinition,
|
|
7
|
-
getPageResolutionPath,
|
|
8
|
-
getLocalizedContent
|
|
9
|
-
} from "../utils/page"
|
|
10
|
-
import { useI18n } from "../utils/editorHelpers"
|
|
11
|
-
import PageRenderer from "../PageRenderer.vue"
|
|
12
|
-
import RLVButton from "../../button/RLVButton.vue"
|
|
13
|
-
import PageVersionSelector from "../PageVersionSelector.vue"
|
|
14
|
-
|
|
15
|
-
export interface PageReviewViewProps {
|
|
16
|
-
lookupPath: string
|
|
17
|
-
versionId?: string | null
|
|
18
|
-
pageDefinitions: Record<string, PageDefinition>
|
|
19
|
-
baseUrl: string
|
|
20
|
-
isAdmin?: boolean
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const {
|
|
24
|
-
lookupPath,
|
|
25
|
-
versionId,
|
|
26
|
-
pageDefinitions,
|
|
27
|
-
baseUrl,
|
|
28
|
-
isAdmin = false
|
|
29
|
-
} = defineProps<PageReviewViewProps>()
|
|
30
|
-
|
|
31
|
-
export interface PageReviewViewEmits {
|
|
32
|
-
approved: [version: PageVersion]
|
|
33
|
-
rejected: [version: PageVersion]
|
|
34
|
-
}
|
|
35
|
-
const emit = defineEmits<PageReviewViewEmits>()
|
|
36
|
-
|
|
37
|
-
const { t, locale } = useI18n()
|
|
38
|
-
|
|
39
|
-
const page = ref<Page | null>(null)
|
|
40
|
-
const pageStatus = ref<"idle" | "pending" | "success" | "error">("idle")
|
|
41
|
-
|
|
42
|
-
const fetchBasePage = async () => {
|
|
43
|
-
const url = getPageResolutionPath(lookupPath)
|
|
44
|
-
if (!url) return
|
|
45
|
-
pageStatus.value = "pending"
|
|
46
|
-
try {
|
|
47
|
-
const res = await fetch(url)
|
|
48
|
-
if (!res.ok) throw new Error("Failed to fetch page")
|
|
49
|
-
page.value = await res.json()
|
|
50
|
-
pageStatus.value = "success"
|
|
51
|
-
} catch (err) {
|
|
52
|
-
pageStatus.value = "error"
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const versionData = ref<PageVersion | null>(null)
|
|
57
|
-
const versionStatus = ref<"idle" | "pending" | "success" | "error">("idle")
|
|
58
|
-
|
|
59
|
-
const fetchVersionData = async () => {
|
|
60
|
-
if (!versionId) {
|
|
61
|
-
versionData.value = null
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
versionStatus.value = "pending"
|
|
65
|
-
try {
|
|
66
|
-
const res = await fetch(`/api/pages/versions/${versionId}`)
|
|
67
|
-
if (!res.ok) throw new Error("Failed to fetch version")
|
|
68
|
-
versionData.value = await res.json()
|
|
69
|
-
versionStatus.value = "success"
|
|
70
|
-
} catch (err) {
|
|
71
|
-
versionStatus.value = "error"
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
watch(() => lookupPath, fetchBasePage, { immediate: true })
|
|
76
|
-
watch(() => versionId, fetchVersionData, { immediate: true })
|
|
77
|
-
|
|
78
|
-
const localPage = ref<Page | null>(null)
|
|
79
|
-
watch(
|
|
80
|
-
[page, versionData],
|
|
81
|
-
() => {
|
|
82
|
-
if (versionId && versionData.value) {
|
|
83
|
-
const converted = convertVersionToPage(versionData.value)
|
|
84
|
-
const typeKey = Object.keys(pageDefinitions).find(
|
|
85
|
-
(k) => k.toLowerCase() === converted.type?.toLowerCase()
|
|
86
|
-
)
|
|
87
|
-
const definition = typeKey ? pageDefinitions[typeKey] : null
|
|
88
|
-
if (definition) {
|
|
89
|
-
syncPageWithDefinition(converted, definition)
|
|
90
|
-
}
|
|
91
|
-
localPage.value = converted
|
|
92
|
-
} else if (page.value) {
|
|
93
|
-
const cloned = JSON.parse(JSON.stringify(page.value))
|
|
94
|
-
const typeKey = Object.keys(pageDefinitions).find(
|
|
95
|
-
(k) => k.toLowerCase() === cloned.type?.toLowerCase()
|
|
96
|
-
)
|
|
97
|
-
const definition = typeKey ? pageDefinitions[typeKey] : null
|
|
98
|
-
if (definition) {
|
|
99
|
-
syncPageWithDefinition(cloned, definition)
|
|
100
|
-
}
|
|
101
|
-
localPage.value = cloned
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
{ immediate: true }
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
const showCompare = ref(false)
|
|
108
|
-
const leftWidth = ref(50)
|
|
109
|
-
const isActioning = ref<string | null>(null)
|
|
110
|
-
|
|
111
|
-
const resolvePage = async (idOrSlug: string): Promise<Pick<Page, "title" | "icon" | "slug">> => {
|
|
112
|
-
const url = getPageResolutionPath(idOrSlug)
|
|
113
|
-
if (!url) throw new Error(`[Review] Invalid page reference: ${idOrSlug}`)
|
|
114
|
-
const res = await fetch(`${url}?select=title,icon,slug`)
|
|
115
|
-
if (!res.ok) throw new Error(`[Review] Page not found: ${idOrSlug}`)
|
|
116
|
-
return await res.json()
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const navigateToView = () => {
|
|
120
|
-
if (typeof window !== "undefined") {
|
|
121
|
-
window.location.href = `/${relativeSlug.value}`
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const navigateToVersion = (v: PageVersion) => {
|
|
126
|
-
if (typeof window !== "undefined") {
|
|
127
|
-
window.location.search = `?version=${v.id}`
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const handleApprove = async () => {
|
|
132
|
-
if (!versionId || !isAdmin) return
|
|
133
|
-
isActioning.value = "approving"
|
|
134
|
-
try {
|
|
135
|
-
await fetch(`/api/pages/versions/${versionId}/approve`, { method: "POST" })
|
|
136
|
-
await fetchBasePage()
|
|
137
|
-
if (versionData.value) {
|
|
138
|
-
emit("approved", versionData.value)
|
|
139
|
-
}
|
|
140
|
-
if (page.value?.slug) {
|
|
141
|
-
const base = baseUrl.replace(/\/$/, "")
|
|
142
|
-
const cleanBase = base.replace(/^\//, "")
|
|
143
|
-
const slugToUse = page.value.slug.startsWith(cleanBase)
|
|
144
|
-
? page.value.slug
|
|
145
|
-
: `${cleanBase}/${page.value.slug}`
|
|
146
|
-
if (typeof window !== "undefined") {
|
|
147
|
-
window.location.href = `/${slugToUse}`
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
} catch (e) {
|
|
151
|
-
console.error(e)
|
|
152
|
-
} finally {
|
|
153
|
-
isActioning.value = null
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const handleReject = async () => {
|
|
158
|
-
if (!versionId || !isAdmin) return
|
|
159
|
-
isActioning.value = "rejecting"
|
|
160
|
-
try {
|
|
161
|
-
await fetch(`/api/pages/versions/${versionId}/reject`, { method: "POST" })
|
|
162
|
-
await fetchBasePage()
|
|
163
|
-
if (versionData.value) {
|
|
164
|
-
emit("rejected", versionData.value)
|
|
165
|
-
}
|
|
166
|
-
if (page.value?.slug) {
|
|
167
|
-
const base = baseUrl.replace(/\/$/, "")
|
|
168
|
-
const cleanBase = base.replace(/^\//, "")
|
|
169
|
-
const slugToUse = page.value.slug.startsWith(cleanBase)
|
|
170
|
-
? page.value.slug
|
|
171
|
-
: `${cleanBase}/${page.value.slug}`
|
|
172
|
-
if (typeof window !== "undefined") {
|
|
173
|
-
window.location.href = `/${slugToUse}/edit`
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
} catch (e) {
|
|
177
|
-
console.error(e)
|
|
178
|
-
} finally {
|
|
179
|
-
isActioning.value = null
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const relativeSlug = computed(() => {
|
|
184
|
-
if (page.value?.slug) return page.value.slug
|
|
185
|
-
const base = baseUrl.replace(/^\/|\/$/g, "")
|
|
186
|
-
return lookupPath.replace(base, "").replace(/^\/|\/$/g, "")
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
defineOptions({ name: "PageReviewView" })
|
|
190
|
-
</script>
|
|
191
|
-
|
|
192
|
-
<template>
|
|
193
|
-
<div
|
|
194
|
-
v-if="pageStatus === 'pending' || (versionId && versionStatus === 'pending')"
|
|
195
|
-
class="h-full w-full flex items-center justify-center p-12"
|
|
196
|
-
>
|
|
197
|
-
<span class="i-lucide-loader-2 size-12 animate-spin text-neutral-400" />
|
|
198
|
-
</div>
|
|
199
|
-
|
|
200
|
-
<div
|
|
201
|
-
v-else-if="pageStatus === 'error' || versionStatus === 'error' || !localPage"
|
|
202
|
-
class="p-8 text-center"
|
|
203
|
-
>
|
|
204
|
-
<h3 class="text-lg font-bold text-red-600">Version Not Found</h3>
|
|
205
|
-
<p class="text-neutral-500 mt-2">The requested page version could not be located.</p>
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
<template v-else>
|
|
209
|
-
<div
|
|
210
|
-
class="h-12 w-full bg-neutral-100 dark:bg-neutral-800 flex items-center justify-between px-4 border-b border-default"
|
|
211
|
-
>
|
|
212
|
-
<div class="flex items-center gap-2">
|
|
213
|
-
<span class="text-xs text-neutral-500 font-medium">
|
|
214
|
-
Reviewing:
|
|
215
|
-
<span class="text-neutral-900 dark:text-white font-semibold">
|
|
216
|
-
{{ getLocalizedContent(localPage?.title, locale) }}
|
|
217
|
-
</span>
|
|
218
|
-
</span>
|
|
219
|
-
<span class="w-px h-4 bg-neutral-200 dark:bg-neutral-700 mx-2" />
|
|
220
|
-
<PageVersionSelector
|
|
221
|
-
v-if="page?.id"
|
|
222
|
-
:page-id="page.id"
|
|
223
|
-
:current-version-id="versionId || undefined"
|
|
224
|
-
:is-admin="isAdmin"
|
|
225
|
-
@version-selected="navigateToVersion"
|
|
226
|
-
/>
|
|
227
|
-
</div>
|
|
228
|
-
|
|
229
|
-
<div class="flex items-center gap-2">
|
|
230
|
-
<RLVButton
|
|
231
|
-
color="neutral"
|
|
232
|
-
variant="ghost"
|
|
233
|
-
leading-leading-icon="i-lucide-external-link"
|
|
234
|
-
:label="t('page_editor.view_page', 'View Page')"
|
|
235
|
-
@click="navigateToView"
|
|
236
|
-
/>
|
|
237
|
-
<RLVButton
|
|
238
|
-
color="neutral"
|
|
239
|
-
variant="ghost"
|
|
240
|
-
:leading-leading-icon="showCompare ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
|
241
|
-
:label="showCompare ? 'Hide' : 'Compare'"
|
|
242
|
-
@click="showCompare = !showCompare"
|
|
243
|
-
/>
|
|
244
|
-
<span class="w-px h-4 bg-neutral-200 dark:bg-neutral-700 mx-2" />
|
|
245
|
-
|
|
246
|
-
<template v-if="isAdmin && versionId && versionData?.status === 'pending'">
|
|
247
|
-
<RLVButton
|
|
248
|
-
leading-leading-icon="i-lucide-x"
|
|
249
|
-
:label="t('common.reject', 'Reject')"
|
|
250
|
-
color="danger"
|
|
251
|
-
variant="outline"
|
|
252
|
-
:loading="isActioning === 'rejecting'"
|
|
253
|
-
@click="handleReject"
|
|
254
|
-
/>
|
|
255
|
-
<RLVButton
|
|
256
|
-
leading-leading-icon="i-lucide-check"
|
|
257
|
-
:label="t('common.approve', 'Approve Version')"
|
|
258
|
-
color="primary"
|
|
259
|
-
:loading="isActioning === 'approving'"
|
|
260
|
-
@click="handleApprove"
|
|
261
|
-
/>
|
|
262
|
-
</template>
|
|
263
|
-
|
|
264
|
-
<template v-else-if="versionId && versionData?.status === 'approved'">
|
|
265
|
-
<span
|
|
266
|
-
class="text-xs bg-green-500/10 text-green-600 px-2.5 py-1 rounded-md font-medium border border-green-500/20"
|
|
267
|
-
>
|
|
268
|
-
Version Already Approved
|
|
269
|
-
</span>
|
|
270
|
-
</template>
|
|
271
|
-
|
|
272
|
-
<template v-else-if="versionId && versionData?.status === 'rejected'">
|
|
273
|
-
<span
|
|
274
|
-
class="text-xs bg-red-500/10 text-red-600 px-2.5 py-1 rounded-md font-medium border border-green-500/20"
|
|
275
|
-
>
|
|
276
|
-
Version Rejected
|
|
277
|
-
</span>
|
|
278
|
-
</template>
|
|
279
|
-
</div>
|
|
280
|
-
</div>
|
|
281
|
-
|
|
282
|
-
<div class="flex w-full min-h-0">
|
|
283
|
-
<div :style="{ width: showCompare ? `${leftWidth}%` : '100%' }" class="overflow-y-auto">
|
|
284
|
-
<PageRenderer
|
|
285
|
-
v-if="localPage"
|
|
286
|
-
:key="versionId || 'live'"
|
|
287
|
-
v-model="localPage"
|
|
288
|
-
:resolve-page="resolvePage"
|
|
289
|
-
class="h-full"
|
|
290
|
-
/>
|
|
291
|
-
</div>
|
|
292
|
-
|
|
293
|
-
<div
|
|
294
|
-
v-if="showCompare"
|
|
295
|
-
:style="{ width: `${100 - leftWidth}%` }"
|
|
296
|
-
class="overflow-y-auto border-l border-default bg-neutral-50/50 dark:bg-neutral-900/10"
|
|
297
|
-
>
|
|
298
|
-
<PageRenderer
|
|
299
|
-
v-if="page"
|
|
300
|
-
:key="'live-base-' + page.id"
|
|
301
|
-
:model-value="page"
|
|
302
|
-
:resolve-page="resolvePage"
|
|
303
|
-
class="opacity-70 pointer-events-none select-none h-full"
|
|
304
|
-
/>
|
|
305
|
-
</div>
|
|
306
|
-
</div>
|
|
307
|
-
</template>
|
|
308
|
-
</template>
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_ICONS } from "../../icons"
|
|
2
|
-
import { uiConfig } from "../../resolver"
|
|
3
|
-
|
|
4
|
-
export interface ResolvedIcon {
|
|
5
|
-
isClass: boolean
|
|
6
|
-
content: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const c = new Map<string, ResolvedIcon>(),
|
|
10
|
-
isCls = (s: string) => s.startsWith("i-") || s.includes(":")
|
|
11
|
-
|
|
12
|
-
export const resolveIcon = (k = "", custom?: string): ResolvedIcon => {
|
|
13
|
-
if (!k) return { isClass: false, content: "" }
|
|
14
|
-
const id = custom ? `${k}:${custom}` : k,
|
|
15
|
-
cached = c.get(id)
|
|
16
|
-
if (cached) return cached
|
|
17
|
-
const raw = custom || uiConfig.icons?.[k] || k,
|
|
18
|
-
res = isCls(raw)
|
|
19
|
-
? { isClass: true, content: raw }
|
|
20
|
-
: { isClass: false, content: (DEFAULT_ICONS as any)[raw] || "" }
|
|
21
|
-
c.set(id, res)
|
|
22
|
-
return res
|
|
23
|
-
}
|