5htp-core 0.5.0-1 → 0.5.0-5
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/sizing.less +15 -15
- package/src/client/components/Dialog/Manager.tsx +1 -3
- package/src/client/components/Dialog/index.less +4 -8
- package/src/client/components/Form.ts +62 -31
- package/src/client/components/Select/index.tsx +1 -1
- package/src/client/components/button.tsx +22 -21
- package/src/client/components/containers/Popover/getPosition.ts +48 -28
- package/src/client/components/inputv3/Rte/Editor.tsx +1 -1
- package/src/client/components/inputv3/Rte/ToolbarPlugin/BlockFormat.tsx +1 -1
- 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 +1 -1
- package/src/client/services/router/index.tsx +2 -1
- package/src/client/utils/dom.ts +1 -1
- package/src/server/services/auth/index.ts +0 -9
- package/src/server/services/database/index.ts +2 -2
- package/src/server/services/router/index.ts +9 -4
- package/src/server/services/router/request/index.ts +11 -0
- package/src/server/services/router/response/index.ts +2 -0
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.5.0-
|
|
4
|
+
"version": "0.5.0-5",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
@import (reference) '@/client/assets/vars.less';
|
|
2
2
|
|
|
3
3
|
// Utils
|
|
4
|
-
@import './utils/sizing.less';
|
|
5
|
-
@import './utils/spacing.less';
|
|
6
4
|
@import (reference) "./theme.less";
|
|
7
5
|
|
|
8
6
|
// Apply the theme class
|
|
9
7
|
.bg {
|
|
10
|
-
background: var(--cBg);
|
|
8
|
+
background-color: var(--cBg);
|
|
11
9
|
color: var(--cTxtBase);
|
|
12
10
|
|
|
13
11
|
&.img {
|
|
@@ -120,4 +118,8 @@ body {
|
|
|
120
118
|
// Import components style (always after variables declaration)
|
|
121
119
|
@import "@client/assets/css/components.less";
|
|
122
120
|
|
|
121
|
+
// Make utilisy classes priority compared to components
|
|
122
|
+
@import './utils/sizing.less';
|
|
123
|
+
@import './utils/spacing.less';
|
|
124
|
+
|
|
123
125
|
@import '@/client/assets/theme.less';
|
|
@@ -46,7 +46,6 @@ i {
|
|
|
46
46
|
flex: 0 0 @sizeComponent * 0.75;
|
|
47
47
|
height: @sizeComponent * 0.75;
|
|
48
48
|
line-height: @sizeComponent * 0.75;
|
|
49
|
-
//font-size: 0.9em;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
&.unit {
|
|
@@ -88,6 +87,10 @@ i.logo {
|
|
|
88
87
|
border: none; // For img``
|
|
89
88
|
|
|
90
89
|
background-color: var(--cBg);
|
|
90
|
+
|
|
91
|
+
&.circle {
|
|
92
|
+
border-radius: 50%;
|
|
93
|
+
}
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
i.logo {
|
|
@@ -20,9 +20,10 @@ h3 {
|
|
|
20
20
|
color: var(--cTxtAccent);
|
|
21
21
|
font-weight: inherit;
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// Targets the whte card inside bg images, which makes no sense
|
|
24
|
+
// .bg.img & {
|
|
25
|
+
// color: var(--cTxtImportant);
|
|
26
|
+
// }
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -47,52 +47,52 @@
|
|
|
47
47
|
|
|
48
48
|
// Fixes
|
|
49
49
|
.w-@{taille1} {
|
|
50
|
-
width: @taille1 * @sizingUnit;
|
|
50
|
+
width: @taille1 * @sizingUnit !important;
|
|
51
51
|
}
|
|
52
52
|
.h-@{taille1} {
|
|
53
|
-
height: @taille1 * @sizingUnit;
|
|
53
|
+
height: @taille1 * @sizingUnit !important;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
.row > .w-@{taille1},
|
|
57
57
|
.col > .h-@{taille1} {
|
|
58
|
-
flex: 0 0 @taille1 * @sizingUnit;
|
|
58
|
+
flex: 0 0 @taille1 * @sizingUnit !important;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// Min - max
|
|
62
62
|
.w-@{taille1}-a {
|
|
63
|
-
min-width: @taille1 * @sizingUnit;
|
|
63
|
+
min-width: @taille1 * @sizingUnit !important;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
.w-a-@{taille1} {
|
|
67
|
-
max-width: @taille1 * @sizingUnit;
|
|
68
|
-
width: 100
|
|
67
|
+
max-width: @taille1 * @sizingUnit !important;
|
|
68
|
+
width: 100% !important; // We take the maximum space wecan
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
.h-@{taille1}-a {
|
|
72
|
-
min-height: @taille1 * @sizingUnit;
|
|
72
|
+
min-height: @taille1 * @sizingUnit !important;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
.h-a-@{taille1} {
|
|
76
|
-
max-height: @taille1 * @sizingUnit;
|
|
77
|
-
height: 100
|
|
76
|
+
max-height: @taille1 * @sizingUnit !important;
|
|
77
|
+
height: 100% !important; // We take the maximum space wecan
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// Ranges
|
|
81
81
|
.taillesMax(@tailleMax2, @taille2: 0) when (@taille2 <= @tailleMax2) {
|
|
82
82
|
|
|
83
83
|
.w-@{taille1}-@{taille2} {
|
|
84
|
-
min-width: @taille1 * @sizingUnit;
|
|
85
|
-
max-width: @taille2 * @sizingUnit;
|
|
86
|
-
width: 100
|
|
84
|
+
min-width: @taille1 * @sizingUnit !important;
|
|
85
|
+
max-width: @taille2 * @sizingUnit !important;
|
|
86
|
+
width: 100% !important; // We take the maximum space wecan
|
|
87
87
|
|
|
88
88
|
.row > & {
|
|
89
89
|
flex: 1;
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
.h-@{taille1}-@{taille2} {
|
|
93
|
-
min-height: @taille1 * @sizingUnit;
|
|
94
|
-
max-height: @taille2 * @sizingUnit;
|
|
95
|
-
height: 100
|
|
93
|
+
min-height: @taille1 * @sizingUnit !important;
|
|
94
|
+
max-height: @taille2 * @sizingUnit !important;
|
|
95
|
+
height: 100% !important; // We take the maximum space wecan
|
|
96
96
|
|
|
97
97
|
.col > & {
|
|
98
98
|
flex: 1;
|
|
@@ -179,9 +179,7 @@ export const createDialog = (app: Application, isToast: boolean): DialogActions
|
|
|
179
179
|
|
|
180
180
|
if (!isToast)
|
|
181
181
|
render = (
|
|
182
|
-
<div class="modal"
|
|
183
|
-
e.target.classList.contains('modal') && close(false)
|
|
184
|
-
}>
|
|
182
|
+
<div class="modal">
|
|
185
183
|
{render}
|
|
186
184
|
</div>
|
|
187
185
|
)
|
|
@@ -53,19 +53,13 @@
|
|
|
53
53
|
display: flex;
|
|
54
54
|
flex-direction: column;
|
|
55
55
|
align-items: center;
|
|
56
|
-
justify-content:
|
|
56
|
+
justify-content: center;
|
|
57
57
|
gap: @spacing;
|
|
58
58
|
z-index: @modal-zindex;
|
|
59
59
|
|
|
60
60
|
background: fade(#000, 20%);
|
|
61
61
|
border-radius: @radius;
|
|
62
62
|
|
|
63
|
-
// Desktop = vertically center the modal
|
|
64
|
-
@media (min-width: 900px) {
|
|
65
|
-
justify-content: center;
|
|
66
|
-
//padding: @spacing;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
63
|
// Pour les animations (ex: conffetis
|
|
70
64
|
> canvas {
|
|
71
65
|
position: absolute;
|
|
@@ -79,7 +73,9 @@
|
|
|
79
73
|
|
|
80
74
|
position: relative;
|
|
81
75
|
min-width: 300px;
|
|
82
|
-
max-height:
|
|
76
|
+
max-height: 99vh;
|
|
77
|
+
max-width: 99vw;
|
|
78
|
+
width: 400px; // Default width
|
|
83
79
|
box-shadow: none;
|
|
84
80
|
overflow-y: auto;
|
|
85
81
|
|
|
@@ -8,7 +8,7 @@ import React from 'react';
|
|
|
8
8
|
// Core
|
|
9
9
|
import { InputErrorSchema } from '@common/errors';
|
|
10
10
|
import type { Schema } from '@common/validation';
|
|
11
|
-
import type { TValidationResult } from '@common/validation/schema';
|
|
11
|
+
import type { TValidationResult, TValidateOptions } from '@common/validation/schema';
|
|
12
12
|
import useContext from '@/client/context';
|
|
13
13
|
|
|
14
14
|
// Exports
|
|
@@ -36,19 +36,22 @@ export type Form<TFormData extends {} = {}> = {
|
|
|
36
36
|
fields: FieldsAttrs<TFormData>,
|
|
37
37
|
data: TFormData,
|
|
38
38
|
options: TFormOptions<TFormData>,
|
|
39
|
+
backup?: Partial<TFormData>,
|
|
39
40
|
|
|
40
41
|
// Actions
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
setBackup: (backup: Partial<TFormData>) => void,
|
|
43
|
+
validate: (data: Partial<TFormData>, validateAll?: boolean) => TValidationResult<{}>,
|
|
44
|
+
set: (data: Partial<TFormData>, merge?: boolean) => void,
|
|
43
45
|
submit: (additionnalData?: Partial<TFormData>) => Promise<any>,
|
|
44
46
|
|
|
45
47
|
} & FormState
|
|
46
48
|
|
|
47
|
-
type FormState = {
|
|
49
|
+
type FormState<TFormData extends {} = {}> = {
|
|
48
50
|
isLoading: boolean,
|
|
49
51
|
hasChanged: boolean,
|
|
50
52
|
errorsCount: number,
|
|
51
53
|
errors: { [fieldName: string]: string[] },
|
|
54
|
+
backup?: Partial<TFormData>,
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
/*----------------------------------
|
|
@@ -57,7 +60,7 @@ type FormState = {
|
|
|
57
60
|
export default function useForm<TFormData extends {}>(
|
|
58
61
|
schema: Schema<TFormData>,
|
|
59
62
|
options: TFormOptions<TFormData> = {}
|
|
60
|
-
): [ Form
|
|
63
|
+
): [ Form<TFormData>, FieldsAttrs<TFormData> ] {
|
|
61
64
|
|
|
62
65
|
const context = useContext();
|
|
63
66
|
|
|
@@ -65,11 +68,12 @@ export default function useForm<TFormData extends {}>(
|
|
|
65
68
|
- INIT
|
|
66
69
|
----------------------------------*/
|
|
67
70
|
|
|
68
|
-
const [state, setState] = React.useState<FormState
|
|
69
|
-
hasChanged: options.data !== undefined,
|
|
71
|
+
const [state, setState] = React.useState<FormState<TFormData>>({
|
|
72
|
+
hasChanged: false,//options.data !== undefined,
|
|
70
73
|
isLoading: false,
|
|
71
74
|
errorsCount: 0,
|
|
72
|
-
errors: {}
|
|
75
|
+
errors: {},
|
|
76
|
+
backup: undefined
|
|
73
77
|
});
|
|
74
78
|
|
|
75
79
|
const initialData: Partial<TFormData> = options.data || {};
|
|
@@ -78,43 +82,54 @@ export default function useForm<TFormData extends {}>(
|
|
|
78
82
|
const fields = React.useRef<FieldsAttrs<TFormData> | null>(null);
|
|
79
83
|
const [data, setData] = React.useState< Partial<TFormData> >(initialData);
|
|
80
84
|
|
|
81
|
-
//
|
|
85
|
+
// When typed data changes
|
|
82
86
|
React.useEffect(() => {
|
|
83
87
|
|
|
84
88
|
// Validate
|
|
85
|
-
validate(data,
|
|
89
|
+
validate(data, { ignoreMissing: true });
|
|
86
90
|
|
|
87
91
|
// Autosave
|
|
88
|
-
if (options.autoSave !== undefined) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
if (options.autoSave !== undefined && state.hasChanged) {
|
|
93
|
+
saveLocally(data, options.autoSave.id);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
}, [data]);
|
|
97
|
+
|
|
98
|
+
// On start
|
|
99
|
+
React.useEffect(() => {
|
|
100
|
+
|
|
101
|
+
// Restore backup
|
|
102
|
+
if (options.autoSave !== undefined && !state.hasChanged) {
|
|
103
|
+
|
|
104
|
+
const autosaved = localStorage.getItem('form.' + options.autoSave.id);
|
|
105
|
+
if (autosaved !== null) {
|
|
106
|
+
try {
|
|
107
|
+
console.log('[form] Parse autosaved from json:', autosaved);
|
|
108
|
+
setState(c => ({
|
|
109
|
+
...c,
|
|
110
|
+
backup: JSON.parse(autosaved)
|
|
111
|
+
}));
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('[form] Failed to decode autosaved data from json:', autosaved);
|
|
101
114
|
}
|
|
102
115
|
}
|
|
103
116
|
}
|
|
104
117
|
|
|
105
|
-
}, [
|
|
118
|
+
}, []);
|
|
106
119
|
|
|
107
120
|
/*----------------------------------
|
|
108
121
|
- ACTIONS
|
|
109
122
|
----------------------------------*/
|
|
110
|
-
const validate = (allData: Partial<TFormData> = data,
|
|
123
|
+
const validate = (allData: Partial<TFormData> = data, opts: TValidateOptions<TFormData> = {}) => {
|
|
111
124
|
|
|
112
125
|
const validated = schema.validateWithDetails(allData, allData, {}, {
|
|
113
126
|
// Ignore the fields where the vlaue has not been changed
|
|
114
127
|
// if the validation was triggered via onChange
|
|
115
|
-
ignoreMissing:
|
|
128
|
+
ignoreMissing: false,
|
|
116
129
|
// The list of fields we should only validate
|
|
117
|
-
only: options.autoValidateOnly
|
|
130
|
+
only: options.autoValidateOnly,
|
|
131
|
+
// Custom options
|
|
132
|
+
...opts
|
|
118
133
|
});
|
|
119
134
|
|
|
120
135
|
// Update errors
|
|
@@ -224,19 +239,35 @@ export default function useForm<TFormData extends {}>(
|
|
|
224
239
|
- EXPOSE
|
|
225
240
|
----------------------------------*/
|
|
226
241
|
|
|
227
|
-
const form = {
|
|
242
|
+
const form: Form<TFormData> = {
|
|
243
|
+
|
|
228
244
|
fields: fields.current,
|
|
229
245
|
data,
|
|
230
|
-
set: data => {
|
|
231
|
-
|
|
246
|
+
set: (data, merge = true) => {
|
|
247
|
+
|
|
248
|
+
setState( current => ({
|
|
232
249
|
...current,
|
|
233
250
|
hasChanged: true
|
|
234
251
|
}));
|
|
235
|
-
|
|
252
|
+
|
|
253
|
+
setData( merge
|
|
254
|
+
? c => ({ ...c, ...data })
|
|
255
|
+
: data
|
|
256
|
+
);
|
|
236
257
|
},
|
|
258
|
+
|
|
237
259
|
validate,
|
|
238
260
|
submit,
|
|
239
261
|
options,
|
|
262
|
+
|
|
263
|
+
setBackup: (backup: Partial<TFormData>) => {
|
|
264
|
+
|
|
265
|
+
setState(c => ({ ...c, backup }));
|
|
266
|
+
|
|
267
|
+
if (options.autoSave)
|
|
268
|
+
localStorage.setItem('form.' + options.autoSave.id, JSON.stringify(backup));
|
|
269
|
+
},
|
|
270
|
+
|
|
240
271
|
...state
|
|
241
272
|
}
|
|
242
273
|
|
|
@@ -171,35 +171,36 @@ export default ({
|
|
|
171
171
|
// Render
|
|
172
172
|
if ('link' in props || Tag === "a") {
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
174
|
+
// Link (only if enabled)
|
|
175
|
+
if (!disabled) {
|
|
176
|
+
|
|
177
|
+
props.href = props.link;
|
|
178
|
+
|
|
179
|
+
// External = open in new tab by default
|
|
180
|
+
if (props.href && (props.href[0] !== '/' || props.href.startsWith('//')))
|
|
181
|
+
props.target = '_blank';
|
|
182
|
+
}
|
|
181
183
|
|
|
182
|
-
|
|
184
|
+
// Nav
|
|
185
|
+
if (nav && props.target === undefined) {
|
|
183
186
|
|
|
184
|
-
|
|
185
|
-
|
|
187
|
+
const checkIfCurrentUrl = (url: string) =>
|
|
188
|
+
isCurrentUrl(url, props.link, nav === 'exact');
|
|
186
189
|
|
|
187
|
-
|
|
190
|
+
React.useEffect(() => {
|
|
188
191
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
+
// Init
|
|
193
|
+
if (checkIfCurrentUrl(ctx.request.path))
|
|
194
|
+
setIsActive(true);
|
|
192
195
|
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
// On location change
|
|
197
|
+
return history?.listen(({ location }) => {
|
|
195
198
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
})
|
|
199
|
+
setIsActive( checkIfCurrentUrl(location.pathname) );
|
|
199
200
|
|
|
200
|
-
}
|
|
201
|
-
}
|
|
201
|
+
})
|
|
202
202
|
|
|
203
|
+
}, []);
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
Tag = 'a';
|
|
@@ -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: {
|
|
@@ -185,7 +185,7 @@ export default ({ value, setValue, props }: {
|
|
|
185
185
|
<div className="editor-inner">
|
|
186
186
|
<RichTextPlugin
|
|
187
187
|
contentEditable={
|
|
188
|
-
<div className="editor
|
|
188
|
+
<div className="editor" ref={onRef}>
|
|
189
189
|
<ContentEditable
|
|
190
190
|
className="editor-input reading col"
|
|
191
191
|
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) => (
|
|
@@ -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, ...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
|
|
|
@@ -151,7 +151,7 @@ export default ({ service: clientRouter, loaderComponent }: TProps) => {
|
|
|
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 );
|
|
154
|
+
window.location.replace( request ? request.url : window.location.href );
|
|
155
155
|
return { ...page }
|
|
156
156
|
|
|
157
157
|
context.app.setLayout(newLayout);
|
|
@@ -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
|
}
|
|
@@ -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)
|
|
@@ -438,7 +438,7 @@ declare type Routes = {
|
|
|
438
438
|
res.header(response.headers);
|
|
439
439
|
// Data
|
|
440
440
|
res.send(response.data);
|
|
441
|
-
|
|
441
|
+
|
|
442
442
|
});
|
|
443
443
|
}
|
|
444
444
|
|
|
@@ -558,9 +558,14 @@ declare type Routes = {
|
|
|
558
558
|
if (this.app.env.profile === 'prod')
|
|
559
559
|
e.message = "We encountered an internal error, and our team has just been notified. Sorry for the inconvenience.";
|
|
560
560
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
561
|
+
} else {
|
|
562
|
+
|
|
563
|
+
// For debugging HTTP errors
|
|
564
|
+
if (this.app.env.profile === "dev")
|
|
565
|
+
console.warn(e);
|
|
566
|
+
|
|
567
|
+
await this.app.runHook('error.' + code, e, request);
|
|
568
|
+
}
|
|
564
569
|
|
|
565
570
|
// Return error based on the request format
|
|
566
571
|
if (request.accepts("html")) {
|
|
@@ -135,6 +135,17 @@ export default class ServerRequest<
|
|
|
135
135
|
return locale ? locale.toUpperCase() : 'EN'
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
public cookie( key: string, consume: boolean = false ) {
|
|
139
|
+
|
|
140
|
+
const value = this.req.cookies[ key ];
|
|
141
|
+
|
|
142
|
+
if (consume)
|
|
143
|
+
this.res.clearCookie(key);
|
|
144
|
+
|
|
145
|
+
return value;
|
|
146
|
+
|
|
147
|
+
}
|
|
148
|
+
|
|
138
149
|
/*----------------------------------
|
|
139
150
|
- TESTS
|
|
140
151
|
----------------------------------*/
|
|
@@ -81,6 +81,7 @@ export default class ServerResponse<
|
|
|
81
81
|
public statusCode: number = 200;
|
|
82
82
|
public headers: {[cle: string]: string} = {}
|
|
83
83
|
public cookie: express.Response["cookie"];
|
|
84
|
+
public clearCookie: express.Response["clearCookie"];
|
|
84
85
|
|
|
85
86
|
// If data was provided by at lead one controller
|
|
86
87
|
public wasProvided = false;
|
|
@@ -90,6 +91,7 @@ export default class ServerResponse<
|
|
|
90
91
|
super(request);
|
|
91
92
|
|
|
92
93
|
this.cookie = this.request.res.cookie.bind(this.request.res);
|
|
94
|
+
this.clearCookie = this.request.res.clearCookie.bind(this.request.res);
|
|
93
95
|
|
|
94
96
|
this.router = request.router;
|
|
95
97
|
this.app = this.router.app;
|