Show the pictures inside Reddit comments #5
@@ -153,7 +153,10 @@ Things worth knowing before editing:
|
||||
a key in that same comment's own `media_metadata`, so `resolveInlineImages` is one
|
||||
lookup rather than three special cases. A bare `preview.redd.it` address pasted into a
|
||||
comment is in there too, keyed by the id inside the URL. Prefer `s.gif` over `s.mp4`
|
||||
for an animated one: a GIF moves in an `<img>` and an MP4 needs a player.
|
||||
for an animated one: a GIF moves in an `<img>` and an MP4 needs a player. An address
|
||||
typed without a scheme counts as well, but only when it ends in an image extension —
|
||||
the rule wants a host, a path *and* that extension, because comments are full of
|
||||
dotted, slashed prose that must not turn into links.
|
||||
- **Threads** — same media schema as Instagram (`src/platforms/meta-media.ts`). Its
|
||||
payloads are full of empty stub nodes, so the finder only accepts a node with actual
|
||||
candidates in it. The page ships the linked post, the author's follow-ups, other
|
||||
|
||||
+13
-3
@@ -69,7 +69,12 @@ const INLINE = new RegExp(
|
||||
'(?<![\\w*])\\*([^*\\n]+)\\*(?![\\w*])', // 8 em with asterisks
|
||||
'(?<![\\w_])_([^_\\n]+)_(?![\\w_])', // 9 em with underscores
|
||||
'(https?://[^\\s<>]+)', // 10 bare url
|
||||
'(?<![\\w/])(/?[ru]/[A-Za-z0-9_][A-Za-z0-9_-]{1,30})', // 11 r/sub and u/name
|
||||
// 11 the same thing with the scheme left off, which is how people type
|
||||
// them. Narrow on purpose: a host, a path, and an image extension. Prose
|
||||
// is full of dotted words, and `src/render/post.ts` must not become a
|
||||
// link to a website in Tonga.
|
||||
'(?<![\\w@/.])((?:[a-z0-9-]+\\.)+[a-z]{2,}/[^\\s<>]*\\.(?:jpe?g|png|gif|webp|avif)(?:\\?[^\\s<>]*)?)',
|
||||
'(?<![\\w/])(/?[ru]/[A-Za-z0-9_][A-Za-z0-9_-]{1,30})', // 12 r/sub and u/name
|
||||
].join('|'),
|
||||
'g',
|
||||
);
|
||||
@@ -80,8 +85,8 @@ function inline(text: string, image?: ImageRenderer): string {
|
||||
let cursor = 0;
|
||||
|
||||
for (const match of text.matchAll(INLINE)) {
|
||||
const [whole, code, alt, src, label, href, strong, strike, emStar, emScore, url, subOrUser] =
|
||||
match;
|
||||
const [whole, code, alt, src, label, href, strong, strike, emStar, emScore, url, schemeless,
|
||||
subOrUser] = match;
|
||||
out += escapeHtml(text.slice(cursor, match.index));
|
||||
cursor = match.index + whole.length;
|
||||
|
||||
@@ -113,6 +118,11 @@ function inline(text: string, image?: ImageRenderer): string {
|
||||
} else {
|
||||
out += anchor(safe, trimmed.replace(/^https?:\/\/(www\.)?/, '')) + tail;
|
||||
}
|
||||
} else if (schemeless !== undefined) {
|
||||
// Assumed https: every host that serves these redirects to it anyway,
|
||||
// and a picture is the one thing worth guessing a scheme for.
|
||||
const safe = safeHref(`https://${schemeless}`);
|
||||
out += safe ? (image ? image(safe, '') : anchor(safe, schemeless)) : escapeHtml(whole);
|
||||
} else if (subOrUser !== undefined) {
|
||||
const path = subOrUser.startsWith('/') ? subOrUser : `/${subOrUser}`;
|
||||
out += anchor(`${REDDIT}${path}`, subOrUser);
|
||||
|
||||
@@ -122,3 +122,34 @@ test("an image's alt text is escaped like anything else a stranger wrote", () =>
|
||||
test('images inside a quote are still placed', () => {
|
||||
assert.match(mdi('> '), /<blockquote><p><img/);
|
||||
});
|
||||
|
||||
test('an image address typed without a scheme is still the picture', () => {
|
||||
// Which is how people type them: no https, straight from the address bar.
|
||||
const out = mdi('preview.redd.it/lz4drsqh0clh1.jpeg?width=1290&s=b27e');
|
||||
assert.ok(out.includes('<img src="https://preview.redd.it/lz4drsqh0clh1.jpeg?width=1290&s=b27e"'), out);
|
||||
});
|
||||
|
||||
test('a schemeless image address mid-sentence keeps the sentence', () => {
|
||||
const out = mdi('look at i.redd.it/x.png nice one');
|
||||
assert.ok(out.startsWith('<p>look at <img'), out);
|
||||
assert.ok(out.endsWith(' nice one</p>'), out);
|
||||
});
|
||||
|
||||
test('prose full of dots and slashes is not mistaken for an address', () => {
|
||||
// The reason this rule insists on a host, a path and an image extension.
|
||||
for (const text of [
|
||||
'the file is at src/render/post.ts',
|
||||
'see node_modules/foo/bar.js',
|
||||
'a path like ./images/cat.jpg',
|
||||
'C:/Users/x/cat.png',
|
||||
'email [email protected]/nope.jpg',
|
||||
'version 1.2.3/4.png',
|
||||
]) {
|
||||
assert.ok(!mdi(text).includes('<img'), `treated as an image: ${text}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('a schemeless address that is not an image is left alone', () => {
|
||||
// Guessing a scheme is worth it for a picture and not for prose.
|
||||
assert.equal(mdi('example.com/article'), '<p>example.com/article</p>');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user