rimelight-components 2.0.49 → 2.0.50
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 +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/blocks/BlockEditor.d.vue.ts +6 -2
- package/dist/runtime/components/blocks/BlockEditor.vue +4 -64
- package/dist/runtime/components/blocks/BlockEditor.vue.d.ts +6 -2
- package/dist/runtime/components/page/PageEditor.d.vue.ts +24 -0
- package/dist/runtime/components/page/PageEditor.vue +102 -0
- package/dist/runtime/components/page/PageEditor.vue.d.ts +24 -0
- package/dist/runtime/components/page/PagePropertiesEditor.d.vue.ts +11 -0
- package/dist/runtime/components/page/PagePropertiesEditor.vue +111 -0
- package/dist/runtime/components/page/PagePropertiesEditor.vue.d.ts +11 -0
- package/dist/runtime/components/page/PagePropertiesRenderer.d.vue.ts +7 -0
- package/dist/runtime/components/page/PagePropertiesRenderer.vue +93 -0
- package/dist/runtime/components/page/PagePropertiesRenderer.vue.d.ts +7 -0
- package/dist/runtime/composables/useBlockEditor.d.ts +3262 -1
- package/dist/runtime/composables/useBlockEditor.js +6 -2
- package/dist/runtime/composables/usePageEditor.d.ts +10 -0
- package/dist/runtime/composables/usePageEditor.js +41 -0
- package/dist/runtime/types/blocks.d.ts +1 -0
- package/dist/runtime/types/pageTemplates.d.ts +3 -0
- package/dist/runtime/types/pageTemplates.js +170 -0
- package/dist/runtime/types/pages.d.ts +120 -0
- package/dist/runtime/types/pages.js +55 -0
- package/dist/runtime/utils/page.d.ts +7 -0
- package/dist/runtime/utils/page.js +33 -0
- package/package.json +1 -1
|
@@ -20,7 +20,7 @@ function regenerateIds(block) {
|
|
|
20
20
|
block.props.children.forEach((child) => regenerateIds(child));
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
export function useBlockEditor(initialBlocks, maxHistorySize = 100) {
|
|
23
|
+
export function useBlockEditor(initialBlocks, { maxHistorySize = 100, onMutation } = {}) {
|
|
24
24
|
const history = shallowRef([]);
|
|
25
25
|
const future = shallowRef([]);
|
|
26
26
|
const committedBlocks = ref(JSON.parse(JSON.stringify(initialBlocks.value)));
|
|
@@ -34,7 +34,11 @@ export function useBlockEditor(initialBlocks, maxHistorySize = 100) {
|
|
|
34
34
|
history.value = newHistory;
|
|
35
35
|
};
|
|
36
36
|
const executeMutation = (mutationFn) => {
|
|
37
|
-
|
|
37
|
+
if (onMutation) {
|
|
38
|
+
onMutation();
|
|
39
|
+
} else {
|
|
40
|
+
captureSnapshot();
|
|
41
|
+
}
|
|
38
42
|
mutationFn();
|
|
39
43
|
};
|
|
40
44
|
const undo = () => {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Ref } from "vue";
|
|
2
|
+
import type { Page } from "../types/pages.js";
|
|
3
|
+
export declare function usePageEditor(page: Ref<Page>, maxHistorySize?: number): {
|
|
4
|
+
undo: () => void;
|
|
5
|
+
redo: () => void;
|
|
6
|
+
canUndo: import("vue").ComputedRef<boolean>;
|
|
7
|
+
canRedo: import("vue").ComputedRef<boolean>;
|
|
8
|
+
save: () => any;
|
|
9
|
+
captureSnapshot: () => void;
|
|
10
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { computed, shallowRef, watch } from "vue";
|
|
2
|
+
export function usePageEditor(page, maxHistorySize = 100) {
|
|
3
|
+
const history = shallowRef([]);
|
|
4
|
+
const future = shallowRef([]);
|
|
5
|
+
const captureSnapshot = () => {
|
|
6
|
+
const snapshot = JSON.stringify(page.value);
|
|
7
|
+
if (history.value.length > 0 && history.value[history.value.length - 1] === snapshot) return;
|
|
8
|
+
future.value = [];
|
|
9
|
+
const newHistory = [...history.value, snapshot];
|
|
10
|
+
if (newHistory.length > maxHistorySize) newHistory.shift();
|
|
11
|
+
history.value = newHistory;
|
|
12
|
+
};
|
|
13
|
+
const undo = () => {
|
|
14
|
+
if (history.value.length === 0) return;
|
|
15
|
+
future.value = [JSON.stringify(page.value), ...future.value];
|
|
16
|
+
const previous = JSON.parse(history.value.pop());
|
|
17
|
+
page.value = previous;
|
|
18
|
+
};
|
|
19
|
+
const redo = () => {
|
|
20
|
+
if (future.value.length === 0) return;
|
|
21
|
+
history.value = [...history.value, JSON.stringify(page.value)];
|
|
22
|
+
const next = JSON.parse(future.value.shift());
|
|
23
|
+
page.value = next;
|
|
24
|
+
};
|
|
25
|
+
const canUndo = computed(() => history.value.length > 0);
|
|
26
|
+
const canRedo = computed(() => future.value.length > 0);
|
|
27
|
+
const save = () => {
|
|
28
|
+
return JSON.parse(JSON.stringify(page.value));
|
|
29
|
+
};
|
|
30
|
+
watch(() => page.value.properties, () => {
|
|
31
|
+
captureSnapshot();
|
|
32
|
+
}, { deep: true });
|
|
33
|
+
return {
|
|
34
|
+
undo,
|
|
35
|
+
redo,
|
|
36
|
+
canUndo,
|
|
37
|
+
canRedo,
|
|
38
|
+
save,
|
|
39
|
+
captureSnapshot
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { type Block } from "./blocks.js";
|
|
2
|
+
export type PageType = "Default" | "Location" | "Species" | "Character" | "Object" | "Tale" | "Item" | "Skill" | "Hero" | "Card" | "Series" | "Episode";
|
|
3
|
+
export interface Property {
|
|
4
|
+
label: string;
|
|
5
|
+
type: "number" | "text" | "text-array" | "enum" | "page" | "page-array";
|
|
6
|
+
options?: string[];
|
|
7
|
+
allowedPageTypes?: PageType[];
|
|
8
|
+
order?: number;
|
|
9
|
+
visibleIf?: (properties: any) => boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface PropertyGroup {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
order?: number;
|
|
15
|
+
fields: Record<string, Property>;
|
|
16
|
+
}
|
|
17
|
+
export interface BasePageProperties {
|
|
18
|
+
}
|
|
19
|
+
export interface LocationProperties extends BasePageProperties {
|
|
20
|
+
}
|
|
21
|
+
export interface SpeciesProperties extends BasePageProperties {
|
|
22
|
+
}
|
|
23
|
+
export interface CharacterProperties extends BasePageProperties {
|
|
24
|
+
name: string;
|
|
25
|
+
title?: string;
|
|
26
|
+
aliases?: string[];
|
|
27
|
+
flavourText?: string;
|
|
28
|
+
species?: string;
|
|
29
|
+
sex?: string;
|
|
30
|
+
pronouns?: string;
|
|
31
|
+
height?: number;
|
|
32
|
+
weight?: number;
|
|
33
|
+
dateOfBirth?: string;
|
|
34
|
+
dateOfDeath?: string;
|
|
35
|
+
placeOfBirth?: string;
|
|
36
|
+
placeOfDeath?: string;
|
|
37
|
+
formerAffiliations?: string[];
|
|
38
|
+
currentAffiliations?: string[];
|
|
39
|
+
equipment?: string[];
|
|
40
|
+
pets?: string[];
|
|
41
|
+
mounts?: string[];
|
|
42
|
+
favouriteFood?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface ObjectProperties extends BasePageProperties {
|
|
45
|
+
}
|
|
46
|
+
export interface TaleProperties extends BasePageProperties {
|
|
47
|
+
}
|
|
48
|
+
export interface ItemProperties extends BasePageProperties {
|
|
49
|
+
}
|
|
50
|
+
export interface SkillProperties extends BasePageProperties {
|
|
51
|
+
}
|
|
52
|
+
export interface HeroProperties extends BasePageProperties {
|
|
53
|
+
}
|
|
54
|
+
export interface CardProperties extends BasePageProperties {
|
|
55
|
+
name: string;
|
|
56
|
+
alignment: string;
|
|
57
|
+
type: string;
|
|
58
|
+
flavourText: string;
|
|
59
|
+
}
|
|
60
|
+
export interface SeriesProperties extends BasePageProperties {
|
|
61
|
+
}
|
|
62
|
+
export interface EpisodeProperties extends BasePageProperties {
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Common fields shared by every page regardless of type.
|
|
66
|
+
*/
|
|
67
|
+
interface BasePage {
|
|
68
|
+
id: string;
|
|
69
|
+
slug: string;
|
|
70
|
+
tags: string[];
|
|
71
|
+
blocks: Block[];
|
|
72
|
+
createdAt: string;
|
|
73
|
+
updatedAt: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Discriminated Union for Page Data structure
|
|
77
|
+
*/
|
|
78
|
+
export type Page = {
|
|
79
|
+
type: "Default";
|
|
80
|
+
properties: BasePageProperties;
|
|
81
|
+
} & BasePage | {
|
|
82
|
+
type: "Character";
|
|
83
|
+
properties: CharacterProperties;
|
|
84
|
+
} & BasePage | {
|
|
85
|
+
type: "Card";
|
|
86
|
+
properties: CardProperties;
|
|
87
|
+
} & BasePage | {
|
|
88
|
+
type: "Location";
|
|
89
|
+
properties: BasePageProperties;
|
|
90
|
+
} & BasePage | {
|
|
91
|
+
type: "Species";
|
|
92
|
+
properties: BasePageProperties;
|
|
93
|
+
} & BasePage | {
|
|
94
|
+
type: "Object";
|
|
95
|
+
properties: BasePageProperties;
|
|
96
|
+
} & BasePage | {
|
|
97
|
+
type: "Tale";
|
|
98
|
+
properties: BasePageProperties;
|
|
99
|
+
} & BasePage | {
|
|
100
|
+
type: "Item";
|
|
101
|
+
properties: BasePageProperties;
|
|
102
|
+
} & BasePage | {
|
|
103
|
+
type: "Skill";
|
|
104
|
+
properties: BasePageProperties;
|
|
105
|
+
} & BasePage | {
|
|
106
|
+
type: "Hero";
|
|
107
|
+
properties: BasePageProperties;
|
|
108
|
+
} & BasePage | {
|
|
109
|
+
type: "Series";
|
|
110
|
+
properties: BasePageProperties;
|
|
111
|
+
} & BasePage | {
|
|
112
|
+
type: "Episode";
|
|
113
|
+
properties: BasePageProperties;
|
|
114
|
+
} & BasePage;
|
|
115
|
+
/**
|
|
116
|
+
* Registry mapping PageTypes to their UI schema.
|
|
117
|
+
* This drives the dynamic form generation in the Sidebar.
|
|
118
|
+
*/
|
|
119
|
+
export declare const PAGE_SCHEMAS: Record<PageType, PropertyGroup[]>;
|
|
120
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const PAGE_SCHEMAS = {
|
|
2
|
+
Default: [],
|
|
3
|
+
Character: [
|
|
4
|
+
{
|
|
5
|
+
id: "identity",
|
|
6
|
+
label: "Identity",
|
|
7
|
+
fields: {
|
|
8
|
+
name: { label: "Name", type: "text" },
|
|
9
|
+
title: { label: "Social Title", type: "text" },
|
|
10
|
+
aliases: { label: "Aliases", type: "text-array" }
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: "characteristics",
|
|
15
|
+
label: "Characteristics",
|
|
16
|
+
fields: {
|
|
17
|
+
species: { label: "Species", type: "page", allowedPageTypes: ["Species"] },
|
|
18
|
+
sex: { label: "Sex", type: "enum", options: ["Male", "Female", "Other", "Unknown"] },
|
|
19
|
+
height: { label: "Height", type: "number" },
|
|
20
|
+
weight: { label: "Weight", type: "number" }
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "timeline",
|
|
25
|
+
label: "Timeline",
|
|
26
|
+
fields: {
|
|
27
|
+
dateOfBirth: { label: "Date of Birth", type: "text" },
|
|
28
|
+
dateOfDeath: { label: "Date of Death", type: "text" },
|
|
29
|
+
placeOfDeath: {
|
|
30
|
+
label: "Place of Death",
|
|
31
|
+
type: "text",
|
|
32
|
+
visibleIf: (props) => !!props.dateOfDeath
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "other",
|
|
38
|
+
label: "Other",
|
|
39
|
+
order: 3,
|
|
40
|
+
fields: {
|
|
41
|
+
flavourText: { label: "Flavour Text", type: "text" }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
Location: [],
|
|
46
|
+
Species: [],
|
|
47
|
+
Object: [],
|
|
48
|
+
Tale: [],
|
|
49
|
+
Item: [],
|
|
50
|
+
Skill: [],
|
|
51
|
+
Hero: [],
|
|
52
|
+
Card: [],
|
|
53
|
+
Series: [],
|
|
54
|
+
Episode: []
|
|
55
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Page, PageType } from "../types/pages.js";
|
|
2
|
+
export declare function createPage(type: PageType, slug?: string): Page;
|
|
3
|
+
/**
|
|
4
|
+
* Ensures an existing page has all the blocks currently defined in its template.
|
|
5
|
+
* Matches based on SectionBlock titles.
|
|
6
|
+
*/
|
|
7
|
+
export declare function syncTemplateBlocks(page: Page): Page;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { PAGE_TEMPLATES } from "../types/pageTemplates.js";
|
|
2
|
+
import { v7 as uuidv7 } from "uuid";
|
|
3
|
+
export function createPage(type, slug = "new-page") {
|
|
4
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5
|
+
const templateFn = PAGE_TEMPLATES[type];
|
|
6
|
+
const blocks = templateFn ? templateFn() : [];
|
|
7
|
+
return {
|
|
8
|
+
id: uuidv7(),
|
|
9
|
+
slug,
|
|
10
|
+
type,
|
|
11
|
+
tags: [],
|
|
12
|
+
properties: {},
|
|
13
|
+
blocks,
|
|
14
|
+
createdAt: now,
|
|
15
|
+
updatedAt: now
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function syncTemplateBlocks(page) {
|
|
19
|
+
const templateFn = PAGE_TEMPLATES[page.type];
|
|
20
|
+
if (!templateFn) return page;
|
|
21
|
+
const idealBlocks = templateFn();
|
|
22
|
+
const existingTitles = new Set(
|
|
23
|
+
page.blocks.filter((b) => b.type === "SectionBlock").map((b) => b.props.title)
|
|
24
|
+
);
|
|
25
|
+
const missingBlocks = idealBlocks.filter(
|
|
26
|
+
(b) => b.type === "SectionBlock" && !existingTitles.has(b.props.title)
|
|
27
|
+
);
|
|
28
|
+
if (missingBlocks.length > 0) {
|
|
29
|
+
page.blocks = [...page.blocks, ...missingBlocks];
|
|
30
|
+
page.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
31
|
+
}
|
|
32
|
+
return page;
|
|
33
|
+
}
|