diff --git a/apps/joplin-vscode-plugin/package.json b/apps/joplin-vscode-plugin/package.json
index 151b7c38..acd9d93b 100644
--- a/apps/joplin-vscode-plugin/package.json
+++ b/apps/joplin-vscode-plugin/package.json
@@ -41,15 +41,18 @@
"@types/koa__cors": "^3.3.0",
"@types/koa__router": "^12.0.0",
"@types/markdown-it": "^12.2.3",
+ "@types/mime-types": "^2.1.1",
"@types/node": "^16",
"@types/vscode": "^1.71.0",
"chokidar": "^3.5.3",
"cross-path-sort": "^1.0.0",
+ "envfile": "^6.18.0",
"fetch-blob": "^3.2.0",
"formdata-polyfill": "^4.0.10",
"joplin-api": "workspace:*",
"koa": "^2.13.4",
"markdown-it": "^13.0.1",
+ "mime-types": "^2.1.35",
"node-fetch": "^3.2.10",
"node-html-parser": "^6.1.1",
"tsup": "^6.2.3",
diff --git a/apps/joplin-vscode-plugin/src/service/JoplinNoteCommandService.ts b/apps/joplin-vscode-plugin/src/service/JoplinNoteCommandService.ts
index 630d42ae..ae1801ca 100644
--- a/apps/joplin-vscode-plugin/src/service/JoplinNoteCommandService.ts
+++ b/apps/joplin-vscode-plugin/src/service/JoplinNoteCommandService.ts
@@ -17,7 +17,7 @@ import { FolderOrNoteExtendsApi } from '../api/FolderOrNoteExtendsApi'
import { appConfig, AppConfig } from '../config/AppConfig'
import { JoplinNoteUtil } from '../util/JoplinNoteUtil'
import * as path from 'path'
-import { close, createReadStream, mkdirp, pathExists, readFile, remove, writeFile } from '@liuli-util/fs-extra'
+import { close, mkdirp, pathExists, readFile, remove, writeFile } from '@liuli-util/fs-extra'
import { createEmptyFile } from '../util/createEmptyFile'
import { UploadResourceUtil } from '../util/UploadResourceUtil'
import { uploadResourceService } from './UploadResourceService'
@@ -34,7 +34,6 @@ import { filenamify } from '../util/filenamify'
import { NoteProperties } from 'joplin-api'
import { logger } from '../constants/logger'
import { loadLastNoteList } from '../util/api'
-import { Blob } from 'buffer'
export class JoplinNoteCommandService {
private folderOrNoteExtendsApi = new FolderOrNoteExtendsApi()
@@ -89,11 +88,18 @@ export class JoplinNoteCommandService {
if (!id) {
return
}
- await resourceApi.update({
- id,
- // title: path.basename(filePath),
- data: new Blob([await readFile(filePath)]),
- })
+ try {
+ const data = await readFile(filePath)
+ const r = await resourceApi.update({
+ id,
+ // @ts-expect-error
+ data: new Blob([data]),
+ filename: path.basename(filePath),
+ })
+ console.log('resource update? ', r)
+ } catch (err) {
+ logger.error('update resource error: ' + err)
+ }
})
.on('error', (err) => {
logger.error('watch resource error: ' + err)
diff --git a/apps/joplin-vscode-plugin/src/util/UploadResourceUtil.ts b/apps/joplin-vscode-plugin/src/util/UploadResourceUtil.ts
index 6852f83f..31f13a57 100644
--- a/apps/joplin-vscode-plugin/src/util/UploadResourceUtil.ts
+++ b/apps/joplin-vscode-plugin/src/util/UploadResourceUtil.ts
@@ -4,7 +4,6 @@ import { existsSync, mkdirpSync, readFile } from '@liuli-util/fs-extra'
import { spawn } from 'child_process'
import { resourceApi } from 'joplin-api'
import { RootPath } from '../RootPath'
-import { Blob } from 'buffer'
/**
* for clipboard image
@@ -16,29 +15,20 @@ export interface IClipboardImage {
export class UploadResourceUtil {
static async uploadByPath(filePath: string, isImage: boolean) {
- const param = {
- title: path.basename(filePath),
+ const title = path.basename(filePath)
+ console.log('uploadFromExplorer begin: ', filePath, title)
+ const res = await resourceApi.create({
+ title,
+ // @ts-expect-error
data: new Blob([await readFile(filePath)]),
- }
- console.log('uploadFromExplorer begin: ', filePath, param.title)
- const res = await resourceApi.create(param)
- const markdownLink = `${isImage ? '!' : ''}[${param.title}](:/${res.id})`
+ filename: title,
+ })
+ console.log('uploadByPath', res)
+ const markdownLink = `${isImage ? '!' : ''}[${title}](:/${res.id})`
console.log('uploadFromExplorer end: ', markdownLink)
return { res, markdownLink }
}
- static async uploadFileByPath(filePath: string) {
- const param = {
- title: path.basename(filePath),
- data: new Blob([await readFile(filePath)]),
- }
- console.log('uploadFileFromExplorer begin: ', filePath, param.title)
- const res = await resourceApi.create(param)
- const markdownLink = `[${res.title}](:/${res.id})`
- console.log('uploadFileFromExplorer end: ', markdownLink)
- return { res, markdownLink }
- }
-
static getCurrentPlatform(): string {
const platform = process.platform
if (platform !== 'win32') {
diff --git a/apps/joplin-vscode-plugin/src/util/__tests__/UploadResourceUtil.test.ts b/apps/joplin-vscode-plugin/src/util/__tests__/UploadResourceUtil.test.ts
new file mode 100644
index 00000000..8c2b6ce8
--- /dev/null
+++ b/apps/joplin-vscode-plugin/src/util/__tests__/UploadResourceUtil.test.ts
@@ -0,0 +1,35 @@
+import { close, mkdirp, pathExists, readFile, remove } from '@liuli-util/fs-extra'
+import { config, resourceApi } from 'joplin-api'
+import path from 'path'
+import { beforeEach, expect, it } from 'vitest'
+import { findParent } from '../findParent'
+import { UploadResourceUtil } from '../UploadResourceUtil'
+import { parse } from 'envfile'
+import { createEmptyFile } from '../createEmptyFile'
+import '../node-polyfill'
+
+const tempPath = path.resolve(__dirname, '.temp', path.basename(__filename))
+beforeEach(async () => {
+ await remove(tempPath)
+ await mkdirp(tempPath)
+ const dirPath = await findParent(__dirname, (item) => pathExists(path.resolve(item, 'package.json')))
+ const envPath = path.resolve(dirPath!, '.env.local')
+ if (!(await pathExists(envPath))) {
+ throw new Error('请更新 .env.local 文件:' + envPath)
+ }
+ const env = await readFile(envPath, 'utf8')
+
+ config.token = parse(env).TOKEN!
+ config.baseUrl = 'http://127.0.0.1:27583'
+})
+
+it('test create by empty file', async () => {
+ const fsPath = path.resolve(__dirname, 'assets/test.km.svg')
+ const { res, markdownLink } = await UploadResourceUtil.uploadByPath(fsPath, true)
+ expect(res.mime).eq('image/svg+xml')
+ expect(markdownLink).eq(``)
+ const data = await readFile(fsPath)
+ const buffer = await resourceApi.fileByResourceId(res.id)
+ expect(data).deep.eq(buffer)
+ console.log(data.length)
+})
diff --git a/apps/joplin-vscode-plugin/src/util/__tests__/assets/test.km.svg b/apps/joplin-vscode-plugin/src/util/__tests__/assets/test.km.svg
new file mode 100644
index 00000000..ae970274
--- /dev/null
+++ b/apps/joplin-vscode-plugin/src/util/__tests__/assets/test.km.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/joplin-vscode-plugin/src/util/findParent.ts b/apps/joplin-vscode-plugin/src/util/findParent.ts
new file mode 100644
index 00000000..b92aabdb
--- /dev/null
+++ b/apps/joplin-vscode-plugin/src/util/findParent.ts
@@ -0,0 +1,27 @@
+import path from 'path'
+
+/**
+ * 向上查找目录
+ * @param cwd
+ * @param predicate
+ */
+export function findParent(cwd: string, predicate: (dir: string) => boolean): string | null
+export function findParent(cwd: string, predicate: (dir: string) => Promise): Promise
+export function findParent boolean | Promise, R extends string | null>(
+ cwd: string,
+ predicate: T,
+): ReturnType extends Promise ? Promise : R {
+ const res = predicate(cwd)
+ function f(res: boolean): string | null {
+ if (res) {
+ return cwd
+ }
+ const parent = path.dirname(cwd)
+ if (parent === cwd) {
+ return null
+ }
+ return findParent(parent, predicate as any)
+ }
+
+ return res instanceof Promise ? res.then(f) : (f(res) as any)
+}
diff --git a/apps/joplin-vscode-plugin/src/util/node-polyfill.ts b/apps/joplin-vscode-plugin/src/util/node-polyfill.ts
index 466847c1..ad083a79 100644
--- a/apps/joplin-vscode-plugin/src/util/node-polyfill.ts
+++ b/apps/joplin-vscode-plugin/src/util/node-polyfill.ts
@@ -1,10 +1,7 @@
-import nodeFetch from 'node-fetch'
+import fetch from 'node-fetch'
import { FormData } from 'formdata-polyfill/esm.min.js'
+import { Blob } from 'fetch-blob'
-// @ts-expect-errors
-if (typeof fetch === 'undefined') {
- Reflect.set(globalThis, 'fetch', nodeFetch)
-}
-if (typeof FormData === 'undefined') {
- Reflect.set(globalThis, 'FormData', FormData)
-}
+Reflect.set(globalThis, 'fetch', fetch)
+Reflect.set(globalThis, 'FormData', FormData)
+Reflect.set(globalThis, 'Blob', Blob)
diff --git a/libs/joplin-api/package.json b/libs/joplin-api/package.json
index 3276e100..0b4d4fe3 100644
--- a/libs/joplin-api/package.json
+++ b/libs/joplin-api/package.json
@@ -34,8 +34,12 @@
"devDependencies": {
"@liuli-util/eslint-config-ts": "^0.4.0",
"@types/fs-extra": "^9.0.13",
+ "@types/node": "^18.11.3",
"envfile": "^6.18.0",
+ "fetch-blob": "^3.2.0",
+ "formdata-polyfill": "^4.0.10",
"fs-extra": "^10.1.0",
+ "node-fetch": "^3.2.10",
"rimraf": "^3.0.2",
"ts-node": "^10.9.1",
"tslib": "^2.4.0",
diff --git a/libs/joplin-api/src/api/ResourceApi.ts b/libs/joplin-api/src/api/ResourceApi.ts
index 43821b05..19799d91 100644
--- a/libs/joplin-api/src/api/ResourceApi.ts
+++ b/libs/joplin-api/src/api/ResourceApi.ts
@@ -42,12 +42,13 @@ export class ResourceApi {
* The "data" field is required, while the "props" one is not. If not specified, default values will be used.
* @param param
*/
- async create(param: { data: Blob | BufferBlob } & Partial): Promise {
+ async create(param: { data: Blob | BufferBlob } & Partial): Promise {
const { data, ...others } = param
return (await this.ajax.postFormData('/resources', 'post', {
props: JSON.stringify(others),
- data: param.data,
- })) as ResourceGetRes
+ data: data,
+ filename: param.filename,
+ })) as ResourceProperties
}
async update(
@@ -57,6 +58,7 @@ export class ResourceApi {
return await this.ajax.postFormData(`/resources/${id}`, 'put', {
props: JSON.stringify(others),
data: data,
+ filename: param.filename,
})
}
diff --git a/libs/joplin-api/src/api/__tests__/ResourceApi.test.ts b/libs/joplin-api/src/api/__tests__/ResourceApi.test.ts
index 94bf391e..6892e809 100644
--- a/libs/joplin-api/src/api/__tests__/ResourceApi.test.ts
+++ b/libs/joplin-api/src/api/__tests__/ResourceApi.test.ts
@@ -1,6 +1,6 @@
import { expect, it, describe, beforeAll, afterAll, beforeEach } from 'vitest'
import { resourceApi } from '../..'
-import { mkdirp, pathExists, readFile, remove, stat, writeFile } from 'fs-extra'
+import { mkdirp, pathExists, readFile, remove, stat, writeFile, open, close } from 'fs-extra'
import { createTestResource } from './utils/CreateTestResource'
import path from 'path'
import { setupTestEnv } from '../../util/setupTestEnv'
@@ -40,12 +40,29 @@ it.skip('test get filename', async () => {
const resourcePath = path.resolve(__dirname, './assets/resourcesByFileId.png')
it('test create', async () => {
const title = 'image title'
- const json = await resourceApi.create({
+ const r = await resourceApi.create({
title,
data: new Blob([await readFile(resourcePath)]),
})
- console.log(json.id)
- expect(json.title).toBe(title)
+ console.log(r)
+ expect(r.title).toBe(title)
+})
+it.only('test create empty file', async () => {
+ const fsPath = path.resolve(tempPath, 'test.km.svg')
+ const handle = await open(fsPath, 'w')
+ try {
+ expect(await pathExists(fsPath)).toBeTruthy()
+ const r = await resourceApi.create({
+ title: path.basename(fsPath),
+ data: new Blob([await readFile(fsPath)]),
+ filename: path.basename(fsPath),
+ })
+ console.log(r)
+ expect(r.mime).eq('image/svg+xml')
+ expect(r.file_extension).eq('svg')
+ } finally {
+ await close(handle)
+ }
})
it('create by buffer', async () => {
const title = 'image title'
diff --git a/libs/joplin-api/src/api/__tests__/assets/test.km.svg b/libs/joplin-api/src/api/__tests__/assets/test.km.svg
new file mode 100644
index 00000000..3f078daf
--- /dev/null
+++ b/libs/joplin-api/src/api/__tests__/assets/test.km.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/libs/joplin-api/src/api/__tests__/fixs/ResourceApiCreateByPolyfill.test.ts b/libs/joplin-api/src/api/__tests__/fixs/ResourceApiCreateByPolyfill.test.ts
new file mode 100644
index 00000000..ef8c645b
--- /dev/null
+++ b/libs/joplin-api/src/api/__tests__/fixs/ResourceApiCreateByPolyfill.test.ts
@@ -0,0 +1,61 @@
+import { mkdirp, pathExists, readFile, remove, open, close, writeFile } from 'fs-extra'
+import path from 'path'
+import { beforeEach, expect, it } from 'vitest'
+import { resourceApi } from '../../JoplinApiGenerator'
+import fetch from 'node-fetch'
+import { FormData } from 'formdata-polyfill/esm.min.js'
+import { Blob } from 'fetch-blob'
+
+const tempPath = path.resolve(__dirname, '.temp')
+beforeEach(async () => {
+ await remove(tempPath)
+ await mkdirp(tempPath)
+ Reflect.set(globalThis, 'fetch', fetch)
+ Reflect.set(globalThis, 'FormData', FormData)
+ Reflect.set(globalThis, 'Blob', Blob)
+})
+
+it('create empty file and polyfill', async () => {
+ const emptyPath = path.resolve(tempPath, 'test.km.svg')
+ const handle = await open(emptyPath, 'w')
+ try {
+ expect(await pathExists(emptyPath)).toBeTruthy()
+ const r = await resourceApi.create({
+ title: path.basename(emptyPath),
+ data: new Blob([await readFile(emptyPath)]),
+ })
+ expect(r).not.undefined
+ } finally {
+ await close(handle)
+ }
+})
+
+it('create file and polyfill', async () => {
+ const fsPath = path.resolve(__dirname, '../assets/resourcesByFileId.png')
+ const data = await readFile(fsPath)
+ const r = await resourceApi.create({
+ title: path.basename(fsPath),
+ data: new Blob([data]),
+ })
+ expect(r).not.undefined
+ expect(data).deep.eq(await resourceApi.fileByResourceId(r.id))
+})
+
+it('update file', async () => {
+ const emptyPath = path.resolve(tempPath, 'test.km.svg')
+ const fsPath = path.resolve(__dirname, '../assets/resourcesByFileId.png')
+ const handle = await open(emptyPath, 'w')
+ try {
+ expect(await pathExists(emptyPath)).toBeTruthy()
+ const r = await resourceApi.create({
+ title: path.basename(emptyPath),
+ data: new Blob([await readFile(emptyPath)]),
+ })
+ expect(r).not.undefined
+ const data = await readFile(fsPath)
+ await resourceApi.update({ id: r.id, data: new Blob([data]) })
+ expect(data).deep.eq(await resourceApi.fileByResourceId(r.id))
+ } finally {
+ await close(handle)
+ }
+})
diff --git a/libs/joplin-api/src/util/ajax.ts b/libs/joplin-api/src/util/ajax.ts
index a15883c0..2a559caf 100644
--- a/libs/joplin-api/src/util/ajax.ts
+++ b/libs/joplin-api/src/util/ajax.ts
@@ -104,13 +104,20 @@ export class Ajax {
})
}
- async postFormData(url: string, method: 'post' | 'put', data: object): Promise {
+ async postFormData(
+ url: string,
+ method: 'post' | 'put',
+ data: {
+ props: string
+ data?: Blob
+ filename?: string
+ },
+ ): Promise {
const fd = new FormData()
- Object.entries(data).forEach(([k, v]) => {
- if (k && v) {
- fd.append(k, v)
- }
- })
+ fd.append('props', data.props)
+ if (data.data) {
+ fd.append('data', data.data, data.filename)
+ }
return await this.request({ url: this.baseUrl(url), method: method, data: fd })
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8cb7b0fb..6c4bdb1f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -206,15 +206,18 @@ importers:
'@types/koa__cors': ^3.3.0
'@types/koa__router': ^12.0.0
'@types/markdown-it': ^12.2.3
+ '@types/mime-types': ^2.1.1
'@types/node': ^16
'@types/vscode': ^1.71.0
chokidar: ^3.5.3
cross-path-sort: ^1.0.0
+ envfile: ^6.18.0
fetch-blob: ^3.2.0
formdata-polyfill: ^4.0.10
joplin-api: workspace:*
koa: ^2.13.4
markdown-it: ^13.0.1
+ mime-types: ^2.1.35
node-fetch: ^3.2.10
node-html-parser: ^6.1.1
tsup: ^6.2.3
@@ -235,15 +238,18 @@ importers:
'@types/koa__cors': 3.3.0
'@types/koa__router': 12.0.0
'@types/markdown-it': 12.2.3
+ '@types/mime-types': 2.1.1
'@types/node': 16.11.68
'@types/vscode': 1.71.0
chokidar: 3.5.3
cross-path-sort: 1.0.0
+ envfile: 6.18.0
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
joplin-api: link:../../libs/joplin-api
koa: 2.13.4
markdown-it: 13.0.1
+ mime-types: 2.1.35
node-fetch: 3.2.10
node-html-parser: 6.1.1
tsup: 6.2.3_typescript@4.8.4
@@ -366,8 +372,12 @@ importers:
'@liuli-util/eslint-config-ts': ^0.4.0
'@liuli-util/object': ^3.5.0
'@types/fs-extra': ^9.0.13
+ '@types/node': ^18.11.3
envfile: ^6.18.0
+ fetch-blob: ^3.2.0
+ formdata-polyfill: ^4.0.10
fs-extra: ^10.1.0
+ node-fetch: ^3.2.10
query-string: ^7.1.1
rimraf: ^3.0.2
ts-node: ^10.9.1
@@ -382,10 +392,14 @@ importers:
devDependencies:
'@liuli-util/eslint-config-ts': 0.4.0_typescript@4.8.4
'@types/fs-extra': 9.0.13
+ '@types/node': 18.11.3
envfile: 6.18.0
+ fetch-blob: 3.2.0
+ formdata-polyfill: 4.0.10
fs-extra: 10.1.0
+ node-fetch: 3.2.10
rimraf: 3.0.2
- ts-node: 10.9.1_typescript@4.8.4
+ ts-node: 10.9.1_lw7q66ikwuedwcorwkk4v6trsa
tslib: 2.4.0
tsup: 6.2.3_mwhvu7sfp6vq5ryuwb6hlbjfka
typedoc: 0.23.15_typescript@4.8.4
@@ -3356,7 +3370,7 @@ packages:
/@types/fs-extra/9.0.13:
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
dependencies:
- '@types/node': 18.7.23
+ '@types/node': 18.11.3
/@types/gh-pages/3.2.1:
resolution: {integrity: sha512-y5ULkwfoOEUa6sp2te+iEODv2S//DRiKmxpeXboXhhv+s758rSSxLUiBd6NnlR7aAY4nw1X4FGovLrSWEXWLow==}
@@ -3513,6 +3527,10 @@ packages:
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
dev: true
+ /@types/mime-types/2.1.1:
+ resolution: {integrity: sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==}
+ dev: true
+
/@types/mime/3.0.1:
resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
dev: true
@@ -3536,8 +3554,12 @@ packages:
resolution: {integrity: sha512-JkRpuVz3xCNCWaeQ5EHLR/6woMbHZz/jZ7Kmc63AkU+1HxnoUugzSWMck7dsR4DvNYX8jp9wTi9K7WvnxOIQZQ==}
dev: true
+ /@types/node/18.11.3:
+ resolution: {integrity: sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==}
+
/@types/node/18.7.23:
resolution: {integrity: sha512-DWNcCHolDq0ZKGizjx2DZjR/PqsYwAcYUJmfMWqtVU2MBMG5Mo+xFZrhGId5r/O5HOuMPyQEcM6KUBp5lBZZBg==}
+ dev: true
/@types/normalize-package-data/2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@@ -13512,7 +13534,7 @@ packages:
optional: true
dependencies:
lilconfig: 2.0.6
- ts-node: 10.9.1_typescript@4.8.4
+ ts-node: 10.9.1_lw7q66ikwuedwcorwkk4v6trsa
yaml: 1.10.2
dev: true
@@ -16544,7 +16566,7 @@ packages:
yn: 3.1.1
dev: true
- /ts-node/10.9.1_typescript@4.8.4:
+ /ts-node/10.9.1_lw7q66ikwuedwcorwkk4v6trsa:
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
@@ -16563,6 +16585,7 @@ packages:
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.3
+ '@types/node': 18.11.3
acorn: 8.8.0
acorn-walk: 8.2.0
arg: 4.1.3
@@ -17364,7 +17387,7 @@ packages:
dependencies:
'@types/chai': 4.3.3
'@types/chai-subset': 1.3.3
- '@types/node': 18.7.23
+ '@types/node': 18.11.3
chai: 4.3.6
debug: 4.3.4
local-pkg: 0.4.2