From 51654504d5e48f1b8ca49043f2c20ebe2117c2b1 Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 13:24:11 +0900 Subject: [PATCH 1/8] =?UTF-8?q?=E5=BF=9C=E7=94=A8=E8=AA=B2=E9=A1=8C?= =?UTF-8?q?=E3=82=92=E6=9B=B8=E3=81=8F=20extra.js=20=E3=82=92=E9=85=8D?= =?UTF-8?q?=E5=B8=83=E3=82=B3=E3=83=BC=E3=83=89=E3=81=AB=E8=B6=B3=E3=81=97?= =?UTF-8?q?=E3=80=81script.js=20=E3=81=AE=E3=81=82=E3=81=A8=E3=81=AB?= =?UTF-8?q?=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=82=93=E3=81=A7=20livereload=20?= =?UTF-8?q?=E3=81=AE=E5=AF=BE=E8=B1=A1=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/completed/public/extra.js | 18 ++++++++++++++++++ .../board-advanced/completed/public/index.html | 1 + .../completed/public/livereload.js | 2 +- 2026/board-advanced/completed/public/script.js | 14 -------------- 2026/board-advanced/template/public/extra.js | 4 ++++ 2026/board-advanced/template/public/index.html | 1 + .../template/public/livereload.js | 2 +- 7 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 2026/board-advanced/completed/public/extra.js create mode 100644 2026/board-advanced/template/public/extra.js diff --git a/2026/board-advanced/completed/public/extra.js b/2026/board-advanced/completed/public/extra.js new file mode 100644 index 00000000..94998966 --- /dev/null +++ b/2026/board-advanced/completed/public/extra.js @@ -0,0 +1,18 @@ +// 応用課題はこのファイルに書く。 +// +// script.js のあとに読み込むので、script.js の source や posts をそのまま使える。 +// このファイルが途中で止まっても、script.js は動き続ける。 + +const status = document.getElementById('status'); + +function showOnline() { + status.textContent = 'つながっています'; + status.className = 'status online'; +} +function showOffline() { + status.textContent = '切れています'; + status.className = 'status offline'; +} + +source.addEventListener('open', showOnline); +source.addEventListener('error', showOffline); diff --git a/2026/board-advanced/completed/public/index.html b/2026/board-advanced/completed/public/index.html index 9b2502c1..3cba6187 100644 --- a/2026/board-advanced/completed/public/index.html +++ b/2026/board-advanced/completed/public/index.html @@ -26,6 +26,7 @@

みんなの投稿

+ diff --git a/2026/board-advanced/completed/public/livereload.js b/2026/board-advanced/completed/public/livereload.js index 36b2bc3e..1d5c497b 100644 --- a/2026/board-advanced/completed/public/livereload.js +++ b/2026/board-advanced/completed/public/livereload.js @@ -11,7 +11,7 @@ // 再読み込みで名前欄が空になると、共有サーバーが投稿を 400 で弾く。 // 名前欄の中身を sessionStorage に取っておき、読み込んだときに戻す。 -const TARGETS = ['/index.html', '/styles.css', '/script.js']; +const TARGETS = ['/index.html', '/styles.css', '/script.js', '/extra.js']; const INTERVAL = 500; const QUIET = 1000; diff --git a/2026/board-advanced/completed/public/script.js b/2026/board-advanced/completed/public/script.js index d736075e..b2c92058 100644 --- a/2026/board-advanced/completed/public/script.js +++ b/2026/board-advanced/completed/public/script.js @@ -62,17 +62,3 @@ function receivePost(e) { showPosts(); } source.onmessage = receivePost; - -const status = document.getElementById('status'); - -function showOnline() { - status.textContent = 'つながっています'; - status.className = 'status online'; -} -function showOffline() { - status.textContent = '切れています'; - status.className = 'status offline'; -} - -source.addEventListener('open', showOnline); -source.addEventListener('error', showOffline); diff --git a/2026/board-advanced/template/public/extra.js b/2026/board-advanced/template/public/extra.js new file mode 100644 index 00000000..9e426c73 --- /dev/null +++ b/2026/board-advanced/template/public/extra.js @@ -0,0 +1,4 @@ +// 応用課題はこのファイルに書く。 +// +// script.js のあとに読み込むので、script.js の source や posts をそのまま使える。 +// このファイルが途中で止まっても、script.js は動き続ける。 diff --git a/2026/board-advanced/template/public/index.html b/2026/board-advanced/template/public/index.html index d7823331..4bb60442 100644 --- a/2026/board-advanced/template/public/index.html +++ b/2026/board-advanced/template/public/index.html @@ -26,6 +26,7 @@

みんなの投稿

+ diff --git a/2026/board-advanced/template/public/livereload.js b/2026/board-advanced/template/public/livereload.js index 36b2bc3e..1d5c497b 100644 --- a/2026/board-advanced/template/public/livereload.js +++ b/2026/board-advanced/template/public/livereload.js @@ -11,7 +11,7 @@ // 再読み込みで名前欄が空になると、共有サーバーが投稿を 400 で弾く。 // 名前欄の中身を sessionStorage に取っておき、読み込んだときに戻す。 -const TARGETS = ['/index.html', '/styles.css', '/script.js']; +const TARGETS = ['/index.html', '/styles.css', '/script.js', '/extra.js']; const INTERVAL = 500; const QUIET = 1000; From 23a925d188e610854e0b30ce765892e7b77466a0 Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 13:24:34 +0900 Subject: [PATCH 2/8] =?UTF-8?q?=E3=83=97=E3=83=AD=E3=82=B8=E3=82=A7?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=81=AE=E4=B8=AD=E8=BA=AB=E3=81=A8=E6=BC=94?= =?UTF-8?q?=E7=BF=92=E3=81=AE=E9=80=B2=E3=82=81=E6=96=B9=E3=81=A7=E3=80=81?= =?UTF-8?q?=E5=BF=9C=E7=94=A8=E8=AA=B2=E9=A1=8C=E3=81=AF=20extra.js=20?= =?UTF-8?q?=E3=81=AB=E6=9B=B8=E3=81=8D=E3=80=81=E9=80=94=E4=B8=AD=E3=81=A7?= =?UTF-8?q?=E3=82=82=E6=AC=A1=E3=81=AE=E7=AB=A0=E3=81=AB=E6=88=BB=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E3=82=88=E3=81=84=E3=81=A8=E6=A1=88=E5=86=85=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index e62e8911..f72ee72c 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -479,6 +479,7 @@ const posts = []; // 色なし: すでにある行。この下に ``` 早く終わった人は、章の最後のスライドの左上にあるリンクから応用課題に進んでください。 +書きかけのコードがエラーで止まっても本編はそのまま動くので、次の章が始まったら途中のまま戻ってきて大丈夫です。 --- @@ -553,6 +554,7 @@ public/ index.html styles.css script.js ← ブラウザで動く JavaScript + extra.js ← 応用課題を書く package.json server.js ← サーバーで動く JavaScript ``` From dbb3ead231fb610d2ab77ea299c8c9cdef3bc789 Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 13:24:54 +0900 Subject: [PATCH 3/8] =?UTF-8?q?2=E7=AB=A0=E3=83=BB4=E7=AB=A0=E3=83=BB5?= =?UTF-8?q?=E7=AB=A0=E3=81=AE=E5=BF=9C=E7=94=A8=E8=AA=B2=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E3=80=81extra.js=20=E3=81=AB=E6=9B=B8=E3=81=8F=E7=AD=94?= =?UTF-8?q?=E3=81=88=E3=81=AB=E7=9B=B4=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index f72ee72c..a31eb7a6 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -2026,6 +2026,9 @@ data: {"name":"たろう","text":"やっほー"} 章が早く終わった人向けの課題です。終わったら左上のリンクで、来た章に戻ります。 {#advanced} +コードは `public/extra.js` に書きます。`script.js` の `source` や `posts` は、そのまま使えます。 +`script.js` にある名前で変数や関数を作ると `extra.js` 全体が動かなくなるので、別の名前を付けてください。 + --- @@ -2039,7 +2042,7 @@ data: {"name":"たろう","text":"やっほー"} - タイトルは `document.title` で読み書きできる - いま見られていないかは `document.hidden` で分かる - 見られる状態に戻ったことは `visibilitychange` イベントで分かる -- 3章で `onmessage` を置き換えるので、`source.addEventListener('message', 関数)` で書くと消えずに残る +- `source.onmessage` に代入すると本編の関数が外れるので、`source.addEventListener('message', 関数)` で登録する --- @@ -2049,8 +2052,7 @@ data: {"name":"たろう","text":"やっほー"} [← 2章のふりかえりに戻る](#ch2-end) {.jump} -```javascript {data-file=public/script.js} -source.onmessage = showPosts; // すでにある行 +```javascript {data-file=public/extra.js} @@const title = document.title;@@ @@let unread = 0;@@ @@function countUnread() {@@ @@ -2194,8 +2196,7 @@ source.onmessage = receivePost; // すでにある行 [← 4章のふりかえりに戻る](#ch4-end) {.jump} -```javascript {data-file=public/script.js} -source.onmessage = receivePost; // すでにある行 +```javascript {data-file=public/extra.js} @@const status = document.getElementById('status');@@ @@function showOnline() {@@ @@ status.textContent = 'つながっています';@@ @@ -2307,8 +2308,7 @@ const connections = []; // すでにある行 @@ @@ ``` -```javascript {data-file=public/script.js} -source.onmessage = receivePost; // すでにある行 +```javascript {data-file=public/extra.js} @@function showCount(e) {@@ @@ document.getElementById('count').textContent = `${e.data}人が接続中`;@@ @@}@@ From 7bb4c4f0711dffc4b37fe056b3c0b08dabf69d74 Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 13:26:25 +0900 Subject: [PATCH 4/8] =?UTF-8?q?3=E7=AB=A0=E3=81=AE=E5=BF=9C=E7=94=A8?= =?UTF-8?q?=E8=AA=B2=E9=A1=8C=E3=82=92=E3=80=81=E6=9C=AC=E7=B7=A8=E3=82=92?= =?UTF-8?q?=E6=9B=B8=E3=81=8D=E6=8F=9B=E3=81=88=E3=82=8B2=E3=81=A4?= =?UTF-8?q?=E3=81=8B=E3=82=89=E3=80=81extra.js=20=E3=81=AB=E8=B6=B3?= =?UTF-8?q?=E3=81=99=E3=81=A0=E3=81=91=E3=81=A7=E6=9B=B8=E3=81=91=E3=82=8B?= =?UTF-8?q?=E5=90=8D=E5=89=8D=E3=82=92=E5=91=BC=E3=81=B0=E3=82=8C=E3=81=9F?= =?UTF-8?q?=E3=82=89=E7=9F=A5=E3=82=89=E3=81=9B=E3=82=8B=E8=AA=B2=E9=A1=8C?= =?UTF-8?q?=E3=81=AB=E5=B7=AE=E3=81=97=E6=9B=BF=E3=81=88=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 101 +++++++---------------------------- 1 file changed, 19 insertions(+), 82 deletions(-) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index a31eb7a6..6e4ffc73 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -2076,103 +2076,40 @@ data: {"name":"たろう","text":"やっほー"} -## 3章の応用課題1: 再接続したときに、切断中の投稿を取得する +## 3章の応用課題: 名前を呼ばれたら知らせる [← 3章のふりかえりに戻る](#ch3-end) {.jump #adv-ch3} -接続が切れている間の投稿は、再接続しても届きません。接続するたびに `/posts` を取得し直し、`posts` にない投稿だけを追加します。 +ほかの人の投稿のメッセージに自分の名前が入っていたら、`alert` で知らせます。 -- 接続したことは `source.addEventListener('open', 関数)` で分かる -- 投稿には `id` があるので、`posts` にあるかどうかを `posts.some(...)` で確かめられる -- 最初に接続したときも `open` は発生するので、最後の行の `loadPosts();` は消す。残すと取得が2つ同時に走り、同じ投稿が2件ずつ入ることがある +- 届いた1件は、3章と同じく `JSON.parse(e.data)` でオブジェクトに戻せる +- 自分の名前は、名前欄 (`name-input`) の `value` で分かる +- 文字列に別の文字列が入っているかは、`<文字列>.includes(<探す文字列>)` で分かる +- 自分の投稿と、名前欄が空のときは知らせない ---- - - - -## 3章の応用課題1の答え: `posts` にない投稿だけを追加する - -[← 3章のふりかえりに戻る](#ch3-end) {.jump} - -```javascript {data-file=public/script.js} -@@function hasPost(id) {@@ -@@ function isSame(post) {@@ -@@ return post.id === id;@@ -@@ }@@ -@@ return posts.some(isSame);@@ -@@}@@ - -async function loadPosts() { - const res = await fetch(`${API}/posts`); - const loaded = await res.json(); - - for (let i = 0; i < loaded.length; i++) { - @@if (!hasPost(loaded[i].id))@@ posts.push(loaded[i]); - } -``` - -- `some` は、`isSame` が `true` を返す要素が1つでもあれば `true` を返す +**成功**: 「新しいタブで開く」で開いたタブから、違う名前で自分の名前を含むメッセージを投稿し、元のタブに戻ると `alert` が出ている --- -## 3章の応用課題1の答え: 接続するたびに投稿を取得する +## 3章の応用課題の答え [← 3章のふりかえりに戻る](#ch3-end) {.jump} -```javascript {data-file=public/script.js} -%%loadPosts();%% - -const source = new EventSource(`${API}/events`); // すでにある行 -``` - -```javascript {data-file=public/script.js} -source.onmessage = receivePost; // すでにある行 -@@source.addEventListener('open', loadPosts);@@ -``` - -- ページを開いて最初に接続したときも `open` が発生するので、最初の取得もここで済む -- `loadPosts();` の行を残すと取得が2つ同時に走り、同じ投稿が2件ずつ入ることがある - ---- - - - -## 3章の応用課題2: 投稿に失敗したことを伝える - -[← 3章のふりかえりに戻る](#ch3-end) {.jump} - -メッセージを空で投稿すると、共有サーバーは 400 と `{"message": "..."}` を返します。失敗したら `alert` で知らせ、入力欄は空にしないようにします。 - -- 成否は `fetch` の戻り値の `res.ok` で分かる - ---- - - - -## 3章の応用課題2の答え - -[← 3章のふりかえりに戻る](#ch3-end) {.jump} - -```javascript {data-file=public/script.js} - @@const res = @@await fetch(`${API}/posts`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: name, text: text }), - }); - -@@ if (!res.ok) {@@ -@@ const { message } = await res.json();@@ -@@ alert(message);@@ -@@ return;@@ -@@ }@@ - - document.getElementById('text-input').value = ''; +```javascript {data-file=public/extra.js} +@@function notifyMention(e) {@@ +@@ const post = JSON.parse(e.data);@@ +@@ const me = document.getElementById('name-input').value;@@ +@@ if (me === '' || post.name === me) return;@@ +@@ if (!post.text.includes(me)) return;@@ +@@ alert(`${post.name}さんに呼ばれました: ${post.text}`);@@ +@@}@@ +@@source.addEventListener('message', notifyMention);@@ ``` -- 失敗したら `return` で抜けるので、入力欄を空にする行まで進まない -- `const { message }` は、受け取ったオブジェクトから `message` を取り出す +- `receivePost` と同じく、届いた文字列をオブジェクトに戻してから使う +- 名前欄が空だと `includes('')` がいつも `true` になるので、先に抜ける --- From 0f76768662add75d8886529e7fa05bad1ce6e593 Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 13:26:25 +0900 Subject: [PATCH 5/8] =?UTF-8?q?5=E7=AB=A0=E3=81=AE=E5=BF=9C=E7=94=A8?= =?UTF-8?q?=E8=AA=B2=E9=A1=8C2=E3=81=AE=E7=A2=BA=E3=81=8B=E3=82=81?= =?UTF-8?q?=E6=96=B9=E3=82=92=E3=80=81=E6=B6=88=E3=81=97=E3=81=9F3?= =?UTF-8?q?=E7=AB=A0=E3=81=AE=E8=AA=B2=E9=A1=8C=E3=81=A7=E3=81=AF=E3=81=AA?= =?UTF-8?q?=E3=81=8F=20Network=20=E3=82=BF=E3=83=96=E3=81=AE=20400=20?= =?UTF-8?q?=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index 6e4ffc73..0139871e 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -2160,7 +2160,7 @@ data: {"name":"たろう","text":"やっほー"} 上から順に難しくなります。 1. ハートビートを送る: 15秒ごとに、接続している全員へコメント行 `: ping\n\n` を書く。データが流れない時間が続くと、プロキシなどが接続を切ることがある ([付録: SSE のコメント行](#comment-line)) -2. 空の投稿を受け付けない: `text` が空なら保存せず、ステータス `400` と `{"message": "..."}` を返す。3章の「投稿に失敗したことを伝える」を済ませていれば、ブラウザにエラーが表示される +2. 空の投稿を受け付けない: `text` が空なら保存せず、ステータス `400` と `{"message": "..."}` を返す。Network タブで、空の投稿の `posts` の行が 400 になれば成功 3. 接続数を全員に送る: 接続したときと切れたときに `event: count\ndata: 3\n\n` を全員に書く。ブラウザ側は `source.addEventListener('count', 関数)` で受け取り、画面に出す --- From 247f714e62c71398369829fe13924fdd8435b8be Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 13:32:14 +0900 Subject: [PATCH 6/8] =?UTF-8?q?3-2=20=E3=81=A7=E6=B6=88=E3=81=99=20showPos?= =?UTF-8?q?ts=20=E3=81=AE=E4=BD=8D=E7=BD=AE=E3=82=92=E3=80=81=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AE=E6=9C=80=E5=BE=8C=E3=81=AE?= =?UTF-8?q?=E8=A1=8C=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F=E3=80=81=E3=81=99?= =?UTF-8?q?=E3=81=90=E4=B8=8B=E3=81=AB=E3=81=82=E3=82=8B=20EventSource=20?= =?UTF-8?q?=E3=81=AE=E8=A1=8C=E3=81=A7=E7=A4=BA=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index 0139871e..78626871 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -1161,7 +1161,7 @@ source.@@onmessage@@ = showPosts; --- - + ## 3-2. 投稿を取ってくる `loadPosts` を作る @@ -1183,8 +1183,9 @@ async function showPosts() { // すでにある行 ``` ```javascript {data-file=public/script.js} -%%showPosts();%% // ファイルの最後の行 +%%showPosts();%% @@loadPosts();@@ +const source = new EventSource(`${API}/events`); // すでにある行 ``` **成功**: 画面はいままでどおり動く From fa4b2114c46cb6ff8a229edf5c82269f04276573 Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 13:34:38 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=E6=BC=94=E7=BF=92=E3=81=AE=E9=80=B2?= =?UTF-8?q?=E3=82=81=E6=96=B9=E3=81=8B=E3=82=89=E3=80=81=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=81=AE=E5=8F=B3=E4=B8=8A=E3=81=8C=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E5=90=8D=E3=81=A0=E3=81=A8=E3=81=84=E3=81=86?= =?UTF-8?q?=E8=AA=AC=E6=98=8E=E3=82=92=E6=B6=88=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 1 - 1 file changed, 1 deletion(-) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index 78626871..05b51552 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -469,7 +469,6 @@ style: | 黄色い行だけを書き、赤く取り消された行は消します。 `______` は空欄です。前の説明のスライドを見て、自分で埋めます。 -コードの右上はファイル名です。 ```javascript {data-file=public/script.js} const posts = []; // 色なし: すでにある行。この下に書く From 5f2c9a1991f95ee4368d3a98df1e6c8204788399 Mon Sep 17 00:00:00 2001 From: m-sato Date: Wed, 30 Sep 2026 13:42:37 +0900 Subject: [PATCH 8/8] =?UTF-8?q?=E5=BF=9C=E7=94=A8=E8=AA=B2=E9=A1=8C?= =?UTF-8?q?=E3=81=A7=E4=BD=9C=E3=82=8B=E9=96=A2=E6=95=B0=E3=81=AE=E5=90=8D?= =?UTF-8?q?=E5=89=8D=E3=82=92=E3=83=92=E3=83=B3=E3=83=88=E3=81=A7=E6=B1=BA?= =?UTF-8?q?=E3=82=81=E3=80=81=E6=9C=AC=E7=B7=A8=E3=81=AE=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E3=82=92=E7=BD=AE=E3=81=8D=E6=8F=9B=E3=81=88=E3=81=A6=E3=81=97?= =?UTF-8?q?=E3=81=BE=E3=82=8F=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 2026/board-advanced/slide.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/2026/board-advanced/slide.md b/2026/board-advanced/slide.md index 05b51552..67afa9f1 100644 --- a/2026/board-advanced/slide.md +++ b/2026/board-advanced/slide.md @@ -2027,7 +2027,7 @@ data: {"name":"たろう","text":"やっほー"} 章が早く終わった人向けの課題です。終わったら左上のリンクで、来た章に戻ります。 {#advanced} コードは `public/extra.js` に書きます。`script.js` の `source` や `posts` は、そのまま使えます。 -`script.js` にある名前で変数や関数を作ると `extra.js` 全体が動かなくなるので、別の名前を付けてください。 +関数は、課題に書いてある名前で作ってください。`script.js` と同じ名前にすると、本編の関数が置き換わって動かなくなります。 --- @@ -2039,6 +2039,7 @@ data: {"name":"たろう","text":"やっほー"} 別のタブを見ている間に届いた投稿の数を、`(3) みんなの掲示板` のようにタブのタイトルに出します。掲示板のタブに戻ったら元のタイトルに戻します。 +- 届いたときに数える `countUnread` と、タブに戻ったときにタイトルを戻す `resetTitle` の2つの関数を作る - タイトルは `document.title` で読み書きできる - いま見られていないかは `document.hidden` で分かる - 見られる状態に戻ったことは `visibilitychange` イベントで分かる @@ -2082,6 +2083,7 @@ data: {"name":"たろう","text":"やっほー"} ほかの人の投稿のメッセージに自分の名前が入っていたら、`alert` で知らせます。 +- 届いたときに名前を探して知らせる `notifyMention` という関数を作る - 届いた1件は、3章と同じく `JSON.parse(e.data)` でオブジェクトに戻せる - 自分の名前は、名前欄 (`name-input`) の `value` で分かる - 文字列に別の文字列が入っているかは、`<文字列>.includes(<探す文字列>)` で分かる @@ -2121,6 +2123,7 @@ data: {"name":"たろう","text":"やっほー"} 「みんなの投稿」の横に、SSE の接続がつながっているか切れているかを出します。`server.js` を保存するとサーバーが再起動するので、一瞬「切れています」になり、「つながっています」に戻れば完成です。 +- つながったときに表示する `showOnline` と、切れたときに表示する `showOffline` の2つの関数を作る - 表示する場所は `index.html` に `` として用意してある - `class` を `status online` にすると緑、`status offline` にすると赤になる - 接続したことは `source.addEventListener('open', 関数)` で、切れたことは `source.addEventListener('error', 関数)` で分かる @@ -2159,9 +2162,9 @@ data: {"name":"たろう","text":"やっほー"} 上から順に難しくなります。 -1. ハートビートを送る: 15秒ごとに、接続している全員へコメント行 `: ping\n\n` を書く。データが流れない時間が続くと、プロキシなどが接続を切ることがある ([付録: SSE のコメント行](#comment-line)) +1. ハートビートを送る: 15秒ごとに、接続している全員へコメント行 `: ping\n\n` を書く関数 `sendPing` を作る。データが流れない時間が続くと、プロキシなどが接続を切ることがある ([付録: SSE のコメント行](#comment-line)) 2. 空の投稿を受け付けない: `text` が空なら保存せず、ステータス `400` と `{"message": "..."}` を返す。Network タブで、空の投稿の `posts` の行が 400 になれば成功 -3. 接続数を全員に送る: 接続したときと切れたときに `event: count\ndata: 3\n\n` を全員に書く。ブラウザ側は `source.addEventListener('count', 関数)` で受け取り、画面に出す +3. 接続数を全員に送る: 接続したときと切れたときに、`event: count\ndata: 3\n\n` を全員に書く関数 `sendCount` を呼ぶ。ブラウザ側は、受け取って画面に出す関数 `showCount` を `source.addEventListener('count', showCount)` で登録する ---