5htp-core 0.5.0 → 0.5.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/package.json +1 -1
- package/src/client/assets/css/core.less +5 -3
- package/src/client/assets/css/text/icons.less +4 -1
- package/src/client/assets/css/text/text.less +1 -1
- package/src/client/assets/css/text/titres.less +4 -3
- package/src/client/assets/css/theme.less +2 -1
- package/src/client/assets/css/utils/layouts.less +1 -0
- package/src/client/assets/css/utils/sizing.less +15 -15
- package/src/client/components/Dialog/Manager.tsx +1 -3
- package/src/client/components/Dialog/index.less +6 -9
- package/src/client/components/Form.ts +62 -31
- package/src/client/components/Select/index.tsx +1 -1
- package/src/client/components/Table/index.tsx +40 -6
- package/src/client/components/button.tsx +36 -23
- package/src/client/components/containers/Popover/getPosition.ts +48 -28
- package/src/client/components/containers/Popover/index.tsx +9 -3
- package/src/client/components/inputv3/Rte/Editor.tsx +64 -5
- package/src/client/components/inputv3/Rte/ToolbarPlugin/BlockFormat.tsx +1 -1
- package/src/client/components/inputv3/Rte/index.tsx +11 -76
- package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.css +1 -1
- package/src/client/components/inputv3/Rte/style.less +1 -25
- package/src/client/components/inputv3/base.tsx +1 -1
- package/src/client/components/inputv3/index.tsx +7 -1
- package/src/client/services/router/components/router.tsx +4 -3
- package/src/client/services/router/index.tsx +2 -1
- package/src/client/utils/dom.ts +1 -1
- package/src/common/errors/index.tsx +18 -6
- package/src/common/router/index.ts +2 -0
- package/src/server/services/auth/index.ts +0 -9
- package/src/server/services/database/index.ts +2 -2
- package/src/server/services/router/http/index.ts +5 -9
- package/src/server/services/router/index.ts +26 -49
- package/src/server/services/router/request/index.ts +11 -0
- package/src/server/services/router/response/index.ts +21 -0
- package/src/server/services/router/response/page/document.tsx +1 -4
- package/src/server/services/router/response/page/index.tsx +4 -0
|
@@ -1,45 +1,50 @@
|
|
|
1
1
|
export type TSide = "left" | "top" | "right" | "bottom";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Margin from container
|
|
4
|
+
const containerMargin = 8;
|
|
5
|
+
|
|
6
|
+
// Margin from the screen/edges
|
|
7
|
+
const screenMargin = 10;
|
|
4
8
|
|
|
5
9
|
export type TPosition = ReturnType<typeof corrigerPosition>;
|
|
6
10
|
|
|
7
11
|
export default function corrigerPosition(
|
|
8
12
|
container: HTMLElement, // button
|
|
9
|
-
popover: HTMLElement,
|
|
13
|
+
popover: HTMLElement, // popover
|
|
10
14
|
preferredSide: TSide = "bottom",
|
|
11
|
-
frame?: HTMLElement // body
|
|
15
|
+
frame?: HTMLElement | null // body or closest positioned ancestor
|
|
12
16
|
) {
|
|
13
|
-
// Dimensions
|
|
14
|
-
const popoverDims = {
|
|
17
|
+
// Dimensions
|
|
18
|
+
const popoverDims = {
|
|
19
|
+
width: popover.offsetWidth,
|
|
20
|
+
height: popover.offsetHeight,
|
|
21
|
+
};
|
|
15
22
|
const containerRect = container.getBoundingClientRect();
|
|
16
23
|
|
|
17
|
-
// Find
|
|
24
|
+
// Find frame if not provided
|
|
18
25
|
if (!frame) {
|
|
19
|
-
// Find the closest relative-positioned parent
|
|
26
|
+
// Find the closest relative-positioned or sticky-positioned parent
|
|
20
27
|
frame = container.parentElement;
|
|
21
28
|
while (frame && !["relative", "sticky"].includes(getComputedStyle(frame).position)) {
|
|
22
29
|
frame = frame.parentElement;
|
|
23
30
|
}
|
|
24
|
-
|
|
25
31
|
if (!frame) frame = document.body;
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
if (debug) console.log("frame", frame);
|
|
29
|
-
|
|
30
34
|
const frameRect = frame.getBoundingClientRect();
|
|
35
|
+
const frameContRect = document.body.getBoundingClientRect();
|
|
31
36
|
const frameOffsetTop = frame.scrollTop;
|
|
32
37
|
const frameOffsetLeft = frame.scrollLeft;
|
|
33
38
|
|
|
34
|
-
// Calculate available space
|
|
39
|
+
// Calculate available space (relative to the document body) around the container
|
|
35
40
|
const space = {
|
|
36
|
-
top: containerRect.top -
|
|
37
|
-
bottom:
|
|
38
|
-
left: containerRect.left -
|
|
39
|
-
right:
|
|
41
|
+
top: containerRect.top - frameContRect.top,
|
|
42
|
+
bottom: frameContRect.bottom - containerRect.bottom,
|
|
43
|
+
left: containerRect.left - frameContRect.left,
|
|
44
|
+
right: frameContRect.right - containerRect.right,
|
|
40
45
|
};
|
|
41
46
|
|
|
42
|
-
// Helper
|
|
47
|
+
// Helper to check if the popover can fit on a given side without clipping
|
|
43
48
|
const canFit = (side: TSide) => {
|
|
44
49
|
switch (side) {
|
|
45
50
|
case "top":
|
|
@@ -53,7 +58,7 @@ export default function corrigerPosition(
|
|
|
53
58
|
}
|
|
54
59
|
};
|
|
55
60
|
|
|
56
|
-
//
|
|
61
|
+
// Start with the preferred side; if it doesn't fit, pick the first side that fits
|
|
57
62
|
let side: TSide = preferredSide;
|
|
58
63
|
if (!canFit(preferredSide)) {
|
|
59
64
|
if (canFit("top")) side = "top";
|
|
@@ -62,14 +67,17 @@ export default function corrigerPosition(
|
|
|
62
67
|
else if (canFit("right")) side = "right";
|
|
63
68
|
}
|
|
64
69
|
|
|
65
|
-
// Calculate position
|
|
70
|
+
// Calculate initial position (without screen-margin clamping)
|
|
66
71
|
const position = { top: 0, left: 0 };
|
|
72
|
+
|
|
67
73
|
if (side === "top") {
|
|
68
74
|
position.top =
|
|
69
75
|
containerRect.top -
|
|
70
76
|
frameRect.top -
|
|
71
77
|
popoverDims.height +
|
|
72
|
-
frameOffsetTop
|
|
78
|
+
frameOffsetTop -
|
|
79
|
+
containerMargin; // gap above container
|
|
80
|
+
|
|
73
81
|
position.left =
|
|
74
82
|
containerRect.left -
|
|
75
83
|
frameRect.left +
|
|
@@ -79,7 +87,9 @@ export default function corrigerPosition(
|
|
|
79
87
|
position.top =
|
|
80
88
|
containerRect.bottom -
|
|
81
89
|
frameRect.top +
|
|
82
|
-
frameOffsetTop
|
|
90
|
+
frameOffsetTop +
|
|
91
|
+
containerMargin; // gap below container
|
|
92
|
+
|
|
83
93
|
position.left =
|
|
84
94
|
containerRect.left -
|
|
85
95
|
frameRect.left +
|
|
@@ -91,34 +101,44 @@ export default function corrigerPosition(
|
|
|
91
101
|
frameRect.top +
|
|
92
102
|
(containerRect.height - popoverDims.height) / 2 +
|
|
93
103
|
frameOffsetTop;
|
|
104
|
+
|
|
94
105
|
position.left =
|
|
95
106
|
containerRect.left -
|
|
96
107
|
frameRect.left -
|
|
97
108
|
popoverDims.width +
|
|
98
|
-
frameOffsetLeft
|
|
109
|
+
frameOffsetLeft -
|
|
110
|
+
containerMargin; // gap to the left of container
|
|
99
111
|
} else if (side === "right") {
|
|
100
112
|
position.top =
|
|
101
113
|
containerRect.top -
|
|
102
114
|
frameRect.top +
|
|
103
115
|
(containerRect.height - popoverDims.height) / 2 +
|
|
104
116
|
frameOffsetTop;
|
|
117
|
+
|
|
105
118
|
position.left =
|
|
106
119
|
containerRect.right -
|
|
107
120
|
frameRect.left +
|
|
108
|
-
frameOffsetLeft
|
|
121
|
+
frameOffsetLeft +
|
|
122
|
+
containerMargin; // gap to the right of container
|
|
109
123
|
}
|
|
110
124
|
|
|
111
|
-
//
|
|
125
|
+
// Clamp the final position to ensure a screenMargin from edges
|
|
112
126
|
position.top = Math.max(
|
|
113
|
-
frameOffsetTop,
|
|
114
|
-
Math.min(
|
|
127
|
+
frameOffsetTop + screenMargin,
|
|
128
|
+
Math.min(
|
|
129
|
+
frameContRect.height - popoverDims.height + frameOffsetTop - screenMargin,
|
|
130
|
+
position.top
|
|
131
|
+
)
|
|
115
132
|
);
|
|
116
133
|
position.left = Math.max(
|
|
117
|
-
frameOffsetLeft,
|
|
118
|
-
Math.min(
|
|
134
|
+
frameOffsetLeft + screenMargin,
|
|
135
|
+
Math.min(
|
|
136
|
+
frameContRect.width - popoverDims.width + frameOffsetLeft - screenMargin,
|
|
137
|
+
position.left
|
|
138
|
+
)
|
|
119
139
|
);
|
|
120
140
|
|
|
121
|
-
// Return
|
|
141
|
+
// Return the final side and position
|
|
122
142
|
return {
|
|
123
143
|
side,
|
|
124
144
|
css: {
|
|
@@ -77,15 +77,21 @@ export default (props: Props) => {
|
|
|
77
77
|
)
|
|
78
78
|
);
|
|
79
79
|
|
|
80
|
-
// Autofocus elements
|
|
81
|
-
focusContent( refContent.current );
|
|
82
|
-
|
|
83
80
|
// Close when the user clicks elsewere tha the popover
|
|
84
81
|
return blurable([ refCont.current, () => show(false) ])
|
|
85
82
|
}
|
|
86
83
|
|
|
87
84
|
}, [shown]);
|
|
88
85
|
|
|
86
|
+
React.useEffect(() => {
|
|
87
|
+
if (position !== undefined) {
|
|
88
|
+
|
|
89
|
+
// Autofocus elements once the final position has been set
|
|
90
|
+
focusContent( refContent.current );
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
}, [position]);
|
|
94
|
+
|
|
89
95
|
/*----------------------------------
|
|
90
96
|
- ATTRIBUTES
|
|
91
97
|
----------------------------------*/
|
|
@@ -84,10 +84,6 @@ export const EMPTY_STATE = '{"root":{"children":[{"children":[],"direction":null
|
|
|
84
84
|
- TYPES
|
|
85
85
|
----------------------------------*/
|
|
86
86
|
|
|
87
|
-
export type Props = {
|
|
88
|
-
preview?: boolean,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
87
|
const ValueControlPlugin = ({ props, value }) => {
|
|
92
88
|
|
|
93
89
|
const [editor] = useLexicalComposerContext();
|
|
@@ -111,6 +107,69 @@ export default ({ value, setValue, props }: {
|
|
|
111
107
|
setValue: (value: string) => void,
|
|
112
108
|
props: TRteProps
|
|
113
109
|
}) => {
|
|
110
|
+
|
|
111
|
+
let {
|
|
112
|
+
// Decoration
|
|
113
|
+
title,
|
|
114
|
+
// Actions
|
|
115
|
+
preview = true
|
|
116
|
+
} = props;
|
|
117
|
+
|
|
118
|
+
/*----------------------------------
|
|
119
|
+
- PREVIEW
|
|
120
|
+
----------------------------------*/
|
|
121
|
+
|
|
122
|
+
const [isPreview, setIsPreview] = React.useState(preview);
|
|
123
|
+
const [html, setHTML] = React.useState(null);
|
|
124
|
+
|
|
125
|
+
React.useEffect(() => {
|
|
126
|
+
if (isPreview) {
|
|
127
|
+
renderPreview(value).then(setHTML);
|
|
128
|
+
}
|
|
129
|
+
}, [value, isPreview]);
|
|
130
|
+
|
|
131
|
+
// When isPreview changes, close the active editor
|
|
132
|
+
React.useEffect(() => {
|
|
133
|
+
if (!isPreview) {
|
|
134
|
+
|
|
135
|
+
// Close active editor
|
|
136
|
+
if (RichEditorUtils.active && RichEditorUtils.active?.title !== title)
|
|
137
|
+
RichEditorUtils.active.close();
|
|
138
|
+
|
|
139
|
+
// Set active Editor
|
|
140
|
+
RichEditorUtils.active = {
|
|
141
|
+
title,
|
|
142
|
+
close: () => preview ? setIsPreview(true) : null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
}, [isPreview]);
|
|
147
|
+
|
|
148
|
+
const renderPreview = async (value: {} | undefined) => {
|
|
149
|
+
|
|
150
|
+
if (!value)
|
|
151
|
+
return '';
|
|
152
|
+
|
|
153
|
+
if (typeof document === 'undefined')
|
|
154
|
+
throw new Error("HTML preview disabled in server side.");
|
|
155
|
+
|
|
156
|
+
const html = await RichEditorUtils.jsonToHtml(value);
|
|
157
|
+
|
|
158
|
+
return html;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (isPreview)
|
|
162
|
+
return (
|
|
163
|
+
html === null ? (
|
|
164
|
+
<div class="col al-center h-2">
|
|
165
|
+
<i src="spin" />
|
|
166
|
+
</div>
|
|
167
|
+
) : (
|
|
168
|
+
<div class="preview reading h-1-4 scrollable col clickable"
|
|
169
|
+
onClick={() => setIsPreview(false)}
|
|
170
|
+
dangerouslySetInnerHTML={{ __html: html }} />
|
|
171
|
+
)
|
|
172
|
+
)
|
|
114
173
|
|
|
115
174
|
/*----------------------------------
|
|
116
175
|
- INIT
|
|
@@ -185,7 +244,7 @@ export default ({ value, setValue, props }: {
|
|
|
185
244
|
<div className="editor-inner">
|
|
186
245
|
<RichTextPlugin
|
|
187
246
|
contentEditable={
|
|
188
|
-
<div className="editor
|
|
247
|
+
<div className="editor" ref={onRef}>
|
|
189
248
|
<ContentEditable
|
|
190
249
|
className="editor-input reading col"
|
|
191
250
|
aria-placeholder={"Type text here ..."}
|
|
@@ -206,7 +206,7 @@ export default function BlockFormatDropDown({
|
|
|
206
206
|
|
|
207
207
|
return (
|
|
208
208
|
<DropDown disabled={disabled} icon={currentBlockType ? currentBlockType.icon : 'question'} size="s"
|
|
209
|
-
label={currentBlockType ? currentBlockType.label : 'Unknown Block Type'}
|
|
209
|
+
//label={currentBlockType ? currentBlockType.label : 'Unknown Block Type'}
|
|
210
210
|
popover={{ tag: 'li' }}
|
|
211
211
|
>
|
|
212
212
|
{blockTypes.map((block) => (
|
|
@@ -21,6 +21,7 @@ import './style.less';
|
|
|
21
21
|
|
|
22
22
|
export type Props = {
|
|
23
23
|
preview?: boolean,
|
|
24
|
+
title: string,
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/*----------------------------------
|
|
@@ -28,22 +29,14 @@ export type Props = {
|
|
|
28
29
|
----------------------------------*/
|
|
29
30
|
export default (props: Props & InputBaseProps<string>) => {
|
|
30
31
|
|
|
31
|
-
let {
|
|
32
|
-
// Decoration
|
|
33
|
-
required, size, title, className = '',
|
|
34
|
-
// State
|
|
35
|
-
errors,
|
|
36
|
-
// Actions
|
|
37
|
-
preview = true
|
|
38
|
-
} = props;
|
|
32
|
+
let { className = '' } = props;
|
|
39
33
|
|
|
40
34
|
/*----------------------------------
|
|
41
35
|
- INIT
|
|
42
36
|
----------------------------------*/
|
|
43
37
|
|
|
44
38
|
const [Editor, setEditor] = React.useState<{ default: typeof TEditor }>(null);
|
|
45
|
-
|
|
46
|
-
const [html, setHTML] = React.useState(null);
|
|
39
|
+
|
|
47
40
|
const [{ value }, setValue] = useInput(props, undefined, true);
|
|
48
41
|
|
|
49
42
|
className += ' input rte';
|
|
@@ -53,46 +46,13 @@ export default (props: Props & InputBaseProps<string>) => {
|
|
|
53
46
|
----------------------------------*/
|
|
54
47
|
|
|
55
48
|
React.useEffect(() => {
|
|
56
|
-
if (
|
|
57
|
-
renderPreview(value).then(setHTML);
|
|
58
|
-
}
|
|
59
|
-
}, [value, isPreview]);
|
|
60
|
-
|
|
61
|
-
// When isPreview changes, close the active editor
|
|
62
|
-
React.useEffect(() => {
|
|
63
|
-
if (!isPreview) {
|
|
64
|
-
|
|
65
|
-
// Close active editor
|
|
66
|
-
if (RichEditorUtils.active && RichEditorUtils.active?.title !== title)
|
|
67
|
-
RichEditorUtils.active.close();
|
|
68
|
-
|
|
69
|
-
// Set active Editor
|
|
70
|
-
RichEditorUtils.active = {
|
|
71
|
-
title,
|
|
72
|
-
close: () => preview ? setIsPreview(true) : null
|
|
73
|
-
}
|
|
49
|
+
if (!Editor) {
|
|
74
50
|
|
|
75
51
|
// Load editor component if not alreayd done
|
|
76
52
|
// We lazy load since it's heavy and needs to be loade donly on client side
|
|
77
|
-
|
|
78
|
-
import('./Editor').then(setEditor);
|
|
79
|
-
}
|
|
80
|
-
|
|
53
|
+
import('./Editor').then(setEditor);
|
|
81
54
|
}
|
|
82
|
-
}, [
|
|
83
|
-
|
|
84
|
-
const renderPreview = async (value: {} | undefined) => {
|
|
85
|
-
|
|
86
|
-
if (!value)
|
|
87
|
-
return '';
|
|
88
|
-
|
|
89
|
-
if (typeof document === 'undefined')
|
|
90
|
-
throw new Error("HTML preview disabled in server side.");
|
|
91
|
-
|
|
92
|
-
const html = await RichEditorUtils.jsonToHtml(value);
|
|
93
|
-
|
|
94
|
-
return html;
|
|
95
|
-
}
|
|
55
|
+
}, []);
|
|
96
56
|
|
|
97
57
|
/*----------------------------------
|
|
98
58
|
- RENDER
|
|
@@ -101,40 +61,15 @@ export default (props: Props & InputBaseProps<string>) => {
|
|
|
101
61
|
<InputWrapper {...props}>
|
|
102
62
|
<div class={className}>
|
|
103
63
|
|
|
104
|
-
{
|
|
64
|
+
{Editor === null ? (
|
|
105
65
|
|
|
106
|
-
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
</div>
|
|
110
|
-
) : (
|
|
111
|
-
<div class="preview reading h-1-4 scrollable col clickable"
|
|
112
|
-
onClick={() => setIsPreview(false)}
|
|
113
|
-
dangerouslySetInnerHTML={{ __html: html }} />
|
|
114
|
-
)
|
|
66
|
+
<div class="col al-center h-2">
|
|
67
|
+
<i src="spin" />
|
|
68
|
+
</div>
|
|
115
69
|
|
|
116
|
-
) :
|
|
70
|
+
) : (
|
|
117
71
|
<Editor.default value={value} setValue={setValue} props={props} />
|
|
118
72
|
)}
|
|
119
|
-
|
|
120
|
-
{/* <Tag {...fieldProps}
|
|
121
|
-
|
|
122
|
-
placeholder={props.title}
|
|
123
|
-
|
|
124
|
-
// @ts-ignore: Property 'ref' does not exist on type 'IntrinsicAttributes'
|
|
125
|
-
ref={refInput}
|
|
126
|
-
value={value}
|
|
127
|
-
onFocus={() => setState({ focus: true })}
|
|
128
|
-
onBlur={() => setState({ focus: false })}
|
|
129
|
-
onChange={(e) => updateValue(e.target.value)}
|
|
130
|
-
onKeyDown={(e: KeyboardEvent) => {
|
|
131
|
-
|
|
132
|
-
if (onPressEnter && e.key === 'Enter' && value !== undefined) {
|
|
133
|
-
commitValue();
|
|
134
|
-
onPressEnter(value)
|
|
135
|
-
}
|
|
136
|
-
}}
|
|
137
|
-
/> */}
|
|
138
73
|
</div>
|
|
139
74
|
</InputWrapper>
|
|
140
75
|
)
|
|
@@ -2,31 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
.preview {
|
|
4
4
|
position: relative;
|
|
5
|
-
|
|
6
|
-
&:before {
|
|
7
|
-
|
|
8
|
-
content: 'Click here to edit';
|
|
9
|
-
|
|
10
|
-
display: flex;
|
|
11
|
-
justify-content: center;
|
|
12
|
-
align-items: center;
|
|
13
|
-
|
|
14
|
-
position: absolute;
|
|
15
|
-
top: 0;
|
|
16
|
-
left: 0;
|
|
17
|
-
right: 0;
|
|
18
|
-
bottom: 0;
|
|
19
|
-
|
|
20
|
-
background: fade(#FFF, 50%);
|
|
21
|
-
pointer-events: none;
|
|
22
|
-
z-index: 1;
|
|
23
|
-
opacity: 0;
|
|
24
|
-
transition: all 0.3s linear;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
&:hover:before {
|
|
28
|
-
opacity: 1;
|
|
29
|
-
}
|
|
5
|
+
cursor: text;
|
|
30
6
|
}
|
|
31
7
|
|
|
32
8
|
.other h2 {
|
|
@@ -41,7 +41,7 @@ export type TInputState<TValue> = {
|
|
|
41
41
|
- HOOKS
|
|
42
42
|
----------------------------------*/
|
|
43
43
|
export function useInput<TValue>(
|
|
44
|
-
{ value: externalValue, onChange, ...otherProps }: InputBaseProps<TValue>,
|
|
44
|
+
{ value: externalValue, onChange, className, hint, ...otherProps }: InputBaseProps<TValue>,
|
|
45
45
|
defaultValue: TValue,
|
|
46
46
|
autoCommit: boolean = false
|
|
47
47
|
): [
|
|
@@ -173,7 +173,13 @@ export default (props: Props & InputBaseProps<string> & TInputElementProps) => {
|
|
|
173
173
|
----------------------------------*/
|
|
174
174
|
return (
|
|
175
175
|
<InputWrapper {...props}>
|
|
176
|
-
<div class={className} onClick={() =>
|
|
176
|
+
<div class={className} onClick={(e) => {
|
|
177
|
+
|
|
178
|
+
const shouldFocus = props.onClick ? props.onClick() !== false : true;
|
|
179
|
+
if (shouldFocus)
|
|
180
|
+
refInput.current?.focus()
|
|
181
|
+
|
|
182
|
+
}}>
|
|
177
183
|
|
|
178
184
|
{prefix}
|
|
179
185
|
|
|
@@ -147,12 +147,12 @@ export default ({ service: clientRouter, loaderComponent }: TProps) => {
|
|
|
147
147
|
if (newLayout && curLayout && newLayout.path !== curLayout.path) {
|
|
148
148
|
|
|
149
149
|
// TEMPORARY FIX: reload everything when we change layout
|
|
150
|
-
// Because layout can have a different theme
|
|
150
|
+
// Because layout can have a different CSS theme
|
|
151
151
|
// But when we call setLayout, the style of the previous layout are still oaded and applied
|
|
152
152
|
// Find a way to unload the previous layout / page resources before to load the new one
|
|
153
153
|
console.log(LogPrefix, `Changing layout. Before:`, curLayout, 'New layout:', newLayout);
|
|
154
|
-
window.location.replace( request ? request.url : location.href );
|
|
155
|
-
return
|
|
154
|
+
window.location.replace( request ? request.url : window.location.href );
|
|
155
|
+
return page; // Don't spread since it's an instance
|
|
156
156
|
|
|
157
157
|
context.app.setLayout(newLayout);
|
|
158
158
|
}
|
|
@@ -191,6 +191,7 @@ export default ({ service: clientRouter, loaderComponent }: TProps) => {
|
|
|
191
191
|
// Reset scroll
|
|
192
192
|
window.scrollTo(0, 0);
|
|
193
193
|
// Should be called AFTER rendering the page (so after the state change)
|
|
194
|
+
console.log("CHANGE PAGE", currentPage);
|
|
194
195
|
currentPage?.updateClient();
|
|
195
196
|
// Scroll to the selected content via url hash
|
|
196
197
|
restoreScroll(currentPage);
|
|
@@ -124,7 +124,7 @@ export type TRoutesLoaders = {
|
|
|
124
124
|
|
|
125
125
|
export type THookCallback<TRouter extends ClientRouter> = (request: ClientRequest<TRouter>) => void;
|
|
126
126
|
|
|
127
|
-
type THookName = 'page.change' | 'page.changed'
|
|
127
|
+
type THookName = 'page.change' | 'page.changed' | 'page.rendered'
|
|
128
128
|
|
|
129
129
|
type Config<TAdditionnalContext extends {} = {}> = {
|
|
130
130
|
preload: string[], // List of globs
|
|
@@ -428,6 +428,7 @@ export default class ClientRouter<
|
|
|
428
428
|
|
|
429
429
|
console.log(`Render complete`);
|
|
430
430
|
|
|
431
|
+
this.runHook('page.rendered', request);
|
|
431
432
|
});
|
|
432
433
|
}
|
|
433
434
|
|
package/src/client/utils/dom.ts
CHANGED
|
@@ -71,7 +71,7 @@ export const focusContent = ( container: HTMLElement ) => {
|
|
|
71
71
|
|
|
72
72
|
const toFocus = container.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLButtonElement>(
|
|
73
73
|
'input, textarea, button.btn.primary, footer > button.btn'
|
|
74
|
-
) || container;
|
|
74
|
+
) || container; // Is it useful ? Creating unwanted scroll issue on showing popover
|
|
75
75
|
|
|
76
76
|
toFocus?.focus();
|
|
77
77
|
}
|
|
@@ -17,9 +17,13 @@ type TJsonError = {
|
|
|
17
17
|
message: string,
|
|
18
18
|
// Form fields
|
|
19
19
|
errors?: TListeErreursSaisie
|
|
20
|
-
} &
|
|
20
|
+
} & TErrorDetails
|
|
21
21
|
|
|
22
|
-
type
|
|
22
|
+
type TErrorDetails = {
|
|
23
|
+
// Allow to identify the error catched (ex: displaying custop content, running custom actions, ...)
|
|
24
|
+
id?: string,
|
|
25
|
+
data?: {},
|
|
26
|
+
// For debugging
|
|
23
27
|
stack?: string,
|
|
24
28
|
origin?: string,
|
|
25
29
|
}
|
|
@@ -66,13 +70,13 @@ export abstract class CoreError extends Error {
|
|
|
66
70
|
public abstract http: number;
|
|
67
71
|
public title: string = "Uh Oh ...";
|
|
68
72
|
public message: string;
|
|
69
|
-
public details:
|
|
73
|
+
public details: TErrorDetails = {};
|
|
70
74
|
|
|
71
75
|
// Note: On ne le redéfini pas ici, car déjà présent dans Error
|
|
72
76
|
// La redéfinition reset la valeur du stacktrace
|
|
73
77
|
//public stack?: string;
|
|
74
78
|
|
|
75
|
-
public constructor(message?: string, details?:
|
|
79
|
+
public constructor(message?: string, details?: TErrorDetails) {
|
|
76
80
|
|
|
77
81
|
super(message);
|
|
78
82
|
|
|
@@ -120,7 +124,7 @@ export class InputErrorSchema extends CoreError {
|
|
|
120
124
|
return chaines.join('; ');
|
|
121
125
|
}
|
|
122
126
|
|
|
123
|
-
public constructor( public errors: TListeErreursSaisie, details?:
|
|
127
|
+
public constructor( public errors: TListeErreursSaisie, details?: TErrorDetails) {
|
|
124
128
|
|
|
125
129
|
super( InputErrorSchema.listeToString(errors), details );
|
|
126
130
|
|
|
@@ -162,6 +166,12 @@ export class NotFound extends CoreError {
|
|
|
162
166
|
public static msgDefaut = "The resource you asked for was not found.";
|
|
163
167
|
}
|
|
164
168
|
|
|
169
|
+
export class RateLimit extends CoreError {
|
|
170
|
+
public http = 429;
|
|
171
|
+
public title = "You're going too fast";
|
|
172
|
+
public static msgDefaut = "Please slow down a bit and retry again later.";
|
|
173
|
+
}
|
|
174
|
+
|
|
165
175
|
export class Anomaly extends CoreError {
|
|
166
176
|
|
|
167
177
|
public http = 500;
|
|
@@ -193,7 +203,7 @@ export class NetworkError extends Error {
|
|
|
193
203
|
export const viaHttpCode = (
|
|
194
204
|
code: number,
|
|
195
205
|
message: string,
|
|
196
|
-
details?:
|
|
206
|
+
details?: TErrorDetails
|
|
197
207
|
): CoreError => {
|
|
198
208
|
return fromJson({
|
|
199
209
|
code,
|
|
@@ -229,6 +239,8 @@ export const fromJson = ({ code, message, ...details }: TJsonError) => {
|
|
|
229
239
|
|
|
230
240
|
case 404: return new NotFound( message, details );
|
|
231
241
|
|
|
242
|
+
case 429: return new RateLimit( message, details );
|
|
243
|
+
|
|
232
244
|
default: return new Anomaly( message, details );
|
|
233
245
|
}
|
|
234
246
|
|
|
@@ -143,7 +143,7 @@ export default class SQL extends Service<Config, Hooks, Application, Services> {
|
|
|
143
143
|
query.then = (cb: (data: any) => void) => query().then(cb);
|
|
144
144
|
|
|
145
145
|
query.first = <TRowData extends TObjetDonnees = {}>(opts: TSelectQueryOptions = {}) => this.first<TRowData>(string, opts);
|
|
146
|
-
query.firstOrFail = (message
|
|
146
|
+
query.firstOrFail = (message: string, opts: TQueryOptions = {}) => this.firstOrFail<TRowData>(string, message, opts);
|
|
147
147
|
|
|
148
148
|
query.value = <TValue extends any = number>(opts: TQueryOptions = {}) => this.selectVal<TValue>(string, opts);
|
|
149
149
|
|
|
@@ -283,7 +283,7 @@ export default class SQL extends Service<Config, Hooks, Application, Services> {
|
|
|
283
283
|
return resultatRequetes[0] || null;
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
-
public firstOrFail = <TRowData extends TObjetDonnees = {}>(query: string, message
|
|
286
|
+
public firstOrFail = <TRowData extends TObjetDonnees = {}>(query: string, message: string, opts: TSelectQueryOptions = {}): Promise<TRowData> =>
|
|
287
287
|
this.select(query, opts).then((resultatRequetes: any) => {
|
|
288
288
|
|
|
289
289
|
if (resultatRequetes.length === 0)
|
|
@@ -162,16 +162,12 @@ export default class HttpServer {
|
|
|
162
162
|
|
|
163
163
|
// Décodage des données post
|
|
164
164
|
express.json({
|
|
165
|
-
|
|
166
|
-
// NOTE: Encore nécessaire ? Les webhooks stripe & bitgo n'étant plus utilisés
|
|
167
|
-
// Because Stripe needs the raw body, we compute it but only when hitting the Stripe callback URL.
|
|
168
|
-
/*verify: function (req: Request, res: Response, buf: Buffer) {
|
|
169
|
-
if (req.originalUrl.startsWith('/api/paiement/impact/stripe'))
|
|
170
|
-
//req.rawBody = buf.toString();
|
|
171
|
-
},*/
|
|
172
|
-
|
|
173
165
|
// TODO: prendre en considération les upload de fichiers
|
|
174
|
-
limit: '2mb'
|
|
166
|
+
limit: '2mb',
|
|
167
|
+
verify: (req, res, buf, encoding) => {
|
|
168
|
+
// Store the raw request body so we can access it later
|
|
169
|
+
req.rawBody = buf;
|
|
170
|
+
}
|
|
175
171
|
}),
|
|
176
172
|
|
|
177
173
|
// Permet de receptionner les données multipart (req.body + req.files)
|