soiz1 commited on
Commit
9c29a08
·
verified ·
1 Parent(s): c338e05

Update dev-tools.js

Browse files
Files changed (1) hide show
  1. dev-tools.js +173 -200
dev-tools.js CHANGED
@@ -1,61 +1,65 @@
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
- let vitalsMeasurementStarted = false;
19
 
20
- // スタイルの追加
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  const style = document.createElement('style');
22
  style.textContent = `
23
- .dt-root {
24
- --dt-bg-color: #1e1e1e;
25
- --dt-panel-bg: #252526;
26
- --dt-border-color: #3c3c3c;
27
- --dt-text-color: #e0e0e0;
28
- --dt-text-muted: #a0a0a0;
29
- --dt-primary-color: #007acc;
30
- --dt-primary-hover: #3e9fda;
31
- --dt-success-color: #4caf50;
32
- --dt-error-color: #f44336;
33
- --dt-warning-color: #ff9800;
34
- --dt-info-color: #2196f3;
35
- --dt-highlight-bg: rgba(0, 122, 204, 0.2);
36
- --dt-tab-bg: #2d2d2d;
37
- --dt-tab-active-bg: #1e1e1e;
38
- --dt-console-log-color: #e0e0e0;
39
- --dt-console-error-color: #f44336;
40
- --dt-console-warn-color: #ff9800;
41
- --dt-console-info-color: #4fc3f7;
42
- --dt-json-key: #9cdcfe;
43
- --dt-json-string: #ce9178;
44
- --dt-json-number: #b5cea8;
45
- --dt-json-boolean: #569cd6;
46
- --dt-json-null: #569cd6;
47
- --dt-dom-tag: #569cd6;
48
- --dt-dom-attr: #9cdcfe;
49
- --dt-dom-text: #d4d4d4;
50
- }
51
-
52
  .dt-devtools-container {
53
  position: fixed;
54
  bottom: 0;
55
  left: 0;
56
  width: 100%;
57
  height: 300px;
58
- background-color: var(--dt-panel-bg);
59
  border-top: 1px solid var(--dt-border-color);
60
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
61
  z-index: 9999;
@@ -562,58 +566,39 @@
562
  .dt-json-null {
563
  color: var(--dt-json-null);
564
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
  `;
566
  document.head.appendChild(style);
567
 
568
- // ノードをハイライト表示
569
- function highlightNode(node) {
570
- const elementId = node.id || Math.random().toString(36).substr(2, 9);
571
- node.setAttribute('data-dt-element-id', elementId);
572
-
573
- setTimeout(() => {
574
- const domNode = document.querySelector(`[data-dt-element-id="${elementId}"]`);
575
- if (domNode) {
576
- domNode.classList.add('dt-highlight');
577
- setTimeout(() => {
578
- domNode.classList.remove('dt-highlight');
579
- }, 1500);
580
- }
581
- }, 100);
582
- }
583
-
584
- // Web Vitalsスクリプトを動的に読み込み
585
- const loadWebVitals = () => {
586
- return new Promise((resolve) => {
587
- if (window.webVitals) {
588
- resolve();
589
- return;
590
- }
591
-
592
- const webVitalsScript = document.createElement('script');
593
- webVitalsScript.src = 'https://unpkg.com/[email protected]/dist/web-vitals.iife.js';
594
- webVitalsScript.onload = resolve;
595
- document.head.appendChild(webVitalsScript);
596
- });
597
- };
598
-
599
- // DOM変更を監視するMutationObserver
600
- const setupMutationObserver = () => {
601
- if (observer) observer.disconnect();
602
-
603
- observer = new MutationObserver((mutations) => {
604
- if (refreshElementsPanel) {
605
- refreshElementsPanel();
606
- }
607
- });
608
-
609
- observer.observe(document.documentElement, {
610
- childList: true,
611
- subtree: true,
612
- attributes: false,
613
- characterData: false
614
- });
615
- };
616
-
617
  // 開発者ツールのメイン関数
618
  const createDevTools = () => {
619
  const container = document.createElement('div');
@@ -693,7 +678,7 @@
693
  elementsTab.click();
694
  };
695
 
696
- // Web Vitalsパネル作成
697
  function createVitalsPanel() {
698
  const panel = document.createElement('div');
699
  panel.className = 'dt-devtools-panel';
@@ -781,8 +766,8 @@
781
  container.appendChild(ttfbCard);
782
 
783
  function startVitalsMeasurement() {
784
- if (vitalsMeasurementStarted) return;
785
- vitalsMeasurementStarted = true;
786
 
787
  loadWebVitals().then(() => {
788
  if (window.webVitals) {
@@ -801,36 +786,37 @@
801
  vitals.onLCP(updateLCP);
802
  vitals.onTTFB(updateTTFB);
803
  }
804
- logMessage('Web Vitalsの測定を開始しました', 'dt-console-info');
 
805
  } catch (e) {
806
- logMessage('Web Vitals error: ' + e.message, 'dt-console-error');
807
  }
808
  }
809
  });
810
  }
811
 
812
  function updateCLS(metric) {
813
- vitalsData.CLS = metric.value;
814
  updateVitalDisplay('cls', metric.value, 0.1, 0.25);
815
  }
816
 
817
  function updateFCP(metric) {
818
- vitalsData.FCP = metric.value;
819
  updateVitalDisplay('fcp', metric.value, 1800, 3000);
820
  }
821
 
822
  function updateFID(metric) {
823
- vitalsData.FID = metric.value;
824
  updateVitalDisplay('fid', metric.value, 100, 300);
825
  }
826
 
827
  function updateLCP(metric) {
828
- vitalsData.LCP = metric.value;
829
  updateVitalDisplay('lcp', metric.value, 2500, 4000);
830
  }
831
 
832
  function updateTTFB(metric) {
833
- vitalsData.TTFB = metric.value;
834
  updateVitalDisplay('ttfb', metric.value, 800, 1800);
835
  }
836
 
@@ -918,7 +904,7 @@
918
  error: null
919
  };
920
 
921
- networkRequests.push(requestData);
922
  renderNetworkRequests();
923
 
924
  try {
@@ -995,7 +981,7 @@
995
  error: null
996
  };
997
 
998
- networkRequests.push(requestData);
999
  renderNetworkRequests();
1000
 
1001
  this.addEventListener('load', function() {
@@ -1052,15 +1038,15 @@
1052
 
1053
  requestsList.innerHTML = '';
1054
 
1055
- networkRequests.forEach(request => {
1056
  const requestElement = document.createElement('div');
1057
  requestElement.className = 'dt-network-request';
1058
- if (selectedRequest && selectedRequest.id === request.id) {
1059
  requestElement.classList.add('selected');
1060
  }
1061
 
1062
  requestElement.onclick = () => {
1063
- selectedRequest = request;
1064
  renderNetworkRequests();
1065
  renderNetworkDetails();
1066
  };
@@ -1093,7 +1079,7 @@
1093
  // ネットワークリクエストの詳細を表示
1094
  function renderNetworkDetails() {
1095
  const panel = document.getElementById('network-panel');
1096
- if (!panel || !selectedRequest) return;
1097
 
1098
  const detailsPanel = panel.querySelector('.dt-network-details');
1099
  detailsPanel.innerHTML = '';
@@ -1104,16 +1090,16 @@
1104
  generalSection.innerHTML = `
1105
  <div class="dt-network-detail-title">一般</div>
1106
  <div class="dt-network-detail-content">
1107
- <strong>URL:</strong> ${selectedRequest.url}<br>
1108
- <strong>メソッド:</strong> ${selectedRequest.method}<br>
1109
- <strong>ステータス:</strong> ${selectedRequest.response ? selectedRequest.response.status : '-'}<br>
1110
- <strong>時間:</strong> ${selectedRequest.duration ? Math.round(selectedRequest.duration) + 'ms' : '-'}
1111
  </div>
1112
  `;
1113
  detailsPanel.appendChild(generalSection);
1114
 
1115
  // リクエストヘッダー
1116
- if (selectedRequest.requestHeaders) {
1117
  const headersSection = document.createElement('div');
1118
  headersSection.className = 'dt-network-detail-section';
1119
 
@@ -1124,12 +1110,12 @@
1124
  const headersContent = document.createElement('div');
1125
  headersContent.className = 'dt-network-detail-content';
1126
 
1127
- if (typeof selectedRequest.requestHeaders === 'object' && !(selectedRequest.requestHeaders instanceof Headers)) {
1128
- Object.entries(selectedRequest.requestHeaders).forEach(([key, value]) => {
1129
  headersContent.innerHTML += `<strong>${key}:</strong> ${value}<br>`;
1130
  });
1131
- } else if (selectedRequest.requestHeaders instanceof Headers) {
1132
- selectedRequest.requestHeaders.forEach((value, key) => {
1133
  headersContent.innerHTML += `<strong>${key}:</strong> ${value}<br>`;
1134
  });
1135
  }
@@ -1140,7 +1126,7 @@
1140
  }
1141
 
1142
  // リクエストボディ
1143
- if (selectedRequest.requestBody) {
1144
  const bodySection = document.createElement('div');
1145
  bodySection.className = 'dt-network-detail-section';
1146
 
@@ -1152,10 +1138,10 @@
1152
  bodyContent.className = 'dt-network-detail-content';
1153
 
1154
  try {
1155
- if (typeof selectedRequest.requestBody === 'string') {
1156
- bodyContent.textContent = selectedRequest.requestBody;
1157
- } else if (typeof selectedRequest.requestBody === 'object') {
1158
- bodyContent.textContent = JSON.stringify(selectedRequest.requestBody, null, 2);
1159
  }
1160
  } catch (e) {
1161
  bodyContent.textContent = 'ボディを表示できません';
@@ -1167,7 +1153,7 @@
1167
  }
1168
 
1169
  // レスポンス
1170
- if (selectedRequest.response) {
1171
  const responseSection = document.createElement('div');
1172
  responseSection.className = 'dt-network-detail-section';
1173
 
@@ -1178,11 +1164,11 @@
1178
  const responseContent = document.createElement('div');
1179
  responseContent.className = 'dt-network-detail-content';
1180
 
1181
- if (selectedRequest.response.body) {
1182
- if (typeof selectedRequest.response.body === 'object') {
1183
- responseContent.textContent = JSON.stringify(selectedRequest.response.body, null, 2);
1184
  } else {
1185
- responseContent.textContent = selectedRequest.response.body;
1186
  }
1187
  } else {
1188
  responseContent.textContent = 'レスポンスボディがありません';
@@ -1194,7 +1180,7 @@
1194
  }
1195
 
1196
  // エラー
1197
- if (selectedRequest.error) {
1198
  const errorSection = document.createElement('div');
1199
  errorSection.className = 'dt-network-detail-section';
1200
 
@@ -1204,10 +1190,10 @@
1204
 
1205
  const errorContent = document.createElement('div');
1206
  errorContent.className = 'dt-network-detail-content';
1207
- errorContent.textContent = `${selectedRequest.error.name}: ${selectedRequest.error.message}`;
1208
 
1209
- if (selectedRequest.error.stack) {
1210
- errorContent.innerHTML += `<br><br>${selectedRequest.error.stack.replace(/\n/g, '<br>')}`;
1211
  }
1212
 
1213
  errorSection.appendChild(errorTitle);
@@ -1218,9 +1204,9 @@
1218
 
1219
  // コンテキストメニュー作成
1220
  function createContextMenu() {
1221
- contextMenu = document.createElement('div');
1222
- contextMenu.className = 'dt-context-menu';
1223
- contextMenu.innerHTML = `
1224
  <div class="dt-context-menu-item" data-action="edit-html">HTMLとして編集</div>
1225
  <div class="dt-context-menu-item" data-action="edit-whole-html">HTML全体を編集</div>
1226
  <div class="dt-context-menu-item" data-action="add-attribute">属性を追加</div>
@@ -1230,19 +1216,19 @@
1230
  <div class="dt-context-menu-item" data-action="toggle-visibility">要素を非表示</div>
1231
  <div class="dt-context-menu-item" data-action="force-state">状態を強制</div>
1232
  `;
1233
- document.body.appendChild(contextMenu);
1234
 
1235
- contextMenu.querySelectorAll('.dt-context-menu-item').forEach(item => {
1236
  item.addEventListener('click', (e) => {
1237
  const action = e.target.getAttribute('data-action');
1238
  handleContextMenuAction(action);
1239
- contextMenu.style.display = 'none';
1240
  });
1241
  });
1242
 
1243
  document.addEventListener('click', (e) => {
1244
- if (e.target !== contextMenu && !contextMenu.contains(e.target)) {
1245
- contextMenu.style.display = 'none';
1246
  }
1247
  });
1248
  }
@@ -1478,18 +1464,18 @@
1478
 
1479
  // コンテキストメニューアクション処理
1480
  function handleContextMenuAction(action) {
1481
- if (!selectedElement) return;
1482
 
1483
  switch (action) {
1484
  case 'edit-html':
1485
- if (selectedElement === document.documentElement) {
1486
  alert('ルートHTML要素は直接編集できません');
1487
  return;
1488
  }
1489
- startInlineEdit(selectedDOMNode, selectedElement.outerHTML, (newValue) => {
1490
  try {
1491
- selectedElement.outerHTML = newValue;
1492
- refreshElementsPanel();
1493
  } catch (e) {
1494
  alert('この要素は編集できません: ' + e.message);
1495
  }
@@ -1526,7 +1512,7 @@
1526
  try {
1527
  document.documentElement.innerHTML = textarea.value;
1528
  modal.remove();
1529
- refreshElementsPanel();
1530
  } catch (e) {
1531
  alert('HTMLの解析に失敗しました: ' + e.message);
1532
  }
@@ -1550,48 +1536,48 @@
1550
  const attrName = prompt('属性名を入力');
1551
  if (attrName) {
1552
  const attrValue = prompt('属性値を入力');
1553
- selectedElement.setAttribute(attrName, attrValue || '');
1554
- refreshElementsPanel();
1555
  }
1556
  break;
1557
  case 'edit-element':
1558
- startInlineEdit(selectedDOMNode.querySelector('.dt-dom-tag'), selectedElement.tagName.toLowerCase(), (newValue) => {
1559
  const newElement = document.createElement(newValue);
1560
- Array.from(selectedElement.attributes).forEach(attr => {
1561
  newElement.setAttribute(attr.name, attr.value);
1562
  });
1563
- newElement.innerHTML = selectedElement.innerHTML;
1564
- selectedElement.parentNode.replaceChild(newElement, selectedElement);
1565
- selectedElement = newElement;
1566
- refreshElementsPanel();
1567
  });
1568
  break;
1569
  case 'duplicate':
1570
- const clone = selectedElement.cloneNode(true);
1571
- selectedElement.parentNode.insertBefore(clone, selectedElement.nextSibling);
1572
- refreshElementsPanel();
1573
  break;
1574
  case 'remove':
1575
  if (confirm('要素を削除しますか?')) {
1576
- selectedElement.parentNode.removeChild(selectedElement);
1577
- refreshElementsPanel();
1578
  }
1579
  break;
1580
  case 'toggle-visibility':
1581
- if (selectedElement.style.display === 'none') {
1582
- selectedElement.style.display = '';
1583
  } else {
1584
- selectedElement.style.display = 'none';
1585
  }
1586
- refreshElementsPanel();
1587
  break;
1588
  case 'force-state':
1589
  const state = prompt('強制する状態を入力 (例: hover, active, focus)', 'hover');
1590
  if (state) {
1591
- selectedElement.classList.remove('force-hover', 'force-active', 'force-focus',
1592
  'force-focus-within', 'force-focus-visible', 'force-target');
1593
- selectedElement.classList.add(`force-${state}`);
1594
- refreshElementsPanel();
1595
  }
1596
  break;
1597
  }
@@ -1599,7 +1585,7 @@
1599
 
1600
  // インライン編集関数
1601
  function startInlineEdit(element, initialValue, callback) {
1602
- if (activeEditElement) return;
1603
 
1604
  const originalValue = element.textContent;
1605
  const rect = element.getBoundingClientRect();
@@ -1616,7 +1602,7 @@
1616
  input.focus();
1617
  input.select();
1618
 
1619
- activeEditElement = {
1620
  element: element,
1621
  input: input,
1622
  callback: callback
@@ -1659,7 +1645,7 @@
1659
  function cleanup() {
1660
  document.removeEventListener('click', clickOutsideHandler);
1661
  input.remove();
1662
- activeEditElement = null;
1663
  }
1664
  }
1665
 
@@ -1869,21 +1855,21 @@
1869
  const element = document.createElement('div');
1870
  element.className = 'dt-dom-node';
1871
  element.style.marginLeft = `${depth * 15}px`;
1872
- element.dataset.dtElementId = node.id || Math.random().toString(36).substr(2, 9);
1873
 
1874
  // 右クリックイベント
1875
  element.oncontextmenu = (e) => {
1876
  e.preventDefault();
1877
- selectedElement = node;
1878
- selectedDOMNode = element;
1879
 
1880
  document.querySelectorAll('.dt-dom-node').forEach(el => el.classList.remove('selected'));
1881
  element.classList.add('selected');
1882
 
1883
  if (node !== document.documentElement) {
1884
- contextMenu.style.display = 'block';
1885
- contextMenu.style.left = `${e.pageX}px`;
1886
- contextMenu.style.top = `${e.pageY}px`;
1887
  }
1888
 
1889
  updateCSSPanel(node);
@@ -1894,8 +1880,8 @@
1894
  if (e.target.classList.contains('dt-dom-toggle')) return;
1895
 
1896
  e.stopPropagation();
1897
- selectedElement = node;
1898
- selectedDOMNode = element;
1899
 
1900
  document.querySelectorAll('.dt-dom-node').forEach(el => el.classList.remove('selected'));
1901
  element.classList.add('selected');
@@ -1942,8 +1928,8 @@
1942
  });
1943
  newElement.innerHTML = node.innerHTML;
1944
  node.parentNode.replaceChild(newElement, node);
1945
- selectedElement = newElement;
1946
- refreshElementsPanel();
1947
  });
1948
  };
1949
  }
@@ -1962,7 +1948,7 @@
1962
  e.stopPropagation();
1963
  startInlineEdit(attrSpan, attr.value, (newValue) => {
1964
  node.setAttribute(attr.name, newValue);
1965
- refreshElementsPanel();
1966
  });
1967
  };
1968
  }
@@ -2001,7 +1987,7 @@
2001
  e.stopPropagation();
2002
  startInlineEdit(text, node.textContent.trim(), (newValue) => {
2003
  node.textContent = newValue;
2004
- refreshElementsPanel();
2005
  });
2006
  };
2007
  parentElement.appendChild(text);
@@ -2024,12 +2010,12 @@
2024
  }
2025
 
2026
  updateCSSPanel(element);
2027
- refreshElementsPanel();
2028
  }
2029
  }
2030
 
2031
  // refreshElementsPanel関数の定義
2032
- refreshElementsPanel = function() {
2033
  let tree = document.getElementById('dt-dom-tree');
2034
 
2035
  if (!tree) {
@@ -2050,15 +2036,15 @@
2050
  tree.innerHTML = '';
2051
  buildDOMTree(document.documentElement, tree, 0, true);
2052
 
2053
- if (selectedElement) {
2054
- const elementId = selectedElement.id || Array.from(selectedElement.attributes)
2055
- .find(attr => attr.name.startsWith('data-dt-element-id'))?.value;
2056
 
2057
  if (elementId) {
2058
- const node = document.querySelector(`[data-dt-element-id="${elementId}"]`);
2059
  if (node) {
2060
  node.classList.add('selected');
2061
- updateCSSPanel(selectedElement);
2062
  }
2063
  }
2064
  }
@@ -2069,7 +2055,7 @@
2069
 
2070
  // 初期表示
2071
  setTimeout(() => {
2072
- refreshElementsPanel();
2073
  }, 0);
2074
 
2075
  return panel;
@@ -2113,20 +2099,7 @@
2113
  // メインのツールは遅延読み込み
2114
  setTimeout(() => {
2115
  createDevTools();
2116
- logMessage('開発者ツールが初期化されました', 'dt-console-info');
2117
- logMessage('このコンソールでJavaScriptを実行できます', 'dt-console-info');
2118
  }, 1000);
2119
  });
2120
-
2121
- // ログメッセージ用ヘルパー関数
2122
- function logMessage(message, className) {
2123
- const log = document.getElementById('dt-console-log');
2124
- if (!log) return;
2125
-
2126
- const line = document.createElement('div');
2127
- line.className = className;
2128
- line.innerHTML = message;
2129
- log.appendChild(line);
2130
- log.scrollTop = log.scrollHeight;
2131
- }
2132
  })();
 
1
  (function() {
2
  // グローバル変数宣言
3
+ let dtContextMenu = null;
4
+ let dtSelectedElement = null;
5
+ let dtSelectedDOMNode = null;
6
+ let dtActiveEditElement = null;
7
+ let dtNetworkRequests = [];
8
+ let dtSelectedRequest = null;
9
+ let dtVitalsData = {
10
  CLS: null,
11
  FCP: null,
12
  FID: null,
13
  LCP: null,
14
  TTFB: null
15
  };
16
+ let dtObserver = null;
17
+ let dtRefreshElementsPanel = null;
18
+ let dtVitalsMeasurementStarted = false;
19
 
20
+ // Web Vitalsスクリプトを動的に読み込み
21
+ const loadWebVitals = () => {
22
+ return new Promise((resolve) => {
23
+ if (window.webVitals) {
24
+ resolve();
25
+ return;
26
+ }
27
+
28
+ const webVitalsScript = document.createElement('script');
29
+ webVitalsScript.src = 'https://unpkg.com/[email protected]/dist/web-vitals.iife.js';
30
+ webVitalsScript.onload = resolve;
31
+ document.head.appendChild(webVitalsScript);
32
+ });
33
+ };
34
+
35
+ // DOM変更を監視するMutationObserver (最適化版)
36
+ const setupMutationObserver = () => {
37
+ if (dtObserver) dtObserver.disconnect();
38
+
39
+ dtObserver = new MutationObserver(() => {
40
+ if (dtRefreshElementsPanel) {
41
+ dtRefreshElementsPanel();
42
+ }
43
+ });
44
+
45
+ dtObserver.observe(document.documentElement, {
46
+ childList: true,
47
+ subtree: true,
48
+ attributes: false,
49
+ characterData: false
50
+ });
51
+ };
52
+
53
+ // スタイルの追加 (すべてのクラスにdt-プレフィックス)
54
  const style = document.createElement('style');
55
  style.textContent = `
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  .dt-devtools-container {
57
  position: fixed;
58
  bottom: 0;
59
  left: 0;
60
  width: 100%;
61
  height: 300px;
62
+ background-color: var(--dt-bg-color);
63
  border-top: 1px solid var(--dt-border-color);
64
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
65
  z-index: 9999;
 
566
  .dt-json-null {
567
  color: var(--dt-json-null);
568
  }
569
+
570
+ /* CSS変数定義 */
571
+ :root {
572
+ --dt-bg-color: #1e1e1e;
573
+ --dt-panel-bg: #252526;
574
+ --dt-border-color: #3c3c3c;
575
+ --dt-text-color: #e0e0e0;
576
+ --dt-text-muted: #a0a0a0;
577
+ --dt-primary-color: #007acc;
578
+ --dt-primary-hover: #3e9fda;
579
+ --dt-success-color: #4caf50;
580
+ --dt-error-color: #f44336;
581
+ --dt-warning-color: #ff9800;
582
+ --dt-info-color: #2196f3;
583
+ --dt-highlight-bg: rgba(0, 122, 204, 0.2);
584
+ --dt-tab-bg: #2d2d2d;
585
+ --dt-tab-active-bg: #1e1e1e;
586
+ --dt-console-log-color: #e0e0e0;
587
+ --dt-console-error-color: #f44336;
588
+ --dt-console-warn-color: #ff9800;
589
+ --dt-console-info-color: #4fc3f7;
590
+ --dt-json-key: #9cdcfe;
591
+ --dt-json-string: #ce9178;
592
+ --dt-json-number: #b5cea8;
593
+ --dt-json-boolean: #569cd6;
594
+ --dt-json-null: #569cd6;
595
+ --dt-dom-tag: #569cd6;
596
+ --dt-dom-attr: #9cdcfe;
597
+ --dt-dom-text: #d4d4d4;
598
+ }
599
  `;
600
  document.head.appendChild(style);
601
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  // 開発者ツールのメイン関数
603
  const createDevTools = () => {
604
  const container = document.createElement('div');
 
678
  elementsTab.click();
679
  };
680
 
681
+ // Web Vitalsパネル作成 (測定開始ボタンが押されるまで何もしない)
682
  function createVitalsPanel() {
683
  const panel = document.createElement('div');
684
  panel.className = 'dt-devtools-panel';
 
766
  container.appendChild(ttfbCard);
767
 
768
  function startVitalsMeasurement() {
769
+ if (dtVitalsMeasurementStarted) return;
770
+ dtVitalsMeasurementStarted = true;
771
 
772
  loadWebVitals().then(() => {
773
  if (window.webVitals) {
 
786
  vitals.onLCP(updateLCP);
787
  vitals.onTTFB(updateTTFB);
788
  }
789
+ startButton.textContent = '測定中...';
790
+ startButton.disabled = true;
791
  } catch (e) {
792
+ console.error('Web Vitals error:', e);
793
  }
794
  }
795
  });
796
  }
797
 
798
  function updateCLS(metric) {
799
+ dtVitalsData.CLS = metric.value;
800
  updateVitalDisplay('cls', metric.value, 0.1, 0.25);
801
  }
802
 
803
  function updateFCP(metric) {
804
+ dtVitalsData.FCP = metric.value;
805
  updateVitalDisplay('fcp', metric.value, 1800, 3000);
806
  }
807
 
808
  function updateFID(metric) {
809
+ dtVitalsData.FID = metric.value;
810
  updateVitalDisplay('fid', metric.value, 100, 300);
811
  }
812
 
813
  function updateLCP(metric) {
814
+ dtVitalsData.LCP = metric.value;
815
  updateVitalDisplay('lcp', metric.value, 2500, 4000);
816
  }
817
 
818
  function updateTTFB(metric) {
819
+ dtVitalsData.TTFB = metric.value;
820
  updateVitalDisplay('ttfb', metric.value, 800, 1800);
821
  }
822
 
 
904
  error: null
905
  };
906
 
907
+ dtNetworkRequests.push(requestData);
908
  renderNetworkRequests();
909
 
910
  try {
 
981
  error: null
982
  };
983
 
984
+ dtNetworkRequests.push(requestData);
985
  renderNetworkRequests();
986
 
987
  this.addEventListener('load', function() {
 
1038
 
1039
  requestsList.innerHTML = '';
1040
 
1041
+ dtNetworkRequests.forEach(request => {
1042
  const requestElement = document.createElement('div');
1043
  requestElement.className = 'dt-network-request';
1044
+ if (dtSelectedRequest && dtSelectedRequest.id === request.id) {
1045
  requestElement.classList.add('selected');
1046
  }
1047
 
1048
  requestElement.onclick = () => {
1049
+ dtSelectedRequest = request;
1050
  renderNetworkRequests();
1051
  renderNetworkDetails();
1052
  };
 
1079
  // ネットワークリクエストの詳細を表示
1080
  function renderNetworkDetails() {
1081
  const panel = document.getElementById('network-panel');
1082
+ if (!panel || !dtSelectedRequest) return;
1083
 
1084
  const detailsPanel = panel.querySelector('.dt-network-details');
1085
  detailsPanel.innerHTML = '';
 
1090
  generalSection.innerHTML = `
1091
  <div class="dt-network-detail-title">一般</div>
1092
  <div class="dt-network-detail-content">
1093
+ <strong>URL:</strong> ${dtSelectedRequest.url}<br>
1094
+ <strong>メソッド:</strong> ${dtSelectedRequest.method}<br>
1095
+ <strong>ステータス:</strong> ${dtSelectedRequest.response ? dtSelectedRequest.response.status : '-'}<br>
1096
+ <strong>時間:</strong> ${dtSelectedRequest.duration ? Math.round(dtSelectedRequest.duration) + 'ms' : '-'}
1097
  </div>
1098
  `;
1099
  detailsPanel.appendChild(generalSection);
1100
 
1101
  // リクエストヘッダー
1102
+ if (dtSelectedRequest.requestHeaders) {
1103
  const headersSection = document.createElement('div');
1104
  headersSection.className = 'dt-network-detail-section';
1105
 
 
1110
  const headersContent = document.createElement('div');
1111
  headersContent.className = 'dt-network-detail-content';
1112
 
1113
+ if (typeof dtSelectedRequest.requestHeaders === 'object' && !(dtSelectedRequest.requestHeaders instanceof Headers)) {
1114
+ Object.entries(dtSelectedRequest.requestHeaders).forEach(([key, value]) => {
1115
  headersContent.innerHTML += `<strong>${key}:</strong> ${value}<br>`;
1116
  });
1117
+ } else if (dtSelectedRequest.requestHeaders instanceof Headers) {
1118
+ dtSelectedRequest.requestHeaders.forEach((value, key) => {
1119
  headersContent.innerHTML += `<strong>${key}:</strong> ${value}<br>`;
1120
  });
1121
  }
 
1126
  }
1127
 
1128
  // リクエストボディ
1129
+ if (dtSelectedRequest.requestBody) {
1130
  const bodySection = document.createElement('div');
1131
  bodySection.className = 'dt-network-detail-section';
1132
 
 
1138
  bodyContent.className = 'dt-network-detail-content';
1139
 
1140
  try {
1141
+ if (typeof dtSelectedRequest.requestBody === 'string') {
1142
+ bodyContent.textContent = dtSelectedRequest.requestBody;
1143
+ } else if (typeof dtSelectedRequest.requestBody === 'object') {
1144
+ bodyContent.textContent = JSON.stringify(dtSelectedRequest.requestBody, null, 2);
1145
  }
1146
  } catch (e) {
1147
  bodyContent.textContent = 'ボディを表示できません';
 
1153
  }
1154
 
1155
  // レスポンス
1156
+ if (dtSelectedRequest.response) {
1157
  const responseSection = document.createElement('div');
1158
  responseSection.className = 'dt-network-detail-section';
1159
 
 
1164
  const responseContent = document.createElement('div');
1165
  responseContent.className = 'dt-network-detail-content';
1166
 
1167
+ if (dtSelectedRequest.response.body) {
1168
+ if (typeof dtSelectedRequest.response.body === 'object') {
1169
+ responseContent.textContent = JSON.stringify(dtSelectedRequest.response.body, null, 2);
1170
  } else {
1171
+ responseContent.textContent = dtSelectedRequest.response.body;
1172
  }
1173
  } else {
1174
  responseContent.textContent = 'レスポンスボディがありません';
 
1180
  }
1181
 
1182
  // エラー
1183
+ if (dtSelectedRequest.error) {
1184
  const errorSection = document.createElement('div');
1185
  errorSection.className = 'dt-network-detail-section';
1186
 
 
1190
 
1191
  const errorContent = document.createElement('div');
1192
  errorContent.className = 'dt-network-detail-content';
1193
+ errorContent.textContent = `${dtSelectedRequest.error.name}: ${dtSelectedRequest.error.message}`;
1194
 
1195
+ if (dtSelectedRequest.error.stack) {
1196
+ errorContent.innerHTML += `<br><br>${dtSelectedRequest.error.stack.replace(/\n/g, '<br>')}`;
1197
  }
1198
 
1199
  errorSection.appendChild(errorTitle);
 
1204
 
1205
  // コンテキストメニュー作成
1206
  function createContextMenu() {
1207
+ dtContextMenu = document.createElement('div');
1208
+ dtContextMenu.className = 'dt-context-menu';
1209
+ dtContextMenu.innerHTML = `
1210
  <div class="dt-context-menu-item" data-action="edit-html">HTMLとして編集</div>
1211
  <div class="dt-context-menu-item" data-action="edit-whole-html">HTML全体を編集</div>
1212
  <div class="dt-context-menu-item" data-action="add-attribute">属性を追加</div>
 
1216
  <div class="dt-context-menu-item" data-action="toggle-visibility">要素を非表示</div>
1217
  <div class="dt-context-menu-item" data-action="force-state">状態を強制</div>
1218
  `;
1219
+ document.body.appendChild(dtContextMenu);
1220
 
1221
+ dtContextMenu.querySelectorAll('.dt-context-menu-item').forEach(item => {
1222
  item.addEventListener('click', (e) => {
1223
  const action = e.target.getAttribute('data-action');
1224
  handleContextMenuAction(action);
1225
+ dtContextMenu.style.display = 'none';
1226
  });
1227
  });
1228
 
1229
  document.addEventListener('click', (e) => {
1230
+ if (e.target !== dtContextMenu && !dtContextMenu.contains(e.target)) {
1231
+ dtContextMenu.style.display = 'none';
1232
  }
1233
  });
1234
  }
 
1464
 
1465
  // コンテキストメニューアクション処理
1466
  function handleContextMenuAction(action) {
1467
+ if (!dtSelectedElement) return;
1468
 
1469
  switch (action) {
1470
  case 'edit-html':
1471
+ if (dtSelectedElement === document.documentElement) {
1472
  alert('ルートHTML要素は直接編集できません');
1473
  return;
1474
  }
1475
+ startInlineEdit(dtSelectedDOMNode, dtSelectedElement.outerHTML, (newValue) => {
1476
  try {
1477
+ dtSelectedElement.outerHTML = newValue;
1478
+ dtRefreshElementsPanel();
1479
  } catch (e) {
1480
  alert('この要素は編集できません: ' + e.message);
1481
  }
 
1512
  try {
1513
  document.documentElement.innerHTML = textarea.value;
1514
  modal.remove();
1515
+ dtRefreshElementsPanel();
1516
  } catch (e) {
1517
  alert('HTMLの解析に失敗しました: ' + e.message);
1518
  }
 
1536
  const attrName = prompt('属性名を入力');
1537
  if (attrName) {
1538
  const attrValue = prompt('属性値を入力');
1539
+ dtSelectedElement.setAttribute(attrName, attrValue || '');
1540
+ dtRefreshElementsPanel();
1541
  }
1542
  break;
1543
  case 'edit-element':
1544
+ startInlineEdit(dtSelectedDOMNode.querySelector('.dt-dom-tag'), dtSelectedElement.tagName.toLowerCase(), (newValue) => {
1545
  const newElement = document.createElement(newValue);
1546
+ Array.from(dtSelectedElement.attributes).forEach(attr => {
1547
  newElement.setAttribute(attr.name, attr.value);
1548
  });
1549
+ newElement.innerHTML = dtSelectedElement.innerHTML;
1550
+ dtSelectedElement.parentNode.replaceChild(newElement, dtSelectedElement);
1551
+ dtSelectedElement = newElement;
1552
+ dtRefreshElementsPanel();
1553
  });
1554
  break;
1555
  case 'duplicate':
1556
+ const clone = dtSelectedElement.cloneNode(true);
1557
+ dtSelectedElement.parentNode.insertBefore(clone, dtSelectedElement.nextSibling);
1558
+ dtRefreshElementsPanel();
1559
  break;
1560
  case 'remove':
1561
  if (confirm('要素を削除しますか?')) {
1562
+ dtSelectedElement.parentNode.removeChild(dtSelectedElement);
1563
+ dtRefreshElementsPanel();
1564
  }
1565
  break;
1566
  case 'toggle-visibility':
1567
+ if (dtSelectedElement.style.display === 'none') {
1568
+ dtSelectedElement.style.display = '';
1569
  } else {
1570
+ dtSelectedElement.style.display = 'none';
1571
  }
1572
+ dtRefreshElementsPanel();
1573
  break;
1574
  case 'force-state':
1575
  const state = prompt('強制する状態を入力 (例: hover, active, focus)', 'hover');
1576
  if (state) {
1577
+ dtSelectedElement.classList.remove('force-hover', 'force-active', 'force-focus',
1578
  'force-focus-within', 'force-focus-visible', 'force-target');
1579
+ dtSelectedElement.classList.add(`force-${state}`);
1580
+ dtRefreshElementsPanel();
1581
  }
1582
  break;
1583
  }
 
1585
 
1586
  // インライン編集関数
1587
  function startInlineEdit(element, initialValue, callback) {
1588
+ if (dtActiveEditElement) return;
1589
 
1590
  const originalValue = element.textContent;
1591
  const rect = element.getBoundingClientRect();
 
1602
  input.focus();
1603
  input.select();
1604
 
1605
+ dtActiveEditElement = {
1606
  element: element,
1607
  input: input,
1608
  callback: callback
 
1645
  function cleanup() {
1646
  document.removeEventListener('click', clickOutsideHandler);
1647
  input.remove();
1648
+ dtActiveEditElement = null;
1649
  }
1650
  }
1651
 
 
1855
  const element = document.createElement('div');
1856
  element.className = 'dt-dom-node';
1857
  element.style.marginLeft = `${depth * 15}px`;
1858
+ element.dataset.elementId = node.id || Math.random().toString(36).substr(2, 9);
1859
 
1860
  // 右クリックイベント
1861
  element.oncontextmenu = (e) => {
1862
  e.preventDefault();
1863
+ dtSelectedElement = node;
1864
+ dtSelectedDOMNode = element;
1865
 
1866
  document.querySelectorAll('.dt-dom-node').forEach(el => el.classList.remove('selected'));
1867
  element.classList.add('selected');
1868
 
1869
  if (node !== document.documentElement) {
1870
+ dtContextMenu.style.display = 'block';
1871
+ dtContextMenu.style.left = `${e.pageX}px`;
1872
+ dtContextMenu.style.top = `${e.pageY}px`;
1873
  }
1874
 
1875
  updateCSSPanel(node);
 
1880
  if (e.target.classList.contains('dt-dom-toggle')) return;
1881
 
1882
  e.stopPropagation();
1883
+ dtSelectedElement = node;
1884
+ dtSelectedDOMNode = element;
1885
 
1886
  document.querySelectorAll('.dt-dom-node').forEach(el => el.classList.remove('selected'));
1887
  element.classList.add('selected');
 
1928
  });
1929
  newElement.innerHTML = node.innerHTML;
1930
  node.parentNode.replaceChild(newElement, node);
1931
+ dtSelectedElement = newElement;
1932
+ dtRefreshElementsPanel();
1933
  });
1934
  };
1935
  }
 
1948
  e.stopPropagation();
1949
  startInlineEdit(attrSpan, attr.value, (newValue) => {
1950
  node.setAttribute(attr.name, newValue);
1951
+ dtRefreshElementsPanel();
1952
  });
1953
  };
1954
  }
 
1987
  e.stopPropagation();
1988
  startInlineEdit(text, node.textContent.trim(), (newValue) => {
1989
  node.textContent = newValue;
1990
+ dtRefreshElementsPanel();
1991
  });
1992
  };
1993
  parentElement.appendChild(text);
 
2010
  }
2011
 
2012
  updateCSSPanel(element);
2013
+ dtRefreshElementsPanel();
2014
  }
2015
  }
2016
 
2017
  // refreshElementsPanel関数の定義
2018
+ dtRefreshElementsPanel = function() {
2019
  let tree = document.getElementById('dt-dom-tree');
2020
 
2021
  if (!tree) {
 
2036
  tree.innerHTML = '';
2037
  buildDOMTree(document.documentElement, tree, 0, true);
2038
 
2039
+ if (dtSelectedElement) {
2040
+ const elementId = dtSelectedElement.id || Array.from(dtSelectedElement.attributes)
2041
+ .find(attr => attr.name.startsWith('data-element-id'))?.value;
2042
 
2043
  if (elementId) {
2044
+ const node = document.querySelector(`[data-element-id="${elementId}"]`);
2045
  if (node) {
2046
  node.classList.add('selected');
2047
+ updateCSSPanel(dtSelectedElement);
2048
  }
2049
  }
2050
  }
 
2055
 
2056
  // 初期表示
2057
  setTimeout(() => {
2058
+ dtRefreshElementsPanel();
2059
  }, 0);
2060
 
2061
  return panel;
 
2099
  // メインのツールは遅延読み込み
2100
  setTimeout(() => {
2101
  createDevTools();
2102
+ console.log('開発者ツールが初期化されました');
 
2103
  }, 1000);
2104
  });
 
 
 
 
 
 
 
 
 
 
 
 
2105
  })();