PageSourceSearch

https://uwsc.jp/wp-content/themes/codium-extend-child/js/features/uwsc-syntax-check.js

js uwsc.jp collected 2026-09-24 12:35:22 UTC 4,417 bytes, 142 lines download raw bytes

1const CHECK_INTERVAL_MS = 5500;
2const queue = [];
3let processing = false;
4
5export function queueUwscSyntaxCheck(pre) {
6  if (!pre || pre.dataset.uwscCheckQueued === "1") return;
7
8  const root = pre.closest('.highlightjs');
9  const statusEl = root && root.querySelector('.syntax-check-status');
10  if (!statusEl) return;
11
12  // ★サーバー側で既にキャッシュ済みならAJAXせず即反映
13  const cached = statusEl.dataset.checkResult;
14  if (cached) {
15    try {
16      applyStatus(statusEl, JSON.parse(cached));
17      return;
18    } catch (e) {
19      // パース失敗時はフォールバックしてAJAXへ
20    }
21  }
22
23  pre.dataset.uwscCheckQueued = "1";
24  queue.push({ pre, statusEl });
25  processQueue();
26}
27
28async function processQueue() {
29  if (processing) return;
30  processing = true;
31
32  while (queue.length > 0) {
33    const { pre, statusEl } = queue.shift();
34    await runCheck(pre, statusEl);
35    if (queue.length > 0) {
36      await sleep(CHECK_INTERVAL_MS);
37    }
38  }
39
40  processing = false;
41}
42
43function sleep(ms) {
44  return new Promise(resolve => setTimeout(resolve, ms));
45}
46
47async function runCheck(pre, statusEl) {
48  const root = pre.closest('.highlightjs');
49  if (!root) return;
50
51  const textarea = root.querySelector('textarea');
52  const code = textarea ? textarea.value : pre.textContent;
53  if (!code || !code.trim()) return;
54
55  const ajaxUrl = window.SyntaxAnalyzerData && window.SyntaxAnalyzerData.ajaxUrl;
56  if (!ajaxUrl) {
57    console.warn('SyntaxAnalyzerData.ajaxUrl が見つかりません');
58    return;
59  }
60
61  try {
62    const body = new URLSearchParams();
63    body.set('action', 'syntax_analyze');
64    body.set('code', code);
65
66    const res = await fetch(ajaxUrl, {
67      method: 'POST',
68      credentials: 'same-origin',
69      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
70      body: body.toString()
71    });
72
73    const data = await res.json();
74
75    // レート制限メッセージ(文字列のみのerror)は静かにスキップ
76    if (data && data.error && typeof data.error === 'string') {
77      console.warn('UWSC構文チェック: ', data.error);
78      statusEl.classList.remove('is-pending');
79      return;
80    }
81
82    applyStatus(statusEl, data);
83
84  } catch (e) {
85    console.error('UWSC構文チェック失敗:', e);
86    statusEl.classList.remove('is-pending');
87  }
88}
89
90function applyStatus(statusEl, data) {
91  const hasError = !!(data && data.error);
92  const hasWarnings = !!(data && Array.isArray(data.warnings) && data.warnings.length > 0);
93  const status = hasError ? 'error' : (hasWarnings ? 'warning' : 'ok');
94
95  const spinner = statusEl.querySelector('.syntax-check-spinner');
96  const icon = statusEl.querySelector('.syntax-check-icon');
97
98  statusEl.classList.remove('is-pending', 'is-error', 'is-warning', 'is-ok');
99  statusEl.classList.add('is-' + status);
100
101  if (spinner) spinner.style.display = 'none';
102  if (icon) icon.style.display = (status === 'error' || status === 'warning') ? 'inline-block' : 'none';
103
104  if (status === 'ok') return;
105
106  statusEl.style.cursor = 'pointer';
107  statusEl.addEventListener('click', () => {
108    const root = statusEl.closest('.highlightjs');
109    const existing = root.querySelector('.syntax-check-detail');
110    if (existing) { existing.remove(); return; }
111    showSyntaxDetail(root, data);
112  });
113}
114
115function showSyntaxDetail(root, data) {
116  const panel = document.createElement('div');
117  panel.className = 'syntax-check-detail';
118
119  const lines = [];
120  if (data.error) {
121    const loc = (data.error.line != null)
122      ? `(${data.error.line}行目${data.error.col != null ? ' ' + data.error.col + '列目' : ''})`
123      : '';
124    lines.push(`<div class="syntax-check-error">エラー${loc}: ${escapeHtml(data.error.message || '')}</div>`);
125  }
126  if (Array.isArray(data.warnings)) {
127    data.warnings.forEach(w => {
128      const msg = typeof w === 'string' ? w : (w.message || JSON.stringify(w));
129      const loc = (w && w.line != null) ? `(${w.line}行目)` : '';
130      lines.push(`<div class="syntax-check-warning">警告${loc}: ${escapeHtml(msg)}</div>`);
131    });
132  }
133
134  panel.innerHTML = lines.join('');
135  root.appendChild(panel);
136}
137
138function escapeHtml(str) {
139  const div = document.createElement('div');
140  div.textContent = str;
141  return div.innerHTML;
142}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.