5htp-core 0.6.0 → 0.6.1
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/client/app/component.tsx +1 -0
- package/client/assets/css/colors.less +46 -25
- package/client/assets/css/components/button.less +14 -5
- package/client/assets/css/components/card.less +5 -10
- package/client/assets/css/components/mantine.less +6 -5
- package/client/assets/css/components/table.less +1 -1
- package/client/assets/css/text/icons.less +1 -1
- package/client/assets/css/text/text.less +4 -0
- package/client/assets/css/utils/borders.less +1 -1
- package/client/assets/css/utils/layouts.less +8 -5
- package/client/components/Button.tsx +20 -17
- package/client/components/Checkbox.tsx +6 -1
- package/client/components/ConnectedInput.tsx +34 -0
- package/client/components/DropDown.tsx +21 -4
- package/client/components/Input.tsx +2 -2
- package/client/components/Rte/Editor.tsx +23 -9
- package/client/components/Rte/ToolbarPlugin/ElementFormat.tsx +1 -1
- package/client/components/Rte/ToolbarPlugin/index.tsx +272 -183
- package/client/components/Rte/currentEditor.ts +31 -2
- package/client/components/Rte/index.tsx +3 -0
- package/client/components/Rte/plugins/FloatingTextFormatToolbarPlugin/index.tsx +4 -1
- package/client/components/Select.tsx +29 -16
- package/client/components/Table/index.tsx +27 -11
- package/client/components/containers/Popover/index.tsx +21 -4
- package/client/components/index.ts +4 -2
- package/client/services/router/index.tsx +7 -5
- package/common/errors/index.tsx +27 -3
- package/common/router/index.ts +4 -1
- package/common/utils/rte.ts +183 -0
- package/package.json +3 -2
- package/server/app/container/console/index.ts +62 -42
- package/server/app/container/index.ts +4 -0
- package/server/app/service/index.ts +4 -2
- package/server/services/auth/index.ts +28 -14
- package/server/services/auth/router/index.ts +1 -1
- package/server/services/auth/router/request.ts +4 -4
- package/server/services/email/index.ts +8 -51
- package/server/services/prisma/Facet.ts +118 -0
- package/server/services/prisma/index.ts +24 -0
- package/server/services/router/http/index.ts +0 -2
- package/server/services/router/index.ts +220 -86
- package/server/services/router/response/index.ts +0 -15
- package/server/utils/rte.ts +21 -132
- package/types/global/utils.d.ts +4 -22
- package/types/icons.d.ts +1 -1
- package/server/services/email/service.json +0 -6
- package/server/services/email/templates.ts +0 -49
- package/server/services/email/transporter.ts +0 -31
|
@@ -22,7 +22,8 @@ import Popover, { Props as PopoverProps } from '@client/components/containers/Po
|
|
|
22
22
|
----------------------------------*/
|
|
23
23
|
|
|
24
24
|
export type Props = SelectProps & InputBaseProps<ComboboxItem> & {
|
|
25
|
-
popoverProps?: PopoverProps
|
|
25
|
+
popoverProps?: PopoverProps,
|
|
26
|
+
buttonProps?: ButtonProps,
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export type Choice = ComboboxItem;
|
|
@@ -63,7 +64,7 @@ export default (initProps: Props) => {
|
|
|
63
64
|
onChange, value: current,
|
|
64
65
|
required
|
|
65
66
|
}, {
|
|
66
|
-
multiple, choices: initChoices, enableSearch, popoverProps,
|
|
67
|
+
multiple, choices: initChoices, enableSearch, popoverProps, buttonProps,
|
|
67
68
|
...props
|
|
68
69
|
}] = useMantineInput<Props, string|number>(initProps);
|
|
69
70
|
|
|
@@ -95,7 +96,8 @@ export default (initProps: Props) => {
|
|
|
95
96
|
/*----------------------------------
|
|
96
97
|
- ACTIONS
|
|
97
98
|
----------------------------------*/
|
|
98
|
-
|
|
99
|
+
|
|
100
|
+
// Load search results
|
|
99
101
|
React.useEffect(() => {
|
|
100
102
|
|
|
101
103
|
if (choicesViaFunc && opened) {
|
|
@@ -113,12 +115,16 @@ export default (initProps: Props) => {
|
|
|
113
115
|
|
|
114
116
|
}, [
|
|
115
117
|
opened,
|
|
116
|
-
search.keywords
|
|
117
|
-
// When initChoices is a function, React considers it's always different
|
|
118
|
-
// It avoids the choices are fetched everytimle the parent component is re-rendered
|
|
119
|
-
typeof initChoices === 'function' ? true : initChoices
|
|
118
|
+
search.keywords
|
|
120
119
|
]);
|
|
121
120
|
|
|
121
|
+
// When initChoices is not a function and has changed
|
|
122
|
+
React.useEffect(() => {
|
|
123
|
+
if (!choicesViaFunc) {
|
|
124
|
+
setChoices(initChoices);
|
|
125
|
+
}
|
|
126
|
+
}, [initChoices]);
|
|
127
|
+
|
|
122
128
|
/*----------------------------------
|
|
123
129
|
- RENDER
|
|
124
130
|
----------------------------------*/
|
|
@@ -172,16 +178,22 @@ export default (initProps: Props) => {
|
|
|
172
178
|
<Button key={choice.value}
|
|
173
179
|
size="s"
|
|
174
180
|
suffix={isSelected ? <i src="check" /> : null}
|
|
175
|
-
onClick={() =>
|
|
176
|
-
|
|
177
|
-
?
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
onClick={() => {
|
|
182
|
+
onChange( multiple
|
|
183
|
+
? (isSelected
|
|
184
|
+
? current.filter(c => c.value !== choice.value)
|
|
185
|
+
: [...(current || []), choice]
|
|
186
|
+
)
|
|
187
|
+
: ((isSelected && !required)
|
|
188
|
+
? null
|
|
189
|
+
: choice
|
|
190
|
+
)
|
|
183
191
|
)
|
|
184
|
-
|
|
192
|
+
|
|
193
|
+
if (!multiple)
|
|
194
|
+
setOpened(false);
|
|
195
|
+
|
|
196
|
+
}}>
|
|
185
197
|
{choice.label}
|
|
186
198
|
</Button>
|
|
187
199
|
)
|
|
@@ -189,6 +201,7 @@ export default (initProps: Props) => {
|
|
|
189
201
|
</div>
|
|
190
202
|
)}>
|
|
191
203
|
<Button
|
|
204
|
+
{...buttonProps}
|
|
192
205
|
prefix={(
|
|
193
206
|
(multiple && current?.length) ? (
|
|
194
207
|
<span class="badge bg info s">
|
|
@@ -32,8 +32,7 @@ export type Props<TRow> = {
|
|
|
32
32
|
empty?: ComponentChild | false,
|
|
33
33
|
|
|
34
34
|
// Interactions
|
|
35
|
-
|
|
36
|
-
onSort?: (columnId: string | null, order: TSortOptions["order"]) => void,
|
|
35
|
+
sortState?: [TSortOptions, React.SetStateAction<TSortOptions>],
|
|
37
36
|
onCellClick?: (row: TRow) => void,
|
|
38
37
|
|
|
39
38
|
selection?: [TRow[], React.SetStateAction<TRow[]>],
|
|
@@ -57,7 +56,7 @@ type TSortOptions = {
|
|
|
57
56
|
- COMPOSANTS
|
|
58
57
|
----------------------------------*/
|
|
59
58
|
export default function Liste<TRow extends TDonneeInconnue>({
|
|
60
|
-
stickyHeader,
|
|
59
|
+
stickyHeader, sortState,
|
|
61
60
|
data: rows, setData, empty,
|
|
62
61
|
onCellClick,
|
|
63
62
|
selection: selectionState, maxSelection,
|
|
@@ -84,6 +83,23 @@ export default function Liste<TRow extends TDonneeInconnue>({
|
|
|
84
83
|
</div>
|
|
85
84
|
);
|
|
86
85
|
|
|
86
|
+
const sortBy = (columnId: string | null, defaultOrder: 'asc' | 'desc') => {
|
|
87
|
+
|
|
88
|
+
if (!sortState) return;
|
|
89
|
+
|
|
90
|
+
const [sort, setSort] = sortState;
|
|
91
|
+
|
|
92
|
+
if (columnId === sort.id) {
|
|
93
|
+
setSort({
|
|
94
|
+
id: columnId,
|
|
95
|
+
order: defaultOrder === 'asc' ? 'desc' : 'asc'
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
setSort({ columnId, order: defaultOrder });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
87
103
|
/*----------------------------------
|
|
88
104
|
- RENDU COLONNES / LIGNES
|
|
89
105
|
----------------------------------*/
|
|
@@ -99,7 +115,7 @@ export default function Liste<TRow extends TDonneeInconnue>({
|
|
|
99
115
|
<td>
|
|
100
116
|
<Checkbox
|
|
101
117
|
id={"selectionner" + iDonnee}
|
|
102
|
-
value={selection.current.some(s => s
|
|
118
|
+
value={selection.current.some(s => s?.id === row?.id)}
|
|
103
119
|
onChange={(isSelected: boolean) => {
|
|
104
120
|
selection.set(current => isSelected
|
|
105
121
|
// Ajoute
|
|
@@ -141,15 +157,15 @@ export default function Liste<TRow extends TDonneeInconnue>({
|
|
|
141
157
|
if (iDonnee === 0) {
|
|
142
158
|
|
|
143
159
|
const headerProps = { className: '', ...cellProps };
|
|
144
|
-
const isCurrentlySorted = sort &&
|
|
145
|
-
const isSortable = sort
|
|
146
|
-
if (isSortable) {
|
|
160
|
+
const isCurrentlySorted = sort && sort.id === sort.id;
|
|
161
|
+
const isSortable = sort;
|
|
162
|
+
if (isSortable && sortState[1]) {
|
|
147
163
|
headerProps.className += ' clickable';
|
|
148
164
|
headerProps.onClick = () => {
|
|
149
165
|
if (isCurrentlySorted)
|
|
150
|
-
|
|
166
|
+
sortBy(null, sort.order);
|
|
151
167
|
else
|
|
152
|
-
|
|
168
|
+
sortBy(sort.id, sort.order);
|
|
153
169
|
}
|
|
154
170
|
}
|
|
155
171
|
|
|
@@ -203,7 +219,7 @@ export default function Liste<TRow extends TDonneeInconnue>({
|
|
|
203
219
|
render = JSON.stringify(cell);
|
|
204
220
|
|
|
205
221
|
return (
|
|
206
|
-
<td
|
|
222
|
+
<td {...cellProps} className={classe}>
|
|
207
223
|
{render}
|
|
208
224
|
</td>
|
|
209
225
|
)
|
|
@@ -230,7 +246,7 @@ export default function Liste<TRow extends TDonneeInconnue>({
|
|
|
230
246
|
<Checkbox
|
|
231
247
|
value={selection.current.length >= rows.length}
|
|
232
248
|
onChange={(status: boolean) => {
|
|
233
|
-
selection.set(status ? rows : []);
|
|
249
|
+
selection.set(status ? rows.filter(r => r?.id) : []);
|
|
234
250
|
}}
|
|
235
251
|
/>
|
|
236
252
|
</th>
|
|
@@ -20,13 +20,16 @@ export type Props = JSX.HTMLAttributes<HTMLDivElement> & {
|
|
|
20
20
|
id?: string,
|
|
21
21
|
|
|
22
22
|
// Display
|
|
23
|
+
mode: 'hide' | 'remove',
|
|
23
24
|
content?: ComponentChild | JSX.Element
|
|
24
25
|
state?: [boolean, StateUpdater<boolean>],
|
|
25
26
|
width?: number | string,
|
|
26
27
|
disable?: boolean
|
|
28
|
+
|
|
27
29
|
// Position
|
|
28
30
|
frame?: HTMLElement,
|
|
29
31
|
side?: TSide,
|
|
32
|
+
|
|
30
33
|
// Tag
|
|
31
34
|
children: JSX.Element | [JSX.Element],
|
|
32
35
|
tag?: string,
|
|
@@ -45,7 +48,7 @@ export default (props: Props) => {
|
|
|
45
48
|
let {
|
|
46
49
|
id,
|
|
47
50
|
|
|
48
|
-
content, state, width, disable,
|
|
51
|
+
mode = 'remove', content, state, width, disable,
|
|
49
52
|
|
|
50
53
|
frame, side = 'bottom',
|
|
51
54
|
|
|
@@ -108,7 +111,7 @@ export default (props: Props) => {
|
|
|
108
111
|
const Tag = tag || 'div';
|
|
109
112
|
|
|
110
113
|
let renderedContent: ComponentChild;
|
|
111
|
-
if (active) {
|
|
114
|
+
if (active || mode === 'hide') {
|
|
112
115
|
//content = typeof content === 'function' ? React.createElement(content) : content;
|
|
113
116
|
renderedContent = React.cloneElement(
|
|
114
117
|
content,
|
|
@@ -124,13 +127,24 @@ export default (props: Props) => {
|
|
|
124
127
|
|
|
125
128
|
style: {
|
|
126
129
|
...(content.props.style || {}),
|
|
130
|
+
|
|
131
|
+
...(!active && mode === 'hide' ? {
|
|
132
|
+
display: 'none'
|
|
133
|
+
} : {}),
|
|
134
|
+
|
|
135
|
+
// Positionning
|
|
127
136
|
...(position ? {
|
|
128
137
|
top: position.css.top,
|
|
129
138
|
left: position.css.left,
|
|
130
139
|
right: position.css.right,
|
|
131
140
|
bottom: position.css.bottom,
|
|
132
|
-
} :
|
|
133
|
-
|
|
141
|
+
} : {}),
|
|
142
|
+
|
|
143
|
+
...(width !== undefined ? {
|
|
144
|
+
width: typeof width === 'number'
|
|
145
|
+
? width + 'rem'
|
|
146
|
+
: width
|
|
147
|
+
} : {})
|
|
134
148
|
}
|
|
135
149
|
}
|
|
136
150
|
)
|
|
@@ -156,6 +170,9 @@ export default (props: Props) => {
|
|
|
156
170
|
{React.cloneElement( children, {
|
|
157
171
|
onClick: (e) => {
|
|
158
172
|
show(isShown => !isShown);
|
|
173
|
+
e.stopPropagation();
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
return false;
|
|
159
176
|
}
|
|
160
177
|
})}
|
|
161
178
|
|
|
@@ -22,7 +22,9 @@ export { default as Date } from './Date';
|
|
|
22
22
|
export { default as Select } from './Select';
|
|
23
23
|
export { default as Checkbox } from './Checkbox';
|
|
24
24
|
export { InputWrapper } from './utils';
|
|
25
|
-
export { default as DropDown } from './DropDown';
|
|
25
|
+
export { default as DropDown } from './DropDown';
|
|
26
|
+
|
|
27
|
+
export { default as ConnectedInput } from './ConnectedInput';
|
|
26
28
|
|
|
27
29
|
// Mantine
|
|
28
30
|
export {
|
|
@@ -59,7 +61,7 @@ export {
|
|
|
59
61
|
//Button,
|
|
60
62
|
//Card,
|
|
61
63
|
Center,
|
|
62
|
-
|
|
64
|
+
Checkbox as CheckboxMantine,
|
|
63
65
|
Chip,
|
|
64
66
|
Code,
|
|
65
67
|
ColorPicker,
|
|
@@ -168,7 +168,7 @@ export default class ClientRouter<
|
|
|
168
168
|
|
|
169
169
|
// Error code
|
|
170
170
|
if (typeof url === 'number') {
|
|
171
|
-
this.createResponse( this.errors[url], this.context.request ).then(( page ) => {
|
|
171
|
+
this.createResponse( this.errors[url], this.context.request, data ).then(( page ) => {
|
|
172
172
|
this.navigate(page, data);
|
|
173
173
|
})
|
|
174
174
|
return;
|
|
@@ -335,10 +335,10 @@ export default class ClientRouter<
|
|
|
335
335
|
|
|
336
336
|
};
|
|
337
337
|
|
|
338
|
-
console.log("404 error page not found.", this.errors, this.routes);
|
|
339
|
-
|
|
340
338
|
const notFoundRoute = this.errors[404];
|
|
341
|
-
return await this.createResponse(notFoundRoute, request
|
|
339
|
+
return await this.createResponse(notFoundRoute, request, {
|
|
340
|
+
error: new Error("Page not found")
|
|
341
|
+
});
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
private async load(route: TUnresolvedNormalRoute): Promise<TRoute>;
|
|
@@ -504,7 +504,9 @@ export default class ClientRouter<
|
|
|
504
504
|
// Listener remover
|
|
505
505
|
return () => {
|
|
506
506
|
debug && console.info(LogPrefix, `De-register hook ${hookName} (index ${cbIndex})`);
|
|
507
|
-
|
|
507
|
+
this.hooks[hookName] = this.hooks[hookName]?.filter(
|
|
508
|
+
(_, index) => index !== cbIndex
|
|
509
|
+
);
|
|
508
510
|
}
|
|
509
511
|
|
|
510
512
|
}
|
package/common/errors/index.tsx
CHANGED
|
@@ -20,9 +20,16 @@ export type TJsonError = {
|
|
|
20
20
|
} & TErrorDetails
|
|
21
21
|
|
|
22
22
|
type TErrorDetails = {
|
|
23
|
+
|
|
23
24
|
// Allow to identify the error catched (ex: displaying custop content, running custom actions, ...)
|
|
24
25
|
id?: string,
|
|
25
26
|
data?: {},
|
|
27
|
+
|
|
28
|
+
cta?: {
|
|
29
|
+
label: string,
|
|
30
|
+
link: string,
|
|
31
|
+
},
|
|
32
|
+
|
|
26
33
|
// For debugging
|
|
27
34
|
stack?: string,
|
|
28
35
|
origin?: string,
|
|
@@ -55,11 +62,13 @@ export type ServerBug = {
|
|
|
55
62
|
},
|
|
56
63
|
|
|
57
64
|
// Error
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
title?: string,
|
|
66
|
+
stacktraces: string[],
|
|
67
|
+
context: object[],
|
|
61
68
|
}
|
|
62
69
|
|
|
70
|
+
export type TCatchedError = Error | CoreError | Anomaly;
|
|
71
|
+
|
|
63
72
|
/*----------------------------------
|
|
64
73
|
- ERREURS
|
|
65
74
|
----------------------------------*/
|
|
@@ -152,6 +161,21 @@ export class AuthRequired extends CoreError {
|
|
|
152
161
|
public http = 401;
|
|
153
162
|
public title = "Authentication Required";
|
|
154
163
|
public static msgDefaut = "Please Login to Continue.";
|
|
164
|
+
|
|
165
|
+
public constructor(
|
|
166
|
+
message: string,
|
|
167
|
+
public motivation?: string,
|
|
168
|
+
details?: TErrorDetails
|
|
169
|
+
) {
|
|
170
|
+
super(message, details);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public json(): TJsonError & { motivation?: string } {
|
|
174
|
+
return {
|
|
175
|
+
...super.json(),
|
|
176
|
+
motivation: this.motivation,
|
|
177
|
+
}
|
|
178
|
+
}
|
|
155
179
|
}
|
|
156
180
|
|
|
157
181
|
export class UpgradeRequired extends CoreError {
|
package/common/router/index.ts
CHANGED
|
@@ -98,7 +98,10 @@ export type TRouteOptions = {
|
|
|
98
98
|
redirectLogged?: string, // Redirect to this route if auth: false and user is logged
|
|
99
99
|
|
|
100
100
|
// Rendering
|
|
101
|
-
static?:
|
|
101
|
+
static?: {
|
|
102
|
+
refresh?: string,
|
|
103
|
+
urls: string[]
|
|
104
|
+
},
|
|
102
105
|
canonicalParams?: string[], // For SEO + unique ID for static cache
|
|
103
106
|
layout?: false | string, // The nale of the layout
|
|
104
107
|
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
|
|
2
|
+
import type Driver from '@server/services/disks/driver';
|
|
3
|
+
import { Anomaly } from '@common/errors';
|
|
4
|
+
|
|
5
|
+
export type LexicalNode = {
|
|
6
|
+
version: number,
|
|
7
|
+
type: string,
|
|
8
|
+
children?: LexicalNode[],
|
|
9
|
+
// Attachement
|
|
10
|
+
src?: string;
|
|
11
|
+
// Headhing
|
|
12
|
+
text?: string;
|
|
13
|
+
anchor?: string;
|
|
14
|
+
tag?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type LexicalState = {
|
|
18
|
+
root: LexicalNode
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type TRenderOptions = {
|
|
22
|
+
|
|
23
|
+
format?: 'html' | 'text', // Default = html
|
|
24
|
+
transform?: RteUtils["transformNode"],
|
|
25
|
+
|
|
26
|
+
render?: (
|
|
27
|
+
node: LexicalNode,
|
|
28
|
+
parent: LexicalNode | null,
|
|
29
|
+
options: TRenderOptions
|
|
30
|
+
) => Promise<LexicalNode>,
|
|
31
|
+
|
|
32
|
+
attachements?: {
|
|
33
|
+
disk: Driver,
|
|
34
|
+
directory: string,
|
|
35
|
+
prevVersion?: string | LexicalState | null,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type TSkeleton = {
|
|
40
|
+
id: string,
|
|
41
|
+
title: string,
|
|
42
|
+
level: number,
|
|
43
|
+
childrens: TSkeleton
|
|
44
|
+
}[];
|
|
45
|
+
|
|
46
|
+
export type TContentAssets = {
|
|
47
|
+
attachements: string[],
|
|
48
|
+
skeleton: TSkeleton
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default abstract class RteUtils {
|
|
52
|
+
|
|
53
|
+
public async render(
|
|
54
|
+
content: string | LexicalState,
|
|
55
|
+
options: TRenderOptions = {}
|
|
56
|
+
): Promise<TContentAssets & {
|
|
57
|
+
html: string | null,
|
|
58
|
+
json: string | LexicalState,
|
|
59
|
+
}> {
|
|
60
|
+
|
|
61
|
+
// Transform content
|
|
62
|
+
const assets: TContentAssets = {
|
|
63
|
+
attachements: [],
|
|
64
|
+
skeleton: []
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Parse content if string
|
|
68
|
+
let json = this.parseState(content);
|
|
69
|
+
if (json === false)
|
|
70
|
+
return { html: '', json: content, ...assets }
|
|
71
|
+
|
|
72
|
+
// Parse prev version if string
|
|
73
|
+
if (typeof options?.attachements?.prevVersion === 'string') {
|
|
74
|
+
try {
|
|
75
|
+
options.attachements.prevVersion = JSON.parse(options.attachements.prevVersion) as LexicalState;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new Anomaly("Invalid JSON format for the given JSON RTE prev version.");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const root = await this.processContent(json.root, null, async (node, parent) => {
|
|
82
|
+
return await this.transformNode(node, parent, assets, options);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
json = { ...json, root };
|
|
86
|
+
|
|
87
|
+
// Delete unused attachements
|
|
88
|
+
const attachementOptions = options?.attachements;
|
|
89
|
+
if (attachementOptions && attachementOptions.prevVersion !== undefined) {
|
|
90
|
+
|
|
91
|
+
await this.processContent(root, null, async (node) => {
|
|
92
|
+
return await this.deleteUnusedFile(node, assets, attachementOptions);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Convert json to HTML
|
|
97
|
+
let html: string | null;
|
|
98
|
+
if (options.format === 'text')
|
|
99
|
+
html = await this.jsonToText( json.root );
|
|
100
|
+
else
|
|
101
|
+
html = await this.jsonToHtml( json, options );
|
|
102
|
+
|
|
103
|
+
return { html, json: content, ...assets };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private parseState( content: string | LexicalState ): LexicalState | false {
|
|
107
|
+
|
|
108
|
+
if (typeof content === 'string' && content.trim().startsWith('{')) {
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(content) as LexicalState;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
throw new Anomaly("Invalid JSON format for the given JSON RTE content.");
|
|
113
|
+
}
|
|
114
|
+
} else if (content && typeof content === 'object' && content.root)
|
|
115
|
+
return content;
|
|
116
|
+
else
|
|
117
|
+
return false;
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected jsonToText(root: LexicalNode): string {
|
|
122
|
+
let result = '';
|
|
123
|
+
|
|
124
|
+
function traverse(node: LexicalNode) {
|
|
125
|
+
switch (node.type) {
|
|
126
|
+
case 'text':
|
|
127
|
+
// Leaf text node
|
|
128
|
+
result += node.text ?? '';
|
|
129
|
+
break;
|
|
130
|
+
case 'linebreak':
|
|
131
|
+
// Explicit line break node
|
|
132
|
+
result += '\n';
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
// Container or block node: dive into children if any
|
|
136
|
+
if (node.children) {
|
|
137
|
+
node.children.forEach(traverse);
|
|
138
|
+
}
|
|
139
|
+
// After finishing a block-level node, append newline
|
|
140
|
+
if (isBlockNode(node.type)) {
|
|
141
|
+
result += '\n';
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Heuristic: treat these as blocks
|
|
148
|
+
function isBlockNode(type: string): boolean {
|
|
149
|
+
return [
|
|
150
|
+
'root',
|
|
151
|
+
'paragraph',
|
|
152
|
+
'heading',
|
|
153
|
+
'listitem',
|
|
154
|
+
'unorderedlist',
|
|
155
|
+
'orderedlist',
|
|
156
|
+
'quote',
|
|
157
|
+
'codeblock',
|
|
158
|
+
'table',
|
|
159
|
+
].includes(type);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
traverse(root);
|
|
163
|
+
|
|
164
|
+
// Trim trailing whitespace/newlines
|
|
165
|
+
return result.replace(/\s+$/, '');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public abstract jsonToHtml( json: LexicalState, options: TRenderOptions ): Promise<string | null>;
|
|
169
|
+
|
|
170
|
+
protected abstract processContent(
|
|
171
|
+
node: LexicalNode,
|
|
172
|
+
parent: LexicalNode | null,
|
|
173
|
+
callback: (node: LexicalNode, parent: LexicalNode | null) => Promise<LexicalNode>
|
|
174
|
+
): Promise<LexicalNode>;
|
|
175
|
+
|
|
176
|
+
protected abstract transformNode( node: LexicalNode, parent: LexicalNode | null, assets: TContentAssets, options: TRenderOptions ): Promise<LexicalNode>;
|
|
177
|
+
|
|
178
|
+
protected abstract deleteUnusedFile(
|
|
179
|
+
node: LexicalNode,
|
|
180
|
+
assets: TContentAssets,
|
|
181
|
+
options: NonNullable<TRenderOptions["attachements"]>
|
|
182
|
+
): Promise<LexicalNode>;
|
|
183
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
3
|
"description": "Convenient TypeScript framework designed for Performance and Productivity.",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.1",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -63,7 +63,6 @@
|
|
|
63
63
|
"md5": "^2.3.0",
|
|
64
64
|
"mime-types": "^2.1.35",
|
|
65
65
|
"module-alias": "^2.2.2",
|
|
66
|
-
"morgan": "^1.10.0",
|
|
67
66
|
"mysql2": "^2.3.0",
|
|
68
67
|
"object-sizeof": "^1.6.3",
|
|
69
68
|
"path-to-regexp": "^6.2.0",
|
|
@@ -73,9 +72,11 @@
|
|
|
73
72
|
"prettier": "^3.3.3",
|
|
74
73
|
"react-scrollbars-custom": "^4.0.27",
|
|
75
74
|
"react-slider": "^2.0.1",
|
|
75
|
+
"react-textarea-autosize": "^8.5.9",
|
|
76
76
|
"regenerator-runtime": "^0.13.9",
|
|
77
77
|
"request": "^2.88.2",
|
|
78
78
|
"slugify": "^1.6.6",
|
|
79
|
+
"source-map-support": "^0.5.21",
|
|
79
80
|
"sql-formatter": "^4.0.2",
|
|
80
81
|
"stopword": "^3.1.1",
|
|
81
82
|
"tslog": "^4.9.1",
|