|
1 |
| -import { Uri, workspace, window, MessageOptions, MessageItem, ProgressLocation, Range, WorkspaceEdit } from 'vscode' |
2 |
| -import { ImageInformation, MarkdownImagesExtractor } from '@/services/images-extractor.service' |
| 1 | +import { MessageItem, MessageOptions, ProgressLocation, Range, Uri, window, workspace, WorkspaceEdit } from 'vscode' |
| 2 | +import { ImageSrc, MarkdownImagesExtractor, ImageInfo, newImageSrcFilter } from '@/services/images-extractor.service' |
3 | 3 |
|
4 |
| -type ExtractOption = MessageItem & Partial<Pick<MarkdownImagesExtractor, 'imageType'>> |
| 4 | +type ExtractOption = MessageItem & Partial<{ imageSrc: ImageSrc }> |
5 | 5 | const extractOptions: readonly ExtractOption[] = [
|
6 |
| - { title: '提取本地图片', imageType: 'local' }, |
7 |
| - { title: '提取网络图片', imageType: 'web' }, |
8 |
| - { title: '提取全部', imageType: 'all' }, |
9 |
| - { title: '取消', imageType: undefined, isCloseAffordance: true }, |
| 6 | + { title: '提取本地图片', imageSrc: ImageSrc.local }, |
| 7 | + { title: '提取网络图片', imageSrc: ImageSrc.web }, |
| 8 | + { title: '提取全部', imageSrc: ImageSrc.any }, |
| 9 | + { title: '取消', imageSrc: undefined, isCloseAffordance: true }, |
10 | 10 | ]
|
11 | 11 |
|
12 |
| -export const extractImages = async ( |
13 |
| - arg: unknown, |
14 |
| - inputImageType: MarkdownImagesExtractor['imageType'] | null | undefined |
15 |
| -): Promise<void> => { |
16 |
| - if (arg instanceof Uri && arg.scheme === 'file') { |
17 |
| - const shouldIgnoreWarnings = inputImageType != null |
18 |
| - const markdown = (await workspace.fs.readFile(arg)).toString() |
19 |
| - const extractor = new MarkdownImagesExtractor(markdown, arg) |
20 |
| - const images = extractor.findImages() |
21 |
| - const availableWebImagesCount = images.filter(extractor.createImageTypeFilter('web')).length |
22 |
| - const availableLocalImagesCount = images.filter(extractor.createImageTypeFilter('local')).length |
23 |
| - const warnNoImages = (): void => |
24 |
| - void (shouldIgnoreWarnings ? null : window.showWarningMessage('没有可以提取的图片')) |
25 |
| - if (images.length <= 0) return warnNoImages() |
26 |
| - |
27 |
| - let result = extractOptions.find(x => inputImageType != null && x.imageType === inputImageType) |
28 |
| - result = result |
29 |
| - ? result |
30 |
| - : await window.showInformationMessage<ExtractOption>( |
31 |
| - '请选择要提取哪些图片? 注意! 此操作会替换源文件中的图片链接!', |
32 |
| - { |
33 |
| - modal: true, |
34 |
| - detail: |
35 |
| - `共找到 ${availableWebImagesCount} 张可以提取的网络图片\n` + |
36 |
| - `${availableLocalImagesCount} 张可以提取的本地图片`, |
37 |
| - } as MessageOptions, |
38 |
| - ...extractOptions |
39 |
| - ) |
40 |
| - const editor = window.visibleTextEditors.find(x => x.document.fileName === arg.fsPath) |
41 |
| - const textDocument = editor?.document ?? workspace.textDocuments.find(x => x.fileName === arg.fsPath) |
42 |
| - |
43 |
| - if (result && result.imageType && textDocument) { |
44 |
| - if (extractor.findImages().length <= 0) return warnNoImages() |
45 |
| - extractor.imageType = result.imageType |
| 12 | +export async function extractImages(arg: unknown, inputImageSrc: ImageSrc | undefined) { |
| 13 | + if (!(arg instanceof Uri && arg.scheme === 'file')) return |
46 | 14 |
|
47 |
| - await textDocument.save() |
48 |
| - const failedImages = await window.withProgress( |
49 |
| - { title: '提取图片', location: ProgressLocation.Notification }, |
50 |
| - async progress => { |
51 |
| - extractor.onProgress = (idx, images) => { |
52 |
| - const total = images.length |
53 |
| - const image = images[idx] |
| 15 | + const shouldIgnoreWarnings = inputImageSrc != null |
| 16 | + const markdown = (await workspace.fs.readFile(arg)).toString() |
| 17 | + const extractor = new MarkdownImagesExtractor(markdown, arg) |
| 18 | + const images = extractor.findImages() |
| 19 | + const availableWebImagesCount = images.filter(newImageSrcFilter(ImageSrc.web)).length |
| 20 | + const availableLocalImagesCount = images.filter(newImageSrcFilter(ImageSrc.local)).length |
| 21 | + |
| 22 | + const warnNoImages = () => |
| 23 | + void (!shouldIgnoreWarnings ? window.showWarningMessage('没有找到可以提取的图片') : undefined) |
| 24 | + |
| 25 | + if (images.length <= 0) return warnNoImages() |
| 26 | + |
| 27 | + const result = |
| 28 | + extractOptions.find(x => inputImageSrc != null && x.imageSrc === inputImageSrc) ?? |
| 29 | + (await window.showInformationMessage<ExtractOption>( |
| 30 | + '要提取哪些图片? 此操作会替换源文件中的图片链接!', |
| 31 | + { |
| 32 | + modal: true, |
| 33 | + detail: |
| 34 | + `共找到 ${availableWebImagesCount} 张可以提取的网络图片\n` + |
| 35 | + `${availableLocalImagesCount} 张可以提取的本地图片`, |
| 36 | + } as MessageOptions, |
| 37 | + ...extractOptions |
| 38 | + )) |
| 39 | + |
| 40 | + const editor = window.visibleTextEditors.find(x => x.document.fileName === arg.fsPath) |
| 41 | + const textDocument = editor?.document ?? workspace.textDocuments.find(x => x.fileName === arg.fsPath) |
| 42 | + |
| 43 | + if (!(result && result.imageSrc && textDocument)) return |
| 44 | + |
| 45 | + if (extractor.findImages().length <= 0) return warnNoImages() |
| 46 | + extractor.imageSrc = result.imageSrc |
| 47 | + |
| 48 | + await textDocument.save() |
| 49 | + |
| 50 | + const failedImages = await window.withProgress( |
| 51 | + { title: '提取图片', location: ProgressLocation.Notification }, |
| 52 | + async progress => { |
| 53 | + extractor.onProgress = (idx, images) => { |
| 54 | + const total = images.length |
| 55 | + const image = images[idx] |
| 56 | + progress.report({ |
| 57 | + increment: (idx / total) * 80, |
| 58 | + message: `[${idx + 1} / ${total}] 正在提取 ${image.link}`, |
| 59 | + }) |
| 60 | + } |
| 61 | + |
| 62 | + const extracted = await extractor.extract() |
| 63 | + const extractedLen = extracted.length |
| 64 | + const idx = 0 |
| 65 | + |
| 66 | + const we = extracted |
| 67 | + .filter(([, dst]) => dst != null) |
| 68 | + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
| 69 | + .map(([src, dst]) => [src, dst!]) |
| 70 | + .map(([src, dst]) => { |
| 71 | + const startPos = textDocument.positionAt(src.startOffset) |
| 72 | + const endPos = textDocument.positionAt( |
| 73 | + src.startOffset + src.prefix.length + src.link.length + src.postfix.length |
| 74 | + ) |
| 75 | + const range = new Range(startPos, endPos) |
| 76 | + |
| 77 | + const ret: [Range, ImageInfo] = [range, dst] |
| 78 | + return ret |
| 79 | + }) |
| 80 | + .reduce((we, [range, dst]) => { |
| 81 | + if (range) { |
54 | 82 | progress.report({
|
55 |
| - increment: (idx / total) * 80, |
56 |
| - message: `[${idx + 1} / ${total}] 正在提取 ${image.symbol}`, |
| 83 | + increment: (idx / extractedLen) * 20 + 80, |
| 84 | + message: `[${idx + 1} / ${extractedLen}] 正在替换图片链接 ${dst.link}`, |
| 85 | + }) |
| 86 | + const newText = dst.prefix + dst.link + dst.postfix |
| 87 | + we.replace(textDocument.uri, range, newText, { |
| 88 | + needsConfirmation: false, |
| 89 | + label: dst.link, |
57 | 90 | })
|
58 | 91 | }
|
59 |
| - const extractResults = await extractor.extract() |
60 |
| - const idx = 0 |
61 |
| - const total = extractResults.length |
62 |
| - |
63 |
| - await workspace.applyEdit( |
64 |
| - extractResults |
65 |
| - .filter((x): x is [source: ImageInformation, result: ImageInformation] => x[1] != null) |
66 |
| - .map( |
67 |
| - ([sourceImage, result]): [ |
68 |
| - range: Range | null, |
69 |
| - sourceImage: ImageInformation, |
70 |
| - extractedImage: ImageInformation |
71 |
| - ] => { |
72 |
| - if (sourceImage.index == null) return [null, sourceImage, result] |
73 |
| - |
74 |
| - const endPos = textDocument.positionAt( |
75 |
| - sourceImage.index + sourceImage.symbol.length - 1 |
76 |
| - ) |
77 |
| - return [ |
78 |
| - new Range( |
79 |
| - textDocument.positionAt(sourceImage.index), |
80 |
| - endPos.with({ character: endPos.character + 1 }) |
81 |
| - ), |
82 |
| - sourceImage, |
83 |
| - result, |
84 |
| - ] |
85 |
| - } |
86 |
| - ) |
87 |
| - .reduce((workspaceEdit, [range, , extractedImage]) => { |
88 |
| - if (range) { |
89 |
| - progress.report({ |
90 |
| - increment: (idx / total) * 20 + 80, |
91 |
| - message: `[${idx + 1} / ${total}] 正在替换图片链接 ${extractedImage.symbol}`, |
92 |
| - }) |
93 |
| - workspaceEdit.replace(textDocument.uri, range, extractedImage.symbol, { |
94 |
| - needsConfirmation: false, |
95 |
| - label: extractedImage.symbol, |
96 |
| - }) |
97 |
| - } |
98 |
| - |
99 |
| - return workspaceEdit |
100 |
| - }, new WorkspaceEdit()) |
101 |
| - ) |
102 | 92 |
|
103 |
| - await textDocument.save() |
104 |
| - return extractResults.filter(x => x[1] === null).map(x => x[0]) |
105 |
| - } |
106 |
| - ) |
107 |
| - if (failedImages && failedImages.length > 0) { |
108 |
| - window |
109 |
| - .showErrorMessage( |
110 |
| - `${failedImages.length}张图片提取失败\n${failedImages |
111 |
| - .map(x => [x.symbol, extractor.errors.find(y => y[0] === x.symbol)?.[1] ?? ''].join(': ')) |
112 |
| - .join('\n')}` |
113 |
| - ) |
114 |
| - .then(undefined, console.warn) |
115 |
| - } |
| 93 | + return we |
| 94 | + }, new WorkspaceEdit()) |
| 95 | + |
| 96 | + await workspace.applyEdit(we) |
| 97 | + await textDocument.save() |
| 98 | + return extracted.filter(([, dst]) => dst === null).map(([src]) => src) |
116 | 99 | }
|
| 100 | + ) |
| 101 | + |
| 102 | + if (failedImages && failedImages.length > 0) { |
| 103 | + const info = failedImages |
| 104 | + .map(x => [x.link, extractor.errors.find(([link]) => link === x.link)?.[1] ?? ''].join(',')) |
| 105 | + .join('\n') |
| 106 | + window.showErrorMessage(`${failedImages.length}张图片提取失败: ${info}`).then(undefined, console.warn) |
117 | 107 | }
|
118 | 108 | }
|
0 commit comments