misaki-studio-internal 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/next-env.d.ts +5 -0
- package/package.json +35 -0
- package/src/index.ts +26 -0
- package/src/styles.d.ts +4 -0
- package/src/utils/api.ts +87 -0
- package/src/utils/appear.ts +270 -0
- package/src/utils/blog.tsx +223 -0
- package/src/utils/document.tsx +556 -0
- package/src/utils/operations.ts +41 -0
- package/src/utils/popup.module.scss +45 -0
- package/src/utils/popup.tsx +712 -0
- package/src/utils/resize-detecter.ts +113 -0
- package/src/utils/storage.ts +143 -0
- package/src/utils/timeline.ts +478 -0
- package/src/utils/types.ts +6 -0
- package/src/utils/use-resize.ts +55 -0
- package/src/utils/utils.ts +251 -0
- package/tsconfig.json +16 -0
- package/webpack.config.js +39 -0
package/next-env.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "misaki-studio-internal",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"build": "webpack",
|
|
6
|
+
"publish": "npm publish --access public"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@types/lodash": "4.14.202",
|
|
10
|
+
"axios": "0.25.0",
|
|
11
|
+
"joi": "18.0.1",
|
|
12
|
+
"lodash": "4.17.21",
|
|
13
|
+
"styled-components": "6.1.11",
|
|
14
|
+
"use-immer": "0.9.0"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"react": ">=18.2.0",
|
|
18
|
+
"react-dom": ">=18.2.0",
|
|
19
|
+
"next": ">=15.5.4"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"sass": "1.42.1",
|
|
23
|
+
"webpack": "5.103.0",
|
|
24
|
+
"webpack-cli": "6.0.1",
|
|
25
|
+
"typescript": "5.9.3",
|
|
26
|
+
"postcss": "8.3.6",
|
|
27
|
+
"style-loader": "4.0.0",
|
|
28
|
+
"sass-loader": "16.0.6",
|
|
29
|
+
"css-loader": "7.1.2",
|
|
30
|
+
"@types/node": "18.11.19",
|
|
31
|
+
"@types/react": "18.2.61",
|
|
32
|
+
"@types/react-dom": "18.2.19",
|
|
33
|
+
"@types/styled-components": "5.1.26"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as api from "./utils/api";
|
|
2
|
+
import * as appear from "./utils/appear";
|
|
3
|
+
import * as blog from "./utils/blog";
|
|
4
|
+
import * as document from "./utils/document";
|
|
5
|
+
import * as operations from "./utils/operations";
|
|
6
|
+
import * as popup from "./utils/popup";
|
|
7
|
+
import * as resizeDetecter from "./utils/resize-detecter";
|
|
8
|
+
import * as storage from "./utils/storage";
|
|
9
|
+
import * as timeline from "./utils/timeline";
|
|
10
|
+
import * as types from "./utils/types";
|
|
11
|
+
import * as useResize from "./utils/use-resize";
|
|
12
|
+
import * as utils from "./utils/utils";
|
|
13
|
+
export {
|
|
14
|
+
api,
|
|
15
|
+
appear,
|
|
16
|
+
blog,
|
|
17
|
+
document,
|
|
18
|
+
operations,
|
|
19
|
+
popup,
|
|
20
|
+
resizeDetecter,
|
|
21
|
+
storage,
|
|
22
|
+
timeline,
|
|
23
|
+
types,
|
|
24
|
+
useResize,
|
|
25
|
+
utils,
|
|
26
|
+
};
|
package/src/styles.d.ts
ADDED
package/src/utils/api.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
import { deepEqual } from "fast-equals";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
|
|
6
|
+
export type Request<Query, Body> = {
|
|
7
|
+
config: {
|
|
8
|
+
host: string;
|
|
9
|
+
path: string;
|
|
10
|
+
};
|
|
11
|
+
query: Query;
|
|
12
|
+
body: Body;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export class ApiLoader<Query, Body, Response> {
|
|
16
|
+
response?: Response;
|
|
17
|
+
error?: string;
|
|
18
|
+
loading: boolean = false;
|
|
19
|
+
onUpdate?: () => void;
|
|
20
|
+
request?: Request<Query, Body>;
|
|
21
|
+
controller = new AbortController();
|
|
22
|
+
|
|
23
|
+
constructor(onUpdate?: () => void) {
|
|
24
|
+
this.onUpdate = onUpdate;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
load(request: Request<Query, Body>) {
|
|
28
|
+
if (deepEqual(request, this.request)) return;
|
|
29
|
+
if (this.loading) this.controller.abort();
|
|
30
|
+
this.controller = new AbortController();
|
|
31
|
+
this.request = request;
|
|
32
|
+
this.loading = true;
|
|
33
|
+
this.onUpdate?.();
|
|
34
|
+
const { config, body } = request;
|
|
35
|
+
const query = Object.entries(request.query || {}).reduce(
|
|
36
|
+
(prev, [key, param], index) => {
|
|
37
|
+
if (index === 0) {
|
|
38
|
+
return `?${key}=${param}`;
|
|
39
|
+
} else {
|
|
40
|
+
return `${prev}&${key}=${param}`;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"",
|
|
44
|
+
);
|
|
45
|
+
axios({
|
|
46
|
+
signal: this.controller.signal,
|
|
47
|
+
method: "post",
|
|
48
|
+
url: `${config.host}/${config.path.startsWith("/") ? config.path.slice(1) : config.path}${query}`,
|
|
49
|
+
headers: {
|
|
50
|
+
"Content-Type": "application/json",
|
|
51
|
+
},
|
|
52
|
+
data: body,
|
|
53
|
+
})
|
|
54
|
+
.then((response) => {
|
|
55
|
+
this.response = response.data;
|
|
56
|
+
this.error = undefined;
|
|
57
|
+
this.loading = false;
|
|
58
|
+
this.onUpdate?.();
|
|
59
|
+
})
|
|
60
|
+
.catch((error: any) => {
|
|
61
|
+
this.response = undefined;
|
|
62
|
+
this.error = error?.message || "Unknown Error";
|
|
63
|
+
this.loading = false;
|
|
64
|
+
this.onUpdate?.();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const useApi = <Query, Body, Response>(
|
|
70
|
+
request: Request<Query, Body>,
|
|
71
|
+
) => {
|
|
72
|
+
const [, update] = useState({});
|
|
73
|
+
const loader = useMemo(() => {
|
|
74
|
+
return new ApiLoader<Query, Body, Response>(() => {
|
|
75
|
+
update({});
|
|
76
|
+
});
|
|
77
|
+
}, []);
|
|
78
|
+
loader.load(request);
|
|
79
|
+
return {
|
|
80
|
+
loading: loader.loading,
|
|
81
|
+
response: {
|
|
82
|
+
body: loader.response,
|
|
83
|
+
},
|
|
84
|
+
error: loader.error,
|
|
85
|
+
request: loader.request,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
const appear = function (window: Window & typeof globalThis) {
|
|
2
|
+
var scrollLastPos: number | null = null,
|
|
3
|
+
scrollTimer = 0 as any,
|
|
4
|
+
scroll = {} as any;
|
|
5
|
+
|
|
6
|
+
function track() {
|
|
7
|
+
var newPos = window.scrollY || window.pageYOffset; // pageYOffset for IE9
|
|
8
|
+
if (scrollLastPos != null) {
|
|
9
|
+
scroll.velocity = newPos - scrollLastPos;
|
|
10
|
+
scroll.delta =
|
|
11
|
+
scroll.velocity >= 0 ? scroll.velocity : -1 * scroll.velocity;
|
|
12
|
+
}
|
|
13
|
+
scrollLastPos = newPos;
|
|
14
|
+
if (scrollTimer) {
|
|
15
|
+
clearTimeout(scrollTimer);
|
|
16
|
+
}
|
|
17
|
+
scrollTimer = setTimeout(function () {
|
|
18
|
+
scrollLastPos = null;
|
|
19
|
+
}, 30);
|
|
20
|
+
}
|
|
21
|
+
addEventListener("scroll", track, false);
|
|
22
|
+
|
|
23
|
+
function viewable(el: HTMLElement, bounds: any) {
|
|
24
|
+
var rect = el.getBoundingClientRect();
|
|
25
|
+
return (
|
|
26
|
+
rect.top + rect.height >= 0 &&
|
|
27
|
+
rect.left + rect.width >= 0 &&
|
|
28
|
+
rect.bottom - rect.height <=
|
|
29
|
+
(window.innerHeight || document.documentElement.clientHeight) +
|
|
30
|
+
bounds &&
|
|
31
|
+
rect.right - rect.width <=
|
|
32
|
+
(window.innerWidth || document.documentElement.clientWidth) + bounds
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return function (obj: any) {
|
|
37
|
+
const fn = function () {
|
|
38
|
+
var initd = false,
|
|
39
|
+
elements: any[] = [],
|
|
40
|
+
elementsLength: number,
|
|
41
|
+
reappear: boolean[] = [],
|
|
42
|
+
appeared = 0,
|
|
43
|
+
disappeared = 0,
|
|
44
|
+
timer: string | number | NodeJS.Timeout | undefined,
|
|
45
|
+
deltaSet: boolean,
|
|
46
|
+
opts = {} as any,
|
|
47
|
+
done: boolean;
|
|
48
|
+
|
|
49
|
+
// called on scroll and resize event, so debounce the actual function that does
|
|
50
|
+
// the heavy work of determining if an item is viewable and then "appearing" it
|
|
51
|
+
function checkAppear() {
|
|
52
|
+
if (scroll.delta < opts.delta.speed) {
|
|
53
|
+
if (!deltaSet) {
|
|
54
|
+
deltaSet = true;
|
|
55
|
+
doCheckAppear();
|
|
56
|
+
setTimeout(function () {
|
|
57
|
+
deltaSet = false;
|
|
58
|
+
}, opts.delta.timeout);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
doCheckAppear();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function begin() {
|
|
65
|
+
// initial appear check before any scroll or resize event
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
doCheckAppear();
|
|
68
|
+
}, 100);
|
|
69
|
+
|
|
70
|
+
// add relevant listeners
|
|
71
|
+
addEventListener("scroll", checkAppear, false);
|
|
72
|
+
addEventListener("wheel", checkAppear, false);
|
|
73
|
+
addEventListener("resize", checkAppear, false);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function end() {
|
|
77
|
+
elements = [];
|
|
78
|
+
if (timer) {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
}
|
|
81
|
+
removeListeners();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function removeListeners() {
|
|
85
|
+
removeEventListener("scroll", checkAppear, false);
|
|
86
|
+
removeEventListener("resize", checkAppear, false);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function doCheckAppear() {
|
|
90
|
+
if (done) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
elements.forEach(function (n, i) {
|
|
94
|
+
if (n && viewable(n, opts.bounds)) {
|
|
95
|
+
// only act if the element is eligible to reappear
|
|
96
|
+
if (reappear[i]) {
|
|
97
|
+
// mark this element as not eligible to appear
|
|
98
|
+
reappear[i] = false;
|
|
99
|
+
// increment the count of appeared items
|
|
100
|
+
appeared++;
|
|
101
|
+
// call the appear fn
|
|
102
|
+
if (opts.appear) {
|
|
103
|
+
opts.appear(n);
|
|
104
|
+
}
|
|
105
|
+
// if not tracking reappears or disappears, need to remove node here
|
|
106
|
+
if (!opts.disappear && !opts.reappear) {
|
|
107
|
+
// stop tracking this node, which is now viewable
|
|
108
|
+
elements[i] = null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
if (reappear[i] === false) {
|
|
113
|
+
if (opts.disappear) {
|
|
114
|
+
opts.disappear(n);
|
|
115
|
+
}
|
|
116
|
+
// increment the dissappeared count
|
|
117
|
+
disappeared++;
|
|
118
|
+
// if not tracking reappears, need to remove node here
|
|
119
|
+
if (!opts.reappear) {
|
|
120
|
+
// stop tracking this node, which is now viewable
|
|
121
|
+
elements[i] = null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// element is out of view and eligible to be appeared again
|
|
125
|
+
reappear[i] = true;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// remove listeners if all items have (re)appeared
|
|
130
|
+
if (
|
|
131
|
+
!opts.reappear &&
|
|
132
|
+
(!opts.appear || (opts.appear && appeared === elementsLength)) &&
|
|
133
|
+
(!opts.disappear ||
|
|
134
|
+
(opts.disappear && disappeared === elementsLength))
|
|
135
|
+
) {
|
|
136
|
+
// ensure done is only called once (could be called from a trailing debounce/throttle)
|
|
137
|
+
done = true;
|
|
138
|
+
removeListeners();
|
|
139
|
+
// all items have appeared, so call the done fn
|
|
140
|
+
if (opts.done) {
|
|
141
|
+
opts.done();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function init() {
|
|
147
|
+
// make sure we only init once
|
|
148
|
+
if (initd) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
initd = true;
|
|
152
|
+
|
|
153
|
+
// call the obj init fn
|
|
154
|
+
if (opts.init) {
|
|
155
|
+
opts.init();
|
|
156
|
+
}
|
|
157
|
+
// get the elements to work with
|
|
158
|
+
var els;
|
|
159
|
+
if (typeof opts.elements === "function") {
|
|
160
|
+
els = opts.elements();
|
|
161
|
+
} else {
|
|
162
|
+
els = opts.elements;
|
|
163
|
+
}
|
|
164
|
+
if (els) {
|
|
165
|
+
// put elements into an array object to work with
|
|
166
|
+
elementsLength = els.length;
|
|
167
|
+
for (var i = 0; i < elementsLength; i += 1) {
|
|
168
|
+
elements.push(els[i]);
|
|
169
|
+
reappear.push(true);
|
|
170
|
+
}
|
|
171
|
+
begin();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
var appear = function (obj: any) {
|
|
176
|
+
obj = obj || {};
|
|
177
|
+
|
|
178
|
+
// assign the fn to execute when a node is visible
|
|
179
|
+
opts = {
|
|
180
|
+
// a function to be run when the dom is ready (allows for any setup work)
|
|
181
|
+
init: obj.init,
|
|
182
|
+
// either an array of elements or a function that will return an htmlCollection
|
|
183
|
+
elements: obj.elements,
|
|
184
|
+
// function to call when an element is "viewable", will be passed the element to work with
|
|
185
|
+
appear: obj.appear,
|
|
186
|
+
// function to call when an element is no longer "viewable", will be passed the element to work with
|
|
187
|
+
disappear: obj.disappear,
|
|
188
|
+
// function to call when all the elements have "appeared"
|
|
189
|
+
done: obj.done,
|
|
190
|
+
// keep tracking the elements
|
|
191
|
+
reappear: obj.reappear,
|
|
192
|
+
// the extra border around an element to make it viewable outside of the true viewport
|
|
193
|
+
bounds: obj.bounds || 0,
|
|
194
|
+
// the debounce timeout
|
|
195
|
+
debounce: obj.debounce || 50,
|
|
196
|
+
// appear.js will also check for items on continuous slow scrolling
|
|
197
|
+
// you can controll how slow the scrolling should be (deltaSpeed)
|
|
198
|
+
// and when it will check again (deltaTimeout) after it has inspected the dom/viewport;
|
|
199
|
+
delta: {
|
|
200
|
+
speed: obj.deltaSpeed || 50,
|
|
201
|
+
timeout: obj.deltaTimeout || 500,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// add an event listener to init when dom is ready
|
|
206
|
+
addEventListener("DOMContentLoaded", init, false);
|
|
207
|
+
|
|
208
|
+
// http://stackoverflow.com/questions/9900311/how-do-i-target-only-internet-explorer-10-for-certain-situations-like-internet-e/13971998#13971998
|
|
209
|
+
var isIE10 = false;
|
|
210
|
+
if (Function("/*@cc_on return document.documentMode===10@*/")()) {
|
|
211
|
+
isIE10 = true;
|
|
212
|
+
}
|
|
213
|
+
var completeOrLoaded =
|
|
214
|
+
document.readyState === "complete" ||
|
|
215
|
+
document.readyState === ("loaded" as any);
|
|
216
|
+
|
|
217
|
+
// call init if document is ready to be worked with and we missed the event
|
|
218
|
+
if (isIE10) {
|
|
219
|
+
if (completeOrLoaded) {
|
|
220
|
+
init();
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
if (completeOrLoaded || document.readyState === "interactive") {
|
|
224
|
+
init();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
// manually fire check for visibility of tracked elements
|
|
230
|
+
trigger: function trigger() {
|
|
231
|
+
doCheckAppear();
|
|
232
|
+
},
|
|
233
|
+
// pause tracking of elements
|
|
234
|
+
pause: function pause() {
|
|
235
|
+
removeListeners();
|
|
236
|
+
},
|
|
237
|
+
// resume tracking of elements after a pause
|
|
238
|
+
resume: function resume() {
|
|
239
|
+
begin();
|
|
240
|
+
},
|
|
241
|
+
// provide a means to stop monitoring all elements
|
|
242
|
+
destroy: function destroy() {
|
|
243
|
+
end();
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
return appear;
|
|
248
|
+
};
|
|
249
|
+
return fn()(obj);
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
type Param = {
|
|
254
|
+
init?: () => void;
|
|
255
|
+
elements: () => HTMLElement[];
|
|
256
|
+
appear: (el: HTMLElement) => void;
|
|
257
|
+
disappear?: (el: HTMLElement) => void;
|
|
258
|
+
bounds: number;
|
|
259
|
+
reappear: boolean;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export type Return = {
|
|
263
|
+
trigger: () => void;
|
|
264
|
+
pause: () => void;
|
|
265
|
+
resume: () => void;
|
|
266
|
+
destroy: () => void;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export const onAppear: () => (param: Param) => Return = () =>
|
|
270
|
+
appear(window) as any;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { useRouter } from "next/router";
|
|
2
|
+
import { CSSProperties, useState } from "react";
|
|
3
|
+
import styled from "styled-components";
|
|
4
|
+
|
|
5
|
+
export type Blog<T = never> = {
|
|
6
|
+
pagePath: string;
|
|
7
|
+
name: string;
|
|
8
|
+
children?: Blog<T>[];
|
|
9
|
+
properties?: T;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const Item = styled.div`
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: row;
|
|
15
|
+
justify-content: start;
|
|
16
|
+
user-select: none;
|
|
17
|
+
white-space: pre-line;
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: auto;
|
|
20
|
+
justify-content: start;
|
|
21
|
+
align-items: center;
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
border-radius: 8px;
|
|
25
|
+
height: auto;
|
|
26
|
+
&.hide-arrow {
|
|
27
|
+
> .icon {
|
|
28
|
+
display: none;
|
|
29
|
+
}
|
|
30
|
+
> .text {
|
|
31
|
+
padding-left: 8px;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
> .icon {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: row;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
align-items: center;
|
|
39
|
+
width: 30px;
|
|
40
|
+
height: 30px;
|
|
41
|
+
&.open {
|
|
42
|
+
rotate: 90deg;
|
|
43
|
+
}
|
|
44
|
+
&.hide {
|
|
45
|
+
user-select: none;
|
|
46
|
+
opacity: 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
> .text {
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: row;
|
|
52
|
+
justify-content: start;
|
|
53
|
+
align-items: center;
|
|
54
|
+
width: auto;
|
|
55
|
+
height: 30px;
|
|
56
|
+
padding: 8px;
|
|
57
|
+
padding-left: 0px;
|
|
58
|
+
flex: 1;
|
|
59
|
+
}
|
|
60
|
+
&:hover {
|
|
61
|
+
background-color: #0000000a;
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
export const BlogItem = <T,>({
|
|
66
|
+
item,
|
|
67
|
+
level,
|
|
68
|
+
onClick,
|
|
69
|
+
itemWrapper,
|
|
70
|
+
isRoot,
|
|
71
|
+
}: {
|
|
72
|
+
item: Blog<T>;
|
|
73
|
+
level: number;
|
|
74
|
+
onClick?: (id: string) => void;
|
|
75
|
+
itemWrapper?: BlogItemWrapper;
|
|
76
|
+
isRoot?: boolean;
|
|
77
|
+
}) => {
|
|
78
|
+
const router = useRouter();
|
|
79
|
+
const currentPath = location.pathname.slice(0, -1);
|
|
80
|
+
const isPageOpen =
|
|
81
|
+
location.pathname.startsWith(item.pagePath) &&
|
|
82
|
+
currentPath !== item.pagePath;
|
|
83
|
+
const [isOpen, setOpen] = useState(isPageOpen);
|
|
84
|
+
const ItemWrapper = itemWrapper;
|
|
85
|
+
const rootPath = isRoot
|
|
86
|
+
? item.pagePath.split("/").slice(0, -1).join("/")
|
|
87
|
+
: undefined;
|
|
88
|
+
|
|
89
|
+
const isSelected =
|
|
90
|
+
router.asPath === item.pagePath || rootPath === router.asPath;
|
|
91
|
+
return (
|
|
92
|
+
<>
|
|
93
|
+
{ItemWrapper ? (
|
|
94
|
+
<ItemWrapper
|
|
95
|
+
properties={{
|
|
96
|
+
title: item.name,
|
|
97
|
+
isSelected,
|
|
98
|
+
}}
|
|
99
|
+
events={{
|
|
100
|
+
click: () => {
|
|
101
|
+
if (onClick) {
|
|
102
|
+
onClick(item.pagePath);
|
|
103
|
+
} else {
|
|
104
|
+
router.push(item.pagePath);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
}}
|
|
108
|
+
style={{
|
|
109
|
+
paddingLeft: `${level * 15}px`,
|
|
110
|
+
}}
|
|
111
|
+
/>
|
|
112
|
+
) : (
|
|
113
|
+
<Item
|
|
114
|
+
style={{
|
|
115
|
+
paddingLeft: `${level * 15}px`,
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
<div
|
|
119
|
+
onClick={() => setOpen(!isOpen)}
|
|
120
|
+
className={`icon ${isOpen ? "open" : "close"}`}
|
|
121
|
+
>
|
|
122
|
+
{item.children?.length ? (
|
|
123
|
+
<svg
|
|
124
|
+
width="7"
|
|
125
|
+
height="12"
|
|
126
|
+
viewBox="0 0 7 12"
|
|
127
|
+
fill="none"
|
|
128
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
129
|
+
>
|
|
130
|
+
<path
|
|
131
|
+
d="M0.0854187 1.36887L0.746734 0.696899L6.09215 5.95757L5.43083 6.62954L0.0854187 1.36887Z"
|
|
132
|
+
fill="black"
|
|
133
|
+
/>
|
|
134
|
+
<path
|
|
135
|
+
d="M5.42025 5.29636L6.09215 5.95757L0.831473 11.3032L0.159501 10.6419L5.42025 5.29636Z"
|
|
136
|
+
fill="black"
|
|
137
|
+
/>
|
|
138
|
+
</svg>
|
|
139
|
+
) : (
|
|
140
|
+
<svg
|
|
141
|
+
width="6"
|
|
142
|
+
height="6"
|
|
143
|
+
viewBox="0 0 6 6"
|
|
144
|
+
fill="none"
|
|
145
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
146
|
+
>
|
|
147
|
+
<circle cx="3.44977" cy="2.93213" r="2" stroke="#666666" />
|
|
148
|
+
</svg>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
151
|
+
<div
|
|
152
|
+
onClick={() => {
|
|
153
|
+
if (onClick) {
|
|
154
|
+
onClick(item.pagePath);
|
|
155
|
+
} else {
|
|
156
|
+
router.push(item.pagePath);
|
|
157
|
+
}
|
|
158
|
+
}}
|
|
159
|
+
className="text"
|
|
160
|
+
>
|
|
161
|
+
{item.name}
|
|
162
|
+
</div>
|
|
163
|
+
</Item>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
{!!item.children?.length && isOpen && (
|
|
167
|
+
<BlogList
|
|
168
|
+
onClick={onClick}
|
|
169
|
+
list={item.children}
|
|
170
|
+
level={level + 1}
|
|
171
|
+
itemWrapper={itemWrapper}
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
174
|
+
</>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const BlogList = <T = never,>(props: {
|
|
179
|
+
list: Blog<T>[];
|
|
180
|
+
onClick?: (id: string) => void;
|
|
181
|
+
level: number;
|
|
182
|
+
itemWrapper?: BlogItemWrapper;
|
|
183
|
+
isRoot?: boolean;
|
|
184
|
+
}) => {
|
|
185
|
+
return (
|
|
186
|
+
<>
|
|
187
|
+
{props.list.map((item, index) => (
|
|
188
|
+
<BlogItem
|
|
189
|
+
item={item}
|
|
190
|
+
onClick={props.onClick}
|
|
191
|
+
key={item.pagePath}
|
|
192
|
+
level={props.level}
|
|
193
|
+
itemWrapper={props.itemWrapper}
|
|
194
|
+
isRoot={index === 0 && props.isRoot}
|
|
195
|
+
/>
|
|
196
|
+
))}
|
|
197
|
+
</>
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
type BlogItemWrapper = (props: {
|
|
202
|
+
properties?: { title: string; isSelected: boolean };
|
|
203
|
+
events?: { open?: () => void; click?: () => void };
|
|
204
|
+
style?: CSSProperties | undefined;
|
|
205
|
+
}) => JSX.Element;
|
|
206
|
+
|
|
207
|
+
export const BlogSideBar = <T = never,>(props: {
|
|
208
|
+
list: Blog<T>[];
|
|
209
|
+
itemWrapper?: BlogItemWrapper;
|
|
210
|
+
onClick?: (id: string) => void;
|
|
211
|
+
}) => {
|
|
212
|
+
return (
|
|
213
|
+
<>
|
|
214
|
+
<BlogList
|
|
215
|
+
onClick={props.onClick}
|
|
216
|
+
list={props.list}
|
|
217
|
+
level={0}
|
|
218
|
+
itemWrapper={props.itemWrapper}
|
|
219
|
+
isRoot={true}
|
|
220
|
+
/>
|
|
221
|
+
</>
|
|
222
|
+
);
|
|
223
|
+
};
|