chore(export/share): inject font

This commit is contained in:
Elian Doran 2025-12-27 23:31:56 +02:00
parent da28f4505a
commit d5b04864c8
No known key found for this signature in database
3 changed files with 30 additions and 11 deletions

View File

@ -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<string, SearchIndexEntry> = 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() {

View File

@ -20,6 +20,12 @@ const MIME_TO_CSS_FORMAT_MAPPINGS: Record<typeof PREFERRED_MIME_TYPE[number], st
"font/woff2": "woff2"
};
export const MIME_TO_EXTENSION_MAPPINGS: Record<string, string> = {
"font/ttf": "ttf",
"font/woff": "woff",
"font/woff2": "woff2"
};
export interface IconPackManifest {
prefix: string;
icons: Record<string, {
@ -28,7 +34,7 @@ export interface IconPackManifest {
}>;
}
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)) {

View File

@ -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,