添加总结字幕
Browse files- youtube_sub.js +142 -2
youtube_sub.js
CHANGED
|
@@ -29,7 +29,8 @@ GM_addStyle(`
|
|
| 29 |
}
|
| 30 |
const WORD_CACHE_EXPIRY = 7 * 24 * 60 * 60 * 1000; // 7 days in ms
|
| 31 |
const WORD_CACHE_KEY = 'youtube_subtitle_word_cache';
|
| 32 |
-
|
|
|
|
| 33 |
// 增强的样式定义,添加过渡效果
|
| 34 |
const styles = `
|
| 35 |
.subtitle-manager {
|
|
@@ -524,6 +525,141 @@ GM_addStyle(`
|
|
| 524 |
window.subtitleManagerInstance = this;
|
| 525 |
this.speakerClickCount = 0; // Add counter
|
| 526 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 527 |
cleanup() {
|
| 528 |
// Remove existing instance
|
| 529 |
if (this.container) {
|
|
@@ -856,7 +992,8 @@ GM_addStyle(`
|
|
| 856 |
const tabsData = [
|
| 857 |
{ id: 'english', text: 'English' },
|
| 858 |
{ id: 'chinese', text: '中文' },
|
| 859 |
-
{ id: 'mixed', text: 'Mixed' }
|
|
|
|
| 860 |
];
|
| 861 |
|
| 862 |
tabsData.forEach(tab => {
|
|
@@ -1849,6 +1986,9 @@ GM_addStyle(`
|
|
| 1849 |
content.appendChild(this.createMessageElement('No subtitles available for mixed mode'));
|
| 1850 |
}
|
| 1851 |
break;
|
|
|
|
|
|
|
|
|
|
| 1852 |
}
|
| 1853 |
|
| 1854 |
// Fade in
|
|
|
|
| 29 |
}
|
| 30 |
const WORD_CACHE_EXPIRY = 7 * 24 * 60 * 60 * 1000; // 7 days in ms
|
| 31 |
const WORD_CACHE_KEY = 'youtube_subtitle_word_cache';
|
| 32 |
+
// Add after WORD_CACHE_KEY constant:
|
| 33 |
+
const NEW_WORDS_CACHE_KEY = 'youtube_subtitle_newword';
|
| 34 |
// 增强的样式定义,添加过渡效果
|
| 35 |
const styles = `
|
| 36 |
.subtitle-manager {
|
|
|
|
| 525 |
window.subtitleManagerInstance = this;
|
| 526 |
this.speakerClickCount = 0; // Add counter
|
| 527 |
}
|
| 528 |
+
|
| 529 |
+
extractUniqueWords() {
|
| 530 |
+
// Get all English words from subtitles
|
| 531 |
+
const words = this.subtitles.english
|
| 532 |
+
.flatMap(sub => sub.words
|
| 533 |
+
.map(w => w.text.toLowerCase())
|
| 534 |
+
.filter(text =>
|
| 535 |
+
// Only include valid English words
|
| 536 |
+
/^[a-z]+$/.test(text) &&
|
| 537 |
+
text.length > 1
|
| 538 |
+
)
|
| 539 |
+
);
|
| 540 |
+
|
| 541 |
+
// Remove duplicates and sort
|
| 542 |
+
return [...new Set(words)].sort();
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
loadNewWordsCache() {
|
| 546 |
+
try {
|
| 547 |
+
const cached = localStorage.getItem(NEW_WORDS_CACHE_KEY);
|
| 548 |
+
return cached ? new Set(JSON.parse(cached)) : new Set();
|
| 549 |
+
} catch (e) {
|
| 550 |
+
debug.error('Failed to load words cache:', e);
|
| 551 |
+
return new Set();
|
| 552 |
+
}
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
updateNewWordsCache(words) {
|
| 556 |
+
try {
|
| 557 |
+
const existingWords = this.loadNewWordsCache();
|
| 558 |
+
const updatedWords = [...existingWords, ...words];
|
| 559 |
+
localStorage.setItem(NEW_WORDS_CACHE_KEY,
|
| 560 |
+
JSON.stringify([...new Set(updatedWords)]));
|
| 561 |
+
} catch (e) {
|
| 562 |
+
debug.error('Failed to update words cache:', e);
|
| 563 |
+
}
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
displayWordSummary(container) {
|
| 567 |
+
// Clear existing content safely
|
| 568 |
+
while (container.firstChild) {
|
| 569 |
+
container.removeChild(container.firstChild);
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
const uniqueWords = this.extractUniqueWords();
|
| 573 |
+
const existingWords = this.loadNewWordsCache();
|
| 574 |
+
|
| 575 |
+
// Add summary header
|
| 576 |
+
const summaryDiv = document.createElement('div');
|
| 577 |
+
summaryDiv.style.cssText = `
|
| 578 |
+
font-size: 18px;
|
| 579 |
+
padding: 15px;
|
| 580 |
+
margin-bottom: 20px;
|
| 581 |
+
border-bottom: 1px solid #ccc;
|
| 582 |
+
text-align: center;
|
| 583 |
+
font-weight: bold;
|
| 584 |
+
color: #4CAF50;
|
| 585 |
+
`;
|
| 586 |
+
|
| 587 |
+
container.appendChild(summaryDiv);
|
| 588 |
+
|
| 589 |
+
// Group words by first letter
|
| 590 |
+
const grouped = uniqueWords.reduce((acc, word) => {
|
| 591 |
+
const firstLetter = word[0].toUpperCase();
|
| 592 |
+
if (!acc[firstLetter]) acc[firstLetter] = [];
|
| 593 |
+
acc[firstLetter].push(word);
|
| 594 |
+
return acc;
|
| 595 |
+
}, {});
|
| 596 |
+
|
| 597 |
+
// Create letter sections with enhanced styling
|
| 598 |
+
Object.entries(grouped)
|
| 599 |
+
.sort(([a], [b]) => a.localeCompare(b))
|
| 600 |
+
.forEach(([letter, words]) => {
|
| 601 |
+
const section = document.createElement('div');
|
| 602 |
+
section.className = 'word-section';
|
| 603 |
+
section.style.margin = '0 15px 25px 15px';
|
| 604 |
+
|
| 605 |
+
const heading = document.createElement('h3');
|
| 606 |
+
heading.style.cssText = `
|
| 607 |
+
color: #4CAF50;
|
| 608 |
+
margin: 15px 0;
|
| 609 |
+
font-size: 24px;
|
| 610 |
+
font-weight: bold;
|
| 611 |
+
`;
|
| 612 |
+
heading.textContent = letter;
|
| 613 |
+
section.appendChild(heading);
|
| 614 |
+
|
| 615 |
+
const grid = document.createElement('div');
|
| 616 |
+
grid.className = 'word-grid';
|
| 617 |
+
grid.style.cssText = `
|
| 618 |
+
display: grid;
|
| 619 |
+
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
| 620 |
+
gap: 12px;
|
| 621 |
+
padding: 10px;
|
| 622 |
+
`;
|
| 623 |
+
|
| 624 |
+
words.forEach(word => {
|
| 625 |
+
const btn = document.createElement('button');
|
| 626 |
+
btn.className = `word-btn subtitle-word ${existingWords.has(word) ? 'known' : 'new'}`;
|
| 627 |
+
btn.style.cssText = `
|
| 628 |
+
text-align: left;
|
| 629 |
+
padding: 8px 12px;
|
| 630 |
+
font-size: 16px;
|
| 631 |
+
border-radius: 4px;
|
| 632 |
+
transition: all 0.2s;
|
| 633 |
+
cursor: pointer;
|
| 634 |
+
${existingWords.has(word) ? 'opacity: 0.5;' : ''}
|
| 635 |
+
`;
|
| 636 |
+
btn.dataset.word = word;
|
| 637 |
+
btn.textContent = word;
|
| 638 |
+
|
| 639 |
+
btn.onclick = (e) => {
|
| 640 |
+
e.stopPropagation();
|
| 641 |
+
if (word) {
|
| 642 |
+
this.showWordDefinition(word);
|
| 643 |
+
}
|
| 644 |
+
};
|
| 645 |
+
|
| 646 |
+
grid.appendChild(btn);
|
| 647 |
+
});
|
| 648 |
+
|
| 649 |
+
section.appendChild(grid);
|
| 650 |
+
container.appendChild(section);
|
| 651 |
+
});
|
| 652 |
+
|
| 653 |
+
// Add new words to cache
|
| 654 |
+
this.updateNewWordsCache(uniqueWords);
|
| 655 |
+
|
| 656 |
+
// Get fresh count from cache after update
|
| 657 |
+
const freshCache = this.loadNewWordsCache();
|
| 658 |
+
const totalCachedWords = freshCache.size;
|
| 659 |
+
|
| 660 |
+
// Update summary text with fresh counts
|
| 661 |
+
summaryDiv.textContent = `Unique: ${uniqueWords.length} | Total: ${totalCachedWords}`;
|
| 662 |
+
}
|
| 663 |
cleanup() {
|
| 664 |
// Remove existing instance
|
| 665 |
if (this.container) {
|
|
|
|
| 992 |
const tabsData = [
|
| 993 |
{ id: 'english', text: 'English' },
|
| 994 |
{ id: 'chinese', text: '中文' },
|
| 995 |
+
{ id: 'mixed', text: 'Mixed' },
|
| 996 |
+
{ id: 'summary', text: 'Words' }
|
| 997 |
];
|
| 998 |
|
| 999 |
tabsData.forEach(tab => {
|
|
|
|
| 1986 |
content.appendChild(this.createMessageElement('No subtitles available for mixed mode'));
|
| 1987 |
}
|
| 1988 |
break;
|
| 1989 |
+
case 'summary':
|
| 1990 |
+
this.displayWordSummary(content);
|
| 1991 |
+
break;
|
| 1992 |
}
|
| 1993 |
|
| 1994 |
// Fade in
|