Skip to content

Commit 1053736

Browse files
committed
feat: extract image from img tag
1 parent e2ce109 commit 1053736

File tree

4 files changed

+241
-225
lines changed

4 files changed

+241
-225
lines changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -468,16 +468,16 @@
468468
},
469469
"cnblogsClientForVSCode.automaticallyExtractImages": {
470470
"order": 6,
471-
"default": "---",
471+
"default": "disable",
472472
"scope": "application",
473473
"enum": [
474-
"---",
474+
"disable",
475475
"local",
476476
"web",
477-
"all"
477+
"any"
478478
],
479479
"enumItemLabels": [
480-
"不自动提取图片",
480+
"禁用",
481481
"自动提取本地图片",
482482
"自动提取网络图片",
483483
"自动提取全部图片"

src/commands/extract-images.ts

Lines changed: 96 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,108 @@
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'
33

4-
type ExtractOption = MessageItem & Partial<Pick<MarkdownImagesExtractor, 'imageType'>>
4+
type ExtractOption = MessageItem & Partial<{ imageSrc: ImageSrc }>
55
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 },
1010
]
1111

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
4614

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) {
5482
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,
5790
})
5891
}
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-
)
10292

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)
11699
}
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)
117107
}
118108
}

0 commit comments

Comments
 (0)