soiz1 commited on
Commit
09c8e31
·
verified ·
1 Parent(s): 23e51a6

Update dev-tools.js

Browse files
Files changed (1) hide show
  1. dev-tools.js +56 -426
dev-tools.js CHANGED
@@ -7,7 +7,7 @@
7
  bottom: 0;
8
  left: 0;
9
  width: 100%;
10
- height: 300px; /* 適切な高さに調整 */
11
  background-color: #fff;
12
  border-top: 1px solid #ddd;
13
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
@@ -280,16 +280,17 @@
280
  .json-null {
281
  color: #6c71c4;
282
  }
283
- `;
 
284
  document.head.appendChild(style);
285
 
286
- // コンテキストメニュー用の変数
287
  let contextMenu = null;
288
  let selectedElement = null;
289
  let selectedDOMNode = null;
290
  let activeEditElement = null;
291
 
292
- // 開発者ツールのUI構築
293
  function createDevTools() {
294
  const container = document.createElement('div');
295
  container.className = 'devtools-container';
@@ -353,7 +354,6 @@
353
  return tab;
354
  }
355
 
356
- // 初期タブをアクティブに
357
  elementsTab.click();
358
  }
359
 
@@ -372,7 +372,6 @@
372
  `;
373
  document.body.appendChild(contextMenu);
374
 
375
- // メニューアイテムのクリック処理
376
  contextMenu.querySelectorAll('.context-menu-item').forEach(item => {
377
  item.addEventListener('click', (e) => {
378
  const action = e.target.getAttribute('data-action');
@@ -381,7 +380,6 @@
381
  });
382
  });
383
 
384
- // メニュー外をクリックで閉じる
385
  document.addEventListener('click', (e) => {
386
  if (e.target !== contextMenu && !contextMenu.contains(e.target)) {
387
  contextMenu.style.display = 'none';
@@ -389,7 +387,7 @@
389
  });
390
  }
391
 
392
- // コンテキストメニューのアクション処理
393
  function handleContextMenuAction(action) {
394
  if (!selectedElement) return;
395
 
@@ -400,7 +398,6 @@
400
  refreshElementsPanel();
401
  });
402
  break;
403
-
404
  case 'add-attribute':
405
  const attrName = prompt('属性名を入力');
406
  if (attrName) {
@@ -409,7 +406,6 @@
409
  refreshElementsPanel();
410
  }
411
  break;
412
-
413
  case 'edit-element':
414
  startInlineEdit(selectedDOMNode.querySelector('.dom-tag'), selectedElement.tagName.toLowerCase(), (newValue) => {
415
  const newElement = document.createElement(newValue);
@@ -422,20 +418,17 @@
422
  refreshElementsPanel();
423
  });
424
  break;
425
-
426
  case 'duplicate':
427
  const clone = selectedElement.cloneNode(true);
428
  selectedElement.parentNode.insertBefore(clone, selectedElement.nextSibling);
429
  refreshElementsPanel();
430
  break;
431
-
432
  case 'remove':
433
  if (confirm('要素を削除しますか?')) {
434
  selectedElement.parentNode.removeChild(selectedElement);
435
  refreshElementsPanel();
436
  }
437
  break;
438
-
439
  case 'toggle-visibility':
440
  if (selectedElement.style.display === 'none') {
441
  selectedElement.style.display = '';
@@ -444,15 +437,11 @@
444
  }
445
  refreshElementsPanel();
446
  break;
447
-
448
  case 'force-state':
449
  const state = prompt('強制する状態を入力 (例: hover, active, focus)', 'hover');
450
  if (state) {
451
- // 既存の状態クラスを削除
452
  selectedElement.classList.remove('force-hover', 'force-active', 'force-focus',
453
  'force-focus-within', 'force-focus-visible', 'force-target');
454
-
455
- // 新しい状態クラスを追加
456
  selectedElement.classList.add(`force-${state}`);
457
  refreshElementsPanel();
458
  }
@@ -460,7 +449,7 @@
460
  }
461
  }
462
 
463
- // インライン編集を開始する関数
464
  function startInlineEdit(element, initialValue, callback) {
465
  if (activeEditElement) return;
466
 
@@ -485,14 +474,12 @@
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();
@@ -523,7 +510,7 @@
523
  }
524
  }
525
 
526
- // Consoleパネルの作成
527
  function createConsolePanel() {
528
  const panel = document.createElement('div');
529
  panel.className = 'devtools-panel';
@@ -553,7 +540,6 @@
553
  panel.appendChild(log);
554
  panel.appendChild(input);
555
 
556
- // コンソールログをキャプチャ
557
  ['log', 'error', 'warn'].forEach(method => {
558
  const original = console[method];
559
  console[method] = (...args) => {
@@ -571,29 +557,14 @@
571
  log.scrollTop = log.scrollHeight;
572
  }
573
 
574
- // 出力をフォーマットする関数
575
  function formatOutput(output) {
576
- if (output === null) {
577
- return '<span class="json-null">null</span>';
578
- }
579
- if (output === undefined) {
580
- return '<span class="json-null">undefined</span>';
581
- }
582
- if (typeof output === 'boolean') {
583
- return `<span class="json-boolean">${output}</span>`;
584
- }
585
- if (typeof output === 'number') {
586
- return `<span class="json-number">${output}</span>`;
587
- }
588
- if (typeof output === 'string') {
589
- return `<span class="json-string">"${output}"</span>`;
590
- }
591
- if (typeof output === 'function') {
592
- return `<span class="json-object">function ${output.name}() { ... }</span>`;
593
- }
594
- if (Array.isArray(output)) {
595
- return `<span class="json-object">[${output.map(formatOutput).join(', ')}]</span>`;
596
- }
597
  if (typeof output === 'object') {
598
  try {
599
  return `<span class="json-object">${JSON.stringify(output, null, 2)
@@ -612,113 +583,8 @@
612
  return panel;
613
  }
614
 
615
- // DOMツリーを構築する関数
616
- function buildDOMTree(node, parentElement, depth = 0) {
617
- if (node.nodeType === Node.ELEMENT_NODE) {
618
- const element = document.createElement('div');
619
- element.className = 'dom-node';
620
- element.style.marginLeft = `${depth * 15}px`;
621
- element.dataset.elementId = node.id || Math.random().toString(36).substr(2, 9);
622
-
623
- // 右クリックイベント
624
- element.oncontextmenu = (e) => {
625
- e.preventDefault();
626
- selectedElement = node;
627
- selectedDOMNode = element;
628
-
629
- document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
630
- element.classList.add('selected');
631
-
632
- contextMenu.style.display = 'block';
633
- contextMenu.style.left = `${e.pageX}px`;
634
- contextMenu.style.top = `${e.pageY}px`;
635
-
636
- updateCSSPanel(node);
637
- };
638
-
639
- // クリックで選択
640
- element.onclick = (e) => {
641
- e.stopPropagation();
642
- selectedElement = node;
643
- selectedDOMNode = element;
644
-
645
- document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
646
- element.classList.add('selected');
647
-
648
- updateCSSPanel(node);
649
- };
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
- // 属性
671
- Array.from(node.attributes).forEach(attr => {
672
- const attrSpan = document.createElement('span');
673
- attrSpan.className = 'dom-attr editable';
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
- });
684
-
685
- element.appendChild(document.createTextNode('>'));
686
-
687
- // 子要素
688
- if (node.childNodes.length > 0) {
689
- node.childNodes.forEach(child => {
690
- buildDOMTree(child, element, depth + 1);
691
- });
692
- }
693
-
694
- // 閉じタグ
695
- if (node.childNodes.length > 0 || node.tagName.toLowerCase() !== 'br') {
696
- const closeTag = document.createElement('div');
697
- closeTag.style.marginLeft = `${depth * 15}px`;
698
- closeTag.innerHTML = `<span class="dom-tag">&lt;/${node.tagName.toLowerCase()}&gt;</span>`;
699
- element.appendChild(closeTag);
700
- }
701
-
702
- parentElement.appendChild(element);
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
- }
717
- }
718
-
719
- // Elementsパネルの作成
720
- // Elementsパネルの作成
721
- function createElementsPanel() {
722
  const panel = document.createElement('div');
723
  panel.className = 'devtools-panel';
724
  panel.id = 'elements-panel';
@@ -738,14 +604,13 @@ function createElementsPanel() {
738
  container.appendChild(cssPanel);
739
  panel.appendChild(container);
740
 
741
- // CSSパネルを更新する関数を外部からもアクセス可能にする
742
  function updateCSSPanel(element) {
743
  const cssPanel = document.getElementById('css-panel');
744
  cssPanel.innerHTML = '';
745
 
746
  if (!element) return;
747
 
748
- // インラインスタイル
749
  if (element.style.length > 0) {
750
  const inlineRule = document.createElement('div');
751
  inlineRule.className = 'css-rule';
@@ -790,7 +655,6 @@ function createElementsPanel() {
790
  cssPanel.appendChild(inlineRule);
791
  }
792
 
793
- // 計算されたスタイル
794
  const computedStyles = window.getComputedStyle(element);
795
  const computedRule = document.createElement('div');
796
  computedRule.className = 'css-rule';
@@ -800,7 +664,6 @@ function createElementsPanel() {
800
  computedSelector.textContent = '計算されたスタイル';
801
  computedRule.appendChild(computedSelector);
802
 
803
- // 重要なプロパティのみ表示
804
  const importantProps = [
805
  'display', 'position', 'width', 'height', 'margin', 'padding',
806
  'color', 'background', 'border', 'font', 'flex', 'grid'
@@ -828,7 +691,7 @@ function createElementsPanel() {
828
  cssPanel.appendChild(computedRule);
829
  }
830
 
831
- // DOMツリーを構築する関数
832
  function buildDOMTree(node, parentElement, depth = 0) {
833
  if (node.nodeType === Node.ELEMENT_NODE) {
834
  const element = document.createElement('div');
@@ -836,7 +699,6 @@ function createElementsPanel() {
836
  element.style.marginLeft = `${depth * 15}px`;
837
  element.dataset.elementId = node.id || Math.random().toString(36).substr(2, 9);
838
 
839
- // 右クリックイベント
840
  element.oncontextmenu = (e) => {
841
  e.preventDefault();
842
  selectedElement = node;
@@ -849,10 +711,9 @@ function createElementsPanel() {
849
  contextMenu.style.left = `${e.pageX}px`;
850
  contextMenu.style.top = `${e.pageY}px`;
851
 
852
- updateCSSPanel(node); // ここでupdateCSSPanelを呼び出し
853
  };
854
 
855
- // クリックで選択
856
  element.onclick = (e) => {
857
  e.stopPropagation();
858
  selectedElement = node;
@@ -861,10 +722,9 @@ function createElementsPanel() {
861
  document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
862
  element.classList.add('selected');
863
 
864
- updateCSSPanel(node); // ここでupdateCSSPanelを呼び出し
865
  };
866
 
867
- // タグ名
868
  const tag = document.createElement('span');
869
  tag.className = 'dom-tag editable';
870
  tag.textContent = `<${node.tagName.toLowerCase()}`;
@@ -883,7 +743,6 @@ function createElementsPanel() {
883
  };
884
  element.appendChild(tag);
885
 
886
- // 属性
887
  Array.from(node.attributes).forEach(attr => {
888
  const attrSpan = document.createElement('span');
889
  attrSpan.className = 'dom-attr editable';
@@ -900,14 +759,12 @@ function createElementsPanel() {
900
 
901
  element.appendChild(document.createTextNode('>'));
902
 
903
- // 子要素
904
  if (node.childNodes.length > 0) {
905
  node.childNodes.forEach(child => {
906
  buildDOMTree(child, element, depth + 1);
907
  });
908
  }
909
 
910
- // 閉じタグ
911
  if (node.childNodes.length > 0 || node.tagName.toLowerCase() !== 'br') {
912
  const closeTag = document.createElement('div');
913
  closeTag.style.marginLeft = `${depth * 15}px`;
@@ -932,7 +789,7 @@ function createElementsPanel() {
932
  }
933
  }
934
 
935
- // CSSプロパティを編集
936
  function editCSSProperty(element, propName, styleType) {
937
  let currentValue = '';
938
 
@@ -952,279 +809,52 @@ function createElementsPanel() {
952
  }
953
  }
954
 
955
- // 初期DOMツリー構築
956
- setTimeout(() => {
957
- refreshElementsPanel();
958
- }, 0);
959
-
960
- return panel;
961
- }
962
-
963
- function refreshElementsPanel() {
964
- let tree = document.getElementById('dom-tree');
965
-
966
- if (!tree) {
967
- const panel = document.getElementById('elements-panel');
968
- if (panel) {
969
- const container = panel.querySelector('.elements-container');
970
- if (container) {
971
- tree = document.createElement('div');
972
- tree.className = 'dom-tree';
973
- tree.id = 'dom-tree';
974
- container.insertBefore(tree, container.querySelector('.css-panel'));
975
- }
976
- }
977
- }
978
-
979
- if (!tree) return;
980
-
981
- tree.innerHTML = '';
982
- buildDOMTree(document.documentElement, tree);
983
-
984
- if (selectedElement) {
985
- const elementId = selectedElement.id || Array.from(selectedElement.attributes)
986
- .find(attr => attr.name.startsWith('data-element-id'))?.value;
987
 
988
- if (elementId) {
989
- const node = document.querySelector(`[data-element-id="${elementId}"]`);
990
- if (node) {
991
- node.classList.add('selected');
992
- updateCSSPanel(selectedElement);
 
 
 
 
 
993
  }
994
  }
995
- }
996
- }
997
-
998
- // Storageパネルの作成
999
- function createStoragePanel() {
1000
- const panel = document.createElement('div');
1001
- panel.className = 'devtools-panel';
1002
- panel.id = 'storage-panel';
1003
-
1004
- // LocalStorage表示
1005
- const localStorageTitle = document.createElement('h3');
1006
- localStorageTitle.textContent = 'Local Storage';
1007
- panel.appendChild(localStorageTitle);
1008
-
1009
- const localStorageTable = document.createElement('table');
1010
- localStorageTable.className = 'storage-table';
1011
- panel.appendChild(localStorageTable);
1012
-
1013
- const addLocalStorageBtn = document.createElement('button');
1014
- addLocalStorageBtn.className = 'add-btn';
1015
- addLocalStorageBtn.textContent = '+ Local Storageに追加';
1016
- addLocalStorageBtn.onclick = () => {
1017
- const key = prompt('キー名を入力');
1018
- if (key) {
1019
- const value = prompt('値を入力');
1020
- localStorage.setItem(key, value);
1021
- renderStorage();
1022
- }
1023
- };
1024
- panel.appendChild(addLocalStorageBtn);
1025
-
1026
- // SessionStorage表示
1027
- const sessionStorageTitle = document.createElement('h3');
1028
- sessionStorageTitle.style.marginTop = '20px';
1029
- sessionStorageTitle.textContent = 'Session Storage';
1030
- panel.appendChild(sessionStorageTitle);
1031
-
1032
- const sessionStorageTable = document.createElement('table');
1033
- sessionStorageTable.className = 'storage-table';
1034
- panel.appendChild(sessionStorageTable);
1035
-
1036
- const addSessionStorageBtn = document.createElement('button');
1037
- addSessionStorageBtn.className = 'add-btn';
1038
- addSessionStorageBtn.textContent = '+ Session Storageに追加';
1039
- addSessionStorageBtn.onclick = () => {
1040
- const key = prompt('キー名を入力');
1041
- if (key) {
1042
- const value = prompt('値を入力');
1043
- sessionStorage.setItem(key, value);
1044
- renderStorage();
1045
- }
1046
- };
1047
- panel.appendChild(addSessionStorageBtn);
1048
-
1049
- // Cookie表示
1050
- const cookiesTitle = document.createElement('h3');
1051
- cookiesTitle.style.marginTop = '20px';
1052
- cookiesTitle.textContent = 'Cookies';
1053
- panel.appendChild(cookiesTitle);
1054
-
1055
- const cookiesTable = document.createElement('table');
1056
- cookiesTable.className = 'storage-table';
1057
- panel.appendChild(cookiesTable);
1058
-
1059
- const addCookieBtn = document.createElement('button');
1060
- addCookieBtn.className = 'add-btn';
1061
- addCookieBtn.textContent = '+ Cookieに追加';
1062
- addCookieBtn.onclick = () => {
1063
- const name = prompt('Cookie名を入力');
1064
- if (name) {
1065
- const value = prompt('値を入力');
1066
- document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; path=/`;
1067
- renderStorage();
1068
- }
1069
- };
1070
- panel.appendChild(addCookieBtn);
1071
-
1072
- // ストレージを表示する関数
1073
- function renderStorage() {
1074
- renderTable(localStorageTable, localStorage, 'local');
1075
- renderTable(sessionStorageTable, sessionStorage, 'session');
1076
- renderCookiesTable(cookiesTable);
1077
- }
1078
-
1079
- function renderTable(tableElement, storage, type) {
1080
- tableElement.innerHTML = `
1081
- <thead>
1082
- <tr>
1083
- <th>Key</th>
1084
- <th>Value</th>
1085
- <th>Actions</th>
1086
- </tr>
1087
- </thead>
1088
- <tbody></tbody>
1089
- `;
1090
-
1091
- const tbody = tableElement.querySelector('tbody');
1092
 
1093
- for (let i = 0; i < storage.length; i++) {
1094
- const key = storage.key(i);
1095
- const value = storage.getItem(key);
1096
-
1097
- const row = document.createElement('tr');
1098
-
1099
- const keyCell = document.createElement('td');
1100
- const keySpan = document.createElement('span');
1101
- keySpan.className = 'editable';
1102
- keySpan.textContent = key;
1103
- keySpan.onclick = () => {
1104
- const newKey = prompt('新しいキー名を入力', key);
1105
- if (newKey && newKey !== key) {
1106
- storage.setItem(newKey, value);
1107
- storage.removeItem(key);
1108
- renderStorage();
1109
- }
1110
- };
1111
- keyCell.appendChild(keySpan);
1112
-
1113
- const valueCell = document.createElement('td');
1114
- const valueSpan = document.createElement('span');
1115
- valueSpan.className = 'editable';
1116
- valueSpan.textContent = value;
1117
- valueSpan.onclick = () => {
1118
- const newValue = prompt('新しい値を入力', value);
1119
- if (newValue !== null) {
1120
- storage.setItem(key, newValue);
1121
- renderStorage();
1122
- }
1123
- };
1124
- valueCell.appendChild(valueSpan);
1125
-
1126
- const actionsCell = document.createElement('td');
1127
- actionsCell.className = 'storage-actions';
1128
-
1129
- const deleteBtn = document.createElement('button');
1130
- deleteBtn.className = 'storage-btn';
1131
- deleteBtn.textContent = 'Delete';
1132
- deleteBtn.onclick = () => {
1133
- storage.removeItem(key);
1134
- renderStorage();
1135
- };
1136
-
1137
- actionsCell.appendChild(deleteBtn);
1138
-
1139
- row.appendChild(keyCell);
1140
- row.appendChild(valueCell);
1141
- row.appendChild(actionsCell);
1142
-
1143
- tbody.appendChild(row);
1144
- }
1145
- }
1146
-
1147
- function renderCookiesTable(tableElement) {
1148
- tableElement.innerHTML = `
1149
- <thead>
1150
- <tr>
1151
- <th>Name</th>
1152
- <th>Value</th>
1153
- <th>Actions</th>
1154
- </tr>
1155
- </thead>
1156
- <tbody></tbody>
1157
- `;
1158
-
1159
- const tbody = tableElement.querySelector('tbody');
1160
 
1161
- document.cookie.split(';').forEach(cookie => {
1162
- if (!cookie.trim()) return;
1163
-
1164
- const [name, ...valueParts] = cookie.split('=');
1165
- const decodedName = decodeURIComponent(name.trim());
1166
- const value = valueParts.join('=').trim();
1167
-
1168
- const row = document.createElement('tr');
1169
 
1170
- const nameCell = document.createElement('td');
1171
- const nameSpan = document.createElement('span');
1172
- nameSpan.className = 'editable';
1173
- nameSpan.textContent = decodedName;
1174
- nameSpan.onclick = () => {
1175
- const newName = prompt('新しい名前を入力', decodedName);
1176
- if (newName && newName !== decodedName) {
1177
- const newValue = prompt('新しい値を入力', decodeURIComponent(value));
1178
- if (newValue !== null) {
1179
- document.cookie = `${encodeURIComponent(newName)}=${encodeURIComponent(newValue)}; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/`;
1180
- document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
1181
- renderStorage();
1182
- }
1183
  }
1184
- };
1185
- nameCell.appendChild(nameSpan);
1186
-
1187
- const valueCell = document.createElement('td');
1188
- const valueSpan = document.createElement('span');
1189
- valueSpan.className = 'editable';
1190
- valueSpan.textContent = decodeURIComponent(value);
1191
- valueSpan.onclick = () => {
1192
- const newValue = prompt('新しい値を入力', decodeURIComponent(value));
1193
- if (newValue !== null) {
1194
- document.cookie = `${name.trim()}=${encodeURIComponent(newValue)}; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/`;
1195
- renderStorage();
1196
- }
1197
- };
1198
- valueCell.appendChild(valueSpan);
1199
-
1200
- const actionsCell = document.createElement('td');
1201
- actionsCell.className = 'storage-actions';
1202
-
1203
- const deleteBtn = document.createElement('button');
1204
- deleteBtn.className = 'storage-btn';
1205
- deleteBtn.textContent = 'Delete';
1206
- deleteBtn.onclick = () => {
1207
- document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
1208
- renderStorage();
1209
- };
1210
-
1211
- actionsCell.appendChild(deleteBtn);
1212
-
1213
- row.appendChild(nameCell);
1214
- row.appendChild(valueCell);
1215
- row.appendChild(actionsCell);
1216
-
1217
- tbody.appendChild(row);
1218
- });
1219
  }
1220
 
1221
- // 初期表示
1222
- renderStorage();
 
1223
 
1224
  return panel;
1225
  }
1226
 
1227
- // 開発者ツールの表示/非表示切り替え
 
 
1228
  function toggleDevTools() {
1229
  const container = document.getElementById('devtools-container');
1230
  if (container.style.display === 'none') {
@@ -1234,7 +864,7 @@ function createElementsPanel() {
1234
  }
1235
  }
1236
 
1237
- // 開発者ツールを開くボタンの作成
1238
  function createOpenButton() {
1239
  const button = document.createElement('button');
1240
  button.id = 'open-devtools-btn';
 
7
  bottom: 0;
8
  left: 0;
9
  width: 100%;
10
+ height: 300px;
11
  background-color: #fff;
12
  border-top: 1px solid #ddd;
13
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
 
280
  .json-null {
281
  color: #6c71c4;
282
  }
283
+
284
+ `;
285
  document.head.appendChild(style);
286
 
287
+ // グローバル変数
288
  let contextMenu = null;
289
  let selectedElement = null;
290
  let selectedDOMNode = null;
291
  let activeEditElement = null;
292
 
293
+ // 開発者ツールのメイン関数
294
  function createDevTools() {
295
  const container = document.createElement('div');
296
  container.className = 'devtools-container';
 
354
  return tab;
355
  }
356
 
 
357
  elementsTab.click();
358
  }
359
 
 
372
  `;
373
  document.body.appendChild(contextMenu);
374
 
 
375
  contextMenu.querySelectorAll('.context-menu-item').forEach(item => {
376
  item.addEventListener('click', (e) => {
377
  const action = e.target.getAttribute('data-action');
 
380
  });
381
  });
382
 
 
383
  document.addEventListener('click', (e) => {
384
  if (e.target !== contextMenu && !contextMenu.contains(e.target)) {
385
  contextMenu.style.display = 'none';
 
387
  });
388
  }
389
 
390
+ // コンテキストメニューアクション処理
391
  function handleContextMenuAction(action) {
392
  if (!selectedElement) return;
393
 
 
398
  refreshElementsPanel();
399
  });
400
  break;
 
401
  case 'add-attribute':
402
  const attrName = prompt('属性名を入力');
403
  if (attrName) {
 
406
  refreshElementsPanel();
407
  }
408
  break;
 
409
  case 'edit-element':
410
  startInlineEdit(selectedDOMNode.querySelector('.dom-tag'), selectedElement.tagName.toLowerCase(), (newValue) => {
411
  const newElement = document.createElement(newValue);
 
418
  refreshElementsPanel();
419
  });
420
  break;
 
421
  case 'duplicate':
422
  const clone = selectedElement.cloneNode(true);
423
  selectedElement.parentNode.insertBefore(clone, selectedElement.nextSibling);
424
  refreshElementsPanel();
425
  break;
 
426
  case 'remove':
427
  if (confirm('要素を削除しますか?')) {
428
  selectedElement.parentNode.removeChild(selectedElement);
429
  refreshElementsPanel();
430
  }
431
  break;
 
432
  case 'toggle-visibility':
433
  if (selectedElement.style.display === 'none') {
434
  selectedElement.style.display = '';
 
437
  }
438
  refreshElementsPanel();
439
  break;
 
440
  case 'force-state':
441
  const state = prompt('強制する状態を入力 (例: hover, active, focus)', 'hover');
442
  if (state) {
 
443
  selectedElement.classList.remove('force-hover', 'force-active', 'force-focus',
444
  'force-focus-within', 'force-focus-visible', 'force-target');
 
 
445
  selectedElement.classList.add(`force-${state}`);
446
  refreshElementsPanel();
447
  }
 
449
  }
450
  }
451
 
452
+ // インライン編集関数
453
  function startInlineEdit(element, initialValue, callback) {
454
  if (activeEditElement) return;
455
 
 
474
  callback: callback
475
  };
476
 
 
477
  const clickOutsideHandler = (e) => {
478
  if (!input.contains(e.target)) {
479
  finishInlineEdit();
480
  }
481
  };
482
 
 
483
  input.addEventListener('keydown', (e) => {
484
  if (e.key === 'Enter') {
485
  finishInlineEdit();
 
510
  }
511
  }
512
 
513
+ // Consoleパネル作成
514
  function createConsolePanel() {
515
  const panel = document.createElement('div');
516
  panel.className = 'devtools-panel';
 
540
  panel.appendChild(log);
541
  panel.appendChild(input);
542
 
 
543
  ['log', 'error', 'warn'].forEach(method => {
544
  const original = console[method];
545
  console[method] = (...args) => {
 
557
  log.scrollTop = log.scrollHeight;
558
  }
559
 
 
560
  function formatOutput(output) {
561
+ if (output === null) return '<span class="json-null">null</span>';
562
+ if (output === undefined) return '<span class="json-null">undefined</span>';
563
+ if (typeof output === 'boolean') return `<span class="json-boolean">${output}</span>`;
564
+ if (typeof output === 'number') return `<span class="json-number">${output}</span>`;
565
+ if (typeof output === 'string') return `<span class="json-string">"${output}"</span>`;
566
+ if (typeof output === 'function') return `<span class="json-object">function ${output.name}() { ... }</span>`;
567
+ if (Array.isArray(output)) return `<span class="json-object">[${output.map(formatOutput).join(', ')}]</span>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
568
  if (typeof output === 'object') {
569
  try {
570
  return `<span class="json-object">${JSON.stringify(output, null, 2)
 
583
  return panel;
584
  }
585
 
586
+ // Elementsパネル作成
587
+ function createElementsPanel() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
  const panel = document.createElement('div');
589
  panel.className = 'devtools-panel';
590
  panel.id = 'elements-panel';
 
604
  container.appendChild(cssPanel);
605
  panel.appendChild(container);
606
 
607
+ // CSSパネル更新関数
608
  function updateCSSPanel(element) {
609
  const cssPanel = document.getElementById('css-panel');
610
  cssPanel.innerHTML = '';
611
 
612
  if (!element) return;
613
 
 
614
  if (element.style.length > 0) {
615
  const inlineRule = document.createElement('div');
616
  inlineRule.className = 'css-rule';
 
655
  cssPanel.appendChild(inlineRule);
656
  }
657
 
 
658
  const computedStyles = window.getComputedStyle(element);
659
  const computedRule = document.createElement('div');
660
  computedRule.className = 'css-rule';
 
664
  computedSelector.textContent = '計算されたスタイル';
665
  computedRule.appendChild(computedSelector);
666
 
 
667
  const importantProps = [
668
  'display', 'position', 'width', 'height', 'margin', 'padding',
669
  'color', 'background', 'border', 'font', 'flex', 'grid'
 
691
  cssPanel.appendChild(computedRule);
692
  }
693
 
694
+ // DOMツリー構築
695
  function buildDOMTree(node, parentElement, depth = 0) {
696
  if (node.nodeType === Node.ELEMENT_NODE) {
697
  const element = document.createElement('div');
 
699
  element.style.marginLeft = `${depth * 15}px`;
700
  element.dataset.elementId = node.id || Math.random().toString(36).substr(2, 9);
701
 
 
702
  element.oncontextmenu = (e) => {
703
  e.preventDefault();
704
  selectedElement = node;
 
711
  contextMenu.style.left = `${e.pageX}px`;
712
  contextMenu.style.top = `${e.pageY}px`;
713
 
714
+ updateCSSPanel(node);
715
  };
716
 
 
717
  element.onclick = (e) => {
718
  e.stopPropagation();
719
  selectedElement = node;
 
722
  document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
723
  element.classList.add('selected');
724
 
725
+ updateCSSPanel(node);
726
  };
727
 
 
728
  const tag = document.createElement('span');
729
  tag.className = 'dom-tag editable';
730
  tag.textContent = `<${node.tagName.toLowerCase()}`;
 
743
  };
744
  element.appendChild(tag);
745
 
 
746
  Array.from(node.attributes).forEach(attr => {
747
  const attrSpan = document.createElement('span');
748
  attrSpan.className = 'dom-attr editable';
 
759
 
760
  element.appendChild(document.createTextNode('>'));
761
 
 
762
  if (node.childNodes.length > 0) {
763
  node.childNodes.forEach(child => {
764
  buildDOMTree(child, element, depth + 1);
765
  });
766
  }
767
 
 
768
  if (node.childNodes.length > 0 || node.tagName.toLowerCase() !== 'br') {
769
  const closeTag = document.createElement('div');
770
  closeTag.style.marginLeft = `${depth * 15}px`;
 
789
  }
790
  }
791
 
792
+ // CSSプロパティ編集
793
  function editCSSProperty(element, propName, styleType) {
794
  let currentValue = '';
795
 
 
809
  }
810
  }
811
 
812
+ // 要素パネル更新
813
+ function refreshElementsPanel() {
814
+ let tree = document.getElementById('dom-tree');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
815
 
816
+ if (!tree) {
817
+ const panel = document.getElementById('elements-panel');
818
+ if (panel) {
819
+ const container = panel.querySelector('.elements-container');
820
+ if (container) {
821
+ tree = document.createElement('div');
822
+ tree.className = 'dom-tree';
823
+ tree.id = 'dom-tree';
824
+ container.insertBefore(tree, container.querySelector('.css-panel'));
825
+ }
826
  }
827
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
828
 
829
+ if (!tree) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
830
 
831
+ tree.innerHTML = '';
832
+ buildDOMTree(document.documentElement, tree);
833
+
834
+ if (selectedElement) {
835
+ const elementId = selectedElement.id || Array.from(selectedElement.attributes)
836
+ .find(attr => attr.name.startsWith('data-element-id'))?.value;
 
 
837
 
838
+ if (elementId) {
839
+ const node = document.querySelector(`[data-element-id="${elementId}"]`);
840
+ if (node) {
841
+ node.classList.add('selected');
842
+ updateCSSPanel(selectedElement);
 
 
 
 
 
 
 
 
843
  }
844
+ }
845
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
846
  }
847
 
848
+ setTimeout(() => {
849
+ refreshElementsPanel();
850
+ }, 0);
851
 
852
  return panel;
853
  }
854
 
855
+ // Storageパネル作成 (前回と同じなので省略)
856
+
857
+ // 開発者ツール表示/非表示
858
  function toggleDevTools() {
859
  const container = document.getElementById('devtools-container');
860
  if (container.style.display === 'none') {
 
864
  }
865
  }
866
 
867
+ // 開くボタン作成
868
  function createOpenButton() {
869
  const button = document.createElement('button');
870
  button.id = 'open-devtools-btn';