|
| 1 | +import { CommandHandler } from '@/commands/command-handler'; |
| 2 | +import { IngPublishModel } from '@/models/ing'; |
| 3 | +import { AlertService } from '@/services/alert.service'; |
| 4 | +import { globalState } from '@/services/global-state'; |
| 5 | +import { IngApi } from '@/services/ing.api'; |
| 6 | +import { InputStep, MultiStepInput, QuickPickParameters } from '@/services/multi-step-input'; |
| 7 | +import { commands, MessageOptions, ProgressLocation, QuickPickItem, Uri, window } from 'vscode'; |
| 8 | + |
| 9 | +export class PublishIngCommandHandler extends CommandHandler { |
| 10 | + readonly maxLength = 0; |
| 11 | + readonly operation = '发布闪存'; |
| 12 | + readonly editingText = '编辑闪存'; |
| 13 | + readonly inputStep: Record<'content' | 'access' | 'tags', InputStep> = { |
| 14 | + content: async input => { |
| 15 | + this.currentStep = 1; |
| 16 | + this.inputContent = await input.showInputBox({ |
| 17 | + title: this.editingText + ' - 内容', |
| 18 | + value: this.inputContent, |
| 19 | + prompt: '你在做什么? 你在想什么?', |
| 20 | + totalSteps: Object.keys(this.inputStep).length, |
| 21 | + step: this.currentStep, |
| 22 | + validateInput: v => Promise.resolve(v.length > 2000 ? '最多输入2000个字符' : undefined), |
| 23 | + shouldResume: () => Promise.resolve(false), |
| 24 | + }); |
| 25 | + return this.inputContent ? this.inputStep.access : undefined; |
| 26 | + }, |
| 27 | + access: async input => { |
| 28 | + this.currentStep = 2; |
| 29 | + const items = [ |
| 30 | + { label: '公开', value: false }, |
| 31 | + { label: '仅自己', value: true }, |
| 32 | + ]; |
| 33 | + const activeItem = items.filter(x => x.value === this.inputIsPrivate); |
| 34 | + const result = <QuickPickItem & { value: boolean }>await input.showQuickPick< |
| 35 | + QuickPickItem & { value: boolean }, |
| 36 | + QuickPickParameters<QuickPickItem & { value: boolean }> |
| 37 | + >({ |
| 38 | + title: this.editingText + ' - 访问权限', |
| 39 | + placeholder: '', |
| 40 | + step: this.currentStep, |
| 41 | + totalSteps: Object.keys(this.inputStep).length, |
| 42 | + items: items, |
| 43 | + activeItems: activeItem, |
| 44 | + canSelectMany: false, |
| 45 | + shouldResume: () => Promise.resolve(false), |
| 46 | + }); |
| 47 | + if (result && result.value != null) { |
| 48 | + this.inputIsPrivate = result.value; |
| 49 | + return this.inputStep.tags; |
| 50 | + } |
| 51 | + }, |
| 52 | + tags: async input => { |
| 53 | + this.currentStep = 3; |
| 54 | + const value = await input.showInputBox({ |
| 55 | + title: this.editingText + '标签(非必填)', |
| 56 | + step: this.currentStep, |
| 57 | + totalSteps: Object.keys(this.inputStep).length, |
| 58 | + placeHolder: '在此输入标签', |
| 59 | + shouldResume: () => Promise.resolve(false), |
| 60 | + prompt: '输入标签, 以 "," 分隔', |
| 61 | + validateInput: () => Promise.resolve(undefined), |
| 62 | + value: this.inputTags.join(', '), |
| 63 | + }); |
| 64 | + this.inputTags = value |
| 65 | + .split(/, ?/) |
| 66 | + .map(x => x.trim()) |
| 67 | + .filter(x => !!x); |
| 68 | + }, |
| 69 | + }; |
| 70 | + inputTags: string[] = []; |
| 71 | + inputContent = ''; |
| 72 | + inputIsPrivate = false; |
| 73 | + currentStep = 0; |
| 74 | + |
| 75 | + constructor(public readonly contentSource: 'selection' | 'input' = 'selection') { |
| 76 | + super(); |
| 77 | + } |
| 78 | + |
| 79 | + private get formattedIngContent() { |
| 80 | + return `${this.inputTags.map(x => `[${x}]`).join('')}${this.inputContent}`; |
| 81 | + } |
| 82 | + |
| 83 | + async handle() { |
| 84 | + const content = await this.getContent(); |
| 85 | + if (!content) return; |
| 86 | + const api = new IngApi(); |
| 87 | + await this.onPublished( |
| 88 | + await window.withProgress({ location: ProgressLocation.Notification, title: '正在发闪, 请稍候...' }, p => { |
| 89 | + p.report({ increment: 30 }); |
| 90 | + return api.publishIng(content).then(isPublished => { |
| 91 | + p.report({ increment: 70 }); |
| 92 | + return isPublished; |
| 93 | + }); |
| 94 | + }) |
| 95 | + ); |
| 96 | + } |
| 97 | + |
| 98 | + private getContent(): Promise<IngPublishModel | false> { |
| 99 | + switch (this.contentSource) { |
| 100 | + case 'selection': |
| 101 | + return this.getContentFromSelection(); |
| 102 | + case 'input': |
| 103 | + return this.acquireInputContent(); |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + private getContentFromSelection(): Promise<IngPublishModel | false> { |
| 108 | + const text = window.activeTextEditor?.document.getText(window.activeTextEditor?.selection); |
| 109 | + if (!text) { |
| 110 | + this.warnNoSelection(); |
| 111 | + return Promise.resolve(false); |
| 112 | + } |
| 113 | + this.inputContent = text; |
| 114 | + return this.acquireInputContent(); |
| 115 | + } |
| 116 | + |
| 117 | + private async acquireInputContent(step = this.inputStep.content): Promise<IngPublishModel | false> { |
| 118 | + await MultiStepInput.run(step); |
| 119 | + return this.inputContent && |
| 120 | + this.currentStep === Object.keys(this.inputStep).length && |
| 121 | + (await this.confirmPublish()) |
| 122 | + ? { |
| 123 | + content: this.formattedIngContent, |
| 124 | + isPrivate: this.inputIsPrivate, |
| 125 | + } |
| 126 | + : false; |
| 127 | + } |
| 128 | + |
| 129 | + private async confirmPublish(): Promise<boolean> { |
| 130 | + const items = [ |
| 131 | + ['确定', () => Promise.resolve(true)], |
| 132 | + ['编辑内容', async () => (await this.acquireInputContent(this.inputStep.content)) !== false], |
| 133 | + ['编辑访问权限', async () => (await this.acquireInputContent(this.inputStep.access)) !== false], |
| 134 | + ['编辑标签', async () => (await this.acquireInputContent(this.inputStep.tags)) !== false], |
| 135 | + ] as const; |
| 136 | + const selected = await window.showInformationMessage( |
| 137 | + '确定要发布闪存吗?', |
| 138 | + { |
| 139 | + modal: true, |
| 140 | + detail: '📝' + this.formattedIngContent + (this.inputIsPrivate ? '\n\n🔒仅自己可见' : ''), |
| 141 | + } as MessageOptions, |
| 142 | + ...items.map(([title]) => title) |
| 143 | + ); |
| 144 | + return (await items.find(x => x[0] === selected)?.[1].call(null)) ?? false; |
| 145 | + } |
| 146 | + |
| 147 | + private warnNoSelection() { |
| 148 | + AlertService.warning(`无法${this.operation}, 当前没有选中的内容`); |
| 149 | + } |
| 150 | + |
| 151 | + private async onPublished(isPublished: boolean): Promise<void> { |
| 152 | + if (isPublished) { |
| 153 | + const options = [ |
| 154 | + [ |
| 155 | + '打开闪存', |
| 156 | + (): Thenable<void> => commands.executeCommand('vscode.open', Uri.parse(globalState.config.ingSite)), |
| 157 | + ], |
| 158 | + [ |
| 159 | + '我的闪存', |
| 160 | + (): Thenable<void> => |
| 161 | + commands.executeCommand('vscode.open', Uri.parse(globalState.config.ingSite + '/#my')), |
| 162 | + ], |
| 163 | + [ |
| 164 | + '新回应', |
| 165 | + (): Thenable<void> => |
| 166 | + commands.executeCommand( |
| 167 | + 'vscode.open', |
| 168 | + Uri.parse(globalState.config.ingSite + '/#recentcomment') |
| 169 | + ), |
| 170 | + ], |
| 171 | + [ |
| 172 | + '提到我', |
| 173 | + (): Thenable<void> => |
| 174 | + commands.executeCommand('vscode.open', Uri.parse(globalState.config.ingSite + '/#mention')), |
| 175 | + ], |
| 176 | + ] as const; |
| 177 | + const option = await window.showInformationMessage( |
| 178 | + '闪存已发布, 快去看看吧', |
| 179 | + { modal: false }, |
| 180 | + ...options.map(v => ({ title: v[0], id: v[0] })) |
| 181 | + ); |
| 182 | + if (option) return options.find(x => x[0] === option.id)?.[1].call(null); |
| 183 | + } |
| 184 | + } |
| 185 | +} |
0 commit comments