@rimelight/ui 0.0.43 → 0.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -12
  3. package/package.json +11 -14
  4. package/src/components/avatar/RLAAvatar.astro +4 -12
  5. package/src/components/badge/RLABadge.astro +2 -1
  6. package/src/components/badge/badge.theme.ts +0 -4
  7. package/src/components/banner/RLABanner.astro +1 -1
  8. package/src/components/banner/banner.theme.ts +1 -1
  9. package/src/components/breadcrumb/RLABreadcrumb.astro +2 -2
  10. package/src/components/button/RLAButton.astro +4 -1
  11. package/src/components/button/button.theme.ts +1 -5
  12. package/src/components/callout/RLACallout.astro +7 -5
  13. package/src/components/callout/callout.theme.ts +1 -5
  14. package/src/components/card/card.theme.ts +3 -3
  15. package/src/components/carousel/RLACarousel.astro +2 -2
  16. package/src/components/carousel/carousel.theme.ts +2 -2
  17. package/src/components/chart/RLAChart.astro +1 -1
  18. package/src/components/checkbox/RLACheckbox.astro +2 -1
  19. package/src/components/checkbox/checkbox.theme.ts +0 -4
  20. package/src/components/chip/RLAChip.astro +2 -1
  21. package/src/components/chip/chip.theme.ts +0 -4
  22. package/src/components/collapsible/RLACollapsible.astro +1 -1
  23. package/src/components/color-area/RLAColorArea.astro +1 -0
  24. package/src/components/color-area/color-area.theme.ts +1 -5
  25. package/src/components/color-field/RLAColorField.astro +1 -0
  26. package/src/components/color-field/color-field.theme.ts +1 -5
  27. package/src/components/color-slider/RLAColorSlider.astro +1 -0
  28. package/src/components/color-slider/color-slider.theme.ts +1 -5
  29. package/src/components/dashboard-group/RLADashboardGroup.astro +5 -7
  30. package/src/components/dashboard-group/dashboard-group.theme.ts +1 -1
  31. package/src/components/dashboard-group/dashboard-group.ts +1 -1
  32. package/src/components/dashboard-navbar/RLADashboardNavbar.astro +4 -4
  33. package/src/components/dashboard-navbar/dashboard-navbar.theme.ts +3 -3
  34. package/src/components/dashboard-navbar/dashboard-navbar.ts +1 -1
  35. package/src/components/dashboard-panel/RLADashboardPanel.astro +9 -8
  36. package/src/components/dashboard-panel/dashboard-panel.theme.ts +1 -1
  37. package/src/components/dashboard-sidebar/RLADashboardSidebar.astro +7 -4
  38. package/src/components/dashboard-sidebar/dashboard-sidebar.theme.ts +6 -5
  39. package/src/components/dashboard-sidebar-collapse/RLADashboardSidebarCollapse.astro +1 -0
  40. package/src/components/drawer/RLADrawer.astro +1 -1
  41. package/src/components/dropdown-menu/dropdown-menu.theme.ts +2 -2
  42. package/src/components/footer/RLAFooter.astro +2 -0
  43. package/src/components/footer/footer.theme.ts +0 -8
  44. package/src/components/gamepad/RLAGamepad.astro +1 -1
  45. package/src/components/head/UIHead.astro +1 -2
  46. package/src/components/header/header.theme.ts +1 -1
  47. package/src/components/icon/RLAIcon.astro +2 -5
  48. package/src/components/icon/RLSIcon.tsx +2 -2
  49. package/src/components/icon/RLVIcon.vue +18 -6
  50. package/src/components/icon/icon.theme.ts +1 -1
  51. package/src/components/image/image.theme.ts +1 -1
  52. package/src/components/input/RLAInput.astro +2 -1
  53. package/src/components/input/input.theme.ts +0 -4
  54. package/src/components/input-menu/RLAInputMenu.astro +2 -2
  55. package/src/components/input-menu/input-menu.theme.ts +1 -1
  56. package/src/components/input-rating/input-rating.theme.ts +2 -2
  57. package/src/components/kbd/RLAKbd.astro +2 -1
  58. package/src/components/kbd/kbd.theme.ts +0 -4
  59. package/src/components/keyboard/RLAKeyboard.astro +1 -1
  60. package/src/components/link/RLALink.astro +1 -0
  61. package/src/components/link/link.theme.ts +0 -4
  62. package/src/components/link-group/RLALinkGroup.astro +1 -1
  63. package/src/components/link-group/link-group.theme.ts +1 -10
  64. package/src/components/locale-selector/RLALocaleSelector.astro +4 -36
  65. package/src/components/locale-selector/locale-selector.theme.ts +3 -11
  66. package/src/components/logo/RLALogo.astro +1 -2
  67. package/src/components/modal/RLAModal.astro +1 -1
  68. package/src/components/modal/RLSModal.tsx +2 -2
  69. package/src/components/navigation-menu/RLANavigationMenu.astro +17 -21
  70. package/src/components/navigation-menu/navigation-menu.theme.ts +3 -7
  71. package/src/components/pagination/RLAPagination.astro +2 -2
  72. package/src/components/pagination/pagination.theme.ts +1 -1
  73. package/src/components/popover/RLAPopover.astro +39 -0
  74. package/src/components/progress/RLAProgress.astro +2 -0
  75. package/src/components/progress/progress.theme.ts +0 -4
  76. package/src/components/scroll-area/scroll-area.theme.ts +1 -1
  77. package/src/components/scroll-to-top/RLAScrollToTop.astro +6 -5
  78. package/src/components/scroll-to-top/RLVScrollToTop.vue +7 -7
  79. package/src/components/scroll-to-top/scroll-to-top.theme.ts +3 -7
  80. package/src/components/search/RLASearch.astro +3 -3
  81. package/src/components/select/RLASelect.astro +96 -528
  82. package/src/components/select/RLSSelect.tsx +68 -14
  83. package/src/components/select/RLVSelect.vue +57 -0
  84. package/src/components/select/select.theme.ts +11 -96
  85. package/src/components/shortcuts/RLAShortcuts.astro +9 -9
  86. package/src/components/sidebar/RLASidebar.astro +3 -3
  87. package/src/components/slideover/RLASlideover.astro +1 -1
  88. package/src/components/slider/RLASlider.astro +2 -1
  89. package/src/components/slider/slider.theme.ts +1 -5
  90. package/src/components/spinner/RLASpinner.astro +2 -1
  91. package/src/components/spinner/spinner.theme.ts +1 -5
  92. package/src/components/splitter/splitter.theme.ts +1 -1
  93. package/src/components/stepper/RLAStepper.astro +8 -8
  94. package/src/components/stepper/stepper.theme.ts +6 -7
  95. package/src/components/steps/RLASteps.astro +3 -3
  96. package/src/components/switch/RLASwitch.astro +2 -1
  97. package/src/components/switch/switch.theme.ts +0 -4
  98. package/src/components/table/table.theme.ts +1 -1
  99. package/src/components/table-of-contents/RLATableOfContents.astro +5 -5
  100. package/src/components/theme-selector/RLAThemeSelector.astro +16 -14
  101. package/src/components/theme-selector/theme-selector.theme.ts +2 -2
  102. package/src/components/toast/RLAToast.astro +6 -6
  103. package/src/components/toast/store.ts +1 -1
  104. package/src/components/tooltip/tooltip.theme.ts +1 -1
  105. package/src/icons.raw.ts +67 -0
  106. package/src/icons.ts +31 -64
  107. package/src/index.ts +19 -18
  108. package/src/preset.ts +110 -108
  109. package/src/resolver.ts +127 -15
  110. package/src/shortcuts.ts +103 -84
  111. package/src/stores.ts +22 -0
  112. package/src/types.ts +27 -9
  113. package/src/virtual.d.ts +4 -0
  114. package/src/components/editor/PageEditor.vue +0 -459
  115. package/src/components/editor/PageMention.vue +0 -87
  116. package/src/components/editor/PagePropertiesEditor.vue +0 -358
  117. package/src/components/editor/PagePropertiesRenderer.vue +0 -285
  118. package/src/components/editor/PageRenderer.vue +0 -155
  119. package/src/components/editor/PageSurround.vue +0 -130
  120. package/src/components/editor/PageVersionSelector.vue +0 -371
  121. package/src/components/editor/blocks/AddBlockModal.vue +0 -196
  122. package/src/components/editor/blocks/Block.vue +0 -142
  123. package/src/components/editor/blocks/BlockEditRenderer.vue +0 -26
  124. package/src/components/editor/blocks/BlockEditor.vue +0 -141
  125. package/src/components/editor/blocks/BlockViewRenderer.vue +0 -71
  126. package/src/components/editor/blocks/TextRenderer.vue +0 -31
  127. package/src/components/editor/blocks/editor/CalloutBlockEditor.vue +0 -53
  128. package/src/components/editor/blocks/editor/ImageBlockEditor.vue +0 -55
  129. package/src/components/editor/blocks/editor/ParagraphBlockEditor.vue +0 -31
  130. package/src/components/editor/blocks/editor/SectionBlockEditor.vue +0 -65
  131. package/src/components/editor/blocks/nodes/LinkNode.vue +0 -28
  132. package/src/components/editor/blocks/nodes/TextNode.vue +0 -22
  133. package/src/components/editor/blocks/renderer/CalloutBlockRenderer.vue +0 -65
  134. package/src/components/editor/blocks/renderer/ImageBlockRenderer.vue +0 -20
  135. package/src/components/editor/blocks/renderer/ParagraphBlockRenderer.vue +0 -21
  136. package/src/components/editor/blocks/renderer/SectionBlockRenderer.vue +0 -55
  137. package/src/components/editor/composables/injectionKeys.ts +0 -3
  138. package/src/components/editor/composables/useBlockEditor.ts +0 -261
  139. package/src/components/editor/composables/usePageEditor.ts +0 -97
  140. package/src/components/editor/modals/CreatePageModal.vue +0 -160
  141. package/src/components/editor/modals/DeletePageModal.vue +0 -116
  142. package/src/components/editor/modals/PageTreeModal.vue +0 -275
  143. package/src/components/editor/types/blocks.ts +0 -100
  144. package/src/components/editor/types/pages.ts +0 -89
  145. package/src/components/editor/types/schemas.ts +0 -36
  146. package/src/components/editor/utils/blocks.ts +0 -42
  147. package/src/components/editor/utils/editorHelpers.ts +0 -24
  148. package/src/components/editor/utils/page.ts +0 -219
  149. package/src/components/editor/utils/richTextHelpers.ts +0 -35
  150. package/src/components/editor/views/PageEditView.vue +0 -195
  151. package/src/components/editor/views/PageLiveView.vue +0 -90
  152. package/src/components/editor/views/PageReviewView.vue +0 -308
  153. package/src/components/icon/resolver.ts +0 -23
@@ -1,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>