Fix markdown images having duplicate ids (#157177)

Fixes #153144
This commit is contained in:
Matt Bierner 2022-08-04 21:31:42 -07:00 committed by GitHub
parent 5589b81019
commit b012216211
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 20 additions and 42 deletions

View file

@ -11,7 +11,6 @@ import { MarkdownContributionProvider } from './markdownExtensions';
import { Slugifier } from './slugify';
import { ITextDocument } from './types/textDocument';
import { Disposable } from './util/dispose';
import { stringHash } from './util/hash';
import { WebviewResourceProvider } from './util/resources';
import { isOfScheme, Schemes } from './util/schemes';
import { MdDocumentInfoCache } from './util/workspaceCache';
@ -86,11 +85,11 @@ class TokenCache {
export interface RenderOutput {
html: string;
containingImages: { src: string }[];
containingImages: Set<string>;
}
interface RenderEnv {
containingImages: { src: string }[];
containingImages: Set<string>;
currentDocument: vscode.Uri | undefined;
resourceProvider: WebviewResourceProvider | undefined;
}
@ -209,7 +208,7 @@ export class MarkdownItEngine implements IMdParser {
: this.tokenizeDocument(input, config, engine);
const env: RenderEnv = {
containingImages: [],
containingImages: new Set<string>(),
currentDocument: typeof input === 'string' ? undefined : input.uri,
resourceProvider,
};
@ -248,13 +247,9 @@ export class MarkdownItEngine implements IMdParser {
const original = md.renderer.rules.image;
md.renderer.rules.image = (tokens: Token[], idx: number, options, env: RenderEnv, self) => {
const token = tokens[idx];
token.attrJoin('class', 'loading');
const src = token.attrGet('src');
if (src) {
env.containingImages?.push({ src });
const imgHash = stringHash(src);
token.attrSet('id', `image-hash-${imgHash}`);
env.containingImages?.add(src);
if (!token.attrGet('data-src')) {
token.attrSet('src', this.toResourceUri(src, env.currentDocument, env.resourceProvider));

View file

@ -38,7 +38,7 @@ const previewStrings = {
export interface MarkdownContentProviderOutput {
html: string;
containingImages: { src: string }[];
containingImages: Set<string>;
}
@ -88,7 +88,7 @@ export class MdDocumentRenderer {
const body = await this.renderBody(markdownDocument, resourceProvider);
if (token.isCancellationRequested) {
return { html: '', containingImages: [] };
return { html: '', containingImages: new Set() };
}
const html = `<!DOCTYPE html>

View file

@ -396,9 +396,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
}
}
private updateImageWatchers(containingImages: { src: string }[]) {
const srcs = new Set(containingImages.map(img => img.src));
private updateImageWatchers(srcs: Set<string>) {
// Delete stale file watchers.
for (const [src, watcher] of this._fileWatchersBySrc) {
if (!srcs.has(src)) {

View file

@ -30,22 +30,23 @@ suite('markdown.engine', () => {
});
});
suite('image-caching', () => {
suite.only('image-caching', () => {
const input = '![](img.png) [](no-img.png) ![](http://example.org/img.png) ![](img.png) ![](./img2.png)';
test('Extracts all images', async () => {
const engine = createNewMarkdownEngine();
assert.deepStrictEqual((await engine.render(input)), {
html: '<p data-line="0" class="code-line" dir="auto">'
+ '<img src="img.png" alt="" class="loading" id="image-hash--754511435" data-src="img.png"> '
+ '<a href="no-img.png" data-href="no-img.png"></a> '
+ '<img src="http://example.org/img.png" alt="" class="loading" id="image-hash--1903814170" data-src="http://example.org/img.png"> '
+ '<img src="img.png" alt="" class="loading" id="image-hash--754511435" data-src="img.png"> '
+ '<img src="./img2.png" alt="" class="loading" id="image-hash-265238964" data-src="./img2.png">'
+ '</p>\n'
,
containingImages: [{ src: 'img.png' }, { src: 'http://example.org/img.png' }, { src: 'img.png' }, { src: './img2.png' }],
});
const result = await engine.render(input);
assert.deepStrictEqual(result.html,
'<p data-line="0" class="code-line" dir="auto">'
+ '<img src="img.png" alt="" data-src="img.png"> '
+ '<a href="no-img.png" data-href="no-img.png"></a> '
+ '<img src="http://example.org/img.png" alt="" data-src="http://example.org/img.png"> '
+ '<img src="img.png" alt="" data-src="img.png"> '
+ '<img src="./img2.png" alt="" data-src="./img2.png">'
+ '</p>\n'
);
assert.deepStrictEqual([...result.containingImages], ['img.png', 'http://example.org/img.png', './img2.png']);
});
});
});

View file

@ -1,16 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function numberHash(val: number, initialHashVal: number): number {
return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32
}
export function stringHash(s: string) {
let hashVal = numberHash(149417, 0);
for (let i = 0, length = s.length; i < length; i++) {
hashVal = numberHash(s.charCodeAt(i), hashVal);
}
return hashVal;
}