Skip to content
37 changes: 37 additions & 0 deletions 2026/board-advanced/completed/public/extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,36 @@
// script.js のあとに読み込むので、script.js の source や posts をそのまま使える。
// このファイルが途中で止まっても、script.js は動き続ける。

// 2章の応用課題: 見ていない間に届いた件数をタブに出す
const title = document.title;
let unread = 0;

function countUnread() {
if (!document.hidden) return;
unread = unread + 1;
document.title = `(${unread}) ${title}`;
}
function resetTitle() {
if (document.hidden) return;
unread = 0;
document.title = title;
}

source.addEventListener('message', countUnread);
document.addEventListener('visibilitychange', resetTitle);

// 3章の応用課題: 名前を呼ばれたら知らせる
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);

// 4章の応用課題: 接続状態を画面に出す
const status = document.getElementById('status');

function showOnline() {
Expand All @@ -16,3 +46,10 @@ function showOffline() {

source.addEventListener('open', showOnline);
source.addEventListener('error', showOffline);

// 5章の応用課題3: 接続数を画面に出す
function showCount(e) {
document.getElementById('count').textContent = `${e.data}人が接続中`;
}

source.addEventListener('count', showCount);
1 change: 1 addition & 0 deletions 2026/board-advanced/completed/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ <h1>みんなの掲示板</h1>

<div class="list-header">
<h2>みんなの投稿</h2>
<span id="count"></span>
<span id="status" class="status">—</span>
<button id="reload-btn">更新する</button>
</div>
Expand Down
8 changes: 1 addition & 7 deletions 2026/board-advanced/completed/public/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,12 @@ async function addPost() {
const name = document.getElementById('name-input').value;
const text = document.getElementById('text-input').value;

const res = await fetch(`${API}/posts`, {
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 = '';
}

Expand Down
22 changes: 22 additions & 0 deletions 2026/board-advanced/completed/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ const posts = [];
// 現在の接続の一覧。接続した順に並ぶ。
const connections = [];

// 15 秒ごとにコメント行を送り、データが流れない接続が切られないようにする
function sendPing() {
for (let i = 0; i < connections.length; i++) {
connections[i].write(': ping\n\n');
}
}
setInterval(sendPing, 15000);

// いまの接続数を、接続している全員に送る
function sendCount() {
for (let i = 0; i < connections.length; i++) {
connections[i].write(`event: count\ndata: ${connections.length}\n\n`);
}
}

const server = createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);

Expand All @@ -33,10 +48,12 @@ const server = createServer(async (req, res) => {
res.flushHeaders();
connections.push(res);
console.log(`接続数: ${connections.length}`);
sendCount();

function removeConnection() {
connections.splice(connections.indexOf(res), 1);
console.log(`接続数: ${connections.length}`);
sendCount();
}
req.on('close', removeConnection);
return;
Expand All @@ -45,6 +62,11 @@ const server = createServer(async (req, res) => {
// 投稿を 1 件受け取る
if (req.method === 'POST' && url.pathname === '/posts') {
const post = await readPost(req);
if (post.text === '') {
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({ message: 'text が空です' }));
return;
}
posts.push(post);
if (posts.length > MAX_POSTS) posts.shift();

Expand Down
Loading