5htp-core 0.3.4-3 → 0.3.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 +2 -2
- package/src/client/app/component.tsx +3 -0
- package/src/client/assets/css/components/button.less +6 -5
- package/src/client/assets/css/components/card.less +0 -22
- package/src/client/assets/css/components.less +0 -25
- package/src/client/assets/css/core.less +62 -3
- package/src/client/assets/css/text/icons.less +3 -2
- package/src/client/assets/css/text/text.less +0 -4
- package/src/client/assets/css/theme.less +69 -39
- package/src/client/assets/css/utils/layouts.less +1 -12
- package/src/client/assets/css/utils/medias.less +2 -15
- package/src/client/components/Dialog/index.less +2 -0
- package/src/client/components/Row/index.less +2 -0
- package/src/client/components/Video/index.less +6 -1
- package/src/client/components/containers/Popover/index.tsx +1 -2
- package/src/client/components/containers/Popover/popover.less +2 -0
- package/src/client/components/data/progressbar/index.less +2 -0
- package/src/client/components/inputv3/base.less +6 -0
- package/src/client/components/inputv3/file/index.less +2 -0
- package/src/client/pages/_messages/401.tsx +1 -2
- package/src/client/services/router/components/Page.tsx +1 -0
- package/src/client/services/router/components/router.tsx +24 -14
- package/src/client/services/router/index.tsx +11 -6
- package/src/client/services/router/request/api.ts +6 -3
- package/src/client/services/router/response/index.tsx +20 -15
- package/src/client/services/router/response/page.ts +1 -1
- package/src/common/router/index.ts +41 -0
- package/src/common/router/request/api.ts +2 -0
- package/src/common/router/response/index.ts +2 -2
- package/src/common/router/response/page.ts +1 -0
- package/src/server/app/index.ts +0 -1
- package/src/server/app/service/index.ts +12 -3
- package/src/server/services/router/http/index.ts +5 -7
- package/src/server/services/router/index.ts +9 -10
- package/src/server/services/router/request/api.ts +7 -2
- package/src/server/services/router/response/index.ts +12 -11
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.3.
|
|
4
|
+
"version": "0.3.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",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"cookie-parser": "^1.4.5",
|
|
29
29
|
"core-js": "^3.18.1",
|
|
30
30
|
"cors": "^2.8.5",
|
|
31
|
-
"cron-parser": "^4.0.0",
|
|
31
|
+
"cron-parser": "^4.0.0",
|
|
32
32
|
"dayjs": "^1.11.5",
|
|
33
33
|
"deep-extend": "^0.6.0",
|
|
34
34
|
"dottie": "^2.0.2",
|
|
@@ -14,6 +14,9 @@ import DialogManager from '@client/components/Dialog/Manager'
|
|
|
14
14
|
import RouterComponent from '@client/services/router/components/router';
|
|
15
15
|
import type { TClientOrServerContext } from '@common/router';
|
|
16
16
|
|
|
17
|
+
// Resources
|
|
18
|
+
import '@client/assets/css/core.less';
|
|
19
|
+
|
|
17
20
|
/*----------------------------------
|
|
18
21
|
- COMPOSANT
|
|
19
22
|
----------------------------------*/
|
|
@@ -27,16 +27,17 @@
|
|
|
27
27
|
font-weight: 600;
|
|
28
28
|
|
|
29
29
|
// Colors
|
|
30
|
+
background: var(--cBg);
|
|
30
31
|
color: var(--cTxtAccent);
|
|
31
32
|
|
|
32
33
|
// Hover
|
|
33
|
-
transition: all .5s linear;
|
|
34
|
+
//transition: all .5s linear;
|
|
34
35
|
&:hover,
|
|
35
36
|
&.selected,
|
|
36
37
|
li:hover > & {
|
|
37
38
|
|
|
38
39
|
color: var(--cTxtImportant);
|
|
39
|
-
transition: all .1s linear;
|
|
40
|
+
//transition: all .1s linear;
|
|
40
41
|
|
|
41
42
|
> i {
|
|
42
43
|
color: var(--cAccent)
|
|
@@ -63,8 +64,8 @@
|
|
|
63
64
|
text-align: left;
|
|
64
65
|
white-space: nowrap; // Autrement, si plusieurs mots, affiché sur plusieurs ligne
|
|
65
66
|
gap: @spacing / 2;
|
|
66
|
-
min-width: 4em;
|
|
67
67
|
font-size: 1rem;
|
|
68
|
+
line-height: 1.5em;
|
|
68
69
|
z-index: 1; // Make the label on top of ::before for example
|
|
69
70
|
|
|
70
71
|
li > & {
|
|
@@ -99,7 +100,7 @@
|
|
|
99
100
|
&:not(:disabled) {
|
|
100
101
|
&:hover,
|
|
101
102
|
li:hover > & {
|
|
102
|
-
background: var(--
|
|
103
|
+
background: var(--cBgActive);
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
|
|
@@ -220,7 +221,7 @@ ul.row {
|
|
|
220
221
|
|
|
221
222
|
&.selected,
|
|
222
223
|
&:hover {
|
|
223
|
-
background: var(--
|
|
224
|
+
background: var(--cBgActive);
|
|
224
225
|
color: var(--cTxtImportant);
|
|
225
226
|
}
|
|
226
227
|
|
|
@@ -145,28 +145,6 @@
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
.msg, .card.bg.info {
|
|
149
|
-
|
|
150
|
-
padding: @spacing @cardPaddingLong;
|
|
151
|
-
|
|
152
|
-
.build-theme-bg( lighten(@cInfo, 35%), darken(@cInfo, 20%));
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.msg, .card.bg {
|
|
157
|
-
&.error {
|
|
158
|
-
.build-theme-bg( lighten(@cInfo, 35%), darken(@cInfo, 20%));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
&.warn {
|
|
162
|
-
.build-theme-bg( lighten(@cWarn, 25%), darken(@cWarn, 30%));
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
&.success {
|
|
166
|
-
.build-theme-bg( lighten(@cSuccess, 50%), darken(@cSuccess, 30%));
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
148
|
iframe {
|
|
171
149
|
border: none;
|
|
172
150
|
}
|
|
@@ -23,31 +23,6 @@
|
|
|
23
23
|
@import './utils/layouts.less';
|
|
24
24
|
@import './utils/medias.less';
|
|
25
25
|
|
|
26
|
-
.white-card() {
|
|
27
|
-
background: white;
|
|
28
|
-
box-shadow: 0 3px 2px fade(#000, 10%);
|
|
29
|
-
border: solid 1px fade(#000, 10%);
|
|
30
|
-
border-radius: @radius;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.card,
|
|
34
|
-
.btn,
|
|
35
|
-
.input.text,
|
|
36
|
-
.input.select,
|
|
37
|
-
i.solid {
|
|
38
|
-
|
|
39
|
-
.build-theme-bg( #fff, #8E8E8E);
|
|
40
|
-
|
|
41
|
-
&:not(.bg) {
|
|
42
|
-
.white-card();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.bg & {
|
|
46
|
-
box-shadow: none;
|
|
47
|
-
border: none;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
26
|
.input.text {
|
|
52
27
|
&.error {
|
|
53
28
|
border-color: @cError;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import (reference) '@/client/assets/vars.less';
|
|
2
|
+
|
|
1
3
|
// Utils
|
|
2
4
|
@import './utils/sizing.less';
|
|
3
5
|
@import './utils/spacing.less';
|
|
@@ -18,16 +20,41 @@
|
|
|
18
20
|
background-repeat: no-repeat;
|
|
19
21
|
background-position: center;
|
|
20
22
|
|
|
21
|
-
.build-theme-bg(#000, #fff);
|
|
22
23
|
text-shadow: 0 0 10px fade(#000, 20%);
|
|
23
24
|
|
|
25
|
+
.apply-theme({
|
|
26
|
+
background: #000;
|
|
27
|
+
foreground: #fff;
|
|
28
|
+
accent1: #fff;
|
|
29
|
+
accent2: #fff;
|
|
30
|
+
}, {
|
|
31
|
+
background: #111;
|
|
32
|
+
foreground: #fff;
|
|
33
|
+
accent1: #fff;
|
|
34
|
+
accent2: #fff;
|
|
35
|
+
});
|
|
36
|
+
|
|
24
37
|
--cTxtDiscret: fade(#fff, 40%)
|
|
25
38
|
--cTxtDesc: fade(#fff, 60%);
|
|
26
39
|
--cTxtBase: fade(#fff, 80%);
|
|
27
40
|
--cTxtImportant: fade(#fff, 100%);
|
|
28
|
-
--cTxtAccent:
|
|
41
|
+
--cTxtAccent: #FFF;
|
|
29
42
|
|
|
30
43
|
&.light {
|
|
44
|
+
.apply-theme({
|
|
45
|
+
background: #fff;
|
|
46
|
+
foreground: #000;
|
|
47
|
+
accent1: #000;
|
|
48
|
+
accent2: #000;
|
|
49
|
+
},{
|
|
50
|
+
background: #fff;
|
|
51
|
+
foreground: #000;
|
|
52
|
+
accent1: #000;
|
|
53
|
+
accent2: #000;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
text-shadow: none;
|
|
57
|
+
|
|
31
58
|
--cTxtDiscret: fade(#000, 40%)
|
|
32
59
|
--cTxtDesc: fade(#000, 60%);
|
|
33
60
|
--cTxtBase: fade(#000, 80%);
|
|
@@ -55,6 +82,33 @@
|
|
|
55
82
|
position: relative;
|
|
56
83
|
}
|
|
57
84
|
}
|
|
85
|
+
|
|
86
|
+
&.faded {
|
|
87
|
+
position: relative;
|
|
88
|
+
|
|
89
|
+
> * {
|
|
90
|
+
z-index: 1;
|
|
91
|
+
position: relative;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&:after {
|
|
95
|
+
content: ' ';
|
|
96
|
+
display: block;
|
|
97
|
+
|
|
98
|
+
z-index: 0;
|
|
99
|
+
position: absolute;
|
|
100
|
+
bottom: 0px;
|
|
101
|
+
left: 0px;
|
|
102
|
+
right: 0px;
|
|
103
|
+
|
|
104
|
+
height: 85%;
|
|
105
|
+
background: linear-gradient(
|
|
106
|
+
to bottom,
|
|
107
|
+
transparent,
|
|
108
|
+
#fff
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
58
112
|
}
|
|
59
113
|
|
|
60
114
|
&.bg-cover {
|
|
@@ -106,4 +160,9 @@ body {
|
|
|
106
160
|
-webkit-font-smoothing: antialiased;
|
|
107
161
|
-moz-osx-font-smoothing: grayscale;
|
|
108
162
|
|
|
109
|
-
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Import components style (always after variables declaration)
|
|
166
|
+
@import "@client/assets/css/components.less";
|
|
167
|
+
|
|
168
|
+
@import '@/client/assets/theme.less';
|
|
@@ -201,11 +201,7 @@ pre {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
code {
|
|
204
|
-
background: @c3;
|
|
205
|
-
border-top: solid 1px @c3 - #111;
|
|
206
|
-
border-bottom: solid 1px @c3 - #111;
|
|
207
204
|
padding: @spacing @readingMargin;
|
|
208
|
-
color: @c3 + #888;
|
|
209
205
|
text-align: left;
|
|
210
206
|
font-size: 1rem;
|
|
211
207
|
line-height: 2em;
|
|
@@ -1,67 +1,97 @@
|
|
|
1
1
|
@bgDelta: #111;
|
|
2
2
|
@fgDelta: #999;
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
@bg,
|
|
6
|
-
@fg: if( lightness(@bg) >= 80%, @bg - @fgDelta, @bg + @fgDelta),
|
|
7
|
-
@cAccent: @c1,
|
|
8
|
-
@cAccent2: @c2,
|
|
9
|
-
@apply: true
|
|
10
|
-
) {
|
|
4
|
+
/* Theme structure:
|
|
11
5
|
|
|
12
|
-
|
|
6
|
+
background?: COLOR;
|
|
7
|
+
foreground: COLOR;
|
|
8
|
+
accent1: COLOR;
|
|
9
|
+
accent2: COLOR;
|
|
10
|
+
*/
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
.apply-theme( @theme, @componentsTheme: false, @apply: true ) {
|
|
13
|
+
|
|
14
|
+
.apply-theme-style( @theme, @componentsTheme, @apply );
|
|
15
|
+
|
|
16
|
+
// Children Components
|
|
17
|
+
& when not (@componentsTheme = false) {
|
|
18
|
+
// Default theming for the children components
|
|
19
|
+
@{componentsSelector} {
|
|
20
|
+
// Don't apply the default theme to already themed components
|
|
21
|
+
&:not(.bg) {
|
|
22
|
+
.apply-theme-style( @componentsTheme, false, false );
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.apply-theme-style( @theme, @componentsTheme: false, @apply: true ) {
|
|
29
|
+
|
|
30
|
+
@isLight: boolean( lightness( @bg ) >= 80% );
|
|
31
|
+
|
|
32
|
+
// Flags
|
|
33
|
+
@bg: @theme[background];
|
|
34
|
+
@bgActive: if( @isLight,
|
|
35
|
+
@bg - #111,
|
|
36
|
+
@bg + #111,
|
|
37
|
+
);
|
|
38
|
+
@fg: @theme[foreground];
|
|
16
39
|
|
|
17
40
|
// Background
|
|
18
|
-
// TODO: Nettoyer
|
|
19
|
-
@bg2: darken(@bg, 8%);
|
|
20
|
-
@bgDark: darken(@bg, 7%);
|
|
21
|
-
@bgDarkPlus: darken(@bg, 10%);
|
|
22
41
|
--cBg: @bg;
|
|
23
|
-
--
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
--cBgHover: #f5f5f5;
|
|
42
|
+
--cBgActive: @bgActive;
|
|
43
|
+
& when (@apply = true) {
|
|
44
|
+
background: var(--cBg);
|
|
45
|
+
}
|
|
28
46
|
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
// Accent
|
|
48
|
+
& when (@theme[accent1]) {
|
|
49
|
+
--cAccent: @theme[accent1];
|
|
50
|
+
}
|
|
51
|
+
& when (@theme[accent2]) {
|
|
52
|
+
--cAccent2: @theme[accent2];
|
|
53
|
+
}
|
|
31
54
|
|
|
32
|
-
//
|
|
55
|
+
// Lines
|
|
33
56
|
@cLine: if( @isLight, @bg - #151515, @bg + #151515);
|
|
34
|
-
@cLine2: if( @isLight, @bg - #191919, @bg + #050505);
|
|
35
57
|
--cLine: @cLine;
|
|
58
|
+
@cLine2: if( @isLight, @bg - #191919, @bg + #050505);
|
|
36
59
|
--cLine2: @cLine2;
|
|
37
60
|
|
|
38
61
|
// Text
|
|
39
|
-
.build-theme-fg(@fg, @bg, @
|
|
62
|
+
.build-theme-fg(@fg, @bg, @theme[accent1], @theme[accent2]);
|
|
40
63
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/*.input.text {
|
|
45
|
-
border: if(@bg = #ffffff, solid 2px var(--cLine), none);
|
|
46
|
-
}*/
|
|
47
|
-
}
|
|
64
|
+
// TO REMOVE
|
|
65
|
+
--cBgControl: if( @bg = #fff, @cBgPage, #fff );
|
|
66
|
+
--cBgHover: #f5f5f5;
|
|
48
67
|
}
|
|
49
68
|
|
|
50
|
-
.build-theme-fg(
|
|
69
|
+
.build-theme-fg(
|
|
70
|
+
@cTxtBase,
|
|
71
|
+
@bg: @cBgPage,
|
|
72
|
+
@cAccent: false,
|
|
73
|
+
@cAccent2: false
|
|
74
|
+
) {
|
|
51
75
|
|
|
52
76
|
@isLight: boolean( lightness(@bg) >= 70% );
|
|
77
|
+
|
|
78
|
+
// Base
|
|
79
|
+
--cTxtBase: @cTxtBase;
|
|
53
80
|
|
|
54
|
-
//
|
|
81
|
+
// Discret
|
|
55
82
|
@cTxtDiscret: if( @isLight, @bg - #444, @bg + #444);
|
|
56
|
-
@cTxtDesc: if( @isLight, @cTxtBase + #222, @cTxtBase - #222);
|
|
57
|
-
@cTxtImportant: if( @isLight, @cDark, #fff);
|
|
58
|
-
@cTxtAccent: if( @bg = @c1, @cTxtImportant, @c1);
|
|
59
|
-
|
|
60
83
|
--cTxtDiscret: @cTxtDiscret;
|
|
84
|
+
|
|
85
|
+
// Desc
|
|
86
|
+
@cTxtDesc: if( @isLight, @cTxtBase + #222, @cTxtBase - #222);
|
|
61
87
|
--cTxtDesc: @cTxtDesc;
|
|
62
|
-
|
|
88
|
+
|
|
89
|
+
// Important
|
|
90
|
+
@cTxtImportant: if( @isLight, @cDark, #fff);
|
|
63
91
|
--cTxtImportant: @cTxtImportant;
|
|
64
|
-
|
|
92
|
+
|
|
93
|
+
// Accent
|
|
94
|
+
--cTxtAccent: @cAccent;
|
|
65
95
|
|
|
66
96
|
color: var(--cTxtBase);
|
|
67
97
|
}
|
|
@@ -47,17 +47,6 @@
|
|
|
47
47
|
> li {
|
|
48
48
|
position: relative;
|
|
49
49
|
|
|
50
|
-
> a:hover::before {
|
|
51
|
-
content: ' ';
|
|
52
|
-
position: absolute;
|
|
53
|
-
top: 0; left: 0; bottom: 0; right: 0;
|
|
54
|
-
margin: 0 0.3em;
|
|
55
|
-
background: var(--cBgAccent);
|
|
56
|
-
border-radius: @radius;
|
|
57
|
-
z-index: 0;
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
50
|
> a > * {
|
|
62
51
|
z-index: 1;
|
|
63
52
|
position: relative;
|
|
@@ -298,7 +287,7 @@
|
|
|
298
287
|
&.y-@{i} {
|
|
299
288
|
/*@t_zones: (@L_max) / @i;
|
|
300
289
|
@espace_zone: ~"repeat(@{i}, minmax(@{L_min}, @{t_zones}))";*/
|
|
301
|
-
@espace_zone: ~"repeat(@{i},
|
|
290
|
+
@espace_zone: ~"repeat(@{i}, min-content)";
|
|
302
291
|
.construire(@espace_zone, row);
|
|
303
292
|
}
|
|
304
293
|
|
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
img.img,
|
|
3
|
-
.bg.img {
|
|
4
|
-
background: @cBgPage - #101010;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
1
|
img {
|
|
8
2
|
max-width: 100%;
|
|
9
3
|
display: block;
|
|
10
4
|
|
|
11
5
|
&.img {
|
|
12
6
|
border-radius: @radius;
|
|
7
|
+
object-fit: cover;
|
|
8
|
+
object-position: center;
|
|
13
9
|
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/*----------------------------------
|
|
17
|
-
- IMAGE CONFIG
|
|
18
|
-
----------------------------------*/
|
|
19
|
-
|
|
20
|
-
img.img {
|
|
21
|
-
object-fit: cover;
|
|
22
|
-
object-position: center;
|
|
23
10
|
}
|
|
@@ -102,12 +102,11 @@ export default (props: Props) => {
|
|
|
102
102
|
let renderedContent: ComponentChild;
|
|
103
103
|
if (active) {
|
|
104
104
|
//content = typeof content === 'function' ? React.createElement(content) : content;
|
|
105
|
-
console.log("render content", content);
|
|
106
105
|
renderedContent = React.cloneElement(
|
|
107
106
|
content,
|
|
108
107
|
{
|
|
109
108
|
className: (content.props.className || '')
|
|
110
|
-
+ ' card
|
|
109
|
+
+ ' card popover pd-1'
|
|
111
110
|
+ (position ? ' pos_' + position.cote : ''),
|
|
112
111
|
|
|
113
112
|
ref: (ref: any) => {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import (reference) "@/client/assets/vars.less";
|
|
2
|
+
|
|
1
3
|
@hInput: @sizeComponent;
|
|
2
4
|
@labelH: 0.6em;
|
|
3
5
|
|
|
@@ -14,6 +16,9 @@
|
|
|
14
16
|
&.text,
|
|
15
17
|
&.select {
|
|
16
18
|
|
|
19
|
+
background: var(--cBg);
|
|
20
|
+
border-radius: @radius;
|
|
21
|
+
|
|
17
22
|
> i {
|
|
18
23
|
color: var(--cTxtDesc);
|
|
19
24
|
}
|
|
@@ -108,6 +113,7 @@
|
|
|
108
113
|
width: 100%;
|
|
109
114
|
padding: @labelH 0 0 0;
|
|
110
115
|
font-size: 1rem;
|
|
116
|
+
color: inherit;
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
input {
|
|
@@ -21,7 +21,7 @@ import useHeader from '@client/pages/useHeader';
|
|
|
21
21
|
----------------------------------*/
|
|
22
22
|
Router.error( 401, ({ message, request, page }) => {
|
|
23
23
|
|
|
24
|
-
request.response?.redirect('
|
|
24
|
+
request.response?.redirect('https://becrosspath.com');
|
|
25
25
|
|
|
26
26
|
useHeader({
|
|
27
27
|
title: 'Authentication Required',
|
|
@@ -32,7 +32,6 @@ Router.error( 401, ({ message, request, page }) => {
|
|
|
32
32
|
|
|
33
33
|
page?.go('/');
|
|
34
34
|
|
|
35
|
-
|
|
36
35
|
}, []);
|
|
37
36
|
|
|
38
37
|
return (
|
|
@@ -44,6 +44,13 @@ const PageLoading = ({ clientRouter }: { clientRouter?: ClientRouter }) => {
|
|
|
44
44
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
const scrollToElement = (selector: string) => document.querySelector( selector )
|
|
48
|
+
?.scrollIntoView({
|
|
49
|
+
behavior: "smooth",
|
|
50
|
+
block: "start",
|
|
51
|
+
inline: "nearest"
|
|
52
|
+
})
|
|
53
|
+
|
|
47
54
|
/*----------------------------------
|
|
48
55
|
- COMPONENT
|
|
49
56
|
----------------------------------*/
|
|
@@ -72,15 +79,25 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
|
|
|
72
79
|
|
|
73
80
|
if (!clientRouter) return;
|
|
74
81
|
|
|
82
|
+
const currentRequest = context.request;
|
|
83
|
+
context.request = request;
|
|
84
|
+
|
|
75
85
|
// WARNING: Don"t try to play with pages here, since the object will not be updated
|
|
76
86
|
// If needed to play with pages, do it in the setPages callback below
|
|
87
|
+
// Unchanged path
|
|
88
|
+
if (request.path === currentRequest.path) {
|
|
89
|
+
|
|
90
|
+
// Scroll to component
|
|
91
|
+
if (request.hash) {
|
|
92
|
+
scrollToElement(request.hash);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
77
97
|
|
|
78
98
|
// Set loading state
|
|
79
99
|
clientRouter.setLoading(true);
|
|
80
|
-
|
|
81
|
-
// Load the route chunks
|
|
82
|
-
context.request = request;
|
|
83
|
-
const newpage = await clientRouter.resolve(request);
|
|
100
|
+
const newpage = context.page = await clientRouter.resolve(request);
|
|
84
101
|
|
|
85
102
|
// Page not found: Directly load with the browser
|
|
86
103
|
if (newpage === undefined) {
|
|
@@ -93,7 +110,7 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
|
|
|
93
110
|
}
|
|
94
111
|
|
|
95
112
|
// Fetch API data to hydrate the page
|
|
96
|
-
const newData =
|
|
113
|
+
const newData = await newpage.fetchData();
|
|
97
114
|
|
|
98
115
|
// Add page container
|
|
99
116
|
setPages( pages => {
|
|
@@ -122,16 +139,12 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
|
|
|
122
139
|
context.app.setLayout(newLayout);
|
|
123
140
|
}
|
|
124
141
|
|
|
125
|
-
return
|
|
142
|
+
return { current: newpage }
|
|
126
143
|
});
|
|
127
144
|
}
|
|
128
145
|
|
|
129
146
|
const restoreScroll = (currentPage?: Page) => currentPage?.scrollToId
|
|
130
|
-
&&
|
|
131
|
-
behavior: "smooth",
|
|
132
|
-
block: "start",
|
|
133
|
-
inline: "nearest"
|
|
134
|
-
})
|
|
147
|
+
&& scrollToElement( currentPage.scrollToId.substring(1) )
|
|
135
148
|
|
|
136
149
|
// First render
|
|
137
150
|
React.useEffect(() => {
|
|
@@ -146,9 +159,6 @@ export default ({ service: clientRouter }: { service?: ClientRouter }) => {
|
|
|
146
159
|
// Load the concerned route
|
|
147
160
|
const request = new ClientRequest(locationUpdate.location, context.Router);
|
|
148
161
|
await resolvePage(request);
|
|
149
|
-
|
|
150
|
-
// Scroll to the selected content via url hash
|
|
151
|
-
//restoreScroll(pages.current);
|
|
152
162
|
})
|
|
153
163
|
}, []);
|
|
154
164
|
|
|
@@ -15,7 +15,9 @@ import type {
|
|
|
15
15
|
import type { TBasicSSrData } from '@server/services/router/response';
|
|
16
16
|
|
|
17
17
|
import BaseRouter, {
|
|
18
|
-
defaultOptions, TRoute, TErrorRoute,
|
|
18
|
+
defaultOptions, TRoute, TErrorRoute,
|
|
19
|
+
TClientOrServerContext, TRouteModule,
|
|
20
|
+
buildUrl, TDomainsList
|
|
19
21
|
} from '@common/router'
|
|
20
22
|
import { getLayout } from '@common/router/layouts';
|
|
21
23
|
import { getRegisterPageArgs, buildRegex } from '@common/router/register';
|
|
@@ -123,7 +125,8 @@ type THookName = 'location.change' | 'page.changed'
|
|
|
123
125
|
|
|
124
126
|
type Config<TAdditionnalContext extends {} = {}> = {
|
|
125
127
|
preload: string[], // List of globs
|
|
126
|
-
context: (router:
|
|
128
|
+
context: (context: ClientContext, router: ClientRouter) => TAdditionnalContext,
|
|
129
|
+
domains: TDomainsList
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
/*----------------------------------
|
|
@@ -153,9 +156,13 @@ export default class ClientRouter<
|
|
|
153
156
|
this.initialRender(currentRoute);
|
|
154
157
|
}
|
|
155
158
|
|
|
159
|
+
public url = (path: string, params: {} = {}, absolute: boolean = true) =>
|
|
160
|
+
buildUrl(path, params, this.config.domains, absolute);
|
|
161
|
+
|
|
156
162
|
public go( url: string ) {
|
|
163
|
+
url = this.url(url, {}, false);
|
|
157
164
|
console.log( LogPrefix, "Go to", url);
|
|
158
|
-
history?.replace(url);
|
|
165
|
+
history?.replace( url );
|
|
159
166
|
}
|
|
160
167
|
|
|
161
168
|
/*----------------------------------
|
|
@@ -371,8 +378,6 @@ export default class ClientRouter<
|
|
|
371
378
|
let apiData: {} = {}
|
|
372
379
|
if (this.ssrContext) {
|
|
373
380
|
|
|
374
|
-
console.log("SSR Response restitution ...");
|
|
375
|
-
|
|
376
381
|
request.user = this.ssrContext.user || null;
|
|
377
382
|
|
|
378
383
|
request.data = this.ssrContext.request.data;
|
|
@@ -382,7 +387,7 @@ export default class ClientRouter<
|
|
|
382
387
|
|
|
383
388
|
// Replacer api data par ssr data
|
|
384
389
|
|
|
385
|
-
const response = await this.createResponse(route, request, apiData)
|
|
390
|
+
const response = await this.createResponse(route, request, apiData);
|
|
386
391
|
|
|
387
392
|
ReactDOM.hydrate((
|
|
388
393
|
<App context={response.context} />
|
|
@@ -66,7 +66,6 @@ export default class ApiClient implements ApiClientService {
|
|
|
66
66
|
public delete = <TData extends unknown = unknown>(path: string, data?: TPostData, opts?: TApiFetchOptions) =>
|
|
67
67
|
this.createFetcher<TData>('DELETE', path, data, opts);
|
|
68
68
|
|
|
69
|
-
|
|
70
69
|
public set( newData: TObjetDonnees ) {
|
|
71
70
|
|
|
72
71
|
if (!('context' in this.router))
|
|
@@ -174,12 +173,15 @@ export default class ApiClient implements ApiClientService {
|
|
|
174
173
|
public configure = (...[method, path, data, options]: TFetcherArgs): AxiosRequestConfig => {
|
|
175
174
|
|
|
176
175
|
const { onProgress, captcha } = options || {};
|
|
176
|
+
|
|
177
|
+
const url = this.router.url( path, {}, false );
|
|
177
178
|
|
|
178
|
-
debug && console.log(`[api] Sending request`, method,
|
|
179
|
+
debug && console.log(`[api] Sending request`, method, url, data);
|
|
179
180
|
|
|
181
|
+
// Create AXIOS config
|
|
180
182
|
const config: AxiosRequestConfig = {
|
|
181
183
|
|
|
182
|
-
url
|
|
184
|
+
url,
|
|
183
185
|
method: method,
|
|
184
186
|
headers: {
|
|
185
187
|
'Content-Type': "application/json",
|
|
@@ -197,6 +199,7 @@ export default class ApiClient implements ApiClientService {
|
|
|
197
199
|
|
|
198
200
|
};
|
|
199
201
|
|
|
202
|
+
// Format request data
|
|
200
203
|
if (data) {
|
|
201
204
|
// URL params
|
|
202
205
|
if (method === "GET") {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import type ServerRouter from '@server/services/router';
|
|
7
7
|
import type ServerResponse from '@server/services/router/response';
|
|
8
8
|
|
|
9
|
-
import type {
|
|
10
|
-
import BaseResponse from '@common/router/response';
|
|
9
|
+
import type { TAnyRoute, TErrorRoute } from '@common/router';
|
|
10
|
+
import BaseResponse, { TResponseData } from '@common/router/response';
|
|
11
11
|
|
|
12
12
|
import type ClientApplication from '@client/app';
|
|
13
13
|
import type { default as ClientRouter } from '@client/services/router'
|
|
@@ -15,7 +15,6 @@ import type ClientResponse from '@client/services/router/response'
|
|
|
15
15
|
import ClientRequest from '@client/services/router/request'
|
|
16
16
|
import ClientPage from '@client/services/router/response/page'
|
|
17
17
|
import { history } from '@client/services/router/request/history';
|
|
18
|
-
import CommonPage from '@common/router/response/page';
|
|
19
18
|
|
|
20
19
|
/*----------------------------------
|
|
21
20
|
- TYPES
|
|
@@ -34,12 +33,11 @@ export type TRouterContext<
|
|
|
34
33
|
// ClientPage context
|
|
35
34
|
{
|
|
36
35
|
app: TApplication,
|
|
37
|
-
context: TRouterContext<TRouter, TApplication>,
|
|
38
36
|
request: ClientRequest<TRouter>,
|
|
39
|
-
route:
|
|
37
|
+
route: TAnyRoute<TRouterContext>,
|
|
40
38
|
api: ClientRequest<TRouter>["api"],
|
|
41
39
|
page: ClientPage<TRouter>,
|
|
42
|
-
|
|
40
|
+
data: TObjetDonnees
|
|
43
41
|
}
|
|
44
42
|
&
|
|
45
43
|
// Expose client application services (api, socket, ...)
|
|
@@ -61,7 +59,7 @@ export default class ClientPageResponse<
|
|
|
61
59
|
|
|
62
60
|
public constructor(
|
|
63
61
|
public request: ClientRequest<TRouter>,
|
|
64
|
-
public route:
|
|
62
|
+
public route: TAnyRoute | TErrorRoute,
|
|
65
63
|
|
|
66
64
|
public app = request.app,
|
|
67
65
|
) {
|
|
@@ -83,24 +81,31 @@ export default class ClientPageResponse<
|
|
|
83
81
|
|
|
84
82
|
// Router context
|
|
85
83
|
app: this.app,
|
|
86
|
-
context: undefined as unknown as TRouterContext<TRouter, TRouter["app"]>,
|
|
87
84
|
request: this.request,
|
|
88
85
|
route: this.route,
|
|
89
86
|
api: this.request.api,
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
// Will be assigned when the controller will be runned
|
|
88
|
+
page: undefined as unknown as ClientPage<TRouter>,
|
|
89
|
+
data: {},
|
|
92
90
|
}
|
|
93
91
|
|
|
94
|
-
const
|
|
92
|
+
const newContext: TRouterContext<TRouter, TRouter["app"]> = {
|
|
95
93
|
...basicContext,
|
|
96
|
-
|
|
97
94
|
// Custom context
|
|
98
|
-
...this.request.router.config.context( basicContext )
|
|
95
|
+
...this.request.router.config.context( basicContext, this.request.router )
|
|
99
96
|
}
|
|
100
97
|
|
|
101
|
-
|
|
98
|
+
// Update context object if already exists
|
|
99
|
+
// NOTE: we don't create a nex instance of context because we don't want to rereder the full page (inc layout) to update the context given by thr react context provider
|
|
100
|
+
const existingContext = this.request.router.context;
|
|
101
|
+
if (existingContext === undefined) {
|
|
102
|
+
|
|
103
|
+
this.request.router.context = newContext
|
|
104
|
+
|
|
105
|
+
} else for(const key in newContext)
|
|
106
|
+
existingContext[ key ] = newContext[ key ];
|
|
102
107
|
|
|
103
|
-
return
|
|
108
|
+
return newContext
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
public async runController( additionnalData: {} = {} ): Promise<ClientPage> {
|
|
@@ -45,7 +45,7 @@ export default class ClientPage<TRouter = ClientRouter> extends PageResponse<TRo
|
|
|
45
45
|
this.context.page = this;
|
|
46
46
|
|
|
47
47
|
// Data succesfully loaded
|
|
48
|
-
this.data = data || await this.fetchData();
|
|
48
|
+
this.context.data = this.data = data || await this.fetchData();
|
|
49
49
|
|
|
50
50
|
return this;
|
|
51
51
|
}
|
|
@@ -107,10 +107,51 @@ export type TRouteModule<TRegisteredRoute = any> = {
|
|
|
107
107
|
__register?: TAppArrowFunction<TRegisteredRoute>
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
export type TDomainsList = {
|
|
111
|
+
[endpointId: string]: string
|
|
112
|
+
} & {
|
|
113
|
+
current: string
|
|
114
|
+
}
|
|
115
|
+
|
|
110
116
|
export const defaultOptions = {
|
|
111
117
|
priority: 0,
|
|
112
118
|
}
|
|
113
119
|
|
|
120
|
+
/*----------------------------------
|
|
121
|
+
- FUNCTIONS
|
|
122
|
+
----------------------------------*/
|
|
123
|
+
export const buildUrl = (
|
|
124
|
+
path: string,
|
|
125
|
+
params: {} = {},
|
|
126
|
+
domains: TDomainsList,
|
|
127
|
+
absolute: boolean
|
|
128
|
+
) => {
|
|
129
|
+
|
|
130
|
+
// Relative to domain
|
|
131
|
+
if (path[0] === '/' && absolute)
|
|
132
|
+
return domains.current + path;
|
|
133
|
+
// Other domains of the project
|
|
134
|
+
else if (path[0] === '@') {
|
|
135
|
+
|
|
136
|
+
// Extract domain ID from path
|
|
137
|
+
let domainId: string;
|
|
138
|
+
const slackPos = path.indexOf('/');
|
|
139
|
+
domainId = path.substring(1, slackPos);
|
|
140
|
+
path = path.substring(slackPos);
|
|
141
|
+
|
|
142
|
+
// Get domain
|
|
143
|
+
const domain = domains[ domainId ];
|
|
144
|
+
if (domain === undefined)
|
|
145
|
+
throw new Error("Unknown API endpoint ID: " + domainId);
|
|
146
|
+
|
|
147
|
+
// Return full url
|
|
148
|
+
return domain + path;
|
|
149
|
+
|
|
150
|
+
// Absolute URL
|
|
151
|
+
} else
|
|
152
|
+
return path;
|
|
153
|
+
}
|
|
154
|
+
|
|
114
155
|
/*----------------------------------
|
|
115
156
|
- BASE ROUTER
|
|
116
157
|
----------------------------------*/
|
|
@@ -72,6 +72,8 @@ export default abstract class ApiClient {
|
|
|
72
72
|
|
|
73
73
|
public abstract delete<TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions): TFetcher<TData>;
|
|
74
74
|
|
|
75
|
+
public abstract set( newData: TObjetDonnees );
|
|
76
|
+
|
|
75
77
|
/*----------------------------------
|
|
76
78
|
- LOW LEVEL
|
|
77
79
|
----------------------------------*/
|
|
@@ -7,14 +7,14 @@ import { FunctionalComponent } from "preact";
|
|
|
7
7
|
|
|
8
8
|
// Core
|
|
9
9
|
import { TAnyRoute } from "..";
|
|
10
|
-
import type ClientRequest from '@client/services/router';
|
|
10
|
+
import type ClientRequest from '@client/services/router/request';
|
|
11
11
|
import Page from '@client/services/router/response/page'
|
|
12
12
|
|
|
13
13
|
/*----------------------------------
|
|
14
14
|
- TYPES
|
|
15
15
|
----------------------------------*/
|
|
16
16
|
|
|
17
|
-
type TResponseData = Page
|
|
17
|
+
export type TResponseData = Page
|
|
18
18
|
|
|
19
19
|
/*----------------------------------
|
|
20
20
|
- CONTEXT
|
package/src/server/app/index.ts
CHANGED
|
@@ -41,6 +41,10 @@ export type StartedServicesIndex = {
|
|
|
41
41
|
[serviceId: string]: AnyService
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
type TServiceUseOptions = {
|
|
45
|
+
optional?: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
44
48
|
/*----------------------------------
|
|
45
49
|
- CONFIG
|
|
46
50
|
----------------------------------*/
|
|
@@ -147,7 +151,8 @@ export default abstract class Service<
|
|
|
147
151
|
TSubServices extends TServiceClass["services"],
|
|
148
152
|
>(
|
|
149
153
|
serviceId: TServiceId,
|
|
150
|
-
subServices?: TSubServices
|
|
154
|
+
subServices?: TSubServices,
|
|
155
|
+
serviceUseOptions: TServiceUseOptions = {}
|
|
151
156
|
): (
|
|
152
157
|
// We can't pass the services types as a generic to TServiceClass
|
|
153
158
|
// So we overwrite the services property
|
|
@@ -164,8 +169,12 @@ export default abstract class Service<
|
|
|
164
169
|
|
|
165
170
|
// Check of the service has been configurated
|
|
166
171
|
const registered = ServicesContainer.registered[ serviceId ];
|
|
167
|
-
if (registered === undefined)
|
|
168
|
-
|
|
172
|
+
if (registered === undefined) {
|
|
173
|
+
if (serviceUseOptions.optional)
|
|
174
|
+
return undefined;
|
|
175
|
+
else
|
|
176
|
+
throw new Error(`Unable to use service "${serviceId}": This one hasn't been setup.`);
|
|
177
|
+
}
|
|
169
178
|
|
|
170
179
|
// Bind subservices
|
|
171
180
|
if (subServices !== undefined)
|
|
@@ -8,7 +8,7 @@ import express from 'express';
|
|
|
8
8
|
import http from 'http';
|
|
9
9
|
import https from 'https';
|
|
10
10
|
import path from 'path';
|
|
11
|
-
import cors from 'cors';
|
|
11
|
+
import cors, { CorsOptions } from 'cors';
|
|
12
12
|
//var serveStatic = require('serve-static')
|
|
13
13
|
|
|
14
14
|
// Middlewares (npm)
|
|
@@ -52,7 +52,8 @@ export type Config = {
|
|
|
52
52
|
styles?: string[],
|
|
53
53
|
images?: string[],
|
|
54
54
|
scripts: string[],
|
|
55
|
-
}
|
|
55
|
+
},
|
|
56
|
+
cors?: CorsOptions
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export type Hooks = {
|
|
@@ -200,11 +201,8 @@ export default class HttpServer {
|
|
|
200
201
|
- PAGES / API
|
|
201
202
|
----------------------------------*/
|
|
202
203
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// TODO: Trouver une solution pour n'autoriser les requetes que depuis l'application & dopamyn.io
|
|
206
|
-
// https://www.google.com/search?q=http+cors+from+android%7Cwindows%7Cdesktop%7Cmodile+app
|
|
207
|
-
//routes.use('/auth', cors());
|
|
204
|
+
if (this.config.cors !== undefined)
|
|
205
|
+
routes.use( cors( this.config.cors ));
|
|
208
206
|
|
|
209
207
|
routes.use( csp.expressCspHeader({
|
|
210
208
|
directives: {
|
|
@@ -24,7 +24,8 @@ import type DisksManager from '@server/services/disks';
|
|
|
24
24
|
import { CoreError, NotFound } from '@common/errors';
|
|
25
25
|
import BaseRouter, {
|
|
26
26
|
TRoute, TErrorRoute, TRouteModule,
|
|
27
|
-
TRouteOptions, defaultOptions
|
|
27
|
+
TRouteOptions, defaultOptions,
|
|
28
|
+
buildUrl, TDomainsList
|
|
28
29
|
} from '@common/router';
|
|
29
30
|
import { buildRegex, getRegisterPageArgs } from '@common/router/register';
|
|
30
31
|
import { layoutsList, getLayout } from '@common/router/layouts';
|
|
@@ -86,6 +87,8 @@ export type Config<
|
|
|
86
87
|
|
|
87
88
|
disk?: string, // Disk driver ID
|
|
88
89
|
|
|
90
|
+
domains: TDomainsList,
|
|
91
|
+
|
|
89
92
|
http: HttpServiceConfig
|
|
90
93
|
|
|
91
94
|
context: (
|
|
@@ -114,7 +117,9 @@ export default class ServerRouter<
|
|
|
114
117
|
TSubservices extends Services = Services
|
|
115
118
|
> extends Service<Config, Hooks, Application, TSubservices> implements BaseRouter {
|
|
116
119
|
|
|
117
|
-
public disks = this.use('Core/Disks'
|
|
120
|
+
public disks = this.use('Core/Disks', undefined, {
|
|
121
|
+
optional: true
|
|
122
|
+
}) as unknown as DisksManager | undefined;
|
|
118
123
|
|
|
119
124
|
// Services
|
|
120
125
|
public http: HTTP;
|
|
@@ -144,7 +149,6 @@ export default class ServerRouter<
|
|
|
144
149
|
|
|
145
150
|
this.http = new HTTP(config.http, this);
|
|
146
151
|
this.render = new DocumentRenderer(this);
|
|
147
|
-
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
/*----------------------------------
|
|
@@ -205,13 +209,8 @@ export default class ServerRouter<
|
|
|
205
209
|
this.afterRegister();
|
|
206
210
|
}
|
|
207
211
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
path: TRoutePath,
|
|
211
|
-
params: Routes[TRoutePath]["params"]
|
|
212
|
-
) {
|
|
213
|
-
return this.http.publicUrl + path;
|
|
214
|
-
}
|
|
212
|
+
public url = (path: string, params: {} = {}, absolute: boolean = true) =>
|
|
213
|
+
buildUrl(path, params, this.config.domains, absolute);
|
|
215
214
|
|
|
216
215
|
/*----------------------------------
|
|
217
216
|
- REGISTER
|
|
@@ -47,6 +47,10 @@ export default class ApiClientRequest extends RequestService implements ApiClien
|
|
|
47
47
|
public delete = <TData extends unknown = unknown>(path: string, data?: TObjetDonnees, opts?: TApiFetchOptions) =>
|
|
48
48
|
this.createFetcher<TData>('DELETE', path, data, opts);
|
|
49
49
|
|
|
50
|
+
public set( newData: TObjetDonnees ) {
|
|
51
|
+
throw new Error("api.set is not available on server side.");
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
/*----------------------------------
|
|
51
55
|
- API CALLS FROM SERVER
|
|
52
56
|
----------------------------------*/
|
|
@@ -65,10 +69,11 @@ export default class ApiClientRequest extends RequestService implements ApiClien
|
|
|
65
69
|
|
|
66
70
|
for (const id in fetchers) {
|
|
67
71
|
|
|
68
|
-
|
|
72
|
+
const fetcher = fetchers[id]
|
|
73
|
+
if (!fetcher)
|
|
69
74
|
continue;
|
|
70
75
|
|
|
71
|
-
const { method, path, data, options } =
|
|
76
|
+
const { method, path, data, options } = fetcher;
|
|
72
77
|
//this.router.config.debug && console.log(`[api] Resolving from internal api`, method, path, data);
|
|
73
78
|
|
|
74
79
|
// We don't fetch the already given data
|
|
@@ -245,17 +245,17 @@ export default class ServerResponse<
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
// TODO: https://github.com/adonisjs/http-server/blob/develop/src/Response/index.ts#L430
|
|
248
|
-
public async file(
|
|
248
|
+
public async file( filename: string ) {
|
|
249
249
|
|
|
250
250
|
// Securité
|
|
251
|
-
if (
|
|
251
|
+
if (filename.includes('..'))
|
|
252
252
|
throw new Forbidden("Disallowed");
|
|
253
253
|
|
|
254
254
|
// // Force absolute path
|
|
255
|
-
// if (!
|
|
256
|
-
//
|
|
257
|
-
// ? this.app.path.root + '/bin' +
|
|
258
|
-
// : this.app.path.data + '/' +
|
|
255
|
+
// if (!filename.startsWith( this.app.path.root ))
|
|
256
|
+
// filename = filename[0] === '/'
|
|
257
|
+
// ? this.app.path.root + '/bin' + filename
|
|
258
|
+
// : this.app.path.data + '/' + filename;
|
|
259
259
|
// Disk not provided = file response disabled
|
|
260
260
|
if (this.router.disks === undefined)
|
|
261
261
|
throw new Anomaly("Router: Unable to return file response in router, because no disk has been given in the router config.");
|
|
@@ -264,14 +264,15 @@ export default class ServerResponse<
|
|
|
264
264
|
const disk = this.router.disks.get('default');
|
|
265
265
|
|
|
266
266
|
// Verif existance
|
|
267
|
-
const fileExists = await disk.exists('data',
|
|
267
|
+
const fileExists = await disk.exists('data', filename);
|
|
268
268
|
if (!fileExists) {
|
|
269
|
-
console.log("File " +
|
|
269
|
+
console.log("File " + filename + " was not found.");
|
|
270
270
|
throw new NotFound();
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
// envoi
|
|
274
|
-
|
|
273
|
+
// envoi filename
|
|
274
|
+
const file = await disk.readFile('data', filename, {});
|
|
275
|
+
this.data = file;
|
|
275
276
|
return this.end();
|
|
276
277
|
}
|
|
277
278
|
|
|
@@ -279,7 +280,7 @@ export default class ServerResponse<
|
|
|
279
280
|
|
|
280
281
|
debug && console.log("[routeur][response] Redirect", url);
|
|
281
282
|
this.statusCode = code;
|
|
282
|
-
this.headers['Location'] = url;
|
|
283
|
+
this.headers['Location'] = this.router.url( url );
|
|
283
284
|
return this.end();
|
|
284
285
|
}
|
|
285
286
|
|