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-row input { flex: 1; border: 1px solid #ccc; padding: 2px; box-sizing: border-box; }
#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 initHl = '';
try {
initSearch = localStorage.getItem('ftbExtSearch') || 'お願いします';
initHl = 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.value = 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.value = initHl;
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.value;
const hlWord = hlInput.value;
try {
localStorage.setItem('ftbExtSearch', keywordStr);
localStorage.setItem('ftbExtHl', hlWord);
} catch(e) {}
if (!keywordStr.trim()) return;
const keywords = keywordStr.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 (hlWord && displayText.includes(hlWord)) {
displayText = displayText.replace(new RegExp(hlWord.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'), `$&`);
}
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') renderList(); });
hlInput.addEventListener('keydown', (e) => { if(e.key === 'Enter') 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.value.trim() !== '') {
renderList();
}
}
}, 2000);
})();