diff --git a/eleventy.config.mjs b/eleventy.config.mjs index 731a27ed0..e8ce4d235 100644 --- a/eleventy.config.mjs +++ b/eleventy.config.mjs @@ -23,6 +23,7 @@ const FEATURED_LABELS = [ 'theme', ] const LABELS_RANK = new Map(FEATURED_LABELS.map((label, index) => [label, index])) +const LABEL_ICON_MINIMUM_USAGE = 2 const MS_IN_DAY = 24 * 60 * 60 * 1000 const MAGIC_FRESHNESS_WINDOW_DAYS = 365 * 2 // bonus for packages that had updates @@ -375,6 +376,7 @@ export default async function (eleventyConfig) { const siteOrigin = isProd ? prodOrigin : devOrigin const staticOutputDir = isProd ? 'static_' + util.gitHash : 'static' const bundledScriptEntries = new Set() + let labelIcons = null eleventyConfig.addPassthroughCopy( { static: staticOutputDir }, @@ -389,6 +391,11 @@ export default async function (eleventyConfig) { eleventyConfig.on('eleventy.after', async ({ directories } = {}) => { const outputDir = directories?.output ?? '_site' await writeVendorModules(path.join(outputDir, staticOutputDir, 'vendor')) + writePrunedLabelIconSprite( + 'static/label-icons.svg', + path.join(outputDir, staticOutputDir, 'label-icons.svg'), + labelIcons?.sources, + ) if (!isProd) { return @@ -514,6 +521,8 @@ export default async function (eleventyConfig) { const packages = all_packages.map(packageData) const packagesWithMagic = computeMagicMetadata(packages) + const labels = util.collectLabels(all_packages) + labelIcons = filters.configureLabelIcons(labels, { minimumUsage: LABEL_ICON_MINIMUM_USAGE }) const livingHomePackages = packages.filter(pkg => !pkg.removed) @@ -603,9 +612,7 @@ export default async function (eleventyConfig) { } } - eleventyConfig.addCollection('labels', () => { - return util.collectLabels(all_packages) - }) + eleventyConfig.addCollection('labels', () => labels) eleventyConfig.addCollection('libraries', () => { return Object.values(workspace.libraries) @@ -668,6 +675,7 @@ export default async function (eleventyConfig) { // Register all named exports from external module as filters for (const [name, fn] of Object.entries(filters)) { + if (name === 'configureLabelIcons') continue eleventyConfig.addFilter(name, fn) } @@ -686,6 +694,21 @@ export default async function (eleventyConfig) { } } +function writePrunedLabelIconSprite(sourcePath, outputPath, visibleSources) { + if (!(visibleSources instanceof Set) || !fs.existsSync(sourcePath)) { + return + } + + const source = fs.readFileSync(sourcePath, 'utf8') + const pruned = source.replace( + /]*\bid="label-icon-([^"]+)"[^>]*>[\s\S]*?<\/symbol>\r?\n?/g, + (symbol, iconSource) => visibleSources.has(iconSource) ? symbol : '', + ) + + fs.mkdirSync(path.dirname(outputPath), { recursive: true }) + fs.writeFileSync(outputPath, pruned, 'utf8') +} + async function bundleJs(staticOutputDir, entries, isProd) { if (!entries.size) { return diff --git a/eleventy.filters.mjs b/eleventy.filters.mjs index ec8247587..2d3058962 100644 --- a/eleventy.filters.mjs +++ b/eleventy.filters.mjs @@ -14,6 +14,8 @@ const configPath = path.join(__dirname, 'label-icons-config.json') let labelIconSourceSet = new Set() let labelIconAliases = {} let labelIconTints = {} +let labelIconVisibleSourceSet = null +let labelIconVisibleTints = null const longDateFormatter = new Intl.DateTimeFormat('en-US', { dateStyle: 'long' }) const compactNumberFormatter = new Intl.NumberFormat('en', { notation: 'compact' }) @@ -67,7 +69,17 @@ export function label_icon_aliases_json() { } export function label_icon_tints_json() { - return JSON.stringify(labelIconTints) + return JSON.stringify(activeLabelIconTints()) +} + +export function configureLabelIcons(labels, { minimumUsage = 1 } = {}) { + labelIconVisibleSourceSet = visibleLabelIconSources(labels, minimumUsage) + labelIconVisibleTints = labelIconTintsForSources(labelIconVisibleSourceSet) + + return { + sources: labelIconVisibleSourceSet, + tints: labelIconVisibleTints, + } } export function label_normalization_note(changes) { @@ -102,7 +114,7 @@ export function search_index_json(packages) { return JSON.stringify({ packages: packages.map(compactSearchPackage), label_icon_aliases: labelIconAliases, - label_icon_tints: labelIconTints, + label_icon_tints: activeLabelIconTints(), }) } @@ -187,7 +199,30 @@ function joinAsSentenceList(parts) { return `${parts.slice(0, -1).join(', ')}, and ${parts.at(-1)}` } -function canonicalLabel(label) { +function visibleLabelIconSources(labels, minimumUsage) { + const counts = new Map() + const threshold = Math.max(1, Number(minimumUsage) || 1) + + for (const item of labels ?? []) { + const key = typeof item?.key === 'string' ? item.key : String(item ?? '') + const canonical = sourceLabelFor(key) + if (!canonical) continue + + const count = Number(item?.count ?? 1) + counts.set(canonical, (counts.get(canonical) ?? 0) + (Number.isFinite(count) ? count : 1)) + } + + const sources = new Set() + for (const source of labelIconSourceSet) { + if ((counts.get(source) ?? 0) >= threshold) { + sources.add(source) + } + } + + return sources +} + +function sourceLabelFor(label) { if (typeof label !== 'string') return '' const normalized = label.trim().toLowerCase() if (!normalized) return '' @@ -204,6 +239,42 @@ function canonicalLabel(label) { return '' } +function labelIconTintsForSources(sources) { + const tints = {} + for (const source of sources) { + if (Object.prototype.hasOwnProperty.call(labelIconTints, source)) { + tints[source] = labelIconTints[source] + } + } + return tints +} + +function activeLabelIconSourceSet() { + return labelIconVisibleSourceSet ?? labelIconSourceSet +} + +function activeLabelIconTints() { + return labelIconVisibleTints ?? labelIconTints +} + +function canonicalLabel(label) { + if (typeof label !== 'string') return '' + const normalized = label.trim().toLowerCase() + if (!normalized) return '' + + const sourceSet = activeLabelIconSourceSet() + const alias = labelIconAliases[normalized] + if (alias && sourceSet.has(alias)) { + return alias + } + + if (sourceSet.has(normalized)) { + return normalized + } + + return '' +} + export function label_icon_id(label) { const canonical = canonicalLabel(label) if (!canonical) return '' @@ -213,7 +284,7 @@ export function label_icon_id(label) { export function label_icon_tint(label) { const canonical = canonicalLabel(label) if (!canonical) return '' - return labelIconTints[canonical] ?? '' + return activeLabelIconTints()[canonical] ?? '' } // number formatting with grouping (e.g. 10,000) diff --git a/label-icons-config.json b/label-icons-config.json index 8714418ae..8952e5758 100644 --- a/label-icons-config.json +++ b/label-icons-config.json @@ -1,9 +1,10 @@ { "_": "Manually maintained set of icons to exclude and an alias map from labels to icon/language ids", - "exclude": ["ai"], + "exclude": ["ai", "c", "c#", "c++"], "aliases": { "javascript": "js", "ecmascript": "js", - "ecmascript6": "js" + "ecmascript6": "js", + "cpp": "c++" } } diff --git a/label-icons.json b/label-icons.json index 1a2a7730c..784ca9a6f 100644 --- a/label-icons.json +++ b/label-icons.json @@ -10,15 +10,11 @@ "binary": "graphite", "blade": "orange", "bower": "yellow", - "c": "purple", - "c#": "blue", - "c++": "sky", "cairo": "graphite", "clojure": "purple", "cmake": "blue", "coffeescript": "orange", "composer": "orange", - "cpp": "sky", "crystal": "graphite", "css": "blue", "csv": "green", diff --git a/static/label-icons.svg b/static/label-icons.svg index 17527e642..52e0e3305 100644 --- a/static/label-icons.svg +++ b/static/label-icons.svg @@ -38,15 +38,6 @@ - - - - - - - - - @@ -62,9 +53,6 @@ - - -