rimelight-components 2.1.76 → 2.1.78

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/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rimelight-components",
3
- "version": "2.1.76",
3
+ "version": "2.1.78",
4
4
  "docs": "https://rimelight.com/tools/rimelight-components",
5
5
  "configKey": "rimelightComponents",
6
6
  "compatibility": {
package/dist/module.mjs CHANGED
@@ -4,7 +4,7 @@ import { readdirSync } from 'node:fs';
4
4
  import { basename } from 'node:path';
5
5
 
6
6
  const name = "rimelight-components";
7
- const version = "2.1.76";
7
+ const version = "2.1.78";
8
8
  const homepage = "https://rimelight.com/tools/rimelight-components";
9
9
 
10
10
  const defaultOptions = {
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { provide } from "vue";
2
+ import { provide, computed } from "vue";
3
3
  import { useBlockEditor } from "../../composables";
4
4
  import { tv } from "../../internal/tv";
5
5
  import { useRC } from "../../composables";
@@ -10,6 +10,16 @@ const { historyLimit, rc: rcProp } = defineProps({
10
10
  const blocks = defineModel({ type: Array, ...{ required: true } });
11
11
  const emit = defineEmits(["save"]);
12
12
  const slots = defineSlots();
13
+ const blockTypes = [
14
+ { label: "Paragraph", value: "ParagraphBlock", icon: "i-lucide-pilcrow" },
15
+ { label: "Section", value: "SectionBlock", icon: "i-lucide-heading" },
16
+ { label: "Image", value: "ImageBlock", icon: "i-lucide-image" },
17
+ { label: "Callout", value: "CalloutBlock", icon: "i-lucide-info" },
18
+ { label: "Quote", value: "QuoteBlock", icon: "i-lucide-quote" },
19
+ { label: "List", value: "UnorderedListBlock", icon: "i-lucide-list" },
20
+ { label: "Card", value: "CardBlock", icon: "i-lucide-square" },
21
+ { label: "Collapsible Card", value: "CollapsibleCardBlock", icon: "i-lucide-chevron-right-square" }
22
+ ];
13
23
  const { rc } = useRC("BlockEditor", rcProp);
14
24
  const {
15
25
  removeBlock,
@@ -22,6 +32,13 @@ const {
22
32
  canUndo,
23
33
  canRedo
24
34
  } = useBlockEditor(blocks, { maxHistorySize: historyLimit });
35
+ const dropdownItems = computed(() => [
36
+ blockTypes.map((type) => ({
37
+ label: type.label,
38
+ icon: type.icon,
39
+ click: () => insertBlock(type.value)
40
+ }))
41
+ ]);
25
42
  provide("block-editor-api", {
26
43
  removeBlock,
27
44
  moveBlock,
@@ -37,5 +54,23 @@ defineExpose({ undo, redo, canUndo, canRedo });
37
54
  </script>
38
55
 
39
56
  <template>
40
- <RCBlockEditRenderer :blocks="blocks" />
57
+ <div class="flex flex-col gap-8 w-full">
58
+ <RCBlockEditRenderer :blocks="blocks" />
59
+
60
+ <div class="flex flex-col items-center justify-center p-4 border-t border-neutral-200 dark:border-neutral-800 border-dashed rounded-lg">
61
+ <span class="text-sm text-dimmed mb-2">Append new block to page</span>
62
+ <UDropdown
63
+ :items="dropdownItems"
64
+ :popper="{ placement: 'bottom' }"
65
+ >
66
+ <UButton
67
+ color="white"
68
+ label="Add Block"
69
+ trailing-icon="i-heroicons-chevron-down-20-solid"
70
+ variant="solid"
71
+ icon="i-lucide-plus"
72
+ />
73
+ </UDropdown>
74
+ </div>
75
+ </div>
41
76
  </template>
@@ -9,7 +9,7 @@ const menuItems = computed(() => {
9
9
  acc[groupId].push({
10
10
  label: action.label,
11
11
  icon: action.icon,
12
- onSelect: action.onSelect
12
+ click: action.onSelect
13
13
  });
14
14
  return acc;
15
15
  }, {});
@@ -18,15 +18,15 @@ const menuItems = computed(() => {
18
18
  </script>
19
19
 
20
20
  <template>
21
- <div class="fixed bottom-6 right-6 z-10000">
21
+ <div v-if="menuItems.length > 0" class="fixed bottom-6 right-6 z-[10000]">
22
22
  <ClientOnly>
23
23
  <UDropdownMenu :items="menuItems" :ui="{ content: 'w-48' }">
24
24
  <UButton
25
- icon="lucide:plus"
25
+ icon="i-lucide-plus"
26
26
  color="primary"
27
27
  square
28
28
  :ui="{
29
- base: 'rounded-full size-16 lg:size-12 justify-center',
29
+ base: 'rounded-full size-14 lg:size-12 justify-center shadow-lg',
30
30
  leadingIcon: 'size-6'
31
31
  }"
32
32
  />
@@ -1,6 +1,5 @@
1
1
  <script setup>
2
- import { useDebounceFn } from "@vueuse/core";
3
- import { computed, reactive, ref, watch } from "vue";
2
+ import { computed, reactive, ref, watch, onUnmounted } from "vue";
4
3
  import { useApi, $api } from "../../composables";
5
4
  const open = defineModel("open", { type: Boolean, ...{ default: false } });
6
5
  const { note } = defineProps({
@@ -58,8 +57,16 @@ const syncState = () => {
58
57
  }
59
58
  };
60
59
  watch(() => note, syncState);
60
+ const saveTimeout = ref(null);
61
+ const isSaving = ref(false);
62
+ const hasPendingSave = ref(false);
61
63
  const saveNote = async () => {
62
64
  if (!state.title.trim() && !state.content.trim()) return;
65
+ if (isSaving.value) {
66
+ hasPendingSave.value = true;
67
+ return;
68
+ }
69
+ isSaving.value = true;
63
70
  try {
64
71
  let savedNote;
65
72
  const payload = {
@@ -96,6 +103,12 @@ const saveNote = async () => {
96
103
  emit("saved", savedNote);
97
104
  } catch (e) {
98
105
  console.error("Failed to save note", e);
106
+ } finally {
107
+ isSaving.value = false;
108
+ if (hasPendingSave.value) {
109
+ hasPendingSave.value = false;
110
+ saveNote();
111
+ }
99
112
  }
100
113
  };
101
114
  const createLabel = async (newLabelName) => {
@@ -112,7 +125,14 @@ const createLabel = async (newLabelName) => {
112
125
  console.error("Failed to create new label", e);
113
126
  }
114
127
  };
115
- const debouncedSave = useDebounceFn(saveNote, 1e3);
128
+ const debouncedSave = () => {
129
+ if (saveTimeout.value) {
130
+ clearTimeout(saveTimeout.value);
131
+ }
132
+ saveTimeout.value = setTimeout(() => {
133
+ saveNote();
134
+ }, 1e3);
135
+ };
116
136
  watch(
117
137
  () => [
118
138
  state.title,
@@ -129,10 +149,18 @@ watch(open, (isOpen) => {
129
149
  if (isOpen) {
130
150
  syncState();
131
151
  } else {
152
+ if (saveTimeout.value) {
153
+ clearTimeout(saveTimeout.value);
154
+ }
132
155
  saveNote();
133
156
  emit("close");
134
157
  }
135
158
  });
159
+ onUnmounted(() => {
160
+ if (saveTimeout.value) {
161
+ clearTimeout(saveTimeout.value);
162
+ }
163
+ });
136
164
  </script>
137
165
 
138
166
  <template>
@@ -1,6 +1,5 @@
1
1
  import { type PageDefinition, type Page } from "../../../types/index.js";
2
2
  export interface CreatePageModalProps {
3
- isOpen: boolean;
4
3
  definitions: Record<string, PageDefinition>;
5
4
  loading?: boolean;
6
5
  rc?: {
@@ -12,6 +11,7 @@ export interface CreatePageModalProps {
12
11
  footer?: string;
13
12
  };
14
13
  }
14
+ type __VLS_Props = CreatePageModalProps;
15
15
  export interface CreatePageModalEmits {
16
16
  close: [];
17
17
  confirm: [page: Partial<Page>];
@@ -20,14 +20,20 @@ export interface CreatePageModalSlots {
20
20
  default: (props: {}) => any;
21
21
  }
22
22
  type __VLS_Slots = CreatePageModalSlots;
23
- declare const __VLS_base: import("vue").DefineComponent<CreatePageModalProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
23
+ type __VLS_ModelProps = {
24
+ "open"?: boolean;
25
+ };
26
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
27
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
24
28
  close: () => any;
29
+ "update:open": (value: boolean) => any;
25
30
  confirm: (page: Partial<{
26
31
  type: "Default";
27
32
  properties: import("../../../types/index.js").BasePageProperties;
28
33
  } & import("../../../types/index.js").BasePage>) => any;
29
- }, string, import("vue").PublicProps, Readonly<CreatePageModalProps> & Readonly<{
34
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
30
35
  onClose?: (() => any) | undefined;
36
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
31
37
  onConfirm?: ((page: Partial<{
32
38
  type: "Default";
33
39
  properties: import("../../../types/index.js").BasePageProperties;
@@ -4,8 +4,8 @@ import {} from "../../../types";
4
4
  import { tv } from "../../../internal/tv";
5
5
  import { useRC } from "../../../composables";
6
6
  import { useI18n } from "vue-i18n";
7
- const { isOpen, loading, definitions, rc: rcProp } = defineProps({
8
- isOpen: { type: Boolean, required: true },
7
+ const open = defineModel("open", { type: Boolean, ...{ default: false } });
8
+ const { loading, definitions, rc: rcProp } = defineProps({
9
9
  definitions: { type: Object, required: true },
10
10
  loading: { type: Boolean, required: false },
11
11
  rc: { type: Object, required: false }
@@ -37,7 +37,7 @@ const title = ref("");
37
37
  const slug = ref("");
38
38
  const typeOptions = computed(() => {
39
39
  return Object.entries(definitions).map(([key, def]) => ({
40
- label: t(def.typeLabelKey),
40
+ label: t(def.typeLabelKey) === def.typeLabelKey ? key : t(def.typeLabelKey),
41
41
  value: key
42
42
  }));
43
43
  });
@@ -67,10 +67,15 @@ const handleConfirm = () => {
67
67
  };
68
68
  emit("confirm", newPage);
69
69
  };
70
+ watch(open, (isOpen) => {
71
+ if (!isOpen) {
72
+ emit("close");
73
+ }
74
+ });
70
75
  </script>
71
76
 
72
77
  <template>
73
- <UModal :model-value="isOpen" @update:model-value="emit('close')">
78
+ <UModal v-model:open="open">
74
79
  <slot />
75
80
  <template #content>
76
81
  <UCard>
@@ -1,6 +1,5 @@
1
1
  import { type PageDefinition, type Page } from "../../../types/index.js";
2
2
  export interface CreatePageModalProps {
3
- isOpen: boolean;
4
3
  definitions: Record<string, PageDefinition>;
5
4
  loading?: boolean;
6
5
  rc?: {
@@ -12,6 +11,7 @@ export interface CreatePageModalProps {
12
11
  footer?: string;
13
12
  };
14
13
  }
14
+ type __VLS_Props = CreatePageModalProps;
15
15
  export interface CreatePageModalEmits {
16
16
  close: [];
17
17
  confirm: [page: Partial<Page>];
@@ -20,14 +20,20 @@ export interface CreatePageModalSlots {
20
20
  default: (props: {}) => any;
21
21
  }
22
22
  type __VLS_Slots = CreatePageModalSlots;
23
- declare const __VLS_base: import("vue").DefineComponent<CreatePageModalProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
23
+ type __VLS_ModelProps = {
24
+ "open"?: boolean;
25
+ };
26
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
27
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
24
28
  close: () => any;
29
+ "update:open": (value: boolean) => any;
25
30
  confirm: (page: Partial<{
26
31
  type: "Default";
27
32
  properties: import("../../../types/index.js").BasePageProperties;
28
33
  } & import("../../../types/index.js").BasePage>) => any;
29
- }, string, import("vue").PublicProps, Readonly<CreatePageModalProps> & Readonly<{
34
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
30
35
  onClose?: (() => any) | undefined;
36
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
31
37
  onConfirm?: ((page: Partial<{
32
38
  type: "Default";
33
39
  properties: import("../../../types/index.js").BasePageProperties;
@@ -20,6 +20,95 @@ function regenerateIds(block) {
20
20
  block.props.children.forEach((child) => regenerateIds(child));
21
21
  }
22
22
  }
23
+ function createDefaultBlock(type) {
24
+ const id = uuidv7();
25
+ switch (type) {
26
+ case "SectionBlock":
27
+ return {
28
+ id,
29
+ type: "SectionBlock",
30
+ props: {
31
+ level: 2,
32
+ title: "New Section",
33
+ children: []
34
+ }
35
+ };
36
+ case "ParagraphBlock":
37
+ return {
38
+ id,
39
+ type: "ParagraphBlock",
40
+ props: {
41
+ text: []
42
+ }
43
+ };
44
+ case "CalloutBlock":
45
+ return {
46
+ id,
47
+ type: "CalloutBlock",
48
+ props: {
49
+ variant: "info",
50
+ children: [
51
+ {
52
+ id: uuidv7(),
53
+ type: "ParagraphBlock",
54
+ props: { text: [{ type: "text", id: uuidv7(), props: { content: "Callout content" } }] }
55
+ }
56
+ ]
57
+ }
58
+ };
59
+ case "ImageBlock":
60
+ return {
61
+ id,
62
+ type: "ImageBlock",
63
+ props: {
64
+ src: "https://placehold.co/600x400",
65
+ alt: "Placeholder Image"
66
+ }
67
+ };
68
+ case "QuoteBlock":
69
+ return {
70
+ id,
71
+ type: "QuoteBlock",
72
+ props: {
73
+ quote: "Quote text...",
74
+ source: "Source"
75
+ }
76
+ };
77
+ case "UnorderedListBlock":
78
+ return {
79
+ id,
80
+ type: "UnorderedListBlock",
81
+ props: {
82
+ items: []
83
+ }
84
+ };
85
+ case "CardBlock":
86
+ return {
87
+ id,
88
+ type: "CardBlock",
89
+ props: {
90
+ title: "New Card",
91
+ children: []
92
+ }
93
+ };
94
+ case "CollapsibleCardBlock":
95
+ return {
96
+ id,
97
+ type: "CollapsibleCardBlock",
98
+ props: {
99
+ openText: "Show Less",
100
+ closeText: "Show More",
101
+ children: []
102
+ }
103
+ };
104
+ default:
105
+ return {
106
+ id,
107
+ type: "ParagraphBlock",
108
+ props: { text: [] }
109
+ };
110
+ }
111
+ }
23
112
  export function useBlockEditor(initialBlocks, { maxHistorySize = 100, onMutation } = {}) {
24
113
  const history = shallowRef([]);
25
114
  const future = shallowRef([]);
@@ -124,6 +213,16 @@ export function useBlockEditor(initialBlocks, { maxHistorySize = 100, onMutation
124
213
  };
125
214
  const insertBlock = (newBlockType, targetId = null, position = "after") => {
126
215
  executeMutation(() => {
216
+ const newBlock = createDefaultBlock(newBlockType);
217
+ if (!targetId) {
218
+ initialBlocks.value.push(newBlock);
219
+ return;
220
+ }
221
+ const loc = findBlockLocation(initialBlocks.value, targetId);
222
+ if (!loc) return;
223
+ const { parentArray, index } = loc;
224
+ const insertIndex = position === "after" ? index + 1 : index;
225
+ parentArray.splice(insertIndex, 0, newBlock);
127
226
  });
128
227
  };
129
228
  const commitChanges = () => {
@@ -20,6 +20,95 @@ function regenerateIds(block) {
20
20
  block.props.children.forEach((child) => regenerateIds(child));
21
21
  }
22
22
  }
23
+ function createDefaultBlock(type) {
24
+ const id = uuidv7();
25
+ switch (type) {
26
+ case "SectionBlock":
27
+ return {
28
+ id,
29
+ type: "SectionBlock",
30
+ props: {
31
+ level: 2,
32
+ title: "New Section",
33
+ children: []
34
+ }
35
+ };
36
+ case "ParagraphBlock":
37
+ return {
38
+ id,
39
+ type: "ParagraphBlock",
40
+ props: {
41
+ text: []
42
+ }
43
+ };
44
+ case "CalloutBlock":
45
+ return {
46
+ id,
47
+ type: "CalloutBlock",
48
+ props: {
49
+ variant: "info",
50
+ children: [
51
+ {
52
+ id: uuidv7(),
53
+ type: "ParagraphBlock",
54
+ props: { text: [{ type: "text", id: uuidv7(), props: { content: "Callout content" } }] }
55
+ }
56
+ ]
57
+ }
58
+ };
59
+ case "ImageBlock":
60
+ return {
61
+ id,
62
+ type: "ImageBlock",
63
+ props: {
64
+ src: "https://placehold.co/600x400",
65
+ alt: "Placeholder Image"
66
+ }
67
+ };
68
+ case "QuoteBlock":
69
+ return {
70
+ id,
71
+ type: "QuoteBlock",
72
+ props: {
73
+ quote: "Quote text...",
74
+ source: "Source"
75
+ }
76
+ };
77
+ case "UnorderedListBlock":
78
+ return {
79
+ id,
80
+ type: "UnorderedListBlock",
81
+ props: {
82
+ items: []
83
+ }
84
+ };
85
+ case "CardBlock":
86
+ return {
87
+ id,
88
+ type: "CardBlock",
89
+ props: {
90
+ title: "New Card",
91
+ children: []
92
+ }
93
+ };
94
+ case "CollapsibleCardBlock":
95
+ return {
96
+ id,
97
+ type: "CollapsibleCardBlock",
98
+ props: {
99
+ openText: "Show Less",
100
+ closeText: "Show More",
101
+ children: []
102
+ }
103
+ };
104
+ default:
105
+ return {
106
+ id,
107
+ type: "ParagraphBlock",
108
+ props: { text: [] }
109
+ };
110
+ }
111
+ }
23
112
  export function useBlockEditor(initialBlocks, { maxHistorySize = 100, onMutation } = {}) {
24
113
  const history = shallowRef([]);
25
114
  const future = shallowRef([]);
@@ -124,6 +213,16 @@ export function useBlockEditor(initialBlocks, { maxHistorySize = 100, onMutation
124
213
  };
125
214
  const insertBlock = (newBlockType, targetId = null, position = "after") => {
126
215
  executeMutation(() => {
216
+ const newBlock = createDefaultBlock(newBlockType);
217
+ if (!targetId) {
218
+ initialBlocks.value.push(newBlock);
219
+ return;
220
+ }
221
+ const loc = findBlockLocation(initialBlocks.value, targetId);
222
+ if (!loc) return;
223
+ const { parentArray, index } = loc;
224
+ const insertIndex = position === "after" ? index + 1 : index;
225
+ parentArray.splice(insertIndex, 0, newBlock);
127
226
  });
128
227
  };
129
228
  const commitChanges = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rimelight-components",
3
- "version": "2.1.76",
3
+ "version": "2.1.78",
4
4
  "description": "A component library by Rimelight Entertainment.",
5
5
  "keywords": [
6
6
  "nuxt",