@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,155 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, provide, watch } from "vue"
|
|
3
|
-
import type { Page, PageSurround, PageDefinition } from "./types/pages"
|
|
4
|
-
import { getLocalizedContent, syncPageWithDefinition } from "./utils/page"
|
|
5
|
-
import { useI18n } from "./utils/editorHelpers"
|
|
6
|
-
import { scv, cx } from "css-variants"
|
|
7
|
-
import PagePropertiesRenderer from "./PagePropertiesRenderer.vue"
|
|
8
|
-
import BlockViewRenderer from "./blocks/BlockViewRenderer.vue"
|
|
9
|
-
import PageSurroundComponent from "./PageSurround.vue"
|
|
10
|
-
|
|
11
|
-
export interface PageRendererProps {
|
|
12
|
-
useSurround?: boolean
|
|
13
|
-
surround?: PageSurround | null
|
|
14
|
-
surroundStatus?: "idle" | "pending" | "success" | "error"
|
|
15
|
-
resolvePage: (id: string) => Promise<Pick<Page, "title" | "icon" | "slug">>
|
|
16
|
-
canEdit?: boolean
|
|
17
|
-
editUrl?: string
|
|
18
|
-
pageDefinitions?: Record<string, PageDefinition>
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const {
|
|
22
|
-
useSurround = false,
|
|
23
|
-
surroundStatus = "idle",
|
|
24
|
-
surround = null,
|
|
25
|
-
resolvePage,
|
|
26
|
-
canEdit = false,
|
|
27
|
-
editUrl,
|
|
28
|
-
pageDefinitions
|
|
29
|
-
} = defineProps<PageRendererProps>()
|
|
30
|
-
provide("page-resolver", resolvePage)
|
|
31
|
-
|
|
32
|
-
const pageRendererStyles = scv({
|
|
33
|
-
slots: [
|
|
34
|
-
"container",
|
|
35
|
-
"grid",
|
|
36
|
-
"toc",
|
|
37
|
-
"properties",
|
|
38
|
-
"contentWrapper",
|
|
39
|
-
"banner",
|
|
40
|
-
"icon",
|
|
41
|
-
"title",
|
|
42
|
-
"surroundSkeleton",
|
|
43
|
-
"skeleton",
|
|
44
|
-
"metadata"
|
|
45
|
-
],
|
|
46
|
-
base: {
|
|
47
|
-
container: "flex flex-col py-16 w-full max-w-7xl mx-auto px-4",
|
|
48
|
-
grid: "grid grid-cols-1 lg:grid-cols-24 gap-8 items-start",
|
|
49
|
-
toc: "hidden lg:flex lg:col-span-4 sticky top-16",
|
|
50
|
-
properties: "order-1 lg:order-2 lg:col-span-6",
|
|
51
|
-
contentWrapper: "order-2 lg:order-1 lg:col-span-14 flex flex-col gap-8 w-full min-w-0",
|
|
52
|
-
banner: "rounded-xl w-full object-cover max-h-[300px]",
|
|
53
|
-
icon: "rounded-full w-12 h-12 object-cover border border-default shadow-sm",
|
|
54
|
-
title: "text-4xl font-bold text-neutral-900 dark:text-neutral-100 mt-2",
|
|
55
|
-
surroundSkeleton: "grid grid-cols-1 gap-4 sm:grid-cols-2 mt-8",
|
|
56
|
-
skeleton: "h-48 w-full rounded-xl bg-neutral-100 dark:bg-neutral-800 animate-pulse",
|
|
57
|
-
metadata:
|
|
58
|
-
"flex flex-col gap-1 text-xs text-dimmed p-4 bg-neutral-50 dark:bg-neutral-900/40 rounded-xl border border-default mt-8"
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
const styles = computed(() => pageRendererStyles())
|
|
63
|
-
|
|
64
|
-
const page = defineModel<Page>({ required: true })
|
|
65
|
-
const { t, locale } = useI18n()
|
|
66
|
-
|
|
67
|
-
const currentDefinition = computed(() => {
|
|
68
|
-
if (!page.value?.type || !pageDefinitions) return null
|
|
69
|
-
const typeKey = Object.keys(pageDefinitions).find(
|
|
70
|
-
(k) => k.toLowerCase() === page.value.type.toLowerCase()
|
|
71
|
-
)
|
|
72
|
-
return typeKey ? pageDefinitions[typeKey] : null
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
const previousPage = computed(() => surround?.previous)
|
|
76
|
-
const nextPage = computed(() => surround?.next)
|
|
77
|
-
const hasSurround = computed(() => !!(surround?.previous || surround?.next))
|
|
78
|
-
|
|
79
|
-
watch(
|
|
80
|
-
[() => page.value?.id, () => page.value?.type, currentDefinition],
|
|
81
|
-
() => {
|
|
82
|
-
if (page.value && currentDefinition.value) {
|
|
83
|
-
syncPageWithDefinition(page.value, currentDefinition.value)
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
{ immediate: true }
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
defineOptions({ name: "PageRenderer" })
|
|
90
|
-
</script>
|
|
91
|
-
|
|
92
|
-
<template>
|
|
93
|
-
<div :class="styles.container">
|
|
94
|
-
<div :class="styles.grid">
|
|
95
|
-
<PagePropertiesRenderer
|
|
96
|
-
v-model="page"
|
|
97
|
-
:can-edit="canEdit"
|
|
98
|
-
:edit-url="editUrl"
|
|
99
|
-
:class="styles.properties"
|
|
100
|
-
/>
|
|
101
|
-
|
|
102
|
-
<div :class="styles.contentWrapper">
|
|
103
|
-
<img
|
|
104
|
-
v-if="page.banner?.src"
|
|
105
|
-
:src="page.banner?.src"
|
|
106
|
-
:alt="page.banner?.alt || 'Banner'"
|
|
107
|
-
:class="styles.banner"
|
|
108
|
-
/>
|
|
109
|
-
|
|
110
|
-
<div class="border-b border-default pb-4">
|
|
111
|
-
<span class="text-xs uppercase tracking-wider text-primary font-bold">
|
|
112
|
-
{{ page.type }}
|
|
113
|
-
</span>
|
|
114
|
-
<h1 :class="styles.title">
|
|
115
|
-
{{ getLocalizedContent(page.title, locale) }}
|
|
116
|
-
</h1>
|
|
117
|
-
<p v-if="page.description" class="text-neutral-500 dark:text-neutral-400 mt-2 text-lg">
|
|
118
|
-
{{ getLocalizedContent(page.description, locale) }}
|
|
119
|
-
</p>
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
<BlockViewRenderer :blocks="page.blocks || []" />
|
|
123
|
-
|
|
124
|
-
<template v-if="useSurround">
|
|
125
|
-
<div v-if="surroundStatus === 'pending'" :class="styles.surroundSkeleton">
|
|
126
|
-
<div :class="styles.skeleton" />
|
|
127
|
-
<div :class="styles.skeleton" />
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
<PageSurroundComponent
|
|
131
|
-
v-else-if="surroundStatus === 'success' && hasSurround"
|
|
132
|
-
:pageType="page.type"
|
|
133
|
-
:previousTitle="getLocalizedContent(previousPage?.title, locale)"
|
|
134
|
-
:previousDescription="getLocalizedContent(previousPage?.description, locale)"
|
|
135
|
-
:previousTo="previousPage?.slug ? `/${previousPage.slug}` : undefined"
|
|
136
|
-
:nextTitle="getLocalizedContent(nextPage?.title, locale)"
|
|
137
|
-
:nextDescription="getLocalizedContent(nextPage?.description, locale)"
|
|
138
|
-
:nextTo="nextPage?.slug ? `/${nextPage.slug}` : undefined"
|
|
139
|
-
/>
|
|
140
|
-
|
|
141
|
-
<div :class="styles.metadata">
|
|
142
|
-
<h6 class="font-bold text-neutral-800 dark:text-neutral-200">Metadata</h6>
|
|
143
|
-
<span>ID: {{ page.id }}</span>
|
|
144
|
-
<span v-if="page.createdAt">
|
|
145
|
-
Created: {{ new Date(page.createdAt).toLocaleString() }}
|
|
146
|
-
</span>
|
|
147
|
-
<span v-if="page.updatedAt">
|
|
148
|
-
Updated: {{ new Date(page.updatedAt).toLocaleString() }}
|
|
149
|
-
</span>
|
|
150
|
-
</div>
|
|
151
|
-
</template>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
</div>
|
|
155
|
-
</template>
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from "vue"
|
|
3
|
-
import { scv, cx } from "css-variants"
|
|
4
|
-
import { useI18n } from "./utils/editorHelpers"
|
|
5
|
-
import RLVButton from "../button/RLVButton.vue"
|
|
6
|
-
|
|
7
|
-
export interface PageSurroundProps {
|
|
8
|
-
pageType: string
|
|
9
|
-
previousTitle?: string
|
|
10
|
-
previousDescription?: string
|
|
11
|
-
previousTo?: string
|
|
12
|
-
nextTitle?: string
|
|
13
|
-
nextDescription?: string
|
|
14
|
-
nextTo?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const {
|
|
18
|
-
pageType,
|
|
19
|
-
previousTitle,
|
|
20
|
-
previousDescription,
|
|
21
|
-
previousTo,
|
|
22
|
-
nextTitle,
|
|
23
|
-
nextDescription,
|
|
24
|
-
nextTo
|
|
25
|
-
} = defineProps<PageSurroundProps>()
|
|
26
|
-
|
|
27
|
-
const pageSurroundStyles = scv({
|
|
28
|
-
slots: [
|
|
29
|
-
"grid",
|
|
30
|
-
"cardClass",
|
|
31
|
-
"cardContent",
|
|
32
|
-
"cardContentEnd",
|
|
33
|
-
"headingGroup",
|
|
34
|
-
"headingGroupEnd",
|
|
35
|
-
"buttonClass",
|
|
36
|
-
"typeLabel",
|
|
37
|
-
"infoGroup",
|
|
38
|
-
"infoGroupEnd",
|
|
39
|
-
"title",
|
|
40
|
-
"titleEnd",
|
|
41
|
-
"description",
|
|
42
|
-
"descriptionEnd"
|
|
43
|
-
],
|
|
44
|
-
base: {
|
|
45
|
-
grid: "grid grid-cols-1 gap-8 sm:grid-cols-2",
|
|
46
|
-
cardClass:
|
|
47
|
-
"group block h-full bg-transparent hover:bg-elevated/50 focus-visible:outline-primary border border-default rounded-xl p-6 transition-all shadow-sm",
|
|
48
|
-
cardContent: "flex flex-col gap-md",
|
|
49
|
-
cardContentEnd: "items-end",
|
|
50
|
-
headingGroup: "flex flex-col gap-xs",
|
|
51
|
-
headingGroupEnd: "items-end",
|
|
52
|
-
buttonClass: "w-fit text-md rounded-full text-primary group-hover:text-highlighted",
|
|
53
|
-
typeLabel: "text-muted text-xs uppercase font-semibold",
|
|
54
|
-
infoGroup: "flex flex-col gap-sm",
|
|
55
|
-
infoGroupEnd: "items-end gap-xs",
|
|
56
|
-
title: "text-primary text-sm font-semibold group-hover:text-highlighted transition-colors",
|
|
57
|
-
titleEnd: "text-right",
|
|
58
|
-
description: "text-toned text-xs line-clamp-2",
|
|
59
|
-
descriptionEnd: "text-right"
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
const styles = computed(() => pageSurroundStyles())
|
|
64
|
-
|
|
65
|
-
const { t } = useI18n()
|
|
66
|
-
|
|
67
|
-
defineOptions({ name: "PageSurround" })
|
|
68
|
-
</script>
|
|
69
|
-
|
|
70
|
-
<template>
|
|
71
|
-
<div :class="styles.grid">
|
|
72
|
-
<div>
|
|
73
|
-
<a v-if="previousTitle" :href="previousTo" class="h-full block">
|
|
74
|
-
<div :class="styles.cardClass">
|
|
75
|
-
<div :class="styles.cardContent">
|
|
76
|
-
<div :class="styles.headingGroup">
|
|
77
|
-
<RLVButton
|
|
78
|
-
variant="outline"
|
|
79
|
-
leading-leading-icon="i-lucide-arrow-left"
|
|
80
|
-
class="rounded-full !p-2"
|
|
81
|
-
:class="styles.buttonClass"
|
|
82
|
-
/>
|
|
83
|
-
<span :class="styles.typeLabel">
|
|
84
|
-
{{ t("navigation_previous", "Previous") }}
|
|
85
|
-
{{ t(pageType, pageType) }}
|
|
86
|
-
</span>
|
|
87
|
-
</div>
|
|
88
|
-
<div :class="styles.infoGroup">
|
|
89
|
-
<p :class="styles.title">
|
|
90
|
-
{{ previousTitle }}
|
|
91
|
-
</p>
|
|
92
|
-
<p :class="styles.description">
|
|
93
|
-
{{ previousDescription }}
|
|
94
|
-
</p>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
</a>
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
<div>
|
|
102
|
-
<a v-if="nextTitle" :href="nextTo" class="h-full block">
|
|
103
|
-
<div :class="styles.cardClass">
|
|
104
|
-
<div :class="[styles.cardContent, styles.cardContentEnd]">
|
|
105
|
-
<div :class="[styles.headingGroup, styles.headingGroupEnd]">
|
|
106
|
-
<RLVButton
|
|
107
|
-
variant="outline"
|
|
108
|
-
leading-leading-icon="i-lucide-arrow-right"
|
|
109
|
-
class="rounded-full !p-2"
|
|
110
|
-
:class="styles.buttonClass"
|
|
111
|
-
/>
|
|
112
|
-
<span :class="styles.typeLabel">
|
|
113
|
-
{{ t("navigation_next", "Next") }}
|
|
114
|
-
{{ t(pageType, pageType) }}
|
|
115
|
-
</span>
|
|
116
|
-
</div>
|
|
117
|
-
<div :class="[styles.infoGroup, styles.infoGroupEnd]">
|
|
118
|
-
<p :class="[styles.title, styles.titleEnd]">
|
|
119
|
-
{{ nextTitle }}
|
|
120
|
-
</p>
|
|
121
|
-
<p :class="[styles.description, styles.descriptionEnd]">
|
|
122
|
-
{{ nextDescription }}
|
|
123
|
-
</p>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
</a>
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
</template>
|
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
import { computed, ref, watch } from "vue"
|
|
3
|
-
import { useToast, useI18n } from "./utils/editorHelpers"
|
|
4
|
-
import { scv, cx } from "css-variants"
|
|
5
|
-
import type { PageVersion } from "./types/pages"
|
|
6
|
-
import RLVButton from "../button/RLVButton.vue"
|
|
7
|
-
|
|
8
|
-
export interface PageVersionSelectorProps {
|
|
9
|
-
pageId: string
|
|
10
|
-
isAdmin?: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const { pageId, isAdmin = false } = defineProps<PageVersionSelectorProps>()
|
|
14
|
-
|
|
15
|
-
export interface PageVersionSelectorEmits {
|
|
16
|
-
"version-selected": [version: PageVersion]
|
|
17
|
-
"version-approved": [version: PageVersion]
|
|
18
|
-
"version-rejected": [version: PageVersion]
|
|
19
|
-
"version-reverted": [version: PageVersion]
|
|
20
|
-
}
|
|
21
|
-
const emit = defineEmits<PageVersionSelectorEmits>()
|
|
22
|
-
|
|
23
|
-
const pageVersionSelectorStyles = scv({
|
|
24
|
-
slots: [
|
|
25
|
-
"root",
|
|
26
|
-
"buttonClass",
|
|
27
|
-
"popoverClass",
|
|
28
|
-
"versionItemClass",
|
|
29
|
-
"popoverHeader",
|
|
30
|
-
"popoverTitle",
|
|
31
|
-
"popoverSubtitle",
|
|
32
|
-
"versionsList",
|
|
33
|
-
"liveVersionItem",
|
|
34
|
-
"liveBadgeWrapper",
|
|
35
|
-
"liveVersionTitle",
|
|
36
|
-
"liveVersionSubtitle",
|
|
37
|
-
"loadingWrapper",
|
|
38
|
-
"emptyWrapper",
|
|
39
|
-
"versionEntries",
|
|
40
|
-
"versionEntryWrapper",
|
|
41
|
-
"versionEntryContent",
|
|
42
|
-
"versionEntryHeader",
|
|
43
|
-
"versionEntryTime",
|
|
44
|
-
"versionEntryTitle",
|
|
45
|
-
"versionEntryApproval",
|
|
46
|
-
"versionEntryActions",
|
|
47
|
-
"pendingFooter",
|
|
48
|
-
"pendingText"
|
|
49
|
-
],
|
|
50
|
-
base: {
|
|
51
|
-
root: "relative inline-block text-left",
|
|
52
|
-
buttonClass: "",
|
|
53
|
-
popoverClass:
|
|
54
|
-
"absolute right-0 mt-2 w-80 bg-popover text-popover-foreground border border-default rounded-xl shadow-lg z-50 overflow-hidden p-2 bg-white dark:bg-neutral-900",
|
|
55
|
-
versionItemClass:
|
|
56
|
-
"px-3 py-2 hover:bg-neutral-100 dark:hover:bg-neutral-800 cursor-pointer rounded transition-colors",
|
|
57
|
-
popoverHeader: "px-3 py-2 border-b border-default mb-1",
|
|
58
|
-
popoverTitle: "text-sm font-semibold",
|
|
59
|
-
popoverSubtitle: "text-xs text-dimmed mt-1",
|
|
60
|
-
versionsList: "max-h-96 overflow-y-auto flex flex-col gap-1",
|
|
61
|
-
liveVersionItem: "flex items-center justify-between",
|
|
62
|
-
liveBadgeWrapper: "flex items-center gap-2",
|
|
63
|
-
liveVersionTitle: "text-sm font-medium",
|
|
64
|
-
liveVersionSubtitle: "text-xs text-dimmed mt-1",
|
|
65
|
-
loadingWrapper: "px-3 py-4 text-center text-xs text-dimmed",
|
|
66
|
-
emptyWrapper: "px-3 py-4 text-center text-sm text-dimmed",
|
|
67
|
-
versionEntries: "divide-y divide-default flex flex-col gap-1",
|
|
68
|
-
versionEntryWrapper: "flex items-start justify-between gap-2",
|
|
69
|
-
versionEntryContent: "flex-1 min-w-0",
|
|
70
|
-
versionEntryHeader: "flex items-center gap-2 mb-1",
|
|
71
|
-
versionEntryTime: "text-xs text-dimmed",
|
|
72
|
-
versionEntryTitle: "text-xs text-default truncate font-medium",
|
|
73
|
-
versionEntryApproval: "text-xs text-dimmed mt-1",
|
|
74
|
-
versionEntryActions: "shrink-0 flex items-center gap-1",
|
|
75
|
-
pendingFooter: "px-3 py-2 border-t border-default mt-2",
|
|
76
|
-
pendingText: "text-xs text-dimmed"
|
|
77
|
-
}
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
const styles = computed(() => pageVersionSelectorStyles())
|
|
81
|
-
|
|
82
|
-
const selectedVersionId = defineModel<string | null>("currentVersionId", { default: null })
|
|
83
|
-
const versions = ref<PageVersion[]>([])
|
|
84
|
-
const isLoading = ref(false)
|
|
85
|
-
const isApproving = ref<string | null>(null)
|
|
86
|
-
const isRejecting = ref<string | null>(null)
|
|
87
|
-
const isReverting = ref<string | null>(null)
|
|
88
|
-
const isOpen = ref(false)
|
|
89
|
-
const toast = useToast()
|
|
90
|
-
const { t } = useI18n()
|
|
91
|
-
|
|
92
|
-
const pendingVersions = computed(() => {
|
|
93
|
-
return versions.value.filter((v) => v.status === "pending")
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
const currentVersionStatus = computed(() => {
|
|
97
|
-
if (!selectedVersionId.value) return "approved"
|
|
98
|
-
const v = versions.value.find((version) => version.id === selectedVersionId.value)
|
|
99
|
-
return v?.status || "pending"
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
const currentVersion = computed(() => versions.value.find((v) => v.id === selectedVersionId.value))
|
|
103
|
-
|
|
104
|
-
watch(
|
|
105
|
-
() => pageId,
|
|
106
|
-
() => {
|
|
107
|
-
if (pageId) fetchVersions()
|
|
108
|
-
},
|
|
109
|
-
{ immediate: true }
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
async function fetchVersions() {
|
|
113
|
-
if (!pageId) return
|
|
114
|
-
isLoading.value = true
|
|
115
|
-
try {
|
|
116
|
-
const res = await fetch(`/api/pages/id/${pageId}/versions`)
|
|
117
|
-
if (!res.ok) throw new Error("Failed to load versions")
|
|
118
|
-
versions.value = await res.json()
|
|
119
|
-
} catch (error) {
|
|
120
|
-
console.error("Failed to fetch versions:", error)
|
|
121
|
-
} finally {
|
|
122
|
-
isLoading.value = false
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function selectVersion(version: PageVersion) {
|
|
127
|
-
selectedVersionId.value = version.id
|
|
128
|
-
emit("version-selected", version)
|
|
129
|
-
isOpen.value = false
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async function approveVersion(version: PageVersion) {
|
|
133
|
-
if (!isAdmin) return
|
|
134
|
-
isApproving.value = version.id
|
|
135
|
-
try {
|
|
136
|
-
const res = await fetch(`/api/pages/versions/${version.id}/approve`, { method: "POST" })
|
|
137
|
-
if (!res.ok) throw new Error("Failed to approve")
|
|
138
|
-
const result = await res.json()
|
|
139
|
-
toast.add({
|
|
140
|
-
color: "success",
|
|
141
|
-
title: t("page_version.approved_successfully", "Approved successfully"),
|
|
142
|
-
description: result?.message || "The page has been updated with the approved version"
|
|
143
|
-
})
|
|
144
|
-
emit("version-approved", version)
|
|
145
|
-
await fetchVersions()
|
|
146
|
-
if (selectedVersionId.value === version.id) {
|
|
147
|
-
selectedVersionId.value = null
|
|
148
|
-
}
|
|
149
|
-
} catch (error: any) {
|
|
150
|
-
toast.add({
|
|
151
|
-
color: "error",
|
|
152
|
-
title: t("page_version.failed_to_approve", "Failed to approve version"),
|
|
153
|
-
description: error.message || "An error occurred"
|
|
154
|
-
})
|
|
155
|
-
} finally {
|
|
156
|
-
isApproving.value = null
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async function rejectVersion(version: PageVersion) {
|
|
161
|
-
if (!isAdmin) return
|
|
162
|
-
isRejecting.value = version.id
|
|
163
|
-
try {
|
|
164
|
-
const res = await fetch(`/api/pages/versions/${version.id}/reject`, { method: "POST" })
|
|
165
|
-
if (!res.ok) throw new Error("Failed to reject")
|
|
166
|
-
const result = await res.json()
|
|
167
|
-
toast.add({
|
|
168
|
-
color: "success",
|
|
169
|
-
title: t("page_version.rejected_successfully", "Version rejected"),
|
|
170
|
-
description: result?.message || "The version has been rejected"
|
|
171
|
-
})
|
|
172
|
-
emit("version-rejected", version)
|
|
173
|
-
await fetchVersions()
|
|
174
|
-
} catch (error: any) {
|
|
175
|
-
toast.add({
|
|
176
|
-
color: "error",
|
|
177
|
-
title: t("page_version.failed_to_reject", "Failed to reject version"),
|
|
178
|
-
description: error.message || "An error occurred"
|
|
179
|
-
})
|
|
180
|
-
} finally {
|
|
181
|
-
isRejecting.value = null
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async function revertVersion(version: PageVersion) {
|
|
186
|
-
if (!isAdmin) return
|
|
187
|
-
isReverting.value = version.id
|
|
188
|
-
try {
|
|
189
|
-
const res = await fetch(`/api/pages/versions/${version.id}/revert`, { method: "POST" })
|
|
190
|
-
if (!res.ok) throw new Error("Failed to revert")
|
|
191
|
-
const result = await res.json()
|
|
192
|
-
toast.add({
|
|
193
|
-
color: "success",
|
|
194
|
-
title: t("page_version.reverted_successfully", "Reverted successfully"),
|
|
195
|
-
description: result?.message || "The page has been reverted to this version."
|
|
196
|
-
})
|
|
197
|
-
emit("version-reverted", version)
|
|
198
|
-
await fetchVersions()
|
|
199
|
-
selectedVersionId.value = null
|
|
200
|
-
} catch (error: any) {
|
|
201
|
-
toast.add({
|
|
202
|
-
color: "error",
|
|
203
|
-
title: t("page_version.failed_to_revert", "Failed to revert version"),
|
|
204
|
-
description: error.message || "An error occurred"
|
|
205
|
-
})
|
|
206
|
-
} finally {
|
|
207
|
-
isReverting.value = null
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function selectLiveVersion() {
|
|
212
|
-
selectedVersionId.value = null
|
|
213
|
-
isOpen.value = false
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function formatDate(date: Date | string | null | undefined) {
|
|
217
|
-
if (!date) return ""
|
|
218
|
-
const d = typeof date === "string" ? new Date(date) : date
|
|
219
|
-
const now = new Date()
|
|
220
|
-
const diffMs = now.getTime() - d.getTime()
|
|
221
|
-
const diffSec = Math.floor(diffMs / 1000)
|
|
222
|
-
const diffMin = Math.floor(diffSec / 60)
|
|
223
|
-
const diffHour = Math.floor(diffMin / 60)
|
|
224
|
-
const diffDay = Math.floor(diffHour / 24)
|
|
225
|
-
if (diffSec < 60) return "just now"
|
|
226
|
-
if (diffMin < 60) return `${diffMin}m ago`
|
|
227
|
-
if (diffHour < 24) return `${diffHour}h ago`
|
|
228
|
-
return `${diffDay}d ago`
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function getStatusColor(status: string) {
|
|
232
|
-
switch (status) {
|
|
233
|
-
case "approved":
|
|
234
|
-
return "text-green-600 bg-green-50 dark:bg-green-950/30 border border-green-200 dark:border-green-800"
|
|
235
|
-
case "pending":
|
|
236
|
-
return "text-yellow-600 bg-yellow-50 dark:bg-yellow-950/30 border border-yellow-200 dark:border-yellow-800"
|
|
237
|
-
case "rejected":
|
|
238
|
-
return "text-red-600 bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800"
|
|
239
|
-
default:
|
|
240
|
-
return "text-neutral-600 bg-neutral-50 dark:bg-neutral-950/30 border border-neutral-200 dark:border-neutral-800"
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
defineExpose({
|
|
245
|
-
approveVersion,
|
|
246
|
-
rejectVersion,
|
|
247
|
-
revertVersion,
|
|
248
|
-
fetchVersions,
|
|
249
|
-
currentVersionStatus,
|
|
250
|
-
currentVersion
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
defineOptions({ name: "PageVersionSelector" })
|
|
254
|
-
</script>
|
|
255
|
-
|
|
256
|
-
<template>
|
|
257
|
-
<div :class="styles.root">
|
|
258
|
-
<RLVButton
|
|
259
|
-
:loading="isLoading"
|
|
260
|
-
color="neutral"
|
|
261
|
-
leading-leading-icon="i-lucide-git-branch"
|
|
262
|
-
:label="t('common.versions', 'Versions')"
|
|
263
|
-
size="xs"
|
|
264
|
-
variant="ghost"
|
|
265
|
-
:class="styles.buttonClass"
|
|
266
|
-
@click="isOpen = !isOpen"
|
|
267
|
-
/>
|
|
268
|
-
<div v-if="isOpen" :class="styles.popoverClass">
|
|
269
|
-
<div :class="styles.popoverHeader">
|
|
270
|
-
<h3 :class="styles.popoverTitle">Page Versions</h3>
|
|
271
|
-
<p :class="styles.popoverSubtitle">View and manage page versions</p>
|
|
272
|
-
</div>
|
|
273
|
-
<div :class="styles.versionsList">
|
|
274
|
-
<div
|
|
275
|
-
:class="[
|
|
276
|
-
styles.versionItemClass,
|
|
277
|
-
{
|
|
278
|
-
'bg-primary-50 dark:bg-primary-900/20 border-l-2 border-primary-500':
|
|
279
|
-
!selectedVersionId
|
|
280
|
-
}
|
|
281
|
-
]"
|
|
282
|
-
@click="selectLiveVersion"
|
|
283
|
-
>
|
|
284
|
-
<div :class="styles.liveVersionItem">
|
|
285
|
-
<div class="flex-1">
|
|
286
|
-
<div :class="styles.liveBadgeWrapper">
|
|
287
|
-
<span
|
|
288
|
-
class="px-1.5 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider text-green-600 bg-green-50 dark:bg-green-950/30 border border-green-200 dark:border-green-800"
|
|
289
|
-
>
|
|
290
|
-
Live
|
|
291
|
-
</span>
|
|
292
|
-
<span :class="styles.liveVersionTitle">Current Version</span>
|
|
293
|
-
</div>
|
|
294
|
-
<p :class="styles.liveVersionSubtitle">The published version of this page</p>
|
|
295
|
-
</div>
|
|
296
|
-
</div>
|
|
297
|
-
</div>
|
|
298
|
-
|
|
299
|
-
<div v-if="isLoading" :class="styles.loadingWrapper">
|
|
300
|
-
<div class="animate-pulse flex flex-col gap-2">
|
|
301
|
-
<div class="h-4 w-full rounded bg-neutral-200 dark:bg-neutral-800"></div>
|
|
302
|
-
<div class="h-3 w-3/4 rounded bg-neutral-200 dark:bg-neutral-800"></div>
|
|
303
|
-
</div>
|
|
304
|
-
</div>
|
|
305
|
-
|
|
306
|
-
<div v-else-if="versions.length === 0" :class="styles.emptyWrapper">No versions yet</div>
|
|
307
|
-
|
|
308
|
-
<div v-else :class="styles.versionEntries">
|
|
309
|
-
<div
|
|
310
|
-
v-for="version in versions"
|
|
311
|
-
:key="version.id"
|
|
312
|
-
:class="[
|
|
313
|
-
styles.versionItemClass,
|
|
314
|
-
{
|
|
315
|
-
'bg-primary-50 dark:bg-primary-900/20 border-l-2 border-primary-500':
|
|
316
|
-
selectedVersionId === version.id
|
|
317
|
-
}
|
|
318
|
-
]"
|
|
319
|
-
@click="selectVersion(version)"
|
|
320
|
-
>
|
|
321
|
-
<div :class="styles.versionEntryWrapper">
|
|
322
|
-
<div :class="styles.versionEntryContent">
|
|
323
|
-
<div :class="styles.versionEntryHeader">
|
|
324
|
-
<span
|
|
325
|
-
class="px-1.5 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider"
|
|
326
|
-
:class="getStatusColor(version.status)"
|
|
327
|
-
>
|
|
328
|
-
{{ version.status }}
|
|
329
|
-
</span>
|
|
330
|
-
<span :class="styles.versionEntryTime">
|
|
331
|
-
{{ formatDate(version.createdAt) }}
|
|
332
|
-
</span>
|
|
333
|
-
</div>
|
|
334
|
-
<p :class="styles.versionEntryTitle">
|
|
335
|
-
{{ (version.title as any)?.en || version.title || "Untitled" }}
|
|
336
|
-
</p>
|
|
337
|
-
<p
|
|
338
|
-
v-if="version.approvedBy && version.approvedAt"
|
|
339
|
-
:class="styles.versionEntryApproval"
|
|
340
|
-
>
|
|
341
|
-
Approved {{ formatDate(version.approvedAt) }}
|
|
342
|
-
</p>
|
|
343
|
-
</div>
|
|
344
|
-
<div
|
|
345
|
-
v-if="selectedVersionId === version.id && isAdmin"
|
|
346
|
-
:class="styles.versionEntryActions"
|
|
347
|
-
>
|
|
348
|
-
<RLVButton
|
|
349
|
-
:loading="isReverting === version.id"
|
|
350
|
-
color="warning"
|
|
351
|
-
leading-leading-icon="i-lucide-rotate-ccw"
|
|
352
|
-
size="xs"
|
|
353
|
-
title="Revert to this version"
|
|
354
|
-
variant="ghost"
|
|
355
|
-
@click.stop="revertVersion(version)"
|
|
356
|
-
/>
|
|
357
|
-
</div>
|
|
358
|
-
</div>
|
|
359
|
-
</div>
|
|
360
|
-
</div>
|
|
361
|
-
</div>
|
|
362
|
-
|
|
363
|
-
<div v-if="pendingVersions.length > 0 && isAdmin" :class="styles.pendingFooter">
|
|
364
|
-
<p :class="styles.pendingText">
|
|
365
|
-
{{ pendingVersions.length }} pending version{{ pendingVersions.length !== 1 ? "s" : "" }}
|
|
366
|
-
awaiting approval
|
|
367
|
-
</p>
|
|
368
|
-
</div>
|
|
369
|
-
</div>
|
|
370
|
-
</div>
|
|
371
|
-
</template>
|