javascript:(function(){ const oldWrapper = document.getElementById('ftb-ext-wrapper'); if(oldWrapper) oldWrapper.remove(); const style = document.createElement('style'); style.id = 'ftb-ext-style'; style.innerHTML = ` #ftb-ext-wrapper { font-family: sans-serif; } #ftb-ext-sidebar { position: fixed; top: 32px; right: 0; width: 30%; height: calc(100% - 32px); background: #F5F5DC; border-left: 2px solid #800000; z-index: 9999; display: flex; flex-direction: column; color: #800000; box-shadow: -2px 0 5px rgba(0,0,0,0.2); } #ftb-ext-header { padding: 10px; border-bottom: 1px solid #800000; background: #F5F5DC; position: relative; } #ftb-ext-close-btn { position: absolute; top: 10px; right: 10px; cursor: pointer; background: #fff; border: 1px solid #800000; padding: 2px 5px; font-size: 12px; } #ftb-ext-update-btn { position: absolute; top: 35px; right: 10px; cursor: pointer; background: #fff; border: 1px solid #800000; padding: 2px 5px; font-size: 12px; } .ftb-ext-input-row { margin-bottom: 5px; display: flex; align-items: center; width: calc(100% - 45px); } .ftb-ext-input-row label { width: 40px; font-size: 12px; } .ftb-ext-input-box { flex: 1; border: 1px solid #ccc; padding: 2px; box-sizing: border-box; background: #fff; min-height: 20px; white-space: nowrap; overflow: hidden; font-size: 13px; } .ftb-ext-input-box:empty:before { content: attr(data-placeholder); color: #888; } #ftb-ext-jump-btn { cursor: pointer; background: #fff; border: 1px solid #800000; padding: 2px 5px; margin-left: 5px; font-size: 10px; } #ftb-ext-list { flex: 1; overflow-y: auto; padding: 10px; padding-left: 45px; } .ftb-ext-item-container { position: relative; margin-bottom: 10px; } .ftb-ext-list-copy-btn { position: absolute; left: -35px; top: 0; width: 30px; height: 100%; text-align: center; cursor: pointer; border: none; background: #FFFFFF; color: #800000; font-size: 16px; display: flex; align-items: center; justify-content: center; padding: 0; box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); transition: all 0.3s ease; } .ftb-ext-list-copy-btn:hover { box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); } .ftb-ext-item { border: 1px solid #800000; padding: 5px; cursor: pointer; background: #F0E0D6; color: #800000; font-size: 14px; word-break: break-all; min-height: 40px;} .ftb-ext-highlight { background-color: yellow; color: black; font-weight: bold; } .ftb-ext-cite-count { color: green; font-weight: bold; margin-left: 5px; font-size: 12px; } #ftb-ext-modal-bg { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 10000; display: flex; align-items: center; justify-content: center; } #ftb-ext-modal { background: #FFFFEE; border: 3px solid #800000; width: 60%; max-height: 90%; display: flex; flex-direction: column; position: relative; } #ftb-ext-modal-close { position: absolute; top: 5px; right: 10px; cursor: pointer; font-weight: bold; z-index: 10001; } #ftb-ext-modal-content { padding: 20px; overflow-y: auto; flex: 1; } .ftb-ext-original-box { display: flow-root; min-height: 100px; } .ftb-ext-modal-btn-row { display: flex; gap: 10px; padding: 15px 0; border-top: 1px solid #800000; border-bottom: 1px dashed #800000; margin: 15px 0; } .ftb-ext-modal-btn { cursor: pointer; border: none; box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); background: #FFFFFF; color: #800000; padding: 5px 10px; font-size: 12px; transition: all 0.3s ease; } `; document.head.appendChild(style); const wrapper = document.createElement('div'); wrapper.id = 'ftb-ext-wrapper'; document.body.appendChild(wrapper); const sidebar = document.createElement('div'); sidebar.id = 'ftb-ext-sidebar'; wrapper.appendChild(sidebar); const header = document.createElement('div'); header.id = 'ftb-ext-header'; sidebar.appendChild(header); const closeBtn = document.createElement('button'); closeBtn.id = 'ftb-ext-close-btn'; closeBtn.innerText = '終了'; header.appendChild(closeBtn); const updateBtn = document.createElement('button'); updateBtn.id = 'ftb-ext-update-btn'; updateBtn.innerText = '更新'; header.appendChild(updateBtn); let initSearch = 'お願いします'; let initHlStr = ''; try { initSearch = localStorage.getItem('ftbExtSearch') || 'お願いします'; initHlStr = localStorage.getItem('ftbExtHl') || ''; } catch(e) {} const searchRow = document.createElement('div'); searchRow.className = 'ftb-ext-input-row'; searchRow.innerHTML = '
'; header.appendChild(searchRow); const searchInput = document.getElementById('ftb-ext-search-input'); searchInput.innerText = initSearch; const hlRow = document.createElement('div'); hlRow.className = 'ftb-ext-input-row'; hlRow.innerHTML = '
'; header.appendChild(hlRow); const hlInput = document.getElementById('ftb-ext-hl-input'); hlInput.innerText = initHlStr; const jumpBtn = document.getElementById('ftb-ext-jump-btn'); const listArea = document.createElement('div'); listArea.id = 'ftb-ext-list'; sidebar.appendChild(listArea); let allRepliesMap = new Map(); let citingMap = new Map(); let hlElements = []; let hlIndex = 0; let autoUpdateTimer = null; closeBtn.onclick = () => { wrapper.remove(); style.remove(); allRepliesMap.clear(); citingMap.clear(); hlElements = []; if(autoUpdateTimer) clearInterval(autoUpdateTimer); }; updateBtn.onclick = () => { renderList(); }; jumpBtn.onclick = () => { if(hlElements.length === 0) return; if(hlIndex >= hlElements.length) hlIndex = 0; hlElements[hlIndex].scrollIntoView({ behavior: 'smooth', block: 'center' }); hlIndex++; }; const hasImage = (replyElement) => { const links = replyElement.querySelectorAll('a'); for (let a of links) { const href = a.href.toLowerCase(); if (href.endsWith('.jpg') || href.endsWith('.png') || href.endsWith('.gif') || href.endsWith('.webp')) return true; } return !!replyElement.querySelector('img'); }; const copyToClipboard = (text, btn, originalText) => { navigator.clipboard.writeText(text).then(() => { btn.innerText = 'OK'; setTimeout(() => btn.innerText = originalText, 1000); }); }; const scanAndMapReplies = () => { allRepliesMap.clear(); citingMap.clear(); document.querySelectorAll('td.rtd').forEach(reply => { const bq = reply.querySelector('blockquote'); if(!bq) return; const resNumEl = reply.querySelector('.no_quote, .cno'); const resNum = resNumEl ? resNumEl.innerText.replace('No.', '') : null; if(!resNum) return; const html = bq.innerHTML; const lines = html.split(//i).map(line => { const temp = document.createElement('div'); temp.innerHTML = line; return temp.innerText.trim(); }); const cleanLines = lines.filter(l => !l.startsWith('>') && !l.startsWith('>') && !l.startsWith('>')); allRepliesMap.set(resNum, { element: reply, cleanText: cleanLines.join('\n'), cleanLines: cleanLines, allLines: lines }); }); allRepliesMap.forEach((bData, bNum) => { let citedNums = new Set(); bData.allLines.forEach(line => { const text = line.trim(); if (/^(?:>|>)(?!(?:>|>|>))/.test(text) && !text.includes('1000なら')) { const numMatch = text.match(/No\.(\d+)/); if (numMatch) citedNums.add(numMatch[1]); const cleanQLine = text.replace(/^(?:>|>|\s)+/, '').trim(); if (cleanQLine.length >= 5) { allRepliesMap.forEach((aData, aNum) => { if (aNum !== bNum && aData.cleanLines.includes(cleanQLine)) citedNums.add(aNum); }); } } }); citedNums.forEach(aNum => { if(!citingMap.has(aNum)) citingMap.set(aNum, new Set()); citingMap.get(aNum).add(bNum); }); }); }; const showModal = (resNum) => { const data = allRepliesMap.get(resNum); if(!data) return; const bg = document.createElement('div'); bg.id = 'ftb-ext-modal-bg'; const modal = document.createElement('div'); modal.id = 'ftb-ext-modal'; const close = document.createElement('span'); close.id = 'ftb-ext-modal-close'; close.innerText = '× 閉じる'; close.onclick = () => { bg.remove(); renderList(); }; const content = document.createElement('div'); content.id = 'ftb-ext-modal-content'; const originalBox = document.createElement('div'); originalBox.className = 'ftb-ext-original-box'; originalBox.innerHTML = data.element.innerHTML; content.appendChild(originalBox); const btnRow = document.createElement('div'); btnRow.className = 'ftb-ext-modal-btn-row'; const quoteFull = data.allLines.map(l => '>' + l).join('\n'); const quotedCleanText = data.cleanText.split('\n').map(l => '>' + l).join('\n'); [ { label: 'レス番号', text: `>No.${resNum}` }, { label: '全文', text: quoteFull }, { label: '本文(引用除外)', text: quotedCleanText } ].forEach(config => { const b = document.createElement('button'); b.className = 'ftb-ext-modal-btn'; b.innerText = config.label; b.onclick = (e) => { e.stopPropagation(); copyToClipboard(config.text, b, config.label); }; btnRow.appendChild(b); }); content.appendChild(btnRow); const citers = citingMap.has(resNum) ? Array.from(citingMap.get(resNum)) : []; if(citers.length > 0) { const citeArea = document.createElement('div'); citeArea.innerHTML = '引用レス一覧:'; citers.forEach(cNum => { const cData = allRepliesMap.get(cNum); if(cData) { const cBox = document.createElement('div'); cBox.style.cssText = 'background:#F0E0D6; border:1px solid #800000; padding:10px; margin-top:10px; display:flow-root; font-size:14px; color:#800000; min-height:100px;'; const img = cData.element.querySelector('img'); if (img) { const imgContainer = img.closest('a') ? img.closest('a').cloneNode(true) : img.cloneNode(true); imgContainer.style.float = 'left'; imgContainer.style.margin = '0 10px 10px 0'; cBox.appendChild(imgContainer); } const formattedLines = cData.allLines.map(l => { if (/^(?:>|>)/.test(l.trim())) return `${l}`; return l; }).join('
'); const textDiv = document.createElement('div'); textDiv.innerHTML = `No.${cNum}
${formattedLines}`; cBox.appendChild(textDiv); citeArea.appendChild(cBox); } }); content.appendChild(citeArea); } modal.appendChild(close); modal.appendChild(content); bg.appendChild(modal); bg.onclick = (e) => { if(e.target === bg) { bg.remove(); renderList(); } }; wrapper.appendChild(bg); }; const renderList = () => { listArea.innerHTML = ''; hlElements = []; hlIndex = 0; const keywordStr = searchInput.innerText.replace(/\r?\n/g, ' ').trim(); const hlWordStr = hlInput.innerText.replace(/\r?\n/g, ' ').trim(); try { localStorage.setItem('ftbExtSearch', keywordStr); localStorage.setItem('ftbExtHl', hlWordStr); } catch(e) {} if (!keywordStr) return; const keywords = keywordStr.split(/[\s ]+/).filter(k => k); const hlWords = hlWordStr ? hlWordStr.split(/[\s ]+/).filter(k => k) : []; scanAndMapReplies(); const fragment = document.createDocumentFragment(); allRepliesMap.forEach((data, resNum) => { if(!keywords.some(kw => data.cleanText.includes(kw))) return; let displayText = data.cleanText; if (hlWords.length > 0) { const escapedWords = hlWords.map(w => w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); const regex = new RegExp(`(${escapedWords.join('|')})`, 'gi'); displayText = displayText.replace(regex, `$1`); } const citers = citingMap.has(resNum) ? Array.from(citingMap.get(resNum)) : []; if(citers.length > 0) displayText += `引用(${citers.length})`; const container = document.createElement('div'); container.className = 'ftb-ext-item-container'; const copyBtn = document.createElement('button'); copyBtn.className = 'ftb-ext-list-copy-btn'; copyBtn.innerText = '❏'; copyBtn.onclick = (e) => { e.stopPropagation(); const quotedCleanText = data.cleanText.split('\n').map(l => '>' + l).join('\n'); copyToClipboard(quotedCleanText, copyBtn, '❏'); }; const item = document.createElement('div'); item.className = 'ftb-ext-item'; item.innerHTML = (hasImage(data.element) ? '⧉ ' : '') + displayText; item.onclick = () => showModal(resNum); container.appendChild(copyBtn); container.appendChild(item); fragment.appendChild(container); }); listArea.appendChild(fragment); hlElements = Array.from(listArea.querySelectorAll('.ftb-ext-highlight')); }; searchInput.addEventListener('keydown', (e) => { if(e.key === 'Enter') { e.preventDefault(); renderList(); } }); hlInput.addEventListener('keydown', (e) => { if(e.key === 'Enter') { e.preventDefault(); renderList(); } }); renderList(); let lastReplyCount = document.querySelectorAll('td.rtd').length; autoUpdateTimer = setInterval(() => { const currentReplyCount = document.querySelectorAll('td.rtd').length; if(currentReplyCount !== lastReplyCount) { lastReplyCount = currentReplyCount; if(searchInput.innerText.trim() !== '') { renderList(); } } }, 2000); })();