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 89 90 91 92 93 94 95 96 97 | 1x 5x 1x 1x 2x 1x 12x 12x 12x 12x 18x 18x 18x 6x 6x 5x 12x 5x 2x 3x 5x 5x 11x 2x 9x 5x 8x 5x 1x 1x 1x | /*
* Builds the published `ui.icons.json` contract. Kept separate from build.ts
* (which runs the full build on import) so the parity-critical ordering,
* filtering, and formatting can be unit tested.
*/
import fs from 'node:fs/promises';
import path from 'node:path';
import type { IconFile, IconType, UiIconsCategory, UiIconsEntry } from '../src/types.js';
/* Legacy action filter: `new_custom*` symbols ship as assets but are left out of ui.icons.json. */
const LEGACY_EXCLUDED_ACTION_SYMBOL = /^new_custom/;
/* Returns the human-readable category description embedded in ui.icons.json. */
function getCategoryDescription(iconType: IconType): string {
switch (iconType) {
case 'action':
return 'Actions can be seen throughout the interface and represent actions a user can take on any given screen.';
case 'doctype':
return 'Doctype icons represent a type of file when a preview or image is unavailable';
case 'standard':
return 'Standard icons represent entities and objects within Salesforce.';
case 'utility':
return "Utility icons are used throughout the interface and are SVG's for extensibility.";
case 'custom':
return 'Custom icons are available for the identity of user created objects.';
default:
return '';
}
}
/*
* Compares symbols the way the legacy ui.icons.json was ordered: digit runs
* numerically (`custom2` before `custom10`), everything else by code point
* (`_` before letters).
*/
export function compareSymbols(a: string, b: string): number {
const chunkRegex = /\d+|\D+/g;
const aChunks = a.match(chunkRegex) ?? [];
const bChunks = b.match(chunkRegex) ?? [];
for (let i = 0; i < Math.min(aChunks.length, bChunks.length); i++) {
const aChunk = aChunks[i];
const bChunk = bChunks[i];
if (/^\d/.test(aChunk) && /^\d/.test(bChunk)) {
const diff = Number(aChunk) - Number(bChunk);
if (diff !== 0) {
return diff;
}
} else if (aChunk !== bChunk) {
return aChunk < bChunk ? -1 : 1;
}
}
return aChunks.length - bChunks.length;
}
/*
* Builds one category per icon type in alphabetical order, each listing its
* unique symbols sorted by `compareSymbols`. Matches the legacy published
* layout, including the legacy action filter that leaves `new_custom*` symbols
* out of the list even though their SVGs, PNGs, and sprite symbols still ship.
*/
export function buildUiIconsCategories(
iconTypes: IconType[],
iconsByType: Record<IconType, IconFile[]>,
): UiIconsCategory[] {
return [...iconTypes].sort().map((iconType) => {
const uniqueSymbols = new Set<string>();
for (const icon of iconsByType[iconType]) {
if (iconType === 'action' && LEGACY_EXCLUDED_ACTION_SYMBOL.test(icon.symbol)) {
continue;
}
uniqueSymbols.add(icon.symbol);
}
const icons = Array.from(uniqueSymbols)
.sort(compareSymbols)
.map<UiIconsEntry>((symbol) => ({
sprite: iconType,
symbol,
}));
return {
name: iconType,
description: getCategoryDescription(iconType),
icons,
};
});
}
/* Writes `ui.icons.json` into distDir and returns its path. */
export async function writeUiIconsJson(distDir: string, categories: UiIconsCategory[]): Promise<string> {
const jsonPath = path.join(distDir, 'ui.icons.json');
// No trailing newline, byte-matching the legacy ui.icons.json.
await fs.writeFile(jsonPath, JSON.stringify(categories, null, 2), 'utf8');
return jsonPath;
}
|