修复按钮与消息显示冲突问题
你的提示消息宽度看起来很小是因为你没有设置明确的宽度,而且消息内容很短(只有”保存成功!”几个字),所以它只会根据内容自动调整宽度。要解决这个问题,你可以:
- 设置一个固定的最小宽度:
1 | message.style.minWidth = '200px'; |
- 或者设置宽度并让文本居中:
1 | message.style.width = '200px'; |
- 或者添加一些内边距让消息看起来更饱满:
1 | message.style.padding = '15px 25px'; |
修改后的完整函数:
1 | function showSuccessMessage() { |
这样修改后,提示消息会:
- 有足够的内边距
- 保持最小宽度
- 文字居中显示
- 看起来更美观专业
如果你想要更精致的效果,还可以考虑添加CSS动画或使用现成的通知库,但上面的修改已经能满足基本需求了。
时间: 2025-04-07T20:09:40
👤 用户
// 从存储中获取配置
chrome.storage.sync.get([‘autoSaveEnabled’, ‘autoSaveInterval’, ‘selectedFormat’], function(result) {
const CONFIG = {
BUTTON_ID: ‘deepseek-save-btn’,
USER_PREFIX: “你”,
AI_PREFIX: “AI”,
AUTO_SAVE_ENABLED: result.autoSaveEnabled !== undefined ? result.autoSaveEnabled : true,
AUTO_SAVE_INTERVAL: result.autoSaveInterval || 600000, // 默认10分钟
SELECTED_FORMAT: result.selectedFormat || ‘md’ // 默认格式为 Markdown
};
let autoSaveIntervalId = null;
function generateMarkdown(messages) {
let md = `# 聊天记录\n\n`;
messages.forEach(msg => {
md += `**${msg.role === 'user' ? CONFIG.USER_PREFIX : CONFIG.AI_PREFIX}**:\n`;
md += `${msg.text}\n\n---\n\n`;
});
md += `> 保存时间: ${new Date().toLocaleString()}\n`;
return md;
}
function convertMarkdownToHtml(markdown) {
// 使用 marked.js 将 Markdown 转换为 HTML
return marked(markdown);
}
function extractMessages() {
const messages = [];
const titleElement = document.querySelector('.d8ed659a');
let title = titleElement ? titleElement.innerText : '聊天记录';
// 获取用户问题
const userQuestions = document.querySelectorAll('.fbb737a4');
// 获取AI思考过程
const aiThoughts = document.querySelectorAll('.e1675d8b');
// 获取AI回答
const aiAnswers = document.querySelectorAll('.ds-markdown.ds-markdown--block');
for (let i = 0; i < userQuestions.length; i++) {
// 用户问题
const userQuestion = userQuestions[i];
messages.push({
role: 'user',
html: userQuestion.innerHTML,
text: userQuestion.innerText,
markdown: userQuestion.getAttribute('data-markdown') || userQuestion.innerText
});
// AI思考过程
if (i < aiThoughts.length) {
const aiThought = aiThoughts[i];
messages.push({
role: 'ai',
html: aiThought.innerHTML,
text: aiThought.innerText,
markdown: aiThought.getAttribute('data-markdown') || aiThought.innerText
});
}
// AI回答
if (i < aiAnswers.length) {
const aiAnswer = aiAnswers[i];
messages.push({
role: 'ai',
html: aiAnswer.innerHTML,
text: aiAnswer.innerText,
markdown: aiAnswer.getAttribute('data-markdown') || aiAnswer.innerText
});
}
}
console.log('提取的消息:', messages);
return { title, messages };
}
function convertToMarkdown(messages) {
let md = `# ${messages.title}\n\n`;
messages.forEach(msg => {
md += `**${msg.role === 'user' ? CONFIG.USER_PREFIX : CONFIG.AI_PREFIX}**:\n`;
// 如果存在原始 Markdown 内容,直接使用
if (msg.markdown) {
md += `${msg.markdown}\n\n---\n\n`;
return;
}
// 如果没有原始 Markdown 内容,则使用 HTML 转换
const tempDiv = document.createElement('div');
tempDiv.innerHTML = msg.html;
// 处理标题
const headings = tempDiv.querySelectorAll('h1, h2, h3, h4, h5, h6');
headings.forEach(heading => {
const level = parseInt(heading.tagName[1]);
const text = heading.textContent.trim();
heading.outerHTML = `${'#'.repeat(level)} ${text}\n\n`;
});
// 处理表格
const tables = tempDiv.querySelectorAll('table');
tables.forEach(table => {
let tableMarkdown = '\n';
const rows = table.querySelectorAll('tr');
// 处理表头
const headers = rows[0].querySelectorAll('th, td');
let headerRow = '|';
let separatorRow = '|';
headers.forEach(header => {
const text = header.textContent.trim();
headerRow += ` ${text} |`;
separatorRow += ' --- |';
});
tableMarkdown += headerRow + '\n' + separatorRow + '\n';
// 处理数据行
for (let i = 1; i < rows.length; i++) {
const cells = rows[i].querySelectorAll('td');
let rowMarkdown = '|';
cells.forEach(cell => {
const text = cell.textContent.trim();
rowMarkdown += ` ${text} |`;
});
tableMarkdown += rowMarkdown + '\n';
}
table.outerHTML = tableMarkdown + '\n';
});
// 处理代码块
const codeBlocks = tempDiv.querySelectorAll('pre code');
codeBlocks.forEach(block => {
const language = block.className.split('-')[1] || '';
const code = block.textContent;
block.outerHTML = `\`\`\`${language}\n${code}\n\`\`\`\n\n`;
});
// 处理链接
const links = tempDiv.querySelectorAll('a');
links.forEach(link => {
const text = link.textContent;
const href = link.href;
link.outerHTML = `[${text}](${href})`;
});
// 处理加粗文本
const boldTexts = tempDiv.querySelectorAll('strong, b');
boldTexts.forEach(bold => {
bold.outerHTML = `**${bold.textContent}**`;
});
// 处理斜体文本
const italicTexts = tempDiv.querySelectorAll('em, i');
italicTexts.forEach(italic => {
italic.outerHTML = `*${italic.textContent}*`;
});
// 处理列表
const lists = tempDiv.querySelectorAll('ul, ol');
lists.forEach(list => {
const items = list.querySelectorAll('li');
let listContent = '\n';
items.forEach((item, index) => {
const prefix = list.tagName === 'OL' ? `${index + 1}. ` : '- ';
listContent += `${prefix}${item.textContent}\n`;
});
list.outerHTML = listContent + '\n';
});
// 处理引用
const quotes = tempDiv.querySelectorAll('blockquote');
quotes.forEach(quote => {
quote.outerHTML = `> ${quote.textContent}\n\n`;
});
// 处理段落和换行
const paragraphs = tempDiv.querySelectorAll('p, div');
paragraphs.forEach(p => {
if (!p.querySelector('pre, blockquote, ul, ol, table, h1, h2, h3, h4, h5, h6')) {
const text = p.textContent.trim();
if (text) {
p.outerHTML = `${text}\n\n`;
}
}
});
// 处理换行符
const brs = tempDiv.querySelectorAll('br');
brs.forEach(br => {
br.outerHTML = '\n';
});
// 获取最终文本并清理多余的空行
let text = tempDiv.innerText
.replace(/\n{3,}/g, '\n\n') // 清理多余的空行
.trim();
md += `${text}\n\n---\n\n`;
});
md += `> 保存时间: ${new Date().toLocaleString()}\n`;
return md;
}
function convertToText(messages) {
let text = `${messages.title}\n\n`;
messages.forEach(msg => {
text += `${msg.role === 'user' ? CONFIG.USER_PREFIX : CONFIG.AI_PREFIX}:\n`;
text += `${msg.text}\n\n---\n\n`;
});
text += `保存时间: ${new Date().toLocaleString()}\n`;
return text;
}
function wrapMessagesInHtmlDocument(messages) {
const { title, messages: messageArray } = messages;
if (!Array.isArray(messageArray)) {
console.error('messageArray 不是一个数组:', messageArray);
return '';
}
let html = `<!DOCTYPE html>
${title}
`;messageArray.forEach(msg => {
const roleClass = msg.role === 'user' ? 'user-message' : 'ai-message';
html += `<div class="message ${roleClass}">\n`;
html += `<div class="message-header">${msg.role === 'user' ? CONFIG.USER_PREFIX : CONFIG.AI_PREFIX}</div>\n`;
html += `<div class="message-content">${msg.html}</div>\n`;
html += `</div>\n\n`;
});
html += `<hr>\n`;
html += `<div class="timestamp">保存时间: ${new Date().toLocaleString()}</div>\n`;
html += `</body>
`;
return html;
}
function downloadFile(content, filename, format) {
let blob;
let mimeType;
if (format === 'pdf' || format === 'png') {
// 确保 content 是一个包含 title 和 messages 的对象
if (!content.title || !content.messages) {
console.error('无效的内容格式:', content);
showErrorMessage('保存失败:内容格式错误');
return;
}
if (format === 'pdf' && typeof window.jspdf === 'undefined') {
console.error('jsPDF library not loaded');
showErrorMessage('PDF保存失败:缺少必要的库文件');
return;
}
if (format === 'png' && typeof html2canvas === 'undefined') {
console.error('html2canvas library not loaded');
showErrorMessage('PNG保存失败:缺少必要的库文件');
return;
}
try {
const htmlContent = wrapMessagesInHtmlDocument(content);
const container = document.createElement('div');
container.innerHTML = htmlContent;
document.body.appendChild(container);
if (format === 'pdf') {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.setFont('helvetica');
doc.setFontSize(12);
html2canvas(container).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const imgWidth = 210;
const pageHeight = 295;
const imgHeight = canvas.height * imgWidth / canvas.width;
let heightLeft = imgHeight;
let position = 0;
doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
doc.addPage();
doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
const url = URL.createObjectURL(doc.output('blob'));
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 100);
}).catch(error => {
console.error('PDF生成失败:', error);
showErrorMessage('PDF生成失败,请重试');
}).finally(() => {
document.body.removeChild(container);
});
} else {
const options = {
scale: 2,
logging: false,
useCORS: true,
allowTaint: true,
backgroundColor: '#f5f5f5'
};
html2canvas(container, options).then(canvas => {
canvas.toBlob(blob => {
if (!blob) {
showErrorMessage('PNG生成失败,请重试');
return;
}
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 100);
}, 'image/png', 1.0);
}).catch(error => {
console.error('PNG生成失败:', error);
showErrorMessage('PNG生成失败,请重试');
}).finally(() => {
document.body.removeChild(container);
});
}
return;
} catch (error) {
console.error(`${format.toUpperCase()}生成失败:`, error);
showErrorMessage(`${format.toUpperCase()}生成失败,请重试`);
return;
}
}
// 处理其他格式
switch (format) {
case 'md':
mimeType = 'text/markdown;charset=utf-8';
break;
case 'txt':
mimeType = 'text/plain;charset=utf-8';
break;
case 'html':
mimeType = 'text/html;charset=utf-8';
break;
default:
mimeType = 'text/plain;charset=utf-8';
}
blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 100);
}
function showErrorMessage(message) {
const errorDiv = document.createElement('div');
errorDiv.innerText = message;
errorDiv.style.position = 'fixed';
errorDiv.style.bottom = '80px';
errorDiv.style.right = '20px';
errorDiv.style.backgroundColor = '#f44336';
errorDiv.style.color = 'white';
errorDiv.style.padding = '10px';
errorDiv.style.borderRadius = '5px';
errorDiv.style.zIndex = '1000';
document.body.appendChild(errorDiv);
setTimeout(() => document.body.removeChild(errorDiv), 3000);
}
function saveChatRecord() {
const { title, messages } = extractMessages();
if (messages.length === 0) {
console.error("未找到对话内容");
return;
}
let content;
const filename = `${title}.${CONFIG.SELECTED_FORMAT}`;
switch (CONFIG.SELECTED_FORMAT) {
case 'md':
content = convertToMarkdown(messages);
break;
case 'txt':
content = convertToText(messages);
break;
case 'html':
content = wrapMessagesInHtmlDocument({title, messages});
break;
case 'pdf':
case 'png':
content = {title, messages}; // 传递包含 title 和 messages 的对象
break;
default:
content = convertToText(messages);
}
downloadFile(content, filename, CONFIG.SELECTED_FORMAT);
showSuccessMessage();
}
function showSuccessMessage() {
const message = document.createElement('div');
message.innerText = '保存成功!';
message.style.position = 'fixed';
message.style.bottom = '80px';
message.style.right = '20px';
message.style.backgroundColor = '#4CAF50';
message.style.color = 'white';
message.style.padding = '10px';
message.style.borderRadius = '5px';
document.body.appendChild(message);
setTimeout(() => document.body.removeChild(message), 3000);
}
function startAutoSave() {
stopAutoSave(); // 先停止现有的自动保存
if (CONFIG.AUTO_SAVE_ENABLED) {
autoSaveIntervalId = setInterval(() => {
try {
saveChatRecord();
} catch (error) {
console.error('自动保存失败:', error);
}
}, CONFIG.AUTO_SAVE_INTERVAL);
}
}
function stopAutoSave() {
if (autoSaveIntervalId) {
clearInterval(autoSaveIntervalId);
autoSaveIntervalId = null;
}
}
function createSaveButton() {
const button = document.createElement('button');
button.id = CONFIG.BUTTON_ID;
button.innerText = '保存对话';
button.style.position = 'fixed';
button.style.bottom = '20px';
button.style.right = '20px';
button.style.padding = '15px 30px';
button.style.fontSize = '16px';
button.style.backgroundColor = '#4CAF50';
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '5px';
button.style.cursor = 'pointer';
button.style.zIndex = '1000';
button.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.3)';
document.body.appendChild(button);
return button;
}
function setupButtonHandler(btn) {
btn.addEventListener('click', saveChatRecord);
}
function initialize() {
const btn = createSaveButton();
setupButtonHandler(btn);
startAutoSave();
// 监听存储变化,更新配置
chrome.storage.onChanged.addListener(function(changes, namespace) {
if ('autoSaveEnabled' in changes) {
CONFIG.AUTO_SAVE_ENABLED = changes.autoSaveEnabled.newValue;
startAutoSave();
}
if ('autoSaveInterval' in changes) {
CONFIG.AUTO_SAVE_INTERVAL = changes.autoSaveInterval.newValue;
startAutoSave();
}
if ('selectedFormat' in changes) {
CONFIG.SELECTED_FORMAT = changes.selectedFormat.newValue;
}
});
}
if (document.readyState === 'complete') initialize();
else window.addEventListener('load', initialize);
});
堆叠追踪
content.js:557 (saveChatRecord)
content.js:606 (匿名函数)
貌似是 message.innerText = ‘保存成功!’; 和 document.body.appendChild(button); 这个按钮没有对上号,帮我修复下
时间: 2025-04-07T20:03:06
🤖 DeepSeek (deepseek-chat)
问题出在按钮创建和消息显示的冲突上。根据错误堆栈,可能是按钮还未正确添加到页面时就尝试保存,或者消息显示时DOM还未准备好。以下是修复后的代码:
1 | // 从存储中获取配置 |
主要修改点:
- 添加了全局的
saveButton变量来保存按钮引用 - 改进了DOM检测机制,确保在创建按钮和显示消息前文档已准备好
- 添加了更严格的错误检查和清理逻辑
- 调整了z-index确保消息显示在按钮上方
- 改进了初始化流程,使用间隔检查确保DOM完全加载
- 添加了按钮存在性检查,避免重复创建
- 改进了消息显示和移除的逻辑
这些修改应该能解决按钮和消息显示的冲突问题。如果还有问题,可能需要检查:
- 页面是否有其他脚本干扰
- 是否有CSS样式覆盖了按钮或消息的定位
- 是否有其他错误阻止了脚本执行
时间: 2025-04-07T20:03:06
👤 用户
function showSuccessMessage() {
const message = document.createElement(‘div’);
message.innerText = ‘保存成功!’;
message.style.position = ‘fixed’;
message.style.bottom = ‘80px’;
message.style.right = ‘20px’;
message.style.backgroundColor = ‘#4CAF50’;
message.style.color = ‘white’;
message.style.padding = ‘10px’;
message.style.borderRadius = ‘5px’;
document.body.appendChild(message);
setTimeout(() => document.body.removeChild(message), 3000);
}
为什么弹出的提示只有一点点宽
时间: 2025-04-07T20:09:40
🤖 DeepSeek (deepseek-chat)
你的提示消息宽度看起来很小是因为你没有设置明确的宽度,而且消息内容很短(只有”保存成功!”几个字),所以它只会根据内容自动调整宽度。要解决这个问题,你可以:
- 设置一个固定的最小宽度:
1 | message.style.minWidth = '200px'; |
- 或者设置宽度并让文本居中:
1 | message.style.width = '200px'; |
- 或者添加一些内边距让消息看起来更饱满:
1 | message.style.padding = '15px 25px'; |
修改后的完整函数:
1 | function showSuccessMessage() { |
这样修改后,提示消息会:
- 有足够的内边距
- 保持最小宽度
- 文字居中显示
- 看起来更美观专业
如果你想要更精致的效果,还可以考虑添加CSS动画或使用现成的通知库,但上面的修改已经能满足基本需求了。
时间: 2025-04-07T20:09:40
对话统计
- 消息总数: 4 条
- 处理状态: 成功转换