copy-notion-page-content-as-markdown

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

当前为 2023-10-11 提交的版本,查看 最新版本

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