// グローバルshowNotification関数(最初に定義) window.showNotification = function(message, type = 'info') { const notification = document.createElement('div'); const colors = { success: '#4CAF50', error: '#f44336', info: '#2196F3', warning: '#ff9800' }; notification.style.cssText = 'position: fixed;' + 'top: 20px;' + 'right: 20px;' + 'background: ' + colors[type] + ';' + 'color: white;' + 'padding: 16px 24px;' + 'border-radius: 8px;' + 'z-index: 10000;' + 'font-weight: 500;' + 'font-size: 14px;' + 'box-shadow: 0 4px 12px rgba(0,0,0,0.3);' + 'animation: slideInRight 0.5s ease;' + 'max-width: 300px;' + 'word-wrap: break-word;' + 'border: 2px solid rgba(255,255,255,0.3);'; notification.innerHTML = message; document.body.appendChild(notification); setTimeout(() => { notification.style.animation = 'slideOutRight 0.5s ease'; setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 500); }, 4000); }; // リアクションシステム window.addReaction = async function(reaction, articleId) { const button = document.getElementById('reaction-' + reaction + '-' + (articleId || '')); const countSpan = button ? button.querySelector('.count') : null; if (button && countSpan) { // 既にリアクション済みかチェック(UI状態用にlocalStorageを使用) const hasReacted = localStorage.getItem('reaction-' + reaction + '-' + (articleId || window.location.pathname)); if (hasReacted) { window.showNotification('もう押してるで!'); return; } try { // リアクション追加のAPIコール const response = await fetch('/api/reactions', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ article_id: articleId, reaction_type: reaction }) }); if (response.ok) { const data = await response.json(); // UIを更新 countSpan.textContent = data.count; button.classList.add('active'); // 重複クリックを防ぐためlocalStorageに保存 localStorage.setItem('reaction-' + reaction + '-' + (articleId || window.location.pathname), 'true'); // 楽しいメッセージを表示 const messages = { 'いいね': 'いいね!ナイスや!', '草': '草生えるwwwww', 'やばい': 'やばいンゴねぇ...', 'ガチ': 'ガチでそれな!', 'ワロタ': 'ワロタwwwww', 'んご': 'んごんごんご〜' }; window.showNotification(messages[reaction] || 'リアクションありがとうやで!'); } else { const errorText = await response.text(); console.error('APIエラーレスポンス:', errorText); throw new Error('リアクション追加に失敗しました: ' + response.status); } } catch (error) { console.error('リアクション追加エラー:', error); window.showNotification('エラーが発生しました。もう一度試してください。'); } } }; // ソーシャル共有機能 window.showDeleteConfirm = function(articleId) { const password = prompt('管理者パスワードを入力してください:'); if (password === null) return; // キャンセル if (confirm('本当にこの記事を削除しますか?この操作は取り消せません。')) { deleteArticle(articleId, password); } }; // ユーティリティ関数 // ソーシャル共有機能をグローバルに定義 window.shareToTwitter = function() { const url = encodeURIComponent(window.location.href); const title = document.title; const nanjTexts = [ 'これはガチでやばいンゴwww', 'この記事草すぎて腹痛いンゴwww', 'なんJ民必見の神記事やんけ', 'ワロタwwwwwwみんな見ろや', 'これは盛り上がりまくりやな', 'ファーwwwwwwこれは草', 'やばすぎワロタンゴwww' ]; const randomText = nanjTexts[Math.floor(Math.random() * nanjTexts.length)]; const text = encodeURIComponent(randomText + ' ' + title + ' #なんJ #ニュース #話題沸騰'); window.open('https://twitter.com/intent/tweet?url=' + url + '&text=' + text, '_blank'); }; window.shareToLine = function() { const url = encodeURIComponent(window.location.href); const title = document.title; const lineText = '🔥' + title + '🔥' + String.fromCharCode(10) + String.fromCharCode(10) + 'これはガチで話題になってるンゴwww' + String.fromCharCode(10) + 'なんJ民も盛り上がってるで!'; const text = encodeURIComponent(lineText); window.open('https://social-plugins.line.me/lineit/share?url=' + url + '&text=' + text, '_blank'); }; window.shareToFacebook = function() { const url = encodeURIComponent(window.location.href); window.open('https://www.facebook.com/sharer/sharer.php?u=' + url + '"e=' + encodeURIComponent('なんJ民も驚愕の話題記事やで!みんなも見てや!'), '_blank'); }; window.copyLink = function() { const url = window.location.href; if (navigator.clipboard) { navigator.clipboard.writeText(url).then(() => { window.showNotification('リンクをコピーしました!', 'success'); }).catch(() => { fallbackCopy(url); }); } else { fallbackCopy(url); } }; function fallbackCopy(text) { const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); window.showNotification('リンクをコピーしました!', 'success'); } catch (err) { window.showNotification('コピーに失敗しました', 'error'); } document.body.removeChild(textArea); } // ページ読み込み時にリアクション数を取得 async function loadReactions() { // 現在のページから記事IDを取得 const articleId = getCurrentArticleId(); if (!articleId) { return; } try { // APIからリアクション数を取得 const response = await fetch('/api/reactions/' + articleId); if (response.ok) { const reactions = await response.json(); // 実際の数値でUIを更新 Object.keys(reactions).forEach(reactionType => { const button = document.getElementById('reaction-' + reactionType + '-' + articleId); if (button) { const countSpan = button.querySelector('.count'); if (countSpan) { countSpan.textContent = reactions[reactionType] || 0; } // ユーザーが既にリアクションしているかチェック(localStorageから) const hasReacted = localStorage.getItem('reaction-' + reactionType + '-' + window.location.pathname); if (hasReacted) { button.classList.add('active'); } } }); } else { const errorText = await response.text(); console.error('リアクションAPIエラー:', errorText); } } catch (error) { console.error('リアクション読み込みエラー:', error); } } // 現在の記事IDを取得するヘルパー関数 function getCurrentArticleId() { const path = window.location.pathname; const match = path.match(/\/article\/(.+)/); if (match) { // URLから記事IDを抽出 const parts = match[1].split('/'); const articleId = parts[parts.length - 1]; // 最後の部分を記事IDとして取得 return articleId; } return null; } // 記事削除機能 async function deleteArticle(articleId, password) { try { const response = await fetch('/api/admin/delete/' + articleId, { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + password } }); const result = await response.json(); if (result.success) { alert('記事を削除しました'); window.location.href = '/'; } else { alert('削除に失敗しました: ' + (result.error || '不明なエラー')); } } catch (error) { alert('削除に失敗しました: ' + error.message); } } // コメントシステム機能 function initCommentSystem() { const commentForm = document.getElementById('commentForm'); const articleId = document.getElementById('articleId')?.value; if (!commentForm) { return; } if (!articleId) { console.error('記事IDが見つかりません'); return; } // コメント一覧を読み込み loadComments(articleId); // コメントフォーム送信イベント commentForm.addEventListener('submit', function(e) { e.preventDefault(); e.stopPropagation(); submitComment(articleId); return false; }); } async function loadComments(articleId) { try { const response = await fetch('/api/comments/' + articleId); if (!response.ok) { throw new Error('HTTP error! status: ' + response.status); } const data = await response.json(); const commentsList = document.getElementById('commentsList'); if (!commentsList) { console.error('コメントリスト要素が見つかりません'); return; } // エラーレスポンスの処理 if (data.error) { console.error('コメント取得エラー:', data.error); commentsList.innerHTML = '