rimelight-components 2.0.59 → 2.0.61

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.0.59",
3
+ "version": "2.0.61",
4
4
  "docs": "https://rimelight.com/tools/rimelight-components",
5
5
  "configKey": "rimelightComponents",
6
6
  "compatibility": {
package/dist/module.mjs CHANGED
@@ -5,7 +5,7 @@ import { basename } from 'node:path';
5
5
  export * from '../dist/runtime/types/index.js';
6
6
 
7
7
  const name = "rimelight-components";
8
- const version = "2.0.59";
8
+ const version = "2.0.61";
9
9
  const homepage = "https://rimelight.com/tools/rimelight-components";
10
10
 
11
11
  const defaultOptions = {
@@ -0,0 +1,4 @@
1
+ declare const _default: {
2
+ rimelightComponents: {};
3
+ };
4
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { defineAppConfig } from "#imports";
2
+ export default defineAppConfig({
3
+ rimelightComponents: {}
4
+ });
@@ -16,7 +16,7 @@ const {
16
16
  redo,
17
17
  canUndo,
18
18
  canRedo
19
- } = useBlockEditor(blocks, historyLimit);
19
+ } = useBlockEditor(blocks, { maxHistorySize: historyLimit });
20
20
  provide("block-editor-api", {
21
21
  removeBlock,
22
22
  moveBlock,
@@ -2,7 +2,7 @@
2
2
  import { useRoute } from "#imports";
3
3
  import { computed } from "vue";
4
4
  import { useClipboard } from "@vueuse/core";
5
- import { useToast } from "#imports";
5
+ import { useToast } from "@nuxt/ui/composables";
6
6
  import { tv } from "tailwind-variants";
7
7
  import { slugify } from "../../utils";
8
8
  const { copy } = useClipboard();
@@ -1,24 +0,0 @@
1
- import type { Page } from "../../types/pages.js";
2
- interface Props {
3
- isSaving: boolean;
4
- }
5
- type __VLS_Props = Props;
6
- type __VLS_ModelProps = {
7
- modelValue: Page;
8
- };
9
- type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
- declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
11
- undo: () => void;
12
- redo: () => void;
13
- canUndo: import("vue").ComputedRef<boolean>;
14
- canRedo: import("vue").ComputedRef<boolean>;
15
- }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
- "update:modelValue": (value: Page) => any;
17
- } & {
18
- save: (value: Page) => any;
19
- }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
20
- onSave?: ((value: Page) => any) | undefined;
21
- "onUpdate:modelValue"?: ((value: Page) => any) | undefined;
22
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
- declare const _default: typeof __VLS_export;
24
- export default _default;
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import { ref, computed, useTemplateRef } from "vue";
3
3
  import { usePageEditor } from "../../composables/usePageEditor";
4
- const page = defineModel({ type: Object, ...{ required: true } });
4
+ const page = defineModel({ type: null, ...{ required: true } });
5
5
  const { undo, redo, canUndo, canRedo, captureSnapshot } = usePageEditor(page);
6
6
  const emit = defineEmits(["save"]);
7
7
  const handleSave = () => {
@@ -1,24 +0,0 @@
1
- import type { Page } from "../../types/pages.js";
2
- interface Props {
3
- isSaving: boolean;
4
- }
5
- type __VLS_Props = Props;
6
- type __VLS_ModelProps = {
7
- modelValue: Page;
8
- };
9
- type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
- declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
11
- undo: () => void;
12
- redo: () => void;
13
- canUndo: import("vue").ComputedRef<boolean>;
14
- canRedo: import("vue").ComputedRef<boolean>;
15
- }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
- "update:modelValue": (value: Page) => any;
17
- } & {
18
- save: (value: Page) => any;
19
- }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
20
- onSave?: ((value: Page) => any) | undefined;
21
- "onUpdate:modelValue"?: ((value: Page) => any) | undefined;
22
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
- declare const _default: typeof __VLS_export;
24
- export default _default;
@@ -1,11 +0,0 @@
1
- import { type Page } from '../../types/pages.js';
2
- type __VLS_ModelProps = {
3
- modelValue: Page;
4
- };
5
- declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
- "update:modelValue": (value: Page) => any;
7
- }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
8
- "onUpdate:modelValue"?: ((value: Page) => any) | undefined;
9
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
- declare const _default: typeof __VLS_export;
11
- export default _default;
@@ -1,10 +1,14 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
- import { PAGE_SCHEMAS } from "../../types/pages";
4
- const page = defineModel({ type: Object, ...{ required: true } });
3
+ import { useAppConfig } from "#imports";
4
+ import { useI18n } from "vue-i18n";
5
+ import {} from "../../types/pages";
6
+ const page = defineModel({ type: null, ...{ required: true } });
7
+ const appConfig = useAppConfig();
8
+ const locale = useI18n().locale;
5
9
  const properties = computed(() => page.value.properties);
6
10
  const groups = computed(() => {
7
- return PAGE_SCHEMAS[page.value.type] || [];
11
+ return page.value.properties;
8
12
  });
9
13
  const isFieldVisible = (fieldSchema) => {
10
14
  if (!fieldSchema.visibleIf) return true;
@@ -26,46 +30,46 @@ const getSortedFields = (fields) => {
26
30
  </UBadge>
27
31
  </div>
28
32
 
29
- <div v-for="group in groups" :key="group.id" class="space-y-4">
33
+ <div v-for="(group, groupId) in groups" :key="groupId" class="space-y-4">
30
34
  <div class="flex items-center gap-3">
31
35
  <span class="text-[10px] font-bold uppercase tracking-widest text-primary">
32
- {{ group.label }}
36
+ {{ group.label[locale] }}
33
37
  </span>
34
38
  <div class="h-px flex-1 bg-border/50"></div>
35
39
  </div>
36
40
 
37
41
  <div class="grid gap-y-4 px-1">
38
- <template v-for="[key, schema] in getSortedFields(group.fields)" :key="key">
42
+ <template v-for="[fieldKey, schema] in getSortedFields(group.fields)" :key="fieldKey">
39
43
 
40
44
  <UFormField
41
45
  v-if="isFieldVisible(schema)"
42
46
  :label="schema.label"
43
- :name="key"
47
+ :name="fieldKey"
44
48
  >
45
49
  <UInput
46
50
  v-if="schema.type === 'text'"
47
- v-model="properties[key]"
51
+ v-model="properties[groupId].fields[fieldKey].value[locale]"
48
52
  variant="subtle"
49
53
  placeholder="..."
50
54
  />
51
55
 
52
56
  <UInput
53
57
  v-else-if="schema.type === 'number'"
54
- v-model.number="properties[key]"
58
+ v-model.number="properties[groupId].fields[fieldKey].value"
55
59
  type="number"
56
60
  variant="subtle"
57
61
  />
58
62
 
59
63
  <USelect
60
64
  v-else-if="schema.type === 'enum'"
61
- v-model="properties[key]"
65
+ v-model="properties[groupId].fields[fieldKey].value"
62
66
  :items="schema.options || []"
63
67
  variant="subtle"
64
68
  />
65
69
 
66
70
  <UInputMenu
67
71
  v-else-if="schema.type === 'text-array'"
68
- v-model="properties[key]"
72
+ v-model="properties[groupId].fields[fieldKey].value[locale]"
69
73
  multiple
70
74
  creatable
71
75
  variant="subtle"
@@ -74,7 +78,7 @@ const getSortedFields = (fields) => {
74
78
 
75
79
  <UInput
76
80
  v-else-if="schema.type === 'page'"
77
- v-model="properties[key]"
81
+ v-model="properties[groupId].fields[fieldKey].value"
78
82
  icon="lucide:link-2"
79
83
  variant="subtle"
80
84
  :placeholder="`Select ${schema.allowedPageTypes?.join('/')}`"
@@ -1,11 +0,0 @@
1
- import { type Page } from '../../types/pages.js';
2
- type __VLS_ModelProps = {
3
- modelValue: Page;
4
- };
5
- declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
- "update:modelValue": (value: Page) => any;
7
- }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
8
- "onUpdate:modelValue"?: ((value: Page) => any) | undefined;
9
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
- declare const _default: typeof __VLS_export;
11
- export default _default;
@@ -1,17 +1,19 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
+ import { useAppConfig } from "#imports";
4
+ import { useI18n } from "vue-i18n";
3
5
  import {
4
- PAGE_SCHEMAS
5
6
  } from "../../types/pages";
6
7
  const props = defineProps({
7
- modelValue: { type: Object, required: true }
8
+ modelValue: { type: null, required: true }
8
9
  });
10
+ const appConfig = useAppConfig();
11
+ const locale = useI18n().locale;
9
12
  const properties = computed(() => props.modelValue.properties);
10
13
  const displayGroups = computed(() => {
11
- const allGroups = PAGE_SCHEMAS[props.modelValue.type] || [];
12
- return allGroups.map((group) => {
13
- const visibleFields = Object.entries(group.fields).filter(([key, schema]) => {
14
- const value = properties.value[key];
14
+ return Object.entries(properties.value || {}).map(([groupId, group]) => {
15
+ const visibleFields = Object.entries(group.fields || {}).filter(([fieldKey, schema]) => {
16
+ const value = schema.type === "text" || schema.type === "text-array" ? schema.value?.[locale.value] : schema.value;
15
17
  const isVisible = !schema.visibleIf || schema.visibleIf(props.modelValue.properties);
16
18
  if (!isVisible) return false;
17
19
  if (Array.isArray(value)) return value.length > 0;
@@ -19,6 +21,7 @@ const displayGroups = computed(() => {
19
21
  }).sort(([, a], [, b]) => (a.order ?? 0) - (b.order ?? 0));
20
22
  return {
21
23
  ...group,
24
+ groupId,
22
25
  visibleFields
23
26
  };
24
27
  }).filter((group) => group.visibleFields.length > 0);
@@ -32,19 +35,19 @@ const displayGroups = computed(() => {
32
35
  {{ props.modelValue.type }}
33
36
  </div>
34
37
  <h2 class="text-xl font-serif font-bold italic text-foreground">
35
- {{ properties.name || props.modelValue.slug }}
38
+ {{ properties.name?.value?.[locale] || props.modelValue.slug }}
36
39
  </h2>
37
40
  </div>
38
41
 
39
- <div v-for="group in displayGroups" :key="group.id" class="border-b last:border-b-0 border-border/40">
42
+ <div v-for="group in displayGroups" :key="group.groupId" class="border-b last:border-b-0 border-border/40">
40
43
  <div class="bg-muted/30 px-3 py-1.5 text-[10px] font-black text-primary uppercase tracking-tighter border-b border-border/10">
41
- {{ group.label }}
44
+ {{ group.label[locale] }}
42
45
  </div>
43
46
 
44
47
  <dl class="p-3 space-y-2.5">
45
48
  <div
46
- v-for="[key, schema] in group.visibleFields"
47
- :key="key"
49
+ v-for="[fieldKey, schema] in group.visibleFields"
50
+ :key="fieldKey"
48
51
  class="grid grid-cols-3 gap-x-3 items-baseline"
49
52
  >
50
53
  <dt class="text-[11px] font-semibold text-muted-foreground leading-tight">
@@ -53,18 +56,22 @@ const displayGroups = computed(() => {
53
56
 
54
57
  <dd class="text-xs col-span-2 text-foreground leading-snug">
55
58
  <div v-if="schema.type === 'text-array'" class="flex flex-wrap gap-1">
56
- <template v-for="(item, index) in properties[key]" :key="index">
59
+ <template v-for="(item, index) in properties[group.groupId].fields[fieldKey].value[locale]" :key="index">
57
60
  <span class="font-medium">{{ item }}</span>
58
- <span v-if="index < properties[key].length - 1" class="text-muted-foreground/50">, </span>
61
+ <span v-if="index < properties[group.groupId].fields[fieldKey].value[locale].length - 1" class="text-muted-foreground/50">, </span>
59
62
  </template>
60
63
  </div>
61
64
 
62
65
  <span v-else-if="schema.type === 'page'" class="text-primary font-bold hover:underline cursor-pointer">
63
- {{ properties[key] }}
66
+ {{ properties[group.groupId].fields[fieldKey].value }}
67
+ </span>
68
+
69
+ <span v-else-if="schema.type === 'text'" class="font-medium">
70
+ {{ properties[group.groupId].fields[fieldKey].value[locale] }}
64
71
  </span>
65
72
 
66
73
  <span v-else class="font-medium">
67
- {{ properties[key] }}
74
+ {{ properties[group.groupId].fields[fieldKey].value }}
68
75
  </span>
69
76
  </dd>
70
77
  </div>
@@ -75,16 +82,17 @@ const displayGroups = computed(() => {
75
82
  <div class="flex flex-wrap gap-1.5">
76
83
  <UBadge
77
84
  v-for="tag in props.modelValue.tags"
78
- :key="tag"
85
+ :key="String(tag[locale])"
79
86
  variant="subtle"
80
87
  size="sm"
81
88
  color="neutral"
82
89
  class="text-[9px] font-bold uppercase tracking-tight"
83
90
  >
84
- #{{ tag }}
91
+ #{{ tag[locale] }}
85
92
  </UBadge>
86
93
  </div>
87
94
  </div>
95
+
88
96
  </div>
89
97
  </template>
90
98
 
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
3
  import { useClipboard } from "@vueuse/core";
4
- import { useToast } from "#imports";
4
+ import { useToast } from "@nuxt/ui/composables";
5
5
  const { copy } = useClipboard();
6
6
  const toast = useToast();
7
7
  const { name, hex, rgb, hsl, oklch, cmyk } = defineProps({
@@ -1,6 +1,14 @@
1
1
  declare module '#build/app.config' {
2
2
  import type { AppConfig } from '@nuxt/schema'
3
3
 
4
- const _default: AppConfig
4
+ interface RimelightComponentsConfig {
5
+ [key: string]: any
6
+ }
7
+
8
+ interface CustomAppConfig extends AppConfig {
9
+ rimelightComponents?: RimelightComponentsConfig
10
+ }
11
+
12
+ const _default: CustomAppConfig
5
13
  export default _default
6
- }
14
+ }
@@ -1,4 +1,3 @@
1
1
  export * from "./blocks.js";
2
2
  export * from "./pages.js";
3
- export * from "./pageTemplates.js";
4
3
  export * from "./schemas.js";
@@ -1,4 +1,3 @@
1
1
  export * from "./blocks.js";
2
2
  export * from "./pages.js";
3
- export * from "./pageTemplates.js";
4
3
  export * from "./schemas.js";
@@ -1,7 +1,12 @@
1
1
  import { type Image } from "../types/schemas.js";
2
2
  import { type Block } from "./blocks.js";
3
- export type PageType = "Default" | "Document" | "BlogPost" | "PatchNote" | "Location" | "Species" | "Character" | "Object" | "Tale" | "Item" | "Skill" | "Hero" | "Card" | "Series" | "Episode";
4
- export interface Property {
3
+ declare global {
4
+ interface RimelightRegisterPageTypes {
5
+ }
6
+ }
7
+ export type PageType = keyof RegisterPageTypes;
8
+ export interface Property<T = any> {
9
+ value: T;
5
10
  label: string;
6
11
  type: "number" | "text" | "text-array" | "enum" | "page" | "page-array";
7
12
  options?: string[];
@@ -9,66 +14,29 @@ export interface Property {
9
14
  order?: number;
10
15
  visibleIf?: (properties: any) => boolean;
11
16
  }
17
+ export type Localized<T = string> = Record<string, T>;
12
18
  export interface PropertyGroup {
13
- id: string;
14
- label: string;
19
+ label: Localized<string>;
15
20
  order?: number;
16
21
  fields: Record<string, Property>;
17
22
  }
18
- export interface BasePageProperties {
19
- }
20
- export interface DocumentProperties extends BasePageProperties {
21
- }
22
- export interface BlogPostProperties extends BasePageProperties {
23
- }
24
- export interface PatchNoteProperties extends BasePageProperties {
25
- }
26
- export interface LocationProperties extends BasePageProperties {
27
- }
28
- export interface SpeciesProperties extends BasePageProperties {
29
- }
30
- export interface CharacterProperties extends BasePageProperties {
31
- name: string;
32
- title?: string;
33
- aliases?: string[];
34
- flavourText?: string;
35
- species?: string;
36
- sex?: string;
37
- pronouns?: string;
38
- height?: number;
39
- weight?: number;
40
- dateOfBirth?: string;
41
- dateOfDeath?: string;
42
- placeOfBirth?: string;
43
- placeOfDeath?: string;
44
- formerAffiliations?: string[];
45
- currentAffiliations?: string[];
46
- equipment?: string[];
47
- pets?: string[];
48
- mounts?: string[];
49
- favouriteFood?: string;
50
- }
51
- export interface ObjectProperties extends BasePageProperties {
52
- }
53
- export interface TaleProperties extends BasePageProperties {
54
- }
55
- export interface ItemProperties extends BasePageProperties {
56
- }
57
- export interface SkillProperties extends BasePageProperties {
58
- }
59
- export interface HeroProperties extends BasePageProperties {
60
- }
61
- export interface CardProperties extends BasePageProperties {
62
- name: string;
63
- alignment: string;
64
- type: string;
65
- flavourText: string;
23
+ /**
24
+ * A PageTemplate is the single definition for a page's properties and initial blocks.
25
+ */
26
+ export interface PageDefinition {
27
+ properties: Record<string, PropertyGroup>;
28
+ initialBlocks?: () => Block[];
66
29
  }
67
- export interface SeriesProperties extends BasePageProperties {
30
+ /**
31
+ * Helper to define a page with full type safety and literal preservation.
32
+ * This is used by consuming apps to define their custom page types.
33
+ */
34
+ export declare function definePageDefinition<T extends PageDefinition>(def: T): T;
35
+ export interface BasePageProperties {
68
36
  }
69
- export interface EpisodeProperties extends BasePageProperties {
37
+ export interface RegisterPageTypes extends RimelightRegisterPageTypes {
38
+ Default: BasePageProperties;
70
39
  }
71
- export type Localized<T = string> = Record<string, T>;
72
40
  /**
73
41
  * Common fields shared by every page regardless of type.
74
42
  */
@@ -89,54 +57,9 @@ interface BasePage {
89
57
  * Discriminated Union for Page Data structure
90
58
  */
91
59
  export type Page = {
92
- type: "Default";
93
- properties: BasePageProperties;
94
- } & BasePage | {
95
- type: "Document";
96
- properties: DocumentProperties;
97
- } & BasePage | {
98
- type: "BlogPost";
99
- properties: BlogPostProperties;
100
- } & BasePage | {
101
- type: "PatchNote";
102
- properties: PatchNoteProperties;
103
- } & BasePage | {
104
- type: "Character";
105
- properties: CharacterProperties;
106
- } & BasePage | {
107
- type: "Card";
108
- properties: CardProperties;
109
- } & BasePage | {
110
- type: "Location";
111
- properties: BasePageProperties;
112
- } & BasePage | {
113
- type: "Species";
114
- properties: BasePageProperties;
115
- } & BasePage | {
116
- type: "Object";
117
- properties: BasePageProperties;
118
- } & BasePage | {
119
- type: "Tale";
120
- properties: BasePageProperties;
121
- } & BasePage | {
122
- type: "Item";
123
- properties: BasePageProperties;
124
- } & BasePage | {
125
- type: "Skill";
126
- properties: BasePageProperties;
127
- } & BasePage | {
128
- type: "Hero";
129
- properties: BasePageProperties;
130
- } & BasePage | {
131
- type: "Series";
132
- properties: BasePageProperties;
133
- } & BasePage | {
134
- type: "Episode";
135
- properties: BasePageProperties;
136
- } & BasePage;
137
- /**
138
- * Registry mapping PageTypes to their UI schema.
139
- * This drives the dynamic form generation in the Sidebar.
140
- */
141
- export declare const PAGE_SCHEMAS: Record<PageType, PropertyGroup[]>;
60
+ [K in PageType]: {
61
+ type: K;
62
+ properties: RegisterPageTypes[K];
63
+ } & BasePage;
64
+ }[PageType];
142
65
  export {};
@@ -1,58 +1,3 @@
1
- export const PAGE_SCHEMAS = {
2
- Default: [],
3
- Document: [],
4
- BlogPost: [],
5
- PatchNote: [],
6
- Character: [
7
- {
8
- id: "identity",
9
- label: "Identity",
10
- fields: {
11
- name: { label: "Name", type: "text" },
12
- title: { label: "Social Title", type: "text" },
13
- aliases: { label: "Aliases", type: "text-array" }
14
- }
15
- },
16
- {
17
- id: "characteristics",
18
- label: "Characteristics",
19
- fields: {
20
- species: { label: "Species", type: "page", allowedPageTypes: ["Species"] },
21
- sex: { label: "Sex", type: "enum", options: ["Male", "Female", "Other", "Unknown"] },
22
- height: { label: "Height", type: "number" },
23
- weight: { label: "Weight", type: "number" }
24
- }
25
- },
26
- {
27
- id: "timeline",
28
- label: "Timeline",
29
- fields: {
30
- dateOfBirth: { label: "Date of Birth", type: "text" },
31
- dateOfDeath: { label: "Date of Death", type: "text" },
32
- placeOfDeath: {
33
- label: "Place of Death",
34
- type: "text",
35
- visibleIf: (props) => !!props.dateOfDeath
36
- }
37
- }
38
- },
39
- {
40
- id: "other",
41
- label: "Other",
42
- order: 3,
43
- fields: {
44
- flavourText: { label: "Flavour Text", type: "text" }
45
- }
46
- }
47
- ],
48
- Location: [],
49
- Species: [],
50
- Object: [],
51
- Tale: [],
52
- Item: [],
53
- Skill: [],
54
- Hero: [],
55
- Card: [],
56
- Series: [],
57
- Episode: []
58
- };
1
+ export function definePageDefinition(def) {
2
+ return def;
3
+ }
@@ -1,7 +1,10 @@
1
- import type { Page, Localized } from "../types/pages.js";
1
+ import type { Page, Localized, PageDefinition } from "../types/pages.js";
2
2
  export declare const getLocalizedContent: (field: Localized | undefined, currentLocale: string) => string;
3
3
  /**
4
- * Ensures an existing page has all the blocks currently defined in its template.
5
- * Matches based on SectionBlock titles.
4
+ * Ensures a page strictly adheres to its PageDefinition.
5
+ * - Adds missing properties and groups in the correct order.
6
+ * - Removes properties and groups no longer in the definition.
7
+ * - Adds missing templated blocks in the correct relative position.
8
+ * - Removes templated blocks no longer in the definition.
6
9
  */
7
- export declare function syncTemplateBlocks(page: Page): Page;
10
+ export declare function syncPageWithDefinition(page: Page, definition?: PageDefinition): Page;
@@ -1,20 +1,71 @@
1
- import { PAGE_TEMPLATES } from "../types/pageTemplates.js";
2
1
  export const getLocalizedContent = (field, currentLocale) => {
3
2
  if (!field) return "";
4
3
  return field[currentLocale] || field["en"] || "";
5
4
  };
6
- export function syncTemplateBlocks(page) {
7
- const templateFn = PAGE_TEMPLATES[page.type];
8
- if (!templateFn) return page;
9
- const idealBlocks = templateFn();
10
- const existingTitles = new Set(
11
- page.blocks.filter((b) => b.type === "SectionBlock").map((b) => b.props.title)
12
- );
13
- const missingBlocks = idealBlocks.filter(
14
- (b) => b.type === "SectionBlock" && !existingTitles.has(b.props.title)
15
- );
16
- if (missingBlocks.length > 0) {
17
- page.blocks = [...page.blocks, ...missingBlocks];
5
+ export function syncPageWithDefinition(page, definition) {
6
+ if (!definition) return page;
7
+ let hasChanged = false;
8
+ const updatedProperties = {};
9
+ const definitionGroups = definition.properties;
10
+ for (const [groupId, definitionGroup] of Object.entries(definitionGroups)) {
11
+ const existingGroup = page.properties[groupId];
12
+ const updatedGroupFields = {};
13
+ for (const [fieldId, definitionField] of Object.entries(definitionGroup.fields)) {
14
+ if (existingGroup?.fields[fieldId]) {
15
+ updatedGroupFields[fieldId] = {
16
+ ...definitionField,
17
+ value: existingGroup.fields[fieldId].value
18
+ };
19
+ } else {
20
+ updatedGroupFields[fieldId] = { ...definitionField };
21
+ hasChanged = true;
22
+ }
23
+ }
24
+ updatedProperties[groupId] = {
25
+ ...definitionGroup,
26
+ fields: updatedGroupFields
27
+ };
28
+ if (!existingGroup) {
29
+ hasChanged = true;
30
+ }
31
+ }
32
+ if (Object.keys(page.properties).length !== Object.keys(updatedProperties).length) {
33
+ hasChanged = true;
34
+ } else {
35
+ for (const groupId in updatedProperties) {
36
+ const pageGroup = page.properties[groupId];
37
+ if (Object.keys(pageGroup?.fields || {}).length !== Object.keys(updatedProperties[groupId].fields).length) {
38
+ hasChanged = true;
39
+ break;
40
+ }
41
+ }
42
+ }
43
+ page.properties = updatedProperties;
44
+ if (definition.initialBlocks) {
45
+ const idealBlocks = definition.initialBlocks();
46
+ const currentBlocks = [...page.blocks];
47
+ const idealBlocksMap = new Map(idealBlocks.map((b) => [b.id, b]));
48
+ const filteredCurrent = currentBlocks.filter((b) => {
49
+ if (!b.isTemplated) return true;
50
+ return idealBlocksMap.has(b.id);
51
+ });
52
+ if (filteredCurrent.length !== currentBlocks.length) {
53
+ hasChanged = true;
54
+ }
55
+ let lastExistingIdealIndex = -1;
56
+ for (const idealBlock of idealBlocks) {
57
+ const existingIndex = filteredCurrent.findIndex((b) => b.id === idealBlock.id);
58
+ if (existingIndex !== -1) {
59
+ lastExistingIdealIndex = existingIndex;
60
+ } else {
61
+ filteredCurrent.splice(lastExistingIdealIndex + 1, 0, { ...idealBlock });
62
+ lastExistingIdealIndex++;
63
+ hasChanged = true;
64
+ }
65
+ }
66
+ page.blocks = filteredCurrent;
67
+ }
68
+ if (hasChanged) {
18
69
  page.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
19
70
  }
20
71
  return page;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rimelight-components",
3
3
  "description": "A component library by Rimelight Entertainment.",
4
- "version": "2.0.59",
4
+ "version": "2.0.61",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -78,8 +78,10 @@
78
78
  "typecheck": "vue-tsc --noEmit && nuxt typecheck",
79
79
  "lint": "oxlint .",
80
80
  "lint:fix": "oxlint . --fix",
81
- "format": "prettier . --check .",
82
- "format:fix": "prettier . --write .",
81
+ "format": "oxfmt --check",
82
+ "format:fix": "oxfmt .",
83
+ "check": "bun run typecheck && bun run lint && bun run format",
84
+ "fix": "bun run lint:fix && bun run format:fix",
83
85
  "test": "vitest",
84
86
  "release": "release-it --ci"
85
87
  },
@@ -1,3 +0,0 @@
1
- import { type Block } from "./blocks.js";
2
- import { type PageType } from "./pages.js";
3
- export declare const PAGE_TEMPLATES: Record<PageType, () => Block[]>;
@@ -1,170 +0,0 @@
1
- import { v7 as uuidv7 } from "uuid";
2
- export const PAGE_TEMPLATES = {
3
- Default: () => [],
4
- Character: () => [
5
- {
6
- id: uuidv7(),
7
- type: "SectionBlock",
8
- isTemplated: true,
9
- props: { level: 2, title: "Appearance", children: [] }
10
- },
11
- {
12
- id: uuidv7(),
13
- type: "SectionBlock",
14
- isTemplated: true,
15
- props: { level: 2, title: "Abilities", children: [] }
16
- },
17
- {
18
- id: uuidv7(),
19
- type: "SectionBlock",
20
- isTemplated: true,
21
- props: { level: 2, title: "History", children: [] }
22
- }
23
- ],
24
- Location: () => [
25
- {
26
- id: uuidv7(),
27
- type: "SectionBlock",
28
- isTemplated: true,
29
- props: { level: 2, title: "Geography", children: [] }
30
- },
31
- {
32
- id: uuidv7(),
33
- type: "SectionBlock",
34
- isTemplated: true,
35
- props: { level: 2, title: "Points of Interest", children: [] }
36
- },
37
- {
38
- id: uuidv7(),
39
- type: "SectionBlock",
40
- isTemplated: true,
41
- props: { level: 2, title: "History", children: [] }
42
- }
43
- ],
44
- Species: () => [
45
- {
46
- id: uuidv7(),
47
- type: "SectionBlock",
48
- isTemplated: true,
49
- props: { level: 2, title: "Biology", children: [] }
50
- },
51
- {
52
- id: uuidv7(),
53
- type: "SectionBlock",
54
- isTemplated: true,
55
- props: { level: 2, title: "Culture", children: [] }
56
- }
57
- ],
58
- Object: () => [
59
- {
60
- id: uuidv7(),
61
- type: "SectionBlock",
62
- isTemplated: true,
63
- props: { level: 2, title: "Description", children: [] }
64
- },
65
- {
66
- id: uuidv7(),
67
- type: "SectionBlock",
68
- isTemplated: true,
69
- props: { level: 2, title: "Properties", children: [] }
70
- }
71
- ],
72
- Tale: () => [
73
- {
74
- id: uuidv7(),
75
- type: "SectionBlock",
76
- isTemplated: true,
77
- props: { level: 2, title: "Summary", children: [] }
78
- },
79
- {
80
- id: uuidv7(),
81
- type: "SectionBlock",
82
- isTemplated: true,
83
- props: { level: 2, title: "The Story", children: [] }
84
- }
85
- ],
86
- Item: () => [
87
- {
88
- id: uuidv7(),
89
- type: "SectionBlock",
90
- isTemplated: true,
91
- props: { level: 2, title: "Statistics", children: [] }
92
- },
93
- {
94
- id: uuidv7(),
95
- type: "SectionBlock",
96
- isTemplated: true,
97
- props: { level: 2, title: "Lore", children: [] }
98
- }
99
- ],
100
- Skill: () => [
101
- {
102
- id: uuidv7(),
103
- type: "SectionBlock",
104
- isTemplated: true,
105
- props: { level: 2, title: "Effects", children: [] }
106
- },
107
- {
108
- id: uuidv7(),
109
- type: "SectionBlock",
110
- isTemplated: true,
111
- props: { level: 2, title: "Acquisition", children: [] }
112
- }
113
- ],
114
- Hero: () => [
115
- {
116
- id: uuidv7(),
117
- type: "SectionBlock",
118
- isTemplated: true,
119
- props: { level: 2, title: "Background", children: [] }
120
- },
121
- {
122
- id: uuidv7(),
123
- type: "SectionBlock",
124
- isTemplated: true,
125
- props: { level: 2, title: "Legacy", children: [] }
126
- }
127
- ],
128
- Card: () => [
129
- {
130
- id: uuidv7(),
131
- type: "SectionBlock",
132
- isTemplated: true,
133
- props: { level: 2, title: "Card Art", children: [] }
134
- },
135
- {
136
- id: uuidv7(),
137
- type: "SectionBlock",
138
- isTemplated: true,
139
- props: { level: 2, title: "Game Mechanics", children: [] }
140
- }
141
- ],
142
- Series: () => [
143
- {
144
- id: uuidv7(),
145
- type: "SectionBlock",
146
- isTemplated: true,
147
- props: { level: 2, title: "Synopsis", children: [] }
148
- },
149
- {
150
- id: uuidv7(),
151
- type: "SectionBlock",
152
- isTemplated: true,
153
- props: { level: 2, title: "List of Episodes", children: [] }
154
- }
155
- ],
156
- Episode: () => [
157
- {
158
- id: uuidv7(),
159
- type: "SectionBlock",
160
- isTemplated: true,
161
- props: { level: 2, title: "Plot Summary", children: [] }
162
- },
163
- {
164
- id: uuidv7(),
165
- type: "SectionBlock",
166
- isTemplated: true,
167
- props: { level: 2, title: "Characters Appearing", children: [] }
168
- }
169
- ]
170
- };