diff --git a/apps/server/src/services/icon_packs.spec.ts b/apps/server/src/services/icon_packs.spec.ts index 4f41258b44..68ee6ca2b0 100644 --- a/apps/server/src/services/icon_packs.spec.ts +++ b/apps/server/src/services/icon_packs.spec.ts @@ -1,6 +1,21 @@ import { buildNote } from "../test/becca_easy_mocking"; import { determineBestFontAttachment, generateCss, IconPackManifest, processIconPack } from "./icon_packs"; +const manifest: IconPackManifest = { + name: "Boxicons v2", + prefix: "bx", + icons: { + "bx-ball": "\ue9c2", + "bxs-party": "\uec92" + } +}; + +const defaultAttachment = { + role: "file", + title: "Font", + mime: "font/woff2" +}; + describe("Processing icon packs", () => { it("doesn't crash if icon pack is incorrect type", () => { const iconPack = processIconPack(buildNote({ @@ -11,18 +26,12 @@ describe("Processing icon packs", () => { }); it("processes manifest", () => { - const manifest: IconPackManifest = { - name: "Boxicons v2", - prefix: "bx", - icons: { - "bx-ball": "\ue9c2", - "bxs-party": "\uec92" - } - }; const iconPack = processIconPack(buildNote({ type: "text", - content: JSON.stringify(manifest) + content: JSON.stringify(manifest), + attachments: [ defaultAttachment ] })); + expect(iconPack).toBeTruthy(); expect(iconPack?.manifest).toMatchObject(manifest); }); }); @@ -31,13 +40,7 @@ describe("Mapping attachments", () => { it("handles woff2", () => { const iconPackNote = buildNote({ type: "text", - attachments: [ - { - role: "file", - title: "Font", - mime: "font/woff2" - } - ] + attachments: [ defaultAttachment ] }); const attachment = determineBestFontAttachment(iconPackNote); expect(attachment?.mime).toStrictEqual("font/woff2"); @@ -109,7 +112,7 @@ describe("CSS generation", () => { "bxs-party": "\uec92" } }; - const iconPackNote = buildNote({ + const processedResult = processIconPack(buildNote({ type: "text", content: JSON.stringify(manifest), attachments: [ @@ -119,13 +122,12 @@ describe("CSS generation", () => { mime: "font/woff2" } ] - }); - const processedResult = processIconPack(iconPackNote); + })); expect(processedResult).toBeTruthy(); - const css = generateCss(processedResult!, iconPackNote); + const css = generateCss(processedResult!); - console.log(css); expect(css).toContain("@font-face"); expect(css).toContain("font-family: 'trilium-icon-pack-bx'"); + expect(css).toContain(`src: url('/api/attachments/${processedResult?.fontAttachmentId}/download') format('woff2');`); }); }); diff --git a/apps/server/src/services/icon_packs.ts b/apps/server/src/services/icon_packs.ts index a9fbaa7471..816160366c 100644 --- a/apps/server/src/services/icon_packs.ts +++ b/apps/server/src/services/icon_packs.ts @@ -6,7 +6,13 @@ const PREFERRED_MIME_TYPE = [ "font/woff2", "font/woff", "font/ttf" -]; +] as const; + +const MIME_TO_CSS_FORMAT_MAPPINGS: Record = { + "font/ttf": "truetype", + "font/woff": "woff", + "font/woff2": "woff2" +}; export interface IconPackManifest { name: string; @@ -16,6 +22,8 @@ export interface IconPackManifest { interface ProcessResult { manifest: IconPackManifest; + fontMime: string; + fontAttachmentId: string; } export function processIconPack(iconPackNote: BNote): ProcessResult | undefined { @@ -25,8 +33,16 @@ export function processIconPack(iconPackNote: BNote): ProcessResult | undefined return; } + const attachment = determineBestFontAttachment(iconPackNote); + if (!attachment || !attachment.attachmentId) { + log.error(`Icon pack is missing WOFF/WOFF2/TTF attachment: ${iconPackNote.title} (${iconPackNote.noteId})`); + return; + } + return { - manifest + manifest, + fontMime: attachment.mime, + fontAttachmentId: attachment.attachmentId }; } @@ -46,12 +62,13 @@ export function determineBestFontAttachment(iconPackNote: BNote) { return null; } -export function generateCss(processedIconPack: ProcessResult, iconPackNote: BNote) { +export function generateCss(processedIconPack: ProcessResult) { return `\ @font-face { font-family: 'trilium-icon-pack-${processedIconPack.manifest.prefix}'; font-weight: normal; font-style: normal; + src: url('/api/attachments/${processedIconPack.fontAttachmentId}/download') format('${MIME_TO_CSS_FORMAT_MAPPINGS[processedIconPack.fontMime]}'); } `; } diff --git a/apps/server/src/test/becca_easy_mocking.ts b/apps/server/src/test/becca_easy_mocking.ts index b36a9cdb3c..d809baa7f9 100644 --- a/apps/server/src/test/becca_easy_mocking.ts +++ b/apps/server/src/test/becca_easy_mocking.ts @@ -4,7 +4,7 @@ import BAttachment from "../becca/entities/battachment.js"; import BAttribute from "../becca/entities/battribute.js"; import BBranch from "../becca/entities/bbranch.js"; import BNote from "../becca/entities/bnote.js"; -import utils from "../services/utils.js"; +import utils, { randomString } from "../services/utils.js"; type AttributeDefinitions = { [key in `#${string}`]: string; }; type RelationDefinitions = { [key in `~${string}`]: string; }; @@ -118,6 +118,7 @@ export function buildNote(noteDef: NoteDefinition) { const allAttachments: BAttachment[] = []; for (const { title, role, mime } of noteDef.attachments) { const attachment = new BAttachment({ + attachmentId: randomString(10), ownerId: note.noteId, title, role,