create-nativecore 0.1.0 → 0.2.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/README.md +10 -18
- package/bin/index.mjs +407 -489
- package/package.json +4 -3
- package/template/.env.example +28 -0
- package/template/.htmlhintrc +14 -0
- package/template/api/data/dashboard.json +11 -0
- package/template/api/data/users.json +18 -0
- package/template/api/mockApi.js +161 -0
- package/template/assets/icon.svg +13 -0
- package/template/assets/logo.svg +25 -0
- package/template/eslint.config.js +94 -0
- package/template/index.html +137 -0
- package/template/manifest.json +19 -0
- package/template/public/.well-known/security.txt +9 -0
- package/template/public/_headers +24 -0
- package/template/public/_redirects +14 -0
- package/template/public/assets/icon.svg +13 -0
- package/template/public/assets/logo.svg +25 -0
- package/template/public/manifest.json +19 -0
- package/template/public/robots.txt +13 -0
- package/template/public/sitemap.xml +27 -0
- package/template/scripts/build-for-bots.mjs +121 -0
- package/template/scripts/convert-to-ts.mjs +106 -0
- package/template/scripts/fix-encoding.mjs +38 -0
- package/template/scripts/fix-svg-paths.mjs +32 -0
- package/template/scripts/generate-cf-router.mjs +52 -0
- package/template/scripts/inject-dev-tools.mjs +41 -0
- package/template/scripts/inject-version.mjs +65 -0
- package/template/scripts/make-component.mjs +445 -0
- package/template/scripts/make-component.mjs.backup +432 -0
- package/template/scripts/make-controller.mjs +119 -0
- package/template/scripts/make-core-component.mjs +303 -0
- package/template/scripts/make-view.mjs +346 -0
- package/template/scripts/minify.mjs +71 -0
- package/template/scripts/prepare-static-assets.mjs +141 -0
- package/template/scripts/prompt-bot-build.mjs +223 -0
- package/template/scripts/remove-component.mjs +170 -0
- package/template/scripts/remove-core-component.mjs +156 -0
- package/template/scripts/remove-dev.mjs +13 -0
- package/template/scripts/remove-view.mjs +200 -0
- package/template/scripts/strip-dev-blocks.mjs +30 -0
- package/template/scripts/watch-compile.mjs +69 -0
- package/template/server.js +1066 -0
- package/template/src/app.ts +115 -0
- package/template/src/components/appRegistry.ts +8 -0
- package/template/src/components/core/app-footer.ts +27 -0
- package/template/src/components/core/app-header.ts +175 -0
- package/template/src/components/core/app-sidebar.ts +238 -0
- package/template/src/components/core/loading-spinner.ts +25 -0
- package/template/src/components/core/nc-a.ts +313 -0
- package/template/src/components/core/nc-accordion.ts +186 -0
- package/template/src/components/core/nc-alert.ts +153 -0
- package/template/src/components/core/nc-animation.ts +1150 -0
- package/template/src/components/core/nc-autocomplete.ts +271 -0
- package/template/src/components/core/nc-avatar-group.ts +113 -0
- package/template/src/components/core/nc-avatar.ts +148 -0
- package/template/src/components/core/nc-badge.ts +86 -0
- package/template/src/components/core/nc-bottom-nav.ts +214 -0
- package/template/src/components/core/nc-breadcrumb.ts +96 -0
- package/template/src/components/core/nc-button.ts +307 -0
- package/template/src/components/core/nc-card.ts +160 -0
- package/template/src/components/core/nc-checkbox.ts +282 -0
- package/template/src/components/core/nc-chip.ts +115 -0
- package/template/src/components/core/nc-code.ts +314 -0
- package/template/src/components/core/nc-collapsible.ts +154 -0
- package/template/src/components/core/nc-color-picker.ts +268 -0
- package/template/src/components/core/nc-copy-button.ts +119 -0
- package/template/src/components/core/nc-date-picker.ts +443 -0
- package/template/src/components/core/nc-div.ts +280 -0
- package/template/src/components/core/nc-divider.ts +81 -0
- package/template/src/components/core/nc-drawer.ts +230 -0
- package/template/src/components/core/nc-dropdown.ts +178 -0
- package/template/src/components/core/nc-empty-state.ts +134 -0
- package/template/src/components/core/nc-file-upload.ts +354 -0
- package/template/src/components/core/nc-form.ts +312 -0
- package/template/src/components/core/nc-image.ts +184 -0
- package/template/src/components/core/nc-input.ts +383 -0
- package/template/src/components/core/nc-kbd.ts +48 -0
- package/template/src/components/core/nc-menu-item.ts +193 -0
- package/template/src/components/core/nc-menu.ts +376 -0
- package/template/src/components/core/nc-modal.ts +238 -0
- package/template/src/components/core/nc-nav-item.ts +151 -0
- package/template/src/components/core/nc-number-input.ts +350 -0
- package/template/src/components/core/nc-otp-input.ts +235 -0
- package/template/src/components/core/nc-pagination.ts +178 -0
- package/template/src/components/core/nc-popover.ts +260 -0
- package/template/src/components/core/nc-progress-circular.ts +119 -0
- package/template/src/components/core/nc-progress.ts +134 -0
- package/template/src/components/core/nc-radio.ts +235 -0
- package/template/src/components/core/nc-rating.ts +266 -0
- package/template/src/components/core/nc-rich-text.ts +283 -0
- package/template/src/components/core/nc-scroll-top.ts +116 -0
- package/template/src/components/core/nc-select.ts +452 -0
- package/template/src/components/core/nc-skeleton.ts +107 -0
- package/template/src/components/core/nc-slider.ts +285 -0
- package/template/src/components/core/nc-snackbar.ts +230 -0
- package/template/src/components/core/nc-splash.ts +343 -0
- package/template/src/components/core/nc-stepper.ts +247 -0
- package/template/src/components/core/nc-switch.ts +281 -0
- package/template/src/components/core/nc-tab-item.ts +138 -0
- package/template/src/components/core/nc-table.ts +279 -0
- package/template/src/components/core/nc-tabs.ts +554 -0
- package/template/src/components/core/nc-tag-input.ts +279 -0
- package/template/src/components/core/nc-textarea.ts +216 -0
- package/template/src/components/core/nc-time-picker.ts +438 -0
- package/template/src/components/core/nc-timeline.ts +186 -0
- package/template/src/components/core/nc-tooltip.ts +143 -0
- package/template/src/components/frameworkRegistry.ts +68 -0
- package/template/src/components/preloadRegistry.ts +28 -0
- package/template/src/components/registry.ts +8 -0
- package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
- package/template/src/constants/apiEndpoints.ts +27 -0
- package/template/src/constants/errorMessages.ts +23 -0
- package/template/src/constants/index.ts +8 -0
- package/template/src/constants/routePaths.ts +15 -0
- package/template/src/constants/storageKeys.ts +18 -0
- package/template/src/controllers/dashboard.controller.ts +200 -0
- package/template/src/controllers/home.controller.ts +21 -0
- package/template/src/controllers/index.ts +11 -0
- package/template/src/controllers/login.controller.ts +131 -0
- package/template/src/core/component.ts +354 -0
- package/template/src/core/errorHandler.ts +85 -0
- package/template/src/core/gpu-animation.ts +604 -0
- package/template/src/core/http.ts +173 -0
- package/template/src/core/lazyComponents.ts +90 -0
- package/template/src/core/router.ts +642 -0
- package/template/src/core/signals.ts +146 -0
- package/template/src/core/state.ts +248 -0
- package/template/src/dev/component-editor.ts +1363 -0
- package/template/src/dev/component-overlay.ts +278 -0
- package/template/src/dev/context-menu.ts +223 -0
- package/template/src/dev/denc-tools.ts +250 -0
- package/template/src/dev/hmr.ts +189 -0
- package/template/src/dev/nfbs.code-workspace +27 -0
- package/template/src/dev/outline-panel.ts +1247 -0
- package/template/src/middleware/auth.middleware.ts +23 -0
- package/template/src/routes/routes.ts +38 -0
- package/template/src/services/api.service.ts +394 -0
- package/template/src/services/auth.service.ts +176 -0
- package/template/src/services/index.ts +8 -0
- package/template/src/services/logger.service.ts +74 -0
- package/template/src/services/storage.service.ts +88 -0
- package/template/src/stores/appStore.ts +57 -0
- package/template/src/stores/uiStore.ts +36 -0
- package/template/src/styles/core-variables.css +219 -0
- package/template/src/styles/core.css +710 -0
- package/template/src/styles/main.css +3164 -0
- package/template/src/styles/variables.css +152 -0
- package/template/src/types/global.d.ts +47 -0
- package/template/src/utils/cacheBuster.ts +20 -0
- package/template/src/utils/dom.ts +149 -0
- package/template/src/utils/events.ts +203 -0
- package/template/src/utils/form.ts +176 -0
- package/template/src/utils/formatters.ts +169 -0
- package/template/src/utils/helpers.ts +195 -0
- package/template/src/utils/markdown.ts +307 -0
- package/template/src/utils/sidebar.ts +96 -0
- package/template/src/utils/smoothScroll.ts +85 -0
- package/template/src/utils/templates.ts +23 -0
- package/template/src/utils/validation.ts +73 -0
- package/template/src/views/protected/dashboard.html +293 -0
- package/template/src/views/public/home.html +150 -0
- package/template/src/views/public/login.html +102 -0
- package/template/tests/unit/component.test.ts +87 -0
- package/template/tests/unit/computed.test.ts +79 -0
- package/template/tests/unit/form.test.ts +68 -0
- package/template/tests/unit/formatters.test.ts +49 -0
- package/template/tests/unit/lazy-components.test.ts +59 -0
- package/template/tests/unit/markdown.test.ts +62 -0
- package/template/tests/unit/router.test.ts +112 -0
- package/template/tests/unit/signals.test.ts +54 -0
- package/template/tests/unit/validation.test.ts +50 -0
- package/template/tsconfig.build.json +21 -0
- package/template/tsconfig.json +51 -0
- package/template/vitest.config.ts +36 -0
|
@@ -0,0 +1,1247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outline Panel - DOM Tree View
|
|
3
|
+
*
|
|
4
|
+
* Shows a collapsible left-side panel with the full DOM tree
|
|
5
|
+
* of the page, including both custom components and regular elements.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class OutlinePanel {
|
|
9
|
+
private onEdit: (element: HTMLElement, fromOutlineTree?: boolean) => void;
|
|
10
|
+
private panel: HTMLElement | null = null;
|
|
11
|
+
private tab: HTMLElement | null = null;
|
|
12
|
+
private isOpen: boolean = false;
|
|
13
|
+
private isVisible: boolean = true;
|
|
14
|
+
private mutationObserver: MutationObserver | null = null;
|
|
15
|
+
private refreshTimeout: number | null = null;
|
|
16
|
+
|
|
17
|
+
constructor(onEdit: (element: HTMLElement, fromOutlineTree?: boolean) => void) {
|
|
18
|
+
this.onEdit = onEdit;
|
|
19
|
+
this.injectStyles();
|
|
20
|
+
this.createPanel();
|
|
21
|
+
// Disable auto-refresh to prevent loops
|
|
22
|
+
// this.setupMutationObserver();
|
|
23
|
+
|
|
24
|
+
// Listen for current element from editor
|
|
25
|
+
document.addEventListener('nc-current-element-response', (e: Event) => {
|
|
26
|
+
const element = (e as CustomEvent).detail.element;
|
|
27
|
+
if (element && this.isOpen) {
|
|
28
|
+
this.expandToElement(element);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Inject panel styles
|
|
35
|
+
*/
|
|
36
|
+
private injectStyles(): void {
|
|
37
|
+
const styleId = 'nativecore-outline-styles';
|
|
38
|
+
if (document.getElementById(styleId)) return;
|
|
39
|
+
|
|
40
|
+
const style = document.createElement('style');
|
|
41
|
+
style.id = styleId;
|
|
42
|
+
style.textContent = `
|
|
43
|
+
.nc-outline-panel {
|
|
44
|
+
position: fixed;
|
|
45
|
+
left: 0;
|
|
46
|
+
top: 0;
|
|
47
|
+
height: 100vh;
|
|
48
|
+
min-width: 280px;
|
|
49
|
+
max-width: 600px;
|
|
50
|
+
width: 280px;
|
|
51
|
+
background: #0f1520;
|
|
52
|
+
border-right: 1px solid #3a485b;
|
|
53
|
+
box-shadow: 2px 0 10px rgba(0,0,0,0.3);
|
|
54
|
+
z-index: 999998;
|
|
55
|
+
transition: transform 0.2s ease, width 0.3s ease;
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.nc-outline-panel.closed {
|
|
61
|
+
transform: translateX(-100%);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.nc-outline-tab {
|
|
65
|
+
position: fixed;
|
|
66
|
+
left: 0;
|
|
67
|
+
top: 50%;
|
|
68
|
+
transform: translateY(-50%);
|
|
69
|
+
width: 40px;
|
|
70
|
+
height: 140px;
|
|
71
|
+
background: #182130;
|
|
72
|
+
color: #e7eef7;
|
|
73
|
+
border: 1px solid #3a485b;
|
|
74
|
+
border-left: 0;
|
|
75
|
+
border-radius: 0 12px 12px 0;
|
|
76
|
+
box-shadow: 2px 2px 8px rgba(0,0,0,0.3);
|
|
77
|
+
writing-mode: vertical-rl;
|
|
78
|
+
text-orientation: mixed;
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
justify-content: center;
|
|
82
|
+
font-weight: 700;
|
|
83
|
+
font-size: 13px;
|
|
84
|
+
letter-spacing: 2px;
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
transition: all 0.2s ease;
|
|
87
|
+
z-index: 999999;
|
|
88
|
+
opacity: 1;
|
|
89
|
+
pointer-events: auto;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.nc-outline-tab.hidden {
|
|
93
|
+
opacity: 0;
|
|
94
|
+
pointer-events: none;
|
|
95
|
+
transform: translateY(-50%) translateX(-50px);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.nc-outline-tab:hover {
|
|
99
|
+
background: #1f2838;
|
|
100
|
+
transform: translateY(-50%) translateX(4px);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.nc-outline-content {
|
|
104
|
+
width: 100%;
|
|
105
|
+
height: 100%;
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
font-family: 'Fira Code', 'Courier New', monospace;
|
|
109
|
+
font-size: 12px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.nc-outline-header {
|
|
113
|
+
position: sticky;
|
|
114
|
+
top: 0;
|
|
115
|
+
z-index: 10;
|
|
116
|
+
background: #0f1520;
|
|
117
|
+
display: flex;
|
|
118
|
+
justify-content: space-between;
|
|
119
|
+
align-items: center;
|
|
120
|
+
padding: 20px 20px 12px 20px;
|
|
121
|
+
border-bottom: 1px solid #3a485b;
|
|
122
|
+
white-space: nowrap;
|
|
123
|
+
flex-shrink: 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.nc-outline-body {
|
|
127
|
+
flex: 1;
|
|
128
|
+
overflow-y: auto;
|
|
129
|
+
overflow-x: hidden;
|
|
130
|
+
padding: 10px 0;
|
|
131
|
+
scrollbar-width: thin;
|
|
132
|
+
scrollbar-color: #3a485b #0f1520;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.nc-outline-body::-webkit-scrollbar {
|
|
136
|
+
width: 8px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.nc-outline-body::-webkit-scrollbar-track {
|
|
140
|
+
background: #0f1520;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.nc-outline-body::-webkit-scrollbar-thumb {
|
|
144
|
+
background: #3a485b;
|
|
145
|
+
border-radius: 4px;
|
|
146
|
+
transition: background 0.2s;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.nc-outline-body::-webkit-scrollbar-thumb:hover {
|
|
150
|
+
background: #4b5e75;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.nc-outline-title {
|
|
154
|
+
font-size: 14px;
|
|
155
|
+
font-weight: 700;
|
|
156
|
+
color: #e7eef7;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.nc-outline-close {
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
padding: 4px 8px;
|
|
162
|
+
border-radius: 6px;
|
|
163
|
+
background: transparent;
|
|
164
|
+
color: #6c7a8a;
|
|
165
|
+
transition: all 0.15s;
|
|
166
|
+
font-size: 16px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.nc-outline-close:hover {
|
|
170
|
+
background: #182130;
|
|
171
|
+
color: #e7eef7;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.nc-outline-tree {
|
|
175
|
+
padding: 0 10px;
|
|
176
|
+
width: max-content;
|
|
177
|
+
min-width: 100%;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.nc-tree-node {
|
|
181
|
+
font-size: 12px;
|
|
182
|
+
padding: 5px 8px;
|
|
183
|
+
border-radius: 6px;
|
|
184
|
+
cursor: pointer;
|
|
185
|
+
margin: 1px 0;
|
|
186
|
+
transition: background 0.15s;
|
|
187
|
+
user-select: none;
|
|
188
|
+
color: #c9d4e0;
|
|
189
|
+
display: flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
gap: 6px;
|
|
192
|
+
white-space: nowrap;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.nc-tree-node:hover {
|
|
196
|
+
background: #182130;
|
|
197
|
+
cursor: pointer;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.nc-tree-node.custom-component {
|
|
201
|
+
background: rgba(102,126,234,0.08);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.nc-tree-node.custom-component:hover {
|
|
205
|
+
background: rgba(102,126,234,0.15);
|
|
206
|
+
cursor: pointer;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@media (max-width: 768px) {
|
|
210
|
+
.nc-outline-panel,
|
|
211
|
+
.nc-outline-tab {
|
|
212
|
+
display: none !important;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.nc-tree-toggle {
|
|
217
|
+
width: 12px;
|
|
218
|
+
height: 12px;
|
|
219
|
+
display: inline-flex;
|
|
220
|
+
align-items: center;
|
|
221
|
+
justify-content: center;
|
|
222
|
+
color: #6c7a8a;
|
|
223
|
+
font-size: 10px;
|
|
224
|
+
flex-shrink: 0;
|
|
225
|
+
transition: transform 0.2s;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.nc-tree-toggle.collapsed {
|
|
229
|
+
transform: rotate(-90deg);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.nc-tree-toggle:hover {
|
|
233
|
+
color: #9ad1ff;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.nc-tree-content {
|
|
237
|
+
display: flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
gap: 4px;
|
|
240
|
+
flex: 1;
|
|
241
|
+
min-width: 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.nc-tree-children {
|
|
245
|
+
margin-left: 20px;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.nc-tree-children.hidden {
|
|
249
|
+
display: none;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.nc-tree-tag {
|
|
253
|
+
color: #9ad1ff;
|
|
254
|
+
font-weight: 600;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.nc-tree-tag.custom {
|
|
258
|
+
color: #b794f4;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.nc-tree-attr {
|
|
262
|
+
color: #c9d4e0;
|
|
263
|
+
opacity: 0.7;
|
|
264
|
+
font-size: 11px;
|
|
265
|
+
margin-left: 4px;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.nc-tree-gear {
|
|
269
|
+
margin-left: 8px;
|
|
270
|
+
width: 14px;
|
|
271
|
+
height: 14px;
|
|
272
|
+
fill: #5cc8ff;
|
|
273
|
+
cursor: pointer;
|
|
274
|
+
opacity: 0.5;
|
|
275
|
+
transition: opacity 0.2s;
|
|
276
|
+
vertical-align: middle;
|
|
277
|
+
display: inline-block;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.nc-tree-gear:hover {
|
|
281
|
+
opacity: 1;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* Session Storage Section */
|
|
285
|
+
.nc-storage-section {
|
|
286
|
+
border-top: 1px solid rgba(255,255,255,0.1);
|
|
287
|
+
margin-top: 1rem;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.nc-storage-header {
|
|
291
|
+
padding: 12px 16px;
|
|
292
|
+
display: flex;
|
|
293
|
+
align-items: center;
|
|
294
|
+
gap: 8px;
|
|
295
|
+
cursor: pointer;
|
|
296
|
+
background: rgba(255,255,255,0.03);
|
|
297
|
+
transition: background 0.2s;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.nc-storage-header:hover {
|
|
301
|
+
background: rgba(255,255,255,0.06);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.nc-storage-toggle {
|
|
305
|
+
font-size: 10px;
|
|
306
|
+
color: #5cc8ff;
|
|
307
|
+
transition: transform 0.2s;
|
|
308
|
+
flex-shrink: 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.nc-storage-toggle.collapsed {
|
|
312
|
+
transform: rotate(-90deg);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.nc-storage-title {
|
|
316
|
+
color: #fff;
|
|
317
|
+
font-size: 11px;
|
|
318
|
+
font-weight: 600;
|
|
319
|
+
letter-spacing: 0.5px;
|
|
320
|
+
text-transform: uppercase;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.nc-storage-content {
|
|
324
|
+
padding: 8px 16px 16px 16px;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.nc-storage-content.hidden {
|
|
328
|
+
display: none;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.nc-storage-item {
|
|
332
|
+
display: flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
justify-content: space-between;
|
|
335
|
+
padding: 8px 12px;
|
|
336
|
+
margin-bottom: 4px;
|
|
337
|
+
background: rgba(255,255,255,0.05);
|
|
338
|
+
border-radius: 4px;
|
|
339
|
+
font-size: 11px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.nc-storage-key {
|
|
343
|
+
color: #5cc8ff;
|
|
344
|
+
font-family: 'Fira Code', monospace;
|
|
345
|
+
flex: 1;
|
|
346
|
+
word-break: break-all;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.nc-storage-value {
|
|
350
|
+
color: #a0aec0;
|
|
351
|
+
margin-left: 8px;
|
|
352
|
+
flex-shrink: 0;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.nc-storage-delete {
|
|
356
|
+
margin-left: 8px;
|
|
357
|
+
padding: 2px 8px;
|
|
358
|
+
background: rgba(239, 68, 68, 0.2);
|
|
359
|
+
color: #ef4444;
|
|
360
|
+
border: none;
|
|
361
|
+
border-radius: 3px;
|
|
362
|
+
cursor: pointer;
|
|
363
|
+
font-size: 10px;
|
|
364
|
+
transition: background 0.2s;
|
|
365
|
+
flex-shrink: 0;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.nc-storage-delete:hover {
|
|
369
|
+
background: rgba(239, 68, 68, 0.4);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.nc-storage-empty {
|
|
373
|
+
color: #718096;
|
|
374
|
+
font-size: 11px;
|
|
375
|
+
font-style: italic;
|
|
376
|
+
padding: 12px;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.nc-storage-clear-all {
|
|
380
|
+
width: 100%;
|
|
381
|
+
padding: 8px;
|
|
382
|
+
margin-top: 8px;
|
|
383
|
+
background: rgba(239, 68, 68, 0.15);
|
|
384
|
+
color: #ef4444;
|
|
385
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
386
|
+
border-radius: 4px;
|
|
387
|
+
cursor: pointer;
|
|
388
|
+
font-size: 11px;
|
|
389
|
+
transition: all 0.2s;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.nc-storage-clear-all:hover {
|
|
393
|
+
background: rgba(239, 68, 68, 0.25);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* Cache Control Section */
|
|
397
|
+
.nc-cache-section {
|
|
398
|
+
border-top: 1px solid rgba(255,255,255,0.1);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.nc-cache-header {
|
|
402
|
+
padding: 12px 16px;
|
|
403
|
+
display: flex;
|
|
404
|
+
align-items: center;
|
|
405
|
+
gap: 8px;
|
|
406
|
+
cursor: pointer;
|
|
407
|
+
background: rgba(255,255,255,0.03);
|
|
408
|
+
transition: background 0.2s;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.nc-cache-header:hover {
|
|
412
|
+
background: rgba(255,255,255,0.06);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.nc-cache-toggle {
|
|
416
|
+
font-size: 10px;
|
|
417
|
+
color: #5cc8ff;
|
|
418
|
+
transition: transform 0.2s;
|
|
419
|
+
flex-shrink: 0;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.nc-cache-toggle.collapsed {
|
|
423
|
+
transform: rotate(-90deg);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.nc-cache-title {
|
|
427
|
+
color: #fff;
|
|
428
|
+
font-size: 11px;
|
|
429
|
+
font-weight: 600;
|
|
430
|
+
letter-spacing: 0.5px;
|
|
431
|
+
text-transform: uppercase;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.nc-cache-content {
|
|
435
|
+
padding: 8px 16px 16px 16px;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.nc-cache-content.hidden {
|
|
439
|
+
display: none;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.nc-cache-button {
|
|
443
|
+
width: 100%;
|
|
444
|
+
padding: 10px;
|
|
445
|
+
margin-bottom: 8px;
|
|
446
|
+
background: rgba(92, 200, 255, 0.15);
|
|
447
|
+
color: #5cc8ff;
|
|
448
|
+
border: 1px solid rgba(92, 200, 255, 0.3);
|
|
449
|
+
border-radius: 4px;
|
|
450
|
+
cursor: pointer;
|
|
451
|
+
font-size: 11px;
|
|
452
|
+
transition: all 0.2s;
|
|
453
|
+
display: flex;
|
|
454
|
+
align-items: center;
|
|
455
|
+
justify-content: center;
|
|
456
|
+
gap: 8px;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.nc-cache-button:hover {
|
|
460
|
+
background: rgba(92, 200, 255, 0.25);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.nc-cache-button.danger {
|
|
464
|
+
background: rgba(239, 68, 68, 0.15);
|
|
465
|
+
color: #ef4444;
|
|
466
|
+
border-color: rgba(239, 68, 68, 0.3);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.nc-cache-button.danger:hover {
|
|
470
|
+
background: rgba(239, 68, 68, 0.25);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.nc-cache-info {
|
|
474
|
+
color: #718096;
|
|
475
|
+
font-size: 10px;
|
|
476
|
+
padding: 8px 12px;
|
|
477
|
+
background: rgba(255,255,255,0.03);
|
|
478
|
+
border-radius: 4px;
|
|
479
|
+
margin-bottom: 8px;
|
|
480
|
+
}
|
|
481
|
+
`;
|
|
482
|
+
document.head.appendChild(style);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Create the panel
|
|
487
|
+
*/
|
|
488
|
+
private createPanel(): void {
|
|
489
|
+
// Create tab
|
|
490
|
+
const tab = document.createElement('div');
|
|
491
|
+
tab.className = 'nc-outline-tab';
|
|
492
|
+
tab.textContent = 'OUTLINE';
|
|
493
|
+
tab.addEventListener('click', () => this.togglePanel());
|
|
494
|
+
document.body.appendChild(tab);
|
|
495
|
+
this.tab = tab;
|
|
496
|
+
|
|
497
|
+
// Create panel
|
|
498
|
+
const panel = document.createElement('div');
|
|
499
|
+
panel.className = 'nc-outline-panel closed';
|
|
500
|
+
|
|
501
|
+
const content = document.createElement('div');
|
|
502
|
+
content.className = 'nc-outline-content';
|
|
503
|
+
|
|
504
|
+
// Header
|
|
505
|
+
const header = document.createElement('div');
|
|
506
|
+
header.className = 'nc-outline-header';
|
|
507
|
+
header.innerHTML = `
|
|
508
|
+
<div class="nc-outline-title">Page Outline</div>
|
|
509
|
+
<div class="nc-outline-close">✕</div>
|
|
510
|
+
`;
|
|
511
|
+
|
|
512
|
+
header.querySelector('.nc-outline-close')?.addEventListener('click', () => this.togglePanel());
|
|
513
|
+
|
|
514
|
+
content.appendChild(header);
|
|
515
|
+
|
|
516
|
+
// Body with tree
|
|
517
|
+
const body = document.createElement('div');
|
|
518
|
+
body.className = 'nc-outline-body';
|
|
519
|
+
|
|
520
|
+
// Tree container
|
|
521
|
+
const tree = document.createElement('div');
|
|
522
|
+
tree.className = 'nc-outline-tree';
|
|
523
|
+
body.appendChild(tree);
|
|
524
|
+
|
|
525
|
+
// Cache Control Section
|
|
526
|
+
const cacheSection = this.createCacheSection();
|
|
527
|
+
body.appendChild(cacheSection);
|
|
528
|
+
|
|
529
|
+
// Session Storage Section
|
|
530
|
+
const sessionStorageSection = this.createStorageSection('session');
|
|
531
|
+
body.appendChild(sessionStorageSection);
|
|
532
|
+
|
|
533
|
+
// Local Storage Section
|
|
534
|
+
const localStorageSection = this.createStorageSection('local');
|
|
535
|
+
body.appendChild(localStorageSection);
|
|
536
|
+
|
|
537
|
+
content.appendChild(body);
|
|
538
|
+
panel.appendChild(content);
|
|
539
|
+
document.body.appendChild(panel);
|
|
540
|
+
|
|
541
|
+
this.panel = panel;
|
|
542
|
+
this.refreshTree();
|
|
543
|
+
this.applyVisibility();
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
public setVisible(visible: boolean): void {
|
|
547
|
+
this.isVisible = visible;
|
|
548
|
+
|
|
549
|
+
if (!visible && this.isOpen) {
|
|
550
|
+
this.isOpen = false;
|
|
551
|
+
this.panel?.classList.add('closed');
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
this.applyVisibility();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private applyVisibility(): void {
|
|
558
|
+
if (!this.panel || !this.tab) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (!this.isVisible) {
|
|
563
|
+
this.panel.style.display = 'none';
|
|
564
|
+
this.tab.style.display = 'none';
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
this.panel.style.display = '';
|
|
569
|
+
this.tab.style.display = this.isOpen ? 'none' : '';
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Create cache control section
|
|
574
|
+
*/
|
|
575
|
+
private createCacheSection(): HTMLElement {
|
|
576
|
+
const section = document.createElement('div');
|
|
577
|
+
section.className = 'nc-cache-section nc-storage-section';
|
|
578
|
+
|
|
579
|
+
// Header
|
|
580
|
+
const header = document.createElement('div');
|
|
581
|
+
header.className = 'nc-cache-header';
|
|
582
|
+
header.innerHTML = `
|
|
583
|
+
<span class="nc-cache-toggle collapsed">▼</span>
|
|
584
|
+
<span class="nc-cache-title">Cache Control</span>
|
|
585
|
+
`;
|
|
586
|
+
|
|
587
|
+
// Content
|
|
588
|
+
const content = document.createElement('div');
|
|
589
|
+
content.className = 'nc-cache-content hidden';
|
|
590
|
+
content.innerHTML = `
|
|
591
|
+
<div class="nc-cache-info">
|
|
592
|
+
Clear browser cache to see latest CSS/JS changes
|
|
593
|
+
</div>
|
|
594
|
+
`;
|
|
595
|
+
|
|
596
|
+
// Hard Refresh Button
|
|
597
|
+
const hardRefreshBtn = document.createElement('button');
|
|
598
|
+
hardRefreshBtn.className = 'nc-cache-button';
|
|
599
|
+
hardRefreshBtn.innerHTML = `
|
|
600
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
601
|
+
<path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/>
|
|
602
|
+
</svg>
|
|
603
|
+
Hard Refresh (Ctrl+Shift+R)
|
|
604
|
+
`;
|
|
605
|
+
hardRefreshBtn.addEventListener('click', () => {
|
|
606
|
+
location.reload();
|
|
607
|
+
});
|
|
608
|
+
content.appendChild(hardRefreshBtn);
|
|
609
|
+
|
|
610
|
+
// Clear Cache & Reload Button
|
|
611
|
+
const clearCacheBtn = document.createElement('button');
|
|
612
|
+
clearCacheBtn.className = 'nc-cache-button';
|
|
613
|
+
clearCacheBtn.innerHTML = `
|
|
614
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
615
|
+
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
|
616
|
+
</svg>
|
|
617
|
+
Clear Cache & Reload
|
|
618
|
+
`;
|
|
619
|
+
clearCacheBtn.addEventListener('click', async () => {
|
|
620
|
+
if ('caches' in window) {
|
|
621
|
+
const cacheNames = await caches.keys();
|
|
622
|
+
await Promise.all(cacheNames.map(name => caches.delete(name)));
|
|
623
|
+
}
|
|
624
|
+
location.reload();
|
|
625
|
+
});
|
|
626
|
+
content.appendChild(clearCacheBtn);
|
|
627
|
+
|
|
628
|
+
// Clear All Data Button
|
|
629
|
+
const clearAllBtn = document.createElement('button');
|
|
630
|
+
clearAllBtn.className = 'nc-cache-button danger';
|
|
631
|
+
clearAllBtn.innerHTML = `
|
|
632
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
633
|
+
<path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14z"/>
|
|
634
|
+
<line x1="10" y1="11" x2="10" y2="17"/>
|
|
635
|
+
<line x1="14" y1="11" x2="14" y2="17"/>
|
|
636
|
+
</svg>
|
|
637
|
+
Clear All Data & Reload
|
|
638
|
+
`;
|
|
639
|
+
clearAllBtn.addEventListener('click', async () => {
|
|
640
|
+
if (confirm('Clear all cache, storage, and reload? This will reset everything.')) {
|
|
641
|
+
// Clear cache
|
|
642
|
+
if ('caches' in window) {
|
|
643
|
+
const cacheNames = await caches.keys();
|
|
644
|
+
await Promise.all(cacheNames.map(name => caches.delete(name)));
|
|
645
|
+
}
|
|
646
|
+
// Clear storage
|
|
647
|
+
sessionStorage.clear();
|
|
648
|
+
localStorage.clear();
|
|
649
|
+
// Reload
|
|
650
|
+
location.reload();
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
content.appendChild(clearAllBtn);
|
|
654
|
+
|
|
655
|
+
// Toggle functionality
|
|
656
|
+
header.addEventListener('click', () => {
|
|
657
|
+
const toggle = header.querySelector('.nc-cache-toggle');
|
|
658
|
+
if (content.classList.contains('hidden')) {
|
|
659
|
+
content.classList.remove('hidden');
|
|
660
|
+
toggle?.classList.remove('collapsed');
|
|
661
|
+
} else {
|
|
662
|
+
content.classList.add('hidden');
|
|
663
|
+
toggle?.classList.add('collapsed');
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
section.appendChild(header);
|
|
668
|
+
section.appendChild(content);
|
|
669
|
+
|
|
670
|
+
return section;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Create storage management section
|
|
675
|
+
*/
|
|
676
|
+
private createStorageSection(type: 'session' | 'local'): HTMLElement {
|
|
677
|
+
const section = document.createElement('div');
|
|
678
|
+
section.className = 'nc-storage-section';
|
|
679
|
+
|
|
680
|
+
const storageObj = type === 'session' ? sessionStorage : localStorage;
|
|
681
|
+
const title = type === 'session' ? 'Session Storage' : 'Local Storage';
|
|
682
|
+
const contentId = type === 'session' ? 'storageContent' : 'localStorageContent';
|
|
683
|
+
|
|
684
|
+
// Header
|
|
685
|
+
const header = document.createElement('div');
|
|
686
|
+
header.className = 'nc-storage-header';
|
|
687
|
+
header.innerHTML = `
|
|
688
|
+
<span class="nc-storage-toggle collapsed">▼</span>
|
|
689
|
+
<span class="nc-storage-title">${title}</span>
|
|
690
|
+
`;
|
|
691
|
+
|
|
692
|
+
// Content
|
|
693
|
+
const content = document.createElement('div');
|
|
694
|
+
content.className = 'nc-storage-content hidden';
|
|
695
|
+
content.id = contentId;
|
|
696
|
+
|
|
697
|
+
this.refreshStorageContent(content, storageObj);
|
|
698
|
+
|
|
699
|
+
// Toggle functionality
|
|
700
|
+
header.addEventListener('click', () => {
|
|
701
|
+
const toggle = header.querySelector('.nc-storage-toggle');
|
|
702
|
+
if (content.classList.contains('hidden')) {
|
|
703
|
+
content.classList.remove('hidden');
|
|
704
|
+
toggle?.classList.remove('collapsed');
|
|
705
|
+
} else {
|
|
706
|
+
content.classList.add('hidden');
|
|
707
|
+
toggle?.classList.add('collapsed');
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
section.appendChild(header);
|
|
712
|
+
section.appendChild(content);
|
|
713
|
+
|
|
714
|
+
return section;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Refresh storage content
|
|
719
|
+
*/
|
|
720
|
+
private refreshStorageContent(container: HTMLElement, storageObj: Storage): void {
|
|
721
|
+
container.innerHTML = '';
|
|
722
|
+
|
|
723
|
+
const items: Array<{key: string, value: string}> = [];
|
|
724
|
+
for (let i = 0; i < storageObj.length; i++) {
|
|
725
|
+
const key = storageObj.key(i);
|
|
726
|
+
if (key) {
|
|
727
|
+
items.push({
|
|
728
|
+
key,
|
|
729
|
+
value: storageObj.getItem(key) || ''
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (items.length === 0) {
|
|
735
|
+
container.innerHTML = '<div class="nc-storage-empty">No items in storage</div>';
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
items.forEach(({key, value}) => {
|
|
740
|
+
const item = document.createElement('div');
|
|
741
|
+
item.className = 'nc-storage-item';
|
|
742
|
+
|
|
743
|
+
const keySpan = document.createElement('span');
|
|
744
|
+
keySpan.className = 'nc-storage-key';
|
|
745
|
+
keySpan.textContent = key;
|
|
746
|
+
|
|
747
|
+
const valueSpan = document.createElement('span');
|
|
748
|
+
valueSpan.className = 'nc-storage-value';
|
|
749
|
+
valueSpan.textContent = value.length > 20 ? value.substring(0, 20) + '...' : value;
|
|
750
|
+
valueSpan.title = value; // Show full value on hover
|
|
751
|
+
|
|
752
|
+
const deleteBtn = document.createElement('button');
|
|
753
|
+
deleteBtn.className = 'nc-storage-delete';
|
|
754
|
+
deleteBtn.textContent = 'Delete';
|
|
755
|
+
deleteBtn.addEventListener('click', () => {
|
|
756
|
+
storageObj.removeItem(key);
|
|
757
|
+
this.refreshStorageContent(container, storageObj);
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
item.appendChild(keySpan);
|
|
761
|
+
item.appendChild(valueSpan);
|
|
762
|
+
item.appendChild(deleteBtn);
|
|
763
|
+
container.appendChild(item);
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
// Add clear all button
|
|
767
|
+
if (items.length > 0) {
|
|
768
|
+
const clearAllBtn = document.createElement('button');
|
|
769
|
+
clearAllBtn.className = 'nc-storage-clear-all';
|
|
770
|
+
clearAllBtn.textContent = `Clear All (${items.length} items)`;
|
|
771
|
+
clearAllBtn.addEventListener('click', () => {
|
|
772
|
+
if (confirm('Clear all items from this storage?')) {
|
|
773
|
+
storageObj.clear();
|
|
774
|
+
this.refreshStorageContent(container, storageObj);
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
container.appendChild(clearAllBtn);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Toggle panel open/closed
|
|
783
|
+
*/
|
|
784
|
+
private togglePanel(): void {
|
|
785
|
+
if (!this.isVisible) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
this.isOpen = !this.isOpen;
|
|
790
|
+
const tab = this.tab;
|
|
791
|
+
|
|
792
|
+
if (this.isOpen) {
|
|
793
|
+
this.panel?.classList.remove('closed');
|
|
794
|
+
tab?.classList.add('hidden');
|
|
795
|
+
this.refreshTree();
|
|
796
|
+
|
|
797
|
+
// Refresh storage content when opening
|
|
798
|
+
const storageContent = this.panel?.querySelector('#storageContent');
|
|
799
|
+
if (storageContent) {
|
|
800
|
+
this.refreshStorageContent(storageContent as HTMLElement, sessionStorage);
|
|
801
|
+
}
|
|
802
|
+
const localStorageContent = this.panel?.querySelector('#localStorageContent');
|
|
803
|
+
if (localStorageContent) {
|
|
804
|
+
this.refreshStorageContent(localStorageContent as HTMLElement, localStorage);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Request current element from editor and expand to it
|
|
808
|
+
setTimeout(() => {
|
|
809
|
+
const event = new CustomEvent('nc-request-current-element');
|
|
810
|
+
document.dispatchEvent(event);
|
|
811
|
+
}, 100); // Small delay to ensure tree is rendered
|
|
812
|
+
} else {
|
|
813
|
+
this.panel?.classList.add('closed');
|
|
814
|
+
tab?.classList.remove('hidden');
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
this.applyVisibility();
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Refresh the tree view
|
|
822
|
+
*/
|
|
823
|
+
private refreshTree(): void {
|
|
824
|
+
const tree = this.panel?.querySelector('.nc-outline-tree');
|
|
825
|
+
if (!tree) return;
|
|
826
|
+
|
|
827
|
+
tree.innerHTML = '';
|
|
828
|
+
|
|
829
|
+
// Build tree starting from body itself to show complete DOM
|
|
830
|
+
this.buildTree(document.body, tree as HTMLElement, -1);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Check if element is a dev tool element that should be excluded from tree
|
|
835
|
+
*/
|
|
836
|
+
private isDevToolElement(element: Element): boolean {
|
|
837
|
+
const tagName = (element as HTMLElement).tagName?.toLowerCase();
|
|
838
|
+
const classList = (element as HTMLElement).classList;
|
|
839
|
+
const id = (element as HTMLElement).id?.toLowerCase();
|
|
840
|
+
|
|
841
|
+
// Check tag names
|
|
842
|
+
if (tagName?.startsWith('nc-dev') ||
|
|
843
|
+
tagName?.startsWith('nc-outline') ||
|
|
844
|
+
tagName?.startsWith('nc-editor') ||
|
|
845
|
+
tagName === 'hmr-indicator' ||
|
|
846
|
+
tagName === 'nativecore-denc-indicator') {
|
|
847
|
+
return true;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Check class names
|
|
851
|
+
if (classList?.contains('nc-denc-overlay') ||
|
|
852
|
+
classList?.contains('nc-editor-overlay') ||
|
|
853
|
+
classList?.contains('nc-editor-modal') ||
|
|
854
|
+
classList?.contains('nc-context-menu') ||
|
|
855
|
+
classList?.contains('nc-component-overlay') ||
|
|
856
|
+
classList?.contains('nc-outline-panel') ||
|
|
857
|
+
classList?.contains('nc-outline-tab') ||
|
|
858
|
+
classList?.contains('hmr-indicator') ||
|
|
859
|
+
classList?.contains('nativecore-denc-indicator')) {
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Check IDs
|
|
864
|
+
if (id?.includes('nc-dev') ||
|
|
865
|
+
id?.includes('nc-editor') ||
|
|
866
|
+
id?.includes('nc-outline') ||
|
|
867
|
+
id?.includes('hmr-') ||
|
|
868
|
+
id?.includes('nativecore-dev') ||
|
|
869
|
+
id?.includes('denc-indicator')) {
|
|
870
|
+
return true;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Check data attributes
|
|
874
|
+
if ((element as HTMLElement).hasAttribute('data-nc-dev') ||
|
|
875
|
+
(element as HTMLElement).hasAttribute('data-denc-tool')) {
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Build tree recursively - collapsible nodes
|
|
884
|
+
*/
|
|
885
|
+
private buildTree(element: Element, container: HTMLElement, level: number = 0): void {
|
|
886
|
+
// Skip dev tools elements
|
|
887
|
+
if (this.isDevToolElement(element)) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Skip script, style, noscript, link, meta
|
|
892
|
+
const tagName = element.tagName.toLowerCase();
|
|
893
|
+
if (['script', 'style', 'noscript', 'link', 'meta'].includes(tagName)) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const isCustom = this.isCustomComponent(element as HTMLElement);
|
|
898
|
+
const hasChildren = Array.from(element.children).some(child => {
|
|
899
|
+
const childTag = child.tagName.toLowerCase();
|
|
900
|
+
return !['script', 'style', 'noscript', 'link', 'meta'].includes(childTag) &&
|
|
901
|
+
!this.isDevToolElement(child);
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
// Create node wrapper
|
|
905
|
+
const wrapper = document.createElement('div');
|
|
906
|
+
|
|
907
|
+
// Create node row
|
|
908
|
+
const nodeDiv = document.createElement('div');
|
|
909
|
+
nodeDiv.className = `nc-tree-node ${isCustom ? 'custom-component' : ''}`;
|
|
910
|
+
nodeDiv.style.paddingLeft = `${level * 14 + 8}px`;
|
|
911
|
+
|
|
912
|
+
// Toggle arrow for nodes with children
|
|
913
|
+
let toggle: HTMLSpanElement | null = null;
|
|
914
|
+
if (hasChildren) {
|
|
915
|
+
toggle = document.createElement('span');
|
|
916
|
+
toggle.className = 'nc-tree-toggle';
|
|
917
|
+
toggle.textContent = '▼';
|
|
918
|
+
nodeDiv.appendChild(toggle);
|
|
919
|
+
} else {
|
|
920
|
+
// Spacer for alignment
|
|
921
|
+
const spacer = document.createElement('span');
|
|
922
|
+
spacer.style.width = '12px';
|
|
923
|
+
spacer.style.display = 'inline-block';
|
|
924
|
+
spacer.style.flexShrink = '0';
|
|
925
|
+
nodeDiv.appendChild(spacer);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Content wrapper
|
|
929
|
+
const content = document.createElement('div');
|
|
930
|
+
content.className = 'nc-tree-content';
|
|
931
|
+
|
|
932
|
+
// Build content: <tag> #id .class
|
|
933
|
+
const id = element.id ? `#${element.id}` : '';
|
|
934
|
+
const firstClass = element.classList.length > 0 ? `.${element.classList[0]}` : '';
|
|
935
|
+
|
|
936
|
+
// Only add gear icon for custom components
|
|
937
|
+
const gearHtml = isCustom ? `
|
|
938
|
+
<svg class="nc-tree-gear" viewBox="0 0 24 24">
|
|
939
|
+
<path d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/>
|
|
940
|
+
</svg>
|
|
941
|
+
` : '';
|
|
942
|
+
|
|
943
|
+
content.innerHTML = `
|
|
944
|
+
<span class="nc-tree-tag ${isCustom ? 'custom' : ''}"><${tagName}></span>
|
|
945
|
+
<span class="nc-tree-attr">${id}${firstClass}</span>
|
|
946
|
+
${gearHtml}
|
|
947
|
+
`;
|
|
948
|
+
|
|
949
|
+
// Gear icon click (only for custom components)
|
|
950
|
+
if (isCustom) {
|
|
951
|
+
const gear = content.querySelector('.nc-tree-gear');
|
|
952
|
+
gear?.addEventListener('click', (e) => {
|
|
953
|
+
e.stopPropagation();
|
|
954
|
+
this.onEdit(element as HTMLElement, true); // true = from outline tree
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
nodeDiv.appendChild(content);
|
|
959
|
+
wrapper.appendChild(nodeDiv);
|
|
960
|
+
|
|
961
|
+
// Children container
|
|
962
|
+
let childrenContainer: HTMLDivElement | null = null;
|
|
963
|
+
if (hasChildren) {
|
|
964
|
+
childrenContainer = document.createElement('div');
|
|
965
|
+
childrenContainer.className = 'nc-tree-children hidden';
|
|
966
|
+
|
|
967
|
+
// Build children recursively
|
|
968
|
+
Array.from(element.children).forEach(child => {
|
|
969
|
+
this.buildTree(child, childrenContainer!, level + 1);
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
wrapper.appendChild(childrenContainer);
|
|
973
|
+
|
|
974
|
+
// Start collapsed by default
|
|
975
|
+
if (toggle) {
|
|
976
|
+
toggle.classList.add('collapsed');
|
|
977
|
+
|
|
978
|
+
// Toggle functionality - clicking anywhere on the row expands/collapses
|
|
979
|
+
nodeDiv.addEventListener('click', (e) => {
|
|
980
|
+
// Don't toggle if clicking the gear icon
|
|
981
|
+
if ((e.target as HTMLElement).closest('.nc-tree-gear')) {
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
e.stopPropagation();
|
|
986
|
+
const isCollapsed = toggle.classList.contains('collapsed');
|
|
987
|
+
if (isCollapsed) {
|
|
988
|
+
toggle.classList.remove('collapsed');
|
|
989
|
+
childrenContainer!.classList.remove('hidden');
|
|
990
|
+
// Expand panel width if needed for deep nesting
|
|
991
|
+
this.adjustPanelWidth();
|
|
992
|
+
} else {
|
|
993
|
+
toggle.classList.add('collapsed');
|
|
994
|
+
childrenContainer!.classList.add('hidden');
|
|
995
|
+
// Recalculate optimal width
|
|
996
|
+
this.adjustPanelWidth();
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Node click to highlight on page (for nodes without children)
|
|
1003
|
+
if (!hasChildren) {
|
|
1004
|
+
content.addEventListener('click', (e) => {
|
|
1005
|
+
e.stopPropagation();
|
|
1006
|
+
|
|
1007
|
+
// If this is a regular element (not custom component), close the editor panel
|
|
1008
|
+
if (!isCustom) {
|
|
1009
|
+
// Dispatch event to close editor
|
|
1010
|
+
document.dispatchEvent(new CustomEvent('nc-close-editor'));
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// Highlight element on page
|
|
1014
|
+
(element as HTMLElement).style.outline = '2px solid #5cc8ff';
|
|
1015
|
+
(element as HTMLElement).style.outlineOffset = '2px';
|
|
1016
|
+
setTimeout(() => {
|
|
1017
|
+
(element as HTMLElement).style.outline = '';
|
|
1018
|
+
(element as HTMLElement).style.outlineOffset = '';
|
|
1019
|
+
}, 2000);
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
container.appendChild(wrapper);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Adjust panel width dynamically based on visible content
|
|
1028
|
+
*/
|
|
1029
|
+
private adjustPanelWidth(): void {
|
|
1030
|
+
if (!this.panel) return;
|
|
1031
|
+
|
|
1032
|
+
// Find the maximum depth of visible (non-hidden) nodes
|
|
1033
|
+
const tree = this.panel.querySelector('.nc-outline-tree');
|
|
1034
|
+
if (!tree) return;
|
|
1035
|
+
|
|
1036
|
+
let maxDepth = 0;
|
|
1037
|
+
const visibleNodes = tree.querySelectorAll('.nc-tree-node');
|
|
1038
|
+
|
|
1039
|
+
visibleNodes.forEach(node => {
|
|
1040
|
+
const parent = node.parentElement;
|
|
1041
|
+
// Check if any ancestor has 'hidden' class
|
|
1042
|
+
let current: HTMLElement | null = parent as HTMLElement;
|
|
1043
|
+
let isVisible = true;
|
|
1044
|
+
|
|
1045
|
+
while (current && current !== tree) {
|
|
1046
|
+
if (current.classList?.contains('hidden')) {
|
|
1047
|
+
isVisible = false;
|
|
1048
|
+
break;
|
|
1049
|
+
}
|
|
1050
|
+
current = current.parentElement;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (isVisible) {
|
|
1054
|
+
const paddingLeft = parseInt((node as HTMLElement).style.paddingLeft || '0');
|
|
1055
|
+
maxDepth = Math.max(maxDepth, paddingLeft);
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
// Calculate optimal width: base + depth padding + buffer for gear icon
|
|
1060
|
+
const optimalWidth = Math.max(280, Math.min(600, 260 + maxDepth + 100));
|
|
1061
|
+
this.panel.style.width = `${optimalWidth}px`;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* Check if element is a custom component
|
|
1066
|
+
*/
|
|
1067
|
+
private isCustomComponent(element: HTMLElement): boolean {
|
|
1068
|
+
const tagName = element.tagName.toLowerCase();
|
|
1069
|
+
return tagName.includes('-') &&
|
|
1070
|
+
!tagName.startsWith('nc-dev') &&
|
|
1071
|
+
!tagName.startsWith('ion-') &&
|
|
1072
|
+
!tagName.startsWith('mat-');
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Setup mutation observer to refresh tree on DOM changes
|
|
1077
|
+
*/
|
|
1078
|
+
private setupMutationObserver(): void {
|
|
1079
|
+
this.mutationObserver = new MutationObserver((mutations) => {
|
|
1080
|
+
// Ignore mutations inside the outline panel itself
|
|
1081
|
+
const isOutlineChange = mutations.some(mutation => {
|
|
1082
|
+
const target = mutation.target as HTMLElement;
|
|
1083
|
+
return target.closest('.nc-outline-panel') ||
|
|
1084
|
+
target.classList?.contains('nc-outline-panel') ||
|
|
1085
|
+
target.classList?.contains('nc-outline-tab');
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
if (isOutlineChange) return;
|
|
1089
|
+
|
|
1090
|
+
// Throttle refreshes
|
|
1091
|
+
if (this.isOpen && !this.refreshTimeout) {
|
|
1092
|
+
this.refreshTimeout = window.setTimeout(() => {
|
|
1093
|
+
this.refreshTree();
|
|
1094
|
+
this.refreshTimeout = null;
|
|
1095
|
+
}, 500);
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
this.mutationObserver.observe(document.body, {
|
|
1100
|
+
childList: true,
|
|
1101
|
+
subtree: true,
|
|
1102
|
+
attributes: true,
|
|
1103
|
+
attributeFilter: ['class', 'id']
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Expand tree to show a specific element
|
|
1109
|
+
*/
|
|
1110
|
+
/**
|
|
1111
|
+
* Expand the outline tree to show a specific element
|
|
1112
|
+
* Public method to be called when gear icon is clicked
|
|
1113
|
+
*/
|
|
1114
|
+
public expandToElement(targetElement: HTMLElement): void {
|
|
1115
|
+
console.log('[OutlinePanel] Expanding to element:', targetElement.tagName);
|
|
1116
|
+
|
|
1117
|
+
// Find all parent elements up to body
|
|
1118
|
+
const parents: Element[] = [];
|
|
1119
|
+
let current: Element | null = targetElement;
|
|
1120
|
+
|
|
1121
|
+
while (current && current !== document.body) {
|
|
1122
|
+
parents.unshift(current);
|
|
1123
|
+
current = current.parentElement;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
console.log('[OutlinePanel] Parents to expand:', parents.map(p => p.tagName));
|
|
1127
|
+
|
|
1128
|
+
// Build a path of indices to identify the exact element
|
|
1129
|
+
const pathIndices: number[] = [];
|
|
1130
|
+
let elem: Element | null = targetElement;
|
|
1131
|
+
while (elem && elem !== document.body) {
|
|
1132
|
+
const parent = elem.parentElement;
|
|
1133
|
+
if (parent) {
|
|
1134
|
+
const siblings = Array.from(parent.children).filter(child =>
|
|
1135
|
+
child.tagName === elem!.tagName
|
|
1136
|
+
);
|
|
1137
|
+
pathIndices.unshift(siblings.indexOf(elem));
|
|
1138
|
+
}
|
|
1139
|
+
elem = parent;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
console.log('[OutlinePanel] Path indices:', pathIndices);
|
|
1143
|
+
|
|
1144
|
+
// Expand each parent node in the tree
|
|
1145
|
+
parents.forEach((parent, _parentIndex) => {
|
|
1146
|
+
// Find the tree node for this element
|
|
1147
|
+
const treeNodes = this.panel?.querySelectorAll('.nc-tree-node');
|
|
1148
|
+
if (!treeNodes) {
|
|
1149
|
+
console.log('[OutlinePanel] No tree nodes found');
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
let matchCount = 0;
|
|
1154
|
+
treeNodes.forEach(node => {
|
|
1155
|
+
const content = node.querySelector('.nc-tree-content');
|
|
1156
|
+
if (!content) return;
|
|
1157
|
+
|
|
1158
|
+
const tagSpan = content.querySelector('.nc-tree-tag');
|
|
1159
|
+
if (!tagSpan) return;
|
|
1160
|
+
|
|
1161
|
+
const tagName = tagSpan.textContent?.replace(/[<>]/g, '') || '';
|
|
1162
|
+
const nodeId = content.querySelector('.nc-tree-attr')?.textContent?.match(/#([\w-]+)/)?.[1];
|
|
1163
|
+
const nodeClass = content.querySelector('.nc-tree-attr')?.textContent?.match(/\.([\w-]+)/)?.[1];
|
|
1164
|
+
|
|
1165
|
+
// Check if this node matches the parent element
|
|
1166
|
+
const tagMatches = parent.tagName.toLowerCase() === tagName;
|
|
1167
|
+
const idMatches = !nodeId || !parent.id || parent.id === nodeId;
|
|
1168
|
+
const classMatches = !nodeClass || parent.classList.contains(nodeClass);
|
|
1169
|
+
|
|
1170
|
+
const matches = tagMatches && idMatches && classMatches;
|
|
1171
|
+
|
|
1172
|
+
if (matches) {
|
|
1173
|
+
matchCount++;
|
|
1174
|
+
console.log(`[OutlinePanel] Matched node for ${parent.tagName}:`, {tagName, nodeId, nodeClass});
|
|
1175
|
+
|
|
1176
|
+
// Expand this node's children
|
|
1177
|
+
const toggle = node.querySelector('.nc-tree-toggle');
|
|
1178
|
+
const wrapper = node.parentElement;
|
|
1179
|
+
const childrenContainer = wrapper?.querySelector('.nc-tree-children');
|
|
1180
|
+
|
|
1181
|
+
if (toggle && childrenContainer) {
|
|
1182
|
+
toggle.classList.remove('collapsed');
|
|
1183
|
+
childrenContainer.classList.remove('hidden');
|
|
1184
|
+
console.log('[OutlinePanel] Expanded node');
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// If this is the target element, highlight and scroll to it
|
|
1188
|
+
if (parent === targetElement) {
|
|
1189
|
+
console.log('[OutlinePanel] Highlighting target element');
|
|
1190
|
+
(node as HTMLElement).style.backgroundColor = 'rgba(92, 200, 255, 0.2)';
|
|
1191
|
+
node.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1192
|
+
|
|
1193
|
+
// Remove highlight after a moment
|
|
1194
|
+
setTimeout(() => {
|
|
1195
|
+
(node as HTMLElement).style.backgroundColor = '';
|
|
1196
|
+
}, 2000);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
if (matchCount === 0) {
|
|
1202
|
+
console.log(`[OutlinePanel] No matches found for ${parent.tagName}`);
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
// Adjust panel width after expanding to accommodate the new depth
|
|
1207
|
+
this.adjustPanelWidth();
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
/**
|
|
1211
|
+
* Show the outline panel and expand to a specific element
|
|
1212
|
+
* Public method to be called when gear icon is clicked
|
|
1213
|
+
*/
|
|
1214
|
+
public showAndExpandTo(element: HTMLElement): void {
|
|
1215
|
+
if (!this.isVisible) {
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// Open the panel if it's closed
|
|
1220
|
+
if (!this.isOpen) {
|
|
1221
|
+
this.togglePanel();
|
|
1222
|
+
// Wait longer for panel animation and tree rendering
|
|
1223
|
+
setTimeout(() => {
|
|
1224
|
+
this.expandToElement(element);
|
|
1225
|
+
}, 250);
|
|
1226
|
+
} else {
|
|
1227
|
+
// Panel is already open, refresh tree and expand
|
|
1228
|
+
this.refreshTree();
|
|
1229
|
+
setTimeout(() => {
|
|
1230
|
+
this.expandToElement(element);
|
|
1231
|
+
}, 100);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Destroy the panel
|
|
1237
|
+
*/
|
|
1238
|
+
destroy(): void {
|
|
1239
|
+
if (this.mutationObserver) {
|
|
1240
|
+
this.mutationObserver.disconnect();
|
|
1241
|
+
}
|
|
1242
|
+
this.panel?.remove();
|
|
1243
|
+
this.tab?.remove();
|
|
1244
|
+
this.tab = null;
|
|
1245
|
+
document.getElementById('nativecore-outline-styles')?.remove();
|
|
1246
|
+
}
|
|
1247
|
+
}
|