From d5b04864c8e750a36d85d409290ea968681682ee Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 27 Dec 2025 23:31:56 +0200 Subject: [PATCH] chore(export/share): inject font --- .../src/services/export/zip/share_theme.ts | 17 ++++++++++++++++- apps/server/src/services/icon_packs.ts | 18 ++++++++++++------ apps/server/src/share/content_renderer.ts | 6 ++---- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/apps/server/src/services/export/zip/share_theme.ts b/apps/server/src/services/export/zip/share_theme.ts index 5e3af2adf..5a960554d 100644 --- a/apps/server/src/services/export/zip/share_theme.ts +++ b/apps/server/src/services/export/zip/share_theme.ts @@ -9,6 +9,7 @@ import type BBranch from "../../../becca/entities/bbranch.js"; import type BNote from "../../../becca/entities/bnote.js"; import { getShareThemeAssetDir } from "../../../routes/assets"; import { getDefaultTemplatePath, readTemplate, renderNoteForExport } from "../../../share/content_renderer"; +import { getIconPacks, MIME_TO_EXTENSION_MAPPINGS, ProcessedIconPack } from "../../icon_packs"; import NoteMeta, { NoteMetaFile } from "../../meta/note_meta"; import { RESOURCE_DIR } from "../../resource_dir"; import { getResourceDir, isDev } from "../../utils"; @@ -29,6 +30,7 @@ export default class ShareThemeExportProvider extends ZipExportProvider { private indexMeta: NoteMeta | null = null; private searchIndex: Map = new Map(); private rootMeta: NoteMeta | null = null; + private iconPacks: ProcessedIconPack[] = []; prepareMeta(metaFile: NoteMetaFile): void { const assets = [ @@ -53,6 +55,7 @@ export default class ShareThemeExportProvider extends ZipExportProvider { dataFileName: "index.html" }; this.rootMeta = metaFile.files[0]; + this.iconPacks = getIconPacks(); metaFile.files.push(this.indexMeta); } @@ -70,7 +73,7 @@ export default class ShareThemeExportProvider extends ZipExportProvider { whitespaceCharacters: "\t\r\n\f\u200b\u00a0\u2002" }) : ""; - content = renderNoteForExport(note, branch, basePath, noteMeta.notePath.slice(0, -1)); + content = renderNoteForExport(note, branch, basePath, noteMeta.notePath.slice(0, -1), this.iconPacks); if (typeof content === "string") { content = content.replace(/href="[^"]*\.\/([a-zA-Z0-9_\/]{12})[^"]*"/g, (match, id) => { if (match.includes("/assets/")) return match; @@ -138,6 +141,18 @@ export default class ShareThemeExportProvider extends ZipExportProvider { const cssContent = getShareThemeAssets(assetMeta.dataFileName); this.archive.append(cssContent, { name: assetMeta.dataFileName }); } + + // Inject the custom fonts. + for (const iconPack of this.iconPacks) { + const attachment = becca.getAttachment(iconPack.fontAttachmentId); + if (!attachment) { + continue; + } + + const fontData = attachment.getContent(); + const fontFileName = `assets/icon-pack-${iconPack.manifest.prefix.toLowerCase()}.${MIME_TO_EXTENSION_MAPPINGS[attachment.mime]}`; + this.archive.append(fontData, { name: fontFileName }); + } } #save404() { diff --git a/apps/server/src/services/icon_packs.ts b/apps/server/src/services/icon_packs.ts index 061a7647d..ce69aeb6b 100644 --- a/apps/server/src/services/icon_packs.ts +++ b/apps/server/src/services/icon_packs.ts @@ -20,6 +20,12 @@ const MIME_TO_CSS_FORMAT_MAPPINGS: Record = { + "font/ttf": "ttf", + "font/woff": "woff", + "font/woff2": "woff2" +}; + export interface IconPackManifest { prefix: string; icons: Record; } -interface ProcessResult { +export interface ProcessedIconPack { manifest: IconPackManifest; manifestNoteId: string; fontMime: string; @@ -38,7 +44,7 @@ interface ProcessResult { } export function getIconPacks() { - const defaultIconPack: ProcessResult = { + const defaultIconPack: ProcessedIconPack = { manifest: boxiconsManifest, manifestNoteId: "builtin-boxicons-v2", fontMime: "font/woff2", @@ -49,7 +55,7 @@ export function getIconPacks() { const customIconPacks = search.searchNotes("#iconPack") .filter(note => !note.isProtected) .map(iconPackNote => processIconPack(iconPackNote)) - .filter(Boolean) as ProcessResult[]; + .filter(Boolean) as ProcessedIconPack[]; return [ defaultIconPack, @@ -57,7 +63,7 @@ export function getIconPacks() { ]; } -export function generateIconRegistry(iconPacks: ProcessResult[]): IconRegistry { +export function generateIconRegistry(iconPacks: ProcessedIconPack[]): IconRegistry { const sources: IconRegistry["sources"] = []; for (const { manifest, title, icon } of iconPacks) { @@ -80,7 +86,7 @@ export function generateIconRegistry(iconPacks: ProcessResult[]): IconRegistry { return { sources }; } -export function processIconPack(iconPackNote: BNote): ProcessResult | undefined { +export function processIconPack(iconPackNote: BNote): ProcessedIconPack | undefined { const manifest = iconPackNote.getJsonContentSafely() as IconPackManifest; if (!manifest) { log.error(`Icon pack is missing JSON manifest (or has syntax errors): ${iconPackNote.title} (${iconPackNote.noteId})`); @@ -119,7 +125,7 @@ export function determineBestFontAttachment(iconPackNote: BNote) { return null; } -export function generateCss({ manifest, fontAttachmentId, fontMime }: ProcessResult, isShare = false) { +export function generateCss({ manifest, fontAttachmentId, fontMime }: ProcessedIconPack, isShare = false) { try { const iconDeclarations: string[] = []; for (const [ key, mapping ] of Object.entries(manifest.icons)) { diff --git a/apps/server/src/share/content_renderer.ts b/apps/server/src/share/content_renderer.ts index 02ff9e80a..023c70cf9 100644 --- a/apps/server/src/share/content_renderer.ts +++ b/apps/server/src/share/content_renderer.ts @@ -12,7 +12,7 @@ import BAttachment from '../becca/entities/battachment.js'; import type BBranch from "../becca/entities/bbranch.js"; import BNote from "../becca/entities/bnote.js"; import assetPath, { assetUrlFragment } from "../services/asset_path.js"; -import { generateCss, getIconPacks } from "../services/icon_packs.js"; +import { generateCss, getIconPacks, ProcessedIconPack } from "../services/icon_packs.js"; import log from "../services/log.js"; import options from "../services/options.js"; import utils, { getResourceDir, isDev, safeExtractMessageAndStackFromError } from "../services/utils.js"; @@ -69,14 +69,12 @@ function getSharedSubTreeRoot(note: SNote | BNote | undefined): Subroot { return getSharedSubTreeRoot(parentBranch.getParentNote()); } -export function renderNoteForExport(note: BNote, parentBranch: BBranch, basePath: string, ancestors: string[]) { +export function renderNoteForExport(note: BNote, parentBranch: BBranch, basePath: string, ancestors: string[], iconPacks: ProcessedIconPack[]) { const subRoot: Subroot = { branch: parentBranch, note: parentBranch.getNote() }; - const iconPacks = getIconPacks(); - return renderNoteContentInternal(note, { subRoot, rootNoteId: parentBranch.noteId,