diff --git a/package.json b/package.json index 89fa708a..55b2e6c2 100644 --- a/package.json +++ b/package.json @@ -154,6 +154,13 @@ "category": "Cnblogs Posts List", "enablement": "vscode-cnb.isAuthorized" }, + { + "command": "vscode-cnb.upload-post-no-confirm", + "title": "上传博文", + "icon": "$(cloud-upload)", + "category": "Cnblogs Posts List", + "enablement": "vscode-cnb.isAuthorized" + }, { "command": "vscode-cnb.delete-post", "title": "删除随笔(支持多选)", @@ -199,7 +206,14 @@ "category": "Cnblogs" }, { - "command": "vscode-cnb.upload-post-file-to-cnblogs", + "command": "vscode-cnb.upload-post-file", + "title": "上传到博客园", + "icon": "$(vscode-cnb-cloud-upload)", + "enablement": "vscode-cnb.isAuthorized", + "category": "Cnblogs" + }, + { + "command": "vscode-cnb.upload-post-file-no-confirm", "title": "上传到博客园", "icon": "$(vscode-cnb-cloud-upload)", "enablement": "vscode-cnb.isAuthorized", @@ -546,10 +560,11 @@ "markdownDescription": "在拉取博文时显示确认消息" }, "cnblogsClientForVSCode.menus.context.explorer": { + "markdownDescription": "控制要在资源管理器右键菜单中显示的命令", "type": "object", "additionalProperties": false, "default": { - "upload-post-file-to-cnblogs": true, + "upload-post-file-no-confirm": true, "pull-post-remote-updates": true, "modify-post-settings": true, "show-post-to-local-file-info": true, @@ -557,10 +572,9 @@ "export-post-to-pdf": true, "copy-post-link": true }, - "markdownDescription": "控制要在资源管理器右键菜单中显示的命令", "order": 13, "properties": { - "upload-post-file-to-cnblogs": { + "upload-post-file-no-confirm": { "description": "上传到博客园", "type": "boolean", "order": 0, @@ -605,9 +619,10 @@ } }, "cnblogsClientForVSCode.menus.context.editor": { + "markdownDescription": "控制要在编辑器右键菜单中显示的命令", "type": "object", "default": { - "upload-post-file-to-cnblogs": true, + "upload-post-file-no-confirm": true, "pull-post-remote-updates": true, "modify-post-settings": true, "show-post-to-local-file-info": true, @@ -620,7 +635,7 @@ "ing:publish-selection": false }, "properties": { - "upload-post-file-to-cnblogs": { + "upload-post-file-no-confirm": { "description": "上传到博客园", "type": "boolean", "order": 0 @@ -679,7 +694,6 @@ }, "scope": "application", "additionalProperties": false, - "markdownDescription": "控制要在编辑器右键菜单中显示的命令", "order": 14 } } @@ -856,7 +870,7 @@ "when": "false" }, { - "command": "vscode-cnb.upload-post-file-to-cnblogs", + "command": "vscode-cnb.upload-post-file", "when": "true" }, { @@ -1006,7 +1020,7 @@ "group": "delete@2" }, { - "command": "vscode-cnb.upload-post", + "command": "vscode-cnb.upload-post-no-confirm", "group": "0@1", "when": "viewItem == cnb-post-cached" }, @@ -1111,8 +1125,8 @@ "group": "cnblogs@1" }, { - "command": "vscode-cnb.upload-post-file-to-cnblogs", - "when": "resourceLangId == markdown && config.cnblogsClientForVSCode.menus.context.editor.upload-post-file-to-cnblogs", + "command": "vscode-cnb.upload-post-file-no-confirm", + "when": "resourceLangId == markdown && config.cnblogsClientForVSCode.menus.context.editor.upload-post-file-no-confirm", "group": "cnblogs@2" }, { @@ -1163,7 +1177,7 @@ "group": "navigation" }, { - "command": "vscode-cnb.upload-post-file-to-cnblogs", + "command": "vscode-cnb.upload-post-file", "when": "resourceLangId == markdown", "group": "navigation" } @@ -1175,8 +1189,8 @@ "group": "cnblogs@1" }, { - "command": "vscode-cnb.upload-post-file-to-cnblogs", - "when": "resourceLangId == markdown && config.cnblogsClientForVSCode.menus.context.explorer.upload-post-file-to-cnblogs", + "command": "vscode-cnb.upload-post-file", + "when": "resourceLangId == markdown && config.cnblogsClientForVSCode.menus.context.explorer.upload-post-file", "group": "cnblogs@2" }, { diff --git a/src/commands/commands-registration.ts b/src/commands/commands-registration.ts index d28b2fe8..57ebe668 100644 --- a/src/commands/commands-registration.ts +++ b/src/commands/commands-registration.ts @@ -11,7 +11,7 @@ import { refreshPostsList, seekPostsList, } from './posts-list/refresh-posts-list' -import { uploadPostFileToCnblogs, uploadPostToCnblogs } from './posts-list/upload-post' +import { uploadPostFile, uploadPostFileNoConfirm, uploadPost, uploadPostNoConfirm } from './posts-list/upload-post' import { createLocalDraft } from './posts-list/create-local-draft' import { deleteSelectedPosts } from './posts-list/delete-post' import { modifyPostSettings } from './posts-list/modify-post-settings' @@ -55,11 +55,13 @@ export const registerCommands = () => { commands.registerCommand(`${appName}.seek-posts-list`, seekPostsList), commands.registerCommand(`${appName}.next-posts-list`, gotoNextPostsList), commands.registerCommand(`${appName}.edit-post`, openPostInVscode), - commands.registerCommand(`${appName}.upload-post`, uploadPostToCnblogs), commands.registerCommand(`${appName}.modify-post-settings`, modifyPostSettings), commands.registerCommand(`${appName}.delete-post`, deleteSelectedPosts), commands.registerCommand(`${appName}.create-local-draft`, createLocalDraft), - commands.registerCommand(`${appName}.upload-post-file-to-cnblogs`, uploadPostFileToCnblogs), + commands.registerCommand(`${appName}.upload-post`, uploadPost), + commands.registerCommand(`${appName}.upload-post-no-confirm`, uploadPostNoConfirm), + commands.registerCommand(`${appName}.upload-post-file`, uploadPostFile), + commands.registerCommand(`${appName}.upload-post-file-no-confirm`, uploadPostFileNoConfirm), commands.registerCommand(`${appName}.pull-post-remote-updates`, pullPostRemoteUpdates), commands.registerCommand(`${appName}.upload-clipboard-image`, () => uploadImage(true, 'clipboard')), commands.registerCommand(`${appName}.upload-local-disk-image`, () => uploadImage(true, 'local')), diff --git a/src/commands/posts-list/upload-post.ts b/src/commands/posts-list/upload-post.ts index 0ba5050a..6dca913b 100644 --- a/src/commands/posts-list/upload-post.ts +++ b/src/commands/posts-list/upload-post.ts @@ -33,7 +33,73 @@ async function parseFileUri(fileUri: Uri | undefined) { return undefined } -export const uploadPostFileToCnblogs = async (fileUri: Uri | undefined) => { +// TODO: need better impl +export const uploadPostNoConfirm = async (input: Post | PostTreeItem | PostEditDto | undefined) => { + if (input === undefined) return + if (input instanceof PostTreeItem) input = input.post + + let post: Post | undefined + + if (input instanceof PostEditDto) { + post = input.post + } else { + const dto = await PostService.fetchPostEditDto(input.id) + post = dto?.post + } + + if (post === undefined) return + + const localFilePath = PostFileMapManager.getFilePath(post.id) + if (!localFilePath) return AlertService.warn('本地无该博文的编辑记录') + + if (Settings.autoExtractImgType !== undefined) + await extractImages(Uri.file(localFilePath), Settings.autoExtractImgType).catch(console.warn) + + await saveFilePendingChanges(localFilePath) + post.postBody = (await workspace.fs.readFile(Uri.file(localFilePath))).toString() + + if (isEmptyBody(post.postBody)) return false + + post.isMarkdown = + path.extname(localFilePath).endsWith('md') || path.extname(localFilePath).endsWith('mkd') || post.isMarkdown + + const thePost = post // Dup code for type checking + + return window.withProgress( + { + location: ProgressLocation.Notification, + title: '正在上传博文', + cancellable: false, + }, + async progress => { + progress.report({ + increment: 10, + }) + + let isSaved = false + + try { + const { id: postId } = await PostService.updatePost(thePost) + await openPostInVscode(postId) + thePost.id = postId + + isSaved = true + progress.report({ increment: 100 }) + AlertService.info('上传成功') + await refreshPostsList() + } catch (err) { + progress.report({ increment: 100 }) + AlertService.err(`上传失败\n${err instanceof Error ? err.message : JSON.stringify(err)}`) + console.error(err) + } + + return isSaved + } + ) +} + +// TODO: need better impl +export const uploadPostFileNoConfirm = async (fileUri: Uri | undefined) => { const parsedFileUri = await parseFileUri(fileUri) if (parsedFileUri === undefined) return @@ -42,7 +108,7 @@ export const uploadPostFileToCnblogs = async (fileUri: Uri | undefined) => { if (postId !== undefined && postId >= 0) { const dto = await PostService.fetchPostEditDto(postId) - if (dto !== undefined) await uploadPostToCnblogs(dto) + if (dto !== undefined) await uploadPostNoConfirm(dto) return } @@ -70,65 +136,24 @@ export const uploadPostFileToCnblogs = async (fileUri: Uri | undefined) => { if (postEditDto === undefined) return if (!fileContent) await workspace.fs.writeFile(parsedFileUri, Buffer.from(postEditDto.post.postBody)) - await uploadPostToCnblogs(postEditDto.post) + await uploadPostNoConfirm(postEditDto.post) } else if (selected === '新建博文') { await saveLocalDraftToCnblogs(new LocalDraft(filePath)) } } -export async function saveLocalDraftToCnblogs(localDraft: LocalDraft) { - // check format - if (!['.md', '.mkd'].some(x => localDraft.fileExt === x)) { - AlertService.warn('格式错误, 只支持 Markdown 文件') - return - } - const editDto = await PostService.fetchPostEditTemplate() - if (!editDto) return - - const { post } = editDto - - post.title = localDraft.fileNameWithoutExt - post.isMarkdown = true - post.categoryIds ??= [] - void postConfigurationPanel.open({ - panelTitle: '', - localFileUri: localDraft.filePathUri, - breadcrumbs: ['新建博文', '博文设置', post.title], - post, - successCallback: async savedPost => { - await refreshPostsList() - await openPostFile(localDraft) - - await PostFileMapManager.updateOrCreate(savedPost.id, localDraft.filePath) - await openPostFile(localDraft) - postsDataProvider.fireTreeDataChangedEvent(undefined) - AlertService.info('博文已创建') - }, - beforeUpdate: async (postToSave, panel) => { - await saveFilePendingChanges(localDraft.filePath) - // 本地文件已经被删除了 - if (!localDraft.exist && panel) { - AlertService.warn('本地文件已删除, 无法新建博文') - return false - } - - if (Settings.autoExtractImgType !== undefined) - await extractImages(localDraft.filePathUri, Settings.autoExtractImgType).catch(console.warn) - - postToSave.postBody = await localDraft.readAllText() - return true - }, - }) -} - -export const uploadPostToCnblogs = async (input: Post | PostTreeItem | PostEditDto | undefined) => { +export const uploadPost = async (input: Post | PostTreeItem | PostEditDto | undefined) => { if (input === undefined) return if (input instanceof PostTreeItem) input = input.post let post: Post | undefined - if (input instanceof PostEditDto) post = input.post - else (await PostService.fetchPostEditDto(input.id))?.post + if (input instanceof PostEditDto) { + post = input.post + } else { + const dto = await PostService.fetchPostEditDto(input.id) + post = dto?.post + } if (post === undefined) return @@ -193,6 +218,94 @@ export const uploadPostToCnblogs = async (input: Post | PostTreeItem | PostEditD ) } +export const uploadPostFile = async (fileUri: Uri | undefined) => { + const parsedFileUri = await parseFileUri(fileUri) + if (parsedFileUri === undefined) return + + const { fsPath: filePath } = parsedFileUri + const postId = PostFileMapManager.getPostId(filePath) + + if (postId !== undefined && postId >= 0) { + const dto = await PostService.fetchPostEditDto(postId) + if (dto !== undefined) await uploadPost(dto) + return + } + + const fileContent = Buffer.from(await workspace.fs.readFile(parsedFileUri)).toString() + if (isEmptyBody(fileContent)) return + + const options = ['新建博文', '关联已有博文'] + const selected = await window.showInformationMessage( + '本地文件尚未关联到博客园博文', + { + modal: true, + detail: `您可以选择新建一篇博文或将本地文件关联到一篇博客园博文(您可以根据标题搜索您在博客园博文)`, + } as MessageOptions, + ...options + ) + if (selected === '关联已有博文') { + const selectedPost = await searchPostsByTitle({ + postTitle: path.basename(filePath, path.extname(filePath)), + quickPickTitle: '搜索要关联的博文', + }) + if (selectedPost === undefined) return + + await PostFileMapManager.updateOrCreate(selectedPost.id, filePath) + const postEditDto = await PostService.fetchPostEditDto(selectedPost.id) + if (postEditDto === undefined) return + if (!fileContent) await workspace.fs.writeFile(parsedFileUri, Buffer.from(postEditDto.post.postBody)) + + await uploadPost(postEditDto.post) + } else if (selected === '新建博文') { + await saveLocalDraftToCnblogs(new LocalDraft(filePath)) + } +} + +export async function saveLocalDraftToCnblogs(localDraft: LocalDraft) { + // check format + if (!['.md', '.mkd'].some(x => localDraft.fileExt === x)) { + AlertService.warn('格式错误, 只支持 Markdown 文件') + return + } + const editDto = await PostService.fetchPostEditTemplate() + if (!editDto) return + + const { post } = editDto + + post.title = localDraft.fileNameWithoutExt + post.isMarkdown = true + post.categoryIds ??= [] + void postConfigurationPanel.open({ + panelTitle: '', + localFileUri: localDraft.filePathUri, + breadcrumbs: ['新建博文', '博文设置', post.title], + post, + successCallback: async savedPost => { + await refreshPostsList() + await openPostFile(localDraft) + + await PostFileMapManager.updateOrCreate(savedPost.id, localDraft.filePath) + await openPostFile(localDraft) + postsDataProvider.fireTreeDataChangedEvent(undefined) + AlertService.info('博文已创建') + }, + beforeUpdate: async (postToSave, panel) => { + await saveFilePendingChanges(localDraft.filePath) + // 本地文件已经被删除了 + if (!localDraft.exist && panel) { + AlertService.warn('本地文件已删除, 无法新建博文') + return false + } + + if (Settings.autoExtractImgType !== undefined) + await extractImages(localDraft.filePathUri, Settings.autoExtractImgType).catch(console.warn) + + postToSave.postBody = await localDraft.readAllText() + return true + }, + }) +} + function isEmptyBody(body: string) { if (body === '') { AlertService.warn('博文内容不能为空')