Update dev-tools.js
Browse files- dev-tools.js +99 -17
dev-tools.js
CHANGED
@@ -140,6 +140,15 @@
|
|
140 |
color: #333;
|
141 |
}
|
142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
143 |
.css-panel {
|
144 |
flex: 1;
|
145 |
overflow: auto;
|
@@ -278,6 +287,7 @@
|
|
278 |
let contextMenu = null;
|
279 |
let selectedElement = null;
|
280 |
let selectedDOMNode = null;
|
|
|
281 |
|
282 |
// 開発者ツールのUI構築
|
283 |
function createDevTools() {
|
@@ -385,11 +395,10 @@
|
|
385 |
|
386 |
switch (action) {
|
387 |
case 'edit-html':
|
388 |
-
|
389 |
-
|
390 |
-
selectedElement.outerHTML = html;
|
391 |
refreshElementsPanel();
|
392 |
-
}
|
393 |
break;
|
394 |
|
395 |
case 'add-attribute':
|
@@ -402,9 +411,8 @@
|
|
402 |
break;
|
403 |
|
404 |
case 'edit-element':
|
405 |
-
|
406 |
-
|
407 |
-
const newElement = document.createElement(tagName);
|
408 |
Array.from(selectedElement.attributes).forEach(attr => {
|
409 |
newElement.setAttribute(attr.name, attr.value);
|
410 |
});
|
@@ -412,7 +420,7 @@
|
|
412 |
selectedElement.parentNode.replaceChild(newElement, selectedElement);
|
413 |
selectedElement = newElement;
|
414 |
refreshElementsPanel();
|
415 |
-
}
|
416 |
break;
|
417 |
|
418 |
case 'duplicate':
|
@@ -452,6 +460,69 @@
|
|
452 |
}
|
453 |
}
|
454 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
455 |
// Consoleパネルの作成
|
456 |
function createConsolePanel() {
|
457 |
const panel = document.createElement('div');
|
@@ -579,8 +650,21 @@
|
|
579 |
|
580 |
// タグ名
|
581 |
const tag = document.createElement('span');
|
582 |
-
tag.className = 'dom-tag';
|
583 |
tag.textContent = `<${node.tagName.toLowerCase()}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
584 |
element.appendChild(tag);
|
585 |
|
586 |
// 属性
|
@@ -590,11 +674,10 @@
|
|
590 |
attrSpan.textContent = ` ${attr.name}="${attr.value}"`;
|
591 |
attrSpan.onclick = (e) => {
|
592 |
e.stopPropagation();
|
593 |
-
|
594 |
-
if (newValue !== null) {
|
595 |
node.setAttribute(attr.name, newValue);
|
596 |
refreshElementsPanel();
|
597 |
-
}
|
598 |
};
|
599 |
element.appendChild(attrSpan);
|
600 |
});
|
@@ -620,15 +703,14 @@
|
|
620 |
} else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
|
621 |
const text = document.createElement('div');
|
622 |
text.style.marginLeft = `${depth * 15}px`;
|
623 |
-
text.className = 'dom-text';
|
624 |
text.textContent = `"${node.textContent.trim()}"`;
|
625 |
text.onclick = (e) => {
|
626 |
e.stopPropagation();
|
627 |
-
|
628 |
-
|
629 |
-
node.textContent = newText;
|
630 |
refreshElementsPanel();
|
631 |
-
}
|
632 |
};
|
633 |
parentElement.appendChild(text);
|
634 |
}
|
|
|
140 |
color: #333;
|
141 |
}
|
142 |
|
143 |
+
.dom-edit-input {
|
144 |
+
background: #fff;
|
145 |
+
border: 1px solid #4fc3f7;
|
146 |
+
padding: 0 2px;
|
147 |
+
margin: -1px 0;
|
148 |
+
font-family: monospace;
|
149 |
+
min-width: 50px;
|
150 |
+
}
|
151 |
+
|
152 |
.css-panel {
|
153 |
flex: 1;
|
154 |
overflow: auto;
|
|
|
287 |
let contextMenu = null;
|
288 |
let selectedElement = null;
|
289 |
let selectedDOMNode = null;
|
290 |
+
let activeEditElement = null;
|
291 |
|
292 |
// 開発者ツールのUI構築
|
293 |
function createDevTools() {
|
|
|
395 |
|
396 |
switch (action) {
|
397 |
case 'edit-html':
|
398 |
+
startInlineEdit(selectedDOMNode, selectedElement.outerHTML, (newValue) => {
|
399 |
+
selectedElement.outerHTML = newValue;
|
|
|
400 |
refreshElementsPanel();
|
401 |
+
});
|
402 |
break;
|
403 |
|
404 |
case 'add-attribute':
|
|
|
411 |
break;
|
412 |
|
413 |
case 'edit-element':
|
414 |
+
startInlineEdit(selectedDOMNode.querySelector('.dom-tag'), selectedElement.tagName.toLowerCase(), (newValue) => {
|
415 |
+
const newElement = document.createElement(newValue);
|
|
|
416 |
Array.from(selectedElement.attributes).forEach(attr => {
|
417 |
newElement.setAttribute(attr.name, attr.value);
|
418 |
});
|
|
|
420 |
selectedElement.parentNode.replaceChild(newElement, selectedElement);
|
421 |
selectedElement = newElement;
|
422 |
refreshElementsPanel();
|
423 |
+
});
|
424 |
break;
|
425 |
|
426 |
case 'duplicate':
|
|
|
460 |
}
|
461 |
}
|
462 |
|
463 |
+
// インライン編集を開始する関数
|
464 |
+
function startInlineEdit(element, initialValue, callback) {
|
465 |
+
if (activeEditElement) return;
|
466 |
+
|
467 |
+
const originalValue = element.textContent;
|
468 |
+
const rect = element.getBoundingClientRect();
|
469 |
+
|
470 |
+
const input = document.createElement('input');
|
471 |
+
input.className = 'dom-edit-input';
|
472 |
+
input.value = initialValue || originalValue;
|
473 |
+
input.style.position = 'absolute';
|
474 |
+
input.style.left = `${rect.left}px`;
|
475 |
+
input.style.top = `${rect.top}px`;
|
476 |
+
input.style.width = `${rect.width + 20}px`;
|
477 |
+
|
478 |
+
document.body.appendChild(input);
|
479 |
+
input.focus();
|
480 |
+
input.select();
|
481 |
+
|
482 |
+
activeEditElement = {
|
483 |
+
element: element,
|
484 |
+
input: input,
|
485 |
+
callback: callback
|
486 |
+
};
|
487 |
+
|
488 |
+
// 入力フィールド外をクリックしたら編集を終了
|
489 |
+
const clickOutsideHandler = (e) => {
|
490 |
+
if (!input.contains(e.target)) {
|
491 |
+
finishInlineEdit();
|
492 |
+
}
|
493 |
+
};
|
494 |
+
|
495 |
+
// Enterキーで編集を終了
|
496 |
+
input.addEventListener('keydown', (e) => {
|
497 |
+
if (e.key === 'Enter') {
|
498 |
+
finishInlineEdit();
|
499 |
+
} else if (e.key === 'Escape') {
|
500 |
+
cancelInlineEdit();
|
501 |
+
}
|
502 |
+
});
|
503 |
+
|
504 |
+
setTimeout(() => {
|
505 |
+
document.addEventListener('click', clickOutsideHandler);
|
506 |
+
}, 0);
|
507 |
+
|
508 |
+
function finishInlineEdit() {
|
509 |
+
if (input.value !== originalValue && callback) {
|
510 |
+
callback(input.value);
|
511 |
+
}
|
512 |
+
cleanup();
|
513 |
+
}
|
514 |
+
|
515 |
+
function cancelInlineEdit() {
|
516 |
+
cleanup();
|
517 |
+
}
|
518 |
+
|
519 |
+
function cleanup() {
|
520 |
+
document.removeEventListener('click', clickOutsideHandler);
|
521 |
+
input.remove();
|
522 |
+
activeEditElement = null;
|
523 |
+
}
|
524 |
+
}
|
525 |
+
|
526 |
// Consoleパネルの作成
|
527 |
function createConsolePanel() {
|
528 |
const panel = document.createElement('div');
|
|
|
650 |
|
651 |
// タグ名
|
652 |
const tag = document.createElement('span');
|
653 |
+
tag.className = 'dom-tag editable';
|
654 |
tag.textContent = `<${node.tagName.toLowerCase()}`;
|
655 |
+
tag.onclick = (e) => {
|
656 |
+
e.stopPropagation();
|
657 |
+
startInlineEdit(tag, node.tagName.toLowerCase(), (newValue) => {
|
658 |
+
const newElement = document.createElement(newValue);
|
659 |
+
Array.from(node.attributes).forEach(attr => {
|
660 |
+
newElement.setAttribute(attr.name, attr.value);
|
661 |
+
});
|
662 |
+
newElement.innerHTML = node.innerHTML;
|
663 |
+
node.parentNode.replaceChild(newElement, node);
|
664 |
+
selectedElement = newElement;
|
665 |
+
refreshElementsPanel();
|
666 |
+
});
|
667 |
+
};
|
668 |
element.appendChild(tag);
|
669 |
|
670 |
// 属性
|
|
|
674 |
attrSpan.textContent = ` ${attr.name}="${attr.value}"`;
|
675 |
attrSpan.onclick = (e) => {
|
676 |
e.stopPropagation();
|
677 |
+
startInlineEdit(attrSpan, attr.value, (newValue) => {
|
|
|
678 |
node.setAttribute(attr.name, newValue);
|
679 |
refreshElementsPanel();
|
680 |
+
});
|
681 |
};
|
682 |
element.appendChild(attrSpan);
|
683 |
});
|
|
|
703 |
} else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
|
704 |
const text = document.createElement('div');
|
705 |
text.style.marginLeft = `${depth * 15}px`;
|
706 |
+
text.className = 'dom-text editable';
|
707 |
text.textContent = `"${node.textContent.trim()}"`;
|
708 |
text.onclick = (e) => {
|
709 |
e.stopPropagation();
|
710 |
+
startInlineEdit(text, node.textContent.trim(), (newValue) => {
|
711 |
+
node.textContent = newValue;
|
|
|
712 |
refreshElementsPanel();
|
713 |
+
});
|
714 |
};
|
715 |
parentElement.appendChild(text);
|
716 |
}
|