|
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