🏠 Home 

MarkDown Cloud Cut Notes

A tool that converts web content to Markdown format, supports features such as copying, downloading, and sending to GitHub and Obsidian.

// ==UserScript==
// @name              MarkDown Cloud Cut Notes
// @name:ar           ملاحظات قطع السحابة المتولد
// @name:bg           Бележки за изрязване на облак от Маркдаун
// @name:cs           Poznámky k cloudovému řezu Markdown
// @name:da           Markdown Cloud Cut Notes
// @name:de           Markdown Cloud Cut -Notizen
// @name:el           Σημειώσεις κοπής σύννεφων Markdown
// @name:en           MarkDown Cloud Cut Notes
// @name:eo           Markdown Cloud Cut Notes
// @name:es           Notas de corte de nube de markdown
// @name:fi           Markdown Cloud Cut Notes
// @name:fr           Notes de coupe de cloud de Markdown
// @name:fr-CA        Notes de coupe de cloud de Markdown
// @name:he           הערות Culd Cloud Markdown
// @name:hr           Markdown Cloud Cut Notes
// @name:hu           Markdown Cloud Cut jegyzetek
// @name:id           Markdown Cloud Cut Notes
// @name:it           Markdown Cloud Cut Notes
// @name:ja           マークダウンクラウドカットノート
// @name:ka           Markdown Cloud Cut Notes
// @name:ko           마크 다운 클라우드 컷 메모
// @name:nb           Markdown Cloud Cut Notes
// @name:nl           Markdown Cloud Cut Notes
// @name:pl           Notatki z Cloud Cloud Cloud
// @name:pt-BR        Notas de corte na nuvem de marcação
// @name:ro           Note de tăiere a norului markdown
// @name:ru           Примечания к облаку отметки
// @name:sk           Poznámky k mraku cloudu
// @name:sr           Напомена Цлоуд Цлоуд Сцрет Нотес
// @name:sv           Markdown Cloud Cut -anteckningar
// @name:th           MARKDOWN Cloud Cut Notes
// @name:tr           Markdown bulut kesim notları
// @name:ug           ماركېل بۇلۇت ئۈزۈلۈپ قالدى
// @name:uk           Нотатки з вирізанням Cloud Cloud
// @name:vi           Markdown Cloud Cut Ghi chú
// @name:zh           MarkDown 云剪笔记
// @name:zh-CN        MarkDown 云剪笔记
// @name:zh-HK        MarkDown 雲剪筆記
// @name:zh-SG        MarkDown 云剪笔记
// @name:zh-TW        MarkDown 雲剪筆記
// @description       A tool that converts web content to Markdown format, supports features such as copying, downloading, and sending to GitHub and Obsidian.
// @description:ar    تقوم أداة بتحويل محتوى الويب إلى تنسيق Markdown ، ويدعم ميزات مثل نسخ وتنزيل وإرسال Github و Obsidian.
// @description:bg    Инструмент, който преобразува уеб съдържание във формат за маркиране, поддържа функции като копиране, изтегляне и изпращане на GitHub и Obsidian.
// @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.
// @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.
// @description:de    Ein Tool, das Webinhalte in Markdown -Format umwandelt, unterstützt Funktionen wie das Kopieren, Herunterladen und Senden an GitHub und Obsidian.
// @description:el    Ένα εργαλείο που μετατρέπει το περιεχόμενο ιστού σε μορφή Markdown, υποστηρίζει χαρακτηριστικά όπως η αντιγραφή, η λήψη και η αποστολή στο GitHub και ο Obsidian.
// @description:en    A tool that converts web content to Markdown format, supports features such as copying, downloading, and sending to GitHub and Obsidian.
// @description:eo    Ilo, kiu konvertas retan enhavon al Markdown -formato, subtenas funkciojn kiel kopii, elŝuti kaj sendi al Github kaj Obsidian.
// @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.
// @description:fi    Työkalu, joka muuntaa verkkosisällön merkinnän muotoon, tukee ominaisuuksia, kuten kopiointia, lataamista ja lähettämistä GitHubille ja Obsidianille.
// @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.
// @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.
// @description:he    כלי שממיר תוכן אינטרנט לפורמט Markdown, תומך בתכונות כמו העתקה, הורדה ושליחה ל- Github ו- Obsidian.
// @description:hr    Alat koji pretvara web sadržaj u Markdown format, podržava značajke poput kopiranja, preuzimanja i slanja GitHub i Obsidian.
// @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.
// @description:id    Alat yang mengonversi konten web ke format penurunan harga, mendukung fitur seperti menyalin, mengunduh, dan mengirim ke GitHub dan Obsidian.
// @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.
// @description:ja    WebコンテンツをMarkdown形式に変換するツールは、GithubやObsidianへのコピー、ダウンロード、送信などの機能をサポートします。
// @description:ka    ინსტრუმენტი, რომელიც გარდაქმნის ვებ შინაარსს MarkDown ფორმატში, მხარს უჭერს ისეთ ფუნქციებს, როგორიცაა კოპირება, ჩამოტვირთვა და გაგზავნა Github და Obsidian.
// @description:ko    웹 컨텐츠를 Markdown 형식으로 변환하는 도구는 복사, 다운로드 및 Github 및 Obsidian으로 전송과 같은 기능을 지원합니다.
// @description:nb    Et verktøy som konverterer nettinnhold til Markdown -format, støtter funksjoner som kopiering, nedlasting og sending til Github og Obsidian.
// @description:nl    Een tool die webinhoud converteert naar Markdown -indeling, ondersteunt functies zoals kopiëren, downloaden en verzenden naar GitHub en Obsidian.
// @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.
// @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.
// @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.
// @description:ru    Инструмент, который преобразует веб -контент в формат разметки, поддерживает такие функции, как копирование, загрузка и отправка в Github и Obsidian.
// @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.
// @description:sr    Алат који претвара Веб садржај у ознаку формата, подржава функције као што су копирање, преузимање и слање Гитхуб-а и Обсидијан.
// @description:sv    Ett verktyg som konverterar webbinnehåll till Markdown -format, stöder funktioner som kopiering, nedladdning och skickning till GitHub och Obsidian.
// @description:th    เครื่องมือที่แปลงเนื้อหาเว็บเป็นรูปแบบ Markdown รองรับคุณสมบัติต่าง ๆ เช่นการคัดลอกการดาวน์โหลดและการส่งไปยัง GitHub และ Obsidian
// @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.
// @description:ug    تور مەزمۇنىنى بەلگە قىلىپ بەلگە قىلالايدىغان بىر قورال گاتسىيە ۋە Obstidiation غا توپلاش ۋە ئەۋەتىش قاتارلىق ئىقتىدارلارنى قوللايدىغان ئىقتىدارلارنى قوللايدۇ.
// @description:uk    Інструмент, який перетворює веб -вміст у формат Markdown, підтримує такі функції, як копіювання, завантаження та надсилання в Github та Обсидіан.
// @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.
// @description:zh    将网页内容转换为 Markdown 格式的工具,支持复制、下载、发送到 GitHub 和 Obsidian 等功能。
// @description:zh-CN 将网页内容转换为 Markdown 格式的工具,支持复制、下载、发送到 GitHub 和 Obsidian 等功能。
// @description:zh-HK 將網頁內容轉換為 Markdown 格式的工具,支持複製、下載、發送到 GitHub 和 Obsidian 等功能。
// @description:zh-SG 将网页内容转换为 Markdown 格式的工具,支持复制、下载、发送到 GitHub 和 Obsidian 等功能。
// @description:zh-TW 將網頁內容轉換為 Markdown 格式的工具,支持複製、下載、發送到 GitHub 和 Obsidian 等功能。
// @author            shiquda,人民的勤务员 <[email protected]>
// @namespace         https://github.com/#####GodMan/UserScripts
// @supportURL        https://github.com/#####GodMan/UserScripts/issues
// @homepageURL       https://github.com/#####GodMan/UserScripts
// @license           MIT
// @grant             GM_addStyle
// @grant             GM_registerMenuCommand
// @grant             GM_setClipboard
// @grant             GM_setValue
// @grant             GM_getValue
// @require           https://code.jquery.com/jquery-3.6.0.min.js
// @require           https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
// @require           https://unpkg.com/turndown/dist/turndown.js
// @require           https://unpkg.com/@guyplusplus/turndown-plugin-gfm/dist/turndown-plugin-gfm.js
// @require           https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.0/marked.min.js
// @match             *://*/*
// @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==
// @compatible        chrome
// @compatible        firefox
// @compatible        edge
// @compatible        opera
// @compatible        safari
// @compatible        kiwi
// @compatible        qq
// @compatible        via
// @compatible        brave
// @version           2025.03.19.0450
// @created           2025-03-18 07:13:13
// @modified          2025-03-18 07:13:13
// ==/UserScript==
/**
* File: web-clipper.user.js
* Project: UserScripts
* File Created: 2025/03/18,Tuesday 07:13:13
* Author: 人民的勤务员@#####GodMan ([email protected])
* -----
* Last Modified: 2025/03/19,Wednesday 04:50:15
* Modified By: 人民的勤务员@#####GodMan ([email protected])
* -----
* License: MIT License
* Copyright © 2024 - 2025 #####GodMan,Inc
*/
//! https://greasyfork.org/zh-CN/scripts/486888
(function () {
'use strict'
// User Config
// Short cut
const shortCutUserConfig = {
/* Example:
"Shift": false,
"Ctrl": true,
"Alt": false,
"Key": "m"
*/
}
// Obsidian
const obsidianUserConfig = {
/* Example:
"my note": [
"Inbox/Web/",
"Collection/Web/Reading/"
]
*/
}
const userLang =
(navigator.languages && navigator.languages[0]) ||
navigator.language ||
'en'
const translations = {
en: {
copy: 'Copy to clipboard',
copied: 'Copied successfully!',
download_md: 'Download as MD',
send_to_github: 'Send to Github',
send_to_obsidian: 'Send to Obsidian',
github_failed: 'Creation failed:',
github_success: 'Creation succeeded:',
configure: 'Please configure your GitHub information first',
menu: 'Convert to Markdown',
gui_title: 'Set Up GitHub',
gui_tokeninput: 'Please enter your GitHub Personal Access Token',
gui_github_repo: 'Please enter your GitHub repository name',
gui_github_generate: 'Generate',
gui_github_owner: 'Please enter your GitHub username',
gui_save: 'Save',
gui_cancel: 'Cancel',
guide: `
- Use **arrow keys** to select elements:
- Up: Select parent element
- Down: Select the first child element
- Left: Select the previous sibling element
- Right: Select the next sibling element
- Use **scroll wheel** to zoom in and out:
* Up: Select parent element
- Down: Select the first child element
- Click to select an element
- Press \`Esc\` key to cancel selection
`
},
'zh-CN,zh,zh-SG': {
copy: '复制到剪辑版',
copied: '复制成功!',
download_md: '下载为MD',
send_to_github: '保存到GitHub',
send_to_obsidian: '发送到Obsidian',
github_failed: '创建失败:',
github_success: '创建成功:',
configure: '请先配置你的GitHub信息',
menu: '转换为Markdown',
gui_title: '设置 GitHub',
gui_tokeninput: '请输入您的GitHub个人访问令牌',
gui_github_repo: '请输入您的GitHub仓库名称',
gui_github_generate: '生成',
gui_github_owner: '请输入您的GitHub用户名',
gui_save: '保存',
gui_cancel: '取消',
guide: `
- 使用**方向键**选择元素
- 上:选择父元素
- 下:选择第一个子元素
- 左:选择上一个兄弟元素
- 右:选择下一个兄弟元素
- 使用**滚轮**放大缩小
- 上:选择父元素
- 下:选择第一个子元素
- 点击元素选择
- 按下 \`Esc\` 键取消选择
`
},
'zh-TW,zh-HK,zh-MO': {
copy: '複製到剪貼簿',
copied: '複製成功!',
download_md: '下載為MD',
send_to_github: '保存到GitHub',
send_to_obsidian: '發送到Obsidian',
github_failed: '創建失敗:',
github_success: '創建成功:',
configure: '請先配置你的GitHub 信息',
menu: '轉換為Markdown',
guide: `
- 使用**方向鍵**選擇元素
- 上:選擇父元素
- 下:選擇第一個子元素
- 左:選擇上一個兄弟元素
- 右:選擇下一個兄弟元素
- 使用**滾輪**放大縮小
- 上:選擇父元素
- 下:選擇第一個子元素
- 點擊元素選擇
- 按下 \`Esc\` 鍵取消選擇
`
},
vi: {
copy: 'Sao chép vào clipboard',
copied: 'Sao chép thành công!',
download_md: 'Tải xuống dưới dạng MD',
send_to_github: 'Gửi đến Github',
send_to_obsidian: 'Gửi đến Obsidian',
github_failed: 'Tạo thất bại:',
github_success: 'Tạo thành công:',
configure: 'Vui lòng cấu hình thông tin GitHub của bạn trước',
menu: 'Chuyển đổi sang Markdown',
guide: `
- Sử dụng **phím mũi tên** để chọn các phần tử:
- Lên: Chọn phần tử cha
- Xuống: Chọn phần tử con đầu tiên
- Trái: Chọn phần tử anh em trước
- Phải: Chọn phần tử anh em sau
- Sử dụng **bánh xe cuộn** để phóng to và thu nhỏ:
- Lên: Chọn phần tử cha
- Xuống: Chọn phần tử con đầu tiên
- Nhấp để chọn một phần tử
- Nhấn phím \`Esc\` để hủy chọn
`
},
ja: {
copy: 'クリップボードにコピー',
copied: 'コピー成功!',
download_md: 'MDとしてダウンロード',
send_to_github: 'Githubに送信',
send_to_obsidian: 'Obsidianに送信',
github_failed: '作成失敗:',
github_success: '作成成功:',
configure: 'まずGitHub情報を設定してください',
menu: 'Markdownに変換',
guide: `
- **矢印キー**を使用して要素を選択します:
- 上: 親要素を選択
- 下: 最初の子要素を選択
- 左: 前の兄弟要素を選択
- 右: 次の兄弟要素を選択
- **スクロールホイール**を使用してズームインおよびズームアウトします:
- 上: 親要素を選択
- 下: 最初の子要素を選択
- 要素をクリックして選択
- \`Esc\`キーを押して選択をキャンセル
`
},
ko: {
copy: '클립보드에 복사',
copied: '복사 성공!',
download_md: 'MD로 다운로드',
send_to_github: 'Github에 보내기',
send_to_obsidian: 'Obsidian에 보내기',
github_failed: '생성 실패:',
github_success: '생성 성공:',
configure: '먼저 GitHub 정보를 구성하십시오',
menu: 'Markdown으로 변환',
guide: `
- **화살표 키**를 사용하여 요소를 선택합니다:
- 위: 부모 요소 선택
- 아래: 첫 번째 자식 요소 선택
- 왼쪽: 이전 형제 요소 선택
- 오른쪽: 다음 형제 요소 선택
- **스크롤 휠**을 사용하여 확대 및 축소합니다:
- 위: 부모 요소 선택
- 아래: 첫 번째 자식 요소 선택
- 요소를 선택하려면 클릭
- 선택을 취소하려면 \`Esc\` 키를 누르십시오
`
},
fr: {
copy: 'Copier dans le presse-papiers',
copied: 'Copié avec succès!',
download_md: 'Télécharger en tant que MD',
send_to_github: 'Envoyer à Github',
send_to_obsidian: 'Envoyer à Obsidian',
github_failed: 'Échec de la création:',
github_success: 'Création réussie:',
configure: 'Veuillez d\'abord configurer vos informations GitHub',
menu: 'Convertir en Markdown',
guide: `
- Utilisez les **touches fléchées** pour sélectionner les éléments:
- Haut: Sélectionner l'élément parent
- Bas: Sélectionner le premier élément enfant
- Gauche: Sélectionner l'élément frère précédent
- Droite: Sélectionner l'élément frère suivant
- Utilisez la **molette de défilement** pour zoomer et dézoomer:
- Haut: Sélectionner l'élément parent
- Bas: Sélectionner le premier élément enfant
- Cliquez pour sélectionner un élément
- Appuyez sur la touche \`Esc\` pour annuler la sélection
`
},
it: {
copy: 'Copia negli appunti',
copied: 'Copiato con successo!',
download_md: 'Scarica come MD',
send_to_github: 'Invia a Github',
send_to_obsidian: 'Invia a Obsidian',
github_failed: 'Creazione fallita:',
github_success: 'Creazione riuscita:',
configure: 'Si prega di configurare prima le informazioni di GitHub',
menu: 'Converti in Markdown',
guide: `
- Usa i **tasti freccia** per selezionare gli elementi:
- Su: Seleziona l'elemento padre
- Giù: Seleziona il primo elemento figlio
- Sinistra: Seleziona l'elemento fratello precedente
- Destra: Seleziona l'elemento fratello successivo
- Usa la **rotella di scorrimento** per ingrandire e ridurre:
- Su: Seleziona l'elemento padre
- Giù: Seleziona il primo elemento figlio
- Clicca per selezionare un elemento
- Premi il tasto \`Esc\` per annullare la selezione
`
},
de: {
copy: 'In die Zwischenablage kopieren',
copied: 'Erfolgreich kopiert!',
download_md: 'Als MD herunterladen',
send_to_github: 'An Github senden',
send_to_obsidian: 'An Obsidian senden',
github_failed: 'Erstellung fehlgeschlagen:',
github_success: 'Erstellung erfolgreich:',
configure: 'Bitte konfigurieren Sie zuerst Ihre GitHub-Informationen',
menu: 'In Markdown konvertieren',
guide: `
- Verwenden Sie die **Pfeiltasten**, um Elemente auszuwählen:
- Oben: Elternelement auswählen
- Unten: Erstes Kindelement auswählen
- Links: Vorheriges Geschwisterelement auswählen
- Rechts: Nächstes Geschwisterelement auswählen
- Verwenden Sie das **Scrollrad**, um hinein- und herauszuzoomen:
- Oben: Elternelement auswählen
- Unten: Erstes Kindelement auswählen
- Klicken Sie, um ein Element auszuwählen
- Drücken Sie die \`Esc\`-Taste, um die Auswahl abzubrechen
`
}
}
const getTranslations = (lang) => {
for (const key in translations) {
if (key === lang || key.split(',').includes(lang)) {
return translations[key]
}
}
return translations['en']
}
const translate = new Proxy(
function (key) {
const lang = userLang
const strings = getTranslations(lang)
return strings[key] || translations['en'][key]
},
{
get(target, prop) {
const lang = userLang
const strings = getTranslations(lang)
return strings[prop] || translations['en'][prop]
}
}
)
// 全局变量
var isSelecting = false
var selectedElement = null
let shortCutConfig, obsidianConfig
// 读取配置
// 初始化快捷键配置
let storedShortCutConfig = GM_getValue('shortCutConfig')
if (Object.keys(shortCutUserConfig).length !== 0) {
GM_setValue('shortCutConfig', JSON.stringify(shortCutUserConfig))
shortCutConfig = shortCutUserConfig
} else if (storedShortCutConfig) {
shortCutConfig = JSON.parse(storedShortCutConfig)
}
// 初始化Obsidian配置
let storedObsidianConfig = GM_getValue('obsidianConfig')
if (Object.keys(obsidianUserConfig).length !== 0) {
GM_setValue('obsidianConfig', JSON.stringify(obsidianUserConfig))
obsidianConfig = obsidianUserConfig
} else if (storedObsidianConfig) {
obsidianConfig = JSON.parse(storedObsidianConfig)
}
GM_addStyle(`
.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;}
.modal-content{background:white;padding:20px;border-radius:8px;width:400px;box-shadow:0 4px 15px rgba(0,0,0,0.2);position:relative;}
.modal-title{margin:0 0 10px 0;font-size:20px;}
.modal-description{margin-bottom:20px;font-size:14px;color:#666;}
.modal-description a{color:#007bff;text-decoration:underline;}
.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;}
.modal-close-btn:hover {background-color: darkred;}
.gui-input{width:100%;padding:8px;border:1px solid #ccc;border-radius:4px;margin-bottom:20px;font-size:14px;}
#save-token{background-color:#28a745;color:white;border:none;padding:10px 20px;cursor:pointer;border-radius:4px;margin-right:10px;}
#cancel-token{background-color:#dc3545;color:white;border:none;padding:10px 20px;cursor:pointer;border-radius:4px;}
`)
function showConfig() {
const modalHTML = `
<div class="modal-overlay">
<div class="modal-content">
<h3 class="modal-title">${translate.gui_title}</h3>
<p class="modal-description">${translate.gui_tokeninput}
<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>
</p>
<input type="text" id="github-token-input" class="gui-input" placeholder="${translate.gui_tokeninput}">
<p class="modal-description">${translate.gui_github_owner}</p>
<input type="text" id="github-owner-input" class="gui-input" placeholder="${translate.gui_github_owner}">
<p class="modal-description">${translate.gui_github_repo}</p>
<input type="text" id="github-repo-input" class="gui-input" placeholder="${translate.gui_github_repo}">
<button id="save-token">${translate.gui_save}</button>
<button id="cancel-token" class="cancel">${translate.gui_cancel}</button>
</div>
</div>
`
const modalContainer = document.createElement('div')
modalContainer.innerHTML = modalHTML
document.body.appendChild(modalContainer)
const elements = {
token: modalContainer.querySelector('#github-token-input'),
owner: modalContainer.querySelector('#github-owner-input'),
repo: modalContainer.querySelector('#github-repo-input'),
saveButton: modalContainer.querySelector('#save-token'),
cancelButton: modalContainer.querySelector('#cancel-token')
}
elements.token.value = GM_getValue('github_token', '')
elements.owner.value = GM_getValue('OWNER', '')
elements.repo.value = GM_getValue('REPO', '')
elements.saveButton.addEventListener('click', () => {
if (elements.token.value && elements.owner.value && elements.repo.value) {
GM_setValue('github_token', elements.token.value)
GM_setValue('OWNER', elements.owner.value)
GM_setValue('REPO', elements.repo.value)
modalContainer.remove()
} else {
alert(`${translate.configure}`)
}
})
elements.cancelButton.addEventListener('click', () => modalContainer.remove())
}
function showDialog(info, link) {
const modalHTML = `
<div class="modal-overlay">
<div class="modal-content">
<button class="modal-close-btn" id="cancel-token" >×</button>
<h3 class="modal-title">${info}</h3>
<p class="modal-description">
${info}
<a href="${link}" target="_blank" rel="noopener noreferrer">${link}</a>
</p>
</div>
</div>
`
const modalContainer = document.createElement('div')
modalContainer.innerHTML = modalHTML
document.body.appendChild(modalContainer)
modalContainer.querySelector('#cancel-token').addEventListener('click', () => modalContainer.remove())
}
// HTML2Markdown
function convertToMarkdown(element) {
var html = element.outerHTML
let turndownMd = turndownService.turndown(html)
turndownMd = turndownMd.replaceAll('[\n\n]', '[]') // 防止 <a> 元素嵌套的暂时方法,并不完善
return turndownMd
}
// 预览
function showMarkdownModal(markdown) {
var $modal = $(`
<div class="h2m-modal-overlay">
<div class="h2m-modal">
<textarea>${markdown}</textarea>
<div class="h2m-preview">${marked.parse(markdown)}</div>
<div class="h2m-buttons">
<button class="h2m-copy">${translate.copy}</button>
<button class="h2m-download">${translate.download_md}</button>
<button class="h2m-github">${translate.send_to_github}</button>
<select class="h2m-obsidian-select">${translate.send_to_obsidian}</select>
</div>
<button class="h2m-close">X</button>
</div>
</div>
`)
$modal.find('.h2m-obsidian-select').append($('<option>').val('').text(`${translate.send_to_obsidian}`))
for (const vault in obsidianConfig) {
for (const path of obsidianConfig[vault]) {
// 插入元素
const $option = $('<option>')
.val(`obsidian://advanced-uri?vault=${vault}&filepath=${path}`)
.text(`${vault}: ${path}`)
$modal.find('.h2m-obsidian-select').append($option)
}
}
$modal.find('textarea').on('input', function () {
// console.log("Input event triggered");
var markdown = $(this).val()
var html = marked.parse(markdown)
// console.log("Markdown:", markdown);
// console.log("HTML:", html);
$modal.find('.h2m-preview').html(html)
})
$modal.on('keydown', function (e) {
if (e.key === 'Escape') {
$modal.remove()
}
})
$modal.find('.h2m-copy').on('click', function () { // 复制到剪贴板
GM_setClipboard($modal.find('textarea').val())
$modal.find('.h2m-copy').text(`${translate.copied}`)
setTimeout(() => {
$modal.find('.h2m-copy').text(`${translate.copy}`)
}, 1000)
})
$modal.find('.h2m-github').on('click', function () {
const github_token = GM_getValue('github_token', '')
const github_owner = GM_getValue('OWNER', '')
const github_repo = GM_getValue('REPO', '')
if (!github_token || !github_owner || !github_repo) {
showConfig()
return
}
const labels = ['web-clipper']//标签,可多项
const markdown = $modal.find('textarea').val()
const title = markdown.split('\n')[0]
const body = markdown
createIssue(github_token, github_owner, github_repo, title, body, labels)
})
$modal.find('.h2m-download').on('click', function () { // 下载
var markdown = $modal.find('textarea').val()
var blob = new Blob([markdown], { type: 'text/markdown' })
var url = URL.createObjectURL(blob)
var a = document.createElement('a')
a.href = url
// 当前页面标题 + 时间
a.download = `${document.title}-${new Date().toISOString().replace(/:/g, '-')}.md`
a.click()
})
$modal.find('.h2m-obsidian-select').on('change', function () { // 发送到 Obsidian
const val = $(this).val()
if (!val) return
const markdown = $modal.find('textarea').val()
GM_setClipboard(markdown)
const title = document.title.replaceAll(/[\\/:*?"<>|]/g, '_') // File name cannot contain any of the following characters: * " \ / < > : | ?
const url = `${val}${title}.md&clipboard=true`
window.open(url)
})
$modal.find('.h2m-close').on('click', function () { // 关闭按钮 X
$modal.remove()
})
// 同步滚动
// 获取两个元素
var $textarea = $modal.find('textarea')
var $preview = $modal.find('.h2m-preview')
var isScrolling = false
// 当 textarea 滚动时,设置 preview 的滚动位置
$textarea.on('scroll', function () {
if (isScrolling) {
isScrolling = false
return
}
var scrollPercentage = this.scrollTop / (this.scrollHeight - this.offsetHeight)
$preview[0].scrollTop = scrollPercentage * ($preview[0].scrollHeight - $preview[0].offsetHeight)
isScrolling = true
})
// 当 preview 滚动时,设置 textarea 的滚动位置
$preview.on('scroll', function () {
if (isScrolling) {
isScrolling = false
return
}
var scrollPercentage = this.scrollTop / (this.scrollHeight - this.offsetHeight)
$textarea[0].scrollTop = scrollPercentage * ($textarea[0].scrollHeight - $textarea[0].offsetHeight)
isScrolling = true
})
$(document).on('keydown', function (e) {
if (e.key === 'Escape' && $('.h2m-modal-overlay').length > 0) {
$('.h2m-modal-overlay').remove()
}
})
$('body').append($modal)
}
// 开始选择
function startSelecting() {
$('body').addClass('h2m-no-scroll') // 防止页面滚动
isSelecting = true
// 操作指南
tip(marked.parse(translate.guide))
}
// 结束选择
function endSelecting() {
isSelecting = false
$('.h2m-selection-box').removeClass('h2m-selection-box')
$('body').removeClass('h2m-no-scroll')
$('.h2m-tip').remove()
}
function tip(message, timeout = null) {
var $tipElement = $('<div>')
.addClass('h2m-tip')
.html(message)
.appendTo('body')
.hide()
.fadeIn(200)
if (timeout === null) {
return
}
setTimeout(function () {
$tipElement.fadeOut(200, function () {
$tipElement.remove()
})
}, timeout)
}
// Turndown 配置
var turndownPluginGfm = TurndownPluginGfmService
var turndownService = new TurndownService({ codeBlockStyle: 'fenced' })
turndownPluginGfm.gfm(turndownService) // 引入全部插件
// turndownService.addRule('strikethrough', {
//     filter: ['del', 's', 'strike'],
//     replacement: function (content) {
//         return '~' + content + '~'
//     }
// });
// turndownService.addRule('latex', {
//     filter: ['mjx-container'],
//     replacement: function (content, node) {
//         const text = node.querySelector('img')?.title;
//         const isInline = !node.getAttribute('display');
//         if (text) {
//             if (isInline) {
//                 return '$' + text + '$'
//             }
//             else {
//                 return '$$' + text + '$$'
//             }
//         }
//         return '';
//     }
// });
// 添加CSS样式
GM_addStyle(`
.h2m-selection-box {
border: 2px dashed #f00;
background-color: rgba(255, 0, 0, 0.2);
}
.h2m-no-scroll {
overflow: hidden;
z-index: 9997;
}
.h2m-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
height: 80%;
background: white;
border-radius: 10px;
display: flex;
flex-direction: row;
z-index: 9999;
}
.h2m-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 9998;
}
.h2m-modal textarea,
.h2m-modal .h2m-preview {
width: 50%;
height: 100%;
padding: 20px;
box-sizing: border-box;
overflow-y: auto;
}
.h2m-modal .h2m-buttons {
position: absolute;
bottom: 10px;
right: 10px;
}
.h2m-modal .h2m-buttons button,
.h2m-modal .h2m-obsidian-select {
margin-left: 10px;
background-color: #4CAF50; /* Green */
border: none;
color: white;
padding: 13px 16px;
border-radius: 10px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
transition-duration: 0.4s;
cursor: pointer;
}
.h2m-modal .h2m-buttons button:hover,
.h2m-modal .h2m-obsidian-select:hover {
background-color: #45a049;
}
.h2m-modal .h2m-close {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
width: 25px;
height: 25px;
background-color: #f44336;
color: white;
font-size: 16px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.h2m-tip {
position: fixed;
top: 22%;
left: 82%;
transform: translate(-50%, -50%);
background-color: white;
border: 1px solid black;
padding: 8px;
z-index: 9999;
border-radius: 10px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);
background-color: rgba(255, 255, 255, 0.7);
}
`)
// 注册触发器
shortCutConfig = shortCutConfig ? shortCutConfig : {
'Shift': false,
'Ctrl': true,
'Alt': false,
'Key': 'm'
}
$(document).on('keydown', function (e) {
if (e.ctrlKey === shortCutConfig['Ctrl'] &&
e.altKey === shortCutConfig['Alt'] &&
e.shiftKey === shortCutConfig['Shift'] &&
e.key.toUpperCase() === shortCutConfig['Key'].toUpperCase()) {
e.preventDefault()
startSelecting()
}
// else {
//     console.log(e.ctrlKey, e.altKey, e.shiftKey, e.key.toUpperCase());
// }
})
// $(document).on('keydown', function (e) {
//     if (e.ctrlKey && e.key === 'm') {
//         e.preventDefault();
//         startSelecting()
//     }
// });
GM_registerMenuCommand(`${translate.menu}`, function () {
startSelecting()
})
GM_registerMenuCommand(`${translate.gui_title}`, function () {
showConfig()
})
$(document).on('mouseover', function (e) { // 开始选择
if (isSelecting) {
$(selectedElement).removeClass('h2m-selection-box')
selectedElement = e.target
$(selectedElement).addClass('h2m-selection-box')
}
}).on('wheel', function (e) { // 滚轮事件
if (isSelecting) {
e.preventDefault()
if (e.originalEvent.deltaY < 0) {
selectedElement = selectedElement.parentElement ? selectedElement.parentElement : selectedElement // 扩大
if (selectedElement.tagName === 'HTML' || selectedElement.tagName === 'BODY') {
selectedElement = selectedElement.firstElementChild
}
} else {
selectedElement = selectedElement.firstElementChild ? selectedElement.firstElementChild : selectedElement // 缩小
}
$('.h2m-selection-box').removeClass('h2m-selection-box')
$(selectedElement).addClass('h2m-selection-box')
}
}).on('keydown', function (e) { // 键盘事件
if (isSelecting) {
e.preventDefault()
if (e.key === 'Escape') {
endSelecting()
return
}
switch (e.key) { // 方向键:上下左右
case 'ArrowUp':
selectedElement = selectedElement.parentElement ? selectedElement.parentElement : selectedElement // 扩大
if (selectedElement.tagName === 'HTML' || selectedElement.tagName === 'BODY') { // 排除HTML 和 BODY
selectedElement = selectedElement.firstElementChild
}
break
case 'ArrowDown':
selectedElement = selectedElement.firstElementChild ? selectedElement.firstElementChild : selectedElement // 缩小
break
case 'ArrowLeft': // 寻找上一个元素,若是最后一个子元素则选择父元素的下一个兄弟元素,直到找到一个元素
var prev = selectedElement.previousElementSibling
while (prev === null && selectedElement.parentElement !== null) {
selectedElement = selectedElement.parentElement
prev = selectedElement.previousElementSibling ? selectedElement.previousElementSibling.lastChild : null
}
if (prev !== null) {
if (selectedElement.tagName === 'HTML' || selectedElement.tagName === 'BODY') {
selectedElement = selectedElement.firstElementChild
}
selectedElement = prev
}
break
case 'ArrowRight':
var next = selectedElement.nextElementSibling
while (next === null && selectedElement.parentElement !== null) {
selectedElement = selectedElement.parentElement
next = selectedElement.nextElementSibling ? selectedElement.nextElementSibling.firstElementChild : null
}
if (next !== null) {
if (selectedElement.tagName === 'HTML' || selectedElement.tagName === 'BODY') {
selectedElement = selectedElement.firstElementChild
}
selectedElement = next
}
break
}
$('.h2m-selection-box').removeClass('h2m-selection-box')
$(selectedElement).addClass('h2m-selection-box') // 更新选中元素的样式
}
}
).on('mousedown', function (e) { // 鼠标事件,选择 mousedown 是因为防止点击元素后触发其他事件
if (isSelecting) {
e.preventDefault()
var markdown = convertToMarkdown(selectedElement)
showMarkdownModal(markdown)
endSelecting()
}
})
function createIssue(token, owner, repo, title, body, labels = []) {
const url = `https://api.github.com/repos/${owner}/${repo}/issues`
const issueData = {
title: title,
body: body,
labels: labels
}
const xhr = new XMLHttpRequest()
xhr.open('POST', url, true)
xhr.setRequestHeader('Authorization', `token ${token}`)
xhr.setRequestHeader('Accept', 'application/vnd.github+json')
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
const response = JSON.parse(xhr.responseText)
showDialog(translate.github_success, response.html_url)
} else {
alert(`${translate.github_failed}\n ${xhr.status}\n ${xhr.statusText}\n ${xhr.responseText}`)
console.error(`${translate.github_failed}`, xhr.status, xhr.statusText, xhr.responseText)
}
}
}
xhr.send(JSON.stringify(issueData))
}
})()