feat(icon_packs): integrate boxicons JSON

This commit is contained in:
Elian Doran 2025-12-27 18:04:16 +02:00
parent 0c77563672
commit 78bec0c782
No known key found for this signature in database
4 changed files with 10487 additions and 8531 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,17 @@
import "./note_icon.css"; import "./note_icon.css";
import { IconRegistry } from "@triliumnext/commons";
import { t } from "i18next"; import { t } from "i18next";
import { useEffect, useRef, useState } from "preact/hooks"; import { useEffect, useRef, useState } from "preact/hooks";
import FNote from "../entities/fnote"; import FNote from "../entities/fnote";
import attributes from "../services/attributes"; import attributes from "../services/attributes";
import server from "../services/server"; import server from "../services/server";
import type { Icon } from "./icon_list";
import ActionButton from "./react/ActionButton";
import Button from "./react/Button"; import Button from "./react/Button";
import Dropdown from "./react/Dropdown"; import Dropdown from "./react/Dropdown";
import { FormDropdownDivider, FormListItem } from "./react/FormList"; import { FormDropdownDivider, FormListItem } from "./react/FormList";
import FormTextBox from "./react/FormTextBox"; import FormTextBox from "./react/FormTextBox";
import { useNoteContext, useNoteLabel } from "./react/hooks"; import { useNoteContext, useNoteLabel } from "./react/hooks";
import Icon from "./react/Icon";
interface IconToCountCache { interface IconToCountCache {
iconClassToCountMap: Record<string, number>; iconClassToCountMap: Record<string, number>;
@ -21,12 +19,9 @@ interface IconToCountCache {
interface IconData { interface IconData {
iconToCount: Record<string, number>; iconToCount: Record<string, number>;
icons: Icon[]; icons: IconRegistry["sources"][number]["icons"];
} }
let fullIconData: {
icons: Icon[];
};
let iconToCountCache!: Promise<IconToCountCache> | null; let iconToCountCache!: Promise<IconToCountCache> | null;
export default function NoteIcon() { export default function NoteIcon() {
@ -62,31 +57,21 @@ function NoteIconList({ note }: { note: FNote }) {
useEffect(() => { useEffect(() => {
async function loadIcons() { async function loadIcons() {
if (!fullIconData) {
fullIconData = (await import("./icon_list.js")).default;
}
// Filter by text and/or category. // Filter by text and/or category.
let icons: Pick<Icon, "name" | "term" | "className">[] = [ let icons: IconRegistry["sources"][number]["icons"] = [
...fullIconData.icons, ...glob.iconRegistry.sources.map(s => s.icons).flat()
...glob.iconRegistry.sources.map(s => s.icons.map(icon => ({
name: icon.terms.at(0) ?? "",
term: icon.terms.slice(1),
className: icon.id
}))).flat()
]; ];
const processedSearch = search?.trim()?.toLowerCase(); const processedSearch = search?.trim()?.toLowerCase();
if (processedSearch || filterByPrefix !== null) { if (processedSearch || filterByPrefix !== null) {
icons = icons.filter((icon) => { icons = icons.filter((icon) => {
if (filterByPrefix) { if (filterByPrefix) {
if (!icon.className?.startsWith(`${filterByPrefix} `)) { if (!icon.id?.startsWith(`${filterByPrefix} `)) {
return false; return false;
} }
} }
if (processedSearch) { if (processedSearch) {
if (!icon.name.includes(processedSearch) && if (!icon.terms?.some((t) => t.includes(processedSearch))) {
!icon.term?.find((t) => t.includes(processedSearch))) {
return false; return false;
} }
} }
@ -99,8 +84,8 @@ function NoteIconList({ note }: { note: FNote }) {
const iconToCount = await getIconToCountMap(); const iconToCount = await getIconToCountMap();
if (iconToCount) { if (iconToCount) {
icons.sort((a, b) => { icons.sort((a, b) => {
const countA = iconToCount[a.className ?? ""] || 0; const countA = iconToCount[a.id ?? ""] || 0;
const countB = iconToCount[b.className ?? ""] || 0; const countB = iconToCount[b.id ?? ""] || 0;
return countB - countA; return countB - countA;
}); });
@ -168,8 +153,8 @@ function NoteIconList({ note }: { note: FNote }) {
</div> </div>
)} )}
{(iconData?.icons ?? []).map(({className, name}) => ( {(iconData?.icons ?? []).map(({ id, terms }) => (
<span class={className} title={name} /> <span key={id} class={id} title={terms[0]} />
))} ))}
</div> </div>
</> </>
@ -193,7 +178,7 @@ function IconFilterContent({ filterByPrefix, setFilterByPrefix }: {
<FormDropdownDivider /> <FormDropdownDivider />
{glob.iconRegistry.sources.map(({ prefix, name, icon }) => ( {glob.iconRegistry.sources.map(({ prefix, name, icon }) => (
<FormListItem prefix !== "bx" && <FormListItem
key={prefix} key={prefix}
onClick={() => setFilterByPrefix(prefix)} onClick={() => setFilterByPrefix(prefix)}
icon={icon} icon={icon}

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@ import { IconRegistry } from "@triliumnext/commons";
import type BAttachment from "../becca/entities/battachment"; import type BAttachment from "../becca/entities/battachment";
import type BNote from "../becca/entities/bnote"; import type BNote from "../becca/entities/bnote";
import boxiconsManifest from "./icon_pack_boxicons-v2.json";
import log from "./log"; import log from "./log";
import search from "./search/services/search"; import search from "./search/services/search";
import { safeExtractMessageAndStackFromError } from "./utils"; import { safeExtractMessageAndStackFromError } from "./utils";
@ -30,19 +31,32 @@ interface ProcessResult {
manifest: IconPackManifest; manifest: IconPackManifest;
fontMime: string; fontMime: string;
fontAttachmentId: string; fontAttachmentId: string;
manifestNote: BNote; title: string;
icon: string;
} }
export function getIconPacks() { export function getIconPacks() {
return search.searchNotes("#iconPack") const defaultIconPack: ProcessResult = {
manifest: boxiconsManifest,
fontMime: "font/woff2",
fontAttachmentId: "builtin-boxicons-v2",
title: "Boxicons",
icon: "bx bx-package"
};
const customIconPacks = search.searchNotes("#iconPack")
.map(iconPackNote => processIconPack(iconPackNote)) .map(iconPackNote => processIconPack(iconPackNote))
.filter(Boolean) as ProcessResult[]; .filter(Boolean) as ProcessResult[];
return [
defaultIconPack,
...customIconPacks
];
} }
export function generateIconRegistry(iconPacks: ProcessResult[]): IconRegistry { export function generateIconRegistry(iconPacks: ProcessResult[]): IconRegistry {
const sources: IconRegistry["sources"] = []; const sources: IconRegistry["sources"] = [];
for (const { manifest, manifestNote } of iconPacks) { for (const { manifest, title, icon } of iconPacks) {
const icons: IconRegistry["sources"][number]["icons"] = Object.entries(manifest.icons) const icons: IconRegistry["sources"][number]["icons"] = Object.entries(manifest.icons)
.map(( [id, { terms }] ) => { .map(( [id, { terms }] ) => {
if (!id || !terms) return null; if (!id || !terms) return null;
@ -53,8 +67,8 @@ export function generateIconRegistry(iconPacks: ProcessResult[]): IconRegistry {
sources.push({ sources.push({
prefix: manifest.prefix, prefix: manifest.prefix,
name: manifestNote.title, name: title,
icon: manifestNote.getIcon(), icon,
icons icons
}); });
} }
@ -79,7 +93,8 @@ export function processIconPack(iconPackNote: BNote): ProcessResult | undefined
manifest, manifest,
fontMime: attachment.mime, fontMime: attachment.mime,
fontAttachmentId: attachment.attachmentId, fontAttachmentId: attachment.attachmentId,
manifestNote: iconPackNote title: iconPackNote.title,
icon: iconPackNote.getIcon()
}; };
} }