Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | 21x 18x 18x 8x 17x 13x 13x 13x 13x 17x 17x 5x 5x 4x 4x 4x 4x 2x 1x 1x 1x 1x 1x 1x 1x 9x 13x 13x 9x 10x 9x 22x 22x 9x 9x 13x 1x 1x 13x 9x 9x 10x 6x 5x 3x 2x 2x 10x | /**
* Returns true when a URL looks like a CI badge or shield rather than a real image.
* Used to skip badge images when selecting the representative project image.
*
* @param {string} u - Image URL to test
* @returns {boolean}
*/
function isBadgeLike(u) {
if (!u) return false;
const s = String(u).toLowerCase();
if (s.includes('badge') || s.includes('shield') || s.includes('status') || s.includes('travis') || s.includes('circleci') || s.includes('shields.io') || s.includes('actions/workflows') || s.includes('github.com/badges')) return true;
return false;
}
/**
* Selects the best image URL from a parsed README AST.
* Priority: image under a screenshots/gallery heading → explicit project-image path →
* any raster image that is not badge-like → any non-SVG image → first image found.
*
* @param {object} ast - Parsed README AST
* @returns {string|null} Image URL, or null if no candidate exists
*/
function findImageCandidateFromAst(ast) {
if (!ast || !ast.children) return null;
const candidates = [];
const findUnderHeading = (titleRe) => {
Iif (!ast || !Array.isArray(ast.children)) return null;
for (let i = 0; i < ast.children.length; i++) {
const n = ast.children[i];
if (n.type === 'heading') {
const txt = (n.children || []).map(c => c.value || '').join('').toLowerCase();
if (titleRe.test(txt)) {
let j = i + 1;
while (j < ast.children.length && ast.children[j].type !== 'heading') {
const nn = ast.children[j];
if (nn.type === 'image' && nn.url) return nn.url;
if (nn.type === 'html' && typeof nn.value === 'string') {
const m = nn.value.match(/<img[^>]+src=["']?([^"' >]+)["']?/i);
Eif (m && m[1]) return m[1];
}
Eif (nn.children && Array.isArray(nn.children)) {
// flatten node text instead of serializing the AST
const helpers = require('./helpers');
const flat = helpers.flattenNodeText(nn || '').replace(/\r?\n/g,' ');
const r = flat.match(/https?:\/\/[^"']+|\/.+?\.(png|jpe?g|gif|svg)/i);
Eif (r && r[0]) return r[0];
}
j++;
}
}
}
}
return null;
};
const preferred = findUnderHeading(/screenshots|screenshot|images|gallery/i);
if (preferred) return preferred;
const isRaster = (u) => /\.(png|jpe?g|gif)$/i.test(u);
const isLikelyProjectImage = (u) => /project[-_]?image|src\/assets\/imgs|src\/assets|assets\/imgs|assets\/img|project-image/i.test(u);
const walk = (n) => {
Iif (!n) return;
if (n.type === 'image' && n.url) {
candidates.push(n.url);
return;
}
if (n.type === 'html' && typeof n.value === 'string') {
const m = n.value.match(/<img[^>]+src=["']?([^"' >]+)["']?/i);
Eif (m && m[1]) candidates.push(m[1]);
}
if (n.children && Array.isArray(n.children)) for (const c of n.children) walk(c);
};
walk(ast);
for (const u of candidates) {
if (isLikelyProjectImage(u) && isRaster(u)) return u;
}
for (const u of candidates) {
if (isRaster(u) && !isBadgeLike(u)) return u;
}
for (const u of candidates) {
if (!/\.svg$/i.test(u) && !isBadgeLike(u)) return u;
}
return candidates.length ? candidates[0] : null;
}
module.exports = { findImageCandidateFromAst, isBadgeLike };
|