diff --git a/common.ts b/common.ts new file mode 100644 index 0000000..4534884 --- /dev/null +++ b/common.ts @@ -0,0 +1,154 @@ +import { App, getLanguage } from "obsidian" +export const folderName = 'ClipperMaster' +export const upsertFile = async (app: App, fileName: string, content: string) => { + const folder = await app.vault.getFolderByPath(folderName) + if (!folder) { + await app.vault.createFolder(folderName) + } + const filePath = `${folderName}/${fileName}.md` + + const file = await app.vault.getFileByPath(filePath) + if (file) { + await app.vault.modify(file, content) + } else { + await app.vault.create(filePath, content) + } +} +const language = getLanguage() +export const isZh = language.startsWith('zh') + +const getTexts = () => { + if (isZh) { + return { + listeningPort: '监听端口号', + desc: 'HTTP请求的端口号', + enterPort: '输入端口号', + error: '必须是一个有效的端口号 (1 - 65535)', + welcome: '欢迎使用 ClipperMaster for Obsidian', + fileSaved:'🎉 已保存到:' + } + } + return { + listeningPort: 'Listening Port', + desc: 'Port to listen for HTTP requests', + enterPort: 'Enter Port Number', + error: 'Must be a valid port number (1 - 65535)', + welcome: 'Welcome to ClipperMaster for Obsidian', + fileSaved: '🎉 File saved to:' + } +} +export const texts = getTexts() + + +export const Welcome = isZh ? `# ClipperMaster for Obsidian + +[ClipperMaster](https://clippermaster.com) 是一款强大的 Chrome 浏览器扩展,能让你轻松从网页中提取结构化数据,构建你的专属信息数据库。 +![](https://clippermaster.com/clippermaster-intro.png) + + +# ClipperMaster Obsidian 插件使用指南 + +**让网页内容一键成为你的知识资产** + +ClipperMaster Obsidian 插件是 **ClipperMaster Chrome 插件** 的完美搭档,帮助你将从网页中提取的结构化信息无缝发送并存储到 Obsidian,打造自动化知识管理流水线。 + +--- + +## **插件作用** +- **一键存储**:将从网页提取的文本、图片等内容,转换为 Markdown 格式并保存到 Obsidian。 +- **结构化存储**:支持模板化存储、让内容井井有条。 + +--- + +## **安装步骤** + +### **1. 安装 ClipperMaster Chrome 插件** +如果你还没有安装 Chrome 插件,请先前往 [Chrome 商店](https://chromewebstore.google.com/detail/clippermaster/bdilooeboaoifijdnpomfoinefbokjlj) 安装。 + +### **2. 安装 ClipperMaster Obsidian 插件** +1. 打开 Obsidian,进入 **设置** → **社区插件**。 +2. 点击 **浏览**,搜索 **ClipperMaster**。 +3. 找到插件后,点击 **安装**,然后 **启用**。 +4. 在插件设置中,获取或者修改 **Port**。 + +### **3. 配置 Chrome 插件** +1. 打开 ClipperMaster Chrome 插件,进入 **Options** → **自动化** → **添加自动化** → 发送到**Obsidian** 。 +2. 输入 Obsidian 端设置的 **Port**。 + +--- + +## **常见问题** + +### **1. 插件支持哪些网页内容?** +- 文本、链接、图片等均可提取并存储。 + +### **2. 是否需要付费?** +- **发送到Obsidian**这项功能完全免费 + +### **3. 如何获取技术支持?** +- 访问 [ClipperMaster 官网](https://clippermaster.com) 或者 [给我们发送邮件](mailto:tony@clippermaster.com)。 + +--- + +## **立即体验** +点击下方链接,开始你的知识管理革命: +- [安装 ClipperMaster Chrome 插件](https://chromewebstore.google.com/detail/clippermaster/bdilooeboaoifijdnpomfoinefbokjlj) +- 安装 ClipperMaster Obsidian 插件 + +--- +` : `# ClipperMaster for Obsidian + +[ClipperMaster](https://clippermaster.com) is a powerful Chrome extension that empowers you to effortlessly extract structured data from web pages, building your personalized information database. + +![](https://clippermaster.com/clippermaster-intro.png) + + +# ClipperMaster Obsidian Plugin User Guide + +**Turn Web Content into Your Knowledge Assets with One Click** + +The ClipperMaster Obsidian plugin is the perfect companion to the **ClipperMaster Chrome plugin**, helping you seamlessly send and store structured information extracted from web pages to Obsidian, creating an automated knowledge management pipeline. + +--- + +## **Plugin Functionality** +- **One-Click Storage**: Convert text, images, and other content extracted from web pages into Markdown format and save them to Obsidian. +- **Structured Storage**: Supports templated storage, keeping your content organized. + +--- + +## **Installation Steps** + +### **1. Install the ClipperMaster Chrome Plugin** +If not already installed, get the Chrome plugin from the [Chrome Web Store](https://chromewebstore.google.com/detail/clippermaster/bdilooeboaoifijdnpomfoinefbokjlj). + +### **2. Install the ClipperMaster Obsidian Plugin** +1. Open Obsidian, go to **Settings** → **Community plugins**. +2. Click **Browse**, search for **ClipperMaster**. +3. After finding the plugin, click **Install**, then **Enable**. +4. In the plugin settings, get or modify the **Port**. + +### **3. Configure the Chrome Plugin** +1. Open the ClipperMaster Chrome plugin, go to **Options** → **Automations** → **Add Automation** → Send to **Obsidian**. +2. Enter the **Port** set in Obsidian. + +--- + +## **Frequently Asked Questions** + +### **1. What web content does the plugin support?** +- Text, links, images, etc., can be extracted and stored. + +### **2. Is there a fee?** +- The **Send to Obsidian** feature is completely free. + +### **3. How do I get technical support?** +- Visit the [ClipperMaster Official Website](https://clippermaster.com) or [send us an email](mailto:tony@clippermaster.com). + +--- + +## **Experience It Now** +Click the links below to start your knowledge management revolution: +- [Install ClipperMaster Chrome Plugin](https://chromewebstore.google.com/detail/clippermaster/bdilooeboaoifijdnpomfoinefbokjlj) +- Install ClipperMaster Obsidian Plugin +---` \ No newline at end of file diff --git a/main.ts b/main.ts index 8788f71..2bd5957 100644 --- a/main.ts +++ b/main.ts @@ -1,6 +1,6 @@ +import { texts, upsertFile, Welcome } from "common"; import { App, - Notice, Plugin, PluginSettingTab, Setting, @@ -16,6 +16,7 @@ const DEFAULT_SETTINGS: ClipperMasterPluginSettings = { port: 8282, }; + export default class ClipperMasterPlugin extends Plugin { settings: ClipperMasterPluginSettings; @@ -32,18 +33,17 @@ export default class ClipperMasterPlugin extends Plugin { await this.loadSettings(); // This adds a settings tab so the user can configure various aspects of the plugin - this.addSettingTab(new SampleSettingTab(this.app, this)); + this.addSettingTab(new ClipperMasterSettingTab(this.app, this)); // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin) // Using this function will automatically remove the event listener when this plugin is disabled. this.registerDomEvent(document, "click", (evt: MouseEvent) => { console.log("click", evt); }); + this.onUserEnable = () => { + upsertFile(this.app, texts.welcome, Welcome) + } - - this.addRibbonIcon("clippermaster", "Greet", () => { - new Notice("Hello, world!"); - }); server.start(this.app); } @@ -64,14 +64,14 @@ export default class ClipperMasterPlugin extends Plugin { } } -class SampleSettingTab extends PluginSettingTab { +class ClipperMasterSettingTab extends PluginSettingTab { plugin: ClipperMasterPlugin; constructor(app: App, plugin: ClipperMasterPlugin) { super(app, plugin); this.plugin = plugin; - } + } display(): void { const { containerEl } = this; @@ -80,17 +80,17 @@ class SampleSettingTab extends PluginSettingTab { text: `${this.plugin.manifest.name} ${this.plugin.manifest.version}`, }); const portSetting = new Setting(containerEl) - .setName("Listening Port") - .setDesc("Port to listen for HTTP requests") + .setName(texts.listeningPort) + .setDesc(texts.desc) const invalidPortElement = portSetting.infoEl.createDiv(); invalidPortElement.hide(); invalidPortElement .createSpan("settings-error-element") - .setText("Must be a valid port number (1 - 65535)"); + .setText(texts.error); portSetting.addText((text) => { - text.setPlaceholder("Enter Port Number") + text.setPlaceholder(texts.enterPort) text.setValue(String(this.plugin.settings.port)); text.onChange(async (value) => { const numValue = Number(value); @@ -101,7 +101,6 @@ class SampleSettingTab extends PluginSettingTab { invalidPortElement.hide(); this.plugin.settings.port = numValue; await this.saveAndReload(); - }); }); } diff --git a/manifest.json b/manifest.json index da419d2..51689bf 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "clippermaster", "name": "ClipperMaster", - "version": "0.1.7", + "version": "0.1.9", "minAppVersion": "0.15.0", "description": "ClipperMaster is a plugin that allows you to clip content from the web and save it to your vault.", "author": "ClipperMaster Team", diff --git a/package-lock.json b/package-lock.json index 26bca4c..d870f04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { - "name": "obsidian-sample-plugin", - "version": "1.0.0", + "name": "clippermaster-plugin-for-obsidian", + "version": "0.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "obsidian-sample-plugin", - "version": "1.0.0", + "name": "clippermaster-plugin-for-obsidian", + "version": "0.1.8", "license": "MIT", "dependencies": { - "fastify": "^4.28.0" + "fastify": "^4.28.0", + "obsidian": "^1.8.7" }, "devDependencies": { "@types/node": "^16.11.6", @@ -17,26 +18,26 @@ "@typescript-eslint/parser": "5.29.0", "builtin-modules": "3.3.0", "esbuild": "0.17.3", - "obsidian": "latest", "tslib": "2.4.0", "typescript": "^5.8.2" } }, "node_modules/@codemirror/state": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", - "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", - "dev": true, - "peer": true - }, - "node_modules/@codemirror/view": { - "version": "6.28.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.1.tgz", - "integrity": "sha512-BUWr+zCJpMkA/u69HlJmR+YkV4yPpM81HeMkOMZuwFa8iM5uJdEPKAs1icIRZKkKmy0Ub1x9/G3PQLTXdpBxrQ==", - "dev": true, + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", "peer": true, "dependencies": { - "@codemirror/state": "^6.4.0", + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.36.4", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.4.tgz", + "integrity": "sha512-ZQ0V5ovw/miKEXTvjgzRyjnrk9TwriUB1k4R5p7uNnHR9Hus+D1SXHGdJshijEzPFjU25xea/7nhIeSqYFKdbA==", + "peer": true, + "dependencies": { + "@codemirror/state": "^6.5.0", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } @@ -542,6 +543,12 @@ "dev": true, "peer": true }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "peer": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -581,16 +588,14 @@ "version": "5.60.8", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", - "dev": true, "dependencies": { "@types/tern": "*" } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -608,7 +613,6 @@ "version": "0.23.9", "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", - "dev": true, "dependencies": { "@types/estree": "*" } @@ -2069,7 +2073,6 @@ "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "dev": true, "engines": { "node": "*" } @@ -2088,10 +2091,9 @@ "peer": true }, "node_modules/obsidian": { - "version": "1.5.7-1", - "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.5.7-1.tgz", - "integrity": "sha512-T5ZRuQ1FnfXqEoakTTHVDYvzUEEoT8zSPnQCW31PVgYwG4D4tZCQfKHN2hTz1ifnCe8upvwa6mBTAP2WUA5Vng==", - "dev": true, + "version": "1.8.7", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.8.7.tgz", + "integrity": "sha512-h4bWwNFAGRXlMlMAzdEiIM2ppTGlrh7uGOJS6w4gClrsjc+ei/3YAtU2VdFUlCiPuTHpY4aBpFJJW75S1Tl/JA==", "dependencies": { "@types/codemirror": "5.60.8", "moment": "2.29.4" @@ -2593,7 +2595,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", - "dev": true, "peer": true }, "node_modules/supports-color": { @@ -2722,7 +2723,6 @@ "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "dev": true, "peer": true }, "node_modules/which": { diff --git a/package.json b/package.json index 9d984a1..8078f6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clippermaster-plugin-for-obsidian", - "version": "0.1.7", + "version": "0.1.8", "description": "This is a ClipperMaster Plugin for Obsidian (https://clippermaster.com)", "main": "main.js", "scripts": { @@ -17,11 +17,11 @@ "@typescript-eslint/parser": "5.29.0", "builtin-modules": "3.3.0", "esbuild": "0.17.3", - "obsidian": "latest", "tslib": "2.4.0", "typescript": "^5.8.2" }, "dependencies": { - "fastify": "^4.28.0" + "fastify": "^4.28.0", + "obsidian": "^1.8.7" } } diff --git a/server/service.ts b/server/service.ts index 98658d1..655ceea 100644 --- a/server/service.ts +++ b/server/service.ts @@ -1,4 +1,5 @@ -import { App } from "obsidian"; +import { folderName, texts, upsertFile } from "common"; +import { App, Notice } from "obsidian"; export type ClipperAttribute = Pick & { value: unknown }; @@ -131,17 +132,15 @@ export class Service { constructor(private app: App) {} async upsert(clipper: ClipperDoc) { this.app.vault.getFolderByPath - const folder = await this.app.vault.getFolderByPath('test') - if (!folder) { - await this.app.vault.createFolder('test') - } - const title = clipper.attributes?.find(item => item._id === PresetAttributeID.Title)?.value as string - const filePath = `test/${title || `Untitled_${clipper._id}`}.md` - const file = await this.app.vault.getFileByPath(filePath) - if (file) { - await this.app.vault.modify(file, generateMDContent(clipper)) - } else { - await this.app.vault.create(`test/${title || `Untitled_${clipper._id}`}.md`, generateMDContent(clipper)) + let title = clipper.attributes?.find(item => item._id === PresetAttributeID.Title)?.value as string + title = title ? title.replace(/[\\/:]/g, '_') : title + const fileName = `${title || `Untitled_${clipper._id}`}` + try { + await upsertFile(this.app, fileName, generateMDContent(clipper)) + + new Notice(`${texts.fileSaved} ${folderName}/${fileName}.md`, 5000) + } catch (error) { + new Notice('ops, save file failed', 5000) } } } \ No newline at end of file