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
|
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
|
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`
|
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
|
- **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
|
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
|
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*])', // 8 em with asterisks
|
||||||
'(?<![\\w_])_([^_\\n]+)_(?![\\w_])', // 9 em with underscores
|
'(?<![\\w_])_([^_\\n]+)_(?![\\w_])', // 9 em with underscores
|
||||||
'(https?://[^\\s<>]+)', // 10 bare url
|
'(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('|'),
|
].join('|'),
|
||||||
'g',
|
'g',
|
||||||
);
|
);
|
||||||
@@ -80,8 +85,8 @@ function inline(text: string, image?: ImageRenderer): string {
|
|||||||
let cursor = 0;
|
let cursor = 0;
|
||||||
|
|
||||||
for (const match of text.matchAll(INLINE)) {
|
for (const match of text.matchAll(INLINE)) {
|
||||||
const [whole, code, alt, src, label, href, strong, strike, emStar, emScore, url, subOrUser] =
|
const [whole, code, alt, src, label, href, strong, strike, emStar, emScore, url, schemeless,
|
||||||
match;
|
subOrUser] = match;
|
||||||
out += escapeHtml(text.slice(cursor, match.index));
|
out += escapeHtml(text.slice(cursor, match.index));
|
||||||
cursor = match.index + whole.length;
|
cursor = match.index + whole.length;
|
||||||
|
|
||||||
@@ -113,6 +118,11 @@ function inline(text: string, image?: ImageRenderer): string {
|
|||||||
} else {
|
} else {
|
||||||
out += anchor(safe, trimmed.replace(/^https?:\/\/(www\.)?/, '')) + tail;
|
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) {
|
} else if (subOrUser !== undefined) {
|
||||||
const path = subOrUser.startsWith('/') ? subOrUser : `/${subOrUser}`;
|
const path = subOrUser.startsWith('/') ? subOrUser : `/${subOrUser}`;
|
||||||
out += anchor(`${REDDIT}${path}`, 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', () => {
|
test('images inside a quote are still placed', () => {
|
||||||
assert.match(mdi('> '), /<blockquote><p><img/);
|
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