// ==UserScript== // @name copy-notion-page-content-as-markdown // @name:en Copy Notion Page Content AS Markdown // @name:zh-CN 一键复制 Notion 页面内容为标准 Markdown 格式 // @namespace https://github.com/Seven-Steven/tampermonkey-scripts/tree/main/copy-notion-page-content-as-markdown // @supportURL https://github.com/Seven-Steven/tampermonkey-scripts/issues // @description 一键复制 Notion 页面内容为标准 Markdown 格式。 // @description:zh-CN 一键复制 Notion 页面内容为标准 Markdown 格式。 // @description:en Copy Notion Page Content AS Markdown. // @version 2.2 // @license MIT // @author Seven // @homepage https://blog.diqigan.cn // @match *://www.notion.so/* // @icon https://www.google.com/s2/favicons?sz=64&domain=notion.so // @downloadURL https://update.greasyfork.icu/scripts/477050/copy-notion-page-content-as-markdown.user.js // @updateURL https://update.greasyfork.icu/scripts/477050/copy-notion-page-content-as-markdown.meta.js // ==/UserScript== (function () { 'use strict'; /** * 复制按钮 ID */ const DOM_ID_OF_COPY_BUTTON = 'tamper-monkey-plugin-copy-notion-content-as-markdown-copy-button'; /** * Notion 页面祖先节点 Selector */ const DOM_SELECTOR_NOTION_PAGE_ANCESTOR = '#notion-app'; /** * 公共的 Notion Page Content Selector */ const DOM_SELECTOR_NOTION_PAGE_CONTENT_COMMON = `${DOM_SELECTOR_NOTION_PAGE_ANCESTOR} .notion-page-content`; /** * 普通页面的 Notion Page Content Selector */ const DOM_SELECTOR_NOTION_PAGE_CONTENT_NORMAL = `${DOM_SELECTOR_NOTION_PAGE_ANCESTOR} main.notion-frame .notion-page-content`; /** * 插件挂载状态 */ let PLUGIN_MOUNT_STATUS = false; init(); /** * 初始化动作 */ function init() { console.log('init TamperMonkey plugin: Copy Notion Content AS Markdown.'); const mountPlugin = () => { console.log('find Notion Page, mount Plugin directly.'); onMount(); }; waitForElements(10000, 1000, DOM_SELECTOR_NOTION_PAGE_CONTENT_COMMON) // 对于 Notion Page 页面,直接初始化插件就好 .then(mountPlugin).catch(() => { }); waitForElements(10000, 1000, DOM_SELECTOR_NOTION_PAGE_CONTENT_NORMAL) .then(mountPlugin) // 对于 DataBase / View 等其他页面,需要监听 DOM 节点变化判断当前页面有没有 Notion Page Content DOM,进而装载 / 卸载插件 .catch(() => { console.log('can not find notion page, add observe for ancestor.'); autoMountOrUmountPluginByObserverFor(DOM_SELECTOR_NOTION_PAGE_ANCESTOR) }); } /** * 监听指定 DOM 的子节点变化,并根据子节点变化动态装载 / 卸载插件 * @param {string} selector 节点选择器 */ const autoMountOrUmountPluginDebounce = debounce(autoMountOrUmountPlugin, 500); function autoMountOrUmountPluginByObserverFor(selector) { const ancestorDOM = document.querySelector(selector); if (!ancestorDOM) { console.error('Ancestor DOM of Notion Page does not exist!'); return; } // 创建 MutationObserver 实例,监听页面节点变化 const observer = new MutationObserver(mutations => { for (let mutation of mutations) { if (mutation.type === 'childList') { // 在页面节点子元素发生变化时,根据条件挂载/卸载插件 autoMountOrUmountPluginDebounce(); break; } } }); // 配置 MutationObserver 监听选项 const observerConfig = { childList: true, subtree: true, characterData: false, attributes: false, }; // 开始监听页面节点变化 observer.observe(ancestorDOM, observerConfig); } /** * 装载/卸载插件 */ function autoMountOrUmountPlugin() { console.log('auto solve plugin...'); waitForElements(500, 100, DOM_SELECTOR_NOTION_PAGE_CONTENT_COMMON).then(() => { console.log('Find Notion Page Content, begin to mount plugin......'); onMount(); }).catch(() => { console.log('Can not find Notion Page Content, begin to umount plugin......'); onUmount(); }) } /** * 装载插件 */ function onMount() { if (PLUGIN_MOUNT_STATUS) { console.log('Plugin already mounted, return.'); return; } initCopyButton(); window.addEventListener('copy', fixNotionMarkdownInClipboard); PLUGIN_MOUNT_STATUS = true; console.log('Plugin Mounted.'); } /** * 卸载插件 */ function onUmount() { if (!PLUGIN_MOUNT_STATUS) { console.log('Plugin not mounted, return.'); return; } removeCopyButton(); window.removeEventListener('copy', fixNotionMarkdownInClipboard); PLUGIN_MOUNT_STATUS = false; console.log('Plugin UnMounted.'); } /** * 修正剪切板中的 Notion Markdown 文本格式 */ function fixNotionMarkdownInClipboard() { navigator.clipboard.readText().then(text => { const markdown = fixMarkdownFormat(text); navigator.clipboard.writeText(markdown).then(() => { showMessage('复制成功'); }, () => { console.log('failed.'); }) }) } /** * 修正 markdown 格式 */ function fixMarkdownFormat(markdown) { if (!markdown) { return; } // 给没有 Caption 的图片添加默认 ALT 文字 markdown = markdown.replaceAll(/^!(http\S+)$/gm, (match, imgUrl) => { return ``; }); // 给有 Caption 的图片去除多余文字 const captionRegex = /(\!\[(?