@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.
Files changed (154) hide show
  1. package/README.md +14 -12
  2. package/package.json +8 -10
  3. package/src/components/avatar/RLAAvatar.astro +4 -12
  4. package/src/components/badge/RLABadge.astro +2 -1
  5. package/src/components/badge/badge.theme.ts +0 -4
  6. package/src/components/banner/RLABanner.astro +1 -1
  7. package/src/components/banner/banner.theme.ts +1 -1
  8. package/src/components/breadcrumb/RLABreadcrumb.astro +2 -2
  9. package/src/components/button/RLAButton.astro +4 -1
  10. package/src/components/button/RLSButton.tsx +1 -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 +52 -20
  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 +8 -5
  68. package/src/components/modal/RLSModal.tsx +17 -12
  69. package/src/components/modal/modal.theme.ts +5 -4
  70. package/src/components/navigation-menu/RLANavigationMenu.astro +17 -21
  71. package/src/components/navigation-menu/navigation-menu.theme.ts +3 -7
  72. package/src/components/pagination/RLAPagination.astro +2 -2
  73. package/src/components/pagination/pagination.theme.ts +1 -1
  74. package/src/components/popover/RLAPopover.astro +39 -0
  75. package/src/components/progress/RLAProgress.astro +2 -0
  76. package/src/components/progress/progress.theme.ts +0 -4
  77. package/src/components/scroll-area/scroll-area.theme.ts +1 -1
  78. package/src/components/scroll-to-top/RLAScrollToTop.astro +6 -5
  79. package/src/components/scroll-to-top/RLVScrollToTop.vue +7 -7
  80. package/src/components/scroll-to-top/scroll-to-top.theme.ts +3 -7
  81. package/src/components/search/RLASearch.astro +3 -3
  82. package/src/components/select/RLASelect.astro +96 -528
  83. package/src/components/select/RLSSelect.tsx +68 -14
  84. package/src/components/select/RLVSelect.vue +57 -0
  85. package/src/components/select/select.theme.ts +11 -96
  86. package/src/components/shortcuts/RLAShortcuts.astro +9 -9
  87. package/src/components/sidebar/RLASidebar.astro +3 -3
  88. package/src/components/slideover/RLASlideover.astro +1 -1
  89. package/src/components/slider/RLASlider.astro +2 -1
  90. package/src/components/slider/slider.theme.ts +1 -5
  91. package/src/components/spinner/RLASpinner.astro +2 -1
  92. package/src/components/spinner/spinner.theme.ts +1 -5
  93. package/src/components/splitter/splitter.theme.ts +1 -1
  94. package/src/components/stepper/RLAStepper.astro +8 -8
  95. package/src/components/stepper/stepper.theme.ts +6 -7
  96. package/src/components/steps/RLASteps.astro +3 -3
  97. package/src/components/switch/RLASwitch.astro +2 -1
  98. package/src/components/switch/switch.theme.ts +0 -4
  99. package/src/components/table/table.theme.ts +1 -1
  100. package/src/components/table-of-contents/RLATableOfContents.astro +5 -5
  101. package/src/components/theme-selector/RLAThemeSelector.astro +16 -14
  102. package/src/components/theme-selector/theme-selector.theme.ts +2 -2
  103. package/src/components/toast/RLAToast.astro +6 -6
  104. package/src/components/toast/store.ts +1 -1
  105. package/src/components/tooltip/tooltip.theme.ts +1 -1
  106. package/src/icons.raw.ts +67 -0
  107. package/src/icons.ts +31 -64
  108. package/src/index.ts +19 -18
  109. package/src/preset.ts +110 -108
  110. package/src/resolver.ts +127 -15
  111. package/src/shortcuts.ts +103 -84
  112. package/src/stores.ts +22 -0
  113. package/src/types.ts +27 -9
  114. package/src/virtual.d.ts +4 -0
  115. package/src/components/editor/PageEditor.vue +0 -459
  116. package/src/components/editor/PageMention.vue +0 -87
  117. package/src/components/editor/PagePropertiesEditor.vue +0 -358
  118. package/src/components/editor/PagePropertiesRenderer.vue +0 -285
  119. package/src/components/editor/PageRenderer.vue +0 -155
  120. package/src/components/editor/PageSurround.vue +0 -130
  121. package/src/components/editor/PageVersionSelector.vue +0 -371
  122. package/src/components/editor/blocks/AddBlockModal.vue +0 -196
  123. package/src/components/editor/blocks/Block.vue +0 -142
  124. package/src/components/editor/blocks/BlockEditRenderer.vue +0 -26
  125. package/src/components/editor/blocks/BlockEditor.vue +0 -141
  126. package/src/components/editor/blocks/BlockViewRenderer.vue +0 -71
  127. package/src/components/editor/blocks/TextRenderer.vue +0 -31
  128. package/src/components/editor/blocks/editor/CalloutBlockEditor.vue +0 -53
  129. package/src/components/editor/blocks/editor/ImageBlockEditor.vue +0 -55
  130. package/src/components/editor/blocks/editor/ParagraphBlockEditor.vue +0 -31
  131. package/src/components/editor/blocks/editor/SectionBlockEditor.vue +0 -65
  132. package/src/components/editor/blocks/nodes/LinkNode.vue +0 -28
  133. package/src/components/editor/blocks/nodes/TextNode.vue +0 -22
  134. package/src/components/editor/blocks/renderer/CalloutBlockRenderer.vue +0 -65
  135. package/src/components/editor/blocks/renderer/ImageBlockRenderer.vue +0 -20
  136. package/src/components/editor/blocks/renderer/ParagraphBlockRenderer.vue +0 -21
  137. package/src/components/editor/blocks/renderer/SectionBlockRenderer.vue +0 -55
  138. package/src/components/editor/composables/injectionKeys.ts +0 -3
  139. package/src/components/editor/composables/useBlockEditor.ts +0 -261
  140. package/src/components/editor/composables/usePageEditor.ts +0 -97
  141. package/src/components/editor/modals/CreatePageModal.vue +0 -160
  142. package/src/components/editor/modals/DeletePageModal.vue +0 -116
  143. package/src/components/editor/modals/PageTreeModal.vue +0 -275
  144. package/src/components/editor/types/blocks.ts +0 -100
  145. package/src/components/editor/types/pages.ts +0 -89
  146. package/src/components/editor/types/schemas.ts +0 -36
  147. package/src/components/editor/utils/blocks.ts +0 -42
  148. package/src/components/editor/utils/editorHelpers.ts +0 -24
  149. package/src/components/editor/utils/page.ts +0 -219
  150. package/src/components/editor/utils/richTextHelpers.ts +0 -35
  151. package/src/components/editor/views/PageEditView.vue +0 -195
  152. package/src/components/editor/views/PageLiveView.vue +0 -90
  153. package/src/components/editor/views/PageReviewView.vue +0 -308
  154. package/src/components/icon/resolver.ts +0 -23
@@ -1,55 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from "vue"
3
- import type { ImageBlockProps } from "../../types/blocks"
4
- import RLVInput from "../../../input/RLVInput.vue"
5
-
6
- const props = defineProps<{ props: ImageBlockProps }>()
7
- const emit = defineEmits<{ "update:props": [value: ImageBlockProps] }>()
8
-
9
- const src = computed({
10
- get: () => props.props.src || "",
11
- set: (val: string) => emit("update:props", { ...props.props, src: val })
12
- })
13
-
14
- const alt = computed({
15
- get: () => props.props.alt || "",
16
- set: (val: string) => emit("update:props", { ...props.props, alt: val })
17
- })
18
-
19
- const caption = computed({
20
- get: () => props.props.caption || "",
21
- set: (val: string) => emit("update:props", { ...props.props, caption: val })
22
- })
23
-
24
- defineOptions({ name: "ImageBlockEditor" })
25
- </script>
26
-
27
- <template>
28
- <div
29
- class="w-full flex flex-col gap-3 p-4 border border-default rounded-xl bg-white dark:bg-neutral-900"
30
- >
31
- <div class="flex flex-col gap-1">
32
- <label class="text-xs font-bold text-dimmed uppercase">Image URL</label>
33
- <RLVInput v-model="src" placeholder="https://..." class="w-full" />
34
- </div>
35
-
36
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
37
- <div class="flex flex-col gap-1">
38
- <label class="text-xs font-bold text-dimmed uppercase">Alt Text</label>
39
- <RLVInput v-model="alt" placeholder="Alt description" class="w-full" />
40
- </div>
41
-
42
- <div class="flex flex-col gap-1">
43
- <label class="text-xs font-bold text-dimmed uppercase">Caption</label>
44
- <RLVInput v-model="caption" placeholder="Image caption" class="w-full" />
45
- </div>
46
- </div>
47
-
48
- <div
49
- v-if="src"
50
- class="mt-2 flex justify-center bg-neutral-100 dark:bg-neutral-800 p-2 rounded-lg"
51
- >
52
- <img :src="src" :alt="alt" class="max-h-48 object-cover rounded border border-default" />
53
- </div>
54
- </div>
55
- </template>
@@ -1,31 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from "vue"
3
- import type { ParagraphBlockProps } from "../../types/blocks"
4
- import { richTextToHtml, parseHtmlToRichText } from "../../utils/richTextHelpers"
5
-
6
- const props = defineProps<{ props: ParagraphBlockProps }>()
7
- const emit = defineEmits<{ "update:props": [value: ParagraphBlockProps] }>()
8
-
9
- const textValue = computed({
10
- get: () => richTextToHtml(props.props?.text || []),
11
- set: (val: string) => {
12
- emit("update:props", {
13
- ...props.props,
14
- text: parseHtmlToRichText(val)
15
- })
16
- }
17
- })
18
-
19
- defineOptions({ name: "ParagraphBlockEditor" })
20
- </script>
21
-
22
- <template>
23
- <div class="w-full">
24
- <textarea
25
- v-model="textValue"
26
- placeholder="Type paragraph text here..."
27
- rows="3"
28
- class="w-full p-3 bg-white dark:bg-neutral-900 border border-default rounded-lg text-sm text-neutral-900 dark:text-neutral-100 focus:outline-none focus:ring-2 focus:ring-primary resize-y"
29
- />
30
- </div>
31
- </template>
@@ -1,65 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from "vue"
3
- import type { SectionBlockProps } from "../../types/blocks"
4
- import RLVInput from "../../../input/RLVInput.vue"
5
- import RLVButton from "../../../button/RLVButton.vue"
6
- import { useI18n } from "../../utils/editorHelpers"
7
-
8
- import BlockEditRenderer from "../BlockEditRenderer.vue"
9
-
10
- const props = defineProps<{ props: SectionBlockProps; containerId?: string | null }>()
11
- const emit = defineEmits<{ "update:props": [value: SectionBlockProps] }>()
12
-
13
- const { t } = useI18n()
14
-
15
- const title = computed({
16
- get: () => props.props.title || "",
17
- set: (val: string) => emit("update:props", { ...props.props, title: val })
18
- })
19
-
20
- const description = computed({
21
- get: () => props.props.description || "",
22
- set: (val: string) => emit("update:props", { ...props.props, description: val })
23
- })
24
-
25
- const level = computed({
26
- get: () => props.props.level || 2,
27
- set: (val: number) => emit("update:props", { ...props.props, level: val as any })
28
- })
29
-
30
- const children = computed({
31
- get: () => props.props.children || [],
32
- set: (val: any[]) => emit("update:props", { ...props.props, children: val })
33
- })
34
-
35
- defineOptions({ name: "SectionBlockEditor" })
36
- </script>
37
-
38
- <template>
39
- <div
40
- class="w-full flex flex-col gap-4 border border-default rounded-xl p-4 bg-neutral-50/50 dark:bg-neutral-900/30"
41
- >
42
- <div class="flex items-center gap-3">
43
- <select
44
- v-model.number="level"
45
- class="bg-white dark:bg-neutral-800 border border-default rounded-lg px-2 py-1 text-xs font-bold focus:outline-none"
46
- >
47
- <option :value="1">H1</option>
48
- <option :value="2">H2</option>
49
- <option :value="3">H3</option>
50
- <option :value="4">H4</option>
51
- </select>
52
- <RLVInput v-model="title" placeholder="Section Title" class="flex-1 font-bold text-lg" />
53
- </div>
54
-
55
- <RLVInput
56
- v-model="description"
57
- placeholder="Section description (optional)..."
58
- class="text-sm text-dimmed"
59
- />
60
-
61
- <div class="pl-4 border-l-2 border-primary-500/30 flex flex-col gap-4 mt-2">
62
- <BlockEditRenderer v-model:blocks="children" :container-id="containerId" />
63
- </div>
64
- </div>
65
- </template>
@@ -1,28 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from "vue"
3
- import { useI18n } from "../../utils/editorHelpers"
4
- import { getLocalizedContent } from "../../utils/page"
5
-
6
- const props = defineProps<{ node: any }>()
7
- const { locale } = useI18n()
8
-
9
- const href = computed(() => props.node?.props?.href || props.node?.href || props.node?.to || "#")
10
- const target = computed(() => props.node?.props?.target || props.node?.target || "_self")
11
- const contentText = computed(() => {
12
- const n = props.node
13
- const val = n?.props?.content ?? n?.content ?? n?.label ?? href.value
14
- return getLocalizedContent(val, locale.value)
15
- })
16
-
17
- defineOptions({ name: "LinkNode" })
18
- </script>
19
-
20
- <template>
21
- <a
22
- :href="href"
23
- :target="target"
24
- class="text-primary underline hover:text-primary-600 transition-colors"
25
- >
26
- {{ contentText }}
27
- </a>
28
- </template>
@@ -1,22 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from "vue"
3
- import { useI18n } from "../../utils/editorHelpers"
4
- import { getLocalizedContent } from "../../utils/page"
5
-
6
- const props = defineProps<{ node: any }>()
7
- const { locale } = useI18n()
8
-
9
- const textContent = computed(() => {
10
- const n = props.node
11
- if (!n) return ""
12
- if (typeof n === "string") return n
13
- const val = n.props?.content ?? n.content ?? n.text ?? n.value
14
- return getLocalizedContent(val, locale.value)
15
- })
16
-
17
- defineOptions({ name: "TextNode" })
18
- </script>
19
-
20
- <template>
21
- <span>{{ textContent }}</span>
22
- </template>
@@ -1,65 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from "vue"
3
- import BlockViewRenderer from "../BlockViewRenderer.vue"
4
-
5
- const props = defineProps<{ props: any }>()
6
-
7
- const calloutProps = computed(() => props.props || {})
8
- const calloutChildren = computed(() => {
9
- const p = props.props
10
- if (!p) return []
11
- return p.children || p.blocks || p.slots?.default || []
12
- })
13
-
14
- const variantClasses = computed(() => {
15
- switch (calloutProps.value.variant) {
16
- case "success":
17
- return "bg-green-500/10 border-green-500/30 text-green-700 dark:text-green-300"
18
- case "warning":
19
- return "bg-amber-500/10 border-amber-500/30 text-amber-700 dark:text-amber-300"
20
- case "error":
21
- return "bg-red-500/10 border-red-500/30 text-red-700 dark:text-red-300"
22
- case "commentary":
23
- return "bg-purple-500/10 border-purple-500/30 text-purple-700 dark:text-purple-300"
24
- case "ideation":
25
- return "bg-indigo-500/10 border-indigo-500/30 text-indigo-700 dark:text-indigo-300"
26
- case "source":
27
- return "bg-neutral-500/10 border-neutral-500/30 text-neutral-700 dark:text-neutral-300"
28
- default:
29
- return "bg-blue-500/10 border-blue-500/30 text-blue-700 dark:text-blue-300"
30
- }
31
- })
32
-
33
- const iconName = computed(() => {
34
- switch (calloutProps.value.variant) {
35
- case "success":
36
- return "i-lucide-check-circle"
37
- case "warning":
38
- return "i-lucide-alert-triangle"
39
- case "error":
40
- return "i-lucide-alert-circle"
41
- case "commentary":
42
- return "i-lucide-message-square"
43
- case "ideation":
44
- return "i-lucide-lightbulb"
45
- case "source":
46
- return "i-lucide-file-code"
47
- default:
48
- return "i-lucide-info"
49
- }
50
- })
51
-
52
- defineOptions({ name: "CalloutBlockRenderer" })
53
- </script>
54
-
55
- <template>
56
- <div class="p-4 rounded-xl border flex gap-3 my-4 backdrop-blur-sm" :class="variantClasses">
57
- <span :class="[iconName, 'size-5 shrink-0 mt-0.5']" />
58
- <div class="flex-1 min-w-0">
59
- <BlockViewRenderer
60
- v-if="calloutChildren && calloutChildren.length"
61
- :blocks="calloutChildren"
62
- />
63
- </div>
64
- </div>
65
- </template>
@@ -1,20 +0,0 @@
1
- <script setup lang="ts">
2
- import type { ImageBlockProps } from "../../types/blocks"
3
-
4
- defineProps<{ props: ImageBlockProps }>()
5
- defineOptions({ name: "ImageBlockRenderer" })
6
- </script>
7
-
8
- <template>
9
- <figure class="my-6 flex flex-col items-center">
10
- <img
11
- v-if="props.src"
12
- :src="props.src"
13
- :alt="props.alt || ''"
14
- class="rounded-xl border border-default shadow-md max-w-full h-auto object-cover max-h-[500px]"
15
- />
16
- <figcaption v-if="props.caption" class="text-xs text-dimmed mt-2 text-center italic">
17
- {{ props.caption }}
18
- </figcaption>
19
- </figure>
20
- </template>
@@ -1,21 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from "vue"
3
- import TextRenderer from "../TextRenderer.vue"
4
-
5
- const props = defineProps<{ props: any }>()
6
- const textContent = computed(() => {
7
- const p = props.props
8
- if (!p) return ""
9
- return p.text ?? p.content ?? p.value ?? ""
10
- })
11
-
12
- defineOptions({ name: "ParagraphBlockRenderer" })
13
- </script>
14
-
15
- <template>
16
- <p
17
- class="text-base text-neutral-800 dark:text-neutral-200 leading-relaxed my-2 whitespace-pre-wrap"
18
- >
19
- <TextRenderer :content="textContent" />
20
- </p>
21
- </template>
@@ -1,55 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from "vue"
3
- import { useI18n } from "../../utils/editorHelpers"
4
- import { getLocalizedContent } from "../../utils/page"
5
-
6
- import BlockViewRenderer from "../BlockViewRenderer.vue"
7
-
8
- const props = defineProps<{ props: any }>()
9
- const { locale } = useI18n()
10
-
11
- const headingTag = computed(() => `h${props.props?.level || 2}`)
12
- const headingClasses = computed(() => {
13
- switch (Number(props.props?.level || 2)) {
14
- case 1:
15
- return "text-3xl font-extrabold tracking-tight mt-8 mb-4 border-b pb-2 text-neutral-900 dark:text-white"
16
- case 2:
17
- return "text-2xl font-bold tracking-tight mt-6 mb-3 text-neutral-900 dark:text-white"
18
- case 3:
19
- return "text-xl font-semibold mt-4 mb-2 text-neutral-900 dark:text-white"
20
- default:
21
- return "text-lg font-medium mt-3 mb-2 text-neutral-900 dark:text-white"
22
- }
23
- })
24
-
25
- const titleText = computed(() => getLocalizedContent(props.props?.title, locale.value))
26
- const descriptionText = computed(() => getLocalizedContent(props.props?.description, locale.value))
27
- const sectionChildren = computed(() => {
28
- const p = props.props
29
- if (!p) return []
30
- return p.children || p.blocks || p.slots?.default || []
31
- })
32
-
33
- defineOptions({ name: "SectionBlockRenderer" })
34
- </script>
35
-
36
- <template>
37
- <div class="my-4">
38
- <component
39
- :is="headingTag"
40
- v-if="titleText"
41
- :class="headingClasses"
42
- :id="typeof titleText === 'string' ? titleText.toLowerCase().replace(/\s+/g, '-') : undefined"
43
- >
44
- {{ titleText }}
45
- </component>
46
-
47
- <p v-if="descriptionText" class="text-sm text-dimmed mb-4 italic">
48
- {{ descriptionText }}
49
- </p>
50
-
51
- <div v-if="sectionChildren && sectionChildren.length" class="flex flex-col gap-2">
52
- <BlockViewRenderer :blocks="sectionChildren" />
53
- </div>
54
- </div>
55
- </template>
@@ -1,3 +0,0 @@
1
- import type { InjectionKey, ComputedRef } from "vue"
2
-
3
- export const SECTION_LEVEL_KEY = Symbol("section-level") as InjectionKey<ComputedRef<number>>
@@ -1,261 +0,0 @@
1
- import { type Ref, computed, ref, shallowRef } from "vue"
2
- import type { Block } from "../types/pages"
3
-
4
- function findBlockLocation(
5
- blocks: Block[],
6
- id: string
7
- ): { parentArray: Block[]; index: number } | null {
8
- for (let i = 0; i < blocks.length; i++) {
9
- const block = blocks[i]
10
- if (!block) continue
11
- if (block.id === id) {
12
- return { parentArray: blocks, index: i }
13
- }
14
- if ("children" in block.props && Array.isArray(block.props.children)) {
15
- const result = findBlockLocation(block.props.children, id)
16
- if (result) return result
17
- }
18
- }
19
- return null
20
- }
21
-
22
- function regenerateIds(block: Block): void {
23
- block.id =
24
- typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
25
- ? crypto.randomUUID()
26
- : Math.random().toString(36).substring(2)
27
-
28
- if ("children" in block.props && Array.isArray(block.props.children)) {
29
- block.props.children.forEach((child: Block) => regenerateIds(child))
30
- }
31
- }
32
-
33
- function createDefaultBlock(type: Block["type"]): Block {
34
- const id =
35
- typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
36
- ? crypto.randomUUID()
37
- : Math.random().toString(36).substring(2)
38
- switch (type) {
39
- case "SectionBlock":
40
- return {
41
- id,
42
- type: "SectionBlock",
43
- props: {
44
- level: 2,
45
- title: "New Section",
46
- children: []
47
- }
48
- }
49
- case "ParagraphBlock":
50
- return {
51
- id,
52
- type: "ParagraphBlock",
53
- props: {
54
- text: []
55
- }
56
- }
57
- case "CalloutBlock":
58
- return {
59
- id,
60
- type: "CalloutBlock",
61
- props: {
62
- variant: "info",
63
- children: []
64
- }
65
- }
66
- case "ImageBlock":
67
- return {
68
- id,
69
- type: "ImageBlock",
70
- props: {
71
- src: "https://placehold.co/600x400",
72
- alt: "Placeholder Image"
73
- }
74
- }
75
- default:
76
- return {
77
- id,
78
- type: "ParagraphBlock",
79
- props: { text: [] }
80
- }
81
- }
82
- }
83
-
84
- export function useBlockEditor(
85
- initialBlocks: Ref<Block[]>,
86
- { maxHistorySize = 100, onMutation }: { maxHistorySize?: number; onMutation?: () => void } = {}
87
- ) {
88
- const history = shallowRef<Block[][]>([])
89
- const future = shallowRef<Block[][]>([])
90
- const committedBlocks = ref<Block[]>(JSON.parse(JSON.stringify(initialBlocks.value)))
91
-
92
- const canUndo = computed(() => history.value.length > 0)
93
- const canRedo = computed(() => future.value.length > 0)
94
-
95
- const captureSnapshot = () => {
96
- const snapshot: Block[] = JSON.parse(JSON.stringify(initialBlocks.value))
97
- future.value = []
98
- const newHistory = [...history.value, snapshot]
99
- if (newHistory.length > maxHistorySize) {
100
- newHistory.shift()
101
- }
102
- history.value = newHistory
103
- }
104
-
105
- const executeMutation = (mutationFn: () => void) => {
106
- if (onMutation) {
107
- onMutation()
108
- } else {
109
- captureSnapshot()
110
- }
111
- mutationFn()
112
- }
113
-
114
- const undo = () => {
115
- if (history.value.length === 0) return
116
- const currentState: Block[] = JSON.parse(JSON.stringify(initialBlocks.value))
117
- future.value = [currentState, ...future.value]
118
- const previousState = history.value[history.value.length - 1]
119
- if (previousState) {
120
- initialBlocks.value.splice(0, initialBlocks.value.length, ...previousState)
121
- const newHistory = [...history.value]
122
- newHistory.pop()
123
- history.value = newHistory
124
- }
125
- }
126
-
127
- const redo = () => {
128
- if (future.value.length === 0) return
129
- const currentState: Block[] = JSON.parse(JSON.stringify(initialBlocks.value))
130
- let newHistory = [...history.value, currentState]
131
- if (newHistory.length > maxHistorySize) {
132
- newHistory.shift()
133
- }
134
- history.value = newHistory
135
- const nextState = future.value[0]
136
- if (nextState) {
137
- initialBlocks.value.splice(0, initialBlocks.value.length, ...nextState)
138
- const newFuture = [...future.value]
139
- newFuture.shift()
140
- future.value = newFuture
141
- }
142
- }
143
-
144
- const updateBlockProps = (id: string, newProps: Record<string, any>) => {
145
- executeMutation(() => {
146
- const loc = findBlockLocation(initialBlocks.value, id)
147
- if (!loc) return
148
- const oldBlock = loc.parentArray[loc.index]
149
- if (!oldBlock) return
150
- Object.assign(oldBlock.props, newProps)
151
- })
152
- }
153
-
154
- const removeBlock = (id: string) => {
155
- executeMutation(() => {
156
- const loc = findBlockLocation(initialBlocks.value, id)
157
- if (!loc) return
158
- loc.parentArray.splice(loc.index, 1)
159
- })
160
- }
161
-
162
- const moveBlock = (id: string, direction: -1 | 1) => {
163
- executeMutation(() => {
164
- const loc = findBlockLocation(initialBlocks.value, id)
165
- if (!loc) return
166
- const { parentArray, index } = loc
167
- const newIndex = index + direction
168
- if (newIndex >= 0 && newIndex < parentArray.length) {
169
- const movedBlock = parentArray.splice(index, 1)[0]
170
- if (movedBlock) {
171
- parentArray.splice(newIndex, 0, movedBlock)
172
- }
173
- }
174
- })
175
- }
176
-
177
- const duplicateBlock = (id: string) => {
178
- executeMutation(() => {
179
- const loc = findBlockLocation(initialBlocks.value, id)
180
- if (!loc) return
181
- const original = loc.parentArray[loc.index]
182
- const clone: Block = JSON.parse(JSON.stringify(original))
183
- regenerateIds(clone)
184
- loc.parentArray.splice(loc.index + 1, 0, clone)
185
- })
186
- }
187
-
188
- const insertBlock = (
189
- newBlockType: Block["type"],
190
- targetId: string | null = null,
191
- position: "before" | "after" = "after"
192
- ) => {
193
- executeMutation(() => {
194
- const newBlock = createDefaultBlock(newBlockType)
195
- if (!targetId) {
196
- initialBlocks.value.push(newBlock)
197
- return
198
- }
199
- const loc = findBlockLocation(initialBlocks.value, targetId)
200
- if (!loc) return
201
- const { parentArray, index } = loc
202
- const insertIndex = position === "after" ? index + 1 : index
203
- parentArray.splice(insertIndex, 0, newBlock)
204
- })
205
- }
206
-
207
- const commitChanges = (): Block[] => {
208
- const committedSnapshot: Block[] = JSON.parse(JSON.stringify(initialBlocks.value))
209
- committedBlocks.value = committedSnapshot
210
- return committedSnapshot
211
- }
212
-
213
- const relocateBlock = (
214
- blockId: string,
215
- targetContainerId: string | null,
216
- targetIndex: number
217
- ) => {
218
- executeMutation(() => {
219
- const sourceLoc = findBlockLocation(initialBlocks.value, blockId)
220
- if (!sourceLoc) return
221
- const block = sourceLoc.parentArray[sourceLoc.index]
222
- if (!block) return
223
- sourceLoc.parentArray.splice(sourceLoc.index, 1)
224
-
225
- let targetArray: Block[]
226
- if (targetContainerId === null) {
227
- targetArray = initialBlocks.value
228
- } else {
229
- const targetLoc = findBlockLocation(initialBlocks.value, targetContainerId)
230
- if (!targetLoc) {
231
- sourceLoc.parentArray.splice(sourceLoc.index, 0, block)
232
- return
233
- }
234
- const targetBlock = targetLoc.parentArray[targetLoc.index]
235
- if (!targetBlock || !("children" in targetBlock.props)) {
236
- sourceLoc.parentArray.splice(sourceLoc.index, 0, block)
237
- return
238
- }
239
- targetArray = targetBlock.props.children
240
- }
241
-
242
- const safeIndex = Math.max(0, Math.min(targetIndex, targetArray.length))
243
- targetArray.splice(safeIndex, 0, block)
244
- })
245
- }
246
-
247
- return {
248
- updateBlockProps,
249
- removeBlock,
250
- moveBlock,
251
- duplicateBlock,
252
- insertBlock,
253
- relocateBlock,
254
- undo,
255
- redo,
256
- canUndo,
257
- canRedo,
258
- committedBlocks,
259
- commitChanges
260
- }
261
- }