mirror of
https://github.com/purocean/yn.git
synced 2026-09-24 15:46:02 +08:00
feat: 增加自定义图片上传目录配置
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(`})\n`)
|
||||
insert(`})\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()
|
||||
|
||||
@@ -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(`})\n`)
|
||||
const assetPath = await upload(file, store.state.currentFile)
|
||||
insert(`})\n`)
|
||||
}
|
||||
|
||||
refreshTree()
|
||||
|
||||
@@ -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)}`)
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string> => {
|
||||
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) {
|
||||
|
||||
@@ -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<Doc, 'repo' | 'path'>, 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
|
||||
}
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -5,6 +5,7 @@ export const {
|
||||
extname,
|
||||
dirname,
|
||||
join,
|
||||
resolve,
|
||||
} = Path
|
||||
|
||||
export function relative (from: string, to: string) {
|
||||
|
||||
Reference in New Issue
Block a user