Compare commits

..

4 commits
0.1.7 ... main

Author SHA1 Message Date
jiakun
835cc883b7 description 2025-04-25 14:18:21 +08:00
jiakun
626d7566ce guidance 2025-04-11 16:36:43 +08:00
jiakun
2648051185 version 2025-03-25 10:07:14 +08:00
jiakun
3b64b53e58 filename bugfix 2025-03-25 10:03:10 +08:00
7 changed files with 239 additions and 89 deletions

160
common.ts Normal file
View file

@ -0,0 +1,160 @@
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:'🎉 已保存到:',
guidanceButton: '使用说明',
guidanceDesc: '点击打开使用说明页面',
openGuidance: '打开说明'
}
}
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:',
guidanceButton: 'Guidance',
guidanceDesc: 'Click to open guidance page',
openGuidance: 'Open Guidance'
}
}
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
---`

63
main.ts
View file

@ -1,11 +1,5 @@
import {
App,
Notice,
Plugin,
PluginSettingTab,
Setting,
addIcon,
} from "obsidian";
import { texts, upsertFile, Welcome } from "common";
import { App, Plugin, PluginSettingTab, Setting, addIcon } from "obsidian";
import { server } from "server";
interface ClipperMasterPluginSettings {
@ -32,21 +26,13 @@ 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.addRibbonIcon("clippermaster", "Greet", () => {
new Notice("Hello, world!");
});
server.start(this.app);
}
onUserEnable() {
upsertFile(this.app, texts.welcome, Welcome);
}
onunload() {
server.close();
}
@ -64,33 +50,31 @@ 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;
containerEl.empty();
containerEl.createEl("h2", {
text: `${this.plugin.manifest.name} ${this.plugin.manifest.version}`,
});
// Port setting
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,18 +85,23 @@ class SampleSettingTab extends PluginSettingTab {
invalidPortElement.hide();
this.plugin.settings.port = numValue;
await this.saveAndReload();
});
});
// Guidance button setting
new Setting(containerEl)
.setName(texts.guidanceButton)
.setDesc(texts.guidanceDesc)
.addButton((btn) => {
btn.setButtonText(texts.openGuidance)
.onClick(() => {
window.open("https://docs.clippermaster.com/docs/integrations/obsidian", "_blank");
});
});
}
async saveAndReload() {
await this.plugin.saveSettings();
await server.close()
// const serverIsRunning = !!this.plugin.serverController?.isRunning();
// if (serverIsRunning) {
// await this.plugin.stopServer();
// await this.plugin.startServer();
// }
await server.close();
server.start(this.plugin.app);
}
}

View file

@ -1,11 +1,10 @@
{
"id": "clippermaster",
"name": "ClipperMaster",
"version": "0.1.7",
"version": "0.1.10",
"minAppVersion": "0.15.0",
"description": "ClipperMaster is a plugin that allows you to clip content from the web and save it to your vault.",
"description": "Works with the ClipperMaster Chrome extension to clip structured content from the web and save it to your vault as Markdown files.",
"author": "ClipperMaster Team",
"authorUrl": "https://clippermaster.com/",
"fundingUrl": "https://clippermaster.com/",
"isDesktopOnly": true
}
}

62
package-lock.json generated
View file

@ -1,15 +1,16 @@
{
"name": "obsidian-sample-plugin",
"version": "1.0.0",
"name": "clippermaster-plugin-for-obsidian",
"version": "0.1.10",
"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": {

View file

@ -1,6 +1,6 @@
{
"name": "clippermaster-plugin-for-obsidian",
"version": "0.1.7",
"version": "0.1.10",
"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"
}
}

View file

@ -1,4 +1,5 @@
import { App } from "obsidian";
import { folderName, texts, upsertFile } from "common";
import { App, Notice } from "obsidian";
export type ClipperAttribute = Pick<Attribute, "_id" | "key" | 'valueSource'> & { 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)
}
}
}

View file

@ -1,3 +1,6 @@
{
"1.0.0": "0.15.0"
}
"1.0.0": "0.15.0",
"0.1.9": "0.15.0",
"0.1.8": "0.15.0",
"0.1.10": "0.15.0"
}