Skip to content

Commit 0b569bf

Browse files
authored
Merge pull request #8 from ut-code/feature/sidepanel
判定処理の追加
2 parents e9868c4 + ca88fd1 commit 0b569bf

File tree

3 files changed

+88
-6
lines changed

3 files changed

+88
-6
lines changed

public/manifest.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,12 @@
1313
"default_path": "src/sidepanel/index.html"
1414
},
1515
"permissions": ["sidePanel", "tabs"],
16-
"host_permissions": ["<all_urls>"]
16+
"host_permissions": ["<all_urls>"],
17+
"content_scripts": [
18+
{
19+
"matches": ["<all_urls>"],
20+
"js": ["content.js"],
21+
"run_at": "document_idle"
22+
}
23+
]
1724
}

src/content.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
export {}
2+
3+
type CheckResult = { ok: boolean; details: string }
4+
// 判定する関数
5+
function checkAnswer(): CheckResult {
6+
const el = document.querySelector<HTMLElement>('[data-check]')
7+
if (!el) return { ok: false, details: 'data-check 要素が見つかりません' }
8+
const expected = el.getAttribute('data-check') ?? ''
9+
const actual = (el.textContent ?? '').trim()
10+
return actual === expected
11+
? { ok: true, details: `一致: "${actual}"` }
12+
: { ok: false, details: `不一致: expected="${expected}", actual="${actual}"` }
13+
}
14+
15+
// デバウンス
16+
const debounce = <T extends (...a: any[]) => void>(fn: T, delay: number) => {
17+
let t: number | undefined // タイマーの識別子
18+
return (...args: Parameters<T>) => {
19+
if (t) clearTimeout(t)
20+
// @ts-ignore
21+
t = setTimeout(() => fn(...args), delay) as unknown as number
22+
}
23+
}
24+
25+
const ports = new Set<chrome.runtime.Port>()
26+
27+
// サイドパネルからの Port 接続のみ対応
28+
chrome.runtime.onConnect.addListener((port) => {
29+
if (port.name !== 'sidepanel') return
30+
ports.add(port)
31+
// 接続直後に最新判定を送る
32+
port.postMessage({ type: 'UPDATE', result: checkAnswer() })
33+
port.onDisconnect.addListener(() => ports.delete(port))
34+
})
35+
// DevTools等でのDOM変更を監視し、判定結果をサイドパネルへ通知
36+
const notify = debounce(() => {
37+
const result = checkAnswer()
38+
for (const p of ports) {
39+
try { p.postMessage({ type: 'UPDATE', result }) } catch {}
40+
}
41+
}, 200) // 200ms ディレイ
42+
43+
const mo = new MutationObserver(() => notify())
44+
mo.observe(document.documentElement, {
45+
childList: true,
46+
subtree: true,
47+
characterData: true,
48+
attributes: true,
49+
})

src/sidepanel/App.tsx

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,42 @@
1-
import { useState } from 'react'
1+
import { useEffect, useState } from 'react'
22
import './App.css'
33

4+
type CheckResult = { ok: boolean; details: string }
5+
46
function App() {
5-
const [text, setText] = useState('こんにちは、サイドパネル!')
7+
const [result, setResult] = useState<CheckResult | null>(null)
8+
const [error, setError] = useState<string | null>(null)
9+
10+
useEffect(() => {
11+
let disconnected = false
12+
;(async () => {
13+
try {
14+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true })
15+
if (!tab?.id) return
16+
const port = chrome.tabs.connect(tab.id, { name: 'sidepanel' })
17+
port.onMessage.addListener((msg) => {
18+
if (disconnected) return
19+
if (msg?.type === 'UPDATE' && msg.result) setResult(msg.result as CheckResult)
20+
})
21+
return () => {
22+
disconnected = true
23+
try { port.disconnect() } catch {}
24+
}
25+
} catch (e: any) {
26+
setError(e?.message ?? String(e))
27+
}
28+
})()
29+
}, [])
630

731
return (
832
<div className="container">
9-
<h2>{text}</h2>
10-
<button onClick={() => setText('更新されました!')}>更新</button>
33+
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
34+
{result
35+
? (<><div>判定: {result.ok ? '正解' : '不正解'}</div><div>詳細: {result.details}</div></>)
36+
: (<div>判定の更新を待機中…</div>)
37+
}
1138
</div>
1239
)
1340
}
1441

15-
1642
export default App

0 commit comments

Comments
 (0)