Update dev-tools.js
Browse files- dev-tools.js +668 -449
dev-tools.js
CHANGED
@@ -1,101 +1,134 @@
|
|
1 |
(function() {
|
2 |
// グローバル変数宣言
|
3 |
-
let
|
4 |
-
let
|
5 |
-
let
|
6 |
-
let
|
7 |
-
let
|
8 |
-
let
|
9 |
-
let
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
}
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
attributes: false,
|
26 |
-
characterData: false
|
27 |
});
|
28 |
};
|
29 |
|
30 |
-
//
|
31 |
const style = document.createElement('style');
|
32 |
style.textContent = `
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
position: fixed;
|
35 |
bottom: 0;
|
36 |
left: 0;
|
37 |
width: 100%;
|
38 |
height: 300px;
|
39 |
-
background-color: var(--
|
40 |
-
border-top: 1px solid var(--
|
41 |
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
|
42 |
z-index: 9999;
|
43 |
display: flex;
|
44 |
flex-direction: column;
|
45 |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
46 |
-
color: var(--
|
47 |
}
|
48 |
|
49 |
-
.
|
50 |
display: flex;
|
51 |
justify-content: space-between;
|
52 |
align-items: center;
|
53 |
padding: 5px 10px;
|
54 |
-
background-color: var(--
|
55 |
-
border-bottom: 1px solid var(--
|
56 |
}
|
57 |
|
58 |
-
.
|
59 |
display: flex;
|
60 |
gap: 5px;
|
61 |
}
|
62 |
|
63 |
-
.
|
64 |
padding: 5px 10px;
|
65 |
cursor: pointer;
|
66 |
border-radius: 3px 3px 0 0;
|
67 |
-
background-color: var(--
|
68 |
-
border: 1px solid var(--
|
69 |
border-bottom: none;
|
70 |
font-size: 12px;
|
71 |
-
color: var(--
|
72 |
}
|
73 |
|
74 |
-
.
|
75 |
-
background-color: var(--
|
76 |
-
color: var(--
|
77 |
-
border-bottom: 1px solid var(--
|
78 |
margin-bottom: -1px;
|
79 |
font-weight: bold;
|
80 |
}
|
81 |
|
82 |
-
.
|
83 |
background: none;
|
84 |
border: none;
|
85 |
font-size: 16px;
|
86 |
cursor: pointer;
|
87 |
padding: 0 5px;
|
88 |
-
color: var(--
|
89 |
}
|
90 |
|
91 |
-
.
|
92 |
flex: 1;
|
93 |
overflow: auto;
|
94 |
position: relative;
|
95 |
-
background-color: var(--
|
96 |
}
|
97 |
|
98 |
-
.
|
99 |
position: absolute;
|
100 |
top: 0;
|
101 |
left: 0;
|
@@ -104,45 +137,45 @@
|
|
104 |
padding: 10px;
|
105 |
overflow: auto;
|
106 |
display: none;
|
107 |
-
background-color: var(--
|
108 |
}
|
109 |
|
110 |
-
.
|
111 |
display: block;
|
112 |
}
|
113 |
|
114 |
/* Console スタイル */
|
115 |
-
#
|
116 |
white-space: pre-wrap;
|
117 |
margin: 0;
|
118 |
line-height: 1.4;
|
119 |
flex: 1;
|
120 |
-
color: var(--
|
121 |
font-family: 'Consolas', 'Monaco', monospace;
|
122 |
font-size: 13px;
|
123 |
}
|
124 |
|
125 |
-
.
|
126 |
-
color: var(--
|
127 |
}
|
128 |
|
129 |
-
.
|
130 |
-
color: var(--
|
131 |
}
|
132 |
|
133 |
-
.
|
134 |
-
color: var(--
|
135 |
}
|
136 |
|
137 |
-
.
|
138 |
-
color: var(--
|
139 |
}
|
140 |
|
141 |
-
.
|
142 |
width: calc(100% - 16px);
|
143 |
-
background: var(--
|
144 |
-
border: 1px solid var(--
|
145 |
-
color: var(--
|
146 |
padding: 8px;
|
147 |
margin-top: 10px;
|
148 |
font-family: monospace;
|
@@ -150,117 +183,117 @@
|
|
150 |
}
|
151 |
|
152 |
/* Elements スタイル */
|
153 |
-
.
|
154 |
display: flex;
|
155 |
flex: 1;
|
156 |
overflow: hidden;
|
157 |
}
|
158 |
|
159 |
-
.
|
160 |
font-family: 'Consolas', 'Monaco', monospace;
|
161 |
flex: 1;
|
162 |
overflow: auto;
|
163 |
-
border-right: 1px solid var(--
|
164 |
padding-right: 10px;
|
165 |
-
color: var(--
|
166 |
font-size: 13px;
|
167 |
}
|
168 |
|
169 |
-
.
|
170 |
margin-left: 15px;
|
171 |
position: relative;
|
172 |
line-height: 1.4;
|
173 |
transition: background-color 0.3s;
|
174 |
}
|
175 |
|
176 |
-
.
|
177 |
-
background: var(--
|
178 |
}
|
179 |
|
180 |
-
.
|
181 |
-
animation:
|
182 |
}
|
183 |
|
184 |
-
@keyframes
|
185 |
0% { background-color: rgba(79, 195, 247, 0.5); }
|
186 |
100% { background-color: transparent; }
|
187 |
}
|
188 |
|
189 |
-
.
|
190 |
-
color: var(--
|
191 |
font-weight: bold;
|
192 |
}
|
193 |
|
194 |
-
.
|
195 |
-
color: var(--
|
196 |
}
|
197 |
|
198 |
-
.
|
199 |
text-decoration: underline;
|
200 |
cursor: pointer;
|
201 |
}
|
202 |
|
203 |
-
.
|
204 |
-
color: var(--
|
205 |
}
|
206 |
|
207 |
-
.
|
208 |
-
background: var(--
|
209 |
-
border: 1px solid var(--
|
210 |
padding: 0 2px;
|
211 |
margin: -1px 0;
|
212 |
font-family: monospace;
|
213 |
min-width: 50px;
|
214 |
-
color: var(--
|
215 |
}
|
216 |
|
217 |
-
.
|
218 |
flex: 1;
|
219 |
overflow: auto;
|
220 |
padding-left: 10px;
|
221 |
font-size: 13px;
|
222 |
}
|
223 |
|
224 |
-
.
|
225 |
margin-bottom: 15px;
|
226 |
-
border: 1px solid var(--
|
227 |
padding: 8px;
|
228 |
-
background-color: var(--
|
229 |
border-radius: 3px;
|
230 |
}
|
231 |
|
232 |
-
.
|
233 |
-
color: var(--
|
234 |
margin-bottom: 5px;
|
235 |
font-weight: bold;
|
236 |
}
|
237 |
|
238 |
-
.
|
239 |
display: flex;
|
240 |
margin-bottom: 3px;
|
241 |
}
|
242 |
|
243 |
-
.
|
244 |
-
color: var(--
|
245 |
min-width: 120px;
|
246 |
}
|
247 |
|
248 |
-
.
|
249 |
-
color: var(--
|
250 |
flex: 1;
|
251 |
}
|
252 |
|
253 |
-
.
|
254 |
margin-left: 10px;
|
255 |
-
color: var(--
|
256 |
cursor: pointer;
|
257 |
}
|
258 |
|
259 |
/* Context Menu */
|
260 |
-
.
|
261 |
position: absolute;
|
262 |
-
background: var(--
|
263 |
-
border: 1px solid var(--
|
264 |
z-index: 10000;
|
265 |
min-width: 200px;
|
266 |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
@@ -269,43 +302,43 @@
|
|
269 |
overflow: hidden;
|
270 |
}
|
271 |
|
272 |
-
.
|
273 |
padding: 8px 15px;
|
274 |
cursor: pointer;
|
275 |
-
color: var(--
|
276 |
font-size: 13px;
|
277 |
}
|
278 |
|
279 |
-
.
|
280 |
-
background: var(--
|
281 |
color: #000;
|
282 |
}
|
283 |
|
284 |
/* Storage スタイル */
|
285 |
-
.
|
286 |
width: 100%;
|
287 |
border-collapse: collapse;
|
288 |
margin-bottom: 10px;
|
289 |
font-size: 13px;
|
290 |
}
|
291 |
|
292 |
-
.
|
293 |
-
border: 1px solid var(--
|
294 |
padding: 5px;
|
295 |
text-align: left;
|
296 |
}
|
297 |
|
298 |
-
.
|
299 |
-
background: var(--
|
300 |
}
|
301 |
|
302 |
-
.
|
303 |
display: flex;
|
304 |
gap: 5px;
|
305 |
}
|
306 |
|
307 |
-
.
|
308 |
-
background: var(--
|
309 |
border: none;
|
310 |
padding: 2px 5px;
|
311 |
cursor: pointer;
|
@@ -314,18 +347,18 @@
|
|
314 |
font-size: 12px;
|
315 |
}
|
316 |
|
317 |
-
.
|
318 |
cursor: pointer;
|
319 |
padding: 2px 5px;
|
320 |
border: 1px dashed transparent;
|
321 |
}
|
322 |
|
323 |
-
.
|
324 |
-
border-color: var(--
|
325 |
}
|
326 |
|
327 |
-
.
|
328 |
-
background: var(--
|
329 |
color: #000;
|
330 |
border: none;
|
331 |
padding: 5px 10px;
|
@@ -335,47 +368,47 @@
|
|
335 |
font-size: 13px;
|
336 |
}
|
337 |
|
338 |
-
.
|
339 |
-
background: var(--
|
340 |
}
|
341 |
|
342 |
/* Network スタイル */
|
343 |
-
.
|
344 |
display: flex;
|
345 |
height: 100%;
|
346 |
overflow: hidden;
|
347 |
}
|
348 |
|
349 |
-
.
|
350 |
width: 40%;
|
351 |
overflow-y: auto;
|
352 |
-
border-right: 1px solid var(--
|
353 |
font-size: 13px;
|
354 |
}
|
355 |
|
356 |
-
.
|
357 |
width: 60%;
|
358 |
overflow-y: auto;
|
359 |
padding-left: 10px;
|
360 |
}
|
361 |
|
362 |
-
.
|
363 |
padding: 8px;
|
364 |
-
border-bottom: 1px solid var(--
|
365 |
cursor: pointer;
|
366 |
display: flex;
|
367 |
align-items: center;
|
368 |
}
|
369 |
|
370 |
-
.
|
371 |
background-color: rgba(0, 122, 204, 0.1);
|
372 |
}
|
373 |
|
374 |
-
.
|
375 |
-
background-color: var(--
|
376 |
}
|
377 |
|
378 |
-
.
|
379 |
width: 20px;
|
380 |
height: 20px;
|
381 |
border-radius: 50%;
|
@@ -386,51 +419,51 @@
|
|
386 |
flex-shrink: 0;
|
387 |
}
|
388 |
|
389 |
-
.
|
390 |
-
background-color: var(--
|
391 |
color: white;
|
392 |
}
|
393 |
|
394 |
-
.
|
395 |
-
background-color: var(--
|
396 |
color: white;
|
397 |
}
|
398 |
|
399 |
-
.
|
400 |
font-weight: bold;
|
401 |
margin-right: 8px;
|
402 |
-
color: var(--
|
403 |
min-width: 40px;
|
404 |
}
|
405 |
|
406 |
-
.
|
407 |
flex: 1;
|
408 |
white-space: nowrap;
|
409 |
overflow: hidden;
|
410 |
text-overflow: ellipsis;
|
411 |
}
|
412 |
|
413 |
-
.
|
414 |
-
color: var(--
|
415 |
font-size: 11px;
|
416 |
margin-left: 8px;
|
417 |
}
|
418 |
|
419 |
-
.
|
420 |
margin-bottom: 15px;
|
421 |
}
|
422 |
|
423 |
-
.
|
424 |
font-weight: bold;
|
425 |
margin-bottom: 5px;
|
426 |
-
color: var(--
|
427 |
}
|
428 |
|
429 |
-
.
|
430 |
-
background: var(--
|
431 |
padding: 8px;
|
432 |
border-radius: 3px;
|
433 |
-
border: 1px solid var(--
|
434 |
font-family: monospace;
|
435 |
white-space: pre-wrap;
|
436 |
font-size: 12px;
|
@@ -438,107 +471,183 @@
|
|
438 |
overflow-y: auto;
|
439 |
}
|
440 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
441 |
/* DOM Tree Toggle */
|
442 |
-
.
|
443 |
position: absolute;
|
444 |
left: -12px;
|
445 |
top: 2px;
|
446 |
width: 10px;
|
447 |
height: 10px;
|
448 |
cursor: pointer;
|
449 |
-
background-color: var(--
|
450 |
clip-path: polygon(0 0, 100% 50%, 0 100%);
|
451 |
transition: transform 0.2s;
|
452 |
}
|
453 |
|
454 |
-
.
|
455 |
transform: rotate(-90deg);
|
456 |
}
|
457 |
|
458 |
-
.
|
459 |
overflow: hidden;
|
460 |
transition: max-height 0.3s ease-out;
|
461 |
}
|
462 |
|
463 |
/* JSON スタイル */
|
464 |
-
.
|
465 |
-
color: var(--
|
466 |
-
}
|
467 |
-
|
468 |
-
.dt-json-string {
|
469 |
-
color: var(--dt-json-string);
|
470 |
}
|
471 |
|
472 |
-
.
|
473 |
-
color: var(--
|
474 |
}
|
475 |
|
476 |
-
.
|
477 |
-
color: var(--
|
478 |
}
|
479 |
|
480 |
-
.
|
481 |
-
color: var(--
|
482 |
}
|
483 |
|
484 |
-
|
485 |
-
:
|
486 |
-
--dt-bg-color: #1e1e1e;
|
487 |
-
--dt-panel-bg: #252526;
|
488 |
-
--dt-border-color: #3c3c3c;
|
489 |
-
--dt-text-color: #e0e0e0;
|
490 |
-
--dt-text-muted: #a0a0a0;
|
491 |
-
--dt-primary-color: #007acc;
|
492 |
-
--dt-primary-hover: #3e9fda;
|
493 |
-
--dt-success-color: #4caf50;
|
494 |
-
--dt-error-color: #f44336;
|
495 |
-
--dt-warning-color: #ff9800;
|
496 |
-
--dt-info-color: #2196f3;
|
497 |
-
--dt-highlight-bg: rgba(0, 122, 204, 0.2);
|
498 |
-
--dt-tab-bg: #2d2d2d;
|
499 |
-
--dt-tab-active-bg: #1e1e1e;
|
500 |
-
--dt-console-log-color: #e0e0e0;
|
501 |
-
--dt-console-error-color: #f44336;
|
502 |
-
--dt-console-warn-color: #ff9800;
|
503 |
-
--dt-console-info-color: #4fc3f7;
|
504 |
-
--dt-json-key: #9cdcfe;
|
505 |
-
--dt-json-string: #ce9178;
|
506 |
-
--dt-json-number: #b5cea8;
|
507 |
-
--dt-json-boolean: #569cd6;
|
508 |
-
--dt-json-null: #569cd6;
|
509 |
-
--dt-dom-tag: #569cd6;
|
510 |
-
--dt-dom-attr: #9cdcfe;
|
511 |
-
--dt-dom-text: #d4d4d4;
|
512 |
}
|
513 |
`;
|
514 |
document.head.appendChild(style);
|
515 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
516 |
// 開発者ツールのメイン関数
|
517 |
const createDevTools = () => {
|
518 |
const container = document.createElement('div');
|
519 |
-
container.className = '
|
520 |
-
container.id = '
|
521 |
container.style.display = 'none';
|
522 |
|
523 |
// ヘッダー部分
|
524 |
const header = document.createElement('div');
|
525 |
-
header.className = '
|
526 |
|
527 |
const tabs = document.createElement('div');
|
528 |
-
tabs.className = '
|
529 |
|
530 |
const consoleTab = createTab('Console', 'console');
|
531 |
const elementsTab = createTab('Elements', 'elements');
|
532 |
const networkTab = createTab('Network', 'network');
|
533 |
const storageTab = createTab('Storage', 'storage');
|
|
|
534 |
|
535 |
tabs.appendChild(consoleTab);
|
536 |
tabs.appendChild(elementsTab);
|
537 |
tabs.appendChild(networkTab);
|
538 |
tabs.appendChild(storageTab);
|
|
|
539 |
|
540 |
const closeBtn = document.createElement('button');
|
541 |
-
closeBtn.className = '
|
542 |
closeBtn.textContent = '×';
|
543 |
closeBtn.onclick = toggleDevTools;
|
544 |
|
@@ -547,17 +656,19 @@
|
|
547 |
|
548 |
// コンテンツ部分
|
549 |
const content = document.createElement('div');
|
550 |
-
content.className = '
|
551 |
|
552 |
const consolePanel = createConsolePanel();
|
553 |
const elementsPanel = createElementsPanel();
|
554 |
const networkPanel = createNetworkPanel();
|
555 |
const storagePanel = createStoragePanel();
|
|
|
556 |
|
557 |
content.appendChild(consolePanel);
|
558 |
content.appendChild(elementsPanel);
|
559 |
content.appendChild(networkPanel);
|
560 |
content.appendChild(storagePanel);
|
|
|
561 |
|
562 |
container.appendChild(header);
|
563 |
container.appendChild(content);
|
@@ -570,11 +681,11 @@
|
|
570 |
// タブ切り替え機能
|
571 |
function createTab(name, panelId) {
|
572 |
const tab = document.createElement('div');
|
573 |
-
tab.className = '
|
574 |
tab.textContent = name;
|
575 |
tab.onclick = () => {
|
576 |
-
document.querySelectorAll('.
|
577 |
-
document.querySelectorAll('.
|
578 |
tab.classList.add('active');
|
579 |
document.getElementById(panelId + '-panel').classList.add('active');
|
580 |
|
@@ -588,24 +699,188 @@
|
|
588 |
elementsTab.click();
|
589 |
};
|
590 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
591 |
// ネットワークパネル作成
|
592 |
function createNetworkPanel() {
|
593 |
const panel = document.createElement('div');
|
594 |
-
panel.className = '
|
595 |
panel.id = 'network-panel';
|
596 |
|
597 |
const container = document.createElement('div');
|
598 |
-
container.className = '
|
599 |
panel.appendChild(container);
|
600 |
|
601 |
// リクエストリスト
|
602 |
const requestsList = document.createElement('div');
|
603 |
-
requestsList.className = '
|
604 |
container.appendChild(requestsList);
|
605 |
|
606 |
// 詳細パネル
|
607 |
const detailsPanel = document.createElement('div');
|
608 |
-
detailsPanel.className = '
|
609 |
container.appendChild(detailsPanel);
|
610 |
|
611 |
// ネットワークリクエストを監視
|
@@ -637,7 +912,7 @@
|
|
637 |
error: null
|
638 |
};
|
639 |
|
640 |
-
|
641 |
renderNetworkRequests();
|
642 |
|
643 |
try {
|
@@ -714,7 +989,7 @@
|
|
714 |
error: null
|
715 |
};
|
716 |
|
717 |
-
|
718 |
renderNetworkRequests();
|
719 |
|
720 |
this.addEventListener('load', function() {
|
@@ -766,38 +1041,38 @@
|
|
766 |
const panel = document.getElementById('network-panel');
|
767 |
if (!panel || !panel.classList.contains('active')) return;
|
768 |
|
769 |
-
const requestsList = panel.querySelector('.
|
770 |
-
const detailsPanel = panel.querySelector('.
|
771 |
|
772 |
requestsList.innerHTML = '';
|
773 |
|
774 |
-
|
775 |
const requestElement = document.createElement('div');
|
776 |
-
requestElement.className = '
|
777 |
-
if (
|
778 |
requestElement.classList.add('selected');
|
779 |
}
|
780 |
|
781 |
requestElement.onclick = () => {
|
782 |
-
|
783 |
renderNetworkRequests();
|
784 |
renderNetworkDetails();
|
785 |
};
|
786 |
|
787 |
const statusElement = document.createElement('div');
|
788 |
-
statusElement.className = `
|
789 |
statusElement.textContent = request.status === 'success' ? '✓' : '✕';
|
790 |
|
791 |
const methodElement = document.createElement('div');
|
792 |
-
methodElement.className = '
|
793 |
methodElement.textContent = request.method;
|
794 |
|
795 |
const urlElement = document.createElement('div');
|
796 |
-
urlElement.className = '
|
797 |
urlElement.textContent = request.url;
|
798 |
|
799 |
const timeElement = document.createElement('div');
|
800 |
-
timeElement.className = '
|
801 |
timeElement.textContent = request.duration ? `${Math.round(request.duration)}ms` : '';
|
802 |
|
803 |
requestElement.appendChild(statusElement);
|
@@ -812,43 +1087,43 @@
|
|
812 |
// ネットワークリクエストの詳細を表示
|
813 |
function renderNetworkDetails() {
|
814 |
const panel = document.getElementById('network-panel');
|
815 |
-
if (!panel || !
|
816 |
|
817 |
-
const detailsPanel = panel.querySelector('.
|
818 |
detailsPanel.innerHTML = '';
|
819 |
|
820 |
// 一般情報
|
821 |
const generalSection = document.createElement('div');
|
822 |
-
generalSection.className = '
|
823 |
generalSection.innerHTML = `
|
824 |
-
<div class="
|
825 |
-
<div class="
|
826 |
-
<strong>URL:</strong> ${
|
827 |
-
<strong>メソッド:</strong> ${
|
828 |
-
<strong>ステータス:</strong> ${
|
829 |
-
<strong>時間:</strong> ${
|
830 |
</div>
|
831 |
`;
|
832 |
detailsPanel.appendChild(generalSection);
|
833 |
|
834 |
// リクエストヘッダー
|
835 |
-
if (
|
836 |
const headersSection = document.createElement('div');
|
837 |
-
headersSection.className = '
|
838 |
|
839 |
const headersTitle = document.createElement('div');
|
840 |
-
headersTitle.className = '
|
841 |
headersTitle.textContent = 'リクエストヘッダー';
|
842 |
|
843 |
const headersContent = document.createElement('div');
|
844 |
-
headersContent.className = '
|
845 |
|
846 |
-
if (typeof
|
847 |
-
Object.entries(
|
848 |
headersContent.innerHTML += `<strong>${key}:</strong> ${value}<br>`;
|
849 |
});
|
850 |
-
} else if (
|
851 |
-
|
852 |
headersContent.innerHTML += `<strong>${key}:</strong> ${value}<br>`;
|
853 |
});
|
854 |
}
|
@@ -859,22 +1134,22 @@
|
|
859 |
}
|
860 |
|
861 |
// リクエストボディ
|
862 |
-
if (
|
863 |
const bodySection = document.createElement('div');
|
864 |
-
bodySection.className = '
|
865 |
|
866 |
const bodyTitle = document.createElement('div');
|
867 |
-
bodyTitle.className = '
|
868 |
bodyTitle.textContent = 'リクエストボディ';
|
869 |
|
870 |
const bodyContent = document.createElement('div');
|
871 |
-
bodyContent.className = '
|
872 |
|
873 |
try {
|
874 |
-
if (typeof
|
875 |
-
bodyContent.textContent =
|
876 |
-
} else if (typeof
|
877 |
-
bodyContent.textContent = JSON.stringify(
|
878 |
}
|
879 |
} catch (e) {
|
880 |
bodyContent.textContent = 'ボディを表示できません';
|
@@ -886,22 +1161,22 @@
|
|
886 |
}
|
887 |
|
888 |
// レスポンス
|
889 |
-
if (
|
890 |
const responseSection = document.createElement('div');
|
891 |
-
responseSection.className = '
|
892 |
|
893 |
const responseTitle = document.createElement('div');
|
894 |
-
responseTitle.className = '
|
895 |
responseTitle.textContent = 'レスポンス';
|
896 |
|
897 |
const responseContent = document.createElement('div');
|
898 |
-
responseContent.className = '
|
899 |
|
900 |
-
if (
|
901 |
-
if (typeof
|
902 |
-
responseContent.textContent = JSON.stringify(
|
903 |
} else {
|
904 |
-
responseContent.textContent =
|
905 |
}
|
906 |
} else {
|
907 |
responseContent.textContent = 'レスポンスボディがありません';
|
@@ -913,20 +1188,20 @@
|
|
913 |
}
|
914 |
|
915 |
// エラー
|
916 |
-
if (
|
917 |
const errorSection = document.createElement('div');
|
918 |
-
errorSection.className = '
|
919 |
|
920 |
const errorTitle = document.createElement('div');
|
921 |
-
errorTitle.className = '
|
922 |
errorTitle.textContent = 'エラー';
|
923 |
|
924 |
const errorContent = document.createElement('div');
|
925 |
-
errorContent.className = '
|
926 |
-
errorContent.textContent = `${
|
927 |
|
928 |
-
if (
|
929 |
-
errorContent.innerHTML += `<br><br>${
|
930 |
}
|
931 |
|
932 |
errorSection.appendChild(errorTitle);
|
@@ -937,31 +1212,30 @@
|
|
937 |
|
938 |
// コンテキストメニュー作成
|
939 |
function createContextMenu() {
|
940 |
-
|
941 |
-
|
942 |
-
|
943 |
-
<div class="
|
944 |
-
<div class="
|
945 |
-
<div class="
|
946 |
-
<div class="
|
947 |
-
<div class="
|
948 |
-
<div class="
|
949 |
-
<div class="
|
950 |
-
<div class="dt-context-menu-item" data-action="force-state">状態を強制</div>
|
951 |
`;
|
952 |
-
document.body.appendChild(
|
953 |
|
954 |
-
|
955 |
item.addEventListener('click', (e) => {
|
956 |
const action = e.target.getAttribute('data-action');
|
957 |
handleContextMenuAction(action);
|
958 |
-
|
959 |
});
|
960 |
});
|
961 |
|
962 |
document.addEventListener('click', (e) => {
|
963 |
-
if (e.target !==
|
964 |
-
|
965 |
}
|
966 |
});
|
967 |
}
|
@@ -969,7 +1243,7 @@
|
|
969 |
// Storageパネル作成
|
970 |
function createStoragePanel() {
|
971 |
const panel = document.createElement('div');
|
972 |
-
panel.className = '
|
973 |
panel.id = 'storage-panel';
|
974 |
|
975 |
// LocalStorage表示
|
@@ -978,11 +1252,11 @@
|
|
978 |
panel.appendChild(localStorageTitle);
|
979 |
|
980 |
const localStorageTable = document.createElement('table');
|
981 |
-
localStorageTable.className = '
|
982 |
panel.appendChild(localStorageTable);
|
983 |
|
984 |
const addLocalStorageBtn = document.createElement('button');
|
985 |
-
addLocalStorageBtn.className = '
|
986 |
addLocalStorageBtn.textContent = '+ Local Storageに追加';
|
987 |
addLocalStorageBtn.onclick = () => {
|
988 |
const key = prompt('キー名を入力');
|
@@ -1001,11 +1275,11 @@
|
|
1001 |
panel.appendChild(sessionStorageTitle);
|
1002 |
|
1003 |
const sessionStorageTable = document.createElement('table');
|
1004 |
-
sessionStorageTable.className = '
|
1005 |
panel.appendChild(sessionStorageTable);
|
1006 |
|
1007 |
const addSessionStorageBtn = document.createElement('button');
|
1008 |
-
addSessionStorageBtn.className = '
|
1009 |
addSessionStorageBtn.textContent = '+ Session Storageに追加';
|
1010 |
addSessionStorageBtn.onclick = () => {
|
1011 |
const key = prompt('キー名を入力');
|
@@ -1024,11 +1298,11 @@
|
|
1024 |
panel.appendChild(cookiesTitle);
|
1025 |
|
1026 |
const cookiesTable = document.createElement('table');
|
1027 |
-
cookiesTable.className = '
|
1028 |
panel.appendChild(cookiesTable);
|
1029 |
|
1030 |
const addCookieBtn = document.createElement('button');
|
1031 |
-
addCookieBtn.className = '
|
1032 |
addCookieBtn.textContent = '+ Cookieに追加';
|
1033 |
addCookieBtn.onclick = () => {
|
1034 |
const name = prompt('Cookie名を入力');
|
@@ -1069,7 +1343,7 @@
|
|
1069 |
|
1070 |
const keyCell = document.createElement('td');
|
1071 |
const keySpan = document.createElement('span');
|
1072 |
-
keySpan.className = '
|
1073 |
keySpan.textContent = key;
|
1074 |
keySpan.onclick = () => {
|
1075 |
const newKey = prompt('新しいキー名を入力', key);
|
@@ -1083,7 +1357,7 @@
|
|
1083 |
|
1084 |
const valueCell = document.createElement('td');
|
1085 |
const valueSpan = document.createElement('span');
|
1086 |
-
valueSpan.className = '
|
1087 |
valueSpan.textContent = value;
|
1088 |
valueSpan.onclick = () => {
|
1089 |
const newValue = prompt('新しい値を入力', value);
|
@@ -1095,10 +1369,10 @@
|
|
1095 |
valueCell.appendChild(valueSpan);
|
1096 |
|
1097 |
const actionsCell = document.createElement('td');
|
1098 |
-
actionsCell.className = '
|
1099 |
|
1100 |
const deleteBtn = document.createElement('button');
|
1101 |
-
deleteBtn.className = '
|
1102 |
deleteBtn.textContent = 'Delete';
|
1103 |
deleteBtn.onclick = () => {
|
1104 |
storage.removeItem(key);
|
@@ -1140,7 +1414,7 @@
|
|
1140 |
|
1141 |
const nameCell = document.createElement('td');
|
1142 |
const nameSpan = document.createElement('span');
|
1143 |
-
nameSpan.className = '
|
1144 |
nameSpan.textContent = decodedName;
|
1145 |
nameSpan.onclick = () => {
|
1146 |
const newName = prompt('新しい名前を入力', decodedName);
|
@@ -1157,7 +1431,7 @@
|
|
1157 |
|
1158 |
const valueCell = document.createElement('td');
|
1159 |
const valueSpan = document.createElement('span');
|
1160 |
-
valueSpan.className = '
|
1161 |
valueSpan.textContent = decodeURIComponent(value);
|
1162 |
valueSpan.onclick = () => {
|
1163 |
const newValue = prompt('新しい値を入力', decodeURIComponent(value));
|
@@ -1169,10 +1443,10 @@
|
|
1169 |
valueCell.appendChild(valueSpan);
|
1170 |
|
1171 |
const actionsCell = document.createElement('td');
|
1172 |
-
actionsCell.className = '
|
1173 |
|
1174 |
const deleteBtn = document.createElement('button');
|
1175 |
-
deleteBtn.className = '
|
1176 |
deleteBtn.textContent = 'Delete';
|
1177 |
deleteBtn.onclick = () => {
|
1178 |
document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
@@ -1197,120 +1471,69 @@
|
|
1197 |
|
1198 |
// コンテキストメニューアクション処理
|
1199 |
function handleContextMenuAction(action) {
|
1200 |
-
if (!
|
1201 |
|
1202 |
switch (action) {
|
1203 |
case 'edit-html':
|
1204 |
-
if (
|
1205 |
alert('ルートHTML要素は直接編集できません');
|
1206 |
return;
|
1207 |
}
|
1208 |
-
startInlineEdit(
|
1209 |
try {
|
1210 |
-
|
1211 |
-
|
1212 |
} catch (e) {
|
1213 |
alert('この要素は編集できません: ' + e.message);
|
1214 |
}
|
1215 |
});
|
1216 |
break;
|
1217 |
-
case 'edit-whole-html':
|
1218 |
-
const htmlContent = document.documentElement.outerHTML;
|
1219 |
-
const textarea = document.createElement('textarea');
|
1220 |
-
textarea.style.width = '100%';
|
1221 |
-
textarea.style.height = '300px';
|
1222 |
-
textarea.value = htmlContent;
|
1223 |
-
|
1224 |
-
const modal = document.createElement('div');
|
1225 |
-
modal.style.position = 'fixed';
|
1226 |
-
modal.style.top = '0';
|
1227 |
-
modal.style.left = '0';
|
1228 |
-
modal.style.width = '100%';
|
1229 |
-
modal.style.height = '100%';
|
1230 |
-
modal.style.backgroundColor = 'rgba(0,0,0,0.8)';
|
1231 |
-
modal.style.zIndex = '10000';
|
1232 |
-
modal.style.display = 'flex';
|
1233 |
-
modal.style.flexDirection = 'column';
|
1234 |
-
modal.style.padding = '20px';
|
1235 |
-
modal.style.boxSizing = 'border-box';
|
1236 |
-
|
1237 |
-
const buttonContainer = document.createElement('div');
|
1238 |
-
buttonContainer.style.marginTop = '10px';
|
1239 |
-
buttonContainer.style.display = 'flex';
|
1240 |
-
buttonContainer.style.gap = '10px';
|
1241 |
-
|
1242 |
-
const saveButton = document.createElement('button');
|
1243 |
-
saveButton.textContent = '保存';
|
1244 |
-
saveButton.onclick = () => {
|
1245 |
-
try {
|
1246 |
-
document.documentElement.innerHTML = textarea.value;
|
1247 |
-
modal.remove();
|
1248 |
-
dtRefreshElementsPanel();
|
1249 |
-
} catch (e) {
|
1250 |
-
alert('HTMLの解析に失敗しました: ' + e.message);
|
1251 |
-
}
|
1252 |
-
};
|
1253 |
-
|
1254 |
-
const cancelButton = document.createElement('button');
|
1255 |
-
cancelButton.textContent = 'キャンセル';
|
1256 |
-
cancelButton.onclick = () => {
|
1257 |
-
modal.remove();
|
1258 |
-
};
|
1259 |
-
|
1260 |
-
buttonContainer.appendChild(saveButton);
|
1261 |
-
buttonContainer.appendChild(cancelButton);
|
1262 |
-
|
1263 |
-
modal.appendChild(textarea);
|
1264 |
-
modal.appendChild(buttonContainer);
|
1265 |
-
|
1266 |
-
document.body.appendChild(modal);
|
1267 |
-
break;
|
1268 |
case 'add-attribute':
|
1269 |
const attrName = prompt('属性名を入力');
|
1270 |
if (attrName) {
|
1271 |
const attrValue = prompt('属性値を入力');
|
1272 |
-
|
1273 |
-
|
1274 |
}
|
1275 |
break;
|
1276 |
case 'edit-element':
|
1277 |
-
startInlineEdit(
|
1278 |
const newElement = document.createElement(newValue);
|
1279 |
-
Array.from(
|
1280 |
newElement.setAttribute(attr.name, attr.value);
|
1281 |
});
|
1282 |
-
newElement.innerHTML =
|
1283 |
-
|
1284 |
-
|
1285 |
-
|
1286 |
});
|
1287 |
break;
|
1288 |
case 'duplicate':
|
1289 |
-
const clone =
|
1290 |
-
|
1291 |
-
|
1292 |
break;
|
1293 |
case 'remove':
|
1294 |
if (confirm('要素を削除しますか?')) {
|
1295 |
-
|
1296 |
-
|
1297 |
}
|
1298 |
break;
|
1299 |
case 'toggle-visibility':
|
1300 |
-
if (
|
1301 |
-
|
1302 |
} else {
|
1303 |
-
|
1304 |
}
|
1305 |
-
|
1306 |
break;
|
1307 |
case 'force-state':
|
1308 |
const state = prompt('強制する状態を入力 (例: hover, active, focus)', 'hover');
|
1309 |
if (state) {
|
1310 |
-
|
1311 |
'force-focus-within', 'force-focus-visible', 'force-target');
|
1312 |
-
|
1313 |
-
|
1314 |
}
|
1315 |
break;
|
1316 |
}
|
@@ -1318,13 +1541,13 @@
|
|
1318 |
|
1319 |
// インライン編集関数
|
1320 |
function startInlineEdit(element, initialValue, callback) {
|
1321 |
-
if (
|
1322 |
|
1323 |
const originalValue = element.textContent;
|
1324 |
const rect = element.getBoundingClientRect();
|
1325 |
|
1326 |
const input = document.createElement('input');
|
1327 |
-
input.className = '
|
1328 |
input.value = initialValue || originalValue;
|
1329 |
input.style.position = 'absolute';
|
1330 |
input.style.left = `${rect.left}px`;
|
@@ -1335,7 +1558,7 @@
|
|
1335 |
input.focus();
|
1336 |
input.select();
|
1337 |
|
1338 |
-
|
1339 |
element: element,
|
1340 |
input: input,
|
1341 |
callback: callback
|
@@ -1378,32 +1601,32 @@
|
|
1378 |
function cleanup() {
|
1379 |
document.removeEventListener('click', clickOutsideHandler);
|
1380 |
input.remove();
|
1381 |
-
|
1382 |
}
|
1383 |
}
|
1384 |
|
1385 |
// Consoleパネル作成
|
1386 |
function createConsolePanel() {
|
1387 |
const panel = document.createElement('div');
|
1388 |
-
panel.className = '
|
1389 |
panel.id = 'console-panel';
|
1390 |
|
1391 |
const log = document.createElement('div');
|
1392 |
-
log.id = '
|
1393 |
|
1394 |
const input = document.createElement('input');
|
1395 |
-
input.className = '
|
1396 |
input.placeholder = 'ここにJavaScriptを入力... (Enterで実行)';
|
1397 |
input.onkeypress = (e) => {
|
1398 |
if (e.key === 'Enter') {
|
1399 |
try {
|
1400 |
const result = eval(e.target.value);
|
1401 |
if (result !== undefined) {
|
1402 |
-
logMessage('> ' + e.target.value, '
|
1403 |
-
logMessage('← ' + formatOutput(result), '
|
1404 |
}
|
1405 |
} catch (err) {
|
1406 |
-
logMessage(err.message, '
|
1407 |
}
|
1408 |
e.target.value = '';
|
1409 |
}
|
@@ -1422,22 +1645,22 @@
|
|
1422 |
|
1423 |
console.log = (...args) => {
|
1424 |
originalConsole.log.apply(console, args);
|
1425 |
-
logMessage(args.map(arg => formatOutput(arg)).join(' '), '
|
1426 |
};
|
1427 |
|
1428 |
console.error = (...args) => {
|
1429 |
originalConsole.error.apply(console, args);
|
1430 |
-
logMessage(args.map(arg => formatOutput(arg)).join(' '), '
|
1431 |
};
|
1432 |
|
1433 |
console.warn = (...args) => {
|
1434 |
originalConsole.warn.apply(console, args);
|
1435 |
-
logMessage(args.map(arg => formatOutput(arg)).join(' '), '
|
1436 |
};
|
1437 |
|
1438 |
console.info = (...args) => {
|
1439 |
originalConsole.info.apply(console, args);
|
1440 |
-
logMessage(args.map(arg => formatOutput(arg)).join(' '), '
|
1441 |
};
|
1442 |
|
1443 |
function logMessage(message, className) {
|
@@ -1449,23 +1672,23 @@
|
|
1449 |
}
|
1450 |
|
1451 |
function formatOutput(output) {
|
1452 |
-
if (output === null) return '<span class="
|
1453 |
-
if (output === undefined) return '<span class="
|
1454 |
-
if (typeof output === 'boolean') return `<span class="
|
1455 |
-
if (typeof output === 'number') return `<span class="
|
1456 |
-
if (typeof output === 'string') return `<span class="
|
1457 |
-
if (typeof output === 'function') return `<span class="
|
1458 |
-
if (Array.isArray(output)) return `<span class="
|
1459 |
if (typeof output === 'object') {
|
1460 |
try {
|
1461 |
-
return `<span class="
|
1462 |
-
.replace(/"([^"]+)":/g, '<span class="
|
1463 |
-
.replace(/"([^"]+)"/g, '<span class="
|
1464 |
-
.replace(/\b(true|false)\b/g, '<span class="
|
1465 |
-
.replace(/\b(null)\b/g, '<span class="
|
1466 |
-
.replace(/\b(\d+)\b/g, '<span class="
|
1467 |
} catch (e) {
|
1468 |
-
return `<span class="
|
1469 |
}
|
1470 |
}
|
1471 |
return output;
|
@@ -1477,19 +1700,19 @@
|
|
1477 |
// Elementsパネル作成
|
1478 |
function createElementsPanel() {
|
1479 |
const panel = document.createElement('div');
|
1480 |
-
panel.className = '
|
1481 |
panel.id = 'elements-panel';
|
1482 |
|
1483 |
const container = document.createElement('div');
|
1484 |
-
container.className = '
|
1485 |
|
1486 |
const tree = document.createElement('div');
|
1487 |
-
tree.className = '
|
1488 |
-
tree.id = '
|
1489 |
|
1490 |
const cssPanel = document.createElement('div');
|
1491 |
-
cssPanel.className = '
|
1492 |
-
cssPanel.id = '
|
1493 |
|
1494 |
container.appendChild(tree);
|
1495 |
container.appendChild(cssPanel);
|
@@ -1497,17 +1720,17 @@
|
|
1497 |
|
1498 |
// CSSパネル更新関数
|
1499 |
function updateCSSPanel(element) {
|
1500 |
-
const cssPanel = document.getElementById('
|
1501 |
cssPanel.innerHTML = '';
|
1502 |
|
1503 |
if (!element) return;
|
1504 |
|
1505 |
if (element.style.length > 0) {
|
1506 |
const inlineRule = document.createElement('div');
|
1507 |
-
inlineRule.className = '
|
1508 |
|
1509 |
const selector = document.createElement('div');
|
1510 |
-
selector.className = '
|
1511 |
selector.textContent = 'インラインスタイル';
|
1512 |
inlineRule.appendChild(selector);
|
1513 |
|
@@ -1516,20 +1739,20 @@
|
|
1516 |
const propValue = element.style[propName];
|
1517 |
|
1518 |
const propDiv = document.createElement('div');
|
1519 |
-
propDiv.className = '
|
1520 |
|
1521 |
const nameSpan = document.createElement('span');
|
1522 |
-
nameSpan.className = '
|
1523 |
nameSpan.textContent = propName;
|
1524 |
nameSpan.onclick = () => editCSSProperty(element, propName, 'style');
|
1525 |
|
1526 |
const valueSpan = document.createElement('span');
|
1527 |
-
valueSpan.className = '
|
1528 |
valueSpan.textContent = propValue;
|
1529 |
valueSpan.onclick = () => editCSSProperty(element, propName, 'style');
|
1530 |
|
1531 |
const toggleSpan = document.createElement('span');
|
1532 |
-
toggleSpan.className = '
|
1533 |
toggleSpan.textContent = '×';
|
1534 |
toggleSpan.title = 'プロパティを無効化';
|
1535 |
toggleSpan.onclick = () => {
|
@@ -1548,10 +1771,10 @@
|
|
1548 |
|
1549 |
const computedStyles = window.getComputedStyle(element);
|
1550 |
const computedRule = document.createElement('div');
|
1551 |
-
computedRule.className = '
|
1552 |
|
1553 |
const computedSelector = document.createElement('div');
|
1554 |
-
computedSelector.className = '
|
1555 |
computedSelector.textContent = '計算されたスタイル';
|
1556 |
computedRule.appendChild(computedSelector);
|
1557 |
|
@@ -1564,14 +1787,14 @@
|
|
1564 |
const value = computedStyles[prop];
|
1565 |
|
1566 |
const propDiv = document.createElement('div');
|
1567 |
-
propDiv.className = '
|
1568 |
|
1569 |
const nameSpan = document.createElement('span');
|
1570 |
-
nameSpan.className = '
|
1571 |
nameSpan.textContent = prop;
|
1572 |
|
1573 |
const valueSpan = document.createElement('span');
|
1574 |
-
valueSpan.className = '
|
1575 |
valueSpan.textContent = value;
|
1576 |
|
1577 |
propDiv.appendChild(nameSpan);
|
@@ -1586,23 +1809,23 @@
|
|
1586 |
function buildDOMTree(node, parentElement, depth = 0, isRoot = false) {
|
1587 |
if (node.nodeType === Node.ELEMENT_NODE) {
|
1588 |
const element = document.createElement('div');
|
1589 |
-
element.className = '
|
1590 |
element.style.marginLeft = `${depth * 15}px`;
|
1591 |
element.dataset.elementId = node.id || Math.random().toString(36).substr(2, 9);
|
1592 |
|
1593 |
// 右クリックイベント
|
1594 |
element.oncontextmenu = (e) => {
|
1595 |
e.preventDefault();
|
1596 |
-
|
1597 |
-
|
1598 |
|
1599 |
-
document.querySelectorAll('.
|
1600 |
element.classList.add('selected');
|
1601 |
|
1602 |
if (node !== document.documentElement) {
|
1603 |
-
|
1604 |
-
|
1605 |
-
|
1606 |
}
|
1607 |
|
1608 |
updateCSSPanel(node);
|
@@ -1610,13 +1833,13 @@
|
|
1610 |
|
1611 |
// 左クリックで選択
|
1612 |
element.onclick = (e) => {
|
1613 |
-
if (e.target.classList.contains('
|
1614 |
|
1615 |
e.stopPropagation();
|
1616 |
-
|
1617 |
-
|
1618 |
|
1619 |
-
document.querySelectorAll('.
|
1620 |
element.classList.add('selected');
|
1621 |
|
1622 |
updateCSSPanel(node);
|
@@ -1628,10 +1851,10 @@
|
|
1628 |
|
1629 |
if (hasChildren) {
|
1630 |
const toggle = document.createElement('div');
|
1631 |
-
toggle.className = '
|
1632 |
toggle.onclick = (e) => {
|
1633 |
e.stopPropagation();
|
1634 |
-
const children = element.querySelector('.
|
1635 |
if (children) {
|
1636 |
if (children.style.maxHeight === '0px') {
|
1637 |
children.style.maxHeight = children.scrollHeight + 'px';
|
@@ -1647,11 +1870,11 @@
|
|
1647 |
|
1648 |
// タグ名(HTML要素は編集不可)
|
1649 |
const tag = document.createElement('span');
|
1650 |
-
tag.className = '
|
1651 |
tag.textContent = `<${node.tagName.toLowerCase()}`;
|
1652 |
|
1653 |
if (node !== document.documentElement) {
|
1654 |
-
tag.classList.add('
|
1655 |
tag.onclick = (e) => {
|
1656 |
e.stopPropagation();
|
1657 |
startInlineEdit(tag, node.tagName.toLowerCase(), (newValue) => {
|
@@ -1661,8 +1884,8 @@
|
|
1661 |
});
|
1662 |
newElement.innerHTML = node.innerHTML;
|
1663 |
node.parentNode.replaceChild(newElement, node);
|
1664 |
-
|
1665 |
-
|
1666 |
});
|
1667 |
};
|
1668 |
}
|
@@ -1672,16 +1895,16 @@
|
|
1672 |
// 属性(HTML要素は編集不可)
|
1673 |
Array.from(node.attributes).forEach(attr => {
|
1674 |
const attrSpan = document.createElement('span');
|
1675 |
-
attrSpan.className = '
|
1676 |
attrSpan.textContent = ` ${attr.name}="${attr.value}"`;
|
1677 |
|
1678 |
if (node !== document.documentElement) {
|
1679 |
-
attrSpan.classList.add('
|
1680 |
attrSpan.onclick = (e) => {
|
1681 |
e.stopPropagation();
|
1682 |
startInlineEdit(attrSpan, attr.value, (newValue) => {
|
1683 |
node.setAttribute(attr.name, newValue);
|
1684 |
-
|
1685 |
});
|
1686 |
};
|
1687 |
}
|
@@ -1693,7 +1916,7 @@
|
|
1693 |
|
1694 |
if (hasChildren) {
|
1695 |
const childrenContainer = document.createElement('div');
|
1696 |
-
childrenContainer.className = '
|
1697 |
childrenContainer.style.maxHeight = isRoot ? 'none' : '0px';
|
1698 |
|
1699 |
node.childNodes.forEach(child => {
|
@@ -1703,7 +1926,7 @@
|
|
1703 |
if (node.tagName.toLowerCase() !== 'br') {
|
1704 |
const closeTag = document.createElement('div');
|
1705 |
closeTag.style.marginLeft = `${depth * 15}px`;
|
1706 |
-
closeTag.innerHTML = `<span class="
|
1707 |
childrenContainer.appendChild(closeTag);
|
1708 |
}
|
1709 |
|
@@ -1714,13 +1937,13 @@
|
|
1714 |
} else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
|
1715 |
const text = document.createElement('div');
|
1716 |
text.style.marginLeft = `${depth * 15}px`;
|
1717 |
-
text.className = '
|
1718 |
text.textContent = `"${node.textContent.trim()}"`;
|
1719 |
text.onclick = (e) => {
|
1720 |
e.stopPropagation();
|
1721 |
startInlineEdit(text, node.textContent.trim(), (newValue) => {
|
1722 |
node.textContent = newValue;
|
1723 |
-
|
1724 |
});
|
1725 |
};
|
1726 |
parentElement.appendChild(text);
|
@@ -1743,23 +1966,23 @@
|
|
1743 |
}
|
1744 |
|
1745 |
updateCSSPanel(element);
|
1746 |
-
|
1747 |
}
|
1748 |
}
|
1749 |
|
1750 |
// refreshElementsPanel関数の定義
|
1751 |
-
|
1752 |
-
let tree = document.getElementById('
|
1753 |
|
1754 |
if (!tree) {
|
1755 |
const panel = document.getElementById('elements-panel');
|
1756 |
if (panel) {
|
1757 |
-
const container = panel.querySelector('.
|
1758 |
if (container) {
|
1759 |
tree = document.createElement('div');
|
1760 |
-
tree.className = '
|
1761 |
-
tree.id = '
|
1762 |
-
container.insertBefore(tree, container.querySelector('.
|
1763 |
}
|
1764 |
}
|
1765 |
}
|
@@ -1769,15 +1992,15 @@
|
|
1769 |
tree.innerHTML = '';
|
1770 |
buildDOMTree(document.documentElement, tree, 0, true);
|
1771 |
|
1772 |
-
if (
|
1773 |
-
const elementId =
|
1774 |
.find(attr => attr.name.startsWith('data-element-id'))?.value;
|
1775 |
|
1776 |
if (elementId) {
|
1777 |
const node = document.querySelector(`[data-element-id="${elementId}"]`);
|
1778 |
if (node) {
|
1779 |
node.classList.add('selected');
|
1780 |
-
updateCSSPanel(
|
1781 |
}
|
1782 |
}
|
1783 |
}
|
@@ -1788,7 +2011,7 @@
|
|
1788 |
|
1789 |
// 初期表示
|
1790 |
setTimeout(() => {
|
1791 |
-
|
1792 |
}, 0);
|
1793 |
|
1794 |
return panel;
|
@@ -1796,7 +2019,7 @@
|
|
1796 |
|
1797 |
// 開発者ツール表示/非表示
|
1798 |
function toggleDevTools() {
|
1799 |
-
const container = document.getElementById('
|
1800 |
if (container.style.display === 'none') {
|
1801 |
container.style.display = 'flex';
|
1802 |
} else {
|
@@ -1807,13 +2030,13 @@
|
|
1807 |
// 開くボタン作成
|
1808 |
function createOpenButton() {
|
1809 |
const button = document.createElement('button');
|
1810 |
-
button.id = '
|
1811 |
button.textContent = '開発者ツールを開く';
|
1812 |
button.style.position = 'fixed';
|
1813 |
button.style.bottom = '10px';
|
1814 |
button.style.right = '10px';
|
1815 |
button.style.padding = '8px 16px';
|
1816 |
-
button.style.background = 'var(--
|
1817 |
button.style.color = '#000';
|
1818 |
button.style.border = 'none';
|
1819 |
button.style.borderRadius = '4px';
|
@@ -1826,13 +2049,9 @@
|
|
1826 |
|
1827 |
// 初期化
|
1828 |
document.addEventListener('DOMContentLoaded', function() {
|
1829 |
-
|
1830 |
createOpenButton();
|
1831 |
-
|
1832 |
-
|
1833 |
-
setTimeout(() => {
|
1834 |
-
createDevTools();
|
1835 |
-
console.log('開発者ツールが初期化されました');
|
1836 |
-
}, 1000);
|
1837 |
});
|
1838 |
})();
|
|
|
1 |
(function() {
|
2 |
// グローバル変数宣言
|
3 |
+
let contextMenu = null;
|
4 |
+
let selectedElement = null;
|
5 |
+
let selectedDOMNode = null;
|
6 |
+
let activeEditElement = null;
|
7 |
+
let networkRequests = [];
|
8 |
+
let selectedRequest = null;
|
9 |
+
let vitalsData = {
|
10 |
+
CLS: null,
|
11 |
+
FCP: null,
|
12 |
+
FID: null,
|
13 |
+
LCP: null,
|
14 |
+
TTFB: null
|
15 |
+
};
|
16 |
+
let observer = null;
|
17 |
+
let refreshElementsPanel = null;
|
18 |
+
|
19 |
+
// Web Vitalsスクリプトを動的に読み込み
|
20 |
+
const loadWebVitals = () => {
|
21 |
+
return new Promise((resolve) => {
|
22 |
+
if (window.webVitals) {
|
23 |
+
resolve();
|
24 |
+
return;
|
25 |
}
|
26 |
+
|
27 |
+
const webVitalsScript = document.createElement('script');
|
28 |
+
webVitalsScript.src = 'https://unpkg.com/[email protected]/dist/web-vitals.iife.js';
|
29 |
+
webVitalsScript.onload = resolve;
|
30 |
+
document.head.appendChild(webVitalsScript);
|
|
|
|
|
31 |
});
|
32 |
};
|
33 |
|
34 |
+
// スタイルの動的追加 (ダークテーマ)
|
35 |
const style = document.createElement('style');
|
36 |
style.textContent = `
|
37 |
+
:root {
|
38 |
+
--bg-color: #1e1e1e;
|
39 |
+
--panel-bg: #252526;
|
40 |
+
--border-color: #3c3c3c;
|
41 |
+
--text-color: #e0e0e0;
|
42 |
+
--text-muted: #a0a0a0;
|
43 |
+
--primary-color: #007acc;
|
44 |
+
--primary-hover: #3e9fda;
|
45 |
+
--success-color: #4caf50;
|
46 |
+
--error-color: #f44336;
|
47 |
+
--warning-color: #ff9800;
|
48 |
+
--info-color: #2196f3;
|
49 |
+
--highlight-bg: rgba(0, 122, 204, 0.2);
|
50 |
+
--tab-bg: #2d2d2d;
|
51 |
+
--tab-active-bg: #1e1e1e;
|
52 |
+
--console-log-color: #e0e0e0;
|
53 |
+
--console-error-color: #f44336;
|
54 |
+
--console-warn-color: #ff9800;
|
55 |
+
--console-info-color: #4fc3f7;
|
56 |
+
--json-key: #9cdcfe;
|
57 |
+
--json-string: #ce9178;
|
58 |
+
--json-number: #b5cea8;
|
59 |
+
--json-boolean: #569cd6;
|
60 |
+
--json-null: #569cd6;
|
61 |
+
--dom-tag: #569cd6;
|
62 |
+
--dom-attr: #9cdcfe;
|
63 |
+
--dom-text: #d4d4d4;
|
64 |
+
}
|
65 |
+
|
66 |
+
.devtools-container {
|
67 |
position: fixed;
|
68 |
bottom: 0;
|
69 |
left: 0;
|
70 |
width: 100%;
|
71 |
height: 300px;
|
72 |
+
background-color: var(--panel-bg);
|
73 |
+
border-top: 1px solid var(--border-color);
|
74 |
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
|
75 |
z-index: 9999;
|
76 |
display: flex;
|
77 |
flex-direction: column;
|
78 |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
79 |
+
color: var(--text-color);
|
80 |
}
|
81 |
|
82 |
+
.devtools-header {
|
83 |
display: flex;
|
84 |
justify-content: space-between;
|
85 |
align-items: center;
|
86 |
padding: 5px 10px;
|
87 |
+
background-color: var(--tab-bg);
|
88 |
+
border-bottom: 1px solid var(--border-color);
|
89 |
}
|
90 |
|
91 |
+
.devtools-tabs {
|
92 |
display: flex;
|
93 |
gap: 5px;
|
94 |
}
|
95 |
|
96 |
+
.devtools-tab {
|
97 |
padding: 5px 10px;
|
98 |
cursor: pointer;
|
99 |
border-radius: 3px 3px 0 0;
|
100 |
+
background-color: var(--tab-bg);
|
101 |
+
border: 1px solid var(--border-color);
|
102 |
border-bottom: none;
|
103 |
font-size: 12px;
|
104 |
+
color: var(--text-muted);
|
105 |
}
|
106 |
|
107 |
+
.devtools-tab.active {
|
108 |
+
background-color: var(--tab-active-bg);
|
109 |
+
color: var(--text-color);
|
110 |
+
border-bottom: 1px solid var(--tab-active-bg);
|
111 |
margin-bottom: -1px;
|
112 |
font-weight: bold;
|
113 |
}
|
114 |
|
115 |
+
.devtools-close {
|
116 |
background: none;
|
117 |
border: none;
|
118 |
font-size: 16px;
|
119 |
cursor: pointer;
|
120 |
padding: 0 5px;
|
121 |
+
color: var(--text-color);
|
122 |
}
|
123 |
|
124 |
+
.devtools-content {
|
125 |
flex: 1;
|
126 |
overflow: auto;
|
127 |
position: relative;
|
128 |
+
background-color: var(--panel-bg);
|
129 |
}
|
130 |
|
131 |
+
.devtools-panel {
|
132 |
position: absolute;
|
133 |
top: 0;
|
134 |
left: 0;
|
|
|
137 |
padding: 10px;
|
138 |
overflow: auto;
|
139 |
display: none;
|
140 |
+
background-color: var(--panel-bg);
|
141 |
}
|
142 |
|
143 |
+
.devtools-panel.active {
|
144 |
display: block;
|
145 |
}
|
146 |
|
147 |
/* Console スタイル */
|
148 |
+
#console-log {
|
149 |
white-space: pre-wrap;
|
150 |
margin: 0;
|
151 |
line-height: 1.4;
|
152 |
flex: 1;
|
153 |
+
color: var(--console-log-color);
|
154 |
font-family: 'Consolas', 'Monaco', monospace;
|
155 |
font-size: 13px;
|
156 |
}
|
157 |
|
158 |
+
.console-log {
|
159 |
+
color: var(--console-log-color);
|
160 |
}
|
161 |
|
162 |
+
.console-error {
|
163 |
+
color: var(--console-error-color);
|
164 |
}
|
165 |
|
166 |
+
.console-warn {
|
167 |
+
color: var(--console-warn-color);
|
168 |
}
|
169 |
|
170 |
+
.console-info {
|
171 |
+
color: var(--console-info-color);
|
172 |
}
|
173 |
|
174 |
+
.console-input {
|
175 |
width: calc(100% - 16px);
|
176 |
+
background: var(--tab-bg);
|
177 |
+
border: 1px solid var(--border-color);
|
178 |
+
color: var(--text-color);
|
179 |
padding: 8px;
|
180 |
margin-top: 10px;
|
181 |
font-family: monospace;
|
|
|
183 |
}
|
184 |
|
185 |
/* Elements スタイル */
|
186 |
+
.elements-container {
|
187 |
display: flex;
|
188 |
flex: 1;
|
189 |
overflow: hidden;
|
190 |
}
|
191 |
|
192 |
+
.dom-tree {
|
193 |
font-family: 'Consolas', 'Monaco', monospace;
|
194 |
flex: 1;
|
195 |
overflow: auto;
|
196 |
+
border-right: 1px solid var(--border-color);
|
197 |
padding-right: 10px;
|
198 |
+
color: var(--dom-text);
|
199 |
font-size: 13px;
|
200 |
}
|
201 |
|
202 |
+
.dom-node {
|
203 |
margin-left: 15px;
|
204 |
position: relative;
|
205 |
line-height: 1.4;
|
206 |
transition: background-color 0.3s;
|
207 |
}
|
208 |
|
209 |
+
.dom-node.selected {
|
210 |
+
background: var(--highlight-bg);
|
211 |
}
|
212 |
|
213 |
+
.dom-node.highlight {
|
214 |
+
animation: highlight-fade 1.5s;
|
215 |
}
|
216 |
|
217 |
+
@keyframes highlight-fade {
|
218 |
0% { background-color: rgba(79, 195, 247, 0.5); }
|
219 |
100% { background-color: transparent; }
|
220 |
}
|
221 |
|
222 |
+
.dom-tag {
|
223 |
+
color: var(--dom-tag);
|
224 |
font-weight: bold;
|
225 |
}
|
226 |
|
227 |
+
.dom-attr {
|
228 |
+
color: var(--dom-attr);
|
229 |
}
|
230 |
|
231 |
+
.dom-attr.editable:hover {
|
232 |
text-decoration: underline;
|
233 |
cursor: pointer;
|
234 |
}
|
235 |
|
236 |
+
.dom-text {
|
237 |
+
color: var(--dom-text);
|
238 |
}
|
239 |
|
240 |
+
.dom-edit-input {
|
241 |
+
background: var(--panel-bg);
|
242 |
+
border: 1px solid var(--primary-color);
|
243 |
padding: 0 2px;
|
244 |
margin: -1px 0;
|
245 |
font-family: monospace;
|
246 |
min-width: 50px;
|
247 |
+
color: var(--text-color);
|
248 |
}
|
249 |
|
250 |
+
.css-panel {
|
251 |
flex: 1;
|
252 |
overflow: auto;
|
253 |
padding-left: 10px;
|
254 |
font-size: 13px;
|
255 |
}
|
256 |
|
257 |
+
.css-rule {
|
258 |
margin-bottom: 15px;
|
259 |
+
border: 1px solid var(--border-color);
|
260 |
padding: 8px;
|
261 |
+
background-color: var(--tab-bg);
|
262 |
border-radius: 3px;
|
263 |
}
|
264 |
|
265 |
+
.css-selector {
|
266 |
+
color: var(--primary-color);
|
267 |
margin-bottom: 5px;
|
268 |
font-weight: bold;
|
269 |
}
|
270 |
|
271 |
+
.css-property {
|
272 |
display: flex;
|
273 |
margin-bottom: 3px;
|
274 |
}
|
275 |
|
276 |
+
.css-property-name {
|
277 |
+
color: var(--dom-attr);
|
278 |
min-width: 120px;
|
279 |
}
|
280 |
|
281 |
+
.css-property-value {
|
282 |
+
color: var(--dom-text);
|
283 |
flex: 1;
|
284 |
}
|
285 |
|
286 |
+
.css-toggle {
|
287 |
margin-left: 10px;
|
288 |
+
color: var(--error-color);
|
289 |
cursor: pointer;
|
290 |
}
|
291 |
|
292 |
/* Context Menu */
|
293 |
+
.context-menu {
|
294 |
position: absolute;
|
295 |
+
background: var(--panel-bg);
|
296 |
+
border: 1px solid var(--border-color);
|
297 |
z-index: 10000;
|
298 |
min-width: 200px;
|
299 |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
|
|
302 |
overflow: hidden;
|
303 |
}
|
304 |
|
305 |
+
.context-menu-item {
|
306 |
padding: 8px 15px;
|
307 |
cursor: pointer;
|
308 |
+
color: var(--text-color);
|
309 |
font-size: 13px;
|
310 |
}
|
311 |
|
312 |
+
.context-menu-item:hover {
|
313 |
+
background: var(--primary-color);
|
314 |
color: #000;
|
315 |
}
|
316 |
|
317 |
/* Storage スタイル */
|
318 |
+
.storage-table {
|
319 |
width: 100%;
|
320 |
border-collapse: collapse;
|
321 |
margin-bottom: 10px;
|
322 |
font-size: 13px;
|
323 |
}
|
324 |
|
325 |
+
.storage-table th, .storage-table td {
|
326 |
+
border: 1px solid var(--border-color);
|
327 |
padding: 5px;
|
328 |
text-align: left;
|
329 |
}
|
330 |
|
331 |
+
.storage-table th {
|
332 |
+
background: var(--tab-bg);
|
333 |
}
|
334 |
|
335 |
+
.storage-actions {
|
336 |
display: flex;
|
337 |
gap: 5px;
|
338 |
}
|
339 |
|
340 |
+
.storage-btn {
|
341 |
+
background: var(--primary-color);
|
342 |
border: none;
|
343 |
padding: 2px 5px;
|
344 |
cursor: pointer;
|
|
|
347 |
font-size: 12px;
|
348 |
}
|
349 |
|
350 |
+
.editable {
|
351 |
cursor: pointer;
|
352 |
padding: 2px 5px;
|
353 |
border: 1px dashed transparent;
|
354 |
}
|
355 |
|
356 |
+
.editable:hover {
|
357 |
+
border-color: var(--primary-color);
|
358 |
}
|
359 |
|
360 |
+
.add-btn {
|
361 |
+
background: var(--primary-color);
|
362 |
color: #000;
|
363 |
border: none;
|
364 |
padding: 5px 10px;
|
|
|
368 |
font-size: 13px;
|
369 |
}
|
370 |
|
371 |
+
.add-btn:hover {
|
372 |
+
background: var(--primary-hover);
|
373 |
}
|
374 |
|
375 |
/* Network スタイル */
|
376 |
+
.network-container {
|
377 |
display: flex;
|
378 |
height: 100%;
|
379 |
overflow: hidden;
|
380 |
}
|
381 |
|
382 |
+
.network-requests {
|
383 |
width: 40%;
|
384 |
overflow-y: auto;
|
385 |
+
border-right: 1px solid var(--border-color);
|
386 |
font-size: 13px;
|
387 |
}
|
388 |
|
389 |
+
.network-details {
|
390 |
width: 60%;
|
391 |
overflow-y: auto;
|
392 |
padding-left: 10px;
|
393 |
}
|
394 |
|
395 |
+
.network-request {
|
396 |
padding: 8px;
|
397 |
+
border-bottom: 1px solid var(--border-color);
|
398 |
cursor: pointer;
|
399 |
display: flex;
|
400 |
align-items: center;
|
401 |
}
|
402 |
|
403 |
+
.network-request:hover {
|
404 |
background-color: rgba(0, 122, 204, 0.1);
|
405 |
}
|
406 |
|
407 |
+
.network-request.selected {
|
408 |
+
background-color: var(--highlight-bg);
|
409 |
}
|
410 |
|
411 |
+
.network-status {
|
412 |
width: 20px;
|
413 |
height: 20px;
|
414 |
border-radius: 50%;
|
|
|
419 |
flex-shrink: 0;
|
420 |
}
|
421 |
|
422 |
+
.network-status.success {
|
423 |
+
background-color: var(--success-color);
|
424 |
color: white;
|
425 |
}
|
426 |
|
427 |
+
.network-status.error {
|
428 |
+
background-color: var(--error-color);
|
429 |
color: white;
|
430 |
}
|
431 |
|
432 |
+
.network-method {
|
433 |
font-weight: bold;
|
434 |
margin-right: 8px;
|
435 |
+
color: var(--primary-color);
|
436 |
min-width: 40px;
|
437 |
}
|
438 |
|
439 |
+
.network-url {
|
440 |
flex: 1;
|
441 |
white-space: nowrap;
|
442 |
overflow: hidden;
|
443 |
text-overflow: ellipsis;
|
444 |
}
|
445 |
|
446 |
+
.network-time {
|
447 |
+
color: var(--text-muted);
|
448 |
font-size: 11px;
|
449 |
margin-left: 8px;
|
450 |
}
|
451 |
|
452 |
+
.network-detail-section {
|
453 |
margin-bottom: 15px;
|
454 |
}
|
455 |
|
456 |
+
.network-detail-title {
|
457 |
font-weight: bold;
|
458 |
margin-bottom: 5px;
|
459 |
+
color: var(--primary-color);
|
460 |
}
|
461 |
|
462 |
+
.network-detail-content {
|
463 |
+
background: var(--tab-bg);
|
464 |
padding: 8px;
|
465 |
border-radius: 3px;
|
466 |
+
border: 1px solid var(--border-color);
|
467 |
font-family: monospace;
|
468 |
white-space: pre-wrap;
|
469 |
font-size: 12px;
|
|
|
471 |
overflow-y: auto;
|
472 |
}
|
473 |
|
474 |
+
/* Web Vitals スタイル */
|
475 |
+
.vitals-container {
|
476 |
+
display: flex;
|
477 |
+
flex-direction: column;
|
478 |
+
gap: 15px;
|
479 |
+
}
|
480 |
+
|
481 |
+
.vital-card {
|
482 |
+
background: var(--tab-bg);
|
483 |
+
border: 1px solid var(--border-color);
|
484 |
+
border-radius: 5px;
|
485 |
+
padding: 15px;
|
486 |
+
}
|
487 |
+
|
488 |
+
.vital-title {
|
489 |
+
font-weight: bold;
|
490 |
+
margin-bottom: 10px;
|
491 |
+
color: var(--primary-color);
|
492 |
+
display: flex;
|
493 |
+
justify-content: space-between;
|
494 |
+
align-items: center;
|
495 |
+
}
|
496 |
+
|
497 |
+
.vital-value {
|
498 |
+
font-size: 24px;
|
499 |
+
font-weight: bold;
|
500 |
+
margin: 10px 0;
|
501 |
+
}
|
502 |
+
|
503 |
+
.vital-good {
|
504 |
+
color: var(--success-color);
|
505 |
+
}
|
506 |
+
|
507 |
+
.vital-needs-improvement {
|
508 |
+
color: var(--warning-color);
|
509 |
+
}
|
510 |
+
|
511 |
+
.vital-poor {
|
512 |
+
color: var(--error-color);
|
513 |
+
}
|
514 |
+
|
515 |
+
.vital-description {
|
516 |
+
font-size: 13px;
|
517 |
+
color: var(--text-muted);
|
518 |
+
}
|
519 |
+
|
520 |
+
.vital-thresholds {
|
521 |
+
display: flex;
|
522 |
+
margin-top: 10px;
|
523 |
+
font-size: 12px;
|
524 |
+
}
|
525 |
+
|
526 |
+
.vital-threshold {
|
527 |
+
flex: 1;
|
528 |
+
text-align: center;
|
529 |
+
padding: 5px;
|
530 |
+
border-radius: 3px;
|
531 |
+
}
|
532 |
+
|
533 |
+
.vital-threshold.active {
|
534 |
+
background: rgba(0, 122, 204, 0.2);
|
535 |
+
}
|
536 |
+
|
537 |
/* DOM Tree Toggle */
|
538 |
+
.dom-toggle {
|
539 |
position: absolute;
|
540 |
left: -12px;
|
541 |
top: 2px;
|
542 |
width: 10px;
|
543 |
height: 10px;
|
544 |
cursor: pointer;
|
545 |
+
background-color: var(--text-muted);
|
546 |
clip-path: polygon(0 0, 100% 50%, 0 100%);
|
547 |
transition: transform 0.2s;
|
548 |
}
|
549 |
|
550 |
+
.dom-toggle.collapsed {
|
551 |
transform: rotate(-90deg);
|
552 |
}
|
553 |
|
554 |
+
.dom-children {
|
555 |
overflow: hidden;
|
556 |
transition: max-height 0.3s ease-out;
|
557 |
}
|
558 |
|
559 |
/* JSON スタイル */
|
560 |
+
.json-key {
|
561 |
+
color: var(--json-key);
|
|
|
|
|
|
|
|
|
562 |
}
|
563 |
|
564 |
+
.json-string {
|
565 |
+
color: var(--json-string);
|
566 |
}
|
567 |
|
568 |
+
.json-number {
|
569 |
+
color: var(--json-number);
|
570 |
}
|
571 |
|
572 |
+
.json-boolean {
|
573 |
+
color: var(--json-boolean);
|
574 |
}
|
575 |
|
576 |
+
.json-null {
|
577 |
+
color: var(--json-null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
578 |
}
|
579 |
`;
|
580 |
document.head.appendChild(style);
|
581 |
|
582 |
+
// DOM変更を監視するMutationObserver
|
583 |
+
const setupMutationObserver = () => {
|
584 |
+
observer = new MutationObserver((mutations) => {
|
585 |
+
mutations.forEach((mutation) => {
|
586 |
+
if (mutation.type === 'childList') {
|
587 |
+
mutation.addedNodes.forEach((node) => {
|
588 |
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
589 |
+
highlightNode(node);
|
590 |
+
}
|
591 |
+
});
|
592 |
+
}
|
593 |
+
});
|
594 |
+
if (refreshElementsPanel) {
|
595 |
+
refreshElementsPanel();
|
596 |
+
}
|
597 |
+
});
|
598 |
+
|
599 |
+
observer.observe(document.documentElement, {
|
600 |
+
childList: true,
|
601 |
+
subtree: true,
|
602 |
+
attributes: true,
|
603 |
+
characterData: true
|
604 |
+
});
|
605 |
+
};
|
606 |
+
|
607 |
+
// ノードをハイライト表示
|
608 |
+
function highlightNode(node) {
|
609 |
+
const elementId = node.id || Math.random().toString(36).substr(2, 9);
|
610 |
+
node.setAttribute('data-element-id', elementId);
|
611 |
+
|
612 |
+
setTimeout(() => {
|
613 |
+
const domNode = document.querySelector(`[data-element-id="${elementId}"]`);
|
614 |
+
if (domNode) {
|
615 |
+
domNode.classList.add('highlight');
|
616 |
+
setTimeout(() => {
|
617 |
+
domNode.classList.remove('highlight');
|
618 |
+
}, 1500);
|
619 |
+
}
|
620 |
+
}, 100);
|
621 |
+
}
|
622 |
+
|
623 |
// 開発者ツールのメイン関数
|
624 |
const createDevTools = () => {
|
625 |
const container = document.createElement('div');
|
626 |
+
container.className = 'devtools-container';
|
627 |
+
container.id = 'devtools-container';
|
628 |
container.style.display = 'none';
|
629 |
|
630 |
// ヘッダー部分
|
631 |
const header = document.createElement('div');
|
632 |
+
header.className = 'devtools-header';
|
633 |
|
634 |
const tabs = document.createElement('div');
|
635 |
+
tabs.className = 'devtools-tabs';
|
636 |
|
637 |
const consoleTab = createTab('Console', 'console');
|
638 |
const elementsTab = createTab('Elements', 'elements');
|
639 |
const networkTab = createTab('Network', 'network');
|
640 |
const storageTab = createTab('Storage', 'storage');
|
641 |
+
const vitalsTab = createTab('Web Vitals', 'vitals');
|
642 |
|
643 |
tabs.appendChild(consoleTab);
|
644 |
tabs.appendChild(elementsTab);
|
645 |
tabs.appendChild(networkTab);
|
646 |
tabs.appendChild(storageTab);
|
647 |
+
tabs.appendChild(vitalsTab);
|
648 |
|
649 |
const closeBtn = document.createElement('button');
|
650 |
+
closeBtn.className = 'devtools-close';
|
651 |
closeBtn.textContent = '×';
|
652 |
closeBtn.onclick = toggleDevTools;
|
653 |
|
|
|
656 |
|
657 |
// コンテンツ部分
|
658 |
const content = document.createElement('div');
|
659 |
+
content.className = 'devtools-content';
|
660 |
|
661 |
const consolePanel = createConsolePanel();
|
662 |
const elementsPanel = createElementsPanel();
|
663 |
const networkPanel = createNetworkPanel();
|
664 |
const storagePanel = createStoragePanel();
|
665 |
+
const vitalsPanel = createVitalsPanel();
|
666 |
|
667 |
content.appendChild(consolePanel);
|
668 |
content.appendChild(elementsPanel);
|
669 |
content.appendChild(networkPanel);
|
670 |
content.appendChild(storagePanel);
|
671 |
+
content.appendChild(vitalsPanel);
|
672 |
|
673 |
container.appendChild(header);
|
674 |
container.appendChild(content);
|
|
|
681 |
// タブ切り替え機能
|
682 |
function createTab(name, panelId) {
|
683 |
const tab = document.createElement('div');
|
684 |
+
tab.className = 'devtools-tab';
|
685 |
tab.textContent = name;
|
686 |
tab.onclick = () => {
|
687 |
+
document.querySelectorAll('.devtools-tab').forEach(t => t.classList.remove('active'));
|
688 |
+
document.querySelectorAll('.devtools-panel').forEach(p => p.classList.remove('active'));
|
689 |
tab.classList.add('active');
|
690 |
document.getElementById(panelId + '-panel').classList.add('active');
|
691 |
|
|
|
699 |
elementsTab.click();
|
700 |
};
|
701 |
|
702 |
+
// Web Vitalsパネル���成
|
703 |
+
function createVitalsPanel() {
|
704 |
+
const panel = document.createElement('div');
|
705 |
+
panel.className = 'devtools-panel';
|
706 |
+
panel.id = 'vitals-panel';
|
707 |
+
|
708 |
+
const container = document.createElement('div');
|
709 |
+
container.className = 'vitals-container';
|
710 |
+
panel.appendChild(container);
|
711 |
+
|
712 |
+
// CLS (Cumulative Layout Shift)
|
713 |
+
const clsCard = document.createElement('div');
|
714 |
+
clsCard.className = 'vital-card';
|
715 |
+
clsCard.innerHTML = `
|
716 |
+
<div class="vital-title">CLS (Cumulative Layout Shift) <span class="vital-description">視覚的な安定性</span></div>
|
717 |
+
<div class="vital-value" id="cls-value">-</div>
|
718 |
+
<div class="vital-thresholds">
|
719 |
+
<div class="vital-threshold">Good: < 0.1</div>
|
720 |
+
<div class="vital-threshold">Needs Improvement: < 0.25</div>
|
721 |
+
<div class="vital-threshold">Poor: ≥ 0.25</div>
|
722 |
+
</div>
|
723 |
+
`;
|
724 |
+
container.appendChild(clsCard);
|
725 |
+
|
726 |
+
// FCP (First Contentful Paint)
|
727 |
+
const fcpCard = document.createElement('div');
|
728 |
+
fcpCard.className = 'vital-card';
|
729 |
+
fcpCard.innerHTML = `
|
730 |
+
<div class="vital-title">FCP (First Contentful Paint) <span class="vital-description">最初のコンテンツ表示</span></div>
|
731 |
+
<div class="vital-value" id="fcp-value">-</div>
|
732 |
+
<div class="vital-thresholds">
|
733 |
+
<div class="vital-threshold">Good: < 1.8s</div>
|
734 |
+
<div class="vital-threshold">Needs Improvement: < 3s</div>
|
735 |
+
<div class="vital-threshold">Poor: ≥ 3s</div>
|
736 |
+
</div>
|
737 |
+
`;
|
738 |
+
container.appendChild(fcpCard);
|
739 |
+
|
740 |
+
// FID (First Input Delay)
|
741 |
+
const fidCard = document.createElement('div');
|
742 |
+
fidCard.className = 'vital-card';
|
743 |
+
fidCard.innerHTML = `
|
744 |
+
<div class="vital-title">FID (First Input Delay) <span class="vital-description">最初の入力遅延</span></div>
|
745 |
+
<div class="vital-value" id="fid-value">-</div>
|
746 |
+
<div class="vital-thresholds">
|
747 |
+
<div class="vital-threshold">Good: < 100ms</div>
|
748 |
+
<div class="vital-threshold">Needs Improvement: < 300ms</div>
|
749 |
+
<div class="vital-threshold">Poor: ≥ 300ms</div>
|
750 |
+
</div>
|
751 |
+
`;
|
752 |
+
container.appendChild(fidCard);
|
753 |
+
|
754 |
+
// LCP (Largest Contentful Paint)
|
755 |
+
const lcpCard = document.createElement('div');
|
756 |
+
lcpCard.className = 'vital-card';
|
757 |
+
lcpCard.innerHTML = `
|
758 |
+
<div class="vital-title">LCP (Largest Contentful Paint) <span class="vital-description">最大のコンテンツ表示</span></div>
|
759 |
+
<div class="vital-value" id="lcp-value">-</div>
|
760 |
+
<div class="vital-thresholds">
|
761 |
+
<div class="vital-threshold">Good: < 2.5s</div>
|
762 |
+
<div class="vital-threshold">Needs Improvement: < 4s</div>
|
763 |
+
<div class="vital-threshold">Poor: ≥ 4s</div>
|
764 |
+
</div>
|
765 |
+
`;
|
766 |
+
container.appendChild(lcpCard);
|
767 |
+
|
768 |
+
// TTFB (Time to First Byte)
|
769 |
+
const ttfbCard = document.createElement('div');
|
770 |
+
ttfbCard.className = 'vital-card';
|
771 |
+
ttfbCard.innerHTML = `
|
772 |
+
<div class="vital-title">TTFB (Time to First Byte) <span class="vital-description">最初のバイト到達時間</span></div>
|
773 |
+
<div class="vital-value" id="ttfb-value">-</div>
|
774 |
+
<div class="vital-thresholds">
|
775 |
+
<div class="vital-threshold">Good: < 800ms</div>
|
776 |
+
<div class="vital-threshold">Needs Improvement: < 1.8s</div>
|
777 |
+
<div class="vital-threshold">Poor: ≥ 1.8s</div>
|
778 |
+
</div>
|
779 |
+
`;
|
780 |
+
container.appendChild(ttfbCard);
|
781 |
+
|
782 |
+
// Web Vitalsの計測を開始
|
783 |
+
loadWebVitals().then(() => {
|
784 |
+
if (window.webVitals) {
|
785 |
+
try {
|
786 |
+
const vitals = window.webVitals;
|
787 |
+
if (vitals.getCLS) {
|
788 |
+
vitals.getCLS(updateCLS);
|
789 |
+
vitals.getFCP(updateFCP);
|
790 |
+
vitals.getFID(updateFID);
|
791 |
+
vitals.getLCP(updateLCP);
|
792 |
+
vitals.getTTFB(updateTTFB);
|
793 |
+
} else if (vitals.onCLS) {
|
794 |
+
vitals.onCLS(updateCLS);
|
795 |
+
vitals.onFCP(updateFCP);
|
796 |
+
vitals.onFID(updateFID);
|
797 |
+
vitals.onLCP(updateLCP);
|
798 |
+
vitals.onTTFB(updateTTFB);
|
799 |
+
}
|
800 |
+
} catch (e) {
|
801 |
+
console.error('Web Vitals error:', e);
|
802 |
+
}
|
803 |
+
}
|
804 |
+
});
|
805 |
+
|
806 |
+
function updateCLS(metric) {
|
807 |
+
vitalsData.CLS = metric.value;
|
808 |
+
updateVitalDisplay('cls', metric.value, 0.1, 0.25);
|
809 |
+
}
|
810 |
+
|
811 |
+
function updateFCP(metric) {
|
812 |
+
vitalsData.FCP = metric.value;
|
813 |
+
updateVitalDisplay('fcp', metric.value, 1800, 3000);
|
814 |
+
}
|
815 |
+
|
816 |
+
function updateFID(metric) {
|
817 |
+
vitalsData.FID = metric.value;
|
818 |
+
updateVitalDisplay('fid', metric.value, 100, 300);
|
819 |
+
}
|
820 |
+
|
821 |
+
function updateLCP(metric) {
|
822 |
+
vitalsData.LCP = metric.value;
|
823 |
+
updateVitalDisplay('lcp', metric.value, 2500, 4000);
|
824 |
+
}
|
825 |
+
|
826 |
+
function updateTTFB(metric) {
|
827 |
+
vitalsData.TTFB = metric.value;
|
828 |
+
updateVitalDisplay('ttfb', metric.value, 800, 1800);
|
829 |
+
}
|
830 |
+
|
831 |
+
function updateVitalDisplay(id, value, goodThreshold, needsImprovementThreshold) {
|
832 |
+
const element = document.getElementById(`${id}-value`);
|
833 |
+
if (!element) return;
|
834 |
+
|
835 |
+
const displayValue = id === 'ttfb' ?
|
836 |
+
`${Math.round(value)}ms` :
|
837 |
+
`${value.toFixed(2)}${id === 'cls' ? '' : 's'}`;
|
838 |
+
|
839 |
+
element.textContent = displayValue;
|
840 |
+
|
841 |
+
element.className = 'vital-value';
|
842 |
+
if (value <= goodThreshold) {
|
843 |
+
element.classList.add('vital-good');
|
844 |
+
} else if (value <= needsImprovementThreshold) {
|
845 |
+
element.classList.add('vital-needs-improvement');
|
846 |
+
} else {
|
847 |
+
element.classList.add('vital-poor');
|
848 |
+
}
|
849 |
+
|
850 |
+
const thresholds = element.parentElement.querySelectorAll('.vital-threshold');
|
851 |
+
thresholds.forEach((threshold, index) => {
|
852 |
+
threshold.classList.remove('active');
|
853 |
+
if (
|
854 |
+
(index === 0 && value <= goodThreshold) ||
|
855 |
+
(index === 1 && value > goodThreshold && value <= needsImprovementThreshold) ||
|
856 |
+
(index === 2 && value > needsImprovementThreshold)
|
857 |
+
) {
|
858 |
+
threshold.classList.add('active');
|
859 |
+
}
|
860 |
+
});
|
861 |
+
}
|
862 |
+
|
863 |
+
return panel;
|
864 |
+
}
|
865 |
+
|
866 |
// ネットワークパネル作成
|
867 |
function createNetworkPanel() {
|
868 |
const panel = document.createElement('div');
|
869 |
+
panel.className = 'devtools-panel';
|
870 |
panel.id = 'network-panel';
|
871 |
|
872 |
const container = document.createElement('div');
|
873 |
+
container.className = 'network-container';
|
874 |
panel.appendChild(container);
|
875 |
|
876 |
// リクエストリスト
|
877 |
const requestsList = document.createElement('div');
|
878 |
+
requestsList.className = 'network-requests';
|
879 |
container.appendChild(requestsList);
|
880 |
|
881 |
// 詳細パネル
|
882 |
const detailsPanel = document.createElement('div');
|
883 |
+
detailsPanel.className = 'network-details';
|
884 |
container.appendChild(detailsPanel);
|
885 |
|
886 |
// ネットワークリクエストを監視
|
|
|
912 |
error: null
|
913 |
};
|
914 |
|
915 |
+
networkRequests.push(requestData);
|
916 |
renderNetworkRequests();
|
917 |
|
918 |
try {
|
|
|
989 |
error: null
|
990 |
};
|
991 |
|
992 |
+
networkRequests.push(requestData);
|
993 |
renderNetworkRequests();
|
994 |
|
995 |
this.addEventListener('load', function() {
|
|
|
1041 |
const panel = document.getElementById('network-panel');
|
1042 |
if (!panel || !panel.classList.contains('active')) return;
|
1043 |
|
1044 |
+
const requestsList = panel.querySelector('.network-requests');
|
1045 |
+
const detailsPanel = panel.querySelector('.network-details');
|
1046 |
|
1047 |
requestsList.innerHTML = '';
|
1048 |
|
1049 |
+
networkRequests.forEach(request => {
|
1050 |
const requestElement = document.createElement('div');
|
1051 |
+
requestElement.className = 'network-request';
|
1052 |
+
if (selectedRequest && selectedRequest.id === request.id) {
|
1053 |
requestElement.classList.add('selected');
|
1054 |
}
|
1055 |
|
1056 |
requestElement.onclick = () => {
|
1057 |
+
selectedRequest = request;
|
1058 |
renderNetworkRequests();
|
1059 |
renderNetworkDetails();
|
1060 |
};
|
1061 |
|
1062 |
const statusElement = document.createElement('div');
|
1063 |
+
statusElement.className = `network-status ${request.status}`;
|
1064 |
statusElement.textContent = request.status === 'success' ? '✓' : '✕';
|
1065 |
|
1066 |
const methodElement = document.createElement('div');
|
1067 |
+
methodElement.className = 'network-method';
|
1068 |
methodElement.textContent = request.method;
|
1069 |
|
1070 |
const urlElement = document.createElement('div');
|
1071 |
+
urlElement.className = 'network-url';
|
1072 |
urlElement.textContent = request.url;
|
1073 |
|
1074 |
const timeElement = document.createElement('div');
|
1075 |
+
timeElement.className = 'network-time';
|
1076 |
timeElement.textContent = request.duration ? `${Math.round(request.duration)}ms` : '';
|
1077 |
|
1078 |
requestElement.appendChild(statusElement);
|
|
|
1087 |
// ネットワークリクエストの詳細を表示
|
1088 |
function renderNetworkDetails() {
|
1089 |
const panel = document.getElementById('network-panel');
|
1090 |
+
if (!panel || !selectedRequest) return;
|
1091 |
|
1092 |
+
const detailsPanel = panel.querySelector('.network-details');
|
1093 |
detailsPanel.innerHTML = '';
|
1094 |
|
1095 |
// 一般情報
|
1096 |
const generalSection = document.createElement('div');
|
1097 |
+
generalSection.className = 'network-detail-section';
|
1098 |
generalSection.innerHTML = `
|
1099 |
+
<div class="network-detail-title">一般</div>
|
1100 |
+
<div class="network-detail-content">
|
1101 |
+
<strong>URL:</strong> ${selectedRequest.url}<br>
|
1102 |
+
<strong>メソッド:</strong> ${selectedRequest.method}<br>
|
1103 |
+
<strong>ステータス:</strong> ${selectedRequest.response ? selectedRequest.response.status : '-'}<br>
|
1104 |
+
<strong>時間:</strong> ${selectedRequest.duration ? Math.round(selectedRequest.duration) + 'ms' : '-'}
|
1105 |
</div>
|
1106 |
`;
|
1107 |
detailsPanel.appendChild(generalSection);
|
1108 |
|
1109 |
// リクエストヘッダー
|
1110 |
+
if (selectedRequest.requestHeaders) {
|
1111 |
const headersSection = document.createElement('div');
|
1112 |
+
headersSection.className = 'network-detail-section';
|
1113 |
|
1114 |
const headersTitle = document.createElement('div');
|
1115 |
+
headersTitle.className = 'network-detail-title';
|
1116 |
headersTitle.textContent = 'リクエストヘッダー';
|
1117 |
|
1118 |
const headersContent = document.createElement('div');
|
1119 |
+
headersContent.className = 'network-detail-content';
|
1120 |
|
1121 |
+
if (typeof selectedRequest.requestHeaders === 'object' && !(selectedRequest.requestHeaders instanceof Headers)) {
|
1122 |
+
Object.entries(selectedRequest.requestHeaders).forEach(([key, value]) => {
|
1123 |
headersContent.innerHTML += `<strong>${key}:</strong> ${value}<br>`;
|
1124 |
});
|
1125 |
+
} else if (selectedRequest.requestHeaders instanceof Headers) {
|
1126 |
+
selectedRequest.requestHeaders.forEach((value, key) => {
|
1127 |
headersContent.innerHTML += `<strong>${key}:</strong> ${value}<br>`;
|
1128 |
});
|
1129 |
}
|
|
|
1134 |
}
|
1135 |
|
1136 |
// リクエストボディ
|
1137 |
+
if (selectedRequest.requestBody) {
|
1138 |
const bodySection = document.createElement('div');
|
1139 |
+
bodySection.className = 'network-detail-section';
|
1140 |
|
1141 |
const bodyTitle = document.createElement('div');
|
1142 |
+
bodyTitle.className = 'network-detail-title';
|
1143 |
bodyTitle.textContent = 'リクエストボディ';
|
1144 |
|
1145 |
const bodyContent = document.createElement('div');
|
1146 |
+
bodyContent.className = 'network-detail-content';
|
1147 |
|
1148 |
try {
|
1149 |
+
if (typeof selectedRequest.requestBody === 'string') {
|
1150 |
+
bodyContent.textContent = selectedRequest.requestBody;
|
1151 |
+
} else if (typeof selectedRequest.requestBody === 'object') {
|
1152 |
+
bodyContent.textContent = JSON.stringify(selectedRequest.requestBody, null, 2);
|
1153 |
}
|
1154 |
} catch (e) {
|
1155 |
bodyContent.textContent = 'ボディを表示できません';
|
|
|
1161 |
}
|
1162 |
|
1163 |
// レスポンス
|
1164 |
+
if (selectedRequest.response) {
|
1165 |
const responseSection = document.createElement('div');
|
1166 |
+
responseSection.className = 'network-detail-section';
|
1167 |
|
1168 |
const responseTitle = document.createElement('div');
|
1169 |
+
responseTitle.className = 'network-detail-title';
|
1170 |
responseTitle.textContent = 'レスポンス';
|
1171 |
|
1172 |
const responseContent = document.createElement('div');
|
1173 |
+
responseContent.className = 'network-detail-content';
|
1174 |
|
1175 |
+
if (selectedRequest.response.body) {
|
1176 |
+
if (typeof selectedRequest.response.body === 'object') {
|
1177 |
+
responseContent.textContent = JSON.stringify(selectedRequest.response.body, null, 2);
|
1178 |
} else {
|
1179 |
+
responseContent.textContent = selectedRequest.response.body;
|
1180 |
}
|
1181 |
} else {
|
1182 |
responseContent.textContent = 'レスポンスボディがありません';
|
|
|
1188 |
}
|
1189 |
|
1190 |
// エラー
|
1191 |
+
if (selectedRequest.error) {
|
1192 |
const errorSection = document.createElement('div');
|
1193 |
+
errorSection.className = 'network-detail-section';
|
1194 |
|
1195 |
const errorTitle = document.createElement('div');
|
1196 |
+
errorTitle.className = 'network-detail-title';
|
1197 |
errorTitle.textContent = 'エラー';
|
1198 |
|
1199 |
const errorContent = document.createElement('div');
|
1200 |
+
errorContent.className = 'network-detail-content';
|
1201 |
+
errorContent.textContent = `${selectedRequest.error.name}: ${selectedRequest.error.message}`;
|
1202 |
|
1203 |
+
if (selectedRequest.error.stack) {
|
1204 |
+
errorContent.innerHTML += `<br><br>${selectedRequest.error.stack.replace(/\n/g, '<br>')}`;
|
1205 |
}
|
1206 |
|
1207 |
errorSection.appendChild(errorTitle);
|
|
|
1212 |
|
1213 |
// コンテキストメニュー作成
|
1214 |
function createContextMenu() {
|
1215 |
+
contextMenu = document.createElement('div');
|
1216 |
+
contextMenu.className = 'context-menu';
|
1217 |
+
contextMenu.innerHTML = `
|
1218 |
+
<div class="context-menu-item" data-action="edit-html">HTMLとして編集</div>
|
1219 |
+
<div class="context-menu-item" data-action="add-attribute">属性を追加</div>
|
1220 |
+
<div class="context-menu-item" data-action="edit-element">要素を編集</div>
|
1221 |
+
<div class="context-menu-item" data-action="duplicate">要素を複製</div>
|
1222 |
+
<div class="context-menu-item" data-action="remove">要素を削除</div>
|
1223 |
+
<div class="context-menu-item" data-action="toggle-visibility">要素を非表示</div>
|
1224 |
+
<div class="context-menu-item" data-action="force-state">状態を強制</div>
|
|
|
1225 |
`;
|
1226 |
+
document.body.appendChild(contextMenu);
|
1227 |
|
1228 |
+
contextMenu.querySelectorAll('.context-menu-item').forEach(item => {
|
1229 |
item.addEventListener('click', (e) => {
|
1230 |
const action = e.target.getAttribute('data-action');
|
1231 |
handleContextMenuAction(action);
|
1232 |
+
contextMenu.style.display = 'none';
|
1233 |
});
|
1234 |
});
|
1235 |
|
1236 |
document.addEventListener('click', (e) => {
|
1237 |
+
if (e.target !== contextMenu && !contextMenu.contains(e.target)) {
|
1238 |
+
contextMenu.style.display = 'none';
|
1239 |
}
|
1240 |
});
|
1241 |
}
|
|
|
1243 |
// Storageパネル作成
|
1244 |
function createStoragePanel() {
|
1245 |
const panel = document.createElement('div');
|
1246 |
+
panel.className = 'devtools-panel';
|
1247 |
panel.id = 'storage-panel';
|
1248 |
|
1249 |
// LocalStorage表示
|
|
|
1252 |
panel.appendChild(localStorageTitle);
|
1253 |
|
1254 |
const localStorageTable = document.createElement('table');
|
1255 |
+
localStorageTable.className = 'storage-table';
|
1256 |
panel.appendChild(localStorageTable);
|
1257 |
|
1258 |
const addLocalStorageBtn = document.createElement('button');
|
1259 |
+
addLocalStorageBtn.className = 'add-btn';
|
1260 |
addLocalStorageBtn.textContent = '+ Local Storageに追加';
|
1261 |
addLocalStorageBtn.onclick = () => {
|
1262 |
const key = prompt('キー名を入力');
|
|
|
1275 |
panel.appendChild(sessionStorageTitle);
|
1276 |
|
1277 |
const sessionStorageTable = document.createElement('table');
|
1278 |
+
sessionStorageTable.className = 'storage-table';
|
1279 |
panel.appendChild(sessionStorageTable);
|
1280 |
|
1281 |
const addSessionStorageBtn = document.createElement('button');
|
1282 |
+
addSessionStorageBtn.className = 'add-btn';
|
1283 |
addSessionStorageBtn.textContent = '+ Session Storageに追加';
|
1284 |
addSessionStorageBtn.onclick = () => {
|
1285 |
const key = prompt('キー名を入力');
|
|
|
1298 |
panel.appendChild(cookiesTitle);
|
1299 |
|
1300 |
const cookiesTable = document.createElement('table');
|
1301 |
+
cookiesTable.className = 'storage-table';
|
1302 |
panel.appendChild(cookiesTable);
|
1303 |
|
1304 |
const addCookieBtn = document.createElement('button');
|
1305 |
+
addCookieBtn.className = 'add-btn';
|
1306 |
addCookieBtn.textContent = '+ Cookieに追加';
|
1307 |
addCookieBtn.onclick = () => {
|
1308 |
const name = prompt('Cookie名を入力');
|
|
|
1343 |
|
1344 |
const keyCell = document.createElement('td');
|
1345 |
const keySpan = document.createElement('span');
|
1346 |
+
keySpan.className = 'editable';
|
1347 |
keySpan.textContent = key;
|
1348 |
keySpan.onclick = () => {
|
1349 |
const newKey = prompt('新しいキー名を入力', key);
|
|
|
1357 |
|
1358 |
const valueCell = document.createElement('td');
|
1359 |
const valueSpan = document.createElement('span');
|
1360 |
+
valueSpan.className = 'editable';
|
1361 |
valueSpan.textContent = value;
|
1362 |
valueSpan.onclick = () => {
|
1363 |
const newValue = prompt('新しい値を入力', value);
|
|
|
1369 |
valueCell.appendChild(valueSpan);
|
1370 |
|
1371 |
const actionsCell = document.createElement('td');
|
1372 |
+
actionsCell.className = 'storage-actions';
|
1373 |
|
1374 |
const deleteBtn = document.createElement('button');
|
1375 |
+
deleteBtn.className = 'storage-btn';
|
1376 |
deleteBtn.textContent = 'Delete';
|
1377 |
deleteBtn.onclick = () => {
|
1378 |
storage.removeItem(key);
|
|
|
1414 |
|
1415 |
const nameCell = document.createElement('td');
|
1416 |
const nameSpan = document.createElement('span');
|
1417 |
+
nameSpan.className = 'editable';
|
1418 |
nameSpan.textContent = decodedName;
|
1419 |
nameSpan.onclick = () => {
|
1420 |
const newName = prompt('新しい名前を入力', decodedName);
|
|
|
1431 |
|
1432 |
const valueCell = document.createElement('td');
|
1433 |
const valueSpan = document.createElement('span');
|
1434 |
+
valueSpan.className = 'editable';
|
1435 |
valueSpan.textContent = decodeURIComponent(value);
|
1436 |
valueSpan.onclick = () => {
|
1437 |
const newValue = prompt('新しい値を入力', decodeURIComponent(value));
|
|
|
1443 |
valueCell.appendChild(valueSpan);
|
1444 |
|
1445 |
const actionsCell = document.createElement('td');
|
1446 |
+
actionsCell.className = 'storage-actions';
|
1447 |
|
1448 |
const deleteBtn = document.createElement('button');
|
1449 |
+
deleteBtn.className = 'storage-btn';
|
1450 |
deleteBtn.textContent = 'Delete';
|
1451 |
deleteBtn.onclick = () => {
|
1452 |
document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
|
|
1471 |
|
1472 |
// コンテキストメニューアクション処理
|
1473 |
function handleContextMenuAction(action) {
|
1474 |
+
if (!selectedElement) return;
|
1475 |
|
1476 |
switch (action) {
|
1477 |
case 'edit-html':
|
1478 |
+
if (selectedElement === document.documentElement) {
|
1479 |
alert('ルートHTML要素は直接編集できません');
|
1480 |
return;
|
1481 |
}
|
1482 |
+
startInlineEdit(selectedDOMNode, selectedElement.outerHTML, (newValue) => {
|
1483 |
try {
|
1484 |
+
selectedElement.outerHTML = newValue;
|
1485 |
+
refreshElementsPanel();
|
1486 |
} catch (e) {
|
1487 |
alert('この要素は編集できません: ' + e.message);
|
1488 |
}
|
1489 |
});
|
1490 |
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1491 |
case 'add-attribute':
|
1492 |
const attrName = prompt('属性名を入力');
|
1493 |
if (attrName) {
|
1494 |
const attrValue = prompt('属性値を入力');
|
1495 |
+
selectedElement.setAttribute(attrName, attrValue || '');
|
1496 |
+
refreshElementsPanel();
|
1497 |
}
|
1498 |
break;
|
1499 |
case 'edit-element':
|
1500 |
+
startInlineEdit(selectedDOMNode.querySelector('.dom-tag'), selectedElement.tagName.toLowerCase(), (newValue) => {
|
1501 |
const newElement = document.createElement(newValue);
|
1502 |
+
Array.from(selectedElement.attributes).forEach(attr => {
|
1503 |
newElement.setAttribute(attr.name, attr.value);
|
1504 |
});
|
1505 |
+
newElement.innerHTML = selectedElement.innerHTML;
|
1506 |
+
selectedElement.parentNode.replaceChild(newElement, selectedElement);
|
1507 |
+
selectedElement = newElement;
|
1508 |
+
refreshElementsPanel();
|
1509 |
});
|
1510 |
break;
|
1511 |
case 'duplicate':
|
1512 |
+
const clone = selectedElement.cloneNode(true);
|
1513 |
+
selectedElement.parentNode.insertBefore(clone, selectedElement.nextSibling);
|
1514 |
+
refreshElementsPanel();
|
1515 |
break;
|
1516 |
case 'remove':
|
1517 |
if (confirm('要素を削除しますか?')) {
|
1518 |
+
selectedElement.parentNode.removeChild(selectedElement);
|
1519 |
+
refreshElementsPanel();
|
1520 |
}
|
1521 |
break;
|
1522 |
case 'toggle-visibility':
|
1523 |
+
if (selectedElement.style.display === 'none') {
|
1524 |
+
selectedElement.style.display = '';
|
1525 |
} else {
|
1526 |
+
selectedElement.style.display = 'none';
|
1527 |
}
|
1528 |
+
refreshElementsPanel();
|
1529 |
break;
|
1530 |
case 'force-state':
|
1531 |
const state = prompt('強制する状態を入力 (例: hover, active, focus)', 'hover');
|
1532 |
if (state) {
|
1533 |
+
selectedElement.classList.remove('force-hover', 'force-active', 'force-focus',
|
1534 |
'force-focus-within', 'force-focus-visible', 'force-target');
|
1535 |
+
selectedElement.classList.add(`force-${state}`);
|
1536 |
+
refreshElementsPanel();
|
1537 |
}
|
1538 |
break;
|
1539 |
}
|
|
|
1541 |
|
1542 |
// インライン編集関数
|
1543 |
function startInlineEdit(element, initialValue, callback) {
|
1544 |
+
if (activeEditElement) return;
|
1545 |
|
1546 |
const originalValue = element.textContent;
|
1547 |
const rect = element.getBoundingClientRect();
|
1548 |
|
1549 |
const input = document.createElement('input');
|
1550 |
+
input.className = 'dom-edit-input';
|
1551 |
input.value = initialValue || originalValue;
|
1552 |
input.style.position = 'absolute';
|
1553 |
input.style.left = `${rect.left}px`;
|
|
|
1558 |
input.focus();
|
1559 |
input.select();
|
1560 |
|
1561 |
+
activeEditElement = {
|
1562 |
element: element,
|
1563 |
input: input,
|
1564 |
callback: callback
|
|
|
1601 |
function cleanup() {
|
1602 |
document.removeEventListener('click', clickOutsideHandler);
|
1603 |
input.remove();
|
1604 |
+
activeEditElement = null;
|
1605 |
}
|
1606 |
}
|
1607 |
|
1608 |
// Consoleパネル作成
|
1609 |
function createConsolePanel() {
|
1610 |
const panel = document.createElement('div');
|
1611 |
+
panel.className = 'devtools-panel';
|
1612 |
panel.id = 'console-panel';
|
1613 |
|
1614 |
const log = document.createElement('div');
|
1615 |
+
log.id = 'console-log';
|
1616 |
|
1617 |
const input = document.createElement('input');
|
1618 |
+
input.className = 'console-input';
|
1619 |
input.placeholder = 'ここにJavaScriptを入力... (Enterで実行)';
|
1620 |
input.onkeypress = (e) => {
|
1621 |
if (e.key === 'Enter') {
|
1622 |
try {
|
1623 |
const result = eval(e.target.value);
|
1624 |
if (result !== undefined) {
|
1625 |
+
logMessage('> ' + e.target.value, 'console-log');
|
1626 |
+
logMessage('← ' + formatOutput(result), 'console-log');
|
1627 |
}
|
1628 |
} catch (err) {
|
1629 |
+
logMessage(err.message, 'console-error');
|
1630 |
}
|
1631 |
e.target.value = '';
|
1632 |
}
|
|
|
1645 |
|
1646 |
console.log = (...args) => {
|
1647 |
originalConsole.log.apply(console, args);
|
1648 |
+
logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-log');
|
1649 |
};
|
1650 |
|
1651 |
console.error = (...args) => {
|
1652 |
originalConsole.error.apply(console, args);
|
1653 |
+
logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-error');
|
1654 |
};
|
1655 |
|
1656 |
console.warn = (...args) => {
|
1657 |
originalConsole.warn.apply(console, args);
|
1658 |
+
logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-warn');
|
1659 |
};
|
1660 |
|
1661 |
console.info = (...args) => {
|
1662 |
originalConsole.info.apply(console, args);
|
1663 |
+
logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-info');
|
1664 |
};
|
1665 |
|
1666 |
function logMessage(message, className) {
|
|
|
1672 |
}
|
1673 |
|
1674 |
function formatOutput(output) {
|
1675 |
+
if (output === null) return '<span class="json-null">null</span>';
|
1676 |
+
if (output === undefined) return '<span class="json-null">undefined</span>';
|
1677 |
+
if (typeof output === 'boolean') return `<span class="json-boolean">${output}</span>`;
|
1678 |
+
if (typeof output === 'number') return `<span class="json-number">${output}</span>`;
|
1679 |
+
if (typeof output === 'string') return `<span class="json-string">"${output}"</span>`;
|
1680 |
+
if (typeof output === 'function') return `<span class="json-object">function ${output.name}() { ... }</span>`;
|
1681 |
+
if (Array.isArray(output)) return `<span class="json-object">[${output.map(formatOutput).join(', ')}]</span>`;
|
1682 |
if (typeof output === 'object') {
|
1683 |
try {
|
1684 |
+
return `<span class="json-object">${JSON.stringify(output, null, 2)
|
1685 |
+
.replace(/"([^"]+)":/g, '<span class="json-key">"$1"</span>:')
|
1686 |
+
.replace(/"([^"]+)"/g, '<span class="json-string">"$1"</span>')
|
1687 |
+
.replace(/\b(true|false)\b/g, '<span class="json-boolean">$1</span>')
|
1688 |
+
.replace(/\b(null)\b/g, '<span class="json-null">$1</span>')
|
1689 |
+
.replace(/\b(\d+)\b/g, '<span class="json-number">$1</span>')}</span>`;
|
1690 |
} catch (e) {
|
1691 |
+
return `<span class="json-object">${output.toString()}</span>`;
|
1692 |
}
|
1693 |
}
|
1694 |
return output;
|
|
|
1700 |
// Elementsパネル作成
|
1701 |
function createElementsPanel() {
|
1702 |
const panel = document.createElement('div');
|
1703 |
+
panel.className = 'devtools-panel';
|
1704 |
panel.id = 'elements-panel';
|
1705 |
|
1706 |
const container = document.createElement('div');
|
1707 |
+
container.className = 'elements-container';
|
1708 |
|
1709 |
const tree = document.createElement('div');
|
1710 |
+
tree.className = 'dom-tree';
|
1711 |
+
tree.id = 'dom-tree';
|
1712 |
|
1713 |
const cssPanel = document.createElement('div');
|
1714 |
+
cssPanel.className = 'css-panel';
|
1715 |
+
cssPanel.id = 'css-panel';
|
1716 |
|
1717 |
container.appendChild(tree);
|
1718 |
container.appendChild(cssPanel);
|
|
|
1720 |
|
1721 |
// CSSパネル更新関数
|
1722 |
function updateCSSPanel(element) {
|
1723 |
+
const cssPanel = document.getElementById('css-panel');
|
1724 |
cssPanel.innerHTML = '';
|
1725 |
|
1726 |
if (!element) return;
|
1727 |
|
1728 |
if (element.style.length > 0) {
|
1729 |
const inlineRule = document.createElement('div');
|
1730 |
+
inlineRule.className = 'css-rule';
|
1731 |
|
1732 |
const selector = document.createElement('div');
|
1733 |
+
selector.className = 'css-selector';
|
1734 |
selector.textContent = 'インラインスタイル';
|
1735 |
inlineRule.appendChild(selector);
|
1736 |
|
|
|
1739 |
const propValue = element.style[propName];
|
1740 |
|
1741 |
const propDiv = document.createElement('div');
|
1742 |
+
propDiv.className = 'css-property';
|
1743 |
|
1744 |
const nameSpan = document.createElement('span');
|
1745 |
+
nameSpan.className = 'css-property-name editable';
|
1746 |
nameSpan.textContent = propName;
|
1747 |
nameSpan.onclick = () => editCSSProperty(element, propName, 'style');
|
1748 |
|
1749 |
const valueSpan = document.createElement('span');
|
1750 |
+
valueSpan.className = 'css-property-value editable';
|
1751 |
valueSpan.textContent = propValue;
|
1752 |
valueSpan.onclick = () => editCSSProperty(element, propName, 'style');
|
1753 |
|
1754 |
const toggleSpan = document.createElement('span');
|
1755 |
+
toggleSpan.className = 'css-toggle';
|
1756 |
toggleSpan.textContent = '×';
|
1757 |
toggleSpan.title = 'プロパティを無効化';
|
1758 |
toggleSpan.onclick = () => {
|
|
|
1771 |
|
1772 |
const computedStyles = window.getComputedStyle(element);
|
1773 |
const computedRule = document.createElement('div');
|
1774 |
+
computedRule.className = 'css-rule';
|
1775 |
|
1776 |
const computedSelector = document.createElement('div');
|
1777 |
+
computedSelector.className = 'css-selector';
|
1778 |
computedSelector.textContent = '計算されたスタイル';
|
1779 |
computedRule.appendChild(computedSelector);
|
1780 |
|
|
|
1787 |
const value = computedStyles[prop];
|
1788 |
|
1789 |
const propDiv = document.createElement('div');
|
1790 |
+
propDiv.className = 'css-property';
|
1791 |
|
1792 |
const nameSpan = document.createElement('span');
|
1793 |
+
nameSpan.className = 'css-property-name';
|
1794 |
nameSpan.textContent = prop;
|
1795 |
|
1796 |
const valueSpan = document.createElement('span');
|
1797 |
+
valueSpan.className = 'css-property-value';
|
1798 |
valueSpan.textContent = value;
|
1799 |
|
1800 |
propDiv.appendChild(nameSpan);
|
|
|
1809 |
function buildDOMTree(node, parentElement, depth = 0, isRoot = false) {
|
1810 |
if (node.nodeType === Node.ELEMENT_NODE) {
|
1811 |
const element = document.createElement('div');
|
1812 |
+
element.className = 'dom-node';
|
1813 |
element.style.marginLeft = `${depth * 15}px`;
|
1814 |
element.dataset.elementId = node.id || Math.random().toString(36).substr(2, 9);
|
1815 |
|
1816 |
// 右クリックイベント
|
1817 |
element.oncontextmenu = (e) => {
|
1818 |
e.preventDefault();
|
1819 |
+
selectedElement = node;
|
1820 |
+
selectedDOMNode = element;
|
1821 |
|
1822 |
+
document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
|
1823 |
element.classList.add('selected');
|
1824 |
|
1825 |
if (node !== document.documentElement) {
|
1826 |
+
contextMenu.style.display = 'block';
|
1827 |
+
contextMenu.style.left = `${e.pageX}px`;
|
1828 |
+
contextMenu.style.top = `${e.pageY}px`;
|
1829 |
}
|
1830 |
|
1831 |
updateCSSPanel(node);
|
|
|
1833 |
|
1834 |
// 左クリックで選択
|
1835 |
element.onclick = (e) => {
|
1836 |
+
if (e.target.classList.contains('dom-toggle')) return;
|
1837 |
|
1838 |
e.stopPropagation();
|
1839 |
+
selectedElement = node;
|
1840 |
+
selectedDOMNode = element;
|
1841 |
|
1842 |
+
document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
|
1843 |
element.classList.add('selected');
|
1844 |
|
1845 |
updateCSSPanel(node);
|
|
|
1851 |
|
1852 |
if (hasChildren) {
|
1853 |
const toggle = document.createElement('div');
|
1854 |
+
toggle.className = 'dom-toggle';
|
1855 |
toggle.onclick = (e) => {
|
1856 |
e.stopPropagation();
|
1857 |
+
const children = element.querySelector('.dom-children');
|
1858 |
if (children) {
|
1859 |
if (children.style.maxHeight === '0px') {
|
1860 |
children.style.maxHeight = children.scrollHeight + 'px';
|
|
|
1870 |
|
1871 |
// タグ名(HTML要素は編集不可)
|
1872 |
const tag = document.createElement('span');
|
1873 |
+
tag.className = 'dom-tag';
|
1874 |
tag.textContent = `<${node.tagName.toLowerCase()}`;
|
1875 |
|
1876 |
if (node !== document.documentElement) {
|
1877 |
+
tag.classList.add('editable');
|
1878 |
tag.onclick = (e) => {
|
1879 |
e.stopPropagation();
|
1880 |
startInlineEdit(tag, node.tagName.toLowerCase(), (newValue) => {
|
|
|
1884 |
});
|
1885 |
newElement.innerHTML = node.innerHTML;
|
1886 |
node.parentNode.replaceChild(newElement, node);
|
1887 |
+
selectedElement = newElement;
|
1888 |
+
refreshElementsPanel();
|
1889 |
});
|
1890 |
};
|
1891 |
}
|
|
|
1895 |
// 属性(HTML要素は編集不可)
|
1896 |
Array.from(node.attributes).forEach(attr => {
|
1897 |
const attrSpan = document.createElement('span');
|
1898 |
+
attrSpan.className = 'dom-attr';
|
1899 |
attrSpan.textContent = ` ${attr.name}="${attr.value}"`;
|
1900 |
|
1901 |
if (node !== document.documentElement) {
|
1902 |
+
attrSpan.classList.add('editable');
|
1903 |
attrSpan.onclick = (e) => {
|
1904 |
e.stopPropagation();
|
1905 |
startInlineEdit(attrSpan, attr.value, (newValue) => {
|
1906 |
node.setAttribute(attr.name, newValue);
|
1907 |
+
refreshElementsPanel();
|
1908 |
});
|
1909 |
};
|
1910 |
}
|
|
|
1916 |
|
1917 |
if (hasChildren) {
|
1918 |
const childrenContainer = document.createElement('div');
|
1919 |
+
childrenContainer.className = 'dom-children';
|
1920 |
childrenContainer.style.maxHeight = isRoot ? 'none' : '0px';
|
1921 |
|
1922 |
node.childNodes.forEach(child => {
|
|
|
1926 |
if (node.tagName.toLowerCase() !== 'br') {
|
1927 |
const closeTag = document.createElement('div');
|
1928 |
closeTag.style.marginLeft = `${depth * 15}px`;
|
1929 |
+
closeTag.innerHTML = `<span class="dom-tag"></${node.tagName.toLowerCase()}></span>`;
|
1930 |
childrenContainer.appendChild(closeTag);
|
1931 |
}
|
1932 |
|
|
|
1937 |
} else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
|
1938 |
const text = document.createElement('div');
|
1939 |
text.style.marginLeft = `${depth * 15}px`;
|
1940 |
+
text.className = 'dom-text editable';
|
1941 |
text.textContent = `"${node.textContent.trim()}"`;
|
1942 |
text.onclick = (e) => {
|
1943 |
e.stopPropagation();
|
1944 |
startInlineEdit(text, node.textContent.trim(), (newValue) => {
|
1945 |
node.textContent = newValue;
|
1946 |
+
refreshElementsPanel();
|
1947 |
});
|
1948 |
};
|
1949 |
parentElement.appendChild(text);
|
|
|
1966 |
}
|
1967 |
|
1968 |
updateCSSPanel(element);
|
1969 |
+
refreshElementsPanel();
|
1970 |
}
|
1971 |
}
|
1972 |
|
1973 |
// refreshElementsPanel関数の定義
|
1974 |
+
refreshElementsPanel = function() {
|
1975 |
+
let tree = document.getElementById('dom-tree');
|
1976 |
|
1977 |
if (!tree) {
|
1978 |
const panel = document.getElementById('elements-panel');
|
1979 |
if (panel) {
|
1980 |
+
const container = panel.querySelector('.elements-container');
|
1981 |
if (container) {
|
1982 |
tree = document.createElement('div');
|
1983 |
+
tree.className = 'dom-tree';
|
1984 |
+
tree.id = 'dom-tree';
|
1985 |
+
container.insertBefore(tree, container.querySelector('.css-panel'));
|
1986 |
}
|
1987 |
}
|
1988 |
}
|
|
|
1992 |
tree.innerHTML = '';
|
1993 |
buildDOMTree(document.documentElement, tree, 0, true);
|
1994 |
|
1995 |
+
if (selectedElement) {
|
1996 |
+
const elementId = selectedElement.id || Array.from(selectedElement.attributes)
|
1997 |
.find(attr => attr.name.startsWith('data-element-id'))?.value;
|
1998 |
|
1999 |
if (elementId) {
|
2000 |
const node = document.querySelector(`[data-element-id="${elementId}"]`);
|
2001 |
if (node) {
|
2002 |
node.classList.add('selected');
|
2003 |
+
updateCSSPanel(selectedElement);
|
2004 |
}
|
2005 |
}
|
2006 |
}
|
|
|
2011 |
|
2012 |
// 初期表示
|
2013 |
setTimeout(() => {
|
2014 |
+
refreshElementsPanel();
|
2015 |
}, 0);
|
2016 |
|
2017 |
return panel;
|
|
|
2019 |
|
2020 |
// 開発者ツール表示/非表示
|
2021 |
function toggleDevTools() {
|
2022 |
+
const container = document.getElementById('devtools-container');
|
2023 |
if (container.style.display === 'none') {
|
2024 |
container.style.display = 'flex';
|
2025 |
} else {
|
|
|
2030 |
// 開くボタン作成
|
2031 |
function createOpenButton() {
|
2032 |
const button = document.createElement('button');
|
2033 |
+
button.id = 'open-devtools-btn';
|
2034 |
button.textContent = '開発者ツールを開く';
|
2035 |
button.style.position = 'fixed';
|
2036 |
button.style.bottom = '10px';
|
2037 |
button.style.right = '10px';
|
2038 |
button.style.padding = '8px 16px';
|
2039 |
+
button.style.background = 'var(--primary-color)';
|
2040 |
button.style.color = '#000';
|
2041 |
button.style.border = 'none';
|
2042 |
button.style.borderRadius = '4px';
|
|
|
2049 |
|
2050 |
// 初期化
|
2051 |
document.addEventListener('DOMContentLoaded', function() {
|
2052 |
+
createDevTools();
|
2053 |
createOpenButton();
|
2054 |
+
console.log('開発者ツールが初期化されました');
|
2055 |
+
console.log('このコンソールでJavaScriptを実行できます');
|
|
|
|
|
|
|
|
|
2056 |
});
|
2057 |
})();
|