├── types.d.ts ├── logo.gif ├── .gitignore ├── tsconfig.json ├── manifest.json ├── .eslintrc.json ├── .github └── workflows │ └── release.yml ├── package.json ├── LICENSE ├── README.en-US.md ├── README.md └── src ├── settings-tab.ts └── main.ts /types.d.ts: -------------------------------------------------------------------------------- 1 | // Empty declaration to allow for css imports 2 | declare module "*.css" {} 3 | -------------------------------------------------------------------------------- /logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkzzhizhou/obsidian-chevereto-image-uploader/HEAD/logo.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | *-error.log 4 | package-lock.json 5 | yarn.lock 6 | .npmrc 7 | dist/ -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "module": "commonjs", 5 | "declaration": false, 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "moduleResolution": "node", 9 | "forceConsistentCasingInFileNames": true 10 | }, 11 | "include": ["src/**/*.ts", "types.d.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-chevereto-image-uploader", 3 | "name": "Obsidian Chevereto Image Uploader", 4 | "description": "This plugin uploads the image in your clipboard to chevereto automatically when pasting.", 5 | "author": "kkzzhizhou", 6 | "isDesktopOnly": true, 7 | "minAppVersion": "0.11.0", 8 | "version": "1.0.1" 9 | } 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "parser": "@typescript-eslint/parser", 11 | "parserOptions": { 12 | "ecmaVersion": 12, 13 | "sourceType": "module" 14 | }, 15 | "plugins": [ 16 | "@typescript-eslint" 17 | ], 18 | "rules": { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | create: 4 | tags: 5 | - v* 6 | 7 | jobs: 8 | release: 9 | name: Release on GitHub 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: 12 16 | - run: yarn 17 | - run: yarn build 18 | - uses: ncipollo/release-action@v1 19 | with: 20 | artifacts: "dist/*" 21 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-chevereto-image-uploader", 3 | "version": "1.0.1", 4 | "description": "This plugin uploads images in your clipboard to chevereto.", 5 | "main": "lib/main.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "obsidian-plugin build src/main.ts", 9 | "dev": "obsidian-plugin dev src/main.ts" 10 | }, 11 | "devDependencies": { 12 | "@typescript-eslint/eslint-plugin": "^4.28.3", 13 | "@typescript-eslint/parser": "^4.28.3", 14 | "eslint": "^7.30.0", 15 | "obsidian": "obsidianmd/obsidian-api", 16 | "obsidian-plugin-cli": "^0.4.3", 17 | "typescript": "^4.1.5" 18 | }, 19 | "dependencies": { 20 | "axios": "^0.21.1", 21 | "compressorjs": "^1.0.7", 22 | "fs": "^0.0.1-security", 23 | "node-fetch": "^3.2.0", 24 | "object-path": "^0.11.5", 25 | "require": "^2.4.20" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.en-US.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | Obsidian Chevereto Image Uploader 6 |

7 |

8 | GitHub 9 | GitHub 10 | GitHub Repo stars 11 | GitHub user 12 |

13 | 14 | 15 | ## Internationalization 16 | 17 | [简体中文](README.md) | [English](README.en-US.md) 18 | 19 | # Obsidian Chevereto Image Uploader 20 | 21 | This plugin could resize(optional) and upload the image in your clipboard to chevereto when pasting. 22 | 23 | ## Getting started 24 | 25 | ### Settings 26 | 27 | 1. Api Endpoint: the Endpoint of the image hosting api.example:https://your_domain/api/1/upload 28 | 2. Chevereto API Token: the API token of the chevereto. open url: https://your_domian/dashboard/settings/api to obtain 29 | 3. Enable Resize: whether resizing images before uploading. 30 | 4. Max Width: images that wider than this will be resized resized by the natural aspect ratio. example:4096 31 | 32 | ## Thanks 33 | 34 | 1. [gavvvr/obsidian-imgur-plugin](https://github.com/gavvvr/obsidian-imgur-plugin) 35 | 2. [create-obsidian-plugin](https://www.npmjs.com/package/create-obsidian-plugin) 36 | 3. [Creling/obsidian-image-uploader](https://github.com/Creling/obsidian-image-uploader) 37 | 4. [jordanhandy/obsidian-cloudinary-uploader](https://github.com/jordanhandy/obsidian-cloudinary-uploader) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | Obsidian Chevereto Image Uploader 6 |

7 |

8 | GitHub 9 | GitHub 10 | GitHub Repo stars 11 | GitHub user 12 |

13 | 14 | 15 | ## 国际化 16 | 17 | [简体中文](README.md) | [English](README.en-US.md) 18 | 19 | 20 | ## 介绍 21 | 22 | 能够将剪贴板的图片在粘贴到Obsidian时上传至chevereto,并支持上传时调整图像大小。 23 | 24 | ## 新手入门 25 | 26 | ### 设置 27 | 28 | 1. Api Endpoint: Chevereto API链接, 示例:https://your_domain/api/1/upload 29 | 2. Chevereto API Token: Chevereto API令牌, 获取方法:https://your_domian/dashboard/settings/api 30 | 3. Enable Resize: 是否开启上传前大小调整 31 | 4. Max Width: 调整最大宽度,示例:4096 32 | 33 | ### FAQ 34 | 35 | 按Ctrl+Shift+i打开Obsidian开发者控制台时,上传时提示CORS跨域,解决方法,在nginx反代时开启CORS支持 36 | 37 | ```nginx 38 | ...(省略) 39 | location / { 40 | add_header Access-Control-Allow-Origin *; 41 | add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; 42 | add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; 43 | ...(省略) 44 | ``` 45 | 46 | ## 特别感谢 47 | 48 | 1. [gavvvr/obsidian-imgur-plugin](https://github.com/gavvvr/obsidian-imgur-plugin) 49 | 2. [create-obsidian-plugin](https://www.npmjs.com/package/create-obsidian-plugin) 50 | 3. [Creling/obsidian-image-uploader](https://github.com/Creling/obsidian-image-uploader) 51 | 4. [jordanhandy/obsidian-cloudinary-uploader](https://github.com/jordanhandy/obsidian-cloudinary-uploader) -------------------------------------------------------------------------------- /src/settings-tab.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Creling 3 | * @Date: 2021-07-15 23:54:03 4 | * @LastEditors: zzz 5 | * @LastEditTime: 2021-11-09 22:35:28 6 | * @Description: file content 7 | */ 8 | import { 9 | App, 10 | PluginSettingTab, 11 | Setting, 12 | } from 'obsidian'; 13 | 14 | import ImageUploader from './main' 15 | 16 | export default class ImageUploaderSettingTab extends PluginSettingTab { 17 | plugin: ImageUploader; 18 | constructor(app: App, plugin: ImageUploader) { 19 | super(app, plugin); 20 | this.plugin = plugin; 21 | } 22 | display(): void { 23 | const { containerEl } = this; 24 | 25 | containerEl.empty(); 26 | containerEl.createEl("h3", { text: "Chevereto Setting" }); 27 | 28 | new Setting(containerEl) 29 | .setName("Api Endpoint") 30 | .setDesc("The endpoint of the image hosting api.") 31 | .addText((text) => { 32 | text 33 | .setPlaceholder("") 34 | .setValue(this.plugin.settings.apiEndpoint) 35 | .onChange(async (value) => { 36 | this.plugin.settings.apiEndpoint = value; 37 | await this.plugin.saveSettings(); 38 | }) 39 | } 40 | ); 41 | 42 | new Setting(containerEl) 43 | .setName("Chevereto API Token") 44 | .setDesc("the API token of the chevereto.") 45 | .addTextArea((text) => { 46 | text 47 | .setPlaceholder("") 48 | .setValue(this.plugin.settings.token) 49 | .onChange(async (value) => { 50 | try { 51 | this.plugin.settings.token = value; 52 | await this.plugin.saveSettings(); 53 | } 54 | catch (e) { 55 | console.log(e) 56 | } 57 | }) 58 | }); 59 | 60 | new Setting(containerEl) 61 | .setName("Enable Resize") 62 | .setDesc("Resize the image before uploading") 63 | .addToggle((toggle) => { 64 | toggle 65 | .setValue(this.plugin.settings.enableResize) 66 | .onChange(async (value) => { 67 | this.plugin.settings.enableResize = value; 68 | this.display(); 69 | }) 70 | }) 71 | 72 | if (this.plugin.settings.enableResize) { 73 | new Setting(containerEl) 74 | .setName("Max Width") 75 | .setDesc("The image wider than this will be resized by the natural aspect ratio") 76 | .addText((text) => { 77 | text 78 | .setPlaceholder("") 79 | .setValue(this.plugin.settings.maxWidth.toString()) 80 | .onChange(async (value) => { 81 | this.plugin.settings.maxWidth = parseInt(value); 82 | await this.plugin.saveSettings(); 83 | }) 84 | }); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Notice, 3 | Plugin, 4 | Editor, 5 | } from "obsidian"; 6 | 7 | import axios from "axios" 8 | import objectPath from 'object-path' 9 | import Compressor from 'compressorjs' 10 | 11 | import ImageUploaderSettingTab from './settings-tab' 12 | 13 | interface ImageUploaderSettings { 14 | apiEndpoint: string; 15 | token: string; 16 | maxWidth: number; 17 | enableResize: boolean; 18 | } 19 | 20 | const DEFAULT_SETTINGS: ImageUploaderSettings = { 21 | apiEndpoint: null, 22 | token: null, 23 | maxWidth: 4096, 24 | enableResize: false, 25 | }; 26 | 27 | 28 | async function readFileAsDataURL(file) { 29 | let result_base64 = await new Promise((resolve) => { 30 | let fileReader = new FileReader(); 31 | fileReader.onload = (e) => resolve(fileReader.result); 32 | fileReader.readAsDataURL(file); 33 | }); 34 | 35 | return result_base64; 36 | } 37 | 38 | export default class ImageUploader extends Plugin { 39 | settings: ImageUploaderSettings; 40 | 41 | setupPasteHandler(): void { 42 | if (!this.settings.apiEndpoint || !this.settings.token) { 43 | new Notice("Chevereto Image Uploader: Please check the chevereto settings."); 44 | return 45 | } 46 | this.registerEvent(this.app.workspace.on('editor-paste', async (evt: ClipboardEvent, editor: Editor) => { 47 | const { files } = evt.clipboardData; 48 | console.log(files) 49 | console.log(files.length) 50 | if (files.length != 0 && files[0].type.startsWith("image")) { 51 | evt.preventDefault(); 52 | for (let file of files) { 53 | if (file.type.startsWith("image")) { 54 | const randomString = (Math.random() * 10086).toString(36).substr(0, 8) 55 | const pastePlaceText = `![uploading...](${randomString})\n` 56 | editor.replaceSelection(pastePlaceText) 57 | const maxWidth = this.settings.maxWidth 58 | if (this.settings.enableResize) { 59 | const compressedFile = await new Promise((resolve, reject) => { 60 | new Compressor(file, { 61 | maxWidth: maxWidth, 62 | success: resolve, 63 | error: reject, 64 | }) 65 | }) 66 | file = compressedFile as File 67 | } 68 | const params = new URLSearchParams(); 69 | params.append('key', this.settings.token); 70 | let dataURL = await readFileAsDataURL(file) 71 | const source = JSON.stringify(dataURL).split(',')[1].split('"')[0] 72 | params.append('source', source) 73 | axios.post(this.settings.apiEndpoint, params) 74 | .then(res => { 75 | const url = objectPath.get(res.data, 'image.url') 76 | const imgMarkdownText = `![](${url})` 77 | this.replaceText(editor, pastePlaceText, imgMarkdownText) 78 | }, err => { 79 | new Notice(err, 5000) 80 | console.log(err) 81 | }) 82 | } 83 | 84 | } 85 | } 86 | })) 87 | } 88 | 89 | 90 | // Function to replace text 91 | private replaceText(editor: Editor, target: string, replacement: string): void { 92 | target = target.trim() 93 | for (let i = 0; i < editor.lineCount(); i++) { 94 | const ch = editor.getLine(i).indexOf(target) 95 | if (ch !== -1) { 96 | const from = { line: i, ch }; 97 | const to = { line: i, ch: ch + target.length }; 98 | editor.replaceRange(replacement, from, to); 99 | break; 100 | } 101 | } 102 | } 103 | 104 | async onload(): Promise { 105 | // console.log("loading Image Uploader"); 106 | await this.loadSettings(); 107 | this.setupPasteHandler() 108 | this.addSettingTab(new ImageUploaderSettingTab(this.app, this)); 109 | } 110 | 111 | // onunload(): void { 112 | // console.log("unloading Image Uploader"); 113 | // } 114 | 115 | async loadSettings(): Promise { 116 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 117 | } 118 | 119 | async saveSettings(): Promise { 120 | await this.saveData(this.settings); 121 | } 122 | } 123 | --------------------------------------------------------------------------------