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(/