Created
March 6, 2026 04:45
-
-
Save sorrycc/296b442bbe07c6f76442a4fa6233904d to your computer and use it in GitHub Desktop.
语雀文档导出工具
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name 语雀文档导出工具 | |
| // @namespace http://tampermonkey.net/ | |
| // @version 0.1.0 | |
| // @description 导出语雀文档为 Markdown 文件 | |
| // @author sorrycc | |
| // @match https://www.yuque.com/* | |
| // @match https://yuque.antfin.com/* | |
| // @require https://cdn.jsdelivr.net/gh/eligrey/FileSaver.js@v2.0.4/dist/FileSaver.min.js | |
| // @require https://cdn.jsdelivr.net/gh/Stuk/jszip@v3.0.0/dist/jszip.min.js | |
| // @grant none | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| let keySequence = ''; | |
| const TARGET_SEQUENCE = ',d'; | |
| // 日志函数 | |
| const log = (message, isError = false) => { | |
| const style = isError ? 'color: red' : 'color: green'; | |
| console.log(`%c[语雀导出工具] ${message}`, style); | |
| }; | |
| // 获取完整路径 | |
| const getFullPath = (toc, allTocs) => { | |
| const paths = []; | |
| let currentToc = toc; | |
| while (currentToc.parent_uuid) { | |
| const parent = allTocs.find(t => t.uuid === currentToc.parent_uuid); | |
| if (!parent) break; | |
| paths.unshift(parent.title); | |
| currentToc = parent; | |
| } | |
| return paths; | |
| }; | |
| // 导出文档 | |
| async function exportDocs() { | |
| try { | |
| // 1. 检查 window.appData | |
| if (!window.appData) { | |
| throw new Error('未找到页面数据,请确保在语雀文档页面执行'); | |
| } | |
| const { book, docUrl, group, defaultSpaceHost } = window.appData; | |
| if (!book || !book.toc || !group || !defaultSpaceHost) { | |
| throw new Error('页面数据不完整'); | |
| } | |
| let namespace = book.namespace; | |
| if (!namespace) { | |
| const url = new URL(docUrl); | |
| const pathSegments = url.pathname.split('/').filter(Boolean); | |
| namespace = pathSegments.slice(0, 2).join('/'); | |
| } | |
| if (!namespace) { | |
| log(`appData: ${window.appData}`); | |
| throw new Error('namespace not found'); | |
| } | |
| log('开始导出文档...'); | |
| log(`namespace: ${namespace}`); | |
| log(`文档组:${group.name}`); | |
| log(`文档数量:${book.toc.length}`); | |
| // 2. 创建 zip 实例 | |
| const zip = new JSZip(); | |
| // 3. 遍历获取所有文档 | |
| for (const toc of book.toc) { | |
| if (toc.type !== 'DOC' || toc.visible === 0) continue; | |
| try { | |
| // 构建下载 URL | |
| const url = `${defaultSpaceHost}/${namespace}/${toc.url}/markdown?plain=true&linebreak=false&anchor=false`; | |
| // 获取文档内容 | |
| log(`正在获取:${toc.title}`); | |
| const response = await fetch(url); | |
| if (!response.ok) { | |
| throw new Error(`获取失败:${response.status}`); | |
| } | |
| const markdown = await response.text(); | |
| // 构建文件路径 | |
| const paths = getFullPath(toc, book.toc); | |
| const filePath = [...paths, `${toc.title}.md`].join('/'); | |
| // 添加到 zip | |
| zip.file(filePath, markdown); | |
| log(`已添加:${filePath}`); | |
| } catch (err) { | |
| log(`处理文档 "${toc.title}" 时出错: ${err.message}`, true); | |
| } | |
| } | |
| // 4. 生成并下载 zip | |
| log('正在生成压缩包...'); | |
| const content = await zip.generateAsync({type: "blob"}); | |
| const fileName = `${group.name}.zip`; | |
| saveAs(content, fileName); | |
| log(`导出完成:${fileName}`); | |
| } catch (err) { | |
| log(`导出失败:${err.message}`, true); | |
| } | |
| } | |
| // 监听键盘事件 | |
| document.addEventListener('keypress', (e) => { | |
| keySequence += e.key; | |
| // 只保留最后两个字符 | |
| if (keySequence.length > 2) { | |
| keySequence = keySequence.slice(-2); | |
| } | |
| // 检查是否匹配目标序列 | |
| if (keySequence === TARGET_SEQUENCE) { | |
| keySequence = ''; | |
| exportDocs(); | |
| } | |
| }); | |
| log('脚本已加载,按 ,d 开始导出'); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment