keeping formatted summary #580

This commit is contained in:
mic 2026-03-25 23:59:20 +01:00
parent 3cb1fb0972
commit 7e9b507f0f
5 changed files with 103 additions and 45 deletions

View file

@ -924,8 +924,22 @@ switch (message.command) {
const summaryText = document.createElement('div'); const summaryText = document.createElement('div');
summaryText.className = 'thunderai-summary-content'; summaryText.className = 'thunderai-summary-content';
const hasHtml = !!summaryData.summary_html;
// Helper to set summary content using DOMParser (innerHTML is blocked in Thunderbird content scripts)
function setSummaryHtml(element, html) {
element.textContent = '';
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
while (doc.body.firstChild) {
element.appendChild(doc.body.firstChild);
}
}
if (summaryData.error) { if (summaryData.error) {
summaryText.textContent = summaryData.message || browser.i18n.getMessage("summarize_error"); summaryText.textContent = summaryData.message || browser.i18n.getMessage("summarize_error");
} else if (hasHtml) {
setSummaryHtml(summaryText, summaryData.summary_html);
} else { } else {
summaryText.textContent = summaryData.summary; summaryText.textContent = summaryData.summary;
} }
@ -936,58 +950,94 @@ switch (message.command) {
const maxLen = summaryData.maxDisplayLength || 0; const maxLen = summaryData.maxDisplayLength || 0;
const fullText = summaryData.summary; const fullText = summaryData.summary;
if (!summaryData.error && maxLen > 0 && fullText && fullText.length > maxLen) { if (!summaryData.error && maxLen > 0 && fullText && fullText.length > maxLen) {
let cutPos = fullText.lastIndexOf(' ', maxLen);
if (cutPos <= 0) cutPos = maxLen;
const truncated = fullText.substring(0, cutPos) + '\u2026';
summaryText.textContent = truncated;
// Set up animated expand/collapse via max-height transition // Set up animated expand/collapse via max-height transition
summaryText.style.overflow = 'hidden'; summaryText.style.overflow = 'hidden';
summaryText.style.transition = 'max-height 0.2s ease'; summaryText.style.transition = 'max-height 0.2s ease';
// Measure truncated height after layout if (!hasHtml) {
requestAnimationFrame(() => { // Plain text: truncate by character position
const collapsedHeight = summaryText.scrollHeight; let cutPos = fullText.lastIndexOf(' ', maxLen);
summaryText.style.maxHeight = collapsedHeight + 'px'; if (cutPos <= 0) cutPos = maxLen;
}); const truncated = fullText.substring(0, cutPos) + '\u2026';
summaryText.textContent = truncated;
const toggleLink = document.createElement('a'); // Measure truncated height after layout
toggleLink.textContent = browser.i18n.getMessage("summarize_see_more") || "See more"; requestAnimationFrame(() => {
toggleLink.href = '#';
toggleLink.style.cssText = 'display: inline-block; margin-top: 4px; font-size: 13px; color: ' +
(isDarkSummary ? '#6db3f2' : '#1a5fa8') + '; cursor: pointer; text-decoration: underline;';
let expanded = false;
toggleLink.addEventListener('click', (e) => {
e.preventDefault();
if (!expanded) {
// Expand: set full text, measure, animate to full height
summaryText.textContent = fullText;
const fullHeight = summaryText.scrollHeight;
summaryText.style.maxHeight = fullHeight + 'px';
toggleLink.textContent = browser.i18n.getMessage("summarize_see_less") || "See less";
} else {
// Collapse: measure current truncated height, then animate down
summaryText.textContent = truncated;
// Force layout to get the target height before animating
const collapsedHeight = summaryText.scrollHeight; const collapsedHeight = summaryText.scrollHeight;
summaryText.textContent = fullText; summaryText.style.maxHeight = collapsedHeight + 'px';
// Set explicit current height so transition has a starting point });
summaryText.style.maxHeight = summaryText.scrollHeight + 'px';
requestAnimationFrame(() => {
summaryText.style.maxHeight = collapsedHeight + 'px';
});
// Swap text after transition ends
summaryText.addEventListener('transitionend', function handler() {
summaryText.removeEventListener('transitionend', handler);
summaryText.textContent = truncated;
});
toggleLink.textContent = browser.i18n.getMessage("summarize_see_more") || "See more";
}
expanded = !expanded;
});
summaryTextWrapper.appendChild(toggleLink); const toggleLink = document.createElement('a');
toggleLink.textContent = browser.i18n.getMessage("summarize_see_more") || "See more";
toggleLink.href = '#';
toggleLink.style.cssText = 'display: inline-block; margin-top: 4px; font-size: 13px; color: ' +
(isDarkSummary ? '#6db3f2' : '#1a5fa8') + '; cursor: pointer; text-decoration: underline;';
let expanded = false;
toggleLink.addEventListener('click', (e) => {
e.preventDefault();
if (!expanded) {
// Expand: set full text, measure, animate to full height
summaryText.textContent = fullText;
const fullHeight = summaryText.scrollHeight;
summaryText.style.maxHeight = fullHeight + 'px';
toggleLink.textContent = browser.i18n.getMessage("summarize_see_less") || "See less";
} else {
// Collapse: measure current truncated height, then animate down
summaryText.textContent = truncated;
// Force layout to get the target height before animating
const collapsedHeight = summaryText.scrollHeight;
summaryText.textContent = fullText;
// Set explicit current height so transition has a starting point
summaryText.style.maxHeight = summaryText.scrollHeight + 'px';
requestAnimationFrame(() => {
summaryText.style.maxHeight = collapsedHeight + 'px';
});
// Swap text after transition ends
summaryText.addEventListener('transitionend', function handler() {
summaryText.removeEventListener('transitionend', handler);
summaryText.textContent = truncated;
});
toggleLink.textContent = browser.i18n.getMessage("summarize_see_more") || "See more";
}
expanded = !expanded;
});
summaryTextWrapper.appendChild(toggleLink);
} else {
// HTML content: use max-height to collapse, preserve full HTML
const collapsedMaxHeight = '4.2em'; // ~3 lines collapsed
summaryText.style.maxHeight = collapsedMaxHeight;
const toggleLink = document.createElement('a');
toggleLink.textContent = browser.i18n.getMessage("summarize_see_more") || "See more";
toggleLink.href = '#';
toggleLink.style.cssText = 'display: inline-block; margin-top: 4px; font-size: 13px; color: ' +
(isDarkSummary ? '#6db3f2' : '#1a5fa8') + '; cursor: pointer; text-decoration: underline;';
let expanded = false;
toggleLink.addEventListener('click', (e) => {
e.preventDefault();
if (!expanded) {
summaryText.style.maxHeight = summaryText.scrollHeight + 'px';
toggleLink.textContent = browser.i18n.getMessage("summarize_see_less") || "See less";
} else {
summaryText.style.maxHeight = collapsedMaxHeight;
toggleLink.textContent = browser.i18n.getMessage("summarize_see_more") || "See more";
}
expanded = !expanded;
});
// Only show toggle if content is actually taller than collapsed height
requestAnimationFrame(() => {
if (summaryText.scrollHeight > summaryText.clientHeight) {
summaryTextWrapper.appendChild(toggleLink);
} else {
summaryText.style.maxHeight = '';
summaryText.style.overflow = '';
}
});
}
} }
const summaryBody = document.createElement('div'); const summaryBody = document.createElement('div');

View file

@ -198,6 +198,7 @@ export class taStorage {
let now = Date.now(); let now = Date.now();
record[taStorage.FIELD_SUMMARY] = { record[taStorage.FIELD_SUMMARY] = {
summary: summary_data.summary, summary: summary_data.summary,
summary_html: summary_data.summary_html || '',
error: summary_data.error || false, error: summary_data.error || false,
message: summary_data.message || '', message: summary_data.message || '',
summary_date: summary_data.summary_date instanceof Date summary_date: summary_data.summary_date instanceof Date

View file

@ -80,6 +80,7 @@ export class taSummaryStore {
return { return {
headerMessageId: data_id, headerMessageId: data_id,
summary: summary.summary, summary: summary.summary,
summary_html: summary.summary_html || '',
error: summary.error || false, error: summary.error || false,
message: summary.message || '', message: summary.message || '',
summary_date: new Date(summary.summary_date || summary.ts), summary_date: new Date(summary.summary_date || summary.ts),

View file

@ -3,6 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<script src="api_webchat/markdown-it.min.js"></script>
<script src="mzta-background.js" type="module"></script> <script src="mzta-background.js" type="module"></script>
</head> </head>

View file

@ -297,9 +297,11 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
case 'chatgpt_saveSummary': case 'chatgpt_saveSummary':
async function _saveSummaryFromWebchat(msg) { async function _saveSummaryFromWebchat(msg) {
try { try {
let summaryHtml = msg.text.trim();
let cleanedSummary = cleanSummaryText(msg.text); let cleanedSummary = cleanSummaryText(msg.text);
const summaryData = { const summaryData = {
summary: cleanedSummary, summary: cleanedSummary,
summary_html: summaryHtml,
summary_date: new Date(), summary_date: new Date(),
headerMessageId: msg.headerMessageId headerMessageId: msg.headerMessageId
}; };
@ -558,9 +560,12 @@ async function _generateSummaryForMessage(headerMessageId, tabId) {
await cmd.initWorker(); await cmd.initWorker();
const aiResponse = await cmd.sendPrompt(); const aiResponse = await cmd.sendPrompt();
let cleanedSummary = cleanSummaryText(aiResponse); let cleanedSummary = cleanSummaryText(aiResponse);
const md = window.markdownit();
let summaryHtml = md.render(aiResponse);
const summaryData = { const summaryData = {
summary: cleanedSummary, summary: cleanedSummary,
summary_html: summaryHtml,
summary_date: new Date(), summary_date: new Date(),
headerMessageId: headerMessageId headerMessageId: headerMessageId
}; };