diff --git a/src/renderer/components/Setting.vue b/src/renderer/components/Setting.vue index 2607b1d2..361d25ff 100644 --- a/src/renderer/components/Setting.vue +++ b/src/renderer/components/Setting.vue @@ -169,7 +169,7 @@ export default defineComponent({ } ::v-deep(.je-form-input-label) { - width: 90px; + width: 100px; display: inline-flex; align-items: center; flex: none; @@ -177,7 +177,7 @@ export default defineComponent({ } ::v-deep(.je-form-input-label + input) { - max-width: calc(100% - 110px); + max-width: calc(100% - 120px); } ::v-deep(.je-form-input-label ~ .je-form-input-label) { diff --git a/src/renderer/context/index.ts b/src/renderer/context/index.ts index 6fc90638..04b821e3 100644 --- a/src/renderer/context/index.ts +++ b/src/renderer/context/index.ts @@ -8,6 +8,7 @@ import { useToast } from '@fe/support/ui/toast' import { useModal } from '@fe/support/ui/modal' import * as env from '@fe/support/env' import store from '@fe/support/store' +import * as base from '@fe/services/base' import * as api from '@fe/support/api' import * as embed from '@fe/support/embed' import * as args from '@fe/support/args' @@ -23,6 +24,7 @@ import * as setting from '@fe/services/setting' import * as lib from './lib' const ctx = { + base, api, args, store, diff --git a/src/renderer/plugins/editor-attachment.ts b/src/renderer/plugins/editor-attachment.ts index 82f152dc..5d66c2fa 100644 --- a/src/renderer/plugins/editor-attachment.ts +++ b/src/renderer/plugins/editor-attachment.ts @@ -1,27 +1,26 @@ import dayjs from 'dayjs' import { insert, whenEditorReady } from '@fe/services/editor' import type { Plugin } from '@fe/context' -import * as api from '@fe/support/api' import store from '@fe/support/store' import { encodeMarkdownLink } from '@fe/utils' import type { Doc } from '@fe/types' import { dirname, isBelongTo, join, relative } from '@fe/utils/path' import { getActionHandler } from '@fe/core/action' import { refreshTree } from '@fe/services/tree' +import { upload } from '@fe/services/base' async function uploadFile (file: any, asImage: boolean) { if (!store.state.currentFile) { throw new Error('当前未打开文件') } - const { repo, path } = store.state.currentFile const filename = file.name - const { relativePath } = await api.upload(repo, path, file, filename) + const assetPath = await upload(file, store.state.currentFile, filename) if (asImage) { - insert(`![图片](${encodeMarkdownLink(relativePath)})\n`) + insert(`![图片](${encodeMarkdownLink(assetPath)})\n`) } else { - insert(`附件 [${dayjs().format('YYYY-MM-DD HH:mm')}] [${file.name} (${(file.size / 1024).toFixed(2)}KiB)](${encodeMarkdownLink(relativePath)}){class=open target=_blank}\n`) + insert(`附件 [${dayjs().format('YYYY-MM-DD HH:mm')}] [${file.name} (${(file.size / 1024).toFixed(2)}KiB)](${encodeMarkdownLink(assetPath)}){class=open target=_blank}\n`) } refreshTree() diff --git a/src/renderer/plugins/editor-paste.ts b/src/renderer/plugins/editor-paste.ts index 156563c6..3af54638 100644 --- a/src/renderer/plugins/editor-paste.ts +++ b/src/renderer/plugins/editor-paste.ts @@ -3,7 +3,7 @@ import { getEditor, insert } from '@fe/services/editor' import { Plugin } from '@fe/context' import { triggerHook } from '@fe/core/plugin' import { refreshTree } from '@fe/services/tree' -import * as api from '@fe/support/api' +import { upload } from '@fe/services/base' import store from '@fe/support/store' import { encodeMarkdownLink, fileToBase64URL } from '@fe/utils' @@ -35,9 +35,8 @@ async function pasteImage (file: File, asBase64: boolean) { } if (!(await triggerHook('ON_EDITOR_PASTE_IMAGE', file))) { - const { repo, path } = store.state.currentFile - const { relativePath } = await api.upload(repo, path, file) - insert(`![图片](${encodeMarkdownLink(relativePath)})\n`) + const assetPath = await upload(file, store.state.currentFile) + insert(`![图片](${encodeMarkdownLink(assetPath)})\n`) } refreshTree() diff --git a/src/renderer/plugins/markdown-link.ts b/src/renderer/plugins/markdown-link.ts index b7507d88..d574c244 100644 --- a/src/renderer/plugins/markdown-link.ts +++ b/src/renderer/plugins/markdown-link.ts @@ -4,7 +4,7 @@ import { Plugin } from '@fe/context' import store from '@fe/support/store' import { sleep } from '@fe/utils' import { isElectron, nodeRequire } from '@fe/support/env' -import { basename, dirname, join } from '@fe/utils/path' +import { basename, dirname, join, resolve } from '@fe/utils/path' import { switchDoc } from '@fe/services/document' const handleLink = (link: HTMLAnchorElement) => { @@ -121,7 +121,7 @@ function convertLink (state: StateCore) { return } - const filePath = join(basePath, val) + const filePath = resolve(basePath, val) token.attrSet(attrName, `api/attachment/${encodeURIComponent(fileName)}?repo=${repo}&path=${encodeURIComponent(filePath)}`) } diff --git a/src/renderer/plugins/transform-img-out-link.ts b/src/renderer/plugins/transform-img-out-link.ts index 42934afa..3c6cb9da 100644 --- a/src/renderer/plugins/transform-img-out-link.ts +++ b/src/renderer/plugins/transform-img-out-link.ts @@ -5,8 +5,9 @@ import { useToast } from '@fe/support/ui/toast' import store from '@fe/support/store' import { CtrlCmd, getKeysLabel, isCommand, LeftClick, Shift } from '@fe/core/shortcut' import { replaceValue } from '@fe/services/editor' -import { Plugin } from '@fe/context' import { refreshTree } from '@fe/services/tree' +import { upload } from '@fe/services/base' +import type { Plugin } from '@fe/context' import type { BuildInActionName } from '@fe/types' async function transformImgOutLink (img: HTMLImageElement) { @@ -15,8 +16,6 @@ async function transformImgOutLink (img: HTMLImageElement) { return } - const { repo, path } = currentFile - const transform = (ximg: HTMLImageElement): Promise => { const canvas = document.createElement('canvas') canvas.width = ximg.naturalWidth @@ -26,8 +25,8 @@ async function transformImgOutLink (img: HTMLImageElement) { canvas.toBlob(async blob => { try { const imgFile = new File([blob!], 'file.png') - const { relativePath } = await api.upload(repo, path, imgFile) - resolve(relativePath) + const assetPath = await upload(imgFile, currentFile) + resolve(assetPath) } catch (error) { reject(error) } @@ -44,8 +43,8 @@ async function transformImgOutLink (img: HTMLImageElement) { const res = await api.proxyRequest(img.src, { method: 'get' }, headers) const blob = await res.blob() const imgFile = new File([blob!], 'file.' + mime.extension(res.headers.get('content-type')!)) - const { relativePath } = await api.upload(repo, path, imgFile) - replacedLink = relativePath + const assetPath = await upload(imgFile, currentFile) + replacedLink = assetPath } if (replacedLink) { diff --git a/src/renderer/services/base.ts b/src/renderer/services/base.ts new file mode 100644 index 00000000..bea8b486 --- /dev/null +++ b/src/renderer/services/base.ts @@ -0,0 +1,37 @@ +import { slugify } from 'transliteration' +import type { Doc } from '@fe/types' +import * as api from '@fe/support/api' +import { getSettings } from './setting' +import { FLAG_DEMO } from '@fe/support/args' +import { binMd5, fileToBase64URL, getLogger } from '@fe/utils' +import { basename, resolve, extname, dirname, relative } from '@fe/utils/path' +import { dayjs } from '@fe/context/lib' + +const logger = getLogger('service-base') + +export async function upload (file: File, belongDoc: Pick, name?: string) { + if (FLAG_DEMO) { + return Promise.resolve(URL.createObjectURL(file)) + } + + const fileBase64Url = await fileToBase64URL(file) + + const filename = name || binMd5(fileBase64Url).substr(0, 8) + extname(file.name) + const dirName = slugify(basename(belongDoc.path)) + const parentPath = dirname(belongDoc.path) + const assetsDir = getSettings()['assets-dir'] + .replace('{docSlug}', dirName.startsWith('.') ? 'upload' : dirName) + .replace('{date}', dayjs().format('YYYY-MM-DD')) + + const path: string = resolve(parentPath, assetsDir, filename) + + logger.debug('upload', belongDoc, file, path) + + await api.upload(belongDoc.repo, fileBase64Url, path) + + if (!assetsDir.startsWith('/')) { + return './' + relative(parentPath, path) + } + + return path +} diff --git a/src/renderer/services/setting.ts b/src/renderer/services/setting.ts index 94ddc76e..c8c1559d 100644 --- a/src/renderer/services/setting.ts +++ b/src/renderer/services/setting.ts @@ -1,9 +1,20 @@ import * as api from '@fe/support/api' import { FLAG_DISABLE_XTERM } from '@fe/support/args' import store from '@fe/support/store' +import { BuildInSettings } from '@fe/types' import { getThemeName } from './theme' -const schema = { +export type Schema = { + type: string, + title: string, + properties: {[K in keyof BuildInSettings]: { + defaultValue: BuildInSettings[K] extends any ? BuildInSettings[K] : any + [key: string]: any, + }}, + required: (keyof BuildInSettings)[], +} + +const schema: Schema = { type: 'object', title: '配置项', properties: { @@ -41,27 +52,33 @@ const schema = { type: 'string', enum: ['system', 'dark', 'light'] }, + 'assets-dir': { + defaultValue: './FILES/{docSlug}', + title: '图片存放目录', + type: 'string', + minLength: 1, + description: '支持相对路径和绝对路径(限于仓库内部),可用变量:docSlug, date' + } as any, shell: { defaultValue: '', title: '终端 Shell', type: 'string', } as any, - }, + } as any, required: ['theme'], - dependentSchemas: {}, } const settings = getDefaultSetting() if (FLAG_DISABLE_XTERM) { - delete schema.properties.shell + delete (schema.properties as any).shell } export function getSchema () { return schema } -export function tapSchema (fun: (schema: any) => void) { +export function tapSchema (fun: (schema: Schema) => void) { fun(schema) } @@ -81,7 +98,7 @@ function transformSettings (data: any) { export function getDefaultSetting () { return Object.fromEntries( Object.entries(schema.properties).map(([key, val]) => [key, val.defaultValue]) - ) + ) as BuildInSettings } export async function fetchSettings () { diff --git a/src/renderer/support/api.ts b/src/renderer/support/api.ts index 762bdc09..20862a91 100644 --- a/src/renderer/support/api.ts +++ b/src/renderer/support/api.ts @@ -1,9 +1,5 @@ -import { slugify } from 'transliteration' -import { basename, relative, extname, join, dirname } from '@fe/utils/path' -import { binMd5, fileToBase64URL } from '@fe/utils' +import type { Doc } from '@fe/types' import { isElectron } from '@fe/support/env' -import { Doc } from '@fe/types' -import { FLAG_DEMO } from './args' async function fetchHttp (input: RequestInfo, init?: RequestInit) { const response = await fetch(input, init) @@ -118,38 +114,13 @@ export async function search (repo: string, text: string) { return result.data } -export async function upload (repo: string, belongPath: string, uploadFile: any, name: string | null = null) { - if (FLAG_DEMO) { - return Promise.resolve({ - repo, - path: belongPath, - relativePath: URL.createObjectURL(uploadFile) - }) - } - - const fileBase64Url = await fileToBase64URL(uploadFile) - const filename = name || binMd5(fileBase64Url).substr(0, 8) + extname(uploadFile.name) - +export async function upload (repo: string, fileBase64Url: string, filePath: string) { const formData = new FormData() - const dirName = slugify(basename(belongPath)) - const parentPath = dirname(belongPath) - const path: string = join( - parentPath, - 'FILES', - dirName.startsWith('.') ? 'upload' : dirName, filename - ) formData.append('repo', repo) - formData.append('path', path) + formData.append('path', filePath) formData.append('attachment', fileBase64Url) await fetchHttp('/api/attachment', { method: 'POST', body: formData }) - - let relativePath: string = relative(parentPath, path) - if (!relativePath.startsWith('/') || !relativePath.startsWith('./')) { - relativePath = './' + relativePath - } - - return { repo, path, relativePath } } export async function openInOS ({ repo, path }: Doc) { diff --git a/src/renderer/types.ts b/src/renderer/types.ts index 4e4c0075..3448210c 100644 --- a/src/renderer/types.ts +++ b/src/renderer/types.ts @@ -91,6 +91,15 @@ export type BuildInEvents = { 'doc.changed': Doc, } +export type BuildInSettings = { + 'repos': { name: string, path: string }[], + 'theme': ThemeName, + 'assets-dir': string, + 'shell': string, + 'plugin.image-hosting-picgo.server-url': string, + 'plugin.image-hosting-picgo.enable-paste-image': boolean +} + export type HookType = 'ON_STARTUP' | 'ON_EDITOR_PASTE_IMAGE' | 'ON_VIEW_ELEMENT_CLICK' diff --git a/src/renderer/utils/path.ts b/src/renderer/utils/path.ts index bc609481..d1af3205 100644 --- a/src/renderer/utils/path.ts +++ b/src/renderer/utils/path.ts @@ -5,6 +5,7 @@ export const { extname, dirname, join, + resolve, } = Path export function relative (from: string, to: string) {