Greasy Fork 还支持 简体中文。

copy-notion-page-content-as-markdown

复制 Notion Page 内容为标准 Markdown 文本。

目前為 2023-10-09 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name copy-notion-page-content-as-markdown
  3. // @name:zh-CN 复制 Notion Page 内容为标准 Markdown 文本
  4. // @namespace https://blog.diqigan.cn
  5. // @version 0.1
  6. // @description 复制 Notion Page 内容为标准 Markdown 文本。
  7. // @license MIT
  8. // @author Seven
  9. // @match *://www.notion.so/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=notion.so
  11. // @grant GM_setClipboard
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. init();
  18.  
  19. /**
  20. * 初始化动作
  21. */
  22. function init() {
  23. waitFor('#notion-app .notion-page-content').then(([notionContentElement]) => {
  24. initCopyButton();
  25. });
  26. }
  27.  
  28. /**
  29. * 初始化复制按钮
  30. */
  31. function initCopyButton() {
  32. let copyButton = document.createElement('div');
  33.  
  34. copyButton.style.position = "fixed";
  35. copyButton.style.width = "88px";
  36. copyButton.style.height = "22px";
  37. copyButton.style.lineHeight = "22px";
  38. copyButton.style.top = "14%";
  39. copyButton.style.right = "1%";
  40. copyButton.style.background = "#0084ff";
  41. copyButton.style.fontSize = "14px";
  42. copyButton.style.color = "#fff";
  43. copyButton.style.textAlign = "center";
  44. copyButton.style.borderRadius = "6px";
  45. copyButton.style.zIndex = 10000;
  46. copyButton.style.cursor = "pointer";
  47. copyButton.style.opacity = 0.6;
  48. copyButton.innerHTML = "Copy Content";
  49.  
  50. copyButton.addEventListener('click', copyPageContentAsync);
  51. console.log('initCopyButton');
  52. document.body.prepend(copyButton);
  53. }
  54.  
  55. /**
  56. * 复制 Notion Page 内容
  57. */
  58. async function copyPageContentAsync() {
  59. await copyElementAsync('#notion-app .notion-page-content');
  60.  
  61. const clipboardContent = await readClipboard();
  62. if (!clipboardContent) {
  63. showMessage('复制失败');
  64. return;
  65. }
  66.  
  67. console.log('clipboardContent', clipboardContent);
  68. const markdownContent = fixMarkdownFormat(clipboardContent);
  69. console.log('markdown', markdownContent);
  70.  
  71. GM_setClipboard(markdownContent);
  72. showMessage('复制成功');
  73. }
  74.  
  75. /**
  76. * 修正 markdown 格式
  77. */
  78. function fixMarkdownFormat(markdown) {
  79. if (!markdown) {
  80. return;
  81. }
  82.  
  83. // 给没有 Caption 的图片添加 ALT 文字
  84. return markdown.replaceAll(/\!(http.*\.\w+)/g, (match, group1) => {
  85. const processedText = decodeURIComponent(group1);
  86. console.log('regex', processedText);
  87. return `![picture](${processedText})`;
  88. });
  89.  
  90. // TODO 给有 Caption 的图片去除多余文字
  91. }
  92.  
  93. /**
  94. * 读取系统剪切板内容
  95. */
  96. async function readClipboard() {
  97. try {
  98. const clipText = await navigator.clipboard.readText();
  99. return clipText;
  100. } catch (error) {
  101. console.error('Failed to read clipboard:', error);
  102. }
  103. }
  104.  
  105. /**
  106. * 复制 DOM 元素(在 DOM 元素上执行复制操作)
  107. */
  108. async function copyElementAsync(selector) {
  109. const pageContent = document.querySelector(selector);
  110. let range = document.createRange();
  111. range.selectNodeContents(pageContent);
  112.  
  113. let selection = window.getSelection();
  114. selection.removeAllRanges();
  115. selection.addRange(range);
  116. pageContent.focus();
  117. await sleep(500);
  118.  
  119. document.execCommand('copy');
  120. selection.removeAllRanges();
  121. }
  122.  
  123. /**
  124. * 在页面显示提示信息
  125. */
  126. function showMessage(message) {
  127. const toast = document.createElement('div');
  128. toast.style.position = 'fixed';
  129. toast.style.bottom = '20px';
  130. toast.style.left = '50%';
  131. toast.style.transform = 'translateX(-50%)';
  132. toast.style.padding = '10px 20px';
  133. toast.style.background = 'rgba(0, 0, 0, 0.8)';
  134. toast.style.color = 'white';
  135. toast.style.borderRadius = '5px';
  136. toast.style.zIndex = '9999';
  137. toast.innerText = message;
  138. document.body.appendChild(toast);
  139. setTimeout(function() {
  140. toast.remove();
  141. }, 3000);
  142. }
  143.  
  144. /**
  145. * 等待指定 DOM 元素加载完成之后再执行方法
  146. */
  147. function waitFor(...selectors) {
  148. return new Promise(resolve => {
  149. const delay = 500;
  150. const f = () => {
  151. const elements = selectors.map(selector => document.querySelector(selector));
  152. if (elements.every(element => element != null)) {
  153. resolve(elements);
  154. } else {
  155. setTimeout(f, delay);
  156. }
  157. }
  158. f();
  159. });
  160. }
  161.  
  162. /**
  163. * 延迟执行
  164. **/
  165. function sleep(ms) {
  166. return new Promise(resolve => setTimeout(resolve, ms));
  167. }
  168. })();