├── .gitattributes
├── .gitconfig
├── .gitignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── CHANGELOG.md
├── LICENSE
├── README.ja.md
├── README.md
├── README.zh-cn.md
├── README.zh-tw.md
├── asserts
├── crlf.js
├── icon.png
├── linux.sh
├── mac.applescript
├── pc.ps1
├── rtf.ps1
├── rtf.sh
└── sample.js
├── package-lock.json
├── package.json
├── package.nls.json
├── package.nls.zh-cn.json
├── package.nls.zh-tw.json
├── pnpm-lock.yaml
├── src
├── extension.ts
├── i18n
│ ├── index.ts
│ ├── locale.ts
│ ├── locale.zh-cn.ts
│ └── locale.zh-tw.ts
├── lib
│ ├── azure.ts
│ ├── cloudflare.ts
│ ├── cloudinary.ts
│ ├── coding.ts
│ ├── dataurl.ts
│ ├── define.ts
│ ├── github.d.ts
│ ├── github.ts
│ ├── imgur.ts
│ ├── local.ts
│ ├── qiniu.ts
│ ├── s3.ts
│ ├── sftp.ts
│ ├── sm.ms.ts
│ ├── upload.d.ts
│ ├── uploads.ts
│ ├── upyun.ts
│ └── utils.ts
└── test
│ ├── extension.test.ts
│ └── index.ts
├── tsconfig.json
├── tslint.json
└── typings.d.ts
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set default behavior to automatically normalize line endings.
2 | * text=auto
3 |
4 |
--------------------------------------------------------------------------------
/.gitconfig:
--------------------------------------------------------------------------------
1 | [core]
2 | autocrlf = false
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | node_modules
3 | .vscode-test/
4 | *.vsix
5 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "runtimeExecutable": "${execPath}",
13 | "args": [
14 | "--extensionDevelopmentPath=${workspaceFolder}"
15 | ],
16 | "outFiles": [
17 | "${workspaceFolder}/out/**/*.js"
18 | ],
19 | "preLaunchTask": "npm: watch"
20 | },
21 | {
22 | "name": "Extension Tests",
23 | "type": "extensionHost",
24 | "request": "launch",
25 | "runtimeExecutable": "${execPath}",
26 | "args": [
27 | "--extensionDevelopmentPath=${workspaceFolder}",
28 | "--extensionTestsPath=${workspaceFolder}/out/test"
29 | ],
30 | "outFiles": [
31 | "${workspaceFolder}/out/test/**/*.js"
32 | ],
33 | "preLaunchTask": "npm: watch"
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off",
11 | "GitCommitPlugin.ShowEmoji": true,
12 | "editor.tabSize": 4,
13 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": "$tsc-watch",
10 | "isBackground": true,
11 | "presentation": {
12 | "reveal": "never"
13 | },
14 | "group": {
15 | "kind": "build",
16 | "isDefault": true
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/test/**
4 | src/**
5 | .gitignore
6 | vsc-extension-quickstart.md
7 | **/tsconfig.json
8 | **/tslint.json
9 | **/*.map
10 | **/*.ts
11 | asserts/crlf.js
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to the "markdown-image" extension will be documented in this file.
3 |
4 | ## [1.1.49] - 2025-02-13
5 |
6 | ### Fixed
7 | - Fixed bug of format code by DIY.
8 |
9 | ## [1.1.48] - 2025-02-11
10 |
11 | ### Fixed
12 | - Fixed transparent background turning black when copy png Image on paste.
13 |
14 | ## [1.1.47] - 2025-02-08
15 |
16 | ### Fixed
17 | - Fixed multiple files could not be copied and pasted on Mac.
18 | - Fixed pasted rich text on Windows had garbled characters.
19 |
20 | ## [1.1.46] - 2025-02-07
21 |
22 | ### Fixed
23 | - Reverted changes from version `1.1.45` to fix the issue where the background of a copied PNG image turns black when pasted.
24 |
25 | ## [1.1.45] - 2025-02-07
26 |
27 | ### Fixed
28 | - Fixed transparent background turning black when copy png Image on paste.
29 |
30 | ### Updated
31 | - Support auto activate the extension when the markdown file is opened.
32 |
33 | ## [1.1.44] - 2025-02-06
34 |
35 | ### Fixed
36 | - Fixed the issue where disabling Encode caused addresses with spaces to fail to preview correctly.
37 |
38 | ### Added
39 | - Added a new setting `markdown-image.base.codeFormat` to support setting the image code format.
40 |
41 | ### Updated
42 | - Updated the setting `markdown-image.base.codeType` to add the `DIY` option, supporting custom image code formats.
43 | - Updated the setting `markdown-image.base.altFormat` to add support for the `${prompt}` variable.
44 |
45 | ## [1.1.43] - 2024-07-08
46 |
47 | ### Fixed
48 | - Fixed the issue where the `path` variable still had incomplete backslashes replaced.
49 |
50 | ## [1.1.42] - 2024-07-01
51 |
52 | ### Fixed
53 | - Fixed the issue where the `path` variable is not in Unix format when running at Microsoft Windows.
54 |
55 | ## [1.1.41] - 2024-03-14
56 |
57 | ### Added
58 | - Added a new setting item `markdown-image.s3.config` to config s3 client.
59 |
60 | ### Fixed
61 | - Fixed the s3 cdn url generate wrong.
62 |
63 | ## [1.1.40] - 2024-03-11
64 |
65 | ### Added
66 | - Added a new setting item `markdown-image.s3.cdn` to set the s3 cdn url.
67 |
68 | ## [1.1.39] - 2024-03-06
69 |
70 | ### Added
71 | - Add `Content-Type` to s3 upload method.
72 |
73 | ## [1.1.38] - 2024-02-24
74 |
75 | ### Added
76 | - Supports Azure Blob Storage Passwordless authentication method.
77 |
78 | ## [1.1.37] - 2024-02-17
79 |
80 | ### Added
81 | - Add support for Azure Storage.
82 |
83 | ## [1.1.36] - 2023-08-22
84 |
85 | ### Fixed
86 | - Fixed the issue where the placeholder `filename` was not using the value calculated by `fileNameFormat`.
87 |
88 | ## [1.1.35] - 2023-06-30
89 |
90 | ### Fixed
91 | - Fixed the issue where only the first occurrence of the same variable for `fileNameFormat` was effective.
92 |
93 | ## [1.1.34] - 2023-06-28
94 |
95 | ### Added
96 | - Added a new setting item `markdown-image.base.altFormat` to set the placeholder for image code.
97 |
98 | ## [1.1.33] - 2023-06-04
99 |
100 | ### Added
101 | - Added a new setting item `markdown-image.base.uploadMethods` to support concurrent uploads with multiple upload methods.
102 | - Added a new setting item `markdown-image.base.fileFormat` to set the file format for saving clipboard images.
103 | - Supported pasting images into unconventional editing windows.
104 |
105 | ### Deprecated
106 | - Removed the deprecated Coding image upload method.
107 |
108 | ## [1.1.30] - 2023-05-15
109 |
110 | ### Added
111 | - Added new setting `markdown-image.github.httpProxy` to settting GitHub http proxy.
112 |
113 | ## [1.1.29] - 2023-05-02
114 |
115 | ### Fixed
116 | - Fix failed to get image from clipboard on deepin.
117 |
118 | ## [1.1.28] - 2023-04-10
119 |
120 | ### Added
121 | - Support group setting.
122 | - Add support for remote mode.
123 | - Add SFTP upload method.
124 | - Includes the following new settings:
125 | - `markdown-image.sftp.host`
126 | - `markdown-image.sftp.port`
127 | - `markdown-image.sftp.username`
128 | - `markdown-image.sftp.password`
129 | - `markdown-image.sftp.privateKeyPath`
130 | - `markdown-image.sftp.path`
131 | - `markdown-image.sftp.referencePath`
132 |
133 | ## [1.1.27] - 2023-04-07
134 |
135 | ### Added
136 | - Add support for S3 compatible providers.
137 | - Includes the following new settings:
138 | - `markdown-image.s3.endpoint`
139 | - `markdown-image.s3.region`
140 | - `markdown-image.s3.bucketName`
141 | - `markdown-image.s3.accessKeyId`
142 | - `markdown-image.s3.secretAccessKey`
143 |
144 | ## [1.1.26] - 2022-12-09
145 | ### Added
146 | - Add support for Upyun.
147 | - Includes the following new settings:
148 | * `markdown-image.upyun.bucket`
149 | * `markdown-image.upyun.domain`
150 | * `markdown-image.upyun.operator`
151 | * `markdown-image.upyun.password`
152 | * `markdown-image.upyun.path`
153 | * `markdown-image.upyun.link`
154 |
155 | ## [1.1.25] - 2022-12-09
156 | ### Fixed
157 | - Fixed image url of GitHub CDN encode wrong when enable `markdown-image.base.urlEncode` .
158 |
159 | ## [1.1.24] - 2022-12-06
160 | ### Fixed
161 | - Fixed some bug of the option `markdown-image.local.referencePath`.
162 | - Fixed the `alt` count of image restart when restarted extension.
163 |
164 | ## [1.1.23] - 2022-11-30
165 | ### Added
166 | - Added new setting `markdown-image.github.cdn` to settting GitHub CDN Format Address.
167 |
168 | ## [1.1.22] - 2022-05-10
169 | ### Fixed
170 | - Fixed upload wrong path in GitHub Mode.
171 |
172 | ## [1.1.21] - 2022-05-09
173 | ### Fixed
174 | - Fixed the problem of uploading to GitHub when the file path contains Chinese.
175 |
176 | ## [1.1.20] - 2022-04-08
177 | - Fixed bug of the setting start with `/` on `local.referencePath`.
178 |
179 | ## [1.1.19] - 2022-02-23
180 | ### Fixed
181 | - Fixed local mode always save to disk root path.
182 |
183 | ## [1.1.18] - 2022-02-22
184 | ### Fixed
185 | - Fixed local mode can't use absolute path.
186 |
187 | ## [1.1.17] - 2022-01-03
188 | ### Added
189 | - Add support for Cloudflare Images.
190 | - Includes the following new settings:
191 | * `markdown-image.cloudflare.accountId`
192 | * `markdown-image.cloudflare.apiToken`
193 |
194 | ## [1.1.16] - 2021-12-06
195 | ### Added
196 | - Added to support upload image to github repository.
197 |
198 | ## [1.1.15] - 2021-11-24
199 | ### Added
200 | - Added file format variable `prompt`. Makes it possible to enter a custom name through an input prompt when pasting the image.
201 |
202 | ## [1.1.14] - 2021-09-06
203 | ### Updated
204 | - Update Coding Picbed Package to fixed upload to coding error.
205 |
206 | ## [1.1.13] - 2021-08-20
207 | ### Added
208 | - Added new setting `markdown-image.local.referencePath` to support DIY reference path in markdown file.
209 |
210 | ## [1.1.12] - 2021-08-04
211 | ### Added
212 | - Added to support paste image in Jupyter file.
213 |
214 | ## [1.1.11] - 2021-07-08
215 | ### Fixed
216 | - Updated Cloudinary CDN to use the `markdown-image.base.fileNameFormat` setting. The extension will check for existing files and will prompt to overwrite if necessary.
217 |
218 | ## [1.1.10] - 2021-07-05
219 | ### Added
220 | - Added support for Cloudinary CDN
221 | - Includes the following new settings:
222 | * `markdown-image.cloudinary.cloudName`
223 | * `markdown-image.cloudinary.apiKey`
224 | * `markdown-image.cloudinary.apiSecret`
225 | * `markdown-image.cloudinary.folder`
226 |
227 | ## [1.1.9] - 2021-05-17
228 | ### Added
229 | - Added setting options `markdown-image.base.codeType` and `markdown-image.base.imageWidth` use to set the maximum image width.
230 |
231 | ## [1.1.8] - 2021-04-25
232 | ### Fixed
233 | - Fixed vscode caches the `DIY` path code, causing a question that cannot be changed immediately.
234 |
235 | ## [1.1.7] - 2021-04-16
236 | ### Added
237 | - Added a option to switch whether url encode.
238 |
239 | ## [1.1.6] - 2021-04-13
240 | ### Fixed
241 | - Fixed extension log level.
242 | ### Changed
243 | - Update action after replace file.
244 |
245 | ## [1.1.5] - 2021-03-29
246 | ### Fixed
247 | - Fixed `Data URL` Setting Description.
248 |
249 | ## [1.1.4] - 2021-03-26
250 | ### Added
251 | - Added upload method `Data URL`.
252 | ### Fixed
253 | - Fixed paste multiple documents invalid.
254 |
255 | ## [1.1.3] - 2021-03-26
256 | ### Fixed
257 | - Fixed the filename format variable `${path}` were uploaded to the `Coding` failure in the Windows.
258 |
259 | ## [1.1.2] - 2021-03-26
260 | ### Fixed
261 | - Fixed the path contains Chinese prompts cannot be found when pasting the copied picture again. 😂
262 |
263 | ## [1.1.1] - 2021-03-16
264 | ### Fixed
265 | - Fixed the path contains Chinese prompts cannot be found when pasting the copied picture.
266 |
267 | ## [1.1.0] - 2021-02-04
268 | ### Added
269 | - Added Beta feature, support paste rich text. (Only supports Windows)
270 |
271 | ## [1.0.14] - 2021-01-29
272 | ### Fixed
273 | - Fixed incompatibility with Windows 7.
274 |
275 | ## [1.0.13] - 2021-01-29
276 | ### Fixed
277 | - Fixed getting the path resolution error of the clipboard picture in Windows.
278 |
279 | ## [1.0.12] - 2021-01-21
280 | ### Added
281 | - Added file name variable `${path}`: "The path of the Markdown file being edited relative to the root directory."
282 |
283 | ## [1.0.11] - 2021-01-06
284 | ### Fixed
285 | - Fixed api url of `sm.ms`.
286 | - Fixed the file name with spaces cannot be found in Linux.
287 | ### Added
288 | - Added prompt without token in `sm.ms`.
289 |
290 | ## [1.0.10] - 2020-12-06
291 | ### Fixed
292 | - Fix the variable `$mdname` does not remove the extension name of `md`.
293 |
294 | ## [1.0.9] - 2020-10-19
295 | ### Fixed
296 | - Fixed the problem of getting wrong date and hour in filename formatting.
297 |
298 | ## [1.0.8] - 2020-07-22
299 | ### Added
300 | - Added mdx support.
301 |
302 | ## [1.0.7] - 2020-07-21
303 | ### Fixed
304 | - Fixed launch extension home page failed at MacOS and Linux.
305 | - Fixed the colon of the picture address is incorrectly encode.
306 |
307 | ## [1.0.6] - 2020-07-16
308 | ### Fixed
309 | - Fixed the date variable has not consider the time zone.
310 |
311 | ## [1.0.5] - 2020-07-01
312 | ### Fixed
313 | - Fixed file name include space could not preview.
314 | - Fixed random variable not work.
315 |
316 | ## [1.0.4] - 2020-05-18
317 | ### Updated
318 | - Update sponsored links.
319 |
320 | ## [1.0.3] - 2020-05-15
321 | ### Added
322 | - Add sponsored links.
323 |
324 | ## [1.0.2] - 2020-05-09
325 | ### Fixed
326 | - Fixed replace notice bug.
327 |
328 | ## [1.0.1] - 2020-05-09
329 | ### Fixed
330 | - Fixed Readme.
331 |
332 | ## [1.0.0] - 2020-05-09
333 | - Finish First Version.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Hancel Lin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.ja.md:
--------------------------------------------------------------------------------
1 | [English Readme](README.md) / [简体中文说明](README.zh-cn.md) / [繁體中文說明](README.zh-tw.md) / 日本語説明
2 |
3 | # Markdown Image
4 |
5 | Markdownで画像を簡単に挿入できる拡張機能です。ローカルまたはサードパーティのCDNサービスに画像を保存することをサポートしています。
6 |
7 | ❤ [Sponsor me](https://www.paypal.me/imlinhanchao) / [赞助开发者](http://sponsor.hancel.org)
8 |
9 | ## Features
10 |
11 | 1. 画像ファイルのコピーやスクリーンショットのペーストが可能。ショートカットキーは `Alt + Shift + V`、または右クリックメニューの「Paste Image」。
12 | 2. 自動的にMarkdownコードを生成して挿入します。
13 | 3. `Imgur`、`Qiniu`、`SM.MS`、`Cloudflare`、`Cloudinary`、`S3`、`Azure Storage`などのCDNサービスをサポートし、設定可能です。デフォルトはローカルで、Markdownファイルがあるフォルダを開く必要があります。
14 | 4. 独自コードを設定して画像をアップロードすることも可能です。
15 | 5. Windows、MacOS、Linuxをサポートしています。
16 |
17 | ## Requirements
18 |
19 | Linux環境では xclip をインストールする必要があります。
20 |
21 | Ubuntu:
22 |
23 | ```bash
24 | sudo apt install xclip
25 | ```
26 |
27 | CentOS:
28 |
29 | ```bash
30 | sudo yum install epel-release.noarch
31 | sudo yum install xclip
32 | ```
33 |
34 | ## Notice
35 |
36 | Remote Modeで使用する場合は、次のように `remote.extensionKind` を設定してください:
37 |
38 | ```json
39 | "remote.extensionKind": {
40 | "hancel.markdown-image": [
41 | "ui"
42 | ]
43 | }
44 | ```
45 |
46 | また、リモート環境の作業スペースに画像を保存したい場合は、`SFTP` のアップロード方式を使用する必要があります。`Local` はRemote Modeでは使用できません。
47 |
48 | ## Extension Settings
49 |
50 | ### Base Settings
51 |
52 | * `markdown-image.base.uploadMethod`: 画像のアップロード方法を指定します。ローカルまたは他の画像CDNサービスへアップロードします。
53 | * `markdown-image.base.uploadMethods`: 複数のアップロード方式を並行して使うための設定です。ここに設定したアップロード結果はMarkdownファイルに挿入されません。
54 | * `markdown-image.base.fileNameFormat`: アップロード時のファイル名形式。`Imgur` と `SM.MS` ではサポートされていません。利用できる変数については設定項目を参照してください。
55 | * `markdown-image.base.codeType`: 画像コードの形式。`
` タグまたはMarkdown記法を設定できます。
56 | * `markdown-image.base.codeFormat`: `markdown-image.base.codeType` を `DIY` に設定した場合の挿入コードの書式。
57 | * `markdown-image.base.imageWidth`: 画像の最大幅。画像がこの幅より大きい場合は、この値に合わせられます。0に設定するとサイズ変更を行いません。
58 | * `markdown-image.base.urlEncode`: 画像URLをURLエンコードするかどうかを設定します。
59 |
60 | ### Local Settings
61 |
62 | * `markdown-image.local.path`: ローカルに保存する画像ディレクトリ(存在しない場合は自動で作成)。
63 | * `markdown-image.local.referencePath`: Markdown内での参照パス形式(ファイル名は含まない)。空の場合は相対パスを使用します。ここで `#markdown-image.base.fileNameFormat#` の変数が使用可能です。例: `/images/${YY}-${MM}/`
64 |
65 | ### GitHub Settings
66 |
67 | * `markdown-image.github.path`: リポジトリ内に画像をアップロードするディレクトリ(存在しない場合は自動で作成)。リポジトリは事前に初期化しておく必要があります。
68 | * `markdown-image.github.token`: GitHubの個人[アクセストークン](https://github.com/settings/tokens)。
69 | * `markdown-image.github.repository`: GitHubリポジトリ。例: `https://github.com/username/repository`
70 | * `markdown-image.github.branch`: 画像を保存するGitHubリポジトリのブランチ。
71 | * `markdown-image.github.cdn`: 使用するGitHub CDNアドレスの形式。`${username}` は `markdown-image.github.repository` のユーザー名、`${repository}` はリポジトリ名、`${branch}` は `markdown-image.github.branch` の値、`${filepath}` はリポジトリ内のアップロードパスを表します。
72 | * `markdown-image.github.httpProxy`: HTTPプロキシを介してGitHubに接続する場合の設定。
73 |
74 | ### Imgur Settings
75 |
76 | * `markdown-image.imgur.clientId`: Imgurで登録したClient ID。[こちら](https://api.imgur.com/oauth2/addclient)で登録可能です。
77 | * `markdown-image.imgur.httpProxy`: HTTPプロキシを介してImgurに接続する場合の設定。
78 |
79 | ### SM.MS Settings
80 |
81 | * `markdown-image.sm_ms.token`: SM.MSのAPIトークン(オプション)。アカウント登録後、[API Token](https://sm.ms/home/apitoken)ページで秘密トークンを生成可能です。
82 |
83 | ### Qiniu Settings
84 |
85 | * `markdown-image.qiniu.accessKey`: アカウントのAccess Key。
86 | * `markdown-image.qiniu.secretKey`: アカウントのSecret Key。
87 | * `markdown-image.qiniu.bucket`: ストレージ名。
88 | * `markdown-image.qiniu.domain`: ストレージのバインドドメイン。
89 | * `markdown-image.qiniu.zone`: ストレージのゾーン。
90 |
91 | ### Upyun Settings
92 |
93 | * `markdown-image.upyun.bucket`: アップロードするストレージ名。
94 | * `markdown-image.upyun.domain`: ストレージ名にバインドされたドメイン。
95 | * `markdown-image.upyun.operator`: Upyunのオペレーター名。
96 | * `markdown-image.upyun.password`: Upyunオペレーターのパスワード。
97 | * `markdown-image.upyun.path`: 画像を保存するパス。
98 | * `markdown-image.upyun.link`: Upyunに接続するリンク。
99 |
100 | ### Cloudinary Settings
101 |
102 | これらの値はCloudinaryのダッシュボードから確認できます
103 |
104 | * `markdown-image.cloudinary.cloudName`: アカウント名。
105 | * `markdown-image.cloudinary.apiKey`: アカウントのAPIキー。
106 | * `markdown-image.cloudinary.apiSecret`: アカウントのAPIシークレット。
107 | * `markdown-image.cloudinary.folder`: 画像をアップロードするフォルダ。
108 |
109 | ### Cloudflare Settings
110 |
111 | これらの値はCloudflareのダッシュボードで確認できます
112 |
113 | * `markdown-image.cloudflare.accountId`: アカウントID。
114 | * `markdown-image.cloudflare.apiToken`: Cloudflare ImagesのAPIトークン。
115 |
116 | ### S3 Settings
117 |
118 | これらの値はS3サービスプロバイダのダッシュボードで確認できます
119 |
120 | * `markdown-image.s3.endpoint`: S3 APIのエンドポイント。バケット設定から取得可能です。
121 | * `markdown-image.s3.region`: S3バケットのリージョン。バケット設定から取得可能です。
122 | * `markdown-image.s3.bucketName`: 画像をアップロードするS3バケット名。バケットへのアクセスはパブリックである必要があります。
123 | * `markdown-image.s3.accessKeyId`: S3 APIのAccess Key ID。
124 | * `markdown-image.s3.secretAccessKey`: S3のシークレットアクセスキー。
125 | * `markdown-image.s3.cdn`: S3のCDNアドレス。`${bucket}` `${region}` `${pathname}` `${filepath}`などの変数を使用できます。例: `https://${bucket}.${region}.s3.amazonaws.com/${pathname}/${filepath}`
126 |
127 | ### SFTP Settings
128 |
129 | * `markdown-image.sftp.host`: リモートサーバーのホスト名。
130 | * `markdown-image.sftp.port`: リモートサーバーのSSHポート。
131 | * `markdown-image.sftp.username`: リモートサーバーのユーザー名。
132 | * `markdown-image.sftp.password`: リモートサーバーのパスワード。
133 | * `markdown-image.sftp.privateKeyPath`: リモートサーバーにアクセスするための秘密鍵パス。
134 | * `markdown-image.sftp.path`: リモート環境に保存する画像ディレクトリ(存在しない場合は自動で作成)。注意: ここでは変数は使用できません。`#markdown-image.base.fileNameFormat#` の変数はここでは使えません。
135 | * `markdown-image.sftp.referencePath`: Markdown内での参照パス形式(ファイル名は含まない)。空の場合は相対パスを使用。`#markdown-image.base.fileNameFormat#` の変数が使用可能です。例: `/images/${YY}-${MM}/`
136 |
137 | ### Azure Storage Settings
138 |
139 | * `markdown-image.azure.authenticationMethod`: Azure Blob Storageアカウントに対する認証方法を指定します。デフォルトは `Passwordless` です。[詳細はこちら](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-nodejs?tabs=managed-identity%2Croles-azure-portal%2Csign-in-azure-cli#authenticate-to-azure-and-authorize-access-to-blob-data)。
140 | * `markdown-image.azure.accountName`: Azure Blob Storageアカウント名。
141 | * `markdown-image.azure.connectionString`: Azure Storageアカウントの接続文字列。
142 | * `markdown-image.azure.container`: 画像をアップロードするコンテナー名。
143 |
144 | ### DIY Settings
145 |
146 | * `markdown-image.DIY.path`: 独自に記述するコードのパス。ここには `async function (filePath:string, savePath:string, markdownPath:string):string` をエクスポートする関数を含む必要があります。
147 | 例:
148 | ```javascript
149 | const path = require("path");
150 | module.exports = async function (filePath, savePath, markdownPath) {
151 | // 画像アクセス用のリンクを返す
152 | return path.relative(path.dirname(markdownPath), filePath);
153 | };
154 | ```
155 |
156 | ### Others
157 |
158 | * [GitHub](https://github.com/imlinhanchao/vsc-markdown-image)
159 | * [VSCode Market](https://marketplace.visualstudio.com/items?itemName=hancel.markdown-image)
160 | * アイコン: [www.flaticon.com](https://www.flaticon.com/) の [Good Ware](https://www.flaticon.com/authors/good-ware) より作成
161 |
162 | **Enjoy!**
163 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | English Readme / [简体中文说明](README.zh-cn.md) / [繁體中文說明](README.zh-tw.md) / [日本語説明](README.ja.md)
2 |
3 | # Markdown Image
4 |
5 | An extension for conveniently inserting pictures in Markdown, which supports storing pictures in local or third-party CDN service.
6 |
7 | ❤ [Sponsor me](https://www.paypal.me/imlinhanchao) / [赞助开发者](http://sponsor.hancel.org)
8 |
9 | ## Features
10 |
11 | 1. Copy image files or paste screenshots, Shortcut key `Alt + Shift + V`, or right-click menu `Paste Image`.
12 | 2. Automatically generate Markdown code insertion.
13 | 3. Configurable to support `Imgur`, `Qiniu`, `SM.MS`, `Cloudflare`, `Cloudinary`, `S3`, `Azure Storage` and other CDN service. The default is local, you need to open the folder where the Markdown file is located.
14 | 4. You can also customize the code to upload pictures.
15 | 5. Support Windows, MacOS, Linux.
16 |
17 | ## Requirements
18 |
19 | Linux users must install xclip.
20 |
21 | Ubuntu
22 |
23 | ```bash
24 | sudo apt install xclip
25 | ```
26 |
27 | CentOS
28 |
29 | ```bash
30 | sudo yum install epel-release.noarch
31 | sudo yum install xclip
32 | ```
33 |
34 | ## Notice
35 |
36 | If you want to use in the Remote Mode, please set `remote.extensionKind` like this:
37 |
38 | ```json
39 | "remote.extensionKind": {
40 | "hancel.markdown-image": [
41 | "ui"
42 | ]
43 | }
44 | ```
45 |
46 | And if you want to save image in your remote workplace, you must use `SFTP` upload method. `Local` couldn't use in Remote Mode.
47 |
48 | ## Extension Settings
49 |
50 | ### Base Settings
51 |
52 | - `markdown-image.base.uploadMethod`: Method to upload pictures. To the local or another picture CDN service.
53 | - `markdown-image.base.uploadMethods`: The upload method for parallel upload. The upload results of the upload method set here will not be inserted into the Markdown file.
54 | - `markdown-image.base.fileNameFormat`: The filename format for upload. Not Support in `Imgur` and `SM.MS`. You can use some variables. You can find more in setting.
55 | - `markdown-image.base.codeType`: The type of image code, you can set to `
` tag or markdown
56 | - `markdown-image.base.codeFormat`: Insert code format, effective when `markdown-image.base.codeType` is set to `DIY`.
57 | - `markdown-image.base.imageWidth`: The maximum width of the image, if the image is greater than this width, the width is set to this value. Set to 0 means not change.
58 | - `markdown-image.base.urlEncode`: Whether URL encode for the url of image.
59 |
60 | ### Local Settings
61 |
62 | - `markdown-image.local.path`: Picture storage directory that in the local (automatically created if it does not exist).
63 | - `markdown-image.local.referencePath`: The reference path format in markdown(not include file name). Empty means use relative path. You can use variable of `#markdown-image.base.fileNameFormat#` in here. For example: `/images/${YY}-${MM}/`
64 |
65 | ### GitHub Settings
66 |
67 | - `markdown-image.github.path`: Picture upload directory that in the repository (automatically created if it does not exist). The repository must initialization first.
68 | - `markdown-image.github.token`: GitHub person [access token](https://github.com/settings/tokens).
69 | - `markdown-image.github.repository`: GitHub repository, for example: `https://github.com/username/repository`
70 | - `markdown-image.github.branch`: GitHub repository branch to save.
71 | - `markdown-image.github.cdn`: The github cdn address format to be used, `${username}` is the username of `markdown-image.github.repository`, and `${repository}` is the repository name. `${branch}` is the value of `markdown-image.github.branch`. `${filepath}` is the upload path in repository.
72 | - `markdown-image.github.httpProxy`: Connect to Github via http proxy.
73 |
74 | ### Imgur Settings
75 |
76 | - `markdown-image.imgur.clientId`: The client id registered with imgur. You can registed it at [here](https://api.imgur.com/oauth2/addclient).
77 | - `markdown-image.imgur.httpProxy`: Connect to Imgur via http proxy.
78 |
79 | ### SM.MS Settings
80 |
81 | - `markdown-image.sm_ms.token`: SM.MS API token (Options). You can register an account and then visit [API Token](https://sm.ms/home/apitoken) Page to generate secret token.
82 |
83 | ### Qiniu Settings
84 |
85 | - `markdown-image.qiniu.accessKey`: The Access Key of account.
86 | - `markdown-image.qiniu.secretKey`: The Secret Key of account.
87 | - `markdown-image.qiniu.bucket`: The storage name.
88 | - `markdown-image.qiniu.domain`: Bound domain name of storage.
89 | - `markdown-image.qiniu.zone`: Zone of storage.
90 |
91 | ### Upyun Settings
92 |
93 | - `markdown-image.upyun.bucket`: Storge name of upload.
94 | - `markdown-image.upyun.domain`: Domain bind with storge name.
95 | - `markdown-image.upyun.operator`: Operator of upyun.
96 | - `markdown-image.upyun.password`: Password of upyun operator.
97 | - `markdown-image.upyun.path`: The path that img store.
98 | - `markdown-image.upyun.link`: The link that connect to upyun.
99 |
100 | ### Cloudinary Settings
101 |
102 | These values can be found on your Cloudinary Dashboard
103 |
104 | - `markdown-image.cloudinary.cloudName`: Your user account name.
105 | - `markdown-image.cloudinary.apiKey`: API key for your account.
106 | - `markdown-image.cloudinary.apiSecret`: API secret for your account.
107 | - `markdown-image.cloudinary.folder`: Folder to upload the image to.
108 |
109 | ### Cloudflare Settings
110 |
111 | These values can be found on your Cloudflare dashboard
112 |
113 | - `markdown-image.cloudflare.accountId`: Your account ID.
114 | - `markdown-image.cloudflare.apiToken`: Cloudflare Images API token.
115 |
116 | ### S3 Settings
117 |
118 | These values can be found in your S3 service provider dashboard
119 |
120 | - `markdown-image.s3.endpoint`: The endpoint for S3 API, can be obtained from bucket setting.
121 | - `markdown-image.s3.region`: The region for the S3 bucket, can be obtained from bucket setting.
122 | - `markdown-image.s3.bucketName`: The name of the S3 bucket to upload images to. Access to the bucket should be public.
123 | - `markdown-image.s3.accessKeyId`: Your S3 API access key ID.
124 | - `markdown-image.s3.secretAccessKey`: Your S3 secret access key.
125 | - `markdown-image.s3.cdn`: Your S3 CDN Url. You can use variable `${bucket}` `${region}` `${pathname}` and `${filepath}`. For example: `https://${bucket}.${region}.s3.amazonaws.com/${pathname}/${filepath}`.
126 |
127 | ### SFTP Settings
128 |
129 | - `markdown-image.sftp.host`: The host of the remote server.
130 | - `markdown-image.sftp.port`: The ssh port of the remote server.
131 | - `markdown-image.sftp.username`: The username of the remote server.
132 | - `markdown-image.sftp.password`: The password of the remote server.
133 | - `markdown-image.sftp.privateKeyPath`: The private key path of the remote server.
134 | - `markdown-image.sftp.path`: Picture storage directory that in the remote (automatically created if it does not exist). Notice: You can't use variable in here. You can use variable in `#markdown-image.base.fileNameFormat#`.
135 | - `markdown-image.sftp.referencePath`: The reference path format in markdown(not include file name). Empty means use relative path. You can use variable of `#markdown-image.base.fileNameFormat#` in here. For example: `/images/${YY}-${MM}/`
136 |
137 | ### Azure Storage Settings
138 | - `markdown-image.azure.authenticationMethod`: The authentication method to use for the Azure Blob Storage account. The default is `Passwordless`. You can obtain more information from [here](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-nodejs?tabs=managed-identity%2Croles-azure-portal%2Csign-in-azure-cli#authenticate-to-azure-and-authorize-access-to-blob-data).
139 | - `markdown-image.azure.accountName`: Your Azure Blob Storage account.
140 | - `markdown-image.azure.connectionString`: The connection string of the Azure Storage account.
141 | - `markdown-image.azure.container`: The name of the container to upload images to.
142 |
143 | ### DIY Settings
144 |
145 | - `markdown-image.DIY.path`: The Code Path what you write. Your code must exports a function as `async function (filePath:string, savePath:string, markdownPath:string):string`.
146 | For example:
147 | ```javascript
148 | const path = require("path");
149 | module.exports = async function (filePath, savePath, markdownPath) {
150 | // Return a picture access link
151 | return path.relative(path.dirname(markdownPath), filePath);
152 | };
153 | ```
154 |
155 | ### Others
156 |
157 | - [GitHub](https://github.com/imlinhanchao/vsc-markdown-image)
158 | - [VSCode Market](https://marketplace.visualstudio.com/items?itemName=hancel.markdown-image)
159 | - Icons made by [Good Ware](https://www.flaticon.com/authors/good-ware) from [www.flaticon.com](https://www.flaticon.com/)
160 |
161 | **Enjoy!**
162 |
--------------------------------------------------------------------------------
/README.zh-cn.md:
--------------------------------------------------------------------------------
1 | [English Readme](README.md) / 简体中文说明 / [繁體中文說明](README.zh-tw.md) / [日本語説明](README.ja.md)
2 |
3 | # Markdown Image
4 |
5 | 一个用于方便的在 Markdown 中插入图片的扩展,支持将图片存放在本地或第三方的图床或对象存储。
6 |
7 | ❤ [Sponsor me](https://www.paypal.me/imlinhanchao) / [赞助开发者](http://sponsor.hancel.org)
8 |
9 | ## 功能
10 |
11 | 1. 可复制图片文件或截图粘贴,快捷键 `Alt + Shift + V`,或右键菜单 `粘贴图片`。
12 | 2. 自动生成 Markdown 代码插入。
13 | 3. 可配置支持 `Imgur`,`七牛`,`SM.MS`,`Coding` 等图床。默认为本地,需打开 Markdown 文件所在文件夹。
14 | 4. 也可以自定义代码实现图片上传。
15 | 5. 支援 Windows,MacOS,Linux。
16 |
17 | ## 依赖要求
18 |
19 | Windows 和 MacOS 用户可直接使用,Linux 用户须安装 xclip.
20 |
21 | Ubuntu
22 |
23 | ```bash
24 | sudo apt install xclip
25 | ```
26 |
27 | CentOS
28 |
29 | ```bash
30 | sudo yum install epel-release.noarch
31 | sudo yum install xclip
32 | ```
33 |
34 | ## 注意事项
35 |
36 | 如果你要在 Remote 模式中使用,请设置 `remote.extensionKind` 如下:
37 |
38 | ```json
39 | "remote.extensionKind": {
40 | "hancel.markdown-image": [
41 | "ui"
42 | ]
43 | }
44 | ```
45 |
46 | 而且,如果要将图片保存在远程工作目录中,则必须使用 `SFTP` 上传方法,`Local` 方法无法在 Remote 模式中使用。
47 |
48 | ## 扩展设置项目
49 |
50 | ### 基本设置项目
51 |
52 | - `markdown-image.base.uploadMethod`: 上传图片的方式,根据不同的方式,须在设置不同的项目。
53 | - `markdown-image.base.uploadMethods`: 并行上传的上传方法。此处设置的上传方法的上传结果,将不会被插入到 Markdown 文件中。
54 | - `markdown-image.base.fileNameFormat`: 图片文件命名格式化字符串。支持多种变量做格式化,可同时配置文件夹格式,具体见设置。
55 | - `markdown-image.base.codeType`: 插入代码的类型, 你可以设置为使用 `
` 标签, Markdown或自定义。
56 | - `markdown-image.base.codeFormat`: 插入代码的格式代码,当 `markdown-image.base.codeType` 设置为 `DIY` 时生效。
57 | - `markdown-image.base.imageWidth`: 图片的最大宽度,若图片大于这个宽度,则会设置宽度为该值。设置为 0 则表示不设置。
58 | - `markdown-image.base.urlEncode`: 是否对图像的 URL 编码。
59 |
60 | ### Local 设置项目
61 |
62 | - `markdown-image.local.path`: 图片本地存放路径,支持相对路径,相对于所粘贴的 Markdown 文件。`/` 表示打开的文件夹根目录。若路径不存在,将会自动创建。
63 | - `markdown-image.local.referencePath`: Markdown 中的图片的引用路径格式(不包含文件名)。留空表示使用相对路径。 你可以使用 `#markdown-image.base.fileNameFormat#` 中的所有变量。例如:`/images/${YY}-${MM}/`
64 |
65 | ### GitHub Settings
66 |
67 | - `markdown-image.github.path`: 仓库中的图片保存目录(如果不存在,则自动创建)。
68 | - `markdown-image.github.token`: GitHub 的个人[访问令牌](https://github.com/settings/tokens),用于访问仓库,上传图片。
69 | - `markdown-image.github.repository`: 所要上传的目的仓库,比如:`https://github.com/username/repository/`。仓库必须要先初始化。
70 | - `markdown-image.github.branch`: 要存放图片的仓库分支。
71 | - `markdown-image.github.cdn`: 要使用的 CDN 地址格式,`${username}` 表示上传仓库的用户名,`${repository}` 表示上传的仓库,`${branch}` 表示上传的分支,`${filepath}` 表示上传的仓库目录与文件名。
72 | - `markdown-image.github.httpProxy` : 设置访问Github的 HTTP 代理。
73 |
74 | ### Imgur 设置项目
75 |
76 | - `markdown-image.imgur.clientId`: 在 `imgur` 注册的 `Client Id`。您可以在[这儿](https://api.imgur.com/oauth2/addclient)注册。
77 | - `markdown-image.imgur.httpProxy`: 设置访问的 HTTP 代理。
78 |
79 | ### SM.MS 设置项目
80 |
81 | - `markdown-image.sm_ms.token`: SM.MS Secret Token。您可以注册一个帐户,然后访问 [API Access](https://sm.ms/home/apitoken) 页面生成。
82 |
83 | ### 七牛设置项目
84 |
85 | - `markdown-image.qiniu.accessKey`: 七牛账户的 Access Key。
86 | - `markdown-image.qiniu.secretKey`: 七牛账户的 Secret Key。
87 | - `markdown-image.qiniu.bucket`: 七牛的对象存储空间名。
88 | - `markdown-image.qiniu.domain`: 七牛空间绑定的域名。
89 | - `markdown-image.qiniu.zone`: 七牛空间的存储区域。
90 |
91 | ### 又拍云设置项目
92 |
93 | - `markdown-image.upyun.bucket`: 又拍云的云存储服务名称。
94 | - `markdown-image.upyun.domain`: 又拍云云存储服务绑定的域名。
95 | - `markdown-image.upyun.operator`: 又拍云的操作员名。
96 | - `markdown-image.upyun.password`: 又拍云的操作员密码。
97 | - `markdown-image.upyun.path`: 又拍云图片存储路径。
98 | - `markdown-image.upyun.link`: 又拍云链接线路。
99 |
100 | ### Cloudinary 设置项目
101 |
102 | 这些值可以在 Cloudinary Dashboard 上找到
103 |
104 | - `markdown-image.cloudinary.cloudName`: 你的帐户名称。
105 | - `markdown-image.cloudinary.apiKey`: 你的帐户 API key。
106 | - `markdown-image.cloudinary.apiSecret`: 你的用户帐户 API secret。
107 | - `markdown-image.cloudinary.folder`: 图像上传文件夹。
108 |
109 | ### Cloudflare 设置项目
110 |
111 | 这些值可以在 CloudFlare Dashboard 上找到
112 |
113 | - `markdown-image.cloudflare.accountId`: 你的帐户名称
114 | - `markdown-image.cloudflare.apiToken`: Cloudflare API 令牌。
115 |
116 | ### S3 设置项目
117 |
118 | 这些值可以在 S3 服务 Dashboard 上找到
119 |
120 | - `markdown-image.s3.endpoint`: 你的 S3 API 端点,是从存储桶设置或 dashboard 获得的。
121 | - `markdown-image.s3.region`: 你的 S3 存储桶区域,是从存储桶设置中获得的。
122 | - `markdown-image.s3.bucketName`: 你的 S3 存储桶名称。存储桶访问权限应该是公开的。
123 | - `markdown-image.s3.accessKeyId`: 你的 S3 access key ID。
124 | - `markdown-image.s3.secretAccessKey`: 你的 S3 secret access key。
125 | - `markdown-image.s3.cdn`: 设置你的 S3 CDN URL。你可以使用变量 `${bucket}` `${region}` `${pathname}` and `${filepath}`。比如:`https://${bucket}.${region}.s3.amazonaws.com/${pathname}/${filepath}`。
126 |
127 | ### SFTP 设置项目
128 |
129 | - `markdown-image.sftp.host`: 远程服务器地址。
130 | - `markdown-image.sftp.port`: SSH 服务端口。
131 | - `markdown-image.sftp.username`: 远程用户名。
132 | - `markdown-image.sftp.password`: SSH 密码。
133 | - `markdown-image.sftp.privateKeyPath`: 远程私钥文件路径。
134 | - `markdown-image.sftp.path`: 远程服务器的图片存储目录(如果不存在,则自动创建)。支持相对路径,相对于所粘贴的 Markdown 文件。 `/` 表示打开的文件夹根目录。注意:您不能在此处使用变量。您可以在 `#markdown-image.base.fileNameFormat#` 中使用变量。
135 | - `markdown-image.sftp.referencePath`: Markdown 中的图片的引用路径格式(不包含文件名)。留空表示使用相对路径。 你可以使用 `#markdown-image.base.fileNameFormat#` 中的所有变量。例如:`/images/${YY}-${MM}/`
136 |
137 | ### Azure Storage 设置项目
138 |
139 | - `markdown-image.azure.authenticationMethod`: 用于 Azure Blob 存储帐户的身份验证方法。默认值为 `Passwordless`。您可以从[这里](https://learn.microsoft.com/zh-cn/azure/storage/blobs/storage-quickstart-blobs-nodejs?tabs=managed-identity%2Croles-azure-portal%2Csign-in-azure-cli#authenticate-to-azure-and-authorize-access-to-blob-data)获取更多信息。
140 | - `markdown-image.azure.accountName`: 您的 Azure Blob 存储帐户。
141 | - `markdown-image.azure.connectionString`: 你的 Azure 存储连接字符串。
142 | - `markdown-image.azure.container`: 你的 Azure 存储容器名称。
143 |
144 | ### 自定义设置项目
145 |
146 | - `markdown-image.DIY.path`: 你写的代码的路径。 你的代码必须 exports 一个像 `async function (filePath:string, savePath:string, markdownPath:string):string` 的函数。
147 | 比如:
148 | ```javascript
149 | const path = require('path');
150 | module.exports = async function(filePath, savePath, markdownPath) {
151 | // Return a picture access link
152 | return path.relative(path.dirname(markdownPath), filePath);
153 | }
154 | ```
155 |
156 | ### 其他
157 |
158 | * [GitHub](https://github.com/imlinhanchao/vsc-markdown-image)
159 | * [VSCode Market](https://marketplace.visualstudio.com/items?itemName=hancel.markdown-image)
160 | * 图标来自 [www.flaticon.com](https://www.flaticon.com/) 的 [Good Ware](https://www.flaticon.com/authors/good-ware)
161 |
162 | **Enjoy!**
163 |
--------------------------------------------------------------------------------
/README.zh-tw.md:
--------------------------------------------------------------------------------
1 | [English Readme](README.md) / [简体中文说明](README.zh-cn.md) / 繁體中文說明 / [日本語説明](README.ja.md)
2 |
3 | # Markdown Image
4 |
5 | 一個用於方便的在 Markdown 中插入圖片的擴展,支持將圖片存放在本地或第三方的圖床或對象存儲。
6 |
7 | ❤ [Sponsor me](https://www.paypal.me/imlinhanchao) / [贊助開發者](http://sponsor.hancel.org)
8 |
9 | ## 功能
10 |
11 | 1. 可複製圖片文件或截圖粘貼,快捷键 `Alt + Shift + V`,或右键菜单 `粘贴图片`。
12 | 2. 自動生成 Markdown 代碼插入。
13 | 3. 可配置支持 `Imgur`,`七牛`,`SM.MS`,`Coding` 等圖床。默認為本地,需打開 Markdown 文件所在文件夾。
14 | 4. 也可以自定義代碼實現圖片上傳。
15 | 5. 支援 Windows,MacOS,Linux。
16 |
17 | ## 依赖要求
18 |
19 | Windows 和 MacOS 用戶可直接使用,Linux 用戶須安裝 xclip.
20 |
21 | Ubuntu
22 |
23 | ```bash
24 | sudo apt install xclip
25 | ```
26 |
27 | CentOS
28 |
29 | ```bash
30 | sudo yum install epel-release.noarch
31 | sudo yum install xclip
32 | ```
33 |
34 | ## 注意事項
35 |
36 | 如果你要在 Remote 模式中使用,請設置 `remote.extensionKind` 如下:
37 |
38 | ```json
39 | "remote.extensionKind": {
40 | "hancel.markdown-image": [
41 | "ui"
42 | ]
43 | }
44 | ```
45 |
46 | 而且,如果要將圖像保存在遠程工作目录中,則必須使用 `SFTP` 上傳方法,`Local` 方法無法在 Remote 模式中使用。
47 |
48 | ## 擴展設置項目
49 |
50 | ### 基本設置項目
51 |
52 | - `markdown-image.base.uploadMethod`: 上傳圖片的方式,根據不同的方式,須在設置不同的項目。
53 | - `markdown-image.base.uploadMethods`: 平行上傳的上傳方法。 此處設置的上傳方法的上傳結果,將不會被插入到 Markdown 文件中。
54 | - `markdown-image.base.fileNameFormat`: 圖片文件命名格式化字符串。支持多種變量做格式化,可同時配置文件夾格式,具體見設置。
55 | - `markdown-image.base.codeType`: 插入代碼的類型, 你可以設置為使用 `
` 標籤或 Markdown。
56 | - `markdown-image.base.codeFormat`: 插入代碼的格式代碼,當 `markdown-image.base.codeType` 設置為 `DIY` 時生效。
57 | - `markdown-image.base.imageWidth`: 圖片的最大寬度,若圖片大於這個寬度,則會設置寬度為該值。設置為 0 則表示不設置。
58 | - `markdown-image.base.urlEncode`: 是否對圖像的 URL 編碼。
59 |
60 | ### Local 设置项目
61 |
62 | - `markdown-image.local.path`: 圖片本地存放路徑,支持相對路徑,相對於所粘貼的 Markdown 文件。 `/` 表示打開的文件夾根目錄。若路徑不存在,將會自動創建。
63 | - `markdown-image.local.referencePath`: Markdown 中的圖片的引用路徑格式(不包含文件名)。留空表示使用相對路徑。你可以使用 `#markdown-image.base.fileNameFormat#` 中的所有變量。例如:`/images/${YY}-${MM}/`
64 |
65 | ### GitHub Settings
66 |
67 | - `markdown-image.github.path`: 倉庫中的圖片保存目錄(如果不存在,則自動創建)。
68 | - `markdown-image.github.token`: Git Hub 的個人[訪問令牌](https://github. com/settings/tokens),用於訪問倉庫,上傳圖片。
69 | - `markdown-image.github.repository`: 所要上傳的目的倉庫,比如:`https://github.com/username/repository/`。倉庫必須要先初始化。
70 | - `markdown-image.github.branch`: 要存放圖片的倉庫分支。
71 | - `markdown-image.github.cdn`: 要使用的 CDN 地址格式,`${username}` 表示上傳倉庫的用戶名,`${repository}` 表示上傳的倉庫,`${branch}` 表示上傳的分支,`${filepath}` 表示上傳的倉庫目錄與文件名。
72 | - `markdown-image.github.httpProxy` : 設置訪問Github的 HTTP 代理。
73 |
74 | ### Imgur 設置項目
75 |
76 | - `markdown-image.imgur.clientId`: 在 `imgur` 註冊的 `Client Id`。您可以在[這裡](https://api.imgur.com/oauth2/addclient)註冊。
77 | - `markdown-image.imgur.httpProxy`: 設置訪問的 HTTP 代理。
78 |
79 | ### SM.MS 設置項目
80 |
81 | - `markdown-image.sm_ms.token`: SM.MS Secret Token。您可以註冊一個帳戶,然後訪問 [API Access](https://sm.ms/home/apitoken) 頁面生成。
82 |
83 | ### 七牛設置項目
84 |
85 | - `markdown-image.qiniu.accessKey`: 七牛賬戶的 Access Key。
86 | - `markdown-image.qiniu.secretKey`: 七牛賬戶的 Secret Key。
87 | - `markdown-image.qiniu.bucket`: 七牛的對象存儲空間名。
88 | - `markdown-image.qiniu.domain`: 七牛空间绑定的域名。
89 | - `markdown-image.qiniu.zone`: 七牛空間的存儲區域。
90 |
91 | ### 又拍云设置项目
92 |
93 | - `markdown-image.upyun.bucket`: 又拍雲的雲存儲服務名稱。
94 | - `markdown-image.upyun.domain`: 又拍雲雲存儲服務綁定的域名。
95 | - `markdown-image.upyun.operator`: 又拍雲的操作員名。
96 | - `markdown-image.upyun.password`: 又拍雲的操作員密碼。
97 | - `markdown-image.upyun.path`: 又拍雲圖片存儲路徑。
98 | - `markdown-image.upyun.link`: 又拍雲鏈接線路。
99 |
100 | ### Cloudinary 設置項目
101 |
102 | 這些值可以在 Cloudinary Dashboard 上找到
103 |
104 | - `markdown-image.cloudinary.cloudName`: 你的帳戶名稱。
105 | - `markdown-image.cloudinary.apiKey`: 你的帳戶 API key。
106 | - `markdown-image.cloudinary.apiSecret`: 你的帳戶 API secret。
107 | - `markdown-image.cloudinary.folder`: 圖像上傳文件夾。
108 |
109 | ### Cloudflare 設置項目
110 |
111 | 這些值可以在 CloudFlare Dashboard 上找到
112 |
113 | - `markdown-image.cloudflare.accountId`: 你的帳戶名稱。
114 | - `markdown-image.cloudflare.apiToken`: Cloudflare API 令牌.
115 |
116 | ### S3 設置項目
117 |
118 | 這些值可以在 S3 服務 Dashboard 上找到
119 |
120 | - `markdown-image.s3.endpoint`: 你的 S3 API 端點,是從存儲桶設置或 dashboard 獲得的。
121 | - `markdown-image.s3.region`: 你的 S3 存儲桶區域,是從存儲桶設置中獲得的。
122 | - `markdown-image.s3.bucketName`: 你的 S3 存儲桶名稱。存儲桶訪問權限應該是公開的。
123 | - `markdown-image.s3.accessKeyId`: 你的 S3 access key ID。
124 | - `markdown-image.s3.secretAccessKey`: 你的 S3 secret access key。
125 | - `markdown-image.s3.cdn`: 設定你的 S3 CDN URL。你可以使用變數 `${bucket}` `${region}` `${pathname}` and `${filepath}`。比如:`https://${bucket}.${region}.s3.amazonaws.com/${pathname}/${filepath}`。
126 |
127 | ### SFTP 設置項目
128 |
129 | - `markdown-image.sftp.host`: 遠端伺服器地址。
130 | - `markdown-image.sftp.port`: SSH 服務端口。
131 | - `markdown-image.sftp.username`: 遠端賬戶。
132 | - `markdown-image.sftp.password`: SSH 密碼。
133 | - `markdown-image.sftp.privateKeyPath`: 遠端私鑰文件路徑。
134 | - `markdown-image.sftp.path`: 遠端伺服器的圖片存儲目錄(如果不存在,則自動創建)。支持相對路徑,相對於所粘貼的 Markdown 文件。 `/` 表示打開的文件夾根目錄。注意:您不能在此處使用變量。您可以在 `#markdown-image.base.fileNameFormat#` 中使用變量。
135 | - `markdown-image.sftp.referencePath`: Markdown 中的圖片的引用路徑格式(不包含文件名)。留空表示使用相對路徑。你可以使用 `#markdown-image.base.fileNameFormat#` 中的所有變量。例如:`/images/${YY}-${MM}/`
136 |
137 | ### Azure Storage 設置項目
138 |
139 | - `markdown-image.azure.authenticationMethod`: 用於 Azure Blob 存儲帳戶的身份驗證方法。 默認值為 `Passwordless`。 您可以從[這裡](https://learn.microsoft.com/zh-tw/azure/storage/blobs/storage-quickstart-blobs-nodejs?tabs=managed-identity%2Croles-azure-portal%2Csign-in-azure-cli#authenticate-to-azure-and-authorize-access-to-blob-data)獲取更多信息。
140 | - `markdown-image.azure.accountName`: 你的 Azure Blob 存儲帳戶名。
141 | - `markdown-image.azure.connectionString`: 你的 Azure 存儲連接字符串。
142 | - `markdown-image.azure.container`: 你的 Azure 存儲容器名稱。
143 |
144 | ### 自定義設置項目
145 |
146 | - `markdown-image.DIY.path`: 你寫的代碼的路徑。你的代碼必須 exports 一個像 `async function (filePath:string, savePath:string, markdownPath:string):string` 的函數。
147 | 比如:
148 | ```javascript
149 | const path = require('path');
150 | module.exports = async function(filePath, savePath, markdownPath) {
151 | // Return a picture access link
152 | return path.relative(path.dirname(markdownPath), filePath);
153 | }
154 | ```
155 |
156 | ### 其他
157 |
158 | * [GitHub](https://github.com/imlinhanchao/vsc-markdown-image)
159 | * [VSCode Market](https://marketplace.visualstudio.com/items?itemName=hancel.markdown-image)
160 | * 圖標來自 [www.flaticon.com](https://www.flaticon.com/) 的 [Good Ware](https://www.flaticon.com/authors/good-ware)
161 |
162 | **Enjoy!**
163 |
--------------------------------------------------------------------------------
/asserts/crlf.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | const filePath = path.join(__dirname, 'linux.sh');
5 |
6 | fs.readFile(filePath, 'utf8', (err, data) => {
7 | if (err) {
8 | console.error('Read file failded:', err);
9 | return;
10 | }
11 |
12 | const result = data.replace(/\r\n/g, '\n');
13 |
14 | fs.writeFile(filePath, result, 'utf8', (err) => {
15 | if (err) {
16 | console.error('Write file failed:', err);
17 | return;
18 | }
19 | console.log('linux.sh: CR LF => LF');
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/asserts/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imlinhanchao/vsc-markdown-image/93d7e222f3816e9a17568f2a4145631fa861b686/asserts/icon.png
--------------------------------------------------------------------------------
/asserts/linux.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # require xclip(see http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script/677212#677212)
4 | command -v xclip >/dev/null 2>&1 || {
5 | echo >&1 "no xclip"
6 | exit 1
7 | }
8 |
9 | # write image in clipboard to file (see http://unix.stackexchange.com/questions/145131/copy-image-from-clipboard-to-file)
10 | xclip -selection clipboard -target image/png -o >$1
11 | if [ $? -eq 0 ]; then
12 | echo $1
13 | else
14 | content=$(xclip -selection c -o)
15 | echo $content
16 | fi
17 |
--------------------------------------------------------------------------------
/asserts/mac.applescript:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imlinhanchao/vsc-markdown-image/93d7e222f3816e9a17568f2a4145631fa861b686/asserts/mac.applescript
--------------------------------------------------------------------------------
/asserts/pc.ps1:
--------------------------------------------------------------------------------
1 | param($imagePath)
2 |
3 | # Adapted from https://github.com/octan3/img-clipboard-dump/blob/master/dump-clipboard-png.ps1
4 | chcp 65001 | Out-Null
5 |
6 | Add-Type -Assembly PresentationCore
7 |
8 | $image = [System.Windows.Clipboard]::GetImage()
9 | if (-not $image) {
10 | $fileDropList = [System.Windows.Clipboard]::GetFileDropList()
11 | if (-not $fileDropList) {
12 | Write-Host "no image in clipboard"
13 | Exit 1
14 | }
15 | Write-Host $fileDropList
16 | Exit 0
17 | }
18 |
19 | if (-not $imagePath) {
20 | Write-Host "image path unspecified"
21 | Exit 1
22 | }
23 |
24 | function Is-Bgra32AndMissingAlpha {
25 | param([System.Windows.Media.Imaging.BitmapSource] $source)
26 |
27 | if ($source.Format -ne [System.Windows.Media.PixelFormats]::Bgra32) {
28 | return $false
29 | }
30 |
31 | $stride = $source.PixelWidth * 4
32 | $pixelData = New-Object byte[] ($stride * $source.PixelHeight)
33 |
34 | try {
35 | $source.CopyPixels($pixelData, $stride, 0)
36 | } catch {
37 | return $false
38 | }
39 |
40 | for ($i = 0; $i -lt $pixelData.Length; $i += 4) {
41 | if ($pixelData[$i + 3] -gt 0) {
42 | return $false
43 | }
44 | }
45 |
46 | return $true
47 | }
48 |
49 | $targetFormat = if (-not @(
50 | [System.Windows.Media.PixelFormats]::Bgra32,
51 | [System.Windows.Media.PixelFormats]::Pbgra32,
52 | [System.Windows.Media.PixelFormats]::Rgba64,
53 | [System.Windows.Media.PixelFormats]::Prgba64,
54 | [System.Windows.Media.PixelFormats]::Rgba128Float
55 | ) -contains $image.Format) {
56 | [System.Windows.Media.PixelFormats]::Rgb24
57 | } elseif (Is-Bgra32AndMissingAlpha -source $image) {
58 | [System.Windows.Media.PixelFormats]::Rgb24
59 | } else {
60 | [System.Windows.Media.PixelFormats]::Bgra32
61 | }
62 |
63 | $fcb = New-Object Windows.Media.Imaging.FormatConvertedBitmap($image, $targetFormat, $null, 0)
64 | $stream = [System.IO.File]::Open($imagePath, "Create")
65 | $encoder = New-Object Windows.Media.Imaging.PngBitmapEncoder
66 | $encoder.Frames.Add([System.Windows.Media.Imaging.BitmapFrame]::Create($fcb)) | Out-Null
67 | $encoder.Save($stream) | Out-Null
68 | $stream.Dispose() | Out-Null
69 |
70 | Write-Host $imagePath
71 |
--------------------------------------------------------------------------------
/asserts/rtf.ps1:
--------------------------------------------------------------------------------
1 | Add-Type -Assembly PresentationCore
2 |
3 | ($html = [Windows.Clipboard]::GetData([Windows.DataFormats]::Html)) | out-null
4 |
5 | [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
6 | [Console]::WriteLine($html)
7 |
8 |
--------------------------------------------------------------------------------
/asserts/rtf.sh:
--------------------------------------------------------------------------------
1 | command -v xclip >/dev/null 2>&1 || { echo >&1 "no xclip"; exit 1; }
2 |
3 | xclip -selection clipboard -o -t text/html
4 |
--------------------------------------------------------------------------------
/asserts/sample.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = async function(filePath, savePath, markdownPath) {
4 | // Return a picture access link
5 | return path.relative(path.dirname(markdownPath), filePath);
6 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "markdown-image",
3 | "displayName": "Markdown Image",
4 | "description": "Easy to insert a image to markdown",
5 | "version": "1.1.49",
6 | "publisher": "hancel",
7 | "engines": {
8 | "vscode": "^1.62.0"
9 | },
10 | "keywords": [
11 | "Picture",
12 | "cdn",
13 | "markdown",
14 | "imgur",
15 | "七牛",
16 | "sm.ms",
17 | "helper"
18 | ],
19 | "icon": "asserts/icon.png",
20 | "license": "MIT",
21 | "categories": [
22 | "Other"
23 | ],
24 | "activationEvents": [
25 | "onLanguage:markdown"
26 | ],
27 | "repository": "https://github.com/imlinhanchao/vsc-markdown-image",
28 | "main": "./out/extension",
29 | "contributes": {
30 | "commands": [
31 | {
32 | "command": "markdown-image.paste",
33 | "category": "Markdown Image",
34 | "title": "%markdown-image.command.paste%"
35 | },
36 | {
37 | "command": "markdown-image.config",
38 | "category": "Markdown Image",
39 | "title": "%markdown-image.command.config%"
40 | },
41 | {
42 | "command": "markdown-image.paste-rich-text",
43 | "category": "Markdown Image",
44 | "title": "%markdown-image.command.paste-rich-text%"
45 | }
46 | ],
47 | "keybindings": [
48 | {
49 | "command": "markdown-image.paste",
50 | "key": "alt+shift+v",
51 | "mac": "alt+shift+v",
52 | "when": "editorLangId == markdown || editorLangId == mdx || resourceExtname == .ipynb"
53 | }
54 | ],
55 | "menus": {
56 | "editor/context": [
57 | {
58 | "command": "markdown-image.paste",
59 | "when": "editorLangId == markdown || editorLangId == mdx || resourceExtname == .ipynb",
60 | "group": "9_cutcopypaste@4"
61 | },
62 | {
63 | "command": "markdown-image.paste-rich-text",
64 | "when": "editorLangId == markdown || editorLangId == mdx || resourceExtname == .ipynb",
65 | "group": "9_cutcopypaste@5"
66 | }
67 | ]
68 | },
69 | "configuration": [
70 | {
71 | "properties": {
72 | "markdown-image.base.codeType": {
73 | "default": "Markdown",
74 | "enum": [
75 | "Markdown",
76 | "HTML",
77 | "DIY"
78 | ],
79 | "enumDescriptions": [
80 | "%markdown-image.base.codeType.Markdown%",
81 | "%markdown-image.base.codeType.HTML%",
82 | "%markdown-image.base.codeType.DIY%"
83 | ],
84 | "markdownDescription": "%markdown-image.base.codeType%",
85 | "order": 2,
86 | "type": "string"
87 | },
88 | "markdown-image.base.codeFormat": {
89 | "default": "",
90 | "markdownDescription": "%markdown-image.base.codeFormat%",
91 | "order": 3,
92 | "type": "string"
93 | },
94 | "markdown-image.base.fileNameFormat": {
95 | "default": "${hash}",
96 | "markdownDescription": "%markdown-image.base.fileNameFormat%",
97 | "order": 5,
98 | "pattern": "^[^:*?<>|]+$",
99 | "type": "string"
100 | },
101 | "markdown-image.base.altFormat": {
102 | "default": "%markdown-image.base.altFormat.default%",
103 | "markdownDescription": "%markdown-image.base.altFormat%",
104 | "order": 6,
105 | "type": "string"
106 | },
107 | "markdown-image.base.imageWidth": {
108 | "default": 0,
109 | "markdownDescription": "%markdown-image.base.imageWidth%",
110 | "order": 4,
111 | "type": "number"
112 | },
113 | "markdown-image.base.uploadMethod": {
114 | "default": "Local",
115 | "enum": [
116 | "Local",
117 | "GitHub",
118 | "Imgur",
119 | "SM.MS",
120 | "Data URL",
121 | "%markdown-image.qiniu%",
122 | "%markdown-image.upyun%",
123 | "Cloudinary",
124 | "Cloudflare",
125 | "S3",
126 | "SFTP",
127 | "Azure Storage",
128 | "%markdown-image.DIY%"
129 | ],
130 | "enumDescriptions": [
131 | "%markdown-image.base.uploadMethod.Local%",
132 | "%markdown-image.base.uploadMethod.GitHub%",
133 | "%markdown-image.base.uploadMethod.Imgur%",
134 | "%markdown-image.base.uploadMethod.SM.MS%",
135 | "%markdown-image.base.uploadMethod.DataURL%",
136 | "%markdown-image.base.uploadMethod.Qiniu%",
137 | "%markdown-image.base.uploadMethod.Upyun%",
138 | "%markdown-image.base.uploadMethod.Cloudinary%",
139 | "%markdown-image.base.uploadMethod.Cloudflare%",
140 | "%markdown-image.base.uploadMethod.S3%",
141 | "%markdown-image.base.uploadMethod.SFTP%",
142 | "%markdown-image.base.uploadMethod.Azure%",
143 | "%markdown-image.base.uploadMethod.DIY%"
144 | ],
145 | "markdownDescription": "%markdown-image.base.uploadMethod%",
146 | "order": 0,
147 | "type": "string"
148 | },
149 | "markdown-image.base.uploadMethods": {
150 | "default": [],
151 | "markdownDescription": "%markdown-image.base.uploadMethods%",
152 | "order": 1,
153 | "type": "array",
154 | "items": {
155 | "type": "string",
156 | "enum": [
157 | "Local",
158 | "GitHub",
159 | "Imgur",
160 | "SM.MS",
161 | "Data URL",
162 | "%markdown-image.qiniu%",
163 | "%markdown-image.upyun%",
164 | "Cloudinary",
165 | "Cloudflare",
166 | "S3",
167 | "SFTP",
168 | "Azure Storage",
169 | "%markdown-image.DIY%"
170 | ]
171 | }
172 | },
173 | "markdown-image.base.urlEncode": {
174 | "default": true,
175 | "markdownDescription": "%markdown-image.base.urlEncode%",
176 | "order": 7,
177 | "type": "boolean"
178 | },
179 | "markdown-image.base.fileFormat": {
180 | "default": "png",
181 | "markdownDescription": "%markdown-image.base.fileFormat%",
182 | "enum": [
183 | "png",
184 | "jpg"
185 | ],
186 | "order": 8,
187 | "type": "string"
188 | }
189 | },
190 | "title": "Base"
191 | },
192 | {
193 | "properties": {
194 | "markdown-image.local.path": {
195 | "default": "/images",
196 | "markdownDescription": "%markdown-image.local.path%",
197 | "order": 0,
198 | "type": "string"
199 | },
200 | "markdown-image.local.referencePath": {
201 | "default": "",
202 | "markdownDescription": "%markdown-image.local.referencePath%",
203 | "order": 1,
204 | "type": "string"
205 | }
206 | },
207 | "title": "Local"
208 | },
209 | {
210 | "properties": {
211 | "markdown-image.github.branch": {
212 | "default": "master",
213 | "markdownDescription": "%markdown-image.github.branch%",
214 | "order": 3,
215 | "type": "string"
216 | },
217 | "markdown-image.github.cdn": {
218 | "default": "https://cdn.jsdelivr.net/gh/${username}/${repository}@${branch}/${filepath}",
219 | "markdownDescription": "%markdown-image.github.cdn%",
220 | "order": 4,
221 | "type": "string"
222 | },
223 | "markdown-image.github.path": {
224 | "default": "/",
225 | "markdownDescription": "%markdown-image.github.path%",
226 | "order": 0,
227 | "type": "string"
228 | },
229 | "markdown-image.github.repository": {
230 | "default": "",
231 | "markdownDescription": "%markdown-image.github.repository%",
232 | "order": 2,
233 | "pattern": "^(https://github.com/[^/]*?/[^/]*?/*|)$",
234 | "type": "string"
235 | },
236 | "markdown-image.github.token": {
237 | "default": "",
238 | "markdownDescription": "%markdown-image.github.token%",
239 | "order": 1,
240 | "type": "string"
241 | },
242 | "markdown-image.github.httpProxy": {
243 | "default": "",
244 | "markdownDescription": "%markdown-image.github.httpProxy%",
245 | "order": 5,
246 | "type": "string"
247 | }
248 | },
249 | "title": "GitHub"
250 | },
251 | {
252 | "properties": {
253 | "markdown-image.imgur.clientId": {
254 | "default": "",
255 | "markdownDescription": "%markdown-image.imgur.clientId%",
256 | "order": 0,
257 | "type": "string"
258 | },
259 | "markdown-image.imgur.httpProxy": {
260 | "default": "",
261 | "markdownDescription": "%markdown-image.imgur.httpProxy%",
262 | "order": 1,
263 | "type": "string"
264 | }
265 | },
266 | "title": "Imgur"
267 | },
268 | {
269 | "properties": {
270 | "markdown-image.sm_ms.token": {
271 | "default": "",
272 | "markdownDescription": "%markdown-image.sm_ms.token%",
273 | "order": 0,
274 | "type": "string"
275 | }
276 | },
277 | "title": "SM.MS"
278 | },
279 | {
280 | "properties": {
281 | "markdown-image.qiniu.accessKey": {
282 | "default": "",
283 | "markdownDescription": "%markdown-image.qiniu.accessKey%",
284 | "order": 0,
285 | "type": "string"
286 | },
287 | "markdown-image.qiniu.bucket": {
288 | "default": "",
289 | "markdownDescription": "%markdown-image.qiniu.bucket%",
290 | "order": 2,
291 | "type": "string"
292 | },
293 | "markdown-image.qiniu.domain": {
294 | "default": "",
295 | "markdownDescription": "%markdown-image.qiniu.domain%",
296 | "order": 3,
297 | "pattern": "^(http(s|)://[a-zA-Z0-9][-\\w]{0,62}(.[a-zA-Z0-9][-\\w]{0,62})+.|)?",
298 | "type": "string"
299 | },
300 | "markdown-image.qiniu.secretKey": {
301 | "default": "",
302 | "markdownDescription": "%markdown-image.qiniu.secretKey%",
303 | "order": 1,
304 | "type": "string"
305 | },
306 | "markdown-image.qiniu.zone": {
307 | "default": "East China",
308 | "enum": [
309 | "%markdown-image.qiniu.east%",
310 | "%markdown-image.qiniu.north%",
311 | "%markdown-image.qiniu.south%",
312 | "%markdown-image.qiniu.na%",
313 | "%markdown-image.qiniu.sa%"
314 | ],
315 | "markdownDescription": "%markdown-image.qiniu.zone%",
316 | "order": 4,
317 | "type": "string"
318 | }
319 | },
320 | "title": "Qiniu"
321 | },
322 | {
323 | "properties": {
324 | "markdown-image.upyun.bucket": {
325 | "default": "",
326 | "markdownDescription": "%markdown-image.upyun.bucket%",
327 | "order": 0,
328 | "type": "string"
329 | },
330 | "markdown-image.upyun.domain": {
331 | "default": "",
332 | "markdownDescription": "%markdown-image.upyun.domain%",
333 | "order": 1,
334 | "pattern": "^(http(s|)://[a-zA-Z0-9][-\\w]{0,62}(.[a-zA-Z0-9][-\\w]{0,62})+.|)?",
335 | "type": "string"
336 | },
337 | "markdown-image.upyun.link": {
338 | "default": "%markdown-image.upyun.smart%",
339 | "enum": [
340 | "%markdown-image.upyun.smart%",
341 | "%markdown-image.upyun.telecom%",
342 | "%markdown-image.upyun.unicom%",
343 | "%markdown-image.upyun.mobile%"
344 | ],
345 | "markdownDescription": "%markdown-image.upyun.link%",
346 | "order": 5,
347 | "type": "string"
348 | },
349 | "markdown-image.upyun.operator": {
350 | "default": "",
351 | "markdownDescription": "%markdown-image.upyun.operator%",
352 | "order": 2,
353 | "type": "string"
354 | },
355 | "markdown-image.upyun.password": {
356 | "default": "",
357 | "markdownDescription": "%markdown-image.upyun.password%",
358 | "order": 3,
359 | "type": "string"
360 | },
361 | "markdown-image.upyun.path": {
362 | "default": "",
363 | "markdownDescription": "%markdown-image.upyun.path%",
364 | "order": 4,
365 | "type": "string"
366 | }
367 | },
368 | "title": "Upyun"
369 | },
370 | {
371 | "properties": {
372 | "markdown-image.cloudinary.apiKey": {
373 | "default": "",
374 | "markdownDescription": "%markdown-image.cloudinary.apiKey%",
375 | "order": 1,
376 | "type": "string"
377 | },
378 | "markdown-image.cloudinary.apiSecret": {
379 | "default": "",
380 | "markdownDescription": "%markdown-image.cloudinary.apiSecret%",
381 | "order": 2,
382 | "type": "string"
383 | },
384 | "markdown-image.cloudinary.cloudName": {
385 | "default": "",
386 | "markdownDescription": "%markdown-image.cloudinary.cloudName%",
387 | "order": 0,
388 | "type": "string"
389 | },
390 | "markdown-image.cloudinary.folder": {
391 | "default": "",
392 | "markdownDescription": "%markdown-image.cloudinary.folder%",
393 | "order": 3,
394 | "type": "string"
395 | }
396 | },
397 | "title": "Cloudinary"
398 | },
399 | {
400 | "properties": {
401 | "markdown-image.cloudflare.accountId": {
402 | "default": "",
403 | "markdownDescription": "%markdown-image.cloudflare.accountId%",
404 | "order": 0,
405 | "type": "string"
406 | },
407 | "markdown-image.cloudflare.apiToken": {
408 | "default": "",
409 | "markdownDescription": "%markdown-image.cloudflare.apiToken%",
410 | "order": 1,
411 | "type": "string"
412 | }
413 | },
414 | "title": "Cloudflare"
415 | },
416 | {
417 | "properties": {
418 | "markdown-image.s3.accessKeyId": {
419 | "default": "",
420 | "markdownDescription": "%markdown-image.s3.accessKeyId%",
421 | "order": 3,
422 | "type": "string"
423 | },
424 | "markdown-image.s3.bucketName": {
425 | "default": "",
426 | "markdownDescription": "%markdown-image.s3.bucketName%",
427 | "order": 2,
428 | "type": "string"
429 | },
430 | "markdown-image.s3.endpoint": {
431 | "default": "",
432 | "markdownDescription": "%markdown-image.s3.endpoint%",
433 | "order": 0,
434 | "type": "string"
435 | },
436 | "markdown-image.s3.region": {
437 | "default": "",
438 | "markdownDescription": "%markdown-image.s3.region%",
439 | "order": 1,
440 | "type": "string"
441 | },
442 | "markdown-image.s3.secretAccessKey": {
443 | "default": "",
444 | "markdownDescription": "%markdown-image.s3.secretAccessKey%",
445 | "order": 4,
446 | "type": "string"
447 | },
448 | "markdown-image.s3.cdn": {
449 | "default": "",
450 | "markdownDescription": "%markdown-image.s3.cdn%",
451 | "order": 5,
452 | "type": "string"
453 | },
454 | "markdown-image.s3.config": {
455 | "markdownDescription": "%markdown-image.s3.config%",
456 | "order": 6,
457 | "type": "object"
458 | }
459 | },
460 | "title": "S3"
461 | },
462 | {
463 | "properties": {
464 | "markdown-image.sftp.host": {
465 | "default": "",
466 | "markdownDescription": "%markdown-image.sftp.host%",
467 | "order": 0,
468 | "type": "string"
469 | },
470 | "markdown-image.sftp.port": {
471 | "default": 22,
472 | "markdownDescription": "%markdown-image.sftp.port%",
473 | "order": 1,
474 | "type": "number"
475 | },
476 | "markdown-image.sftp.username": {
477 | "default": "",
478 | "markdownDescription": "%markdown-image.sftp.username%",
479 | "order": 2,
480 | "type": "string"
481 | },
482 | "markdown-image.sftp.password": {
483 | "default": "",
484 | "markdownDescription": "%markdown-image.sftp.password%",
485 | "order": 3,
486 | "type": "string"
487 | },
488 | "markdown-image.sftp.privateKeyPath": {
489 | "default": "",
490 | "markdownDescription": "%markdown-image.sftp.privateKeyPath%",
491 | "order": 4,
492 | "type": "string"
493 | },
494 | "markdown-image.sftp.path": {
495 | "default": "/images",
496 | "markdownDescription": "%markdown-image.sftp.path%",
497 | "order": 5,
498 | "type": "string"
499 | },
500 | "markdown-image.sftp.referencePath": {
501 | "default": "",
502 | "markdownDescription": "%markdown-image.sftp.referencePath%",
503 | "order": 6,
504 | "type": "string"
505 | }
506 | },
507 | "title": "SFTP"
508 | },
509 | {
510 | "properties": {
511 | "markdown-image.azure.authenticationMethod": {
512 | "default": "Passwordless",
513 | "markdownDescription": "%markdown-image.azure.authenticationMethod%",
514 | "enum": [
515 | "Passwordless",
516 | "Connection String"
517 | ],
518 | "enumDescriptions": [
519 | "%markdown-image.azure.authenticationMethod.Passwordless%",
520 | "%markdown-image.azure.authenticationMethod.ConnectionString%"
521 | ],
522 | "order": 0,
523 | "type": "string"
524 | },
525 | "markdown-image.azure.accountName": {
526 | "default": "",
527 | "markdownDescription": "%markdown-image.azure.accountName%",
528 | "order": 1,
529 | "type": "string"
530 | },
531 | "markdown-image.azure.connectionString": {
532 | "default": "",
533 | "markdownDescription": "%markdown-image.azure.connect-string%",
534 | "order": 2,
535 | "type": "string"
536 | },
537 | "markdown-image.azure.container": {
538 | "default": "",
539 | "markdownDescription": "%markdown-image.azure.container%",
540 | "order": 3,
541 | "type": "string"
542 | }
543 | },
544 | "title": "Azure Storage"
545 | },
546 | {
547 | "properties": {
548 | "markdown-image.DIY.path": {
549 | "default": "",
550 | "markdownDescription": "%markdown-image.DIY.path%",
551 | "order": 0,
552 | "type": "string"
553 | }
554 | },
555 | "title": "DIY"
556 | }
557 | ]
558 | },
559 | "scripts": {
560 | "vscode:prepublish": "npm run compile",
561 | "compile": "node asserts/crlf.js && tsc -p ./",
562 | "watch": "tsc -watch -p ./",
563 | "postinstall": "node ./node_modules/vscode/bin/install",
564 | "test": "npm run compile && node ./node_modules/vscode/bin/test"
565 | },
566 | "devDependencies": {
567 | "@types/mime": "^3.0.4",
568 | "@types/mocha": "^2.2.42",
569 | "@types/node": "^8.10.25",
570 | "@types/node-fetch": "^2.5.12",
571 | "@types/turndown": "^5.0.0",
572 | "@types/ssh2-sftp-client": "^9.0.0",
573 | "tslint": "^5.8.0",
574 | "typescript": "4.1",
575 | "vscode": "^1.1.25"
576 | },
577 | "dependencies": {
578 | "@aws-sdk/client-s3": "^3.304.0",
579 | "@aws-sdk/s3-request-presigner": "^3.304.0",
580 | "@azure/identity": "^4.0.1",
581 | "@azure/storage-blob": "^12.17.0",
582 | "cloudinary": "^1.26.1",
583 | "coding-picbed": "^0.0.13",
584 | "form-data": "^3.0.1",
585 | "github-picbed": "^0.0.7",
586 | "got": "^10.7.0",
587 | "https-proxy-agent": "^4.0.0",
588 | "image-size": "^1.0.0",
589 | "mime": "^3.0.0",
590 | "node-fetch": "^2.6.6",
591 | "png-to-jpeg": "^1.0.1",
592 | "qiniu": "^7.3.1",
593 | "ssh2-sftp-client": "^9.0.4",
594 | "turndown": "^7.0.0",
595 | "turndown-plugin-gfm": "^1.0.2",
596 | "upyun": "^3.4.6",
597 | "vscode-nls": "^4.1.2"
598 | }
599 | }
600 |
--------------------------------------------------------------------------------
/package.nls.json:
--------------------------------------------------------------------------------
1 | {
2 | "markdown-image.title": "Markdown Image",
3 | "markdown-image.local": "Local",
4 | "markdown-image.qiniu": "Qiniu",
5 | "markdown-image.upyun": "Upyun",
6 | "markdown-image.DIY": "DIY",
7 | "markdown-image.Cloudinary": "Cloudinary",
8 | "markdown-image.Cloudflare": "Cloudflare",
9 | "markdown-image.command.paste": "Paste Image",
10 | "markdown-image.command.config": "Markdown Image Config Setting",
11 | "markdown-image.command.paste-rich-text": "Paste Rich Text (Beta)",
12 | "markdown-image.base.uploadMethod": "Method to upload pictures. To the local or another picture CDN service.\n\n- Local [Options](#markdown-image.local.path)\n\n- GitHub [Options](#markdown-image.github.branch)\n\n- Imgur [Options](#markdown-image.imgur.clientId)\n\n- SM.MS [Options](#markdown-image.sm_ms.token)\n\n- Qiniu [Options](#markdown-image.qiniu.accessKey)\n\n- Upyun [Options](#markdown-image.upyun.bucket)\n\n- DIY [Options](#markdown-image.DIY.path)\n\n- Cloudflare [Options](#markdown-image.cloudflare.accountId)\n\n- Cloudinary [Options](#markdown-image.cloudinary.apiKey)\n\n- S3 [Options](#markdown-image.s3.endpoint)",
13 | "markdown-image.base.uploadMethod.Local": "Upload the image to the project directory. You can use markdown-image.local.path sets the relative path for upload.",
14 | "markdown-image.base.uploadMethod.Coding": "Upload the image to git repository in Coding.net . You can configure the repository through markdown-image.coding.repository. You need to configure the Token to access the repository through markdown-image.coding.token.",
15 | "markdown-image.base.uploadMethod.GitHub": "Upload the image to git repository in GitHub.com . You can configure the repository through markdown-image.github.repository. You need to configure the Token to access the repository through markdown-image.github.token.",
16 | "markdown-image.base.uploadMethod.Imgur": "Upload the image to Imgur. You can configure `Client Id` through markdown-image.imgur.clientId.",
17 | "markdown-image.base.uploadMethod.SM.MS": "Upload the image to sm.ms. You can configure `Token` through markdown-image.sm_ms.token if you have an account.",
18 | "markdown-image.base.uploadMethod.DataURL": "Turn the picture into the DATA URL insert.",
19 | "markdown-image.base.uploadMethod.Qiniu": "Upload the image to qiniu.com.",
20 | "markdown-image.base.uploadMethod.Upyun": "Upload the image to upyun.com.",
21 | "markdown-image.base.uploadMethod.DIY": "You can define your code used to upload. You need to configure the code path through markdown-image.DIY.path",
22 | "markdown-image.base.uploadMethod.Cloudinary": "Upload the image to Cloudinary. You can configure `Cloud Name` through markdown-image.cloudinary.cloudName.",
23 | "markdown-image.base.uploadMethod.S3": "Upload the image to any S3 API compatible server, for example, AWS S3, MinIO, Backblaze B2, etc.",
24 | "markdown-image.base.uploadMethod.Cloudflare": "Upload the image to Cloudflare Image. You can configure `accountId` through markdown-image.cloudflare.accountId.",
25 | "markdown-image.base.uploadMethod.SFTP": "Upload the image to SFTP server. You can configure the server through markdown-image.sftp.host.",
26 | "markdown-image.base.uploadMethod.Azure": "Upload the image to Azure Blob Storage. You can configure the connection string through markdown-image.azure.connect-string.",
27 | "markdown-image.base.uploadMethods": "Multiple upload services are used for concurrent uploading. The default is empty. The list should contain the selectable values of the `#markdown-image.base.uploadMethod#` option mentioned above. The upload results from the configured upload methods will not be inserted into the Markdown file.",
28 | "markdown-image.base.fileNameFormat": "The filenname and path format string for upload. Not Support in `Imgur` and `SM.MS`. You can use some variables: \n\n- `${filename}`: The original filename. \n- `${mdname}`: The name of the Markdown file being edited. \n- `${path}`: The path of the Markdown file being edited relative to the root directory. \n- `${hash}`: The sha256 hash of image. \n- `${timestamp}`: The timestamp of upload time. \n- `${YY}`: The Year \n- `${MM}`:The Month \n- `${DD}`: The Day \n- `${hh}`: The 12-hour clock \n- `${HH}`: The 24-hour clock \n- `${mm}`: The minutes \n- `${ss}`: The seconds \n- `${mss}`: The milliseconds \n- `${rand,number}`: A random number, for example: `${rand,100}`. It will generate random numbers from 0 to 99 \n- `${prompt}`: Makes it possible to enter a custom name through an input prompt when pasting the image.",
29 | "markdown-image.base.urlEncode": "Whether URL encode for the url of image.",
30 | "markdown-image.base.fileFormat": "The Clipboard Image File Format. You can use `png` or `jpg`.",
31 | "markdown-image.base.codeType": "The type of image code",
32 | "markdown-image.base.codeType.Markdown": "Markdown Code as: ",
33 | "markdown-image.base.codeType.HTML": "HTML Code as:
",
34 | "markdown-image.base.codeType.DIY": "Custom image code, you need to configure the code format through `#markdown-image.base.codeFormat#`.",
35 | "markdown-image.base.codeFormat": "The format of custom image code, need to set `#markdown-image.base.codeType#` to `DIY`. You can use `${src}` and `${alt}` variables, which represent the image address and default placeholder (defined by `#markdown-image.base.altFormat#`). For example: ``. You can also use all variables in `#markdown-image.base.fileNameFormat#`.",
36 | "markdown-image.base.imageWidth": "The maximum width of the image, if the image is greater than this width, the width is set to this value. Set to `0` means not change.",
37 | "markdown-image.base.altFormat": "Placeholder for Markdown code. You have some variables available: \n\n- ${filename}: The original filename of the image. \n- ${mdname}: The name of the Markdown file being edited (without the extension). \n- ${hash}: The sha256 hash of the image. \n- ${timestamp}: The timestamp of the upload time. \n- ${YY}: The year \n- ${MM}: The month \n- ${DD}: The date \n- ${hh}: The hour in 12-hour format \n- ${HH}: The hour in 24-hour format \n- ${mm}: The minute \n- ${ss}: The second \n- ${mss}: The millisecond \n- ${rand,number}: Random number, for example: ${rand,100}. It will generate a random number from 0 to 99. \n- ${index}: The index of the image pasted into the file by the plugin.\n- `${prompt}`: enter a custom name through an input prompt.",
38 | "markdown-image.base.altFormat.default": "picture ${index}",
39 | "markdown-image.local.path": "Picture storage directory that in the local (automatically created if it does not exist). Notice: You can't use variable in here. You can use variable in `#markdown-image.base.fileNameFormat#`.",
40 | "markdown-image.local.referencePath": "The reference path format in markdown(not include file name). Empty means use relative path. You can use variable of `#markdown-image.base.fileNameFormat#` in here. For example: `/images/${YY}-${MM}/`",
41 | "markdown-image.coding.path": "Picture upload directory that in the repository (automatically created if it does not exist). The repository must initialization first.",
42 | "markdown-image.coding.token": "Coding person [access token](https://help.coding.net/docs/member/tokens.html).",
43 | "markdown-image.coding.repository": "Coding repository, for example: `https://coding-demo.coding.net/p/coding-demo/d/coding-demo/git`",
44 | "markdown-image.github.path": "Picture upload directory that in the repository (automatically created if it does not exist). The repository must initialization first.",
45 | "markdown-image.github.token": "GitHub person [access token](https://github.com/settings/tokens).",
46 | "markdown-image.github.repository": "GitHub repository, for example: `https://github.com/username/repository`",
47 | "markdown-image.github.branch": "GitHub repository branch to save.",
48 | "markdown-image.github.cdn": "The github cdn address format to be used, `${username}` is the username of `#markdown-image.github.repository#`, and `${repository}` is the repository name. `${branch}` is the value of `#markdown-image.github.branch#`. `${filepath}` is the upload path in repository.",
49 | "markdown-image.github.httpProxy": "Connect to Github via http proxy.",
50 | "markdown-image.imgur.clientId": "The client id registered with imgur. You can registed it at [here](https://api.imgur.com/oauth2/addclient).",
51 | "markdown-image.imgur.httpProxy": "Connect to Imgur via http proxy.",
52 | "markdown-image.sm_ms.token": "SM.MS API token (Options). You can register an account and then visit [API Token](https://smms.app/home/apitoken) Page to generate secret token.",
53 | "markdown-image.qiniu.accessKey": "AccessKey of upload.",
54 | "markdown-image.qiniu.secretKey": "SecretKey of upload",
55 | "markdown-image.qiniu.bucket": "Storge name of upload",
56 | "markdown-image.qiniu.domain": "Domain bind with storge name",
57 | "markdown-image.qiniu.zone": "Zone of storge",
58 | "markdown-image.qiniu.east": "East China",
59 | "markdown-image.qiniu.north": "North China",
60 | "markdown-image.qiniu.south": "South China",
61 | "markdown-image.qiniu.na": "North America",
62 | "markdown-image.qiniu.sa": "Southeast Asia",
63 | "markdown-image.upyun.bucket": "Storge name of upload",
64 | "markdown-image.upyun.domain": "Domain bind with storge name",
65 | "markdown-image.upyun.operator": "Operator of upyun",
66 | "markdown-image.upyun.password": "Password of upyun operator",
67 | "markdown-image.upyun.path": "The path that img store",
68 | "markdown-image.upyun.link": "The link that connect to upyun",
69 | "markdown-image.upyun.smart": "Smart choose: v0.api.upyun.com",
70 | "markdown-image.upyun.telecom": "China Telecom: v1.api.upyun.com",
71 | "markdown-image.upyun.unicom": "China Unicom: v2.api.upyun.com",
72 | "markdown-image.upyun.mobile": "China Mobile: v3.api.upyun.com",
73 | "markdown-image.DIY.path": "The Code File Path. You can write a Node.js code file to upload, and fill in the file path to here. Your code must exports a function as `async function (filePath:string, savePath:string, markdownPath:string):string`.\n\nFor example: \n\n ```javascript\nconst path = require('path');\nmodule.exports = async function(filePath, savePath, markdownPath) {\n\t// Return a picture access link\n\treturn path.relative(path.dirname(markdownPath), filePath); \n}\n```\nThe arguments are :\n- `filePath`: The absolute path of the file. \n- `savePath`: The path of the saved file generate according to `#markdown-image.base.fileNameFormat#`. \n- `markdownPath`: The path of markdown file being edited.",
74 | "markdown-image.cloudinary.cloudName": "Your user account name.",
75 | "markdown-image.cloudinary.apiKey": "API key for your account.",
76 | "markdown-image.cloudinary.apiSecret": "API secret for your account.",
77 | "markdown-image.cloudinary.folder": "Folder to upload the image to.",
78 | "markdown-image.cloudflare.accountId": "Your Cloudflare account ID.",
79 | "markdown-image.cloudflare.apiToken": "You Clouflare Image API token.",
80 | "markdown-image.s3.endpoint": "Your S3 API endpoint obtained from bucket setting or dashboard. It should include protocol as well, e.g. `http/https`.",
81 | "markdown-image.s3.region": "Your S3 bucket region obtained from the bucket setting.",
82 | "markdown-image.s3.bucketName": "Your S3 bucket name.",
83 | "markdown-image.s3.accessKeyId": "Your S3 access key ID.",
84 | "markdown-image.s3.secretAccessKey": "Your S3 secret access key.",
85 | "markdown-image.s3.cdn": "Your S3 CDN Url. You can use variable `${bucket}` `${region}` `${pathname}` and `${filepath}`. For example: `https://${bucket}.${region}.s3.amazonaws.com/${pathname}/${filepath}`.",
86 | "markdown-image.s3.config": "The other configuration of the S3Client. You can get all options in [here](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/).",
87 | "markdown-image.sftp.host": "The host of the remote server.",
88 | "markdown-image.sftp.port": "The ssh port of the remote server.",
89 | "markdown-image.sftp.username": "The username of the remote server.",
90 | "markdown-image.sftp.password": "The password of the remote server.",
91 | "markdown-image.sftp.privateKeyPath": "The private key path of the remote server.",
92 | "markdown-image.sftp.path": "Picture storage directory that in the remote (automatically created if it does not exist). Notice: You can't use variable in here. You can use variable in `#markdown-image.base.fileNameFormat#`.",
93 | "markdown-image.sftp.referencePath": "The reference path format in markdown(not include file name). Empty means use relative path. You can use variable of `#markdown-image.base.fileNameFormat#` in here. For example: `/images/${YY}-${MM}/`",
94 | "markdown-image.azure.authenticationMethod": "The authentication method to use for the Azure Blob Storage account. The default is `Passwordless`. You can obtain more information from [here](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-nodejs?tabs=managed-identity%2Croles-azure-portal%2Csign-in-azure-cli#authenticate-to-azure-and-authorize-access-to-blob-data) .",
95 | "markdown-image.azure.authenticationMethod.Passwordless": "Use the account name to authenticate. You must first execute `az login` to log in to Azure CLI.",
96 | "markdown-image.azure.authenticationMethod.ConnectionString": "Use the connection string to authenticate.",
97 | "markdown-image.azure.accountName": "Your Azure Blob Storage account.",
98 | "markdown-image.azure.connect-string": "The connection string of your Azure Blob Storage account.",
99 | "markdown-image.azure.container": "The container name of your Azure Blob Storage account."
100 | }
101 |
--------------------------------------------------------------------------------
/package.nls.zh-cn.json:
--------------------------------------------------------------------------------
1 | {
2 | "markdown-image.title": "Markdown 图片",
3 | "markdown-image.local": "本地",
4 | "markdown-image.qiniu": "七牛",
5 | "markdown-image.upyun": "又拍云",
6 | "markdown-image.DIY": "自定义",
7 | "markdown-image.Cloudinary": "Cloudinary",
8 | "markdown-image.command.paste": "粘贴图片",
9 | "markdown-image.command.config": "Markdown 图片配置",
10 | "markdown-image.command.paste-rich-text": "粘贴富文本 (Beta)",
11 | "markdown-image.base.uploadMethod": "上传图片的方式。到本地或其他图片CDN服务。\n\n- 本地 [设置项目](#markdown-image.local.path)\n\n- GitHub [设置项目](#markdown-image.github.branch)\n\n- Imgur [设置项目](#markdown-image.imgur.clientId)\n\n- SM.MS [设置项目](#markdown-image.sm_ms.token)\n\n- 七牛 [设置项目](#markdown-image.qiniu.accessKey)\n\n- 又拍云 [设置项目](#markdown-image.upyun.bucket)\n\n- 自定义 [设置项目](#markdown-image.DIY.path)\n\n- Cloudinary [设置项目](#markdown-image.cloudinary.apiKey)\n\n- Cloudflare [设置项目](#markdown-image.cloudflare.accountId)\n\n- S3 [设置项目](#markdown-image.s3.endpoint)",
12 | "markdown-image.base.uploadMethod.Local": "将图像上传到项目目录。您可以使用 markdown-image.local.path 设置上传的相对路径。",
13 | "markdown-image.base.uploadMethod.Coding": "将图像上传到 Coding.net 中的 Git 存储库。您可以通过 markdown-image.coding.repository 配置存储库. 您必须通过 markdown-image.coding.token 配置个人访问令牌访问存储库。",
14 | "markdown-image.base.uploadMethod.GitHub": "将图像上传到 GitHub.com 中的 Git 存储库。您可以通过 markdown-image.github.repository 配置存储库. 您必须通过 markdown-image.github.token 配置个人访问令牌访问存储库。",
15 | "markdown-image.base.uploadMethod.Imgur": "将图像上传到 Imgur 。您可以通过 markdown-image.imgur.clientId 配置`Client Id`。",
16 | "markdown-image.base.uploadMethod.SM.MS": "将图像上传到 sm.ms。 您如果有一个帐户,可以通过 markdown-image.sm_ms.token 配置 `Secret Token` 。",
17 | "markdown-image.base.uploadMethod.DataURL": "将图片转为Data URL 插入.",
18 | "markdown-image.base.uploadMethod.Qiniu": "将图片上传到七牛对象存储,你有很多项目需要配置。",
19 | "markdown-image.base.uploadMethod.Upyun": "将图片上传到又拍云对象存储,你有很多项目需要配置。",
20 | "markdown-image.base.uploadMethod.DIY": "您可以自定义用于上传的代码。您需要通过 markdown-image.DIY.path 配置代码路径。",
21 | "markdown-image.base.uploadMethod.Cloudinary": "上传图片到 Cloudinary。你有很多项目需要配置。",
22 | "markdown-image.base.uploadMethod.Cloudflare": "上传图片到 Cloudflare。你有很多项目需要配置。",
23 | "markdown-image.base.uploadMethod.S3": "上传图片到 S3。你有很多项目需要配置。",
24 | "markdown-image.base.uploadMethod.SFTP": "上传图片到 SFTP。你有很多项目需要配置。",
25 | "markdown-image.base.uploadMethod.Azure": "上传图片到 Azure。",
26 | "markdown-image.base.uploadMethods": "可用于并行上传的上传方法,默认为空,列表内容为上面 `#markdown-image.base.uploadMethod#` 选项的可选值。此处设置的上传方法的上传结果,将不会被插入到 Markdown 文件中。",
27 | "markdown-image.base.fileNameFormat": "上传的文件名与路径的格式字符串。不支持 `Imgur` 和 `SM.MS`。 你有一些变量可以使用: \n\n- `${filename}`: 图片原始的文件名。 \n- `${mdname}`: 正在编辑的 Markdown 文件的名称(不含扩展名)。 \n- `${path}`: 正在编辑的 Markdown 文件相对于根目录的路径。 \n- `${hash}`: 图像的 sha256 哈希。 \n- `${timestamp}`: 上传时间的时间戳。 \n- `${YY}`: 年份 \n- `${MM}`: 月份 \n- `${DD}`: 日期 \n- `${hh}`: 12小时制小时 \n- `${HH}`: 24小时制小时 \n- `${mm}`: 分 \n- `${ss}`: 秒 \n- `${mss}`: 毫秒 \n- `${rand,number}`: 随机数, 比如:`${rand,100}`. 它将生成从 0 到 99 的随机数。\n- `${prompt}`: 可以在粘贴图像时通过输入框输入自定义名称。",
28 | "markdown-image.base.urlEncode": "是否对图像的 URL 编码。",
29 | "markdown-image.base.fileFormat": "剪贴板图像文件格式。您可以使用`png`或`jpg`。",
30 | "markdown-image.base.codeType": "插入代码的类型",
31 | "markdown-image.base.codeType.Markdown": "Markdown 代码,比如:",
32 | "markdown-image.base.codeType.HTML": "HTML 代码,比如:
",
33 | "markdown-image.base.codeType.DIY": "自定义插入代码,你需要通过 `#markdown-image.base.codeFormat#` 配置代码格式。",
34 | "markdown-image.base.codeFormat": "自定义插入代码的格式,需设置 `#markdown-image.base.codeType#` 为 `DIY`。你可以使用 `${src}` 和 `${alt}` 变量,分别表示图片地址与默认占位符(由 `#markdown-image.base.altFormat#` 定义)。比如:``。也可以使用 `#markdown-image.base.fileNameFormat#` 中的所有变量。",
35 | "markdown-image.base.imageWidth": "图片的最大宽度,若图片大于这个宽度,则会设置宽度为该值。设置为 0 则表示不设置。",
36 | "markdown-image.base.altFormat": "Markdown 代码的占位符。你有一些变量可以使用: \n\n- `${filename}`: 图片原始的文件名。 \n- `${mdname}`: 正在编辑的 Markdown 文件的名称(不含扩展名)。 \n- `${hash}`: 图像的 sha256 哈希。 \n- `${timestamp}`: 上传时间的时间戳。 \n- `${YY}`: 年份 \n- `${MM}`: 月份 \n- `${DD}`: 日期 \n- `${hh}`: 12小时制小时 \n- `${HH}`: 24小时制小时 \n- `${mm}`: 分 \n- `${ss}`: 秒 \n- `${mss}`: 毫秒 \n- `${rand,number}`: 随机数, 比如:`${rand,100}`. 它将生成从 0 到 99 的随机数。\n- `${index}`: 文件通过插件粘贴的图片 index。\n- `${prompt}`: 通过输入框输入自定义名称。",
37 | "markdown-image.base.altFormat.default": "图 ${index}",
38 | "markdown-image.local.path": "本地的图片存储目录(如果不存在,则自动创建)。支持相对路径,相对于所粘贴的 Markdown 文件。 `/` 表示打开的文件夹根目录。注意:您不能在此处使用变量。您可以在 `#markdown-image.base.fileNameFormat#` 中使用变量。",
39 | "markdown-image.local.referencePath": "Markdown 中的图片的引用路径格式(不包含文件名)。留空表示使用相对路径。 你可以使用 `#markdown-image.base.fileNameFormat#` 中的所有变量。例如:`/images/${YY}-${MM}/`",
40 | "markdown-image.coding.path": "仓库中的图片保存目录(如果不存在,则自动创建)。",
41 | "markdown-image.coding.token": "Coding 的个人[访问令牌](https://help.coding.net/docs/member/tokens.html),用于访问仓库,上传图片。",
42 | "markdown-image.coding.repository": "所要上传的目的仓库,比如:`https://coding-demo.coding.net/p/coding-demo/d/coding-demo/git`。仓库必须要先初始化。",
43 | "markdown-image.github.path": "仓库中的图片保存目录(如果不存在,则自动创建)。仓库需要先初始化。",
44 | "markdown-image.github.token": "GitHub 的个人[访问令牌](https://github.com/settings/tokens),用于访问仓库,上传图片。",
45 | "markdown-image.github.repository": "所要上传的目的仓库,比如:`https://github.com/username/repository/`。仓库必须要先初始化。",
46 | "markdown-image.github.branch": "要存放图片的仓库分支。",
47 | "markdown-image.github.cdn": "要使用的 CDN 地址格式,${username} 表示上传仓库的用户名,${repository} 表示上传的仓库,${branch} 表示上传的分支,${filepath} 表示上传的仓库目录与文件名。",
48 | "markdown-image.github.httpProxy": "通过 HTTP 代理连接到 Github",
49 | "markdown-image.imgur.clientId": "在 `imgur` 注册的 `Client Id`。您可以在[这儿](https://api.imgur.com/oauth2/addclient)注册。",
50 | "markdown-image.imgur.httpProxy": "通过 HTTP 代理连接到 Imgur。",
51 | "markdown-image.sm_ms.token": "SM.MS Secret Token。您可以注册一个帐户,然后访问 [API Access](https://smms.app/home/apitoken) 页面以生成。",
52 | "markdown-image.qiniu.accessKey": "七牛账户的 Access Key。",
53 | "markdown-image.qiniu.secretKey": "七牛账户的 Secret Key。",
54 | "markdown-image.qiniu.bucket": "七牛的对象存储空间名。",
55 | "markdown-image.qiniu.domain": "七牛空间绑定的域名。",
56 | "markdown-image.qiniu.zone": "七牛空间的存储区域。",
57 | "markdown-image.qiniu.east": "华东",
58 | "markdown-image.qiniu.north": "华北",
59 | "markdown-image.qiniu.south": "华南",
60 | "markdown-image.qiniu.na": "北美",
61 | "markdown-image.qiniu.sa": "东南亚",
62 | "markdown-image.upyun.bucket": "又拍云的云存储服务名称。",
63 | "markdown-image.upyun.domain": "又拍云云存储服务绑定的域名。",
64 | "markdown-image.upyun.operator": "又拍云的操作员名。",
65 | "markdown-image.upyun.password": "又拍云的操作员密码。",
66 | "markdown-image.upyun.path": "又拍云图片存储路径。",
67 | "markdown-image.upyun.link": "又拍云链接线路。",
68 | "markdown-image.upyun.smart": "智能选路(推荐):v0.api.upyun.com",
69 | "markdown-image.upyun.telecom": "电信线路:v1.api.upyun.com",
70 | "markdown-image.upyun.unicom": "联通(网通)线路:v2.api.upyun.com",
71 | "markdown-image.upyun.mobile": "移动(铁通)线路:v3.api.upyun.com",
72 | "markdown-image.DIY.path": "你写的代码的绝对路径。你可以写一个 Node.js 代码文件用于上传,并在此处填写文件路径。你的代码必须 exports 一个像 `async function (filePath:string, savePath:string, markdownPath:string):string` 的函数。\n\n比如:\n\n ```javascript\nconst path = require('path');\nmodule.exports = async function(filePath, savePath, markdownPath) {\n\t// Return a picture access link\n\treturn path.relative(path.dirname(markdownPath), filePath); \n}\n```\n函数参数分别是:\n- `filePath`: 文件的绝对路径。 \n- `savePath`: 保存的文件的路径,根据 `#markdown-image.base.fileNameFormat#` 生成。 \n- `markdownPath`: 正在编辑的 markdown 文件的路径。",
73 | "markdown-image.cloudinary.cloudName": "你的 Cloudinary 用户名",
74 | "markdown-image.cloudinary.apiKey": "你的 Cloudinary 账户 API key",
75 | "markdown-image.cloudinary.apiSecret": "你的 Cloudinary 账户 API secret",
76 | "markdown-image.cloudinary.folder": "你所要上传到的文件夹",
77 | "markdown-image.cloudflare.accountId": "你的帐户名称",
78 | "markdown-image.cloudflare.apiToken": "Cloudflare API 令牌",
79 | "markdown-image.s3.endpoint": "你的 S3 API 端点,是从存储桶设置或 dashboard 获得的。应包括协议,比如:`http/https`。",
80 | "markdown-image.s3.region": "你的 S3 存储桶区域,是从存储桶设置中获得的。",
81 | "markdown-image.s3.bucketName": "你的 S3 存储桶名称。",
82 | "markdown-image.s3.accessKeyId": "你的 S3 access key ID。",
83 | "markdown-image.s3.secretAccessKey": "你的 S3 secret access key。",
84 | "markdown-image.s3.cdn": "设置你的 S3 CDN URL。你可以使用变量 `${bucket}` `${region}` `${pathname}` and `${filepath}`。比如:`https://${bucket}.${region}.s3.amazonaws.com/${pathname}/${filepath}`。",
85 | "markdown-image.s3.config": "S3Client 的其它配置项目。你可以在[此处](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/)中获得所有选项。",
86 | "markdown-image.sftp.host": "远程服务器地址。",
87 | "markdown-image.sftp.port": "SSH 服务端口。",
88 | "markdown-image.sftp.username": "远程用户名。",
89 | "markdown-image.sftp.password": "SSH 密码。",
90 | "markdown-image.sftp.privateKeyPath": "远程私钥文件路径。",
91 | "markdown-image.sftp.path": "远程服务器的图片存储目录(如果不存在,则自动创建)。支持相对路径,相对于所粘贴的 Markdown 文件。 `/` 表示打开的文件夹根目录。注意:您不能在此处使用变量。您可以在 `#markdown-image.base.fileNameFormat#` 中使用变量。",
92 | "markdown-image.sftp.referencePath": "Markdown 中的图片的引用路径格式(不包含文件名)。留空表示使用相对路径。 你可以使用 `#markdown-image.base.fileNameFormat#` 中的所有变量。例如:`/images/${YY}-${MM}/`",
93 | "markdown-image.azure.authenticationMethod": "用于 Azure Blob 存储帐户的身份验证方法。默认值为 `Passwordless`。您可以从[这里](https://learn.microsoft.com/zh-cn/azure/storage/blobs/storage-quickstart-blobs-nodejs?tabs=managed-identity%2Croles-azure-portal%2Csign-in-azure-cli#authenticate-to-azure-and-authorize-access-to-blob-data)获取更多信息。",
94 | "markdown-image.azure.authenticationMethod.Passwordless": "使用帐户名称进行身份验证。您必须首先执行 `az login` 登录到 Azure CLI。",
95 | "markdown-image.azure.authenticationMethod.ConnectionString": "使用连接字符串进行身份验证。",
96 | "markdown-image.azure.accountName": "Azure Blob 存储帐户。",
97 | "markdown-image.azure.connect-string": "Azure 存储连接字符串。",
98 | "markdown-image.azure.container": "Azure 存储容器名称。"
99 | }
100 |
--------------------------------------------------------------------------------
/package.nls.zh-tw.json:
--------------------------------------------------------------------------------
1 | {
2 | "markdown-image.title": "Markdown 圖片",
3 | "markdown-image.local": "本地",
4 | "markdown-image.qiniu": "七牛",
5 | "markdown-image.upyun": "又拍雲",
6 | "markdown-image.DIY": "自定義",
7 | "markdown-image.Cloudinary": "Cloudinary",
8 | "markdown-image.command.paste": "粘貼圖片",
9 | "markdown-image.command.config": "Markdown 圖片配置",
10 | "markdown-image.command.paste-rich-text": "粘貼富文本 (Beta)",
11 | "markdown-image.base.uploadMethod": "上傳圖片的方式。到本地或其他圖片 CDN 服務。\n\n- 本地 [設置項目](#markdown-image.local.path)\n\n- GitHub [設置項目](#markdown-image.github.branch)\n\n- Imgur [設置項目](#markdown-image.imgur.clientId)\n\n- SM.MS [設置項目](#markdown-image.sm_ms.token)\n\n- 七牛 [設置項目](#markdown-image.qiniu.accessKey)\n\n- 又拍雲 [設置項目](#markdown-image.upyun.bucket)\n\n- 自定義 [設置項目](#markdown-image.DIY.path)\n\n- Cloudinary [設置項目](#markdown-image.cloudinary.apiKey)\n\n- Cloudflare [設置項目](#markdown-image.cloudflare.accountId)\n\n- S3 [設置項目](#markdown-image.s3.endpoint)",
12 | "markdown-image.base.uploadMethod.Local": "將圖像上傳到項目目錄。您可以使用 markdown-image.local.path 設置上傳的相對路徑。",
13 | "markdown-image.base.uploadMethod.Coding": "將圖像上傳到 Coding.net 中的 Git 存儲庫。您可以通過markdown-image.coding.repository 配置存儲庫. 您必須通過 markdown-image.coding.token 配置個人訪問令牌訪問存儲庫。",
14 | "markdown-image.base.uploadMethod.Imgur": "將圖像上傳到 Imgur。您可以通過 markdown-image.imgur.clientId 配置`Client Id`。",
15 | "markdown-image.base.uploadMethod.SM.MS": "將圖像上傳到 sm.ms 。 您如果有一個帳戶,可以通過 markdown-image.sm_ms.token 配置 `Secret Token` 。",
16 | "markdown-image.base.uploadMethod.DataURL": "將圖片轉爲Data URL 插入.",
17 | "markdown-image.base.uploadMethod.Qiniu": "將圖片上傳到七牛對象存儲,你有很多項目需要配置。",
18 | "markdown-image.base.uploadMethod.Upyun": "將圖片上傳到又拍雲對象存儲,你有很多項目需要配置。",
19 | "markdown-image.base.uploadMethod.DIY": "您可以自定義用於上傳的代碼。您需要通過 markdown-image.DIY.path 配置代碼路徑。",
20 | "markdown-image.base.uploadMethod.Cloudinary": "將圖片上傳到 Cloudinary。您需要配置 Cloudinary 的用戶名、API key 和 API secret。",
21 | "markdown-image.base.uploadMethod.Cloudflare": "將圖片上傳到 Cloudflare Workers KV。您需要配置 Cloudflare 的帳戶名和 API 令牌。",
22 | "markdown-image.base.uploadMethod.S3": "將圖片上傳到 Amazon S3。您需要配置 S3 的 API 端點、區域、存儲桶名、Access Key ID 和 Secret Access Key",
23 | "markdown-image.base.uploadMethod.SFTP": "將圖片上傳到 SFTP 服務器。您需要配置 SFTP 服務器地址、端口、賬戶、密碼或私鑰文件路徑。",
24 | "markdown-image.base.uploadMethod.Azure": "將圖片上傳到 Azure Blob 存儲。您需要配置 Azure 存儲帳戶連接字符串和容器名。",
25 | "markdown-image.base.fileNameFormat": "上傳的檔案名稱與路徑的格式字串。不支援 Imgur 和 SM.MS。您可以使用以下變數:\n\n- ${filename}:圖片原始的檔案名稱。\n- ${mdname}:正在編輯的 Markdown 檔案的名稱(不包含副檔名)。\n- ${path}:正在編輯的 Markdown 檔案相對於根目錄的路徑。\n- ${hash}:圖片的 sha256 雜湊。\n- ${timestamp}:上傳時間的時間戳記。\n- ${YY}:年份\n- ${MM}:月份\n- ${DD}:日期\n- ${hh}:12 小時制的小時\n- ${HH}:24 小時制的小時\n- ${mm}:分鐘\n- ${ss}:秒\n- ${mss}:毫秒\n- ${rand,number}:隨機數,例如:${rand,100}。它將生成從 0 到 99 的隨機數。\n- ${prompt}:在貼上圖片時可通過輸入框輸入自訂名稱。",
26 | "markdown-image.base.uploadMethods": "可用於並行上傳的多個上傳方法。默認為空,列表應包含上述提到的 `#markdown-image.base.uploadMethod#` 選項的可選值。配置的上傳方法的上傳結果將不會插入到 Markdown 文件中。",
27 | "markdown-image.base.urlEncode": "是否對圖像的 URL 編碼。",
28 | "markdown-image.base.fileFormat": "剪貼板圖像文件格式。 您可以使用`png`或`jpg`。",
29 | "markdown-image.base.codeType": "插入代碼的類型",
30 | "markdown-image.base.codeType.Markdown": "Markdown 代碼,比如:",
31 | "markdown-image.base.codeType.HTML": "HTML 代碼,比如:
",
32 | "markdown-image.base.codeType.DIY": "自定義插入代碼,你需要通過 `#markdown-image.base.codeFormat#` 配置代碼格式。",
33 | "markdown-image.base.codeFormat": "自定義插入代碼的格式,需設置 `#markdown-image.base.codeType#` 爲 `DIY`。你可以使用 `${src}` 和 `${alt}` 變量,分別表示圖片地址與默認佔位符(由 `#markdown-image.base.altFormat#` 定義)。比如:``。也可以使用 `#markdown-image.base.fileNameFormat#` 中的所有變量。",
34 | "markdown-image.base.imageWidth": "圖片的最大寬度,若圖片大於這個寬度,則會設置寬度為該值。設置為 0 則表示不設置。",
35 | "markdown-image.base.altFormat": "Markdown 代碼的佔位符。您可以使用以下變量:\n\n- `${filename}`:圖片原始的檔案名稱。\n- `${mdname}`:正在編輯的 Markdown 檔案的名稱(不包含副檔名)。\n- `${hash}`:圖像的 sha256 雜湊。\n- `${timestamp}`:上傳時間的時間戳。\n- `${YY}`:年份\n- `${MM}`:月份\n- `${DD}`:日期\n- `${hh}`:12 小時制的小時\n- `${HH}`:24 小時制的小時\n- `${mm}`:分鐘\n- `${ss}`:秒\n- `${mss}`:毫秒\n- `${rand,number}`:隨機數,例如:`${rand,100}`。它將生成從 0 到 99 的隨機數。\n- `${index}`:插件粘貼到文件中的圖片索引。\n- `${prompt}`: 通過輸入框輸入自定義名稱。",
36 | "markdown-image.base.altFormat.default": "圖 ${index}",
37 | "markdown-image.local.path": "本地的圖片存儲目錄(如果不存在,則自動創建)。支持相對路徑,相對於所粘貼的 Markdown 文件。`/` 表示打開的文件夾根目錄。注意:您不能在此處使用變量。您可以在 `#markdown-image.base.fileNameFormat#` 中使用變量。",
38 | "markdown-image.local.referencePath": "Markdown 中的圖片的引用路徑格式(不包含文件名)。留空表示使用相對路徑。你可以使用 `#markdown-image.base.fileNameFormat#` 中的所有變量。例如:`/images/${YY}-${MM}/`",
39 | "markdown-image.coding.path": "倉庫中的圖片保存目錄(如果不存在,則自動創建)。",
40 | "markdown-image.coding.token": "Coding 的個人[訪問令牌](https://help.coding.net/docs/member/tokens.html),用於訪問倉庫,上傳圖片。",
41 | "markdown-image.coding.repository": "所要上傳的目的倉庫,比如:`https://coding-demo.coding.net/p/coding-demo/d/coding-demo/git`。倉庫必須要先初始化。",
42 | "markdown-image.github.path": "倉庫中的圖片保存目錄(如果不存在,則自動創建)。",
43 | "markdown-image.github.token": "GitHub 的個人[訪問令牌](https://github.com/settings/tokens),用於訪問倉庫,上傳圖片。",
44 | "markdown-image.github.repository": "所要上傳的目的倉庫,比如:`https://github.com/username/repository/`。倉庫必須要先初始化。 ",
45 | "markdown-image.github.branch": "要存放圖片的倉庫分支。",
46 | "markdown-image.github.cdn": "要使用的 CDN 地址格式,${username} 表示上傳倉庫的用戶名,${repository} 表示上傳的倉庫,${branch} 表示上傳的分支,${filepath} 表示上傳的倉庫目錄與文件名。",
47 | "markdown-image.github.httpProxy": "通過 HTTP 代理連接到 Github。",
48 | "markdown-image.imgur.clientId": "在 `imgur` 註冊的 `Client Id`。您可以在[這兒](https://api.imgur.com/oauth2/addclient)註冊。",
49 | "markdown-image.imgur.httpProxy": "通過 HTTP 代理連接到 Imgur。",
50 | "markdown-image.sm_ms.token": "SM.MS Secret Token。您可以註冊一個帳戶,然後訪問 [API Access](https://smms.app/home/apitoken) 頁面生成。",
51 | "markdown-image.qiniu.accessKey": "七牛賬戶的 Access Key。",
52 | "markdown-image.qiniu.secretKey": "七牛賬戶的 Secret Key。",
53 | "markdown-image.qiniu.bucket": "七牛的對象存儲空間名。",
54 | "markdown-image.qiniu.domain": "七牛空間綁定的域名。",
55 | "markdown-image.qiniu.zone": "七牛空間的存儲區域。",
56 | "markdown-image.qiniu.east": "華東",
57 | "markdown-image.qiniu.north": "華北",
58 | "markdown-image.qiniu.south": "華南",
59 | "markdown-image.qiniu.na": "北美",
60 | "markdown-image.qiniu.sa": "東南亞",
61 | "markdown-image.upyun.bucket": "又拍雲的雲存儲服務名稱。",
62 | "markdown-image.upyun.domain": "又拍雲雲存儲服務綁定的域名。",
63 | "markdown-image.upyun.operator": "又拍雲的操作員名。",
64 | "markdown-image.upyun.password": "又拍雲的操作員密碼。",
65 | "markdown-image.upyun.path": "又拍雲圖片存儲路徑。",
66 | "markdown-image.upyun.link": "又拍雲鏈接線路。",
67 | "markdown-image.upyun.smart": "智能選路(推薦):v0.api.upyun.com",
68 | "markdown-image.upyun.telecom": "電信線路:v1.api.upyun.com",
69 | "markdown-image.upyun.unicom": "聯通(網通)線路:v2.api.upyun.com",
70 | "markdown-image.upyun.mobile": "移動(鐵通)線路:v3.api.upyun.com",
71 | "markdown-image.DIY.path": "你寫的代碼的絕對路徑。你可以寫一個 Node.js 代碼文件用於上傳,並在此處填寫文件路徑。你的代碼必須 exports 一個像 `async function (filePath:string, savePath:string, markdownPath:string):string` 的函數。\n \n比如:\n\n ```javascript\nconst path = require('path');\nmodule.exports = async function(filePath, savePath, markdownPath) {\n\t// Return a picture access link\n\treturn path.relative(path.dirname(markdownPath), filePath); \n}\n```\n函數參數分別是:\n- `filePath`: 文件的絕對路徑。 \n- `savePath` : 保存的文件的路徑,根據 `#markdown-image.base.fileNameFormat#` 生成。 \n- `markdownPath`: 正在編輯的 markdown 文件的路徑。",
72 | "markdown-image.cloudinary.cloudName": "你的 Cloudinary 用戶名",
73 | "markdown-image.cloudinary.apiKey": "你的 Cloudinary 賬戶 API key",
74 | "markdown-image.cloudinary.apiSecret": "你的 Cloudinary 賬戶 API secret",
75 | "markdown-image.cloudinary.folder": "你所要上傳到的文件夾",
76 | "markdown-image.cloudflare.accountId": "你的帳戶名稱",
77 | "markdown-image.cloudflare.apiToken": "Cloudflare API 令牌",
78 | "markdown-image.s3.endpoint": "你的 S3 API 端點,是从存儲桶設置或 dashboard 獲得的。應包括協議,比如:`http/https`。",
79 | "markdown-image.s3.region": "你的 S3 存儲桶區域,是從存儲桶設置中獲得的。",
80 | "markdown-image.s3.bucketName": "你的 S3 存儲桶名稱。",
81 | "markdown-image.s3.accessKeyId": "你的 S3 access key ID.",
82 | "markdown-image.s3.secretAccessKey": "你的 S3 secret access key.",
83 | "markdown-image.s3.cdn": "設定你的 S3 CDN URL。你可以使用變數 `${bucket}` `${region}` `${pathname}` and `${filepath}`。比如:`https://${bucket}.${region}.s3.amazonaws.com/${pathname}/${filepath}`。",
84 | "markdown-image.s3.config": "S3Client 的其它配置項目。你可以在[此處](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/)中获得所有选项。",
85 | "markdown-image.sftp.host": "遠端伺服器地址。",
86 | "markdown-image.sftp.port": "SSH 服務端口。",
87 | "markdown-image.sftp.username": "遠端賬戶。",
88 | "markdown-image.sftp.password": "SSH 密碼。",
89 | "markdown-image.sftp.privateKeyPath": "遠端私鑰文件路徑。",
90 | "markdown-image.sftp.path": "遠端伺服器的圖片存儲目錄(如果不存在,則自動創建)。支持相對路徑,相對於所粘貼的 Markdown 文件。 `/` 表示打開的文件夾根目錄。注意:您不能在此處使用變量。您可以在 `#markdown-image.base.fileNameFormat#` 中使用變量。",
91 | "markdown-image.sftp.referencePath": "Markdown 中的圖片的引用路徑格式(不包含文件名)。留空表示使用相對路徑。你可以使用 `#markdown-image.base.fileNameFormat#` 中的所有變量。例如:`/images/${YY}-${MM}/`",
92 | "markdown-image.azure.authenticationMethod": "用於 Azure Blob 存儲帳戶的身份驗證方法。 默認值為 `Passwordless`。 您可以從[這裡](https://learn.microsoft.com/zh-tw/azure/storage/blobs/storage-quickstart-blobs-nodejs?tabs=managed-identity%2Croles-azure-portal%2Csign-in-azure-cli#authenticate-to-azure-and-authorize-access-to-blob-data)獲取更多信息。",
93 | "markdown-image.azure.authenticationMethod.Passwordless": "使用帳戶名稱進行身份驗證。 您必須首先執行 `az login` 登錄到 Azure CLI。",
94 | "markdown-image.azure.authenticationMethod.ConnectionString": "使用連接字符串進行身份驗證。",
95 | "markdown-image.azure.accountName": "Azure Blob 存儲帳戶名。",
96 | "markdown-image.azure.connect-string": "Azure 存儲帳戶連接字符串。",
97 | "markdown-image.azure.container": "Azure 存儲容器名。"
98 | }
99 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | // The module 'vscode' contains the VS Code extensibility API
3 | // Import the module and reference it with the alias vscode in your code below
4 | import * as vscode from 'vscode';
5 | import * as path from 'path';
6 | import utils from './lib/utils';
7 | import { locale as $l } from './lib/utils';
8 | import { imageSize } from 'image-size';
9 |
10 | function getUploadInstance(config: any) {
11 | let uploads: Upload[] = [];
12 |
13 | // Get All Upload Methods
14 | let uploadMethods = config.base.uploadMethods || [];
15 | uploadMethods.unshift(config.base.uploadMethod);
16 | uploadMethods = Array.from(new Set(uploadMethods));
17 |
18 | // Load All Upload Method Instance
19 | for (const t of uploadMethods) {
20 | const upload = utils.getUpload(t, config)
21 | if (upload) uploads.push(upload);
22 | }
23 |
24 | return uploads;
25 | }
26 |
27 | export function activate(context: vscode.ExtensionContext) {
28 | let config = utils.getConfig();
29 | let uploads: Upload[] = getUploadInstance(config);
30 |
31 | let pasteCommand = vscode.commands.registerCommand('markdown-image.paste', async () => {
32 | let stop = () => {};
33 | try {
34 | stop = utils.showProgress($l['uploading']);
35 |
36 | let editor = vscode.window.activeTextEditor;
37 | let selections = utils.getSelections();
38 | let savePath = utils.getTmpFolder();
39 | savePath = path.resolve(savePath, `pic_${new Date().getTime()}.png`);
40 | let images = await utils.getPasteImage(savePath);
41 | images = images.filter(img => ['.jpg', '.jpeg', '.gif', '.bmp', '.png', '.webp', '.svg'].find(ext => img.endsWith(ext)));
42 | if (config.base.fileFormat == 'jpg' && images.length === 1 && images[0] == savePath) {
43 | images[0] = await utils.convertImage(images[0])
44 | }
45 |
46 | let urls = [], maxWidth = [];
47 | for (let i = 0; i < images.length; i++) {
48 | let width = imageSize(images[i]).width || 0;
49 | maxWidth.push(config.base.imageWidth < width ? config.base.imageWidth : 0);
50 | let name = config.base.fileNameFormat ? await utils.formatName(config.base.fileNameFormat, images[i], savePath === images[i]) + (images[i] ? path.extname(images[i]) : '.png') : images[i];
51 | console.debug(`Uploading ${images[i]} to ${name}.`);
52 |
53 | let p;
54 | for (let j = 0; j < uploads.length; j++) {
55 | let upload = uploads[j];
56 | const result = await upload.upload(images[i], name);
57 | if (j == 0) p = result;
58 | }
59 | images[i] = name;
60 | if(p) { urls.push(p); }
61 | }
62 |
63 | let insertCode = '', insertTag = '';
64 | for (let i = 0; i < urls.length; i++) {
65 | let selection = await utils.getAlt(config.base.altFormat, images[i], context);
66 | if (selections?.length === 1 && editor?.document.getText(selections[0])) {
67 | selection = `${editor?.document.getText(selections[0])} ${i + 1}`;
68 | }
69 | else if(selections?.[i] && editor?.document.getText(selections[i]))
70 | {
71 | selection = selections?.[i] && editor?.document.getText(selections[i]);
72 | }
73 |
74 | if (config.base.uploadMethod !== 'Data URL') {
75 | if(config.base.urlEncode) {
76 | urls[i] = encodeURIComponent(urls[i].toString()).replace(/%5C/g, '\\').replace(/%2F/g, '/').replace(/%3A/g, ':').replace(/%40/g, '@');
77 | } else {
78 | urls[i] = urls[i].replaceAll(' ', '%20');
79 | }
80 | let text = await utils.formatCode(urls[i], selection, maxWidth[i], config.base.codeType, config.base.codeFormat || '');
81 | if (selections?.[i] && selections?.length > 1) {
82 | await utils.editorEdit(selections[i], text);
83 | }
84 | else {
85 | insertCode += text;
86 | }
87 | }
88 | else
89 | {
90 | let tag = new Date().getTime().toString();
91 | let text = await utils.formatCode(tag, selection, maxWidth[i], 'Markdown', '');
92 | tag = `\n[${tag}]: ${urls[i]}`;
93 | if (selections?.[i] && selections?.length > 1) {
94 | await utils.insertToEnd(tag);
95 | await utils.editorEdit(selections[i], text);
96 | }
97 | else {
98 | insertCode += text;
99 | insertTag += tag;
100 | }
101 | }
102 | }
103 |
104 | if (insertCode) {
105 | let pos = editor?.selection.active;
106 | if (config.base.uploadMethod === 'Data URL') {
107 | await utils.insertToEnd(insertTag);
108 | } else if (pos) {
109 | await utils.editorEdit(pos, insertCode);
110 | } else {
111 | vscode.env.clipboard.writeText(insertCode);
112 | setTimeout(() => {
113 | vscode.commands.executeCommand("editor.action.clipboardPasteAction");
114 | }, 100);
115 | }
116 | }
117 |
118 | utils.noticeComment(context);
119 | } catch (error) {
120 | let e = error as Error;
121 | vscode.window.showErrorMessage(`${$l['something_wrong']}${e.message in $l ? ($l)[e.message] : e.message}\n${e.toString()}`);
122 | }
123 |
124 | stop();
125 | });
126 |
127 | context.subscriptions.push(pasteCommand);
128 |
129 | let configCommand = vscode.commands.registerCommand('markdown-image.config', () => {
130 | vscode.commands.executeCommand('workbench.action.openSettings', 'markdown-image' );
131 | });
132 |
133 | context.subscriptions.push(configCommand);
134 |
135 | let richTextCommand = vscode.commands.registerCommand('markdown-image.paste-rich-text', async () => {
136 | let stop = () => {};
137 | try {
138 | let editor = vscode.window.activeTextEditor;
139 | let text = await utils.getRichText();
140 |
141 | if(text) {
142 | utils.editorEdit(editor?.selection, utils.html2Markdown(text));
143 | }
144 | } catch (error) {
145 | let e = error as Error;
146 | vscode.window.showErrorMessage(`${$l['something_wrong']}${e.message}\n${e.toString()}`);
147 | }
148 |
149 | stop();
150 | });
151 |
152 | context.subscriptions.push(richTextCommand);
153 |
154 | vscode.workspace.onDidChangeConfiguration(function(event) {
155 | config = utils.getConfig();
156 | uploads = getUploadInstance(config);
157 | });
158 | }
159 |
160 | // this method is called when your extension is deactivated
161 | export function deactivate() {
162 | }
163 |
--------------------------------------------------------------------------------
/src/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import * as i18n from './locale';
3 |
4 | export default () => {
5 | let locale = i18n.default;
6 | let lang = vscode.env.language;
7 | let langLocale: any = null;
8 |
9 | try {
10 | langLocale = i18n.$[lang];
11 | } catch (error) {
12 | lang = lang.split('-')[0];
13 | }
14 |
15 | try {
16 | langLocale = i18n.$[lang];
17 | } catch (error) { }
18 |
19 | if (langLocale) { locale = Object.assign(locale, langLocale); }
20 |
21 | return locale;
22 | };
--------------------------------------------------------------------------------
/src/i18n/locale.ts:
--------------------------------------------------------------------------------
1 | import zh_cn from './locale.zh-cn';
2 | import zh_tw from './locale.zh-tw';
3 |
4 | export default {
5 | config_failed: "Config Failed: ",
6 | upload_failed: "Upload File Failed: ",
7 | save_failed: "Save File Failed: ",
8 | something_wrong: "Something was wrong: ",
9 | prompt_name_component: "Please enter a file name. The full name will be: ",
10 | user_did_not_answer_prompt: "User did not answer prompt",
11 | open_with_folder: "Please Open the project of the file with folder.",
12 | replace_or_no: "The file was exists. Would you replace it?",
13 | Yes: "Yes",
14 | No: "No",
15 | 'qiniu.east': "East China",
16 | 'qiniu.north': "North China",
17 | 'qiniu.south': "South China",
18 | 'qiniu.na': "North America",
19 | 'qiniu.sa': "Southeast Asia",
20 | 'upyun.smart': "Smart choose: v0.api.upyun.com",
21 | 'upyun.telecom': "China Telecom: v1.api.upyun.com",
22 | 'upyun.unicom': "China Unicom: v2.api.upyun.com",
23 | 'upyun.mobile': "China Mobile: v3.api.upyun.com",
24 | 'smms.token-miss': 'Please setting sm.ms token first.',
25 | named_paste: "Name the picture you pasted (don't include extname, it's will be replace the ${filename} in the format).",
26 | uploading: "Uploading...",
27 | picture: "picture",
28 | install_xclip: "You need to install xclip command first.",
29 | powershell_not_found: "The powershell command is not in you PATH environment variables. Please add it and retry.",
30 | "like.extension": "Do you like this extension? Hope to get your kindly favorable comments.",
31 | "like.ok": "Yes",
32 | "like.no": "No",
33 | "like.later": "Remind later",
34 | sftp_not_host: "You must config the ssh host.",
35 | sftp_not_port: "You must config the ssh port.",
36 | sftp_not_username: "You must config the ssh username.",
37 | sftp_not_password: "You must config a ssh password or private key file path.",
38 | sftp_connect_failed: "Connect to the ssh server failed: ",
39 | connection_string_empty: "The connection string is empty.",
40 | account_name_empty: "The account name is empty.",
41 | };
42 |
43 | let $: any = { 'zh-cn': zh_cn, 'zh-tw': zh_tw };
44 | export { $ };
--------------------------------------------------------------------------------
/src/i18n/locale.zh-cn.ts:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | config_failed: "配置失败了:",
4 | upload_failed: "上传文件失败了:",
5 | save_failed: "保存文件失败了:",
6 | something_wrong: "糟糕!出现一个我没想到的错误:",
7 | open_with_folder: "请使用打开文件夹的方式编辑文件。",
8 | replace_or_no: "这个文件已存在。要把它替换掉吗?",
9 | Yes: "可以",
10 | No: "不要",
11 | 'qiniu.east': "华东",
12 | 'qiniu.north': "华北",
13 | 'qiniu.south': "华南",
14 | 'qiniu.na': "北美",
15 | 'qiniu.sa': "东南亚",
16 | 'upyun.smart': "智能选路(推荐):v0.api.upyun.com",
17 | 'upyun.telecom': "电信线路:v1.api.upyun.com",
18 | 'upyun.unicom': "联通(网通)线路:v2.api.upyun.com",
19 | 'upyun.mobile': "移动(铁通)线路:v3.api.upyun.com",
20 | 'smms.token-miss': '请先设置 sm.ms token。',
21 | named_paste: "给你粘贴的图片起个名字(不需要包含后缀名,它将替换掉格式字符串中的 ${filename})。",
22 | uploading: "正在上传...",
23 | picture: "图",
24 | install_xclip: "您需要先安装 xclip 命令。",
25 | powershell_not_found: "Powershell 不在你的 PATH 环境变量中。请添加把他加进去之后重试。",
26 | "like.extension": "您喜欢这个扩展吗?给我一个好评吧!",
27 | "like.ok": "好啊",
28 | "like.no": "不要",
29 | "like.later": "等等再说",
30 | sftp_not_host: "您必须配置远程服务器地址。",
31 | sftp_not_port: "您必须配置 SSH 端口。",
32 | sftp_not_username: "您必须配置SSH 用户名。",
33 | sftp_not_password: "您必须配置 SSH 密码或私钥文件路径。",
34 | sftp_connect_failed: "连接到 SSH 服务器失败:",
35 | connection_string_empty: "连接字符串为空。",
36 | account_name_empty: "账户名为空。",
37 | };
--------------------------------------------------------------------------------
/src/i18n/locale.zh-tw.ts:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | config_failed: "配置失敗了:",
4 | upload_failed: "上傳文件失敗了:",
5 | save_failed: "保存文件失敗了:",
6 | something_wrong: "糟糕!出現一個我沒想到的錯誤:",
7 | open_with_folder: "請使用打開文件夾的方式編輯文件。",
8 | replace_or_no: "這個文件已存在。要把它替換掉嗎?",
9 | Yes: "可以",
10 | No: "不要",
11 | "qiniu.east": "華東",
12 | "qiniu.north": "華北",
13 | "qiniu.south": "華南",
14 | "qiniu.na": "北美",
15 | "qiniu.sa": "東南亞",
16 | 'upyun.smart': "智能選路(推薦):v0.api.upyun.com",
17 | 'upyun.telecom': "電信線路:v1.api.upyun.com",
18 | 'upyun.unicom': "聯通(網通)線路:v2.api.upyun.com",
19 | 'upyun.mobile': "移動(鐵通)線路:v3.api.upyun.com",
20 | 'smms.token-miss': '請先設置 sm.ms token。',
21 | named_paste: "給你粘貼的圖片起個名字(不需要包含後綴名,它將替換掉格式字符串中的 ${filename})。",
22 | uploading: "正在上傳...",
23 | picture: "圖",
24 | install_xclip: "您需要先安裝 xclip 命令。",
25 | powershell_not_found: "Powershell 不在你的 PATH 環境變量中。請添加把他加進去之後重試。",
26 | "like.extension": "您喜歡這個擴展嗎?給我一個好評吧!",
27 | "like.ok": "好啊",
28 | "like.no": "不要",
29 | "like.later": "等等再説",
30 | sftp_not_host: "您必須配置遠端伺服器地址。",
31 | sftp_not_port: "您必須配置 SSH 端口。",
32 | sftp_not_username: "您必須配置 SSH 賬戶。",
33 | sftp_not_password: "您必須配置 SSH 密碼或私鑰文件路徑。",
34 | sftp_connect_failed: "連接到 SSH 伺服器失敗:",
35 | connection_string_empty: "連接字符串為空。",
36 | account_name_empty: "賬戶名為空。",
37 | };
--------------------------------------------------------------------------------
/src/lib/azure.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { locale as $l } from './utils';
3 | import { BlobServiceClient } from "@azure/storage-blob";
4 | import { DefaultAzureCredential } from '@azure/identity';
5 |
6 | class Azure implements Upload
7 | {
8 | config: Config;
9 | lastTimestamp: number;
10 | constructor(config: Config) {
11 | this.config = config;
12 | this.lastTimestamp = 0;
13 | }
14 |
15 | async reconfig(config: Config) {
16 | this.config = config;
17 | this.lastTimestamp = 0;
18 | }
19 |
20 | async upload(filePath: string, savePath: string): Promise {
21 | try {
22 | const { authenticationMethod, connectionString, accountName, container } = this.config.azure;
23 | let authMethod = authenticationMethod;
24 | if (connectionString !== '' && accountName === '') {
25 | authMethod = 'connectionString';
26 | }
27 | else if (connectionString === '' && accountName !== '') {
28 | authMethod = 'Passwordless';
29 | }
30 | if (authMethod === 'connectionString' && connectionString === '') {
31 | vscode.window.showInformationMessage($l['connection_string_empty']);
32 | return null;
33 | }
34 | if (authMethod === 'Passwordless' && accountName === '') {
35 | vscode.window.showInformationMessage($l['account_name_empty']);
36 | return null;
37 | }
38 |
39 | let blobServiceClient: BlobServiceClient;
40 | if (authMethod === 'connectionString') {
41 | blobServiceClient = BlobServiceClient.fromConnectionString(
42 | connectionString
43 | );
44 | } else {
45 | blobServiceClient = new BlobServiceClient(
46 | `https://${accountName}.blob.core.windows.net`,
47 | new DefaultAzureCredential()
48 | );
49 | }
50 |
51 | const containerClient = blobServiceClient.getContainerClient(container);
52 | const blockBlobClient = containerClient.getBlockBlobClient(savePath);
53 | await blockBlobClient.uploadFile(filePath);
54 |
55 | return blockBlobClient.url;
56 | }
57 | catch(error) {
58 | let e = error as Error;
59 | vscode.window.showInformationMessage(`${$l['upload_failed']}${e.message}`);
60 | return null;
61 | }
62 | }
63 | }
64 |
65 | export default Azure;
--------------------------------------------------------------------------------
/src/lib/cloudflare.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import * as fs from 'fs';
3 | import fetch from 'node-fetch';
4 | import * as FormData from 'form-data';
5 | import { locale as $l } from './utils';
6 |
7 | const cloudflareUrl = (accountId: string) =>
8 | `https://api.cloudflare.com/client/v4/accounts/${accountId}/images/v1`;
9 |
10 | class Cloudflare implements Upload {
11 | config: Config;
12 | constructor(config: Config) {
13 | this.config = config;
14 | }
15 |
16 | async getSavePath(filePath: string) {
17 | return filePath;
18 | }
19 |
20 | async reconfig(config: Config) {
21 | this.config = config;
22 | }
23 |
24 | async upload(filePath: string, savePath: string): Promise {
25 | try {
26 | const result = await this.doUpload(filePath, savePath);
27 | if (result.success) {
28 | return result.result.variants[0];
29 | }
30 | throw new Error(result.errors[0].message);
31 | } catch (error) {
32 | let e = error as Error;
33 | vscode.window.showInformationMessage(
34 | `${$l['upload_failed']}${e.message}\n${e.stack}`
35 | );
36 | return null;
37 | }
38 | }
39 |
40 | async doUpload(filePath: string, savePath: string): Promise {
41 | const { accountId, apiToken } = this.config.cloudflare;
42 | const formData = new FormData();
43 | formData.append('file', fs.createReadStream(filePath), {
44 | filepath: savePath,
45 | });
46 | const response = await fetch(cloudflareUrl(accountId), {
47 | method: 'POST',
48 | body: formData,
49 | headers: {
50 | authorization: `Bearer ${apiToken}`,
51 | },
52 | });
53 | return response.json();
54 | }
55 | }
56 |
57 | export default Cloudflare;
58 |
--------------------------------------------------------------------------------
/src/lib/cloudinary.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import * as path from "path";
3 | import utils, { locale as $l } from "./utils";
4 | import { UploadApiResponse, v2 as cloudinary } from "cloudinary";
5 |
6 | class Cloudinary implements Upload {
7 | config: Config;
8 | constructor(config: Config) {
9 | this.config = config;
10 | }
11 |
12 | async getSavePath(filePath: string) {
13 | return filePath;
14 | }
15 |
16 | async reconfig(config: Config) {
17 | this.config = config;
18 | }
19 |
20 | async upload(filePath: string, savePath: string): Promise {
21 | try {
22 | let result = await this.doUpload(filePath, savePath, {
23 | overwrite: false,
24 | });
25 | if (result.existing) {
26 | const replace = await utils.confirm($l["replace_or_no"], [
27 | $l["Yes"],
28 | $l["No"],
29 | ]);
30 | if (replace === $l["No"]) {
31 | return null;
32 | }
33 | // yes, so re-upload with overwrite: true
34 | result = await this.doUpload(filePath, savePath, { overwrite: true });
35 | }
36 | return result.secure_url;
37 | } catch (error) {
38 | let e = error as Error;
39 | vscode.window.showInformationMessage(
40 | `${$l["upload_failed"]}${e.message}`,
41 | );
42 | return null;
43 | }
44 | }
45 |
46 | async doUpload(
47 | filePath: string,
48 | savePath: string,
49 | options: { overwrite: boolean },
50 | ): Promise {
51 | cloudinary.config({
52 | cloud_name: this.config.cloudinary.cloudName,
53 | api_key: this.config.cloudinary.apiKey,
54 | api_secret: this.config.cloudinary.apiSecret,
55 | });
56 |
57 | const folder = path.join(
58 | this.config.cloudinary.folder,
59 | path.dirname(savePath),
60 | );
61 | const filename = path.basename(savePath);
62 |
63 | return cloudinary.uploader.upload(filePath, {
64 | folder: folder,
65 | filename_override: filename,
66 | use_filename: true,
67 | unique_filename: false,
68 | overwrite: options.overwrite,
69 | fetch_format: "auto",
70 | quality: "auto",
71 | });
72 | }
73 | }
74 |
75 | export default Cloudinary;
76 |
--------------------------------------------------------------------------------
/src/lib/coding.ts:
--------------------------------------------------------------------------------
1 | import utils from './utils';
2 | import { locale as $l } from './utils';
3 | import * as vscode from 'vscode';
4 | import { Coding as CodingPicbed } from 'coding-picbed';
5 |
6 | class Coding implements Upload
7 | {
8 | config: Config;
9 | static coding: any;
10 | constructor(config: Config) {
11 | if (!Coding.coding) { Coding.coding = new CodingPicbed(); }
12 | if(!Coding.coding.lastconfig ||
13 | Coding.coding.lastconfig.token !== config.coding.token ||
14 | Coding.coding.lastconfig.repository !== config.coding.repository
15 | ) {
16 | this.reconfig(config);
17 | }
18 | this.config = config;
19 | }
20 |
21 | async reconfig(config: Config) {
22 | try {
23 | this.config = config;
24 | Coding.coding.lastconfig = config.coding;
25 | await Coding.coding.config({ token: config.coding.token, repository: config.coding.repository });
26 | } catch (error) {
27 | let e = error as Error;
28 | vscode.window.showErrorMessage(`${$l['config_failed']}${e.message}`);
29 | }
30 | }
31 |
32 | async upload(filePath: string, savePath: string): Promise {
33 | try {
34 | while (!Coding.coding.isInitialized()) { await utils.sleep(100); }
35 |
36 | let saveFolder = this.config.coding.path;
37 | let data = await Coding.coding.upload(filePath, saveFolder.replace(/\\/g, '/'), savePath.replace(/\\/g, '/'));
38 |
39 | return data.urls[0].replace('http:', 'https:');
40 | } catch (error) {
41 | let e = error as Error;
42 | vscode.window.showInformationMessage(`${$l['upload_failed']}${e.message}`);
43 | return null;
44 | }
45 | }
46 | }
47 |
48 | export default Coding;
--------------------------------------------------------------------------------
/src/lib/dataurl.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as path from 'path';
3 | import * as vscode from 'vscode';
4 | import { locale as $l } from './utils';
5 |
6 | class Local implements Upload
7 | {
8 | config: Config;
9 | constructor(config: Config) {
10 | this.config = config;
11 | }
12 |
13 | async reconfig(config: Config) {
14 | this.config = config;
15 | }
16 |
17 | async upload(filePath: string, savePath: string): Promise {
18 | try {
19 | return 'data:image/' + path.extname(filePath).substr(1) + ';base64,' + Buffer.from(fs.readFileSync(filePath)).toString('base64');
20 | }
21 | catch(error) {
22 | let e = error as Error;
23 | vscode.window.showInformationMessage(`${$l['save_failed']}${e.message}`);
24 | return null;
25 | }
26 | }
27 | }
28 |
29 | export default Local;
--------------------------------------------------------------------------------
/src/lib/define.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import utils from './utils';
3 | import { locale as $l } from './utils';
4 |
5 | class Define implements Upload
6 | {
7 | config: Config;
8 | constructor(config: Config) {
9 | this.config = config;
10 | }
11 |
12 | async reconfig(config: Config) {
13 | this.config = config;
14 | }
15 |
16 | async upload(filePath: string, savePath: string): Promise {
17 | try {
18 | delete require.cache[this.config.DIY.path];
19 | let define = require(this.config.DIY.path);
20 | return await define(filePath, savePath, utils.getCurrentFilePath());
21 | }
22 | catch(error) {
23 | let e = error as Error
24 | vscode.window.showInformationMessage(`${$l['upload_failed']}${e.message}`);
25 | return null;
26 | }
27 | }
28 | }
29 |
30 | export default Define;
--------------------------------------------------------------------------------
/src/lib/github.d.ts:
--------------------------------------------------------------------------------
1 | export function GitHubPic(options: any): any
2 |
--------------------------------------------------------------------------------
/src/lib/github.ts:
--------------------------------------------------------------------------------
1 | import utils from './utils';
2 | import { locale as $l } from './utils';
3 | import * as path from 'path';
4 | import * as vscode from 'vscode';
5 | import * as GitHubPic from 'github-picbed';
6 |
7 | class GitHub implements Upload
8 | {
9 | config: Config;
10 | static github: any;
11 | constructor(config: Config) {
12 | if (!GitHub.github) { GitHub.github = GitHubPic(config.github); }
13 | if(!GitHub.github.lastconfig ||
14 | GitHub.github.lastconfig.token !== config.github.token ||
15 | GitHub.github.lastconfig.branch !== config.github.branch ||
16 | GitHub.github.lastconfig.repository !== config.github.repository
17 | ) {
18 | this.reconfig(config);
19 | }
20 | this.config = config;
21 | }
22 |
23 | async reconfig(config: Config) {
24 | try {
25 | this.config = config;
26 | GitHub.github.lastconfig = config.github;
27 | await GitHub.github.config(config.github);
28 | } catch (error) {
29 | let e = error as Error;
30 | vscode.window.showErrorMessage(`${$l['config_failed']}${e.message}`);
31 | }
32 | }
33 |
34 | async upload(filePath: string, savePath: string): Promise {
35 | try {
36 | while (!GitHub.github.isInitialized()) { await utils.sleep(100); }
37 |
38 | savePath = path.join(this.config.github.path, savePath).replace(/\\/g, '/').replace(/^\/|\/$/, '');
39 | let data = await GitHub.github.upload({ data: filePath, filename: savePath });
40 | let options = GitHub.github.options;
41 |
42 | return this.config.github.cdn
43 | .replace(/\${username}/g, options.username)
44 | .replace(/\${repository}/g, options.repository)
45 | .replace(/\${branch}/g, options.branch)
46 | .replace(/\${filepath}/g, data.filename)
47 |
48 | } catch (error) {
49 | let e = error as Error;
50 | vscode.window.showErrorMessage(`${$l['upload_failed']}${e.message}`);
51 | return null;
52 | }
53 | }
54 | }
55 |
56 | export default GitHub;
--------------------------------------------------------------------------------
/src/lib/imgur.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import * as HttpsProxyAgent from 'https-proxy-agent';
3 | import got from 'got';
4 | import * as formData from 'form-data';
5 | import * as fs from 'fs';
6 | import * as path from 'path';
7 | import { locale as $l } from './utils';
8 |
9 | class Imgur implements Upload
10 | {
11 | config: Config;
12 | constructor(config: Config) {
13 | this.config = config;
14 | }
15 |
16 | async getSavePath(filePath: string) {
17 | return filePath;
18 | }
19 |
20 | async reconfig(config: Config) {
21 | this.config = config;
22 | }
23 |
24 | async upload(filePath: string, savePath: string): Promise {
25 | try {
26 | let tunnel = this.config.imgur.httpProxy ? new HttpsProxyAgent(this.config.imgur.httpProxy) : undefined;
27 |
28 | const form = new formData();
29 | form.append('name', path.basename(filePath));
30 | form.append('type', 'file');
31 | form.append('image', fs.createReadStream(filePath));
32 | let rsp = await got.post('https://api.imgur.com/3/image', {
33 | headers: {
34 | 'Authorization': `Client-ID ${this.config.imgur.clientId}`
35 | },
36 | body: form,
37 | agent: tunnel
38 | });
39 |
40 | return JSON.parse(rsp.body).data.link;
41 | }
42 | catch(error) {
43 | let e = error as Error;
44 | vscode.window.showInformationMessage(`${$l['upload_failed']}${e.message}`);
45 | return null;
46 | }
47 | }
48 | }
49 |
50 | export default Imgur;
--------------------------------------------------------------------------------
/src/lib/local.ts:
--------------------------------------------------------------------------------
1 | import utils from './utils';
2 | import * as fs from 'fs';
3 | import * as path from 'path';
4 | import * as vscode from 'vscode';
5 | import { locale as $l } from './utils';
6 |
7 | class Local implements Upload
8 | {
9 | config: Config;
10 | constructor(config: Config) {
11 | this.config = config;
12 | }
13 |
14 | async reconfig(config: Config) {
15 | this.config = config;
16 | }
17 |
18 | async upload(filePath: string, savePath: string): Promise {
19 | try {
20 | if (!utils.getCurrentRoot()) {
21 | vscode.window.showInformationMessage($l['open_with_folder']);
22 | return null;
23 | }
24 |
25 | let saveFolder = this.config.local.path.startsWith('/') ?
26 | path.join(utils.getCurrentRoot(), this.config.local.path) :
27 | path.resolve(path.dirname(utils.getCurrentFilePath()), this.config.local.path);
28 |
29 | console.debug(`Create Project Upload Folder.`);
30 |
31 | savePath = path.resolve(saveFolder, savePath);
32 | saveFolder = path.dirname(savePath);
33 |
34 | if (!fs.existsSync(saveFolder)) {
35 | utils.mkdirs(saveFolder);
36 | }
37 |
38 | if (fs.existsSync(savePath) &&
39 | (await utils.confirm($l['replace_or_no'], [$l['Yes'], $l['No']])) === $l['No']) {
40 | return savePath;
41 | }
42 | fs.copyFileSync(filePath, savePath);
43 |
44 | if(this.config.local.referencePath === '') {
45 | return path.relative(path.dirname(utils.getCurrentFilePath()), savePath).replace(/\\/g, '/');
46 | }
47 |
48 | return await utils.formatName(this.config.local.referencePath, savePath, false)
49 | .then(data => data + path.basename(savePath))
50 | }
51 | catch(error) {
52 | let e = error as Error;
53 | vscode.window.showInformationMessage(`${$l['save_failed']}${e.message}`);
54 | return null;
55 | }
56 | }
57 | }
58 |
59 | export default Local;
--------------------------------------------------------------------------------
/src/lib/qiniu.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import * as fs from 'fs';
3 | import * as path from 'path';
4 | import * as url from 'url';
5 | import utils from './utils';
6 | import { locale as $l } from './utils';
7 | import * as qiniu from 'qiniu';
8 |
9 | class Qiniu implements Upload
10 | {
11 | config: Config;
12 | token: string;
13 | lastTimestamp: number;
14 | constructor(config: Config) {
15 | this.config = config;
16 | this.token = '';
17 | this.lastTimestamp = 0;
18 | }
19 |
20 | async reconfig(config: Config) {
21 | this.config = config;
22 | this.token = '';
23 | this.lastTimestamp = 0;
24 | }
25 |
26 | async upload(filePath: string, savePath: string): Promise {
27 | try {
28 | let key = savePath.replace(/\\/g, '/') || (utils.hash(fs.readFileSync(filePath)) + path.extname(filePath));
29 | let token = this.getToken(key);
30 | let config: qiniu.conf.ConfigOptions = new qiniu.conf.Config();
31 | switch(this.config.qiniu.zone) {
32 | case $l['qiniu.east']: config.zone = qiniu.zone.Zone_z0; break;
33 | case $l['qiniu.north']: config.zone = qiniu.zone.Zone_z1; break;
34 | case $l['qiniu.south']: config.zone = qiniu.zone.Zone_z2; break;
35 | case $l['qiniu.na']: config.zone = qiniu.zone.Zone_na0; break;
36 | case $l['qiniu.sa']: config.zone = qiniu.zone.Zone_as0; break;
37 | }
38 |
39 | let formUploader = new qiniu.form_up.FormUploader(config);
40 | let putExtra = new qiniu.form_up.PutExtra();
41 |
42 | let upload = () : Promise => {
43 | return new Promise((resolve, reject) => {
44 | formUploader.putFile(token, key, filePath, putExtra, (respErr, respBody, respInfo) => {
45 | if (respErr) {
46 | reject(respErr);
47 | }
48 | if (respInfo.statusCode === 200) {
49 | console.debug(respBody);
50 | resolve(url.resolve(this.config.qiniu.domain, key));
51 | } else {
52 | console.debug(respInfo.statusCode);
53 | console.debug(respBody);
54 | reject(new Error(respBody.error));
55 | }
56 | });
57 | });
58 | };
59 |
60 | return await upload();
61 | }
62 | catch(error) {
63 | let e = error as Error;
64 | vscode.window.showInformationMessage(`${$l['upload_failed']}${e.message}`);
65 | return null;
66 | }
67 | }
68 |
69 | getToken(filename: string): string {
70 | let mac = new qiniu.auth.digest.Mac(this.config.qiniu.accessKey, this.config.qiniu.secretKey);
71 | let putPolicy = new qiniu.rs.PutPolicy({
72 | scope: this.config.qiniu.bucket + ':' + filename,
73 | });
74 | return putPolicy.uploadToken(mac);
75 | }
76 | }
77 |
78 | export default Qiniu;
--------------------------------------------------------------------------------
/src/lib/s3.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import * as mime from "mime";
3 | import { readFileSync } from "fs";
4 | import {
5 | S3Client,
6 | PutObjectCommand,
7 | GetObjectCommand,
8 | } from "@aws-sdk/client-s3";
9 | import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
10 |
11 | import { locale as $l } from "./utils";
12 |
13 | class S3 implements Upload {
14 | config: Config;
15 | s3Client: S3Client;
16 |
17 | constructor(config: Config) {
18 | this.config = config;
19 | const { endpoint, region, accessKeyId, secretAccessKey, config: cfg } = this.config.s3;
20 | this.s3Client = new S3Client({
21 | ...cfg,
22 | endpoint: endpoint,
23 | region: region,
24 | credentials: {
25 | accessKeyId: accessKeyId,
26 | secretAccessKey: secretAccessKey,
27 | },
28 | });
29 | }
30 |
31 | async getSavePath(filePath: string) {
32 | return filePath;
33 | }
34 |
35 | async reconfig(config: Config) {
36 | this.config = config;
37 | }
38 |
39 | sanitizeURL = (url: string): string => {
40 | /*
41 | * preSignedURLs generated by @aws-sdk are for sharing resources only for a limited time
42 | * but we are going to get the permanent URL by removing the authorization information
43 | * from the URL. This will give us the direct link to the image in S3 bucket.
44 | * Another way would be concatenating the bucket name with region, host and
45 | * image key but it is not reliable if we are going to support multiple S3
46 | * compatible providers.
47 | */
48 | const decodedURL = decodeURIComponent(url);
49 | const urlObject = new URL(decodedURL);
50 | urlObject.search = "";
51 | urlObject.hash = "";
52 | return urlObject.toString();
53 | };
54 |
55 | async upload(filePath: string, savePath: string): Promise {
56 | try {
57 | await this.doUpload(filePath, savePath);
58 |
59 | const { bucketName } = this.config.s3;
60 | const getObjectCommand = new GetObjectCommand({
61 | Bucket: bucketName,
62 | Key: savePath,
63 | });
64 | const encodedURL = await getSignedUrl(this.s3Client, getObjectCommand);
65 |
66 | if (!this.config.s3.cdn) {
67 | return this.sanitizeURL(encodedURL);
68 | }
69 |
70 | const decodedURL = decodeURIComponent(encodedURL);
71 | const urlObject = new URL(decodedURL);
72 |
73 | return this.config.s3.cdn
74 | .replace(/\${bucket}/g, this.config.s3.bucketName)
75 | .replace(/\${region}/g, this.config.s3.region)
76 | .replace(/\${pathname}/g, urlObject.pathname)
77 | .replace(/\${filepath}/g, savePath);
78 | } catch (error) {
79 | let e = error as Error;
80 | vscode.window.showInformationMessage(
81 | `${$l["upload_failed"]}${e.message}\n${e.stack}`
82 | );
83 | return null;
84 | }
85 | }
86 |
87 | doUpload(filePath: string, savePath: string): Promise {
88 | const body = readFileSync(filePath);
89 |
90 | const { bucketName } = this.config.s3;
91 | const putObjectCommand = new PutObjectCommand({
92 | Bucket: bucketName,
93 | Body: body,
94 | Key: savePath,
95 | ContentType: mime.getType(filePath) || "application/octet-stream",
96 | });
97 |
98 | return this.s3Client.send(putObjectCommand);
99 | }
100 | }
101 |
102 | export default S3;
103 |
--------------------------------------------------------------------------------
/src/lib/sftp.ts:
--------------------------------------------------------------------------------
1 | import utils from './utils';
2 | import * as fs from 'fs';
3 | import * as path from 'path';
4 | import * as vscode from 'vscode';
5 | import { locale as $l } from './utils';
6 | import * as SFTP from 'ssh2-sftp-client';
7 |
8 | class Sftp implements Upload {
9 | config: Config;
10 | constructor(config: Config) {
11 | this.config = config;
12 | }
13 |
14 | async reconfig (config: Config) {
15 | this.config = config;
16 | }
17 |
18 | async upload (filePath: string, savePath: string): Promise {
19 | try {
20 | if (!utils.getCurrentRoot()) {
21 | vscode.window.showInformationMessage($l['open_with_folder']);
22 | return null;
23 | }
24 |
25 | if (!this.config.sftp.host) {
26 | vscode.window.showInformationMessage($l['sftp_not_host']);
27 | return null;
28 | }
29 |
30 | if (!this.config.sftp.port) {
31 | vscode.window.showInformationMessage($l['sftp_not_port']);
32 | return null;
33 | }
34 |
35 | if (!this.config.sftp.username) {
36 | vscode.window.showInformationMessage($l['sftp_not_username']);
37 | return null;
38 | }
39 |
40 | const sftpConfig: SFTP.ConnectOptions = {
41 | host: this.config.sftp.host,
42 | port: this.config.sftp.port,
43 | username: this.config.sftp.username,
44 | };
45 |
46 | if (this.config.sftp.password) {
47 | sftpConfig.password = this.config.sftp.password;
48 | } else if (fs.existsSync(this.config.sftp.privateKeyPath)) {
49 | sftpConfig.privateKey = fs.readFileSync(this.config.sftp.privateKeyPath).toString();
50 | } else {
51 | vscode.window.showInformationMessage($l['sftp_not_password']);
52 | return null;
53 | }
54 |
55 | const sftp = new SFTP();
56 |
57 | try {
58 | await sftp.connect(sftpConfig)
59 | } catch (error) {
60 | let e = error as Error;
61 | vscode.window.showErrorMessage(`${$l['sftp_connect_failed']}${e.message}`);
62 | return null;
63 | }
64 |
65 | let saveFolder = this.config.sftp.path.startsWith('/') ?
66 | path.join(utils.getCurrentRoot(), this.config.sftp.path) :
67 | path.join(path.dirname(utils.getCurrentFilePath()), this.config.sftp.path);
68 |
69 | savePath = path.join(saveFolder, savePath).replace(/\\/g, '/');
70 | saveFolder = path.dirname(savePath);
71 |
72 | if (!await sftp.exists(saveFolder)) {
73 | await sftp.mkdir(saveFolder);
74 | }
75 |
76 | if (await sftp.exists(savePath) &&
77 | (await utils.confirm($l['replace_or_no'], [$l['Yes'], $l['No']])) === $l['No']) {
78 | return savePath;
79 | }
80 | await sftp.put(fs.createReadStream(filePath), savePath);
81 |
82 | sftp.end();
83 |
84 | if (this.config.local.referencePath === '') {
85 | return path.relative(path.dirname(utils.getCurrentFilePath()), savePath).replace(/\\/g, '/');
86 | }
87 |
88 | return await utils.formatName(this.config.local.referencePath, savePath, false)
89 | .then(data => data + path.basename(savePath))
90 | }
91 | catch (error) {
92 | let e = error as Error;
93 | vscode.window.showInformationMessage(`${$l['save_failed']}${e.message}`);
94 | return null;
95 | }
96 | }
97 | }
98 |
99 | export default Sftp;
--------------------------------------------------------------------------------
/src/lib/sm.ms.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import got from 'got';
3 | import * as formData from 'form-data';
4 | import * as fs from 'fs';
5 | import { locale as $l } from './utils';
6 |
7 | class Imgur implements Upload
8 | {
9 | config: Config;
10 | constructor(config: Config) {
11 | this.config = config;
12 | }
13 |
14 | async getSavePath(filePath: string) {
15 | return filePath;
16 | }
17 |
18 | async reconfig(config: Config) {
19 | this.config = config;
20 | }
21 |
22 | async upload(filePath: string, savePath: string): Promise {
23 | try {
24 | const form = new formData();
25 | form.append('smfile', fs.createReadStream(filePath));
26 | if(!this.config.sm_ms.token) {
27 | vscode.window.showInformationMessage(`${$l['smms.token-miss']}`);
28 | return null;
29 | }
30 | let headers = {
31 | Authorization: `Basic ${this.config.sm_ms.token}`
32 | };
33 | let rsp = await got.post('https://smms.app/api/v2/upload', {
34 | body: form,
35 | headers
36 | });
37 |
38 | let body = JSON.parse(rsp.body);
39 | return body.images || body.data.url;
40 | }
41 | catch(error) {
42 | let e = error as Error;
43 | vscode.window.showInformationMessage(`${$l['upload_failed']}${e.message}`);
44 | return null;
45 | }
46 | }
47 | }
48 |
49 | export default Imgur;
--------------------------------------------------------------------------------
/src/lib/upload.d.ts:
--------------------------------------------------------------------------------
1 | interface Upload
2 | {
3 | config: Config;
4 | upload(filePath: string, savePath: string): Promise;
5 | reconfig(config: Config): Promise;
6 | }
--------------------------------------------------------------------------------
/src/lib/uploads.ts:
--------------------------------------------------------------------------------
1 | import Local from './local';
2 | import Coding from './coding';
3 | import GitHub from './github';
4 | import Imgur from './imgur';
5 | import SM_MS from './sm.ms';
6 | import Qiniu from './qiniu';
7 | import Upyun from './upyun';
8 | import DataUrl from './dataurl';
9 | import Define from './define';
10 | import Cloudinary from './cloudinary';
11 | import Cloudflare from './cloudflare';
12 | import S3 from './s3';
13 | import Sftp from './sftp';
14 | import Azure from './azure';
15 |
16 | export default {
17 | Local, Coding, GitHub, Imgur, SM_MS, Qiniu, DataUrl, Define, Cloudinary, Cloudflare, Upyun, S3, Sftp, Azure
18 | };
--------------------------------------------------------------------------------
/src/lib/upyun.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import * as path from 'path';
3 | import { locale as $l } from './utils';
4 | import * as fs from 'fs';
5 | import * as url from 'url';
6 | const upyun = require('upyun');
7 |
8 | class Upyun implements Upload
9 | {
10 | config: Config;
11 | client: any;
12 | lastTimestamp: number;
13 | constructor(config: Config) {
14 | this.config = config;
15 | this.client = null;
16 | this.lastTimestamp = 0;
17 | }
18 |
19 | async reconfig(config: Config) {
20 | this.config = config;
21 | this.client = null;
22 | this.lastTimestamp = 0;
23 | }
24 |
25 | async upload(filePath: string, savePath: string): Promise {
26 | try{
27 | savePath = path.join(this.config.upyun.path, savePath).replace(/\\/g, '/').replace(/^\/|\/$/, '');
28 | let link = "v0.api.upyun.com";
29 | switch(this.config.upyun.link) {
30 | case $l['upyun.smart']: link = "v0.api.upyun.com"; break;
31 | case $l['upyun.telecom']: link = "v1.api.upyun.com"; break;
32 | case $l['upyun.unicom']: link = "v2.api.upyun.com"; break;
33 | case $l['upyun.mobile']: link = "v3.api.upyun.com"; break;
34 | }
35 | let client = this.getClient(
36 | this.config.upyun.bucket,
37 | this.config.upyun.operator,
38 | this.config.upyun.password,
39 | link);
40 | let file = fs.readFileSync(filePath)
41 |
42 | let upload = () : Promise => {
43 | return new Promise((resolve, reject) => {
44 | client.putFile(savePath, file).then( (res: any) => {
45 | console.debug(res);
46 | if(res){
47 | resolve(url.resolve(this.config.upyun.domain, savePath));
48 | }else{
49 | reject();
50 | }
51 | });
52 | });
53 | };
54 | return await upload();
55 | }catch(error) {
56 | let e = error as Error;
57 | vscode.window.showInformationMessage(`${$l['upload_failed']}${e.message}`);
58 | return null;
59 | }
60 | }
61 |
62 |
63 | getClient(bucket: string, operator: string, password: string, domain: string){
64 | var options = {
65 | domain: domain
66 | };
67 | return new upyun.Client(new upyun.Service(bucket, operator, password),options);
68 | }
69 |
70 |
71 | }
72 |
73 | export default Upyun;
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { spawn, exec } from 'child_process';
3 | import { tmpdir } from 'os';
4 | import * as fs from 'fs';
5 | import * as path from 'path';
6 | import * as packages from '../../package.json';
7 | import * as crypto from 'crypto';
8 | import Uploads from './uploads';
9 | import i18n from '../i18n/index';
10 | import * as TurndownService from 'turndown';
11 | import { gfm } from 'turndown-plugin-gfm';
12 | import * as pngToJpeg from 'png-to-jpeg';
13 |
14 | let pkg = packages as any;
15 | let locale = i18n();
16 |
17 | function getUpload (uploadMethod: string, config: any): Upload | null {
18 | switch (uploadMethod) {
19 | case 'Local': return new Uploads.Local(config);
20 | case 'Coding': return new Uploads.Coding(config);
21 | case 'GitHub': return new Uploads.GitHub(config);
22 | case 'Imgur': return new Uploads.Imgur(config);
23 | case 'SM.MS': return new Uploads.SM_MS(config);
24 | case 'Data URL': return new Uploads.DataUrl(config);
25 | case 'Qiniu': return new Uploads.Qiniu(config);
26 | case 'DIY': return new Uploads.Define(config);
27 | case '本地': return new Uploads.Local(config);
28 | case '七牛': return new Uploads.Qiniu(config);
29 | case 'Upyun': return new Uploads.Upyun(config);
30 | case '又拍云': return new Uploads.Upyun(config);
31 | case '又拍雲': return new Uploads.Upyun(config);
32 | case '自定义': return new Uploads.Define(config);
33 | case '自定義': return new Uploads.Define(config);
34 | case 'Cloudinary': return new Uploads.Cloudinary(config);
35 | case 'Cloudflare': return new Uploads.Cloudflare(config);
36 | case 'S3': return new Uploads.S3(config);
37 | case 'SFTP': return new Uploads.Sftp(config);
38 | case 'Azure Storage': return new Uploads.Azure(config);
39 | }
40 | return null;
41 | }
42 |
43 | function showProgress (message: string) {
44 | let show = true;
45 | function stopProgress () {
46 | show = false;
47 | }
48 |
49 | vscode.window.withProgress({
50 | location: vscode.ProgressLocation.Window,
51 | title: message,
52 | cancellable: false
53 | }, (progress, token) => {
54 | return new Promise(resolve => {
55 | let timer = setInterval(() => {
56 | if (show) { return; }
57 | clearInterval(timer);
58 | resolve(show);
59 | }, 100);
60 | });
61 | });
62 |
63 | return stopProgress;
64 | }
65 |
66 | function editorEdit (selection: vscode.Selection | vscode.Position | undefined | null, text: string): Promise {
67 | return new Promise((resolve, reject) => {
68 | vscode.window.activeTextEditor?.edit(editBuilder => {
69 | if (selection) {
70 | editBuilder.replace(selection, text);
71 | }
72 | }).then(resolve);
73 | });
74 | }
75 |
76 | function insertToEnd (text: string): Promise {
77 | return new Promise((resolve, reject) => {
78 | let linenumber = vscode.window.activeTextEditor?.document.lineCount || 1;
79 | let pos = vscode.window.activeTextEditor?.document.lineAt(linenumber - 1).range.end || new vscode.Position(0, 0);
80 | vscode.window.activeTextEditor?.edit(editBuilder => {
81 | editBuilder.insert(pos, text);
82 | }).then(resolve);
83 | });
84 | }
85 |
86 | function getSelections (): vscode.Selection[] | null {
87 | let editor = vscode.window.activeTextEditor;
88 | if (!editor) {
89 | return null; // No open text editor
90 | }
91 |
92 | let selections = editor.selections;
93 | return selections;
94 | }
95 |
96 | async function formatCode(filePath: string, selection: string, maxWidth: number, codeType: string, format: string) {
97 | switch (codeType) {
98 | case "Markdown":
99 | return ` \n`;
100 |
101 | case "DIY":
102 | let code = await formatName(format, filePath, false);
103 | code = code.replaceAll('${src}', filePath);
104 | code = code.replaceAll('${alt}', selection);
105 | return code;
106 |
107 | default:
108 | return `
0 ? `width="${maxWidth}" ` : ''}/> \n`;
109 | }
110 | }
111 |
112 | async function variableGetter (variable: string,
113 | { filePath, isPaste, match, context, saveName }:
114 | {
115 | filePath: string,
116 | isPaste: boolean,
117 | match?: string,
118 | context?: vscode.ExtensionContext,
119 | saveName?: string;
120 | }): Promise {
121 | switch (variable) {
122 | case 'filename': {
123 | return !isPaste ? path.basename(filePath, path.extname(filePath)) :
124 | (path.basename((await prompt(locale['named_paste'], path.basename(filePath, '.png'))) || '') || '');
125 | }
126 | case 'mdname': {
127 | return path.basename(getCurrentFilePath(), path.extname(getCurrentFilePath()));
128 | }
129 | case 'path': {
130 | return path.dirname(getCurrentFilePath()).replace(getCurrentRoot(), '').slice(1).replace(/\\/g, '/');
131 | }
132 | case 'hash': {
133 | return hash(fs.readFileSync(filePath));
134 | }
135 | case 'timestramp': // spell mistake
136 | case 'timestamp': {
137 | return new Date().getTime().toString();
138 | }
139 | case 'YY': {
140 | return new Date().getFullYear().toString();
141 | }
142 | case 'MM': {
143 | return (new Date().getMonth() + 1).toString().padStart(2, '0');
144 | }
145 | case 'DD': {
146 | return (new Date().getDate()).toString().padStart(2, '0');
147 | }
148 | case 'hh': {
149 | let hours = new Date().getHours();
150 | return (hours > 12 ? hours - 12 : hours).toString().padStart(2, '0');
151 | }
152 | case 'HH': {
153 | return new Date().getHours().toString().padStart(2, '0');
154 | }
155 | case 'mm': {
156 | return new Date().getMinutes().toString().padStart(2, '0');
157 | }
158 | case 'ss': {
159 | return new Date().getSeconds().toString().padStart(2, '0');
160 | }
161 | case 'mss': {
162 | return new Date().getMilliseconds().toString().padStart(3, '0');
163 | }
164 | case 'rand,(\\d+)': {
165 | let numberMat = match!.match(/\d+/);
166 | if (!numberMat) { return ''; }
167 | let n = parseInt(numberMat[0]);
168 | return parseInt((Math.random() * n).toString()).toString();
169 | }
170 | case 'prompt': {
171 | let promptInput = await vscode.window.showInputBox({ prompt: `${locale['prompt_name_component']}${saveName || ''}` });
172 | // make sure that promptInput is a filename-safe string
173 | promptInput = promptInput?.replace(/[:*?<>|]/g, '').trim();
174 | if (promptInput)
175 | return promptInput;
176 | else
177 | throw Error(locale['user_did_not_answer_prompt']);
178 | }
179 | case 'index': {
180 | const fsPath = getCurrentFilePath();
181 | let lastIndex = parseInt(context!.globalState.get(fsPath) || '0')
182 | context!.globalState.update(fsPath, `${lastIndex + 1}`);
183 | return lastIndex.toString();
184 | }
185 | }
186 | return '';
187 | }
188 |
189 | async function formatName (format: string, filePath: string, isPaste: boolean): Promise {
190 | let saveName = format;
191 | let variables = [
192 | 'filename', 'mdname', 'path', 'hash', 'timestramp', 'timestamp', 'YY', 'MM', 'DD', 'HH', 'hh', 'mm', 'ss', 'mss', 'rand,(\\d+)', 'prompt'
193 | ];
194 | for (let i = 0; i < variables.length; i++) {
195 | let reg = new RegExp(`\\$\\{${variables[i]}\\}`, 'g');
196 | let mat = format.match(reg);
197 | if (!mat) { continue; }
198 | if (variables[i] === 'rand,(\\d+)') {
199 | for (let j = 0; j < mat.length; j++) {
200 | const data = await variableGetter(variables[i], { filePath, isPaste, match: mat[j] });
201 | if (!data) continue;
202 | saveName = saveName.replaceAll(mat[j], data);
203 | }
204 | } else {
205 | const data = await variableGetter(variables[i], { filePath, isPaste, saveName });
206 | if (!data) continue;
207 | saveName = saveName.replaceAll(mat[0], data);
208 | }
209 | }
210 |
211 | return saveName;
212 | }
213 |
214 | async function getAlt (format: string, filePath: string, context: vscode.ExtensionContext) {
215 | let alt = format;
216 | let variables = [
217 | 'filename', 'mdname', 'path', 'hash', 'timestamp', 'YY', 'MM', 'DD', 'HH', 'hh', 'mm', 'ss', 'mss', 'rand,(\\d+)', 'index', 'prompt'
218 | ];
219 |
220 | for (let i = 0; i < variables.length; i++) {
221 | let reg = new RegExp(`\\$\\{${variables[i]}\\}`, 'g');
222 | let mat = format.match(reg);
223 | if (!mat) { continue; }
224 | if (variables[i] === 'rand,(\\d+)') {
225 | for (let j = 0; j < mat.length; j++) {
226 | const data = await variableGetter(variables[i], { filePath, isPaste: false, match: mat[j] });
227 | if (!data) continue;
228 | alt = alt.replaceAll(mat[j], data);
229 | }
230 | } else {
231 | const data = await variableGetter(variables[i], { filePath, isPaste: false, context });
232 | if (!data) continue;
233 | alt = alt.replaceAll(mat[0], data);
234 | }
235 | }
236 | return alt;
237 | }
238 |
239 | function mkdirs (dirname: string) {
240 | //console.debug(dirname);
241 | if (fs.existsSync(dirname)) {
242 | return true;
243 | } else {
244 | if (mkdirs(path.dirname(dirname))) {
245 | fs.mkdirSync(dirname);
246 | return true;
247 | }
248 | }
249 | }
250 |
251 | function html2Markdown (data: string): string {
252 | let turndownService = new TurndownService({ codeBlockStyle: 'fenced', headingStyle: 'atx', });
253 | turndownService.use(gfm);
254 | return turndownService.turndown(data);
255 | }
256 |
257 | function getConfig () {
258 | const configurations: { properties: object, title: string }[] = pkg.contributes.configuration;
259 | const keys: string[] = configurations.reduce((acc, config) => acc.concat(Object.keys(config.properties)), [] as string[])
260 |
261 | let values: Config = {};
262 | function toVal (str: string, val: string | undefined, cfg: Config): string | Config {
263 | let keys = str.split('.');
264 | if (keys.length === 1) {
265 | cfg[keys[0]] = val;
266 | } else {
267 | cfg[keys[0]] = toVal(keys.slice(1).join('.'), val, cfg[keys[0]] || {});
268 | }
269 | return cfg;
270 | }
271 | keys.forEach(k => toVal(k.split('.').slice(1).join('.'), vscode.workspace.getConfiguration().get(k), values));
272 | return values;
273 | }
274 |
275 | function getPasteImage (imagePath: string): Promise {
276 | return new Promise((resolve, reject) => {
277 | if (!imagePath) { return; }
278 |
279 | let platform = process.platform;
280 | if (platform === 'win32') {
281 | // Windows
282 | const scriptPath = path.join(__dirname, '..', '..', 'asserts/pc.ps1');
283 |
284 | let command = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
285 | let powershellExisted = fs.existsSync(command);
286 | let output = '';
287 | if (!powershellExisted) {
288 | command = "powershell";
289 | }
290 |
291 | const powershell = spawn(command, [
292 | '-noprofile',
293 | '-noninteractive',
294 | '-nologo',
295 | '-sta',
296 | '-executionpolicy', 'unrestricted',
297 | '-windowstyle', 'hidden',
298 | '-file', scriptPath,
299 | imagePath
300 | ]);
301 | // the powershell can't auto exit in windows 7 .
302 | let timer = setTimeout(() => powershell.kill(), 8000);
303 |
304 | powershell.on('error', (e: any) => {
305 | if (e.code === 'ENOENT') {
306 | vscode.window.showErrorMessage(locale['powershell_not_found']);
307 | } else {
308 | vscode.window.showErrorMessage(e);
309 | }
310 | });
311 |
312 | powershell.on('exit', function (code, signal) {
313 | // console.debug('exit', code, signal);
314 | });
315 | powershell.stdout.on('data', (data) => {
316 | output += data.toString();
317 | clearTimeout(timer);
318 | timer = setTimeout(() => powershell.kill(), 2000);
319 | });
320 | powershell.on('close', (code) => {
321 | resolve(output.trim().split('\n').map(i => i.trim()));
322 | clearTimeout(timer);
323 | });
324 | }
325 | else if (platform === 'darwin') {
326 | // Mac
327 | let scriptPath = path.join(__dirname, '..', '..', 'asserts/mac.applescript');
328 |
329 | let ascript = spawn('osascript', [scriptPath, imagePath]);
330 | ascript.on('error', (e: any) => {
331 | vscode.window.showErrorMessage(e);
332 | });
333 | ascript.on('exit', (code, signal) => {
334 | // console.debug('exit', code, signal);
335 | });
336 | ascript.stdout.on('data', (data) => {
337 | resolve(data.toString().trim().split('\n'));
338 | });
339 | } else {
340 | // Linux
341 |
342 | let scriptPath = path.join(__dirname, '..', '..', 'asserts/linux.sh');
343 |
344 | let ascript = spawn('sh', [scriptPath, imagePath]);
345 | ascript.on('error', (e: any) => {
346 | vscode.window.showErrorMessage(e);
347 | });
348 | ascript.on('exit', (code, signal) => {
349 | // console.debug('exit',code,signal);
350 | });
351 | ascript.stdout.on('data', (data) => {
352 | let result = data.toString().trim();
353 | if (result === "no xclip") {
354 | vscode.window.showInformationMessage(locale['install_xclip']);
355 | return;
356 | }
357 | let match = decodeURI(result).trim().match(/((\/[^\/]+)+\/[^\/]*?\.(jpg|jpeg|gif|bmp|png))/g);
358 | resolve(match || []);
359 | });
360 | }
361 | });
362 | }
363 |
364 | function getRichText (): Promise {
365 | return new Promise((resolve, reject) => {
366 | let platform = process.platform;
367 | if (platform === 'win32') {
368 | // Windows
369 | const scriptPath = path.join(__dirname, '..', '..', 'asserts/rtf.ps1');
370 |
371 | let command = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
372 | let powershellExisted = fs.existsSync(command);
373 | let output = '';
374 | if (!powershellExisted) {
375 | command = "powershell";
376 | }
377 |
378 | const powershell = spawn(command, [
379 | '-noprofile',
380 | '-noninteractive',
381 | '-nologo',
382 | '-sta',
383 | '-executionpolicy', 'unrestricted',
384 | '-windowstyle', 'hidden',
385 | '-file', scriptPath,
386 | ]);
387 | // the powershell can't auto exit in windows 7 .
388 | let timer = setTimeout(() => powershell.kill(), 8000);
389 | let result = '';
390 | powershell.on('error', (e: any) => {
391 | if (e.code === 'ENOENT') {
392 | vscode.window.showErrorMessage(locale['powershell_not_found']);
393 | } else {
394 | vscode.window.showErrorMessage(e);
395 | }
396 | });
397 |
398 | powershell.on('exit', function (code, signal) {
399 | // console.debug('exit', code, signal);
400 | });
401 | powershell.stdout.on('data', (data) => {
402 | result += data.toString();
403 | clearTimeout(timer);
404 | timer = setTimeout(() => powershell.kill(), 8000);
405 | });
406 | powershell.on('close', (code) => {
407 | output = (result.match(/[\s\S]*<\/body>/g) || [''])[0];
408 | resolve(output.replace(/<\/*body>/g, '').trim());
409 | });
410 | }
411 | else if (platform === 'darwin') {
412 | // Mac
413 | vscode.window.showInformationMessage('Not support in macos yet.');
414 | resolve('');
415 | } else {
416 | // Linux
417 |
418 | let scriptPath = path.join(__dirname, '..', '..', 'asserts/rtf.sh');
419 | let result = '';
420 | let ascript = spawn('sh', [scriptPath]);
421 | ascript.on('error', (e: any) => {
422 | vscode.window.showErrorMessage(e);
423 | });
424 | ascript.on('exit', (code, signal) => {
425 | if (result === "no xclip") {
426 | vscode.window.showInformationMessage(locale['install_xclip']);
427 | return;
428 | }
429 | resolve(result);
430 | });
431 | ascript.stdout.on('data', (data) => {
432 | result += data.toString().trim();
433 | });
434 | }
435 | });
436 | }
437 |
438 | function getCurrentRoot (): string {
439 | const editor = vscode.window.activeTextEditor;
440 | if (!editor || !vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length < 1) { return ''; }
441 | const resource = editor.document.uri;
442 | if (resource.scheme == 'vscode-notebook-cell') {
443 | let filePath = resource.fsPath;
444 | let root = vscode.workspace.workspaceFolders.find(f => filePath.indexOf(f.uri.fsPath) >= 0);
445 | if (root) return root.uri.fsPath;
446 | else return '';
447 | }
448 | if (resource.scheme !== 'file' && resource.scheme !== 'vscode-remote') { return ''; }
449 | const folder = vscode.workspace.getWorkspaceFolder(resource);
450 | if (!folder) { return ''; }
451 | return folder.uri.fsPath;
452 | }
453 |
454 | function getCurrentFilePath (): string {
455 | const editor = vscode.window.activeTextEditor;
456 | if (!editor || !vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length < 1) { return ''; }
457 | return editor.document.uri.fsPath;
458 | }
459 |
460 | function getTmpFolder () {
461 | let savePath = path.join(tmpdir(), pkg.name);
462 | if (!fs.existsSync(savePath)) { fs.mkdirSync(savePath); }
463 | return savePath;
464 | }
465 |
466 | function convertImage (imagePath: string): Promise {
467 | return new Promise((resolve, reject) => {
468 | pngToJpeg({ quality: 90 })(fs.readFileSync(imagePath)).then((output: Buffer) => {
469 | const newImage = path.join(path.dirname(imagePath), path.basename(imagePath, path.extname(imagePath)) + '.jpg');
470 | fs.writeFileSync(newImage, output);
471 | resolve(newImage);
472 | fs.unlinkSync(imagePath);
473 | })
474 | });
475 | }
476 |
477 | function sleep (time: number) {
478 | return new Promise((resolve) => setTimeout(resolve, time));
479 | }
480 |
481 | function confirm (message: string, options: string[]): Promise {
482 | return new Promise((resolve, reject) => {
483 | return vscode.window.showInformationMessage(message, ...options).then(resolve);
484 | });
485 | }
486 |
487 | function prompt (message: string, defaultVal: string = ''): Promise {
488 | return new Promise((resolve, reject) => {
489 | return vscode.window.showInputBox({
490 | value: defaultVal,
491 | prompt: message
492 | }).then(resolve);
493 | });
494 | }
495 |
496 | function hash (buffer: Buffer): string {
497 | let sha256 = crypto.createHash('sha256');
498 | let hash = sha256.update(buffer).digest('hex');
499 | return hash;
500 | }
501 |
502 | function getOpenCmd (): string {
503 | let cmd = 'start';
504 | if (process.platform === 'win32') {
505 | cmd = 'start';
506 | } else if (process.platform === 'linux') {
507 | cmd = 'xdg-open';
508 | } else if (process.platform === 'darwin') {
509 | cmd = 'open';
510 | }
511 | return cmd;
512 | }
513 |
514 | function noticeComment (context: vscode.ExtensionContext) {
515 | let notice = context.globalState.get('notice');
516 | let usetimes: number = context.globalState.get('usetimes') || 0;
517 | if (!notice && usetimes > 100) {
518 | confirm(locale['like.extension'], [locale['like.ok'], locale['like.no'], locale['like.later']])
519 | .then((option) => {
520 | switch (option) {
521 | case locale['like.ok']:
522 | exec(`${getOpenCmd()} https://marketplace.visualstudio.com/items?itemName=hancel.markdown-image`);
523 | context.globalState.update('notice', true);
524 | break;
525 | case locale['like.no']:
526 | context.globalState.update('notice', true);
527 | break;
528 | case locale['like.later']:
529 | usetimes = 50;
530 | context.globalState.update('usetimes', usetimes);
531 | context.globalState.update('notice', false);
532 | break;
533 | }
534 | })
535 | .catch(e => console.debug(e));
536 | } else if (!notice) {
537 | context.globalState.update('usetimes', ++usetimes);
538 | }
539 | }
540 |
541 | export default {
542 | getUpload,
543 | showProgress,
544 | editorEdit,
545 | insertToEnd,
546 | formatCode,
547 | formatName,
548 | getAlt,
549 | mkdirs,
550 | html2Markdown,
551 | getConfig,
552 | getSelections,
553 | getPasteImage,
554 | getRichText,
555 | getCurrentRoot,
556 | getCurrentFilePath,
557 | getTmpFolder,
558 | convertImage,
559 | noticeComment,
560 | sleep,
561 | confirm,
562 | prompt,
563 | hash,
564 | };
565 | export { locale };
566 |
--------------------------------------------------------------------------------
/src/test/extension.test.ts:
--------------------------------------------------------------------------------
1 | //
2 | // Note: This example test is leveraging the Mocha test framework.
3 | // Please refer to their documentation on https://mochajs.org/ for help.
4 | //
5 |
6 | // The module 'assert' provides assertion methods from node
7 | import * as assert from 'assert';
8 |
9 | // You can import and use all API from the 'vscode' module
10 | // as well as import your extension to test it
11 | // import * as vscode from 'vscode';
12 | // import * as myExtension from '../extension';
13 |
14 | // Defines a Mocha test suite to group tests of similar kind together
15 | suite("Extension Tests", function () {
16 |
17 | // Defines a Mocha unit test
18 | test("Something 1", function() {
19 | assert.equal(-1, [1, 2, 3].indexOf(5));
20 | assert.equal(-1, [1, 2, 3].indexOf(0));
21 | });
22 | });
--------------------------------------------------------------------------------
/src/test/index.ts:
--------------------------------------------------------------------------------
1 | //
2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
3 | //
4 | // This file is providing the test runner to use when running extension tests.
5 | // By default the test runner in use is Mocha based.
6 | //
7 | // You can provide your own test runner if you want to override it by exporting
8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension
9 | // host can call to run the tests. The test runner is expected to use console.debug
10 | // to report the results back to the caller. When the tests are finished, return
11 | // a possible error to the callback or null if none.
12 |
13 | import * as testRunner from 'vscode/lib/testrunner';
14 |
15 | // You can directly control Mocha options by uncommenting the following lines
16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
17 | testRunner.configure({
18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
19 | useColors: true // colored output from test results
20 | });
21 |
22 | module.exports = testRunner;
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "out",
6 | "lib": [
7 | "es6",
8 | "dom"
9 | ],
10 | "sourceMap": true,
11 | "rootDir": "src",
12 | /* Strict Type-Checking Option */
13 | "strict": true, /* enable all strict type-checking options */
14 | /* Additional Checks */
15 | "noUnusedLocals": true /* Report errors on unused locals. */
16 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
17 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
18 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
19 | },
20 | "exclude": [
21 | "node_modules",
22 | ".vscode-test"
23 | ]
24 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-string-throw": true,
4 | "no-unused-expression": true,
5 | "no-duplicate-variable": true,
6 | "curly": true,
7 | "class-name": true,
8 | "semicolon": [
9 | true,
10 | "always"
11 | ],
12 | "triple-equals": true
13 | },
14 | "exclude": [
15 | "node_modules/**/*.ts"
16 | ],
17 | "defaultSeverity": "warning"
18 | }
--------------------------------------------------------------------------------
/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.json" {
2 | const value: any;
3 | export default value;
4 | }
5 |
6 | declare type Config = {
7 | [key: string]: any;
8 | }
9 |
10 | declare module 'coding-picbed' {
11 | const Coding: any;
12 | }
13 |
14 | declare module 'github-picbed';
15 |
16 | declare module 'turndown-plugin-gfm' {
17 | export function gfm(turndownService: any) : void;
18 | export function highlightedCodeBlock(turndownService: any) : void;
19 | export function strikethrough(turndownService: any) : void;
20 | export function tables(turndownService: any) : void;
21 | export function taskListItems(turndownService: any) : void;
22 | }
23 |
24 | declare module 'png-to-jpeg';
--------------------------------------------------------------------------------