@saena-io/create 0.1.0 → 0.2.0
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/index.js +9 -9
- package/package.json +1 -1
- package/template/base/package.json +44 -2
- package/template/base/scripts/ui-update.ts +83 -0
- package/template/base/src/components/ui/accordion.tsx +75 -0
- package/template/base/src/components/ui/alert-dialog.tsx +162 -0
- package/template/base/src/components/ui/alert.tsx +73 -0
- package/template/base/src/components/ui/app-sidebar.tsx +183 -0
- package/template/base/src/components/ui/aspect-ratio.tsx +22 -0
- package/template/base/src/components/ui/asset-input.tsx +211 -0
- package/template/base/src/components/ui/avatar.tsx +91 -0
- package/template/base/src/components/ui/badge.tsx +50 -0
- package/template/base/src/components/ui/breadcrumb.tsx +104 -0
- package/template/base/src/components/ui/button-group.tsx +78 -0
- package/template/base/src/components/ui/button.tsx +56 -0
- package/template/base/src/components/ui/calendar.tsx +205 -0
- package/template/base/src/components/ui/card.tsx +85 -0
- package/template/base/src/components/ui/carousel.tsx +232 -0
- package/template/base/src/components/ui/chart.tsx +337 -0
- package/template/base/src/components/ui/checkbox.tsx +29 -0
- package/template/base/src/components/ui/collapsible.tsx +15 -0
- package/template/base/src/components/ui/combobox.tsx +276 -0
- package/template/base/src/components/ui/command.tsx +190 -0
- package/template/base/src/components/ui/context-menu.tsx +243 -0
- package/template/base/src/components/ui/dialog.tsx +134 -0
- package/template/base/src/components/ui/direction.tsx +4 -0
- package/template/base/src/components/ui/drawer.tsx +120 -0
- package/template/base/src/components/ui/dropdown-menu.tsx +254 -0
- package/template/base/src/components/ui/empty.tsx +94 -0
- package/template/base/src/components/ui/field.tsx +222 -0
- package/template/base/src/components/ui/focal-point-picker.tsx +175 -0
- package/template/base/src/components/ui/hover-card.tsx +46 -0
- package/template/base/src/components/ui/input-group.tsx +149 -0
- package/template/base/src/components/ui/input-otp.tsx +85 -0
- package/template/base/src/components/ui/input.tsx +20 -0
- package/template/base/src/components/ui/item.tsx +188 -0
- package/template/base/src/components/ui/kbd.tsx +26 -0
- package/template/base/src/components/ui/label.tsx +20 -0
- package/template/base/src/components/ui/menubar.tsx +268 -0
- package/template/base/src/components/ui/native-select.tsx +58 -0
- package/template/base/src/components/ui/nav-main.tsx +70 -0
- package/template/base/src/components/ui/nav-projects.tsx +97 -0
- package/template/base/src/components/ui/nav-secondary.tsx +37 -0
- package/template/base/src/components/ui/nav-user.tsx +108 -0
- package/template/base/src/components/ui/navigation-menu.tsx +164 -0
- package/template/base/src/components/ui/pagination.tsx +123 -0
- package/template/base/src/components/ui/popover.tsx +80 -0
- package/template/base/src/components/ui/progress.tsx +66 -0
- package/template/base/src/components/ui/radio-group.tsx +36 -0
- package/template/base/src/components/ui/resizable.tsx +42 -0
- package/template/base/src/components/ui/rich-text/ai-chat-editor.tsx +20 -0
- package/template/base/src/components/ui/rich-text/ai-command.tsx +90 -0
- package/template/base/src/components/ui/rich-text/ai-copilot.tsx +67 -0
- package/template/base/src/components/ui/rich-text/ai-menu.tsx +456 -0
- package/template/base/src/components/ui/rich-text/ai-node.tsx +42 -0
- package/template/base/src/components/ui/rich-text/ai-toolbar-button.tsx +29 -0
- package/template/base/src/components/ui/rich-text/block-draggable.tsx +187 -0
- package/template/base/src/components/ui/rich-text/block-selection.tsx +17 -0
- package/template/base/src/components/ui/rich-text/code-block-node.tsx +204 -0
- package/template/base/src/components/ui/rich-text/codec.ts +63 -0
- package/template/base/src/components/ui/rich-text/extension.ts +53 -0
- package/template/base/src/components/ui/rich-text/ghost-text.tsx +23 -0
- package/template/base/src/components/ui/rich-text/import-export-toolbar.tsx +103 -0
- package/template/base/src/components/ui/rich-text/link.tsx +18 -0
- package/template/base/src/components/ui/rich-text/list-node.tsx +65 -0
- package/template/base/src/components/ui/rich-text/nodes.tsx +44 -0
- package/template/base/src/components/ui/rich-text/plugins.ts +233 -0
- package/template/base/src/components/ui/rich-text/rich-text-editor.tsx +82 -0
- package/template/base/src/components/ui/rich-text/static.tsx +117 -0
- package/template/base/src/components/ui/rich-text/table-node.tsx +934 -0
- package/template/base/src/components/ui/rich-text/table-toolbar.tsx +232 -0
- package/template/base/src/components/ui/rich-text/toggle-node.tsx +36 -0
- package/template/base/src/components/ui/rich-text/toolbar-slots.ts +41 -0
- package/template/base/src/components/ui/rich-text/toolbar.tsx +668 -0
- package/template/base/src/components/ui/rich-text/use-ai-chat.ts +35 -0
- package/template/base/src/components/ui/rich-text/variable-type.ts +4 -0
- package/template/base/src/components/ui/rich-text/variable.tsx +97 -0
- package/template/base/src/components/ui/scroll-area.tsx +49 -0
- package/template/base/src/components/ui/select.tsx +202 -0
- package/template/base/src/components/ui/separator.tsx +19 -0
- package/template/base/src/components/ui/sheet.tsx +126 -0
- package/template/base/src/components/ui/sidebar.tsx +695 -0
- package/template/base/src/components/ui/skeleton.tsx +13 -0
- package/template/base/src/components/ui/slider.tsx +52 -0
- package/template/base/src/components/ui/sonner.tsx +50 -0
- package/template/base/src/components/ui/spinner.tsx +18 -0
- package/template/base/src/components/ui/switch.tsx +30 -0
- package/template/base/src/components/ui/table.tsx +89 -0
- package/template/base/src/components/ui/tabs.tsx +73 -0
- package/template/base/src/components/ui/textarea.tsx +18 -0
- package/template/base/src/components/ui/toggle-group.tsx +85 -0
- package/template/base/src/components/ui/toggle.tsx +45 -0
- package/template/base/src/components/ui/toolbar.tsx +451 -0
- package/template/base/src/components/ui/tooltip.tsx +52 -0
- package/template/base/src/hooks/use-mobile.ts +19 -0
- package/template/base/src/lib/utils.ts +6 -0
- package/template/base/src/routes/__root.tsx +1 -1
- package/template/base/src/server/auth.ts +2 -2
- package/template/base/src/styles/globals.css +230 -0
- package/template/base/vite.config.ts +15 -1
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrowDown01Icon,
|
|
3
|
+
ArrowLeft01Icon,
|
|
4
|
+
ArrowRight01Icon,
|
|
5
|
+
ArrowUp01Icon,
|
|
6
|
+
CombineIcon,
|
|
7
|
+
Delete02Icon,
|
|
8
|
+
GridTableIcon,
|
|
9
|
+
LayoutTopIcon,
|
|
10
|
+
MultiplicationSignIcon,
|
|
11
|
+
SplitIcon,
|
|
12
|
+
} from '@hugeicons/core-free-icons';
|
|
13
|
+
import { HugeiconsIcon } from '@hugeicons/react';
|
|
14
|
+
import {
|
|
15
|
+
deleteColumn,
|
|
16
|
+
deleteRow,
|
|
17
|
+
deleteTable,
|
|
18
|
+
insertTable,
|
|
19
|
+
insertTableColumn,
|
|
20
|
+
insertTableRow,
|
|
21
|
+
mergeTableCells,
|
|
22
|
+
splitTableCell,
|
|
23
|
+
} from '@platejs/table';
|
|
24
|
+
import {
|
|
25
|
+
DropdownMenuSub,
|
|
26
|
+
DropdownMenuSubContent,
|
|
27
|
+
DropdownMenuSubTrigger,
|
|
28
|
+
} from '@saena-io/ui/components/dropdown-menu';
|
|
29
|
+
import type { ToolbarNode } from '@saena-io/ui/components/toolbar';
|
|
30
|
+
import { cn } from '@saena-io/ui/lib/utils';
|
|
31
|
+
import { useState } from 'react';
|
|
32
|
+
import type { RichTextEditor } from './plugins';
|
|
33
|
+
|
|
34
|
+
// The Table control as a declarative toolbar menu node (see `tableMenuNode`): an Insert-table grid picker
|
|
35
|
+
// plus Cell / Row / Column submenus and Delete table. Cell/Row/Column/Delete are disabled unless the caret
|
|
36
|
+
// is inside a table. The 8×8 grid picker is custom menu content (not a menu item), so it closes the
|
|
37
|
+
// dropdown via useToolbarMenuClose() after inserting. Building a similar tool = build the same node shape.
|
|
38
|
+
|
|
39
|
+
/** Flip the table's first row between header cells (th) and body cells (td) — Plate has no built-in toggle. */
|
|
40
|
+
function toggleHeaderRow(editor: RichTextEditor) {
|
|
41
|
+
const tableEntry = editor.api.node({ match: { type: 'table' } });
|
|
42
|
+
if (!tableEntry) return;
|
|
43
|
+
const [tableNode, tablePath] = tableEntry;
|
|
44
|
+
const firstRow = (tableNode as { children?: { children?: { type?: string }[] }[] }).children?.[0];
|
|
45
|
+
const firstCellType = firstRow?.children?.[0]?.type;
|
|
46
|
+
const nextType = firstCellType === 'th' ? 'td' : 'th';
|
|
47
|
+
editor.tf.setNodes(
|
|
48
|
+
{ type: nextType },
|
|
49
|
+
{
|
|
50
|
+
at: [...tablePath, 0],
|
|
51
|
+
match: (n: { type?: unknown }) => n.type === 'td' || n.type === 'th',
|
|
52
|
+
mode: 'lowest',
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The 8×8 grid-size picker; hovering sizes the selection, clicking inserts that table. Focusing the editor
|
|
58
|
+
* afterwards dismisses the dropdown (focus leaves the menu), so no explicit close is needed. */
|
|
59
|
+
function TableGridPicker({ editor }: { editor: RichTextEditor | null }) {
|
|
60
|
+
const [size, setSize] = useState({ rows: 0, cols: 0 });
|
|
61
|
+
const insertSize = (rows: number, cols: number) => {
|
|
62
|
+
if (!editor) return;
|
|
63
|
+
// Ensure an insertion point if the editor was never focused.
|
|
64
|
+
if (!editor.selection) {
|
|
65
|
+
const end = editor.api.end([]);
|
|
66
|
+
if (end) editor.tf.select(end);
|
|
67
|
+
}
|
|
68
|
+
insertTable(editor, { rowCount: rows, colCount: cols }, { select: true });
|
|
69
|
+
editor.tf.focus();
|
|
70
|
+
};
|
|
71
|
+
return (
|
|
72
|
+
<div className="flex flex-col gap-1.5" onMouseDown={(e) => e.preventDefault()}>
|
|
73
|
+
<div className="grid grid-cols-8 gap-0.5">
|
|
74
|
+
{Array.from({ length: 64 }, (_, i) => {
|
|
75
|
+
const r = Math.floor(i / 8);
|
|
76
|
+
const c = i % 8;
|
|
77
|
+
const on = r < size.rows && c < size.cols;
|
|
78
|
+
return (
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
key={`cell-${r}-${c}`}
|
|
82
|
+
aria-label={`${r + 1} by ${c + 1}`}
|
|
83
|
+
onMouseMove={() => setSize({ rows: r + 1, cols: c + 1 })}
|
|
84
|
+
onClick={() => insertSize(r + 1, c + 1)}
|
|
85
|
+
className={cn(
|
|
86
|
+
'size-4 rounded-[2px] border',
|
|
87
|
+
on ? 'border-primary bg-primary/20' : 'border-border bg-muted/50',
|
|
88
|
+
)}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
})}
|
|
92
|
+
</div>
|
|
93
|
+
<div className="text-center text-muted-foreground text-xs">
|
|
94
|
+
{size.rows} × {size.cols}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The "Insert table" entry — a submenu holding the grid picker (custom content, not a menu item). */
|
|
101
|
+
function TableGridSubmenu({ editor }: { editor: RichTextEditor | null }) {
|
|
102
|
+
return (
|
|
103
|
+
<DropdownMenuSub>
|
|
104
|
+
<DropdownMenuSubTrigger>
|
|
105
|
+
<HugeiconsIcon icon={GridTableIcon} strokeWidth={2} className="size-4" />
|
|
106
|
+
Insert table
|
|
107
|
+
</DropdownMenuSubTrigger>
|
|
108
|
+
<DropdownMenuSubContent className="w-auto p-2">
|
|
109
|
+
<TableGridPicker editor={editor} />
|
|
110
|
+
</DropdownMenuSubContent>
|
|
111
|
+
</DropdownMenuSub>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Builds the Table menu node for the toolbar config. */
|
|
116
|
+
export function tableMenuNode(editor: RichTextEditor | null): ToolbarNode {
|
|
117
|
+
// "Caret inside a table" — scope to the selection's ancestors (not the whole doc).
|
|
118
|
+
const inTable = Boolean(editor?.selection && editor.api.above({ match: { type: 'table' } }));
|
|
119
|
+
const act = (fn: (e: RichTextEditor) => void) => () => {
|
|
120
|
+
if (!editor) return;
|
|
121
|
+
fn(editor);
|
|
122
|
+
editor.tf.focus();
|
|
123
|
+
};
|
|
124
|
+
return {
|
|
125
|
+
kind: 'menu',
|
|
126
|
+
id: 'table',
|
|
127
|
+
label: 'Table',
|
|
128
|
+
icon: GridTableIcon,
|
|
129
|
+
variant: 'plain',
|
|
130
|
+
items: [
|
|
131
|
+
{ kind: 'custom', id: 'table-grid', collapsed: <TableGridSubmenu editor={editor} /> },
|
|
132
|
+
{
|
|
133
|
+
kind: 'menu',
|
|
134
|
+
id: 'table-cell',
|
|
135
|
+
label: 'Cell',
|
|
136
|
+
disabled: !inTable,
|
|
137
|
+
items: [
|
|
138
|
+
{
|
|
139
|
+
kind: 'button',
|
|
140
|
+
id: 'merge',
|
|
141
|
+
label: 'Merge cells',
|
|
142
|
+
icon: CombineIcon,
|
|
143
|
+
onSelect: act((e) => mergeTableCells(e)),
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
kind: 'button',
|
|
147
|
+
id: 'split',
|
|
148
|
+
label: 'Split cell',
|
|
149
|
+
icon: SplitIcon,
|
|
150
|
+
onSelect: act((e) => splitTableCell(e)),
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
kind: 'menu',
|
|
156
|
+
id: 'table-row',
|
|
157
|
+
label: 'Row',
|
|
158
|
+
disabled: !inTable,
|
|
159
|
+
items: [
|
|
160
|
+
{
|
|
161
|
+
kind: 'button',
|
|
162
|
+
id: 'row-before',
|
|
163
|
+
label: 'Insert row before',
|
|
164
|
+
icon: ArrowUp01Icon,
|
|
165
|
+
onSelect: act((e) => insertTableRow(e, { before: true })),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
kind: 'button',
|
|
169
|
+
id: 'row-after',
|
|
170
|
+
label: 'Insert row after',
|
|
171
|
+
icon: ArrowDown01Icon,
|
|
172
|
+
onSelect: act((e) => insertTableRow(e)),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
kind: 'button',
|
|
176
|
+
id: 'row-delete',
|
|
177
|
+
label: 'Delete row',
|
|
178
|
+
icon: MultiplicationSignIcon,
|
|
179
|
+
onSelect: act((e) => deleteRow(e)),
|
|
180
|
+
},
|
|
181
|
+
{ kind: 'separator', id: 'row-sep' },
|
|
182
|
+
{
|
|
183
|
+
kind: 'button',
|
|
184
|
+
id: 'row-header',
|
|
185
|
+
label: 'Toggle header row',
|
|
186
|
+
icon: LayoutTopIcon,
|
|
187
|
+
onSelect: act(toggleHeaderRow),
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
kind: 'menu',
|
|
193
|
+
id: 'table-column',
|
|
194
|
+
label: 'Column',
|
|
195
|
+
disabled: !inTable,
|
|
196
|
+
items: [
|
|
197
|
+
{
|
|
198
|
+
kind: 'button',
|
|
199
|
+
id: 'col-before',
|
|
200
|
+
label: 'Insert column before',
|
|
201
|
+
icon: ArrowLeft01Icon,
|
|
202
|
+
onSelect: act((e) => insertTableColumn(e, { before: true })),
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
kind: 'button',
|
|
206
|
+
id: 'col-after',
|
|
207
|
+
label: 'Insert column after',
|
|
208
|
+
icon: ArrowRight01Icon,
|
|
209
|
+
onSelect: act((e) => insertTableColumn(e)),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
kind: 'button',
|
|
213
|
+
id: 'col-delete',
|
|
214
|
+
label: 'Delete column',
|
|
215
|
+
icon: MultiplicationSignIcon,
|
|
216
|
+
onSelect: act((e) => deleteColumn(e)),
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
{ kind: 'separator', id: 'table-sep' },
|
|
221
|
+
{
|
|
222
|
+
kind: 'button',
|
|
223
|
+
id: 'delete-table',
|
|
224
|
+
label: 'Delete table',
|
|
225
|
+
icon: Delete02Icon,
|
|
226
|
+
destructive: true,
|
|
227
|
+
disabled: !inTable,
|
|
228
|
+
onSelect: act((e) => deleteTable(e)),
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
};
|
|
232
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ArrowRight01Icon } from '@hugeicons/core-free-icons';
|
|
2
|
+
import { HugeiconsIcon } from '@hugeicons/react';
|
|
3
|
+
import { useToggleButton, useToggleButtonState } from '@platejs/toggle/react';
|
|
4
|
+
import { Button } from '@saena-io/ui/components/button';
|
|
5
|
+
import { cn } from '@saena-io/ui/lib/utils';
|
|
6
|
+
import { PlateElement, type PlateElementProps } from 'platejs/react';
|
|
7
|
+
|
|
8
|
+
// A collapsible "toggle" block: a disclosure chevron that shows/hides the blocks indented beneath it. The
|
|
9
|
+
// @platejs/toggle plugin keeps per-block open/closed state (keyed by the block id the editor already assigns
|
|
10
|
+
// for drag-and-drop) and hides the enclosed blocks when closed. Rendered under a concrete <Plate>, so the
|
|
11
|
+
// toggle hooks resolve to the right editor — unlike the shared toolbar (the dual-pane controller hazard).
|
|
12
|
+
export function ToggleElement(props: PlateElementProps) {
|
|
13
|
+
const state = useToggleButtonState(props.element.id as string);
|
|
14
|
+
const { open, buttonProps } = useToggleButton(state);
|
|
15
|
+
return (
|
|
16
|
+
<PlateElement {...props} className="relative mb-1 pl-6">
|
|
17
|
+
<Button
|
|
18
|
+
type="button"
|
|
19
|
+
size="icon-sm"
|
|
20
|
+
variant="ghost"
|
|
21
|
+
title={open ? 'Collapse' : 'Expand'}
|
|
22
|
+
aria-label={open ? 'Collapse' : 'Expand'}
|
|
23
|
+
className="-left-1 absolute top-0 size-6 text-muted-foreground"
|
|
24
|
+
contentEditable={false}
|
|
25
|
+
{...buttonProps}
|
|
26
|
+
>
|
|
27
|
+
<HugeiconsIcon
|
|
28
|
+
icon={ArrowRight01Icon}
|
|
29
|
+
strokeWidth={2}
|
|
30
|
+
className={cn('size-4 transition-transform', open && 'rotate-90')}
|
|
31
|
+
/>
|
|
32
|
+
</Button>
|
|
33
|
+
{props.children}
|
|
34
|
+
</PlateElement>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ToolbarGroup, ToolbarNode } from '@saena-io/ui/components/toolbar';
|
|
2
|
+
import type { RichTextExtension, ToolbarItem } from './extension';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Place extension-contributed controls into the toolbar by their `slot` (see extension.ts). Items whose slot
|
|
6
|
+
* matches a core group are appended to it — after the built-in controls, so those keep their positions — and
|
|
7
|
+
* sorted by `order`; `ai` / `end` items have no core group, so they form their own trailing group (AI first,
|
|
8
|
+
* then a catch-all "More"). Mutates `groups`, which the toolbar rebuilds each render (no shared state).
|
|
9
|
+
*
|
|
10
|
+
* Pure (no React rendering) and free of editor/platejs imports — split out from toolbar.tsx so the placement
|
|
11
|
+
* rules are unit-testable in isolation.
|
|
12
|
+
*/
|
|
13
|
+
export function mergeExtensionItems(groups: ToolbarGroup[], extensions: RichTextExtension[]): void {
|
|
14
|
+
const addonItems = extensions.flatMap((e) => e.toolbar ?? []);
|
|
15
|
+
if (addonItems.length === 0) return;
|
|
16
|
+
const toNode = (it: ToolbarItem): ToolbarNode => ({
|
|
17
|
+
kind: 'custom',
|
|
18
|
+
id: it.id,
|
|
19
|
+
expanded: it.render(),
|
|
20
|
+
});
|
|
21
|
+
const nodesForSlot = (slot: string) =>
|
|
22
|
+
addonItems
|
|
23
|
+
.filter((i) => i.slot === slot)
|
|
24
|
+
.sort((a, b) => a.order - b.order)
|
|
25
|
+
.map(toNode);
|
|
26
|
+
// Append into the matching core group (kept in place so built-in controls don't move).
|
|
27
|
+
for (const group of groups) {
|
|
28
|
+
const extra = nodesForSlot(group.id);
|
|
29
|
+
if (extra.length > 0) group.items = [...group.items, ...extra];
|
|
30
|
+
}
|
|
31
|
+
// Slots with no core group get a trailing group of their own.
|
|
32
|
+
const coreIds = new Set(groups.map((g) => g.id));
|
|
33
|
+
for (const { slot, label } of [
|
|
34
|
+
{ slot: 'ai', label: 'AI' },
|
|
35
|
+
{ slot: 'end', label: 'More' },
|
|
36
|
+
] as const) {
|
|
37
|
+
if (coreIds.has(slot)) continue;
|
|
38
|
+
const items = nodesForSlot(slot);
|
|
39
|
+
if (items.length > 0) groups.push({ id: slot, label, collapse: 'never', items });
|
|
40
|
+
}
|
|
41
|
+
}
|