@remloyal/docsify-plugins 1.0.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/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/flexsearch/docsify-flexsearch.css +248 -0
- package/dist/flexsearch/docsify-flexsearch.js +4614 -0
- package/dist/flexsearch/docsify-flexsearch.min.css +2 -0
- package/dist/flexsearch/docsify-flexsearch.min.css.map +1 -0
- package/dist/flexsearch/docsify-flexsearch.min.js +8 -0
- package/dist/flexsearch/docsify-flexsearch.min.js.map +1 -0
- package/dist/sidebar-collapse/docsify-sidebar-collapse.css +25 -0
- package/dist/sidebar-collapse/docsify-sidebar-collapse.js +193 -0
- package/dist/sidebar-collapse/docsify-sidebar-collapse.min.css +2 -0
- package/dist/sidebar-collapse/docsify-sidebar-collapse.min.css.map +1 -0
- package/dist/sidebar-collapse/docsify-sidebar-collapse.min.js +8 -0
- package/dist/sidebar-collapse/docsify-sidebar-collapse.min.js.map +1 -0
- package/package.json +60 -0
- package/src/flexsearch/index.js +822 -0
- package/src/flexsearch/markdown-to-txt.js +206 -0
- package/src/flexsearch/style.css +247 -0
- package/src/sidebar-collapse/index.js +11 -0
- package/src/sidebar-collapse/sidebar-collapse-plugin.js +251 -0
- package/src/sidebar-collapse/sidebar.css +27 -0
- package/src/sidebar-collapse/style.css +25 -0
- package/src/utils/utils.js +96 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a function to convert markdown to txt based on markedjs v13+.
|
|
3
|
+
* Copies the escape/unescape functions from [lodash](https://www.npmjs.com/package/lodash) instead import to reduce the size.
|
|
4
|
+
*/
|
|
5
|
+
import { marked } from 'marked';
|
|
6
|
+
|
|
7
|
+
const reEscapedHtml = /&(?:amp|lt|gt|quot|#(0+)?39);/g;
|
|
8
|
+
const reHasEscapedHtml = RegExp(reEscapedHtml.source);
|
|
9
|
+
const htmlUnescapes = {
|
|
10
|
+
'&': '&',
|
|
11
|
+
'<': '<',
|
|
12
|
+
'>': '>',
|
|
13
|
+
'"': '"',
|
|
14
|
+
''': "'",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function unescape(string) {
|
|
18
|
+
return string && reHasEscapedHtml.test(string)
|
|
19
|
+
? string.replace(reEscapedHtml, entity => htmlUnescapes[entity] || "'")
|
|
20
|
+
: string || '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const reUnescapedHtml = /[&<>"']/g;
|
|
24
|
+
const reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
|
|
25
|
+
const htmlEscapes = {
|
|
26
|
+
'&': '&',
|
|
27
|
+
'<': '<',
|
|
28
|
+
'>': '>',
|
|
29
|
+
'"': '"',
|
|
30
|
+
"'": ''',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function escape(string) {
|
|
34
|
+
return string && reHasUnescapedHtml.test(string)
|
|
35
|
+
? string.replace(reUnescapedHtml, chr => htmlEscapes[chr])
|
|
36
|
+
: string || '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function helpersCleanup(string) {
|
|
40
|
+
return string && string.replace('!>', '').replace('?>', '');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const markdownToTxtRenderer = {
|
|
44
|
+
space() {
|
|
45
|
+
return '';
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
code({ text }) {
|
|
49
|
+
const code = text.replace(/\n$/, '');
|
|
50
|
+
return escape(code);
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
blockquote({ tokens }) {
|
|
54
|
+
return this.parser?.parse(tokens) || '';
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
html() {
|
|
58
|
+
return '';
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
heading({ tokens }) {
|
|
62
|
+
return this.parser?.parseInline(tokens) || '';
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
hr() {
|
|
66
|
+
return '';
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
list(token) {
|
|
70
|
+
let body = '';
|
|
71
|
+
for (let j = 0; j < token.items.length; j++) {
|
|
72
|
+
const item = token.items[j];
|
|
73
|
+
body += this.listitem?.(item);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return body;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
listitem(item) {
|
|
80
|
+
let itemBody = '';
|
|
81
|
+
if (item.task) {
|
|
82
|
+
const checkbox = this.checkbox?.({
|
|
83
|
+
checked: !!item.checked,
|
|
84
|
+
});
|
|
85
|
+
if (item.loose) {
|
|
86
|
+
if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') {
|
|
87
|
+
item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
|
|
88
|
+
if (
|
|
89
|
+
item.tokens[0].tokens &&
|
|
90
|
+
item.tokens[0].tokens.length > 0 &&
|
|
91
|
+
item.tokens[0].tokens[0].type === 'text'
|
|
92
|
+
) {
|
|
93
|
+
item.tokens[0].tokens[0].text =
|
|
94
|
+
checkbox + ' ' + item.tokens[0].tokens[0].text;
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
item.tokens.unshift({
|
|
98
|
+
type: 'text',
|
|
99
|
+
raw: checkbox + ' ',
|
|
100
|
+
text: checkbox + ' ',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
itemBody += checkbox + ' ';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
itemBody += this.parser?.parse(item.tokens, !!item.loose);
|
|
109
|
+
|
|
110
|
+
return `${itemBody || ''}`;
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {{checked: boolean}} options
|
|
115
|
+
* @return {string}
|
|
116
|
+
*/
|
|
117
|
+
checkbox(options) {
|
|
118
|
+
return '';
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
paragraph({ tokens }) {
|
|
122
|
+
return this.parser?.parseInline(tokens) || '';
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
table(token) {
|
|
126
|
+
let header = '';
|
|
127
|
+
|
|
128
|
+
let cell = '';
|
|
129
|
+
for (let j = 0; j < token.header.length; j++) {
|
|
130
|
+
cell += this.tablecell?.(token.header[j]);
|
|
131
|
+
}
|
|
132
|
+
header += this.tablerow?.({ text: cell });
|
|
133
|
+
|
|
134
|
+
let body = '';
|
|
135
|
+
for (let j = 0; j < token.rows.length; j++) {
|
|
136
|
+
const row = token.rows[j];
|
|
137
|
+
|
|
138
|
+
cell = '';
|
|
139
|
+
for (let k = 0; k < row.length; k++) {
|
|
140
|
+
cell += this.tablecell?.(row[k]);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
body += this.tablerow?.({ text: cell });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return header + ' ' + body;
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
tablerow({ text }) {
|
|
150
|
+
return text;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
tablecell(token) {
|
|
154
|
+
return this.parser?.parseInline(token.tokens) || '';
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
strong({ text }) {
|
|
158
|
+
return text;
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
em({ tokens }) {
|
|
162
|
+
return this.parser?.parseInline(tokens) || '';
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
codespan({ text }) {
|
|
166
|
+
return text;
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
br() {
|
|
170
|
+
return ' ';
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
del({ tokens }) {
|
|
174
|
+
return this.parser?.parseInline(tokens);
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
link({ tokens, href, title }) {
|
|
178
|
+
// Remain the href and title attributes for searching, so is the image
|
|
179
|
+
// e.g. [filename](_media/example.js ':include :type=code :fragment=demo')
|
|
180
|
+
// Result: filename _media/example.js :include :type=code :fragment=demo
|
|
181
|
+
return `${this.parser?.parseInline(tokens) || ''} ${href || ''} ${title || ''}`;
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
image({ title, text, href }) {
|
|
185
|
+
return `${text || ''} ${href || ''} ${title || ''}`;
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
text(token) {
|
|
189
|
+
return token.tokens
|
|
190
|
+
? this.parser?.parseInline(token.tokens) || ''
|
|
191
|
+
: token.text || '';
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
const _marked = marked.setOptions({
|
|
195
|
+
// @ts-expect-error missing properties
|
|
196
|
+
renderer: markdownToTxtRenderer,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
export function markdownToTxt(markdown) {
|
|
200
|
+
const unmarked = _marked.parse(markdown);
|
|
201
|
+
const unescaped = unescape(unmarked);
|
|
202
|
+
const helpersCleaned = helpersCleanup(unescaped);
|
|
203
|
+
return helpersCleaned.trim();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export default markdownToTxt;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/* prettier-ignore */
|
|
2
|
+
:root {
|
|
3
|
+
--plugin-flexsearch-input-bg : var(--form-element-bg);
|
|
4
|
+
--plugin-flexsearch-input-border-color : var(--sidebar-border-color);
|
|
5
|
+
--plugin-flexsearch-input-border-radius: var(--form-element-border-radius);
|
|
6
|
+
--plugin-flexsearch-input-color : var(--form-element-color);
|
|
7
|
+
--plugin-flexsearch-kbd-bg : var(--color-bg);
|
|
8
|
+
--plugin-flexsearch-kbd-border : 1px solid var(--color-mono-3);
|
|
9
|
+
--plugin-flexsearch-kbd-border-radius : 4px;
|
|
10
|
+
--plugin-flexsearch-kbd-color : var(--color-mono-5);
|
|
11
|
+
--plugin-flexsearch-margin : 10px;
|
|
12
|
+
--plugin-flexsearch-reset-bg : var(--theme-color);
|
|
13
|
+
--plugin-flexsearch-reset-border : transparent;
|
|
14
|
+
--plugin-flexsearch-reset-border-radius: var(--border-radius);
|
|
15
|
+
--plugin-flexsearch-reset-color : #fff;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.flexsearch-search {
|
|
19
|
+
margin: var(--plugin-flexsearch-margin);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.flexsearch-search .input-wrap {
|
|
23
|
+
position: relative;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.flexsearch-search input {
|
|
27
|
+
width: 100%;
|
|
28
|
+
padding-inline-end: 36px;
|
|
29
|
+
border: 1px solid var(--plugin-flexsearch-input-border-color);
|
|
30
|
+
border-radius: var(--plugin-flexsearch-input-border-radius);
|
|
31
|
+
background: var(--plugin-flexsearch-input-bg);
|
|
32
|
+
color: var(--plugin-flexsearch-input-color);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.flexsearch-search input::-webkit-search-decoration,
|
|
36
|
+
.flexsearch-search input::-webkit-search-cancel-button {
|
|
37
|
+
appearance: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.flexsearch-search .clear-button,
|
|
41
|
+
.flexsearch-search .kbd-group {
|
|
42
|
+
visibility: hidden;
|
|
43
|
+
display: flex;
|
|
44
|
+
gap: 0.15em;
|
|
45
|
+
position: absolute;
|
|
46
|
+
right: 7px;
|
|
47
|
+
top: 50%;
|
|
48
|
+
opacity: 0;
|
|
49
|
+
translate: 0 -50%;
|
|
50
|
+
transition-property: opacity, visibility;
|
|
51
|
+
transition-duration: var(--duration-medium);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.flexsearch-search input:valid ~ .clear-button,
|
|
55
|
+
.flexsearch-search input:invalid:where(:focus, :hover) ~ .kbd-group,
|
|
56
|
+
.flexsearch-search .kbd-group:hover {
|
|
57
|
+
visibility: visible;
|
|
58
|
+
opacity: 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.flexsearch-search .clear-button {
|
|
62
|
+
--_button-size: 20px;
|
|
63
|
+
--_content-size: 12px;
|
|
64
|
+
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
height: var(--_button-size);
|
|
69
|
+
width: var(--_button-size);
|
|
70
|
+
border: var(--plugin-flexsearch-reset-border);
|
|
71
|
+
border-radius: var(--plugin-flexsearch-reset-border-radius);
|
|
72
|
+
background: var(--plugin-flexsearch-reset-bg);
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.flexsearch-search .clear-button::before,
|
|
77
|
+
.flexsearch-search .clear-button::after {
|
|
78
|
+
content: "";
|
|
79
|
+
position: absolute;
|
|
80
|
+
height: 2px;
|
|
81
|
+
width: var(--_content-size);
|
|
82
|
+
color: var(--plugin-flexsearch-reset-color);
|
|
83
|
+
background: var(--plugin-flexsearch-reset-color);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.flexsearch-search .clear-button::before {
|
|
87
|
+
rotate: 45deg;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.flexsearch-search .clear-button::after {
|
|
91
|
+
rotate: -45deg;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.flexsearch-search kbd {
|
|
95
|
+
border: var(--plugin-flexsearch-kbd-border);
|
|
96
|
+
border-radius: var(--plugin-flexsearch-kbd-border-radius);
|
|
97
|
+
background: var(--plugin-flexsearch-kbd-bg);
|
|
98
|
+
color: var(--plugin-flexsearch-kbd-color);
|
|
99
|
+
font-size: var(--font-size-s);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.flexsearch-search a:hover {
|
|
103
|
+
color: var(--theme-color);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.flexsearch-search .results-panel:empty {
|
|
107
|
+
display: none;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.flexsearch-search:has(.results-panel:not(:empty)) ~ * {
|
|
111
|
+
display: none;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.flexsearch-search:where(:has(input:valid:focus), :has(.results-panel::empty))
|
|
115
|
+
~ * {
|
|
116
|
+
opacity: 0.2;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.flexsearch-search .matching-post {
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
padding: 1em 0 1.2em 0;
|
|
122
|
+
border-bottom: 1px solid var(--color-mono-2);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.flexsearch-search .matching-post:hover a {
|
|
126
|
+
text-decoration-color: transparent;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.flexsearch-search .matching-post:hover .title {
|
|
130
|
+
text-decoration: inherit;
|
|
131
|
+
text-decoration-color: var(--link-underline-color-hover);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.flexsearch-search .matching-post .title {
|
|
135
|
+
margin: 0 0 0.5em 0;
|
|
136
|
+
line-height: 1.4;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.flexsearch-search .matching-post .content {
|
|
140
|
+
margin: 0;
|
|
141
|
+
color: var(--color-mono-6);
|
|
142
|
+
font-size: var(--font-size-s);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.flexsearch-search .results-status {
|
|
146
|
+
margin-bottom: 0;
|
|
147
|
+
color: var(--color-mono-6);
|
|
148
|
+
font-size: var(--font-size-s);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.flexsearch-search .results-status:empty {
|
|
152
|
+
display: none;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Modal */
|
|
156
|
+
/* ================================== */
|
|
157
|
+
.flexsearch-modal {
|
|
158
|
+
position: fixed;
|
|
159
|
+
inset: 0;
|
|
160
|
+
display: none;
|
|
161
|
+
z-index: 9999;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.flexsearch-modal.is-open {
|
|
165
|
+
display: block;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.flexsearch-modal__backdrop {
|
|
169
|
+
position: absolute;
|
|
170
|
+
inset: 0;
|
|
171
|
+
background: rgba(0, 0, 0, 0.35);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.flexsearch-modal__dialog {
|
|
175
|
+
position: relative;
|
|
176
|
+
margin: 8vh auto 0 auto;
|
|
177
|
+
max-width: min(720px, calc(100vw - 24px));
|
|
178
|
+
max-height: 80vh;
|
|
179
|
+
overflow: hidden;
|
|
180
|
+
display: flex;
|
|
181
|
+
flex-direction: column;
|
|
182
|
+
background: var(--sidebar-background, var(--color-bg));
|
|
183
|
+
border: 1px solid var(--sidebar-border-color, var(--color-mono-2));
|
|
184
|
+
border-radius: 10px;
|
|
185
|
+
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.25);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.flexsearch-modal__dialog .flexsearch-search {
|
|
189
|
+
margin: 12px;
|
|
190
|
+
display: flex;
|
|
191
|
+
flex-direction: column;
|
|
192
|
+
min-height: 0;
|
|
193
|
+
flex: 1 1 auto;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.flexsearch-modal__dialog .flexsearch-search .results-panel {
|
|
197
|
+
overflow: auto;
|
|
198
|
+
min-height: 0;
|
|
199
|
+
flex: 1 1 auto;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* Modal trigger (sidebar) */
|
|
203
|
+
/* ================================== */
|
|
204
|
+
.flexsearch-trigger {
|
|
205
|
+
margin: var(--plugin-flexsearch-margin);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.flexsearch-trigger .input-wrap {
|
|
209
|
+
position: relative;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.flexsearch-trigger input {
|
|
213
|
+
width: 100%;
|
|
214
|
+
padding-inline-end: 36px;
|
|
215
|
+
border: 1px solid var(--plugin-flexsearch-input-border-color);
|
|
216
|
+
border-radius: var(--plugin-flexsearch-input-border-radius);
|
|
217
|
+
background: var(--plugin-flexsearch-input-bg);
|
|
218
|
+
color: var(--plugin-flexsearch-input-color);
|
|
219
|
+
cursor: pointer;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.flexsearch-trigger .kbd-group {
|
|
223
|
+
visibility: hidden;
|
|
224
|
+
display: flex;
|
|
225
|
+
gap: 0.15em;
|
|
226
|
+
position: absolute;
|
|
227
|
+
right: 7px;
|
|
228
|
+
top: 50%;
|
|
229
|
+
opacity: 0;
|
|
230
|
+
translate: 0 -50%;
|
|
231
|
+
transition-property: opacity, visibility;
|
|
232
|
+
transition-duration: var(--duration-medium);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.flexsearch-trigger input:where(:focus, :hover) ~ .kbd-group,
|
|
236
|
+
.flexsearch-trigger .kbd-group:hover {
|
|
237
|
+
visibility: visible;
|
|
238
|
+
opacity: 1;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.flexsearch-trigger kbd {
|
|
242
|
+
border: var(--plugin-flexsearch-kbd-border);
|
|
243
|
+
border-radius: var(--plugin-flexsearch-kbd-border-radius);
|
|
244
|
+
background: var(--plugin-flexsearch-kbd-bg);
|
|
245
|
+
color: var(--plugin-flexsearch-kbd-color);
|
|
246
|
+
font-size: var(--font-size-s);
|
|
247
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import sidebarCollapsePlugin from './sidebar-collapse-plugin'
|
|
2
|
+
|
|
3
|
+
function install(...plugins) {
|
|
4
|
+
if (!window.$docsify) {
|
|
5
|
+
console.error('这是一个docsify插件,请先引用docsify库!')
|
|
6
|
+
} else {
|
|
7
|
+
$docsify.plugins = plugins.concat($docsify.plugins || [])
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
install(sidebarCollapsePlugin)
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import "./style.css";
|
|
2
|
+
|
|
3
|
+
// 首次加载标记 - 控制层级仅首次展开
|
|
4
|
+
let isFirstLoad = true;
|
|
5
|
+
// 全局侧边栏折叠状态对象,持久化每个节点状态(键:href,值:open/collapse)
|
|
6
|
+
const sidebarCollapseState = {};
|
|
7
|
+
|
|
8
|
+
function sidebarCollapsePlugin(hook, vm) {
|
|
9
|
+
hook.doneEach(function (html, next) {
|
|
10
|
+
const activeNode = getActiveNode();
|
|
11
|
+
// 自动展开当前激活项的根层级(核心体验保留)
|
|
12
|
+
openActiveToRoot(activeNode);
|
|
13
|
+
|
|
14
|
+
addFolderFileClass();
|
|
15
|
+
// 仅首次加载执行层级类添加+初始展开
|
|
16
|
+
isFirstLoad && addLevelClass();
|
|
17
|
+
syncScrollTop(activeNode);
|
|
18
|
+
|
|
19
|
+
// doneEach最后恢复折叠状态,避免自动逻辑覆盖
|
|
20
|
+
restoreCollapseState(activeNode);
|
|
21
|
+
|
|
22
|
+
next(html);
|
|
23
|
+
});
|
|
24
|
+
hook.ready(function () {
|
|
25
|
+
// 1. 关键:hook.ready中先收集全量初始状态(DOM已完整,仅执行一次)
|
|
26
|
+
collectInitialCollapseState();
|
|
27
|
+
// 2. 再绑定点击事件,确保初始状态收集完成后再处理用户操作
|
|
28
|
+
document
|
|
29
|
+
.querySelector(".sidebar-nav")
|
|
30
|
+
.addEventListener("click", handleMenuClick);
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
const activeNode = getActiveNode();
|
|
33
|
+
restoreCollapseState(activeNode);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function init() {
|
|
39
|
+
document.addEventListener("scroll", scrollSyncMenuStatus);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let lastTop; // 侧边栏滚动状态
|
|
43
|
+
function syncScrollTop(activeNode) {
|
|
44
|
+
if (activeNode && lastTop != undefined) {
|
|
45
|
+
const curTop = activeNode.getBoundingClientRect().top;
|
|
46
|
+
document.querySelector(".sidebar").scrollBy(0, curTop - lastTop);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function scrollSyncMenuStatus() {
|
|
51
|
+
requestAnimationFrame(() => {
|
|
52
|
+
let el = document.querySelector(".app-sub-sidebar > .active");
|
|
53
|
+
if (el) {
|
|
54
|
+
el.parentNode.parentNode
|
|
55
|
+
.querySelectorAll(".app-sub-sidebar")
|
|
56
|
+
.forEach((dom) => dom.classList.remove("open"));
|
|
57
|
+
while (el.parentNode.classList.contains("app-sub-sidebar")) {
|
|
58
|
+
if (el.parentNode.classList.contains("open")) {
|
|
59
|
+
break;
|
|
60
|
+
} else {
|
|
61
|
+
el.parentNode.classList.add("open");
|
|
62
|
+
el = el.parentNode;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function handleMenuClick(e) {
|
|
70
|
+
lastTop = e.target.getBoundingClientRect().top;
|
|
71
|
+
// 兼容点击<a>标签或父级LI的情况,找到实际的LI节点
|
|
72
|
+
const targetNode = e.target.tagName === "A" ? e.target.parentNode : e.target;
|
|
73
|
+
const newActiveNode = findTagParent(targetNode, "LI", 2);
|
|
74
|
+
if (!newActiveNode) return;
|
|
75
|
+
// 找到节点内的<a>标签,获取唯一标识href
|
|
76
|
+
const nodeHref = newActiveNode.querySelector("a")?.getAttribute("href");
|
|
77
|
+
if (!nodeHref) return;
|
|
78
|
+
|
|
79
|
+
if (newActiveNode.classList.contains("open")) {
|
|
80
|
+
// 手动折叠:移除open,添加collapse
|
|
81
|
+
newActiveNode.classList.remove("open");
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
newActiveNode.classList.add("collapse");
|
|
84
|
+
}, 0);
|
|
85
|
+
// 收集状态:折叠状态存入对象
|
|
86
|
+
sidebarCollapseState[nodeHref] = "collapse";
|
|
87
|
+
} else {
|
|
88
|
+
// 手动展开:先关闭其他激活项,再展开当前
|
|
89
|
+
removeOpenToRoot(getActiveNode());
|
|
90
|
+
openActiveToRoot(newActiveNode);
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
newActiveNode.classList.remove("collapse");
|
|
93
|
+
}, 0);
|
|
94
|
+
// 收集状态:展开状态存入对象
|
|
95
|
+
sidebarCollapseState[nodeHref] = "open";
|
|
96
|
+
}
|
|
97
|
+
syncScrollTop(newActiveNode);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getActiveNode() {
|
|
101
|
+
let node = document.querySelector(".sidebar-nav .active");
|
|
102
|
+
|
|
103
|
+
if (!node) {
|
|
104
|
+
const curHref = decodeURIComponent(location.hash).replace(/ /gi, "%20");
|
|
105
|
+
const curLink = document.querySelector(`.sidebar-nav a[href="${curHref}"]`);
|
|
106
|
+
node = findTagParent(curLink, "LI", 2);
|
|
107
|
+
if (node) {
|
|
108
|
+
node.classList.add("active");
|
|
109
|
+
// 初始化激活项状态:默认展开
|
|
110
|
+
const nodeHref = node.querySelector("a")?.getAttribute("href");
|
|
111
|
+
nodeHref && (sidebarCollapseState[nodeHref] = "open");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return node;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function openActiveToRoot(node) {
|
|
118
|
+
if (node) {
|
|
119
|
+
node.classList.add("open", "active");
|
|
120
|
+
while (node && node.className !== "sidebar-nav" && node.parentNode) {
|
|
121
|
+
if (
|
|
122
|
+
node.parentNode.tagName === "LI" ||
|
|
123
|
+
node.parentNode.className === "app-sub-sidebar"
|
|
124
|
+
) {
|
|
125
|
+
node.parentNode.classList.add("open");
|
|
126
|
+
}
|
|
127
|
+
node = node.parentNode;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function removeOpenToRoot(node) {
|
|
133
|
+
if (node) {
|
|
134
|
+
node.classList.remove("open", "active");
|
|
135
|
+
while (node && node.className !== "sidebar-nav" && node.parentNode) {
|
|
136
|
+
if (
|
|
137
|
+
node.parentNode.tagName === "LI" ||
|
|
138
|
+
node.parentNode.className === "app-sub-sidebar"
|
|
139
|
+
) {
|
|
140
|
+
node.parentNode.classList.remove("open");
|
|
141
|
+
}
|
|
142
|
+
node = node.parentNode;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function findTagParent(curNode, tagName, level) {
|
|
148
|
+
if (curNode && curNode.tagName === tagName) return curNode;
|
|
149
|
+
let l = 0;
|
|
150
|
+
while (curNode) {
|
|
151
|
+
l++;
|
|
152
|
+
if (l > level) return;
|
|
153
|
+
if (curNode.parentNode.tagName === tagName) {
|
|
154
|
+
return curNode.parentNode;
|
|
155
|
+
}
|
|
156
|
+
curNode = curNode.parentNode;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function addFolderFileClass() {
|
|
161
|
+
document.querySelectorAll(".sidebar-nav li").forEach((li) => {
|
|
162
|
+
if (li.querySelector("ul:not(.app-sub-sidebar)")) {
|
|
163
|
+
li.classList.add("folder");
|
|
164
|
+
} else {
|
|
165
|
+
li.classList.add("file");
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function addLevelClass() {
|
|
171
|
+
function find(root, level) {
|
|
172
|
+
root &&
|
|
173
|
+
root.childNodes &&
|
|
174
|
+
root.childNodes.forEach((child) => {
|
|
175
|
+
if (child.classList && child.classList.contains("folder")) {
|
|
176
|
+
child.classList.add(`level-${level}`);
|
|
177
|
+
// 优先读取专属配置,兼容原配置
|
|
178
|
+
const firstOpenLevel =
|
|
179
|
+
window.$docsify?.sidebarFirstOpenLevel ||
|
|
180
|
+
window.$docsify?.sidebarDisplayLevel;
|
|
181
|
+
if (
|
|
182
|
+
firstOpenLevel &&
|
|
183
|
+
typeof firstOpenLevel === "number" &&
|
|
184
|
+
level <= firstOpenLevel
|
|
185
|
+
) {
|
|
186
|
+
child.classList.add("open");
|
|
187
|
+
// 初始化首次展开的节点状态:存入open
|
|
188
|
+
const nodeHref = child.querySelector("a")?.getAttribute("href");
|
|
189
|
+
nodeHref && (sidebarCollapseState[nodeHref] = "open");
|
|
190
|
+
}
|
|
191
|
+
if (child && child.childNodes.length > 1) {
|
|
192
|
+
find(child.childNodes[1], level + 1);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
find(document.querySelector(".sidebar-nav > ul"), 1);
|
|
198
|
+
// 首次执行后关闭标记
|
|
199
|
+
isFirstLoad = false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function restoreCollapseState(activeNode) {
|
|
203
|
+
// 获取激活节点的href(用于排除,不覆盖其状态)
|
|
204
|
+
const activeHref = activeNode?.querySelector("a")?.getAttribute("href");
|
|
205
|
+
// 遍历所有菜单节点,根据状态对象恢复
|
|
206
|
+
document
|
|
207
|
+
.querySelectorAll(".sidebar-nav li.folder, .sidebar-nav li.file")
|
|
208
|
+
.forEach((li) => {
|
|
209
|
+
const nodeHref = li.querySelector("a")?.getAttribute("href");
|
|
210
|
+
// 容错:无href的节点不处理,排除激活项
|
|
211
|
+
if (!nodeHref || nodeHref === activeHref) return;
|
|
212
|
+
// 从状态对象获取保存的状态
|
|
213
|
+
const saveState = sidebarCollapseState[nodeHref];
|
|
214
|
+
if (saveState) {
|
|
215
|
+
// 恢复状态:移除相反类名,添加保存的类名
|
|
216
|
+
li.classList.remove(saveState === "open" ? "collapse" : "open");
|
|
217
|
+
li.classList.add(saveState);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 2. 新增:hook.ready专用 - 收集侧边栏全量初始状态函数(仅执行一次)
|
|
223
|
+
function collectInitialCollapseState() {
|
|
224
|
+
// 遍历所有侧边栏可交互节点(folder/file,此时addFolderFileClass已执行,类名存在)
|
|
225
|
+
const allSidebarNodes = document.querySelectorAll(
|
|
226
|
+
".sidebar-nav li.folder, .sidebar-nav li.file",
|
|
227
|
+
);
|
|
228
|
+
allSidebarNodes.forEach((li) => {
|
|
229
|
+
// 获取节点唯一标识:a标签的href(容错,无a/href则跳过)
|
|
230
|
+
const aTag = li.querySelector("a");
|
|
231
|
+
const nodeHref = aTag?.getAttribute("href");
|
|
232
|
+
if (!nodeHref) return;
|
|
233
|
+
|
|
234
|
+
// 核心:根据DOM真实类名判断初始状态,与实际展示严格一致
|
|
235
|
+
let initialState = "collapse"; // 默认折叠(Docsify原生默认行为)
|
|
236
|
+
if (li.classList.contains("open")) {
|
|
237
|
+
initialState = "open"; // 有open类=展开状态
|
|
238
|
+
} else if (li.classList.contains("collapse")) {
|
|
239
|
+
initialState = "collapse"; // 有collapse类=折叠状态
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 将真实初始状态存入全局状态对象,完成初始化
|
|
243
|
+
sidebarCollapseState[nodeHref] = initialState;
|
|
244
|
+
});
|
|
245
|
+
// 可选:调试用,查看收集的初始状态(开发完成后可删除)
|
|
246
|
+
console.log("侧边栏初始状态收集完成:", sidebarCollapseState);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
init();
|
|
250
|
+
|
|
251
|
+
export default sidebarCollapsePlugin;
|