MarkDown 雲剪筆記

將網頁內容轉換為 Markdown 格式的工具,支持複製、下載、發送到 GitHub 和 Obsidian 等功能。

  1. // ==UserScript==
  2. // @name MarkDown Cloud Cut Notes
  3. // @name:ar ملاحظات قطع السحابة المتولد
  4. // @name:bg Бележки за изрязване на облак от Маркдаун
  5. // @name:cs Poznámky k cloudovému řezu Markdown
  6. // @name:da Markdown Cloud Cut Notes
  7. // @name:de Markdown Cloud Cut -Notizen
  8. // @name:el Σημειώσεις κοπής σύννεφων Markdown
  9. // @name:en MarkDown Cloud Cut Notes
  10. // @name:eo Markdown Cloud Cut Notes
  11. // @name:es Notas de corte de nube de markdown
  12. // @name:fi Markdown Cloud Cut Notes
  13. // @name:fr Notes de coupe de cloud de Markdown
  14. // @name:fr-CA Notes de coupe de cloud de Markdown
  15. // @name:he הערות Culd Cloud Markdown
  16. // @name:hr Markdown Cloud Cut Notes
  17. // @name:hu Markdown Cloud Cut jegyzetek
  18. // @name:id Markdown Cloud Cut Notes
  19. // @name:it Markdown Cloud Cut Notes
  20. // @name:ja マークダウンクラウドカットノート
  21. // @name:ka Markdown Cloud Cut Notes
  22. // @name:ko 마크 다운 클라우드 컷 메모
  23. // @name:nb Markdown Cloud Cut Notes
  24. // @name:nl Markdown Cloud Cut Notes
  25. // @name:pl Notatki z Cloud Cloud Cloud
  26. // @name:pt-BR Notas de corte na nuvem de marcação
  27. // @name:ro Note de tăiere a norului markdown
  28. // @name:ru Примечания к облаку отметки
  29. // @name:sk Poznámky k mraku cloudu
  30. // @name:sr Напомена Цлоуд Цлоуд Сцрет Нотес
  31. // @name:sv Markdown Cloud Cut -anteckningar
  32. // @name:th MARKDOWN Cloud Cut Notes
  33. // @name:tr Markdown bulut kesim notları
  34. // @name:ug ماركېل بۇلۇت ئۈزۈلۈپ قالدى
  35. // @name:uk Нотатки з вирізанням Cloud Cloud
  36. // @name:vi Markdown Cloud Cut Ghi chú
  37. // @name:zh MarkDown 云剪笔记
  38. // @name:zh-CN MarkDown 云剪笔记
  39. // @name:zh-HK MarkDown 雲剪筆記
  40. // @name:zh-SG MarkDown 云剪笔记
  41. // @name:zh-TW MarkDown 雲剪筆記
  42. // @description A tool that converts web content to Markdown format, supports features such as copying, downloading, and sending to GitHub and Obsidian.
  43. // @description:ar تقوم أداة بتحويل محتوى الويب إلى تنسيق Markdown ، ويدعم ميزات مثل نسخ وتنزيل وإرسال Github و Obsidian.
  44. // @description:bg Инструмент, който преобразува уеб съдържание във формат за маркиране, поддържа функции като копиране, изтегляне и изпращане на GitHub и Obsidian.
  45. // @description:cs Nástroj, který převádí webový obsah do formátu označení, podporuje funkce, jako je kopírování, stahování a odesílání do GitHubu a obsidiánu.
  46. // @description:da Et værktøj, der konverterer webindhold til Markdown -format, understøtter funktioner såsom kopiering, download og sender til GitHub og Obsidian.
  47. // @description:de Ein Tool, das Webinhalte in Markdown -Format umwandelt, unterstützt Funktionen wie das Kopieren, Herunterladen und Senden an GitHub und Obsidian.
  48. // @description:el Ένα εργαλείο που μετατρέπει το περιεχόμενο ιστού σε μορφή Markdown, υποστηρίζει χαρακτηριστικά όπως η αντιγραφή, η λήψη και η αποστολή στο GitHub και ο Obsidian.
  49. // @description:en A tool that converts web content to Markdown format, supports features such as copying, downloading, and sending to GitHub and Obsidian.
  50. // @description:eo Ilo, kiu konvertas retan enhavon al Markdown -formato, subtenas funkciojn kiel kopii, elŝuti kaj sendi al Github kaj Obsidian.
  51. // @description:es Una herramienta que convierte el contenido web en formato de Markdown, admite características como copiar, descargar y enviar a Github y Obsidian.
  52. // @description:fi Työkalu, joka muuntaa verkkosisällön merkinnän muotoon, tukee ominaisuuksia, kuten kopiointia, lataamista ja lähettämistä GitHubille ja Obsidianille.
  53. // @description:fr Un outil qui convertit le contenu Web au format Markdown, prend en charge des fonctionnalités telles que la copie, le téléchargement et l’envoi à GitHub et Obsidian.
  54. // @description:fr-CA Un outil qui convertit le contenu Web au format Markdown, prend en charge des fonctionnalités telles que la copie, le téléchargement et l’envoi à GitHub et Obsidian.
  55. // @description:he כלי שממיר תוכן אינטרנט לפורמט Markdown, תומך בתכונות כמו העתקה, הורדה ושליחה ל- Github ו- Obsidian.
  56. // @description:hr Alat koji pretvara web sadržaj u Markdown format, podržava značajke poput kopiranja, preuzimanja i slanja GitHub i Obsidian.
  57. // @description:hu Egy olyan eszköz, amely a webtartalmat Markdown formátumra konvertálja, támogatja azokat a funkciókat, mint például a másolást, a letöltést és a Github -nak és az Obsidian -nak küldését.
  58. // @description:id Alat yang mengonversi konten web ke format penurunan harga, mendukung fitur seperti menyalin, mengunduh, dan mengirim ke GitHub dan Obsidian.
  59. // @description:it Uno strumento che converte i contenuti Web in formato markdown, supporta funzionalità come la copia, il download e l’invio a Github e Obsidian.
  60. // @description:ja WebコンテンツをMarkdown形式に変換するツールは、GithubやObsidianへのコピー、ダウンロード、送信などの機能をサポートします。
  61. // @description:ka ინსტრუმენტი, რომელიც გარდაქმნის ვებ შინაარსს MarkDown ფორმატში, მხარს უჭერს ისეთ ფუნქციებს, როგორიცაა კოპირება, ჩამოტვირთვა და გაგზავნა Github და Obsidian.
  62. // @description:ko 웹 컨텐츠를 Markdown 형식으로 변환하는 도구는 복사, 다운로드 및 Github 및 Obsidian으로 전송과 같은 기능을 지원합니다.
  63. // @description:nb Et verktøy som konverterer nettinnhold til Markdown -format, støtter funksjoner som kopiering, nedlasting og sending til Github og Obsidian.
  64. // @description:nl Een tool die webinhoud converteert naar Markdown -indeling, ondersteunt functies zoals kopiëren, downloaden en verzenden naar GitHub en Obsidian.
  65. // @description:pl Narzędzie, które konwertuje treść sieci w formacie Markdown, obsługuje takie funkcje, jak kopiowanie, pobieranie i wysyłanie do Github i Obsidian.
  66. // @description:pt-BR Uma ferramenta que converte o conteúdo da Web em formato de marcação, suporta recursos como cópia, download e envio para o Github e Obsidian.
  67. // @description:ro Un instrument care convertește conținutul web în format markdown, acceptă funcții precum copierea, descărcarea și trimiterea către Github și Obsidian.
  68. // @description:ru Инструмент, который преобразует веб -контент в формат разметки, поддерживает такие функции, как копирование, загрузка и отправка в Github и Obsidian.
  69. // @description:sk Nástroj, ktorý prevádza webový obsah na formát Markdown, podporuje funkcie, ako je kopírovanie, sťahovanie a odosielanie spoločnosti GitHub a Obsidian.
  70. // @description:sr Алат који претвара Веб садржај у ознаку формата, подржава функције као што су копирање, преузимање и слање Гитхуб-а и Обсидијан.
  71. // @description:sv Ett verktyg som konverterar webbinnehåll till Markdown -format, stöder funktioner som kopiering, nedladdning och skickning till GitHub och Obsidian.
  72. // @description:th เครื่องมือที่แปลงเนื้อหาเว็บเป็นรูปแบบ Markdown รองรับคุณสมบัติต่าง ๆ เช่นการคัดลอกการดาวน์โหลดและการส่งไปยัง GitHub และ Obsidian
  73. // @description:tr Web içeriğini işaretleme biçimine dönüştüren bir araç, GitHub ve Obsidian’a kopyalama, indirme ve gönderme gibi özellikleri destekler.
  74. // @description:ug تور مەزمۇنىنى بەلگە قىلىپ بەلگە قىلالايدىغان بىر قورال گاتسىيە ۋە Obstidiation غا توپلاش ۋە ئەۋەتىش قاتارلىق ئىقتىدارلارنى قوللايدىغان ئىقتىدارلارنى قوللايدۇ.
  75. // @description:uk Інструмент, який перетворює веб -вміст у формат Markdown, підтримує такі функції, як копіювання, завантаження та надсилання в Github та Обсидіан.
  76. // @description:vi Một công cụ chuyển đổi nội dung web thành định dạng đánh dấu, hỗ trợ các tính năng như sao chép, tải xuống và gửi đến GitHub và Obsidian.
  77. // @description:zh 将网页内容转换为 Markdown 格式的工具,支持复制、下载、发送到 GitHub 和 Obsidian 等功能。
  78. // @description:zh-CN 将网页内容转换为 Markdown 格式的工具,支持复制、下载、发送到 GitHub 和 Obsidian 等功能。
  79. // @description:zh-HK 將網頁內容轉換為 Markdown 格式的工具,支持複製、下載、發送到 GitHub 和 Obsidian 等功能。
  80. // @description:zh-SG 将网页内容转换为 Markdown 格式的工具,支持复制、下载、发送到 GitHub 和 Obsidian 等功能。
  81. // @description:zh-TW 將網頁內容轉換為 Markdown 格式的工具,支持複製、下載、發送到 GitHub 和 Obsidian 等功能。
  82. // @author shiquda,人民的勤务员 <china.qinwuyuan@gmail.com>
  83. // @namespace https://github.com/ChinaGodMan/UserScripts
  84. // @supportURL https://github.com/ChinaGodMan/UserScripts/issues
  85. // @homepageURL https://github.com/ChinaGodMan/UserScripts
  86. // @license MIT
  87. // @grant GM_addStyle
  88. // @grant GM_registerMenuCommand
  89. // @grant GM_setClipboard
  90. // @grant GM_setValue
  91. // @grant GM_getValue
  92. // @require https://code.jquery.com/jquery-3.6.0.min.js
  93. // @require https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
  94. // @require https://unpkg.com/turndown/dist/turndown.js
  95. // @require https://unpkg.com/@guyplusplus/turndown-plugin-gfm/dist/turndown-plugin-gfm.js
  96. // @require https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.0/marked.min.js
  97. // @match *://*/*
  98. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAABHNCSVQICAgIfAhkiAAABBdJREFUaIHtmluIVlUUx39pMup4Q1FrRi1CrMAaREUS75KlBhKKSGAKzkPRQ0lBPngjkIZeBMWHAccH8VIKJQTSIAgWVBQKglco7YYiitrYjONtjg/7G2bPPmt/nvOdtc8ndn5wQM/+77X+i73PnrPP/qCgoKCg4P/Bi8CGAHFfAj4JEDcTc4GodM1Ujt1aintcOW4mTtFT8GnFuAusuBGwSjF2xdQCdwlj7LwTt1kpbiYGAh30NtauEHe1EzMCtivEVeEEcXObM8bsFGIuyxhTjY3EzUXA4ArjNQmxHmSIp86zwH3iJvdWEGuoECcC9qs4VWQ3stGXU8b52hNnoppTJcYjG/05RYxJnhiHVZ0qcgjZ8OKE/X/x9J+t7lQJ3wj9lqDvUk/fNDOkKhxDNv5+mT5PAb97+r0d0qwG9nu1fV3DvKRIrPX0+SOwVzVOIxfQJGgHA20e/eocvKrwFnIBETDa0W716P7Ny6wWl5AL+cLS1Hs0EfBpnmY1eId4EfeBkZbmsKCJgHu5OlXkBr0Lsb+ITME/uo352tTjA3qKuATUWG1nkYv9M2ePqvSjZwV+17q/HP/ozsvZozpbgAvOvevIxR7N11oYRgLTrP+vxz+6r+TuLjBDkffNEdBSRV/B2IVcbCe9/1w9ETTgn8obq+grGEeQi70MPF1FX0FYiH90V5Y09Zht4hPBGeRi7VOKc8CM/K3pswb/6L7maB6r86NK6APcIl7oAWB6SdMPuGK1rcjfpg79MdNUGlmbz4X2N0OZ0lgkhmCOSF8FxgF1mDPdCR79JuCz0r/7Yz791Aq6m5gp/jdm43EO+J4qbi4WAD/ifz6lqwMY4MRpThnjH2Ad5lHIhT7Alx4zt/FvDHwvGGMw50aSvgv5UK278IYA9cU4KCQ/CyzBTOfhwPOY00Nb04aZ/hLuKF8HFgEvAGMxq/leIe8N4t/IVJkjJD3k0W5zdJvLxB2H+axj6/sKusVC/j3pSkjHN06yyx5dnaO7hdkplWMnyQr50NHdIdAo12CeGzvZex7tt44uyRfIsZhn1u5X79G6W8zXE1WQkjriC4hkaKqjuYZZ6JLgjnKrR/edo/soYfxUPEd8BKRpetzRrE2R4xmnb0TP66fNV47m4xQ5EjMC8zJgJ5ruaJY47TcryNPixDgmaH51NOUO6ipmIPHThIOOxv2ZUZrR7WY48VF+w2qvFdqDvYoeFZLNKrU1Ovc7MuRxPwWdsNpanbYuzIIXhFXEC76D2d795dzPspAME/I0In8T+yFDnkdS7uDavm4r5NqRII873YMwHv/7bfc1VynX1UfkCfHrXZFBwD7BQAfmtxpajAJOCnkuAPMrCZh1P1wDTMZsCv4DfsLsfLSZiNlItGOKvRggR0FBQcHjx0Ng/VhKUt8K3AAAAABJRU5ErkJggg==
  99. // @compatible chrome
  100. // @compatible firefox
  101. // @compatible edge
  102. // @compatible opera
  103. // @compatible safari
  104. // @compatible kiwi
  105. // @compatible qq
  106. // @compatible via
  107. // @compatible brave
  108. // @version 2025.03.19.0450
  109. // @created 2025-03-18 07:13:13
  110. // @modified 2025-03-18 07:13:13
  111. // ==/UserScript==
  112. /**
  113. * File: web-clipper.user.js
  114. * Project: UserScripts
  115. * File Created: 2025/03/18,Tuesday 07:13:13
  116. * Author: 人民的勤务员@ChinaGodMan (china.qinwuyuan@gmail.com)
  117. * -----
  118. * Last Modified: 2025/03/19,Wednesday 04:50:15
  119. * Modified By: 人民的勤务员@ChinaGodMan (china.qinwuyuan@gmail.com)
  120. * -----
  121. * License: MIT License
  122. * Copyright © 2024 - 2025 ChinaGodMan,Inc
  123. */
  124. //! https://gf.qytechs.cn/zh-CN/scripts/486888
  125. (function () {
  126. 'use strict'
  127.  
  128. // User Config
  129. // Short cut
  130.  
  131. const shortCutUserConfig = {
  132. /* Example:
  133. "Shift": false,
  134. "Ctrl": true,
  135. "Alt": false,
  136. "Key": "m"
  137. */
  138. }
  139.  
  140. // Obsidian
  141. const obsidianUserConfig = {
  142. /* Example:
  143. "my note": [
  144. "Inbox/Web/",
  145. "Collection/Web/Reading/"
  146. ]
  147. */
  148. }
  149. const userLang =
  150. (navigator.languages && navigator.languages[0]) ||
  151. navigator.language ||
  152. 'en'
  153. const translations = {
  154. en: {
  155. copy: 'Copy to clipboard',
  156. copied: 'Copied successfully!',
  157. download_md: 'Download as MD',
  158. send_to_github: 'Send to Github',
  159. send_to_obsidian: 'Send to Obsidian',
  160. github_failed: 'Creation failed:',
  161. github_success: 'Creation succeeded:',
  162. configure: 'Please configure your GitHub information first',
  163. menu: 'Convert to Markdown',
  164. gui_title: 'Set Up GitHub',
  165. gui_tokeninput: 'Please enter your GitHub Personal Access Token',
  166. gui_github_repo: 'Please enter your GitHub repository name',
  167. gui_github_generate: 'Generate',
  168. gui_github_owner: 'Please enter your GitHub username',
  169. gui_save: 'Save',
  170. gui_cancel: 'Cancel',
  171. guide: `
  172. - Use **arrow keys** to select elements:
  173. - Up: Select parent element
  174. - Down: Select the first child element
  175. - Left: Select the previous sibling element
  176. - Right: Select the next sibling element
  177. - Use **scroll wheel** to zoom in and out:
  178. * Up: Select parent element
  179. - Down: Select the first child element
  180. - Click to select an element
  181. - Press \`Esc\` key to cancel selection
  182. `
  183. },
  184. 'zh-CN,zh,zh-SG': {
  185. copy: '复制到剪辑版',
  186. copied: '复制成功!',
  187. download_md: '下载为MD',
  188. send_to_github: '保存到GitHub',
  189. send_to_obsidian: '发送到Obsidian',
  190. github_failed: '创建失败:',
  191. github_success: '创建成功:',
  192. configure: '请先配置你的GitHub信息',
  193. menu: '转换为Markdown',
  194. gui_title: '设置 GitHub',
  195. gui_tokeninput: '请输入您的GitHub个人访问令牌',
  196. gui_github_repo: '请输入您的GitHub仓库名称',
  197. gui_github_generate: '生成',
  198. gui_github_owner: '请输入您的GitHub用户名',
  199. gui_save: '保存',
  200. gui_cancel: '取消',
  201. guide: `
  202. - 使用**方向键**选择元素
  203. - 上:选择父元素
  204. - 下:选择第一个子元素
  205. - 左:选择上一个兄弟元素
  206. - 右:选择下一个兄弟元素
  207. - 使用**滚轮**放大缩小
  208. - 上:选择父元素
  209. - 下:选择第一个子元素
  210. - 点击元素选择
  211. - 按下 \`Esc\` 键取消选择
  212. `
  213. },
  214. 'zh-TW,zh-HK,zh-MO': {
  215. copy: '複製到剪貼簿',
  216. copied: '複製成功!',
  217. download_md: '下載為MD',
  218. send_to_github: '保存到GitHub',
  219. send_to_obsidian: '發送到Obsidian',
  220. github_failed: '創建失敗:',
  221. github_success: '創建成功:',
  222. configure: '請先配置你的GitHub 信息',
  223. menu: '轉換為Markdown',
  224. guide: `
  225. - 使用**方向鍵**選擇元素
  226. - 上:選擇父元素
  227. - 下:選擇第一個子元素
  228. - 左:選擇上一個兄弟元素
  229. - 右:選擇下一個兄弟元素
  230. - 使用**滾輪**放大縮小
  231. - 上:選擇父元素
  232. - 下:選擇第一個子元素
  233. - 點擊元素選擇
  234. - 按下 \`Esc\` 鍵取消選擇
  235. `
  236. },
  237. vi: {
  238. copy: 'Sao chép vào clipboard',
  239. copied: 'Sao chép thành công!',
  240. download_md: 'Tải xuống dưới dạng MD',
  241. send_to_github: 'Gửi đến Github',
  242. send_to_obsidian: 'Gửi đến Obsidian',
  243. github_failed: 'Tạo thất bại:',
  244. github_success: 'Tạo thành công:',
  245. configure: 'Vui lòng cấu hình thông tin GitHub của bạn trước',
  246. menu: 'Chuyển đổi sang Markdown',
  247. guide: `
  248. - S dng **phím mũi tên** để chn các phn tử:
  249. - Lên: Chn phn t cha
  250. - Xung: Chn phn t con đầu tiên
  251. - Trái: Chn phn t anh em trước
  252. - Phi: Chn phn t anh em sau
  253. - S dng **bánh xe cun** để phóng to và thu nhỏ:
  254. - Lên: Chn phn t cha
  255. - Xung: Chn phn t con đầu tiên
  256. - Nhp để chn mt phn t
  257. - Nhn phím \`Esc\` để hy chn
  258. `
  259. },
  260. ja: {
  261. copy: 'クリップボードにコピー',
  262. copied: 'コピー成功!',
  263. download_md: 'MDとしてダウンロード',
  264. send_to_github: 'Githubに送信',
  265. send_to_obsidian: 'Obsidianに送信',
  266. github_failed: '作成失敗:',
  267. github_success: '作成成功:',
  268. configure: 'まずGitHub情報を設定してください',
  269. menu: 'Markdownに変換',
  270. guide: `
  271. - **矢印キー**を使用して要素を選択します:
  272. - 上: 親要素を選択
  273. - 下: 最初の子要素を選択
  274. - 左: 前の兄弟要素を選択
  275. - 右: 次の兄弟要素を選択
  276. - **スクロールホイール**を使用してズームインおよびズームアウトします:
  277. - 上: 親要素を選択
  278. - 下: 最初の子要素を選択
  279. - 要素をクリックして選択
  280. - \`Esc\`キーを押して選択をキャンセル
  281. `
  282. },
  283. ko: {
  284. copy: '클립보드에 복사',
  285. copied: '복사 성공!',
  286. download_md: 'MD로 다운로드',
  287. send_to_github: 'Github에 보내기',
  288. send_to_obsidian: 'Obsidian에 보내기',
  289. github_failed: '생성 실패:',
  290. github_success: '생성 성공:',
  291. configure: '먼저 GitHub 정보를 구성하십시오',
  292. menu: 'Markdown으로 변환',
  293. guide: `
  294. - **화살표 키**를 사용하여 요소를 선택합니다:
  295. - 위: 부모 요소 선택
  296. - 아래: 번째 자식 요소 선택
  297. - 왼쪽: 이전 형제 요소 선택
  298. - 오른쪽: 다음 형제 요소 선택
  299. - **스크롤 휠**을 사용하여 확대 축소합니다:
  300. - 위: 부모 요소 선택
  301. - 아래: 번째 자식 요소 선택
  302. - 요소를 선택하려면 클릭
  303. - 선택을 취소하려면 \`Esc\` 키를 누르십시오
  304. `
  305. },
  306. fr: {
  307. copy: 'Copier dans le presse-papiers',
  308. copied: 'Copié avec succès!',
  309. download_md: 'Télécharger en tant que MD',
  310. send_to_github: 'Envoyer à Github',
  311. send_to_obsidian: 'Envoyer à Obsidian',
  312. github_failed: 'Échec de la création:',
  313. github_success: 'Création réussie:',
  314. configure: 'Veuillez d\'abord configurer vos informations GitHub',
  315. menu: 'Convertir en Markdown',
  316. guide: `
  317. - Utilisez les **touches fléchées** pour sélectionner les éléments:
  318. - Haut: Sélectionner llément parent
  319. - Bas: Sélectionner le premier élément enfant
  320. - Gauche: Sélectionner llément frère précédent
  321. - Droite: Sélectionner llément frère suivant
  322. - Utilisez la **molette de défilement** pour zoomer et dézoomer:
  323. - Haut: Sélectionner llément parent
  324. - Bas: Sélectionner le premier élément enfant
  325. - Cliquez pour sélectionner un élément
  326. - Appuyez sur la touche \`Esc\` pour annuler la sélection
  327. `
  328. },
  329. it: {
  330. copy: 'Copia negli appunti',
  331. copied: 'Copiato con successo!',
  332. download_md: 'Scarica come MD',
  333. send_to_github: 'Invia a Github',
  334. send_to_obsidian: 'Invia a Obsidian',
  335. github_failed: 'Creazione fallita:',
  336. github_success: 'Creazione riuscita:',
  337. configure: 'Si prega di configurare prima le informazioni di GitHub',
  338. menu: 'Converti in Markdown',
  339. guide: `
  340. - Usa i **tasti freccia** per selezionare gli elementi:
  341. - Su: Seleziona l'elemento padre
  342. - Giù: Seleziona il primo elemento figlio
  343. - Sinistra: Seleziona l'elemento fratello precedente
  344. - Destra: Seleziona l'elemento fratello successivo
  345. - Usa la **rotella di scorrimento** per ingrandire e ridurre:
  346. - Su: Seleziona l'elemento padre
  347. - Giù: Seleziona il primo elemento figlio
  348. - Clicca per selezionare un elemento
  349. - Premi il tasto \`Esc\` per annullare la selezione
  350. `
  351. },
  352. de: {
  353. copy: 'In die Zwischenablage kopieren',
  354. copied: 'Erfolgreich kopiert!',
  355. download_md: 'Als MD herunterladen',
  356. send_to_github: 'An Github senden',
  357. send_to_obsidian: 'An Obsidian senden',
  358. github_failed: 'Erstellung fehlgeschlagen:',
  359. github_success: 'Erstellung erfolgreich:',
  360. configure: 'Bitte konfigurieren Sie zuerst Ihre GitHub-Informationen',
  361. menu: 'In Markdown konvertieren',
  362. guide: `
  363. - Verwenden Sie die **Pfeiltasten**, um Elemente auszuwählen:
  364. - Oben: Elternelement auswählen
  365. - Unten: Erstes Kindelement auswählen
  366. - Links: Vorheriges Geschwisterelement auswählen
  367. - Rechts: Nächstes Geschwisterelement auswählen
  368. - Verwenden Sie das **Scrollrad**, um hinein- und herauszuzoomen:
  369. - Oben: Elternelement auswählen
  370. - Unten: Erstes Kindelement auswählen
  371. - Klicken Sie, um ein Element auszuwählen
  372. - Drücken Sie die \`Esc\`-Taste, um die Auswahl abzubrechen
  373. `
  374. }
  375. }
  376. const getTranslations = (lang) => {
  377. for (const key in translations) {
  378. if (key === lang || key.split(',').includes(lang)) {
  379. return translations[key]
  380. }
  381. }
  382. return translations['en']
  383. }
  384. const translate = new Proxy(
  385. function (key) {
  386. const lang = userLang
  387. const strings = getTranslations(lang)
  388. return strings[key] || translations['en'][key]
  389. },
  390. {
  391. get(target, prop) {
  392. const lang = userLang
  393. const strings = getTranslations(lang)
  394. return strings[prop] || translations['en'][prop]
  395. }
  396. }
  397. )
  398.  
  399. // 全局变量
  400. var isSelecting = false
  401. var selectedElement = null
  402. let shortCutConfig, obsidianConfig
  403. // 读取配置
  404. // 初始化快捷键配置
  405. let storedShortCutConfig = GM_getValue('shortCutConfig')
  406. if (Object.keys(shortCutUserConfig).length !== 0) {
  407. GM_setValue('shortCutConfig', JSON.stringify(shortCutUserConfig))
  408. shortCutConfig = shortCutUserConfig
  409. } else if (storedShortCutConfig) {
  410. shortCutConfig = JSON.parse(storedShortCutConfig)
  411. }
  412.  
  413. // 初始化Obsidian配置
  414. let storedObsidianConfig = GM_getValue('obsidianConfig')
  415. if (Object.keys(obsidianUserConfig).length !== 0) {
  416. GM_setValue('obsidianConfig', JSON.stringify(obsidianUserConfig))
  417. obsidianConfig = obsidianUserConfig
  418. } else if (storedObsidianConfig) {
  419. obsidianConfig = JSON.parse(storedObsidianConfig)
  420. }
  421. GM_addStyle(`
  422. .modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;justify-content:center;align-items:center;z-index:10000;}
  423. .modal-content{background:white;padding:20px;border-radius:8px;width:400px;box-shadow:0 4px 15px rgba(0,0,0,0.2);position:relative;}
  424. .modal-title{margin:0 0 10px 0;font-size:20px;}
  425. .modal-description{margin-bottom:20px;font-size:14px;color:#666;}
  426. .modal-description a{color:#007bff;text-decoration:underline;}
  427. .modal-close-btn {position: absolute;top: 10px;right: 10px;background-color: red;color: white;border: none;border-radius: 50%;width: 24px;height: 24px;font-size: 16px;cursor: pointer;display: flex;justify-content: center;align-items: center;line-height: 1;}
  428. .modal-close-btn:hover {background-color: darkred;}
  429. .gui-input{width:100%;padding:8px;border:1px solid #ccc;border-radius:4px;margin-bottom:20px;font-size:14px;}
  430. #save-token{background-color:#28a745;color:white;border:none;padding:10px 20px;cursor:pointer;border-radius:4px;margin-right:10px;}
  431. #cancel-token{background-color:#dc3545;color:white;border:none;padding:10px 20px;cursor:pointer;border-radius:4px;}
  432. `)
  433. function showConfig() {
  434. const modalHTML = `
  435. <div class="modal-overlay">
  436. <div class="modal-content">
  437. <h3 class="modal-title">${translate.gui_title}</h3>
  438. <p class="modal-description">${translate.gui_tokeninput}
  439. <a href="https://github.com/settings/tokens/new?description=Web%20Clipper%20Token%20UserScript&scopes=repo" target="_blank" rel="noopener noreferrer">${translate.gui_github_generate}</a>
  440. </p>
  441. <input type="text" id="github-token-input" class="gui-input" placeholder="${translate.gui_tokeninput}">
  442. <p class="modal-description">${translate.gui_github_owner}</p>
  443. <input type="text" id="github-owner-input" class="gui-input" placeholder="${translate.gui_github_owner}">
  444. <p class="modal-description">${translate.gui_github_repo}</p>
  445. <input type="text" id="github-repo-input" class="gui-input" placeholder="${translate.gui_github_repo}">
  446. <button id="save-token">${translate.gui_save}</button>
  447. <button id="cancel-token" class="cancel">${translate.gui_cancel}</button>
  448. </div>
  449. </div>
  450. `
  451. const modalContainer = document.createElement('div')
  452. modalContainer.innerHTML = modalHTML
  453. document.body.appendChild(modalContainer)
  454. const elements = {
  455. token: modalContainer.querySelector('#github-token-input'),
  456. owner: modalContainer.querySelector('#github-owner-input'),
  457. repo: modalContainer.querySelector('#github-repo-input'),
  458. saveButton: modalContainer.querySelector('#save-token'),
  459. cancelButton: modalContainer.querySelector('#cancel-token')
  460. }
  461. elements.token.value = GM_getValue('github_token', '')
  462. elements.owner.value = GM_getValue('OWNER', '')
  463. elements.repo.value = GM_getValue('REPO', '')
  464. elements.saveButton.addEventListener('click', () => {
  465. if (elements.token.value && elements.owner.value && elements.repo.value) {
  466. GM_setValue('github_token', elements.token.value)
  467. GM_setValue('OWNER', elements.owner.value)
  468. GM_setValue('REPO', elements.repo.value)
  469. modalContainer.remove()
  470. } else {
  471. alert(`${translate.configure}`)
  472. }
  473. })
  474. elements.cancelButton.addEventListener('click', () => modalContainer.remove())
  475. }
  476. function showDialog(info, link) {
  477. const modalHTML = `
  478. <div class="modal-overlay">
  479. <div class="modal-content">
  480. <button class="modal-close-btn" id="cancel-token" >×</button>
  481. <h3 class="modal-title">${info}</h3>
  482. <p class="modal-description">
  483. ${info}
  484. <a href="${link}" target="_blank" rel="noopener noreferrer">${link}</a>
  485. </p>
  486. </div>
  487. </div>
  488. `
  489. const modalContainer = document.createElement('div')
  490. modalContainer.innerHTML = modalHTML
  491. document.body.appendChild(modalContainer)
  492. modalContainer.querySelector('#cancel-token').addEventListener('click', () => modalContainer.remove())
  493. }
  494. // HTML2Markdown
  495. function convertToMarkdown(element) {
  496. var html = element.outerHTML
  497. let turndownMd = turndownService.turndown(html)
  498. turndownMd = turndownMd.replaceAll('[\n\n]', '[]') // 防止 <a> 元素嵌套的暂时方法,并不完善
  499. return turndownMd
  500. }
  501.  
  502. // 预览
  503. function showMarkdownModal(markdown) {
  504. var $modal = $(`
  505. <div class="h2m-modal-overlay">
  506. <div class="h2m-modal">
  507. <textarea>${markdown}</textarea>
  508. <div class="h2m-preview">${marked.parse(markdown)}</div>
  509. <div class="h2m-buttons">
  510. <button class="h2m-copy">${translate.copy}</button>
  511. <button class="h2m-download">${translate.download_md}</button>
  512. <button class="h2m-github">${translate.send_to_github}</button>
  513. <select class="h2m-obsidian-select">${translate.send_to_obsidian}</select>
  514. </div>
  515. <button class="h2m-close">X</button>
  516. </div>
  517. </div>
  518. `)
  519.  
  520. $modal.find('.h2m-obsidian-select').append($('<option>').val('').text(`${translate.send_to_obsidian}`))
  521. for (const vault in obsidianConfig) {
  522. for (const path of obsidianConfig[vault]) {
  523. // 插入元素
  524. const $option = $('<option>')
  525. .val(`obsidian://advanced-uri?vault=${vault}&filepath=${path}`)
  526. .text(`${vault}: ${path}`)
  527. $modal.find('.h2m-obsidian-select').append($option)
  528. }
  529. }
  530.  
  531. $modal.find('textarea').on('input', function () {
  532. // console.log("Input event triggered");
  533. var markdown = $(this).val()
  534. var html = marked.parse(markdown)
  535. // console.log("Markdown:", markdown);
  536. // console.log("HTML:", html);
  537. $modal.find('.h2m-preview').html(html)
  538. })
  539.  
  540. $modal.on('keydown', function (e) {
  541. if (e.key === 'Escape') {
  542. $modal.remove()
  543. }
  544. })
  545.  
  546. $modal.find('.h2m-copy').on('click', function () { // 复制到剪贴板
  547. GM_setClipboard($modal.find('textarea').val())
  548. $modal.find('.h2m-copy').text(`${translate.copied}`)
  549. setTimeout(() => {
  550. $modal.find('.h2m-copy').text(`${translate.copy}`)
  551. }, 1000)
  552. })
  553. $modal.find('.h2m-github').on('click', function () {
  554. const github_token = GM_getValue('github_token', '')
  555. const github_owner = GM_getValue('OWNER', '')
  556. const github_repo = GM_getValue('REPO', '')
  557.  
  558. if (!github_token || !github_owner || !github_repo) {
  559. showConfig()
  560. return
  561. }
  562. const labels = ['web-clipper']//标签,可多项
  563. const markdown = $modal.find('textarea').val()
  564. const title = markdown.split('\n')[0]
  565. const body = markdown
  566. createIssue(github_token, github_owner, github_repo, title, body, labels)
  567. })
  568. $modal.find('.h2m-download').on('click', function () { // 下载
  569. var markdown = $modal.find('textarea').val()
  570. var blob = new Blob([markdown], { type: 'text/markdown' })
  571. var url = URL.createObjectURL(blob)
  572. var a = document.createElement('a')
  573. a.href = url
  574. // 当前页面标题 + 时间
  575. a.download = `${document.title}-${new Date().toISOString().replace(/:/g, '-')}.md`
  576. a.click()
  577. })
  578.  
  579. $modal.find('.h2m-obsidian-select').on('change', function () { // 发送到 Obsidian
  580. const val = $(this).val()
  581. if (!val) return
  582. const markdown = $modal.find('textarea').val()
  583. GM_setClipboard(markdown)
  584. const title = document.title.replaceAll(/[\\/:*?"<>|]/g, '_') // File name cannot contain any of the following characters: * " \ / < > : | ?
  585. const url = `${val}${title}.md&clipboard=true`
  586. window.open(url)
  587. })
  588.  
  589. $modal.find('.h2m-close').on('click', function () { // 关闭按钮 X
  590. $modal.remove()
  591. })
  592.  
  593. // 同步滚动
  594. // 获取两个元素
  595. var $textarea = $modal.find('textarea')
  596. var $preview = $modal.find('.h2m-preview')
  597. var isScrolling = false
  598.  
  599. // 当 textarea 滚动时,设置 preview 的滚动位置
  600. $textarea.on('scroll', function () {
  601. if (isScrolling) {
  602. isScrolling = false
  603. return
  604. }
  605. var scrollPercentage = this.scrollTop / (this.scrollHeight - this.offsetHeight)
  606. $preview[0].scrollTop = scrollPercentage * ($preview[0].scrollHeight - $preview[0].offsetHeight)
  607. isScrolling = true
  608. })
  609.  
  610. // 当 preview 滚动时,设置 textarea 的滚动位置
  611. $preview.on('scroll', function () {
  612. if (isScrolling) {
  613. isScrolling = false
  614. return
  615. }
  616. var scrollPercentage = this.scrollTop / (this.scrollHeight - this.offsetHeight)
  617. $textarea[0].scrollTop = scrollPercentage * ($textarea[0].scrollHeight - $textarea[0].offsetHeight)
  618. isScrolling = true
  619. })
  620.  
  621. $(document).on('keydown', function (e) {
  622. if (e.key === 'Escape' && $('.h2m-modal-overlay').length > 0) {
  623. $('.h2m-modal-overlay').remove()
  624. }
  625. })
  626.  
  627. $('body').append($modal)
  628. }
  629.  
  630. // 开始选择
  631. function startSelecting() {
  632. $('body').addClass('h2m-no-scroll') // 防止页面滚动
  633. isSelecting = true
  634. // 操作指南
  635. tip(marked.parse(translate.guide))
  636. }
  637.  
  638. // 结束选择
  639. function endSelecting() {
  640. isSelecting = false
  641. $('.h2m-selection-box').removeClass('h2m-selection-box')
  642. $('body').removeClass('h2m-no-scroll')
  643. $('.h2m-tip').remove()
  644. }
  645.  
  646. function tip(message, timeout = null) {
  647. var $tipElement = $('<div>')
  648. .addClass('h2m-tip')
  649. .html(message)
  650. .appendTo('body')
  651. .hide()
  652. .fadeIn(200)
  653. if (timeout === null) {
  654. return
  655. }
  656. setTimeout(function () {
  657. $tipElement.fadeOut(200, function () {
  658. $tipElement.remove()
  659. })
  660. }, timeout)
  661. }
  662.  
  663. // Turndown 配置
  664. var turndownPluginGfm = TurndownPluginGfmService
  665. var turndownService = new TurndownService({ codeBlockStyle: 'fenced' })
  666.  
  667. turndownPluginGfm.gfm(turndownService) // 引入全部插件
  668. // turndownService.addRule('strikethrough', {
  669. // filter: ['del', 's', 'strike'],
  670. // replacement: function (content) {
  671. // return '~' + content + '~'
  672. // }
  673. // });
  674.  
  675. // turndownService.addRule('latex', {
  676. // filter: ['mjx-container'],
  677. // replacement: function (content, node) {
  678. // const text = node.querySelector('img')?.title;
  679. // const isInline = !node.getAttribute('display');
  680. // if (text) {
  681. // if (isInline) {
  682. // return '$' + text + '$'
  683. // }
  684. // else {
  685. // return '$$' + text + '$$'
  686. // }
  687. // }
  688. // return '';
  689. // }
  690. // });
  691.  
  692. // 添加CSS样式
  693. GM_addStyle(`
  694. .h2m-selection-box {
  695. border: 2px dashed #f00;
  696. background-color: rgba(255, 0, 0, 0.2);
  697. }
  698. .h2m-no-scroll {
  699. overflow: hidden;
  700. z-index: 9997;
  701. }
  702. .h2m-modal {
  703. position: fixed;
  704. top: 50%;
  705. left: 50%;
  706. transform: translate(-50%, -50%);
  707. width: 80%;
  708. height: 80%;
  709. background: white;
  710. border-radius: 10px;
  711. display: flex;
  712. flex-direction: row;
  713. z-index: 9999;
  714. }
  715. .h2m-modal-overlay {
  716. position: fixed;
  717. top: 0;
  718. left: 0;
  719. width: 100%;
  720. height: 100%;
  721. background: rgba(0, 0, 0, 0.5);
  722. z-index: 9998;
  723. }
  724. .h2m-modal textarea,
  725. .h2m-modal .h2m-preview {
  726. width: 50%;
  727. height: 100%;
  728. padding: 20px;
  729. box-sizing: border-box;
  730. overflow-y: auto;
  731. }
  732. .h2m-modal .h2m-buttons {
  733. position: absolute;
  734. bottom: 10px;
  735. right: 10px;
  736. }
  737. .h2m-modal .h2m-buttons button,
  738. .h2m-modal .h2m-obsidian-select {
  739. margin-left: 10px;
  740. background-color: #4CAF50; /* Green */
  741. border: none;
  742. color: white;
  743. padding: 13px 16px;
  744. border-radius: 10px;
  745. text-align: center;
  746. text-decoration: none;
  747. display: inline-block;
  748. font-size: 16px;
  749. transition-duration: 0.4s;
  750. cursor: pointer;
  751. }
  752. .h2m-modal .h2m-buttons button:hover,
  753. .h2m-modal .h2m-obsidian-select:hover {
  754. background-color: #45a049;
  755. }
  756. .h2m-modal .h2m-close {
  757. position: absolute;
  758. top: 10px;
  759. right: 10px;
  760. cursor: pointer;
  761. width: 25px;
  762. height: 25px;
  763. background-color: #f44336;
  764. color: white;
  765. font-size: 16px;
  766. border-radius: 50%;
  767. display: flex;
  768. justify-content: center;
  769. align-items: center;
  770. }
  771. .h2m-tip {
  772. position: fixed;
  773. top: 22%;
  774. left: 82%;
  775. transform: translate(-50%, -50%);
  776. background-color: white;
  777. border: 1px solid black;
  778. padding: 8px;
  779. z-index: 9999;
  780. border-radius: 10px;
  781. box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);
  782. background-color: rgba(255, 255, 255, 0.7);
  783. }
  784. `)
  785.  
  786. // 注册(不可用)触发器
  787. shortCutConfig = shortCutConfig ? shortCutConfig : {
  788. 'Shift': false,
  789. 'Ctrl': true,
  790. 'Alt': false,
  791. 'Key': 'm'
  792. }
  793. $(document).on('keydown', function (e) {
  794. if (e.ctrlKey === shortCutConfig['Ctrl'] &&
  795. e.altKey === shortCutConfig['Alt'] &&
  796. e.shiftKey === shortCutConfig['Shift'] &&
  797. e.key.toUpperCase() === shortCutConfig['Key'].toUpperCase()) {
  798. e.preventDefault()
  799. startSelecting()
  800. }
  801. // else {
  802. // console.log(e.ctrlKey, e.altKey, e.shiftKey, e.key.toUpperCase());
  803. // }
  804. })
  805. // $(document).on('keydown', function (e) {
  806. // if (e.ctrlKey && e.key === 'm') {
  807. // e.preventDefault();
  808. // startSelecting()
  809. // }
  810. // });
  811.  
  812. GM_registerMenuCommand(`${translate.menu}`, function () {
  813. startSelecting()
  814. })
  815. GM_registerMenuCommand(`${translate.gui_title}`, function () {
  816. showConfig()
  817. })
  818.  
  819. $(document).on('mouseover', function (e) { // 开始选择
  820. if (isSelecting) {
  821. $(selectedElement).removeClass('h2m-selection-box')
  822. selectedElement = e.target
  823. $(selectedElement).addClass('h2m-selection-box')
  824. }
  825. }).on('wheel', function (e) { // 滚轮事件
  826. if (isSelecting) {
  827. e.preventDefault()
  828. if (e.originalEvent.deltaY < 0) {
  829. selectedElement = selectedElement.parentElement ? selectedElement.parentElement : selectedElement // 扩大
  830. if (selectedElement.tagName === 'HTML' || selectedElement.tagName === 'BODY') {
  831. selectedElement = selectedElement.firstElementChild
  832. }
  833. } else {
  834. selectedElement = selectedElement.firstElementChild ? selectedElement.firstElementChild : selectedElement // 缩小
  835. }
  836. $('.h2m-selection-box').removeClass('h2m-selection-box')
  837. $(selectedElement).addClass('h2m-selection-box')
  838. }
  839. }).on('keydown', function (e) { // 键盘事件
  840. if (isSelecting) {
  841. e.preventDefault()
  842. if (e.key === 'Escape') {
  843. endSelecting()
  844. return
  845. }
  846. switch (e.key) { // 方向键:上下左右
  847. case 'ArrowUp':
  848. selectedElement = selectedElement.parentElement ? selectedElement.parentElement : selectedElement // 扩大
  849. if (selectedElement.tagName === 'HTML' || selectedElement.tagName === 'BODY') { // 排除HTML 和 BODY
  850. selectedElement = selectedElement.firstElementChild
  851. }
  852. break
  853. case 'ArrowDown':
  854. selectedElement = selectedElement.firstElementChild ? selectedElement.firstElementChild : selectedElement // 缩小
  855. break
  856. case 'ArrowLeft': // 寻找上一个元素,若是最后一个子元素则选择父元素的下一个兄弟元素,直到找到一个元素
  857. var prev = selectedElement.previousElementSibling
  858. while (prev === null && selectedElement.parentElement !== null) {
  859. selectedElement = selectedElement.parentElement
  860. prev = selectedElement.previousElementSibling ? selectedElement.previousElementSibling.lastChild : null
  861. }
  862. if (prev !== null) {
  863. if (selectedElement.tagName === 'HTML' || selectedElement.tagName === 'BODY') {
  864. selectedElement = selectedElement.firstElementChild
  865. }
  866. selectedElement = prev
  867. }
  868. break
  869. case 'ArrowRight':
  870. var next = selectedElement.nextElementSibling
  871. while (next === null && selectedElement.parentElement !== null) {
  872. selectedElement = selectedElement.parentElement
  873. next = selectedElement.nextElementSibling ? selectedElement.nextElementSibling.firstElementChild : null
  874. }
  875. if (next !== null) {
  876. if (selectedElement.tagName === 'HTML' || selectedElement.tagName === 'BODY') {
  877. selectedElement = selectedElement.firstElementChild
  878. }
  879. selectedElement = next
  880. }
  881. break
  882. }
  883.  
  884. $('.h2m-selection-box').removeClass('h2m-selection-box')
  885. $(selectedElement).addClass('h2m-selection-box') // 更新选中元素的样式
  886. }
  887. }
  888. ).on('mousedown', function (e) { // 鼠标事件,选择 mousedown 是因为防止点击元素后触发其他事件
  889. if (isSelecting) {
  890. e.preventDefault()
  891. var markdown = convertToMarkdown(selectedElement)
  892. showMarkdownModal(markdown)
  893. endSelecting()
  894. }
  895. })
  896. function createIssue(token, owner, repo, title, body, labels = []) {
  897. const url = `https://api.github.com/repos/${owner}/${repo}/issues`
  898. const issueData = {
  899. title: title,
  900. body: body,
  901. labels: labels
  902. }
  903. const xhr = new XMLHttpRequest()
  904. xhr.open('POST', url, true)
  905. xhr.setRequestHeader('Authorization', `token ${token}`)
  906. xhr.setRequestHeader('Accept', 'application/vnd.github+json')
  907. xhr.setRequestHeader('Content-Type', 'application/json')
  908. xhr.onreadystatechange = function () {
  909. if (xhr.readyState === 4) {
  910. if (xhr.status >= 200 && xhr.status < 300) {
  911. const response = JSON.parse(xhr.responseText)
  912. showDialog(translate.github_success, response.html_url)
  913.  
  914. } else {
  915. alert(`${translate.github_failed}\n ${xhr.status}\n ${xhr.statusText}\n ${xhr.responseText}`)
  916. console.error(`${translate.github_failed}`, xhr.status, xhr.statusText, xhr.responseText)
  917. }
  918. }
  919. }
  920. xhr.send(JSON.stringify(issueData))
  921. }
  922. })()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址