├── README-en.md
├── README.md
├── doc
├── add-to-chrome.png
├── example-deepseek.png
├── example-image.png
├── example-markdown.png
├── example-pdf.png
├── example.png
├── pic.png
├── popup.png
├── step1.png
├── step2.png
├── step3.png
└── step4.png
└── src
├── background.js
├── content.js
├── icons
├── icon120.png
├── icon128.png
├── icon16.png
├── icon32.png
├── icon48.png
└── icon96.png
├── lib
├── canvas2image.js
├── html2canvas.min.js
└── marked.min.js
├── manifest.json
├── popup.html
├── popup.js
└── style.css
/README-en.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 | Export DeepSeek conversations to Markdown files
10 |
11 | [Chrome Web Store](https://chromewebstore.google.com/detail/deepseek2markdown/jfolcdbejlennfldgbninbjglbahaejn)
12 |
13 | > If the button does not respond, please refresh the page and try again.
14 |
15 | ## Features
16 |
17 | Plugin interface
18 |
19 |
20 |
21 |
22 |
23 | >If the code blocks do not display correctly when exporting images, try exporting again.
24 |
25 | Original conversation / Exported Markdown text (typora theme is bluetex):
26 |
27 |
28 |
29 |
30 |
31 | Original conversation / Exported PDF
32 |
33 |
34 |
35 |
36 |
37 | Original conversation / Exported image
38 |
39 |
40 |
41 |
42 |
43 | ## Usage
44 |
45 | ### Method 1: Download directly from the [Chrome Store](https://chromewebstore.google.com/detail/deepseek2markdown/jfolcdbejlennfldgbninbjglbahaejn)
46 |
47 | [link](https://chromewebstore.google.com/detail/deepseek2markdown/jfolcdbejlennfldgbninbjglbahaejn)
48 |
49 | 
50 |
51 | ### Method 2: Load unpacked files
52 |
53 | 1. Open the Chrome browser's extension loading page chrome://extensions/
54 |
55 |
56 |
57 |
58 |
59 | 2. Load the unpacked extension. Assuming the downloaded directory is D:\code\github\deepseek2markdown, you should open D:\code\github\deepseek2markdown\src and click "Select Folder" to load the contents of the src folder.
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | 3. Pin the plugin in the plugin bar
70 |
71 | **Note: If you have opened the DeepSeek webpage before using the plugin for the first time, you should refresh it first.**
72 |
73 |
74 |
75 |
76 |
77 | 4. Click the button to extract the current conversation's markdown file. You can choose whether to export the thought chain.
78 |
79 |
80 |
81 |
82 |
83 | ## To-Do List
84 |
85 | - [ ] Support exporting to PDF
86 | - [x] Titles, thought chains, text styles (bold, italic, strikethrough), lists, tables, links and images, code, quotes, dividers, special symbols
87 | - [ ] Code block style optimization, support for formula export, support for highlighted text
88 |
89 | v0.3
90 |
91 | - [x] Support exporting specific messages from a conversation (custom selection of paragraphs, export only questions or answers)
92 | - [x] Popup supports switching between Chinese and English
93 |
94 | v0.2
95 |
96 | - [x] Support strict mode Markdown export
97 | - [x] Support filtering out server busy messages
98 | - [x] Added image export function
99 |
100 | v0.1
101 |
102 | - [x] Popup interface beautification
103 | - [x] Optimized markdown text export format
104 | - [x] Option to export thought chain
105 | - [x] Optimized code export style
106 | - [x] Support for exporting tables
107 | - [x] Support for exporting images
108 | - [x] Optimized export style for multi-level lists
109 |
110 | ## Feedback and Contributions
111 |
112 | If you encounter any issues or have suggestions for improvement, please feel free to submit an Issue or PR.
113 |
114 | ## Acknowledgments
115 |
116 | [DeepSeek-Chat-Exporter](https://github.com/blueberrycongee/DeepSeek-Chat-Exporter)
117 |
118 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 | 导出DeepSeek的对话到Markdown文件
10 |
11 | [Chrome应用商店](https://chromewebstore.google.com/detail/deepseek2markdown/jfolcdbejlennfldgbninbjglbahaejn)
12 |
13 | > 如果点击按钮没反应,请直接刷新页面后进行尝试。
14 |
15 | ## 特性
16 |
17 | 插件界面
18 |
19 |
20 |
21 |
22 |
23 | > 导出图像的时候如果代码块显示不正常,可以尝试重新导出。
24 |
25 | 原始对话 / 导出Markdown文本(typora主题为[bluetex](https://github.com/DaYangtuo247/typora-blueTex-theme)):
26 |
27 |
28 |
29 |
30 |
31 | 原始对话 / 导出PDF
32 |
33 |
34 |
35 |
36 |
37 | 原始对话 / 导出图像
38 |
39 |
40 |
41 |
42 |
43 | ## 使用方法
44 |
45 | ### 方法一:直接从Chrome商店下载
46 |
47 | 直接从[Chrome商店](https://chromewebstore.google.com/detail/deepseek2markdown/jfolcdbejlennfldgbninbjglbahaejn)下载
48 |
49 | 
50 |
51 | ### 方法二:加载已解压的文件
52 |
53 | 1. 打开Chrome浏览器的加载扩展程序页面 [chrome://extensions/](chrome://extensions/)
54 |
55 |
56 |
57 |
58 |
59 | 2. 加载已解压的扩展程序。假设我下载的目录为D:\code\github\deepseek2markdown,则应该打开D:\code\github\deepseek2markdown\src 并点击“选择文件夹”,加载src文件夹中的内容。
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | 3. 在插件栏固定插件
70 |
71 | **注意:首次加载插件使用之前如果打开了DeepSeek的网页,应该先刷新一下。**
72 |
73 |
74 |
75 |
76 |
77 | 4. 点击按钮提取当前对话的markdown文件,可勾选是否导出思维链
78 |
79 |
80 |
81 |
82 |
83 | ## 待办事项
84 |
85 | - [ ] 支持导出为PDF
86 | - [x] 标题、思维链、文本样式(粗体、斜体、删除线)、列表、表格、链接和图片、代码、引用、分割线、特殊符号
87 | - [ ] 代码块样式优化、支持公式导出、支持高亮文本
88 |
89 | v0.3
90 |
91 | - [x] 支持选择一段对话中的特定消息导出(自定义选择段落、只导出提问或者回答)
92 | - [x] Popup界面支持中英文切换
93 |
94 | v0.2
95 |
96 | - [x] markdown严格模式导出
97 | - [x] 支持过滤掉服务器繁忙的消息
98 | - [x] 添加图片导出功能
99 |
100 | v0.1
101 |
102 | - [x] popup界面美化
103 | - [x] 导出markdown文本格式优化
104 | - [x] 可选择是否导出思维链
105 | - [x] 优化代码导出样式
106 | - [x] 支持导出表格
107 | - [x] 支持导出图片
108 | - [x] 优化多层列表的导出样式
109 |
110 | ## 反馈与贡献
111 |
112 | 如果您在使用过程中遇到问题或有改进建议,欢迎提交 Issue 或 PR
113 |
114 | ## 致谢
115 |
116 | [DeepSeek-Chat-Exporter](https://github.com/blueberrycongee/DeepSeek-Chat-Exporter)
117 |
--------------------------------------------------------------------------------
/doc/add-to-chrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/doc/add-to-chrome.png
--------------------------------------------------------------------------------
/doc/example-deepseek.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/doc/example-deepseek.png
--------------------------------------------------------------------------------
/doc/example-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/doc/example-image.png
--------------------------------------------------------------------------------
/doc/example-markdown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/doc/example-markdown.png
--------------------------------------------------------------------------------
/doc/example-pdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/doc/example-pdf.png
--------------------------------------------------------------------------------
/doc/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/doc/example.png
--------------------------------------------------------------------------------
/doc/pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/doc/pic.png
--------------------------------------------------------------------------------
/doc/popup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/doc/popup.png
--------------------------------------------------------------------------------
/doc/step1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/doc/step1.png
--------------------------------------------------------------------------------
/doc/step2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/doc/step2.png
--------------------------------------------------------------------------------
/doc/step3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/doc/step3.png
--------------------------------------------------------------------------------
/doc/step4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/doc/step4.png
--------------------------------------------------------------------------------
/src/background.js:
--------------------------------------------------------------------------------
1 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2 | if (request.action === 'export') {
3 | chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
4 | chrome.scripting.executeScript({
5 | target: { tabId: tabs[0].id },
6 | function: extractConversation
7 | }, (results) => {
8 | const markdown = results[0].result;
9 | sendResponse({ markdown });
10 | });
11 | });
12 | return true; // 保持消息通道打开以等待响应
13 | }
14 | });
--------------------------------------------------------------------------------
/src/content.js:
--------------------------------------------------------------------------------
1 | // =====================
2 | // 配置
3 | // =====================
4 | const config = {
5 | mainPageSelector: '.cb86951c', // 主页面
6 | chatContainerSelector: '.dad65929', // 聊天框容器
7 |
8 | userClassPrefix: '_9663006', // 用户消息 class 前缀
9 |
10 | aiClassPrefix: '_4f9bf79', // AI消息相关 class 前缀
11 |
12 | aiChainOfThought: '._48edb25', // AI的思维链, 包含"已深度思考xxx秒"和"思考过程"
13 | searchHintSelector: '._58a6d71._19db599', // 搜索/思考时间, "已深度思考xxx秒"
14 | thinkingChainSelector: '.e1675d8b', // 思考链, "思考过程"
15 |
16 | userSessionTitleSelector: '.d8ed659a', // 用户会话标题
17 | finalAnswerSelector: 'div.ds-markdown.ds-markdown--block', // 回答的内容
18 | isExportChainOfThought: false, // 是否导出思考链
19 | isExportBusyServerMessages: false, // 是否导出繁忙消息,默认不导出
20 | };
21 |
22 | function initConfig() {
23 | config.isExportChainOfThought = false;
24 | config.isExportBusyServerMessages = false;
25 | }
26 |
27 | // =====================
28 | // 工具函数
29 | // =====================
30 | function isUserMessage(node) {
31 | return node.classList.contains(config.userClassPrefix);
32 | }
33 |
34 | function isAIMessage(node) {
35 | return node.classList.contains(config.aiClassPrefix);
36 | }
37 |
38 | function getUserSessionTitle() {
39 | const thinkingNode = document.querySelector(config.userSessionTitleSelector);
40 | return thinkingNode ? `${thinkingNode.textContent.trim()}` : 'DeepSeek_Chat_Export';
41 | }
42 |
43 | // 已深度思考xxx秒
44 | function extractSearchOrThinking(node) {
45 | const hintNode = node.querySelector(config.searchHintSelector);
46 | return hintNode ? `**${hintNode.textContent.trim()}**` : null;
47 | }
48 |
49 | // 思考过程
50 | function extractThinkingChain(node) {
51 | const thinkingNode = node.querySelector(config.thinkingChainSelector);
52 | const elements = thinkingNode.querySelectorAll('p');
53 | output = '';
54 | elements.forEach((element) => {
55 | if (element.textContent.trim() != '') {
56 | output += `> ${element.textContent.trim()}\n`;
57 | } else {
58 | output += '> \n';
59 | }
60 | });
61 | return thinkingNode ? output : null;
62 | }
63 |
64 | // 下载markdown内容
65 | function downloadMarkdown(markdown, title) {
66 | const blob = new Blob([markdown], { type: 'text/markdown' });
67 | const url = URL.createObjectURL(blob);
68 | const a = document.createElement('a');
69 | a.href = url;
70 | a.download = title + '.md';
71 | a.click();
72 | }
73 |
74 | // 下载PDF内容
75 | function downloadPDF(pdf) {
76 | const printWindow = window.open("", "_blank");
77 | printWindow.document.write(pdf);
78 | printWindow.document.close();
79 | setTimeout(() => { printWindow.print(); }, 500);
80 | }
81 |
82 | // 提取AI回答的内容
83 | function resolveTag_text(node) {
84 | let content = '';
85 | node.childNodes.forEach((childNode) => {
86 | if (childNode.nodeType === Node.TEXT_NODE) {
87 | content += childNode.textContent.trim();
88 | }
89 | else if (childNode.tagName === 'P') {
90 | if (childNode.childNodes.length > 1) {
91 | content += resolveTag_text(childNode)
92 | } else {
93 | content += childNode.textContent.trim();
94 | }
95 | }
96 | else if (childNode.tagName === 'STRONG') {
97 | if (childNode.childNodes.length > 1) {
98 | content += resolveTag_text(childNode)
99 | } else {
100 | content += `**${childNode.textContent.trim()}**`;
101 | }
102 | }
103 | else if (childNode.tagName === 'CODE') {
104 | content += `\`${childNode.textContent.trim()}\``;
105 | }
106 | else if (childNode.tagName === 'EM') {
107 | content += `*${childNode.textContent.trim()}*`;
108 | }
109 | else if (childNode.tagName === 'A') {
110 | const href = childNode.getAttribute('href');
111 | content += `[${childNode.textContent.trim()}](${href})`;
112 | }
113 | else if (childNode.tagName === 'BR') {
114 | content += '\n';
115 | }
116 | else if (childNode.tagName === 'IMG') {
117 | const src = childNode.getAttribute('src');
118 | content += ``;
119 | }
120 | else if (childNode.tagName === 'SPAN' && childNode.classList.contains('ds-markdown-cite')) {
121 | content += '';
122 | }
123 | else if (childNode.classList && childNode.classList.contains('katex')) {
124 | const tex = childNode.querySelector('annotation[encoding="application/x-tex"]');
125 | if (tex) {
126 | content += `$${tex.textContent.trim()}$`;
127 | }
128 | }
129 | else if (childNode.nodeType === Node.ELEMENT_NODE) {
130 | content += childNode.textContent.trim();
131 | }
132 | });
133 | return content;
134 | }
135 |
136 | function resolveTag_pre(preElement, language = 'python') {
137 | // 1. 提取 中的文本内容
138 | const codeContent = preElement.textContent || preElement.innerText;
139 |
140 | // 2. 格式化为 Markdown 代码块
141 | const markdownCodeBlock = `\`\`\`\`${language}\n${codeContent}\n\`\`\`\``;
142 |
143 | return markdownCodeBlock;
144 | }
145 |
146 | function resolveTag_table(tableElement) {
147 | let markdown = '';
148 |
149 | // 1. 处理表头
150 | const headerRow = tableElement.querySelector('thead tr');
151 | if (headerRow) {
152 | const headers = Array.from(headerRow.querySelectorAll('th')).map(
153 | (th) => th.textContent.trim()
154 | );
155 | markdown += `| ${headers.join(' | ')} |\n`; // 表头行
156 | markdown += `| ${headers.map(() => '---').join(' | ')} |\n`; // 分隔行
157 | }
158 |
159 | // 2. 处理表格内容
160 | const bodyRows = tableElement.querySelectorAll('tbody tr');
161 | bodyRows.forEach((row) => {
162 | const cells = Array.from(row.querySelectorAll('td')).map((td) =>
163 | td.textContent.trim()
164 | );
165 | markdown += `| ${cells.join(' | ')} |\n`; // 数据行
166 | });
167 |
168 | return markdown.trim(); // 去除末尾换行
169 | }
170 |
171 | function resolveTag_ul_li(node) {
172 | let markdown = '';
173 |
174 | // 递归处理子节点
175 | function processNode(element, depth = 0) {
176 | // 用于存储最终的 Markdown 内容
177 | let markdown = '';
178 |
179 | // UL 标签
180 | if (element.tagName === 'UL') {
181 | // 遍历 或 的子元素
182 | for (let child of element.children) {
183 | if (child.tagName === 'LI') {
184 | // 获取当前层级的缩进空格
185 | let indent = ' '.repeat(depth * 4);
186 | // 获取 内的文本内容
187 | child.childNodes.forEach((childNode) => {
188 | if (childNode.tagName === 'P') {
189 | markdown += `${indent}- ${resolveTag_text(childNode)}\n`;
190 | }
191 | else if (childNode.classList.contains('md-code-block')) {
192 | codeblock = resolveTag_pre(childNode.querySelector('pre'), childNode.querySelector('.md-code-block-infostring').textContent.trim());
193 | codeblock = codeblock.replaceAll('\n', `\n${indent}`);
194 | markdown += `${indent}${codeblock}\n`;
195 | }
196 | });
197 |
198 | let nestedList = child.querySelector('ul, ol');
199 | if (nestedList) {
200 | markdown += processNode(nestedList, depth + 1);
201 | }
202 | }
203 | }
204 | }
205 | else if (element.tagName === 'OL') {
206 | // 遍历 或 的子元素
207 | for (let child of element.children) {
208 | if (child.tagName === 'LI') {
209 | // 获取当前层级的缩进空格
210 | let indent = ' '.repeat(depth * 4);
211 | // 获取 内的文本内容
212 | child.childNodes.forEach((childNode) => {
213 | if (childNode.tagName === 'P') {
214 | markdown += `${indent}1. ${resolveTag_text(childNode)}\n`;
215 | }
216 | else if (childNode.classList.contains('md-code-block')) {
217 | codeblock = resolveTag_pre(childNode.querySelector('pre'), childNode.querySelector('.md-code-block-infostring').textContent.trim());
218 | codeblock = codeblock.replaceAll('\n', `\n${indent}`);
219 | markdown += `${indent}${codeblock}\n`;
220 | }
221 | });
222 |
223 | let nestedList = child.querySelector('ul, ol');
224 | if (nestedList) {
225 | markdown += processNode(nestedList, depth + 1);
226 | }
227 | }
228 | }
229 | }
230 | return markdown;
231 | }
232 | // 开始处理根节点
233 | markdown = processNode(node);
234 | return markdown.trim(); // 去除首尾空白
235 | }
236 |
237 | function extractFinalAnswer(node) {
238 | const answerNode = node.querySelector(config.finalAnswerSelector);
239 | if (!answerNode) return null;
240 |
241 | let answerContent = '';
242 | const elements = answerNode.querySelectorAll('.ds-markdown--block>.md-code-block, \
243 | .ds-markdown--block>p,\
244 | .ds-markdown--block>h1, \
245 | .ds-markdown--block>h2,\
246 | .ds-markdown--block>h3, \
247 | .ds-markdown--block>h4, \
248 | .ds-markdown--block>h5, \
249 | .ds-markdown--block>blockquote,\
250 | .ds-markdown--block>ol,\
251 | .ds-markdown--block>ul,\
252 | .ds-markdown--block>table,\
253 | .katex-display.ds-markdown-math, \
254 | hr');
255 |
256 | elements.forEach((element) => {
257 | if (element.tagName.toLowerCase() === 'p') {
258 | answerContent += resolveTag_text(element);
259 | answerContent += '\n\n';
260 | }
261 | else if (element.tagName.toLowerCase() === 'h1') {
262 | answerContent += `# ${resolveTag_text(element)}\n\n`;
263 | }
264 | else if (element.tagName.toLowerCase() === 'h2') {
265 | answerContent += `## ${resolveTag_text(element)}\n\n`;
266 | }
267 | else if (element.tagName.toLowerCase() === 'h3') {
268 |
269 | answerContent += `### ${resolveTag_text(element)}\n\n`;
270 | }
271 | else if (element.tagName.toLowerCase() === 'h4') {
272 | answerContent += `#### ${resolveTag_text(element)}\n\n`;
273 | }
274 | else if (element.tagName.toLowerCase() === 'h5') {
275 | answerContent += `##### ${resolveTag_text(element)}\n\n`;
276 | }
277 | else if (element.tagName.toLowerCase() === 'hr') {
278 | answerContent += '\n---\n';
279 | }
280 | else if (element.tagName.toLowerCase() === 'blockquote') {
281 | answerContent += `> ${resolveTag_text(element.querySelector('p'))}\n\n`;
282 | }
283 | else if (element.tagName.toLowerCase() === 'ul' || element.tagName.toLowerCase() === 'ol') {
284 | answerContent += `${resolveTag_ul_li(element)}\n\n`;
285 | }
286 | else if (element.tagName.toLowerCase() === 'table') {
287 | answerContent += `${resolveTag_table(element)}\n\n`;
288 | }
289 | else if (element.classList.contains('katex-display')) {
290 | const tex = element.querySelector('annotation[encoding="application/x-tex"]');
291 | if (tex) {
292 | answerContent += `$${tex.textContent.trim()}$\n\n`;
293 | }
294 | }
295 | else if (element.classList.contains('md-code-block')) {
296 | codeblock = resolveTag_pre(element.querySelector('pre'), element.querySelector('.md-code-block-infostring').textContent.trim());
297 | answerContent += `${codeblock}\n\n`;
298 | }
299 | });
300 |
301 | return `${answerContent.trim()}`;
302 | }
303 |
304 | function getFilteredContainer() {
305 | const chatContainer = document.querySelector(config.chatContainerSelector);
306 | if (!chatContainer) {
307 | console.error('未找到聊天容器');
308 | return messages;
309 | }
310 | for (const node of chatContainer.children) {
311 | if (isAIMessage(node)) {
312 | const thinkingChainNode = node.querySelector(`${config.aiChainOfThought}`);
313 |
314 | if (!config.isExportChainOfThought && thinkingChainNode && node.contains(thinkingChainNode)) {
315 | node.removeChild(thinkingChainNode);
316 | }
317 | }
318 | }
319 | return chatContainer;
320 | }
321 |
322 | function getOrderedMessages() {
323 | const messages = [];
324 | const chatContainer = document.querySelector(config.chatContainerSelector);
325 | if (!chatContainer) {
326 | console.error('未找到聊天容器');
327 | return messages;
328 | }
329 |
330 | for (const node of chatContainer.children) {
331 | if (isUserMessage(node)) {
332 | // 用户消息
333 | const userMessage = `# 用户:\n\n${node.textContent.trim()}`;
334 |
335 | const nextNode = node.nextElementSibling;
336 | if (nextNode && isAIMessage(nextNode)) {
337 | const finalAnswer = extractFinalAnswer(nextNode);
338 | if (finalAnswer && finalAnswer.includes("服务器繁忙,请稍后再试。") && !config.isExportBusyServerMessages) {
339 | continue; // 跳过当前用户消息
340 | }
341 | }
342 | messages.push(userMessage);
343 | } else if (isAIMessage(node)) {
344 | // AI 消息
345 | let output = '';
346 | const aiChainOfThought = node.querySelector(`${config.aiChainOfThought}`);
347 | if (aiChainOfThought && config.isExportChainOfThought) {
348 | // 已深度思考xxx秒
349 | const searchHint = extractSearchOrThinking(aiChainOfThought);
350 | if (searchHint) output += `> ${searchHint}\n> \n`;
351 | // 思考过程
352 | const thinkingChain = extractThinkingChain(aiChainOfThought);
353 | if (thinkingChain) output += `${thinkingChain}\n`;
354 | } else {
355 | const searchHint = extractSearchOrThinking(node);
356 | if (searchHint) output += `${searchHint}\n`;
357 | }
358 | const finalAnswer = extractFinalAnswer(node);
359 | if (finalAnswer && finalAnswer.includes("服务器繁忙,请稍后再试。") && !config.isExportBusyServerMessages) {
360 | continue; // 跳过当前AI消息
361 | }
362 | if (finalAnswer) output = `# DeepSeek:\n${output}\n**原始回答**\n\n${finalAnswer}\n\n`;
363 | if (output.trim()) {
364 | messages.push(output.trim());
365 | }
366 | }
367 | }
368 | return messages;
369 | }
370 |
371 | function generateMdContent(messages = '') {
372 | if (!messages) {
373 | messages = getOrderedMessages();
374 | }
375 | return messages.length ? messages.join('\n\n') : '';
376 | }
377 |
378 | // =====================
379 | // 导出功能
380 | // =====================
381 | function exportMarkdown() {
382 | const mdContent = generateMdContent();
383 | if (!mdContent) {
384 | alert("未找到聊天记录!");
385 | return;
386 | }
387 | return mdContent;
388 | }
389 |
390 | // TODO
391 | function exportPDF() {
392 | const mdContent = generateMdContent();
393 | if (!mdContent) {
394 | alert("未找到聊天记录!");
395 | return;
396 | }
397 |
398 | fixedMdContent = marked.parse(mdContent)
399 | const printContent = `
400 |
401 |
402 | ${getUserSessionTitle()}
403 |
450 |
451 |
452 | ${fixedMdContent}
453 |
454 |
455 | `;
456 | return printContent;
457 | }
458 |
459 | function exportImage() {
460 | let chatContainer = document.querySelector(config.chatContainerSelector);
461 | if (!chatContainer) {
462 | console.error('未找到聊天容器');
463 | }
464 | chatContainer = getFilteredContainer();
465 | html2canvas(chatContainer).then(function (canvas) {
466 | Canvas2Image.saveAsPNG(canvas, canvas.width, canvas.height, getUserSessionTitle());
467 | });
468 | }
469 |
470 |
471 | // ===================== 添加chrome消息通信机制 =====================
472 |
473 | // 监听来自 popup.js 的消息
474 |
475 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
476 | if (request.action === 'updateExportChainOfThoughtState') {
477 | const switchState = request.state;
478 | if (switchState) {
479 | config.isExportChainOfThought = true;
480 | } else {
481 | config.isExportChainOfThought = false;
482 | }
483 | }
484 | else if (request.action === 'updateBlockBusyMessagesState') {
485 | const switchState = request.state;
486 | if (switchState) {
487 | config.isExportBusyServerMessages = true;
488 | } else {
489 | config.isExportBusyServerMessages = false;
490 | }
491 | }
492 | else if (request.action === 'generateMarkdown') {
493 | const markdown = exportMarkdown();
494 | downloadMarkdown(markdown, getUserSessionTitle());
495 | initConfig();
496 | }
497 | else if (request.action === 'generateImage') {
498 | exportImage();
499 | initConfig();
500 | }
501 | else if (request.action === 'generatePDF') {
502 | const pdf = exportPDF();
503 | downloadPDF(pdf, getUserSessionTitle());
504 | initConfig();
505 | }
506 | else if (request.action === 'showNotificationImage') {
507 | // 创建消息框
508 | const notificationDiv = document.createElement('div');
509 | notificationDiv.id = 'notification';
510 | notificationDiv.style.position = 'fixed';
511 | notificationDiv.style.top = '20px';
512 | notificationDiv.style.left = '50%';
513 | notificationDiv.style.transform = 'translateX(-50%)';
514 | notificationDiv.style.backgroundColor = '#4CAF50';
515 | notificationDiv.style.color = 'white';
516 | notificationDiv.style.padding = '10px 25px';
517 | notificationDiv.style.borderRadius = '5px';
518 | notificationDiv.style.zIndex = '1000';
519 |
520 | notificationDiv.textContent = '开始下载...';
521 |
522 | try {
523 | exportImage()
524 | document.querySelector('.cb86951c').appendChild(notificationDiv);
525 | } catch (error) {
526 | console.log(error)
527 | notificationDiv.textContent = '下载失败';
528 | notificationDiv.style.backgroundColor = '#fa3668';
529 | document.querySelector('.cb86951c').appendChild(notificationDiv);
530 | }
531 |
532 | // 3秒后移除消息框
533 | setTimeout(() => {
534 | notificationDiv.remove();
535 | }, 2000);
536 | }
537 | else if (request.action === 'generateBatch') {
538 | const messages = getOrderedMessages();
539 | let currentPage = 0;
540 | const messagesPerPage = 10;
541 | let selectedMessages = [];
542 |
543 | // 创建模态框
544 | const modal = document.createElement('div');
545 | modal.id = 'messageModal';
546 | modal.style.position = 'fixed';
547 | modal.style.top = '0';
548 | modal.style.left = '0';
549 | modal.style.width = '100%';
550 | modal.style.height = '100%';
551 | modal.style.backgroundColor = 'rgba(255, 255, 255, 0.95)';
552 | modal.style.padding = '20px';
553 | modal.style.zIndex = '1001';
554 | modal.style.overflowY = 'auto';
555 |
556 | // 创建模态框内容
557 | const modalContent = document.createElement('div');
558 | modalContent.style.maxHeight = 'calc(100% - 100px)';
559 | modalContent.style.overflowY = 'auto';
560 |
561 | function renderMessages(page) {
562 | modalContent.innerHTML = '';
563 | const start = page * messagesPerPage;
564 | const end = start + messagesPerPage;
565 | const pageMessages = messages.slice(start, end);
566 |
567 | const table = document.createElement('table');
568 | table.style.width = '98%';
569 | table.style.borderCollapse = 'collapse';
570 |
571 | pageMessages.forEach((message, index) => {
572 | const row = document.createElement('tr');
573 | row.style.borderBottom = '1px solid #ddd';
574 |
575 | const idCell = document.createElement('td');
576 | idCell.style.padding = '8px';
577 | idCell.style.width = '25px';
578 | idCell.textContent = start + index + 1;
579 |
580 | const checkboxCell = document.createElement('td');
581 | checkboxCell.style.padding = '8px';
582 | const checkbox = document.createElement('input');
583 | checkbox.type = 'checkbox';
584 | checkbox.id = `message_${start + index}`;
585 | checkbox.value = message;
586 | checkbox.checked = selectedMessages.includes(message);
587 | checkbox.addEventListener('change', (event) => {
588 | if (event.target.checked) {
589 | selectedMessages.push(message);
590 | } else {
591 | selectedMessages = selectedMessages.filter(m => m !== message);
592 | }
593 | });
594 | checkboxCell.appendChild(checkbox);
595 |
596 | const messageCell = document.createElement('td');
597 | messageCell.style.padding = '8px';
598 | messageCell.style.color = 'black';
599 | const label = document.createElement('label');
600 | label.htmlFor = `message_${start + index}`;
601 | label.textContent = message.length > 150 ? message.substring(0, 150) + '...' : message;
602 |
603 | const toggleButton = document.createElement('button');
604 | toggleButton.textContent = '展开';
605 | toggleButton.style.marginLeft = '10px';
606 | toggleButton.style.cursor = 'pointer';
607 | toggleButton.style.background = 'none';
608 | toggleButton.style.border = 'none';
609 | toggleButton.style.color = '#4D6BFE';
610 | toggleButton.style.textDecoration = 'underline';
611 | toggleButton.addEventListener('click', () => {
612 | if (toggleButton.textContent === '展开') {
613 | label.textContent = message;
614 | toggleButton.textContent = '收起';
615 | } else {
616 | label.textContent = message.length > 150 ? message.substring(0, 150) + '...' : message;
617 | toggleButton.textContent = '展开';
618 | }
619 | });
620 |
621 | messageCell.appendChild(label);
622 | messageCell.appendChild(toggleButton);
623 |
624 | row.appendChild(idCell);
625 | row.appendChild(checkboxCell);
626 | row.appendChild(messageCell);
627 | table.appendChild(row);
628 | });
629 |
630 | modalContent.appendChild(table);
631 | }
632 |
633 | // 按钮样式
634 | const buttonStyle = {
635 | marginTop: '10px',
636 | marginRight: '10px',
637 | padding: '10px 20px',
638 | color: 'white',
639 | border: 'none',
640 | borderRadius: '5px',
641 | cursor: 'pointer'
642 | };
643 |
644 | // 创建分页按钮
645 | const paginationDiv = document.createElement('div');
646 | paginationDiv.style.textAlign = 'center';
647 | paginationDiv.style.marginTop = '20px';
648 |
649 | const prevButton = document.createElement('button');
650 | prevButton.textContent = '上一页';
651 | Object.assign(prevButton.style, buttonStyle, { backgroundColor: '#4D6BFE' });
652 | prevButton.disabled = currentPage === 0;
653 |
654 | prevButton.addEventListener('click', () => {
655 | if (currentPage > 0) {
656 | currentPage--;
657 | renderMessages(currentPage);
658 | nextButton.disabled = false;
659 | if (currentPage === 0) prevButton.disabled = true;
660 | }
661 | });
662 |
663 | const nextButton = document.createElement('button');
664 | nextButton.textContent = '下一页';
665 | Object.assign(nextButton.style, buttonStyle, { backgroundColor: '#4D6BFE' });
666 | nextButton.disabled = (currentPage + 1) * messagesPerPage >= messages.length;
667 |
668 | nextButton.addEventListener('click', () => {
669 | if ((currentPage + 1) * messagesPerPage < messages.length) {
670 | currentPage++;
671 | renderMessages(currentPage);
672 | prevButton.disabled = false;
673 | if ((currentPage + 1) * messagesPerPage >= messages.length) nextButton.disabled = true;
674 | }
675 | });
676 |
677 | paginationDiv.appendChild(prevButton);
678 | paginationDiv.appendChild(nextButton);
679 |
680 | // 创建全选按钮
681 | const selectAllButton = document.createElement('button');
682 | selectAllButton.textContent = '全选';
683 | Object.assign(selectAllButton.style, buttonStyle, { backgroundColor: '#2196F3' });
684 |
685 | selectAllButton.addEventListener('click', () => {
686 | const checkboxes = modalContent.querySelectorAll('input[type="checkbox"]');
687 | checkboxes.forEach(checkbox => {
688 | checkbox.checked = true;
689 | const message = checkbox.value;
690 | if (!selectedMessages.includes(message)) {
691 | selectedMessages.push(message);
692 | }
693 | });
694 | });
695 |
696 | // 创建反选按钮
697 | const deselectAllButton = document.createElement('button');
698 | deselectAllButton.textContent = '反选';
699 | Object.assign(deselectAllButton.style, buttonStyle, { backgroundColor: '#FF9800' });
700 |
701 | deselectAllButton.addEventListener('click', () => {
702 | const checkboxes = modalContent.querySelectorAll('input[type="checkbox"]');
703 | checkboxes.forEach(checkbox => {
704 | checkbox.checked = !checkbox.checked;
705 | const message = checkbox.value;
706 | if (checkbox.checked) {
707 | if (!selectedMessages.includes(message)) {
708 | selectedMessages.push(message);
709 | }
710 | } else {
711 | selectedMessages = selectedMessages.filter(m => m !== message);
712 | }
713 | });
714 | });
715 |
716 | // 创建关闭按钮
717 | const closeButton = document.createElement('button');
718 | closeButton.textContent = '关闭';
719 | Object.assign(closeButton.style, buttonStyle, { backgroundColor: '#f44336' });
720 |
721 | closeButton.addEventListener('click', () => {
722 | modal.remove();
723 | });
724 |
725 | // 创建生成按钮
726 | const generateButton = document.createElement('button');
727 | generateButton.textContent = '导出Markdown';
728 | Object.assign(generateButton.style, buttonStyle, { backgroundColor: '#4CAF50' });
729 | generateButton.addEventListener('click', () => {
730 | const markdownContent = generateMdContent(selectedMessages);
731 | if (!markdownContent) {
732 | alert("请选择要导出的消息!");
733 | return;
734 | }
735 | // 下载选中的消息
736 | downloadMarkdown(markdownContent, getUserSessionTitle());
737 |
738 | // 关闭模态框
739 | modal.remove();
740 | });
741 |
742 | modal.appendChild(modalContent);
743 | modal.appendChild(paginationDiv);
744 | modal.appendChild(generateButton);
745 | modal.appendChild(selectAllButton);
746 | modal.appendChild(deselectAllButton);
747 | modal.appendChild(closeButton);
748 |
749 | // 监听esc按键事件
750 | document.addEventListener('keydown', function (event) {
751 | if (event.key === 'Escape') {
752 | modal.remove();
753 | }
754 | });
755 | if (!document.getElementById('messageModal')) {
756 | document.body.appendChild(modal);
757 | }
758 |
759 | renderMessages(currentPage);
760 |
761 | initConfig();
762 | }
763 | });
764 |
--------------------------------------------------------------------------------
/src/icons/icon120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/src/icons/icon120.png
--------------------------------------------------------------------------------
/src/icons/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/src/icons/icon128.png
--------------------------------------------------------------------------------
/src/icons/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/src/icons/icon16.png
--------------------------------------------------------------------------------
/src/icons/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/src/icons/icon32.png
--------------------------------------------------------------------------------
/src/icons/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/src/icons/icon48.png
--------------------------------------------------------------------------------
/src/icons/icon96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderyjc/deepseek2markdown/7812ee5b12598c996538a7498600e008ecd43dd5/src/icons/icon96.png
--------------------------------------------------------------------------------
/src/lib/canvas2image.js:
--------------------------------------------------------------------------------
1 | /**
2 | * covert canvas to image
3 | * and save the image file
4 | */
5 | const Canvas2Image = (function () {
6 | // check if support sth.
7 | const $support = (function () {
8 | const canvas = document.createElement("canvas"),
9 | ctx = canvas.getContext("2d");
10 |
11 | return {
12 | canvas: !!ctx,
13 | imageData: !!ctx.getImageData,
14 | dataURL: !!canvas.toDataURL,
15 | btoa: !!window.btoa,
16 | };
17 | })();
18 |
19 | const downloadMime = "image/octet-stream";
20 |
21 | function scaleCanvas(canvas, width, height) {
22 | const w = canvas.width,
23 | h = canvas.height;
24 | if (width === undefined) {
25 | width = w;
26 | }
27 | if (height === undefined) {
28 | height = h;
29 | }
30 |
31 | let retCanvas = document.createElement("canvas");
32 | let retCtx = retCanvas.getContext("2d");
33 | retCanvas.width = width;
34 | retCanvas.height = height;
35 | retCtx.drawImage(canvas, 0, 0, w, h, 0, 0, width, height);
36 | return retCanvas;
37 | }
38 |
39 | function getDataURL(canvas, type, width, height) {
40 | canvas = scaleCanvas(canvas, width, height);
41 | return canvas.toDataURL(type);
42 | }
43 |
44 | // save file to local with file name and file type
45 | function saveFile(strData, fileType, fileName = "name") {
46 | // document.location.href = strData;
47 | let saveLink = document.createElement("a");
48 | // download file name
49 | saveLink.download = fileName + "." + fileType;
50 | // download file data
51 | saveLink.href = strData;
52 | // start download
53 | saveLink.click();
54 | }
55 |
56 | function genImage(strData) {
57 | let img = document.createElement("img");
58 | img.src = strData;
59 | return img;
60 | }
61 |
62 | function fixType(type) {
63 | type = type.toLowerCase().replace(/jpg/i, "jpeg");
64 | const r = type.match(/png|jpeg|bmp|gif/)[0];
65 | return "image/" + r;
66 | }
67 |
68 | function encodeData(data) {
69 | if (!window.btoa) {
70 | // eslint-disable-next-line no-throw-literal
71 | throw "btoa undefined";
72 | }
73 | let str = "";
74 | if (typeof data == "string") {
75 | str = data;
76 | } else {
77 | for (let i = 0; i < data.length; i++) {
78 | str += String.fromCharCode(data[i]);
79 | }
80 | }
81 |
82 | return btoa(str);
83 | }
84 |
85 | function getImageData(canvas) {
86 | const w = canvas.width,
87 | h = canvas.height;
88 | return canvas.getContext("2d").getImageData(0, 0, w, h);
89 | }
90 |
91 | function makeURI(strData, type) {
92 | return "data:" + type + ";base64," + strData;
93 | }
94 |
95 | /**
96 | * create bitmap image
97 | * 按照规则生成图片响应头和响应体
98 | */
99 | const genBitmapImage = function (oData) {
100 | //
101 | // BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx
102 | // BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx
103 | //
104 |
105 | const biWidth = oData.width;
106 | const biHeight = oData.height;
107 | const biSizeImage = biWidth * biHeight * 3;
108 | const bfSize = biSizeImage + 54; // total header size = 54 bytes
109 |
110 | //
111 | // typedef struct tagBITMAPFILEHEADER {
112 | // WORD bfType;
113 | // DWORD bfSize;
114 | // WORD bfReserved1;
115 | // WORD bfReserved2;
116 | // DWORD bfOffBits;
117 | // } BITMAPFILEHEADER;
118 | //
119 | const BITMAPFILEHEADER = [
120 | // WORD bfType -- The file type signature; must be "BM"
121 | 0x42,
122 | 0x4d,
123 | // DWORD bfSize -- The size, in bytes, of the bitmap file
124 | bfSize & 0xff,
125 | (bfSize >> 8) & 0xff,
126 | (bfSize >> 16) & 0xff,
127 | (bfSize >> 24) & 0xff,
128 | // WORD bfReserved1 -- Reserved; must be zero
129 | 0,
130 | 0,
131 | // WORD bfReserved2 -- Reserved; must be zero
132 | 0,
133 | 0,
134 | // DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.
135 | 54,
136 | 0,
137 | 0,
138 | 0,
139 | ];
140 |
141 | //
142 | // typedef struct tagBITMAPINFOHEADER {
143 | // DWORD biSize;
144 | // LONG biWidth;
145 | // LONG biHeight;
146 | // WORD biPlanes;
147 | // WORD biBitCount;
148 | // DWORD biCompression;
149 | // DWORD biSizeImage;
150 | // LONG biXPelsPerMeter;
151 | // LONG biYPelsPerMeter;
152 | // DWORD biClrUsed;
153 | // DWORD biClrImportant;
154 | // } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
155 | //
156 | const BITMAPINFOHEADER = [
157 | // DWORD biSize -- The number of bytes required by the structure
158 | 40,
159 | 0,
160 | 0,
161 | 0,
162 | // LONG biWidth -- The width of the bitmap, in pixels
163 | biWidth & 0xff,
164 | (biWidth >> 8) & 0xff,
165 | (biWidth >> 16) & 0xff,
166 | (biWidth >> 24) & 0xff,
167 | // LONG biHeight -- The height of the bitmap, in pixels
168 | biHeight & 0xff,
169 | (biHeight >> 8) & 0xff,
170 | (biHeight >> 16) & 0xff,
171 | (biHeight >> 24) & 0xff,
172 | // WORD biPlanes -- The number of planes for the target device. This value must be set to 1
173 | 1,
174 | 0,
175 | // WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap
176 | // has a maximum of 2^24 colors (16777216, Truecolor)
177 | 24,
178 | 0,
179 | // DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed
180 | 0,
181 | 0,
182 | 0,
183 | 0,
184 | // DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps
185 | biSizeImage & 0xff,
186 | (biSizeImage >> 8) & 0xff,
187 | (biSizeImage >> 16) & 0xff,
188 | (biSizeImage >> 24) & 0xff,
189 | // LONG biXPelsPerMeter, unused
190 | 0,
191 | 0,
192 | 0,
193 | 0,
194 | // LONG biYPelsPerMeter, unused
195 | 0,
196 | 0,
197 | 0,
198 | 0,
199 | // DWORD biClrUsed, the number of color indexes of palette, unused
200 | 0,
201 | 0,
202 | 0,
203 | 0,
204 | // DWORD biClrImportant, unused
205 | 0,
206 | 0,
207 | 0,
208 | 0,
209 | ];
210 |
211 | const iPadding = (4 - ((biWidth * 3) % 4)) % 4;
212 |
213 | const aImgData = oData.data;
214 |
215 | let strPixelData = "";
216 | const biWidth4 = biWidth << 2;
217 | let y = biHeight;
218 | const fromCharCode = String.fromCharCode;
219 |
220 | do {
221 | const iOffsetY = biWidth4 * (y - 1);
222 | let strPixelRow = "";
223 | for (let x = 0; x < biWidth; x++) {
224 | const iOffsetX = x << 2;
225 | strPixelRow +=
226 | fromCharCode(aImgData[iOffsetY + iOffsetX + 2]) +
227 | fromCharCode(aImgData[iOffsetY + iOffsetX + 1]) +
228 | fromCharCode(aImgData[iOffsetY + iOffsetX]);
229 | }
230 |
231 | for (let c = 0; c < iPadding; c++) {
232 | strPixelRow += String.fromCharCode(0);
233 | }
234 |
235 | strPixelData += strPixelRow;
236 | } while (--y);
237 |
238 | return (
239 | encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) +
240 | encodeData(strPixelData)
241 | );
242 | };
243 |
244 | /**
245 | * saveAsImage
246 | * @param canvas canvasElement
247 | * @param width {String} image type
248 | * @param height {Number} [optional] png width
249 | * @param type {string} [optional] png height
250 | * @param fileName {String} image name
251 | */
252 | const saveAsImage = function (canvas, width, height, type, fileName) {
253 | // save file type
254 | const fileType = type;
255 | if ($support.canvas && $support.dataURL) {
256 | if (typeof canvas == "string") {
257 | canvas = document.getElementById(canvas);
258 | }
259 | if (type === undefined) {
260 | type = "png";
261 | }
262 | type = fixType(type);
263 | if (/bmp/.test(type)) {
264 | const data = getImageData(scaleCanvas(canvas, width, height));
265 | const strData = genBitmapImage(data);
266 | // use new parameter: fileType
267 | saveFile(makeURI(strData, downloadMime), fileType, fileName);
268 | } else {
269 | const strData = getDataURL(canvas, type, width, height);
270 | // use new parameter: fileType
271 | saveFile(strData.replace(type, downloadMime), fileType, fileName);
272 | }
273 | }
274 | };
275 |
276 | const convertToImage = function (canvas, width, height, type) {
277 | if ($support.canvas && $support.dataURL) {
278 | if (typeof canvas == "string") {
279 | canvas = document.getElementById(canvas);
280 | }
281 | if (type === undefined) {
282 | type = "png";
283 | }
284 | type = fixType(type);
285 |
286 | if (/bmp/.test(type)) {
287 | const data = getImageData(scaleCanvas(canvas, width, height));
288 | const strData = genBitmapImage(data);
289 | return genImage(makeURI(strData, "image/bmp"));
290 | } else {
291 | const strData = getDataURL(canvas, type, width, height);
292 | return genImage(strData);
293 | }
294 | }
295 | };
296 |
297 | return {
298 | saveAsImage: saveAsImage,
299 | saveAsPNG: function (canvas, width, height, fileName) {
300 | return saveAsImage(canvas, width, height, "png", fileName);
301 | },
302 | saveAsJPEG: function (canvas, width, height, fileName) {
303 | return saveAsImage(canvas, width, height, "jpeg", fileName);
304 | },
305 | saveAsGIF: function (canvas, width, height, fileName) {
306 | return saveAsImage(canvas, width, height, "gif", fileName);
307 | },
308 | saveAsBMP: function (canvas, width, height, fileName) {
309 | return saveAsImage(canvas, width, height, "bmp", fileName);
310 | },
311 |
312 | convertToImage: convertToImage,
313 | convertToPNG: function (canvas, width, height) {
314 | return convertToImage(canvas, width, height, "png");
315 | },
316 | convertToJPEG: function (canvas, width, height) {
317 | return convertToImage(canvas, width, height, "jpeg");
318 | },
319 | convertToGIF: function (canvas, width, height) {
320 | return convertToImage(canvas, width, height, "gif");
321 | },
322 | convertToBMP: function (canvas, width, height) {
323 | return convertToImage(canvas, width, height, "bmp");
324 | },
325 | };
326 | })();
327 |
--------------------------------------------------------------------------------
/src/lib/marked.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * marked v15.0.6 - a markdown parser
3 | * Copyright (c) 2011-2025, Christopher Jeffrey. (MIT Licensed)
4 | * https://github.com/markedjs/marked
5 | */
6 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).marked={})}(this,(function(e){"use strict";function t(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}function n(t){e.defaults=t}e.defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};const s={exec:()=>null};function r(e,t=""){let n="string"==typeof e?e:e.source;const s={replace:(e,t)=>{let r="string"==typeof t?t:t.source;return r=r.replace(i.caret,"$1"),n=n.replace(e,r),s},getRegex:()=>new RegExp(n,t)};return s}const i={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^,endAngleBracket:/>$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:e=>new RegExp(`^( {0,3}${e})((?:[\t ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),hrRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}#`),htmlBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}<(?:[a-z].*>|!--)`,"i")},l=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,o=/(?:[*+-]|\d{1,9}[.)])/,a=r(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g,o).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).getRegex(),c=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,h=/(?!\s*\])(?:\\.|[^\[\]\\])+/,p=r(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",h).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),u=r(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,o).getRegex(),g="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",k=/|$))/,d=r("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$))","i").replace("comment",k).replace("tag",g).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),f=r(c).replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",g).getRegex(),x={blockquote:r(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",f).getRegex(),code:/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,def:p,fences:/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,hr:l,html:d,lheading:a,list:u,newline:/^(?:[ \t]*(?:\n|$))+/,paragraph:f,table:s,text:/^[^\n]+/},b=r("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3}\t)[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",g).getRegex(),w={...x,table:b,paragraph:r(c).replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",b).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",g).getRegex()},m={...x,html:r("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?\\1> *(?:\\n{2,}|\\s*$)| \\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",k).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:s,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:r(c).replace("hr",l).replace("heading"," *#{1,6} *[^\n]").replace("lheading",a).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},y=/^( {2,}|\\)\n(?!\s*$)/,$=/[\p{P}\p{S}]/u,R=/[\s\p{P}\p{S}]/u,S=/[^\s\p{P}\p{S}]/u,T=r(/^((?![*_])punctSpace)/,"u").replace(/punctSpace/g,R).getRegex(),z=/(?!~)[\p{P}\p{S}]/u,A=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,_=r(A,"u").replace(/punct/g,$).getRegex(),P=r(A,"u").replace(/punct/g,z).getRegex(),I="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",L=r(I,"gu").replace(/notPunctSpace/g,S).replace(/punctSpace/g,R).replace(/punct/g,$).getRegex(),B=r(I,"gu").replace(/notPunctSpace/g,/(?:[^\s\p{P}\p{S}]|~)/u).replace(/punctSpace/g,/(?!~)[\s\p{P}\p{S}]/u).replace(/punct/g,z).getRegex(),C=r("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,S).replace(/punctSpace/g,R).replace(/punct/g,$).getRegex(),E=r(/\\(punct)/,"gu").replace(/punct/g,$).getRegex(),q=r(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),Z=r(k).replace("(?:--\x3e|$)","--\x3e").getRegex(),v=r("^comment|^[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",Z).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),D=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,M=r(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label",D).replace("href",/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),O=r(/^!?\[(label)\]\[(ref)\]/).replace("label",D).replace("ref",h).getRegex(),Q=r(/^!?\[(ref)\](?:\[\])?/).replace("ref",h).getRegex(),j={_backpedal:s,anyPunctuation:E,autolink:q,blockSkip:/\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g,br:y,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,del:s,emStrongLDelim:_,emStrongRDelimAst:L,emStrongRDelimUnd:C,escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,link:M,nolink:Q,punctuation:T,reflink:O,reflinkSearch:r("reflink|nolink(?!\\()","g").replace("reflink",O).replace("nolink",Q).getRegex(),tag:v,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\":">",'"':""","'":"'"},J=e=>U[e];function K(e,t){if(t){if(i.escapeTest.test(e))return e.replace(i.escapeReplace,J)}else if(i.escapeTestNoEncode.test(e))return e.replace(i.escapeReplaceNoEncode,J);return e}function V(e){try{e=encodeURI(e).replace(i.percentDecode,"%")}catch{return null}return e}function W(e,t){const n=e.replace(i.findPipe,((e,t,n)=>{let s=!1,r=t;for(;--r>=0&&"\\"===n[r];)s=!s;return s?"|":" |"})).split(i.splitPipe);let s=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const e=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?e:Y(e,"\n")}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const e=t[0],n=function(e,t,n){const s=e.match(n.other.indentCodeCompensation);if(null===s)return t;const r=s[1];return t.split("\n").map((e=>{const t=e.match(n.other.beginningSpace);if(null===t)return e;const[s]=t;return s.length>=r.length?e.slice(r.length):e})).join("\n")}(e,t[3]||"",this.rules);return{type:"code",raw:e,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:n}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let e=t[2].trim();if(this.rules.other.endingHash.test(e)){const t=Y(e,"#");this.options.pedantic?e=t.trim():t&&!this.rules.other.endingSpaceChar.test(t)||(e=t.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:e,tokens:this.lexer.inline(e)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:Y(t[0],"\n")}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){let e=Y(t[0],"\n").split("\n"),n="",s="";const r=[];for(;e.length>0;){let t=!1;const i=[];let l;for(l=0;l1,r={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");const i=this.rules.other.listItemRegex(n);let l=!1;for(;e;){let n=!1,s="",o="";if(!(t=i.exec(e)))break;if(this.rules.block.hr.test(e))break;s=t[0],e=e.substring(s.length);let a=t[2].split("\n",1)[0].replace(this.rules.other.listReplaceTabs,(e=>" ".repeat(3*e.length))),c=e.split("\n",1)[0],h=!a.trim(),p=0;if(this.options.pedantic?(p=2,o=a.trimStart()):h?p=t[1].length+1:(p=t[2].search(this.rules.other.nonSpaceChar),p=p>4?1:p,o=a.slice(p),p+=t[1].length),h&&this.rules.other.blankLine.test(c)&&(s+=c+"\n",e=e.substring(c.length+1),n=!0),!n){const t=this.rules.other.nextBulletRegex(p),n=this.rules.other.hrRegex(p),r=this.rules.other.fencesBeginRegex(p),i=this.rules.other.headingBeginRegex(p),l=this.rules.other.htmlBeginRegex(p);for(;e;){const u=e.split("\n",1)[0];let g;if(c=u,this.options.pedantic?(c=c.replace(this.rules.other.listReplaceNesting," "),g=c):g=c.replace(this.rules.other.tabCharGlobal," "),r.test(c))break;if(i.test(c))break;if(l.test(c))break;if(t.test(c))break;if(n.test(c))break;if(g.search(this.rules.other.nonSpaceChar)>=p||!c.trim())o+="\n"+g.slice(p);else{if(h)break;if(a.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4)break;if(r.test(a))break;if(i.test(a))break;if(n.test(a))break;o+="\n"+c}h||c.trim()||(h=!0),s+=u+"\n",e=e.substring(u.length+1),a=g.slice(p)}}r.loose||(l?r.loose=!0:this.rules.other.doubleBlankLine.test(s)&&(l=!0));let u,g=null;this.options.gfm&&(g=this.rules.other.listIsTask.exec(o),g&&(u="[ ] "!==g[0],o=o.replace(this.rules.other.listReplaceTask,""))),r.items.push({type:"list_item",raw:s,task:!!g,checked:u,loose:!1,text:o,tokens:[]}),r.raw+=s}const o=r.items.at(-1);if(!o)return;o.raw=o.raw.trimEnd(),o.text=o.text.trimEnd(),r.raw=r.raw.trimEnd();for(let e=0;e"space"===e.type)),n=t.length>0&&t.some((e=>this.rules.other.anyLine.test(e.raw)));r.loose=n}if(r.loose)for(let e=0;e({text:e,tokens:this.lexer.inline(e),header:!1,align:i.align[t]}))));return i}}lheading(e){const t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:"="===t[2].charAt(0)?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){const t=this.rules.block.paragraph.exec(e);if(t){const e="\n"===t[1].charAt(t[1].length-1)?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:e,tokens:this.lexer.inline(e)}}}text(e){const t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){const t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){const t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){const t=this.rules.inline.link.exec(e);if(t){const e=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(e)){if(!this.rules.other.endAngleBracket.test(e))return;const t=Y(e.slice(0,-1),"\\");if((e.length-t.length)%2==0)return}else{const e=function(e,t){if(-1===e.indexOf(t[1]))return-1;let n=0;for(let s=0;s-1){const n=(0===t[0].indexOf("!")?5:4)+t[1].length+e;t[2]=t[2].substring(0,e),t[0]=t[0].substring(0,n).trim(),t[3]=""}}let n=t[2],s="";if(this.options.pedantic){const e=this.rules.other.pedanticHrefTitle.exec(n);e&&(n=e[1],s=e[3])}else s=t[3]?t[3].slice(1,-1):"";return n=n.trim(),this.rules.other.startAngleBracket.test(n)&&(n=this.options.pedantic&&!this.rules.other.endAngleBracket.test(e)?n.slice(1):n.slice(1,-1)),ee(t,{href:n?n.replace(this.rules.inline.anyPunctuation,"$1"):n,title:s?s.replace(this.rules.inline.anyPunctuation,"$1"):s},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){const e=t[(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," ").toLowerCase()];if(!e){const e=n[0].charAt(0);return{type:"text",raw:e,text:e}}return ee(n,e,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s)return;if(s[3]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){const n=[...s[0]].length-1;let r,i,l=n,o=0;const a="*"===s[0][0]?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(a.lastIndex=0,t=t.slice(-1*e.length+n);null!=(s=a.exec(t));){if(r=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!r)continue;if(i=[...r].length,s[3]||s[4]){l+=i;continue}if((s[5]||s[6])&&n%3&&!((n+i)%3)){o+=i;continue}if(l-=i,l>0)continue;i=Math.min(i,i+l+o);const t=[...s[0]][0].length,a=e.slice(0,n+s.index+t+i);if(Math.min(n,i)%2){const e=a.slice(1,-1);return{type:"em",raw:a,text:e,tokens:this.lexer.inlineTokens(e)}}const c=a.slice(2,-2);return{type:"strong",raw:a,text:c,tokens:this.lexer.inlineTokens(c)}}}}codespan(e){const t=this.rules.inline.code.exec(e);if(t){let e=t[2].replace(this.rules.other.newLineCharGlobal," ");const n=this.rules.other.nonSpaceChar.test(e),s=this.rules.other.startingSpaceChar.test(e)&&this.rules.other.endingSpaceChar.test(e);return n&&s&&(e=e.substring(1,e.length-1)),{type:"codespan",raw:t[0],text:e}}}br(e){const t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){const t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){const t=this.rules.inline.autolink.exec(e);if(t){let e,n;return"@"===t[2]?(e=t[1],n="mailto:"+e):(e=t[1],n=e),{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let e,n;if("@"===t[2])e=t[0],n="mailto:"+e;else{let s;do{s=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??""}while(s!==t[0]);e=t[0],n="www."===t[1]?"http://"+t[0]:t[0]}return{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}inlineText(e){const t=this.rules.inline.text.exec(e);if(t){const e=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:e}}}}class ne{tokens;options;state;tokenizer;inlineQueue;constructor(t){this.tokens=[],this.tokens.links=Object.create(null),this.options=t||e.defaults,this.options.tokenizer=this.options.tokenizer||new te,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};const n={other:i,block:X.normal,inline:F.normal};this.options.pedantic?(n.block=X.pedantic,n.inline=F.pedantic):this.options.gfm&&(n.block=X.gfm,this.options.breaks?n.inline=F.breaks:n.inline=F.gfm),this.tokenizer.rules=n}static get rules(){return{block:X,inline:F}}static lex(e,t){return new ne(t).lex(e)}static lexInline(e,t){return new ne(t).inlineTokens(e)}lex(e){e=e.replace(i.carriageReturn,"\n"),this.blockTokens(e,this.tokens);for(let e=0;e!!(s=n.call({lexer:this},e,t))&&(e=e.substring(s.raw.length),t.push(s),!0))))continue;if(s=this.tokenizer.space(e)){e=e.substring(s.raw.length);const n=t.at(-1);1===s.raw.length&&void 0!==n?n.raw+="\n":t.push(s);continue}if(s=this.tokenizer.code(e)){e=e.substring(s.raw.length);const n=t.at(-1);"paragraph"===n?.type||"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.text,this.inlineQueue.at(-1).src=n.text):t.push(s);continue}if(s=this.tokenizer.fences(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.heading(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.hr(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.blockquote(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.list(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.html(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.def(e)){e=e.substring(s.raw.length);const n=t.at(-1);"paragraph"===n?.type||"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.raw,this.inlineQueue.at(-1).src=n.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title});continue}if(s=this.tokenizer.table(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.lheading(e)){e=e.substring(s.raw.length),t.push(s);continue}let r=e;if(this.options.extensions?.startBlock){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startBlock.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(this.state.top&&(s=this.tokenizer.paragraph(r))){const i=t.at(-1);n&&"paragraph"===i?.type?(i.raw+="\n"+s.raw,i.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=i.text):t.push(s),n=r.length!==e.length,e=e.substring(s.raw.length)}else if(s=this.tokenizer.text(e)){e=e.substring(s.raw.length);const n=t.at(-1);"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=n.text):t.push(s)}else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n=e,s=null;if(this.tokens.links){const e=Object.keys(this.tokens.links);if(e.length>0)for(;null!=(s=this.tokenizer.rules.inline.reflinkSearch.exec(n));)e.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(s=this.tokenizer.rules.inline.blockSkip.exec(n));)n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;null!=(s=this.tokenizer.rules.inline.anyPunctuation.exec(n));)n=n.slice(0,s.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);let r=!1,i="";for(;e;){let s;if(r||(i=""),r=!1,this.options.extensions?.inline?.some((n=>!!(s=n.call({lexer:this},e,t))&&(e=e.substring(s.raw.length),t.push(s),!0))))continue;if(s=this.tokenizer.escape(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.tag(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.link(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(s.raw.length);const n=t.at(-1);"text"===s.type&&"text"===n?.type?(n.raw+=s.raw,n.text+=s.text):t.push(s);continue}if(s=this.tokenizer.emStrong(e,n,i)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.codespan(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.br(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.del(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.autolink(e)){e=e.substring(s.raw.length),t.push(s);continue}if(!this.state.inLink&&(s=this.tokenizer.url(e))){e=e.substring(s.raw.length),t.push(s);continue}let l=e;if(this.options.extensions?.startInline){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startInline.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(l=e.substring(0,t+1))}if(s=this.tokenizer.inlineText(l)){e=e.substring(s.raw.length),"_"!==s.raw.slice(-1)&&(i=s.raw.slice(-1)),r=!0;const n=t.at(-1);"text"===n?.type?(n.raw+=s.raw,n.text+=s.text):t.push(s)}else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return t}}class se{options;parser;constructor(t){this.options=t||e.defaults}space(e){return""}code({text:e,lang:t,escaped:n}){const s=(t||"").match(i.notSpaceStart)?.[0],r=e.replace(i.endingNewline,"")+"\n";return s?''+(n?r:K(r,!0))+"
\n":""+(n?r:K(r,!0))+"
\n"}blockquote({tokens:e}){return`\n${this.parser.parse(e)} \n`}html({text:e}){return e}heading({tokens:e,depth:t}){return`${this.parser.parseInline(e)} \n`}hr(e){return" \n"}list(e){const t=e.ordered,n=e.start;let s="";for(let t=0;t\n"+s+""+r+">\n"}listitem(e){let t="";if(e.task){const n=this.checkbox({checked:!!e.checked});e.loose?"paragraph"===e.tokens[0]?.type?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&"text"===e.tokens[0].tokens[0].type&&(e.tokens[0].tokens[0].text=n+" "+K(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=!0)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:!0}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`${t} \n`}checkbox({checked:e}){return" '}paragraph({tokens:e}){return`${this.parser.parseInline(e)}
\n`}table(e){let t="",n="";for(let t=0;t${s}`),"\n"}tablerow({text:e}){return`\n${e} \n`}tablecell(e){const t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`${n}>\n`}strong({tokens:e}){return`${this.parser.parseInline(e)} `}em({tokens:e}){return`${this.parser.parseInline(e)} `}codespan({text:e}){return`${K(e,!0)}
`}br(e){return" "}del({tokens:e}){return`${this.parser.parseInline(e)}`}link({href:e,title:t,tokens:n}){const s=this.parser.parseInline(n),r=V(e);if(null===r)return s;let i='"+s+" ",i}image({href:e,title:t,text:n}){const s=V(e);if(null===s)return K(n);let r=` ",r}text(e){return"tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):"escaped"in e&&e.escaped?e.text:K(e.text)}}class re{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return""+e}image({text:e}){return""+e}br(){return""}}class ie{options;renderer;textRenderer;constructor(t){this.options=t||e.defaults,this.options.renderer=this.options.renderer||new se,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new re}static parse(e,t){return new ie(t).parse(e)}static parseInline(e,t){return new ie(t).parseInline(e)}parse(e,t=!0){let n="";for(let s=0;s{const r=e[s].flat(1/0);n=n.concat(this.walkTokens(r,t))})):e.tokens&&(n=n.concat(this.walkTokens(e.tokens,t)))}}return n}use(...e){const t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach((e=>{const n={...e};if(n.async=this.defaults.async||n.async||!1,e.extensions&&(e.extensions.forEach((e=>{if(!e.name)throw new Error("extension name required");if("renderer"in e){const n=t.renderers[e.name];t.renderers[e.name]=n?function(...t){let s=e.renderer.apply(this,t);return!1===s&&(s=n.apply(this,t)),s}:e.renderer}if("tokenizer"in e){if(!e.level||"block"!==e.level&&"inline"!==e.level)throw new Error("extension level must be 'block' or 'inline'");const n=t[e.level];n?n.unshift(e.tokenizer):t[e.level]=[e.tokenizer],e.start&&("block"===e.level?t.startBlock?t.startBlock.push(e.start):t.startBlock=[e.start]:"inline"===e.level&&(t.startInline?t.startInline.push(e.start):t.startInline=[e.start]))}"childTokens"in e&&e.childTokens&&(t.childTokens[e.name]=e.childTokens)})),n.extensions=t),e.renderer){const t=this.defaults.renderer||new se(this.defaults);for(const n in e.renderer){if(!(n in t))throw new Error(`renderer '${n}' does not exist`);if(["options","parser"].includes(n))continue;const s=n,r=e.renderer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n||""}}n.renderer=t}if(e.tokenizer){const t=this.defaults.tokenizer||new te(this.defaults);for(const n in e.tokenizer){if(!(n in t))throw new Error(`tokenizer '${n}' does not exist`);if(["options","rules","lexer"].includes(n))continue;const s=n,r=e.tokenizer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.tokenizer=t}if(e.hooks){const t=this.defaults.hooks||new le;for(const n in e.hooks){if(!(n in t))throw new Error(`hook '${n}' does not exist`);if(["options","block"].includes(n))continue;const s=n,r=e.hooks[s],i=t[s];le.passThroughHooks.has(n)?t[s]=e=>{if(this.defaults.async)return Promise.resolve(r.call(t,e)).then((e=>i.call(t,e)));const n=r.call(t,e);return i.call(t,n)}:t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.hooks=t}if(e.walkTokens){const t=this.defaults.walkTokens,s=e.walkTokens;n.walkTokens=function(e){let n=[];return n.push(s.call(this,e)),t&&(n=n.concat(t.call(this,e))),n}}this.defaults={...this.defaults,...n}})),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return ne.lex(e,t??this.defaults)}parser(e,t){return ie.parse(e,t??this.defaults)}parseMarkdown(e){return(t,n)=>{const s={...n},r={...this.defaults,...s},i=this.onError(!!r.silent,!!r.async);if(!0===this.defaults.async&&!1===s.async)return i(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(null==t)return i(new Error("marked(): input parameter is undefined or null"));if("string"!=typeof t)return i(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(t)+", string expected"));r.hooks&&(r.hooks.options=r,r.hooks.block=e);const l=r.hooks?r.hooks.provideLexer():e?ne.lex:ne.lexInline,o=r.hooks?r.hooks.provideParser():e?ie.parse:ie.parseInline;if(r.async)return Promise.resolve(r.hooks?r.hooks.preprocess(t):t).then((e=>l(e,r))).then((e=>r.hooks?r.hooks.processAllTokens(e):e)).then((e=>r.walkTokens?Promise.all(this.walkTokens(e,r.walkTokens)).then((()=>e)):e)).then((e=>o(e,r))).then((e=>r.hooks?r.hooks.postprocess(e):e)).catch(i);try{r.hooks&&(t=r.hooks.preprocess(t));let e=l(t,r);r.hooks&&(e=r.hooks.processAllTokens(e)),r.walkTokens&&this.walkTokens(e,r.walkTokens);let n=o(e,r);return r.hooks&&(n=r.hooks.postprocess(n)),n}catch(e){return i(e)}}}onError(e,t){return n=>{if(n.message+="\nPlease report this to https://github.com/markedjs/marked.",e){const e="An error occurred:
"+K(n.message+"",!0)+" ";return t?Promise.resolve(e):e}if(t)return Promise.reject(n);throw n}}}const ae=new oe;function ce(e,t){return ae.parse(e,t)}ce.options=ce.setOptions=function(e){return ae.setOptions(e),ce.defaults=ae.defaults,n(ce.defaults),ce},ce.getDefaults=t,ce.defaults=e.defaults,ce.use=function(...e){return ae.use(...e),ce.defaults=ae.defaults,n(ce.defaults),ce},ce.walkTokens=function(e,t){return ae.walkTokens(e,t)},ce.parseInline=ae.parseInline,ce.Parser=ie,ce.parser=ie.parse,ce.Renderer=se,ce.TextRenderer=re,ce.Lexer=ne,ce.lexer=ne.lex,ce.Tokenizer=te,ce.Hooks=le,ce.parse=ce;const he=ce.options,pe=ce.setOptions,ue=ce.use,ge=ce.walkTokens,ke=ce.parseInline,de=ce,fe=ie.parse,xe=ne.lex;e.Hooks=le,e.Lexer=ne,e.Marked=oe,e.Parser=ie,e.Renderer=se,e.TextRenderer=re,e.Tokenizer=te,e.getDefaults=t,e.lexer=xe,e.marked=ce,e.options=he,e.parse=de,e.parseInline=ke,e.parser=fe,e.setOptions=pe,e.use=ue,e.walkTokens=ge}));
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "DeepSeek2Markdown",
4 | "version": "0.3.2",
5 | "description": "Export DeepSeek Dialogs to Markdown, PDF, Image, and more",
6 | "permissions": [
7 | "activeTab",
8 | "scripting",
9 | "storage"
10 | ],
11 | "background": {
12 | "service_worker": "background.js"
13 | },
14 | "action": {
15 | "default_popup": "popup.html",
16 | "default_icon": {
17 | "16": "icons/icon16.png",
18 | "32": "icons/icon32.png",
19 | "48": "icons/icon48.png",
20 | "96": "icons/icon96.png",
21 | "120": "icons/icon120.png",
22 | "128": "icons/icon128.png"
23 | }
24 | },
25 | "icons": {
26 | "16": "icons/icon16.png",
27 | "32": "icons/icon32.png",
28 | "48": "icons/icon48.png",
29 | "96": "icons/icon96.png",
30 | "120": "icons/icon120.png",
31 | "128": "icons/icon128.png"
32 | },
33 | "content_scripts": [
34 | {
35 | "matches": ["https://chat.deepseek.com/*"],
36 | "js": ["lib/marked.min.js", "lib/html2canvas.min.js", "lib/canvas2image.js", "content.js"],
37 | "run_at": "document_end"
38 | }
39 | ]
40 | }
--------------------------------------------------------------------------------
/src/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | DeepSeek Export
7 |
8 |
9 |
10 |
11 |
12 |
DeepSeek2Markdown
13 |
14 |
15 |
16 | 🔨展开配置信息
17 |
18 |
19 |
导出思维链
20 |
21 |
22 |
23 |
24 |
25 |
26 |
导出消息繁忙
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 📦批量导出
36 | 📄导出为Markdown
37 | 📕导出为PDF
38 | 🏞️导出为图像
39 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/popup.js:
--------------------------------------------------------------------------------
1 | // 获取第一个开关元素(导出思维链)
2 | document.getElementById('exportChainOfThought').addEventListener('change', (event) => {
3 | const isChecked = event.target.checked;
4 |
5 | // 发送消息给 content.js
6 | chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
7 | const activeTab = tabs[0];
8 | chrome.tabs.sendMessage(activeTab.id, {
9 | action: 'updateExportChainOfThoughtState',
10 | state: isChecked
11 | });
12 | });
13 | });
14 |
15 | // 获取第二个开关元素(屏蔽消息繁忙)
16 | document.getElementById('blockBusyMessages').addEventListener('change', (event) => {
17 | const isChecked = event.target.checked;
18 |
19 | // 发送消息给 content.js
20 | chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
21 | const activeTab = tabs[0];
22 | chrome.tabs.sendMessage(activeTab.id, {
23 | action: 'updateBlockBusyMessagesState',
24 | state: isChecked
25 | });
26 | });
27 | });
28 |
29 | // 批量导出Markdown
30 | document.getElementById('exportBatch').addEventListener('click', () => {
31 | chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
32 | chrome.tabs.sendMessage(tabs[0].id, { action: "generateBatch" });
33 | });
34 | });
35 |
36 | // 导出Markdown
37 | document.getElementById('exportAsMarkdown').addEventListener('click', () => {
38 | chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
39 | chrome.tabs.sendMessage(tabs[0].id, { action: "generateMarkdown" });
40 | });
41 | });
42 |
43 | // 导出PDF的按钮
44 | document.getElementById('exportAsPDF').addEventListener('click', () => {
45 | chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
46 | chrome.tabs.sendMessage(tabs[0].id, { action: "generatePDF" });
47 | });
48 | });
49 |
50 | // 导出Image的按钮
51 | document.getElementById('exportAsImage').addEventListener('click', () => {
52 | chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
53 | chrome.tabs.sendMessage(tabs[0].id, { action: 'showNotificationImage' });
54 | });
55 | });
56 |
57 | // 每次打开popup时,更新 switch 状态为 false
58 | chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
59 | const activeTab = tabs[0];
60 | chrome.tabs.sendMessage(activeTab.id, {
61 | action: 'updateSwitchState',
62 | state: false
63 | });
64 | chrome.tabs.sendMessage(activeTab.id, {
65 | action: 'updateBlockBusyMessagesState',
66 | state: false
67 | });
68 | });
69 |
70 | // TODO 需要测试一下这样是否可行。
71 | chrome.runtime.onInstalled.addListener((details) => {
72 | if (details.reason === 'install') {
73 | // 插件安装时执行
74 | chrome.tabs.query({}, (tabs) => {
75 | tabs.forEach(tab => {
76 | if (tab.url && tab.url.startsWith('http')) {
77 | chrome.tabs.reload(tab.id);
78 | }
79 | });
80 | });
81 | }
82 | });
83 |
84 | // 语言配置
85 | const translations = {
86 | zh: {
87 | summary: "🔨展开配置信息",
88 | exportChainOfThought: "导出思维链",
89 | blockBusyMessages: "导出消息繁忙",
90 | exportBatch: "📦批量导出",
91 | exportAsMarkdown: "📄导出为Markdown",
92 | exportAsPDF: "📕导出为PDF",
93 | exportAsImage: "🏞️导出为图像",
94 | languageToggle: "中文|EN"
95 | },
96 | en: {
97 | summary: "🔨Expand Configuration Information",
98 | exportChainOfThought: "Export Chain of Thought",
99 | blockBusyMessages: "Export Busy Messages",
100 | exportBatch: "📦Batch Export",
101 | exportAsMarkdown: "📄Export as Markdown",
102 | exportAsPDF: "📕Export as PDF",
103 | exportAsImage: "🏞️Export as Image",
104 | languageToggle: "中文|EN"
105 | }
106 | };
107 |
108 | // 当前语言
109 | let currentLang = 'zh';
110 |
111 | // 更新页面文本
112 | function updateLanguage(lang) {
113 | currentLang = lang;
114 | // 更新所有文本内容
115 | document.querySelector('summary').textContent = translations[lang].summary;
116 | document.querySelector('.switch-option:nth-child(1) span').textContent = translations[lang].exportChainOfThought;
117 | document.querySelector('.switch-option:nth-child(2) span').textContent = translations[lang].blockBusyMessages;
118 | document.getElementById('exportBatch').textContent = translations[lang].exportBatch;
119 | document.getElementById('exportAsMarkdown').textContent = translations[lang].exportAsMarkdown;
120 | document.getElementById('exportAsPDF').textContent = translations[lang].exportAsPDF;
121 | document.getElementById('exportAsImage').textContent = translations[lang].exportAsImage;
122 | document.getElementById('languageToggle').textContent = translations[lang].languageToggle;
123 |
124 | // 保存语言设置
125 | chrome.storage.local.set({ language: lang });
126 | }
127 |
128 | // 语言切换按钮事件监听
129 | document.getElementById('languageToggle').addEventListener('click', () => {
130 | const newLang = currentLang === 'zh' ? 'en' : 'zh';
131 | updateLanguage(newLang);
132 | });
133 |
134 | // 初始化语言设置
135 | chrome.storage.local.get('language', (result) => {
136 | const savedLang = result.language || 'zh';
137 | updateLanguage(savedLang);
138 | });
139 |
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | font-size: 17px;
3 | margin-top: 15px;
4 | text-align: center;
5 | width: 100%;
6 | }
7 |
8 | body {
9 | font-family: 'Arial', sans-serif;
10 | background-color: #f4f7fb;
11 | width: 260px;
12 | padding: 10px;
13 | box-sizing: border-box;
14 | }
15 |
16 | button {
17 | font-size: 13px;
18 | font-weight: bold;
19 | border: none;
20 | border-radius: 10px;
21 | color: #fff;
22 | padding: 8px 24px;
23 | background-color: #0072fc;
24 | margin-bottom: 15px;
25 | width: 100%;
26 | cursor: pointer;
27 | transition: background-color 0.3s;
28 | }
29 |
30 | button:hover {
31 | background-color: #005bb5;
32 | }
33 |
34 | .head-container {
35 | display: flex;
36 | justify-content: space-between;
37 | align-items: center;
38 | margin-bottom: 20px;
39 | }
40 |
41 | .switch-container {
42 | border: 2px solid #ccc;
43 | padding: 8px;
44 | border-radius: 8px;
45 | background-color: #fff;
46 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
47 | display: flex;
48 | justify-content: space-between;
49 | margin-bottom: 20px;
50 | flex-direction: column;
51 | align-items: flex-start;
52 | }
53 |
54 | .message-box {
55 | padding: 20px;
56 | background-color: #f0f0f0;
57 | border-radius: 5px;
58 | font-family: Arial, sans-serif;
59 | color: #333;
60 | }
61 |
62 | .switch-box {
63 | display: flex;
64 | flex-direction: column;
65 | gap: 10px;
66 | width: 100%;
67 | padding-top: 10px;
68 | }
69 |
70 | .switch-option {
71 | display: flex;
72 | align-items: center;
73 | justify-content: space-between;
74 | gap: 10px;
75 | width: 100%;
76 | }
77 |
78 | .switch-option span {
79 | font-size: 13px;
80 | margin-right: 10px;
81 | color: #333;
82 | }
83 |
84 | .switch {
85 | position: relative;
86 | display: inline-block;
87 | width: 50px;
88 | height: 28px;
89 | }
90 |
91 | .switch input {
92 | display: none;
93 | }
94 |
95 | .slider {
96 | position: absolute;
97 | cursor: pointer;
98 | top: 0;
99 | left: 0;
100 | right: 0;
101 | bottom: 0;
102 | background-color: #ccc;
103 | border-radius: 30px;
104 | transition: background-color 0.3s;
105 | }
106 |
107 | .slider:before {
108 | position: absolute;
109 | content: "";
110 | height: 20px;
111 | width: 20px;
112 | left: 4px;
113 | bottom: 4px;
114 | background-color: white;
115 | border-radius: 50%;
116 | transition: transform 0.3s;
117 | }
118 |
119 | .footer {
120 | position: fixed;
121 | bottom: -10px;
122 | left: 0;
123 | right: 0;
124 | display: flex;
125 | padding: 0 10px;
126 | flex-direction: row;
127 | justify-content: space-between;
128 | align-items: baseline;
129 | }
130 |
131 | .footer button {
132 | font-size: 12px;
133 | padding: 2px 8px;
134 | border-radius: 4px;
135 | background: #F4F7FB;
136 | cursor: pointer;
137 | width: 80px;
138 | color: blue;
139 | }
140 |
141 | .footer span {
142 | font-size: 12px;
143 | color: #666;
144 | }
145 |
146 | input:checked+.slider {
147 | background-color: #4caf50;
148 | }
149 |
150 | input:checked+.slider:before {
151 | transform: translateX(20px);
152 | }
153 |
154 | #exportAsMarkdown,
155 | #exportAsPDF {
156 | background-color: #28a745;
157 | }
158 |
159 | #exportAsMarkdown:hover,
160 | #exportAsPDF:hover {
161 | background-color: #218838;
162 | }
163 |
164 | #exportAsPDF {
165 | background-color: #dc3545;
166 | }
167 |
168 | #exportAsPDF:hover {
169 | background-color: #a01c2a;
170 | }
--------------------------------------------------------------------------------