mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
feat(invoices): M4 PDF rendering — pdf-lib renderer + preview + download
Adds client-side PDF generation via pdf-lib (Helvetica standard fonts,
~7KB output, no font bytes shipped).
Renderer (pdf/renderer.ts)
- renderInvoicePdf(invoice, settings) → Uint8Array
- renderInvoicePdfBlob(...) → Blob for iframe / download / email attach
- Layout sections: header (sender + meta), recipient, subject, lines
table with wrapping + description row, totals with per-rate VAT
breakdown, notes, terms, footer
- Pagination: lines table opens a continuation page if content would
overflow into the QR-Bill reserved area; continuation pages redraw
the table header
Template (pdf/templates/default.ts)
- A4, margins in mm, emerald accent matching app icon
- Reserves 105mm at page bottom for the Swiss QR-Bill (M5) so the
body never collides with that region
DetailView integration
- Live PDF preview in an iframe — re-renders when invoice.updatedAt
changes (mutations bump the timestamp)
- Blob URLs revoked on render / unmount to avoid memory leaks
- "PDF herunterladen" button produces a Rechnung-{number}.pdf download
- Structured-data view moved behind <details> so the PDF is the primary
surface; raw data still accessible for debugging
pdf-lib dep added to @mana/web.
Plan: docs/plans/invoices-module.md §M4.
Next: M5 swissqrbill (Zahlteil in the reserved region).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0077752456
commit
2dc298a796
5 changed files with 1012 additions and 280 deletions
|
|
@ -28,7 +28,6 @@
|
|||
"@tailwindcss/vite": "^4.1.7",
|
||||
"@types/node": "^22.10.5",
|
||||
"@vite-pwa/sveltekit": "^1.1.0",
|
||||
"workbox-window": "^7.3.0",
|
||||
"@vitest/coverage-v8": "^4.1.2",
|
||||
"@vitest/ui": "^4.1.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
|
|
@ -44,7 +43,8 @@
|
|||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^6.0.7",
|
||||
"vitest": "^4.1.2"
|
||||
"vitest": "^4.1.2",
|
||||
"workbox-window": "^7.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@calc/shared": "workspace:*",
|
||||
|
|
@ -76,13 +76,14 @@
|
|||
"@mana/shared-utils": "workspace:*",
|
||||
"@mana/spiral-db": "workspace:*",
|
||||
"@mana/wallpaper-generator": "workspace:*",
|
||||
"@quotes/content": "workspace:*",
|
||||
"@types/pako": "^2.0.4",
|
||||
"@types/suncalc": "^1.9.2",
|
||||
"@quotes/content": "workspace:*",
|
||||
"date-fns": "^4.1.0",
|
||||
"dexie": "^4.0.11",
|
||||
"marked": "^17.0.5",
|
||||
"pako": "^2.1.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"rrule": "^2.8.1",
|
||||
"suncalc": "^1.9.0",
|
||||
"svelte-dnd-action": "^0.9.68",
|
||||
|
|
|
|||
549
apps/mana/apps/web/src/lib/modules/invoices/pdf/renderer.ts
Normal file
549
apps/mana/apps/web/src/lib/modules/invoices/pdf/renderer.ts
Normal file
|
|
@ -0,0 +1,549 @@
|
|||
/**
|
||||
* PDF renderer — turns an invoice + sender settings into a PDF Uint8Array.
|
||||
*
|
||||
* Layout (top-down, single page for short invoices):
|
||||
*
|
||||
* ┌─────────────────────────────────────────┐ ← MARGIN.top
|
||||
* │ Sender block Meta block │
|
||||
* │ │
|
||||
* │ Rechnung an: │
|
||||
* │ Client name + address │
|
||||
* │ │
|
||||
* │ RECHNUNG {number} │
|
||||
* │ {subject} │
|
||||
* │ │
|
||||
* │ Position | Menge | Preis | MwSt | Total │
|
||||
* │ ... │
|
||||
* │ │
|
||||
* │ Total: CHF x.y │
|
||||
* │ │
|
||||
* │ Notizen │
|
||||
* │ Zahlungsbedingungen │
|
||||
* │ │
|
||||
* │ ─────── (footer) ──────── │
|
||||
* ├─────────────────────────────────────────┤ ← BODY_MIN_Y
|
||||
* │ [reserved for QR-Bill — M5] │
|
||||
* └─────────────────────────────────────────┘
|
||||
*
|
||||
* Pagination kicks in only if the lines table overflows. Overflow pages
|
||||
* do not reserve QR space (only the page with the total does).
|
||||
*
|
||||
* Standard 14 PDF fonts only (Helvetica / Helvetica-Bold) — keeps the
|
||||
* output tiny (~7KB) and avoids shipping font bytes. Upgrade path: embed
|
||||
* Inter/Roboto via fetch in M7 when we care about brand typography.
|
||||
*/
|
||||
|
||||
import { PDFDocument, StandardFonts, rgb, type PDFPage, type PDFFont } from 'pdf-lib';
|
||||
import type { Invoice, InvoiceSettings } from '../types';
|
||||
import { CURRENCIES } from '../constants';
|
||||
import {
|
||||
A4,
|
||||
MARGIN,
|
||||
BODY_MIN_Y,
|
||||
FONT_SIZE,
|
||||
COLORS,
|
||||
LINE_COLS,
|
||||
SPACE,
|
||||
LINE_HEIGHT,
|
||||
} from './templates/default';
|
||||
|
||||
// ─── Small geometry helpers ────────────────────────────────
|
||||
|
||||
type Color = { r: number; g: number; b: number };
|
||||
const toRgb = (c: Color) => rgb(c.r, c.g, c.b);
|
||||
|
||||
interface RenderContext {
|
||||
doc: PDFDocument;
|
||||
page: PDFPage;
|
||||
regular: PDFFont;
|
||||
bold: PDFFont;
|
||||
/** Current Y cursor (drops as we render downward). */
|
||||
y: number;
|
||||
/** Right edge of the usable page area. */
|
||||
rightX: number;
|
||||
/** Left edge (same as MARGIN.left by default). */
|
||||
leftX: number;
|
||||
/** Content width (page - left - right). */
|
||||
contentWidth: number;
|
||||
}
|
||||
|
||||
function newPage(ctx: RenderContext, withQrReserve: boolean): RenderContext {
|
||||
const page = ctx.doc.addPage([A4.width, A4.height]);
|
||||
return {
|
||||
...ctx,
|
||||
page,
|
||||
y: A4.height - MARGIN.top,
|
||||
// Footer / QR reserve only applies to the first page — continuation
|
||||
// pages can use the full body height.
|
||||
// (The caller passes `withQrReserve=false` for continuation pages.)
|
||||
...(withQrReserve ? {} : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function textWidth(font: PDFFont, text: string, size: number): number {
|
||||
return font.widthOfTextAtSize(text, size);
|
||||
}
|
||||
|
||||
function drawText(
|
||||
ctx: RenderContext,
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
opts: { size?: number; font?: PDFFont; color?: Color } = {}
|
||||
): void {
|
||||
ctx.page.drawText(text, {
|
||||
x,
|
||||
y,
|
||||
size: opts.size ?? FONT_SIZE.body,
|
||||
font: opts.font ?? ctx.regular,
|
||||
color: toRgb(opts.color ?? COLORS.text),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Word-wrap for a given max width. Returns the list of lines (caller draws).
|
||||
* Tokenises on spaces + newlines; doesn't break mid-word (unusual for
|
||||
* invoice text, acceptable trade-off for the MVP).
|
||||
*/
|
||||
function wrapLines(font: PDFFont, text: string, size: number, maxWidth: number): string[] {
|
||||
const out: string[] = [];
|
||||
for (const paragraph of text.split('\n')) {
|
||||
const words = paragraph.split(/\s+/).filter(Boolean);
|
||||
if (words.length === 0) {
|
||||
out.push('');
|
||||
continue;
|
||||
}
|
||||
let current = '';
|
||||
for (const w of words) {
|
||||
const probe = current ? `${current} ${w}` : w;
|
||||
if (textWidth(font, probe, size) <= maxWidth) {
|
||||
current = probe;
|
||||
} else {
|
||||
if (current) out.push(current);
|
||||
current = w;
|
||||
}
|
||||
}
|
||||
if (current) out.push(current);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function drawRightAligned(
|
||||
ctx: RenderContext,
|
||||
text: string,
|
||||
rightX: number,
|
||||
y: number,
|
||||
opts: { size?: number; font?: PDFFont; color?: Color } = {}
|
||||
): void {
|
||||
const size = opts.size ?? FONT_SIZE.body;
|
||||
const font = opts.font ?? ctx.regular;
|
||||
const w = textWidth(font, text, size);
|
||||
drawText(ctx, text, rightX - w, y, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw horizontally-wrapped multi-line text starting at (x, y), dropping y
|
||||
* as it goes. Returns the new y (below the block).
|
||||
*/
|
||||
function drawBlock(
|
||||
ctx: RenderContext,
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
opts: {
|
||||
size?: number;
|
||||
font?: PDFFont;
|
||||
color?: Color;
|
||||
maxWidth: number;
|
||||
lineHeight?: number;
|
||||
}
|
||||
): number {
|
||||
const size = opts.size ?? FONT_SIZE.body;
|
||||
const font = opts.font ?? ctx.regular;
|
||||
const lh = size * (opts.lineHeight ?? LINE_HEIGHT.normal);
|
||||
const lines = wrapLines(font, text, size, opts.maxWidth);
|
||||
let cursor = y;
|
||||
for (const line of lines) {
|
||||
drawText(ctx, line, x, cursor, { size, font, color: opts.color });
|
||||
cursor -= lh;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
function formatAmount(minor: number, currency: keyof typeof CURRENCIES): string {
|
||||
const { symbol, minorUnit } = CURRENCIES[currency];
|
||||
return `${symbol} ${(minor / minorUnit).toFixed(2)}`;
|
||||
}
|
||||
|
||||
// ─── Section renderers ────────────────────────────────────
|
||||
|
||||
function renderHeader(ctx: RenderContext, invoice: Invoice, settings: InvoiceSettings): number {
|
||||
// Left: sender
|
||||
let leftY = ctx.y;
|
||||
drawText(ctx, settings.senderName || '—', ctx.leftX, leftY, {
|
||||
size: FONT_SIZE.brand,
|
||||
font: ctx.bold,
|
||||
});
|
||||
leftY -= FONT_SIZE.brand * LINE_HEIGHT.tight;
|
||||
|
||||
const senderBody = [settings.senderAddress, settings.senderEmail, settings.senderIban]
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
leftY = drawBlock(ctx, senderBody, ctx.leftX, leftY, {
|
||||
size: FONT_SIZE.small,
|
||||
color: COLORS.muted,
|
||||
maxWidth: ctx.contentWidth * 0.55,
|
||||
lineHeight: LINE_HEIGHT.normal,
|
||||
});
|
||||
|
||||
// Right: invoice meta (number, issue date, due date)
|
||||
let rightY = ctx.y;
|
||||
drawRightAligned(ctx, 'RECHNUNG', ctx.rightX, rightY, {
|
||||
size: FONT_SIZE.small,
|
||||
font: ctx.bold,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
rightY -= FONT_SIZE.small * LINE_HEIGHT.tight;
|
||||
drawRightAligned(ctx, invoice.number, ctx.rightX, rightY, {
|
||||
size: FONT_SIZE.brand,
|
||||
font: ctx.bold,
|
||||
});
|
||||
rightY -= FONT_SIZE.brand * LINE_HEIGHT.tight + SPACE.sm;
|
||||
|
||||
const metaRows: [string, string][] = [
|
||||
['Datum', invoice.issueDate],
|
||||
['Fällig am', invoice.dueDate],
|
||||
];
|
||||
if (settings.senderVatNumber) metaRows.push(['MwSt-Nr.', settings.senderVatNumber]);
|
||||
|
||||
for (const [label, value] of metaRows) {
|
||||
drawRightAligned(ctx, `${label}: ${value}`, ctx.rightX, rightY, {
|
||||
size: FONT_SIZE.small,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
rightY -= FONT_SIZE.small * LINE_HEIGHT.normal;
|
||||
}
|
||||
|
||||
// Return the lower of the two Ys so the next section clears both columns.
|
||||
return Math.min(leftY, rightY) - SPACE.lg;
|
||||
}
|
||||
|
||||
function renderRecipient(ctx: RenderContext, invoice: Invoice, y: number): number {
|
||||
drawText(ctx, 'Rechnung an', ctx.leftX, y, {
|
||||
size: FONT_SIZE.small,
|
||||
font: ctx.bold,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
y -= FONT_SIZE.small * LINE_HEIGHT.normal;
|
||||
|
||||
drawText(ctx, invoice.clientSnapshot.name, ctx.leftX, y, {
|
||||
size: FONT_SIZE.body,
|
||||
font: ctx.bold,
|
||||
});
|
||||
y -= FONT_SIZE.body * LINE_HEIGHT.tight;
|
||||
|
||||
if (invoice.clientSnapshot.address) {
|
||||
y = drawBlock(ctx, invoice.clientSnapshot.address, ctx.leftX, y, {
|
||||
maxWidth: ctx.contentWidth * 0.55,
|
||||
});
|
||||
}
|
||||
if (invoice.clientSnapshot.vatNumber) {
|
||||
drawText(ctx, `MwSt-Nr.: ${invoice.clientSnapshot.vatNumber}`, ctx.leftX, y, {
|
||||
size: FONT_SIZE.small,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
y -= FONT_SIZE.small * LINE_HEIGHT.normal;
|
||||
}
|
||||
return y - SPACE.lg;
|
||||
}
|
||||
|
||||
function renderSubject(ctx: RenderContext, invoice: Invoice, y: number): number {
|
||||
if (!invoice.subject) return y;
|
||||
drawText(ctx, invoice.subject, ctx.leftX, y, {
|
||||
size: FONT_SIZE.h1,
|
||||
font: ctx.bold,
|
||||
});
|
||||
return y - FONT_SIZE.h1 * LINE_HEIGHT.normal - SPACE.md;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the invoice lines table. If it overflows, opens a continuation page
|
||||
* and keeps going. Returns { ctx, y } — the ctx may have swapped pages so
|
||||
* later sections must use the returned one.
|
||||
*/
|
||||
function renderLinesTable(ctx: RenderContext, invoice: Invoice): { ctx: RenderContext; y: number } {
|
||||
let cur = ctx;
|
||||
let y = cur.y;
|
||||
const cw = cur.contentWidth;
|
||||
|
||||
const colX = {
|
||||
title: cur.leftX,
|
||||
qty: cur.leftX + cw * LINE_COLS.title,
|
||||
unit: cur.leftX + cw * (LINE_COLS.title + LINE_COLS.qty),
|
||||
unitPrice: cur.leftX + cw * (LINE_COLS.title + LINE_COLS.qty + LINE_COLS.unit),
|
||||
vat: cur.leftX + cw * (LINE_COLS.title + LINE_COLS.qty + LINE_COLS.unit + LINE_COLS.unitPrice),
|
||||
total:
|
||||
cur.leftX +
|
||||
cw * (LINE_COLS.title + LINE_COLS.qty + LINE_COLS.unit + LINE_COLS.unitPrice + LINE_COLS.vat),
|
||||
};
|
||||
const rightEdge = cur.rightX;
|
||||
|
||||
const drawHeaderRow = () => {
|
||||
drawText(cur, 'Position', colX.title, y, {
|
||||
size: FONT_SIZE.tableHeader,
|
||||
font: cur.bold,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
drawText(cur, 'Menge', colX.qty, y, {
|
||||
size: FONT_SIZE.tableHeader,
|
||||
font: cur.bold,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
drawText(cur, 'Einheit', colX.unit, y, {
|
||||
size: FONT_SIZE.tableHeader,
|
||||
font: cur.bold,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
drawRightAligned(cur, 'Einzelpreis', colX.vat - SPACE.sm, y, {
|
||||
size: FONT_SIZE.tableHeader,
|
||||
font: cur.bold,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
drawText(cur, 'MwSt.', colX.vat, y, {
|
||||
size: FONT_SIZE.tableHeader,
|
||||
font: cur.bold,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
drawRightAligned(cur, 'Total', rightEdge, y, {
|
||||
size: FONT_SIZE.tableHeader,
|
||||
font: cur.bold,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
y -= FONT_SIZE.tableHeader * LINE_HEIGHT.normal;
|
||||
cur.page.drawLine({
|
||||
start: { x: cur.leftX, y: y + SPACE.xs },
|
||||
end: { x: rightEdge, y: y + SPACE.xs },
|
||||
thickness: 0.5,
|
||||
color: toRgb(COLORS.border),
|
||||
});
|
||||
y -= SPACE.sm;
|
||||
};
|
||||
|
||||
drawHeaderRow();
|
||||
|
||||
for (const line of invoice.lines) {
|
||||
// Wrap the title so long descriptions don't run into the qty column.
|
||||
const titleMax = cw * LINE_COLS.title - SPACE.sm;
|
||||
const titleLines = wrapLines(cur.regular, line.title || '—', FONT_SIZE.tableCell, titleMax);
|
||||
const descriptionLines = line.description
|
||||
? wrapLines(cur.regular, line.description, FONT_SIZE.small, titleMax)
|
||||
: [];
|
||||
|
||||
const rowHeight =
|
||||
Math.max(
|
||||
titleLines.length * FONT_SIZE.tableCell * LINE_HEIGHT.tight +
|
||||
descriptionLines.length * FONT_SIZE.small * LINE_HEIGHT.tight,
|
||||
FONT_SIZE.tableCell * LINE_HEIGHT.normal
|
||||
) + SPACE.sm;
|
||||
|
||||
// Paginate: if this row would cross into the reserved footer, open
|
||||
// a new page and redraw the header there.
|
||||
if (y - rowHeight < BODY_MIN_Y) {
|
||||
cur = newPage(cur, false);
|
||||
y = cur.y;
|
||||
drawHeaderRow();
|
||||
}
|
||||
|
||||
// Title + optional description, wrapped
|
||||
let titleY = y;
|
||||
for (const tl of titleLines) {
|
||||
drawText(cur, tl, colX.title, titleY, { size: FONT_SIZE.tableCell });
|
||||
titleY -= FONT_SIZE.tableCell * LINE_HEIGHT.tight;
|
||||
}
|
||||
for (const dl of descriptionLines) {
|
||||
drawText(cur, dl, colX.title, titleY, {
|
||||
size: FONT_SIZE.small,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
titleY -= FONT_SIZE.small * LINE_HEIGHT.tight;
|
||||
}
|
||||
|
||||
// Single-line fields (qty / unit / price / vat / total)
|
||||
const qtyText = String(line.quantity);
|
||||
drawText(cur, qtyText, colX.qty, y, { size: FONT_SIZE.tableCell });
|
||||
if (line.unit) {
|
||||
drawText(cur, line.unit, colX.unit, y, {
|
||||
size: FONT_SIZE.tableCell,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
}
|
||||
drawRightAligned(cur, formatAmount(line.unitPrice, invoice.currency), colX.vat - SPACE.sm, y, {
|
||||
size: FONT_SIZE.tableCell,
|
||||
});
|
||||
drawText(cur, `${line.vatRate}%`, colX.vat, y, {
|
||||
size: FONT_SIZE.tableCell,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
const rowTotal = line.quantity * line.unitPrice * (1 - (line.discount ?? 0) / 100);
|
||||
drawRightAligned(cur, formatAmount(rowTotal, invoice.currency), rightEdge, y, {
|
||||
size: FONT_SIZE.tableCell,
|
||||
});
|
||||
|
||||
y -= rowHeight;
|
||||
}
|
||||
|
||||
// Separator before totals
|
||||
cur.page.drawLine({
|
||||
start: { x: cur.leftX, y },
|
||||
end: { x: rightEdge, y },
|
||||
thickness: 0.5,
|
||||
color: toRgb(COLORS.border),
|
||||
});
|
||||
return { ctx: cur, y: y - SPACE.md };
|
||||
}
|
||||
|
||||
function renderTotals(ctx: RenderContext, invoice: Invoice, y: number): number {
|
||||
const labelX = ctx.rightX - 180;
|
||||
const rows: [string, string, boolean][] = [];
|
||||
|
||||
rows.push(['Netto', formatAmount(invoice.totals.net, invoice.currency), false]);
|
||||
for (const b of invoice.totals.vatBreakdown) {
|
||||
rows.push([`MwSt. ${b.rate}%`, formatAmount(b.tax, invoice.currency), false]);
|
||||
}
|
||||
rows.push(['Total', formatAmount(invoice.totals.gross, invoice.currency), true]);
|
||||
|
||||
for (const [label, value, gross] of rows) {
|
||||
const size = gross ? FONT_SIZE.h2 : FONT_SIZE.body;
|
||||
const font = gross ? ctx.bold : ctx.regular;
|
||||
drawText(ctx, label, labelX, y, { size, font });
|
||||
drawRightAligned(ctx, value, ctx.rightX, y, { size, font });
|
||||
y -= size * LINE_HEIGHT.normal;
|
||||
if (gross) break;
|
||||
}
|
||||
|
||||
// Underline the gross row
|
||||
ctx.page.drawLine({
|
||||
start: { x: labelX, y: y + SPACE.xs },
|
||||
end: { x: ctx.rightX, y: y + SPACE.xs },
|
||||
thickness: 1,
|
||||
color: toRgb(COLORS.accent),
|
||||
});
|
||||
return y - SPACE.lg;
|
||||
}
|
||||
|
||||
function renderNotesAndTerms(ctx: RenderContext, invoice: Invoice, y: number): number {
|
||||
if (invoice.notes) {
|
||||
drawText(ctx, 'Notizen', ctx.leftX, y, {
|
||||
size: FONT_SIZE.small,
|
||||
font: ctx.bold,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
y -= FONT_SIZE.small * LINE_HEIGHT.normal;
|
||||
y = drawBlock(ctx, invoice.notes, ctx.leftX, y, {
|
||||
size: FONT_SIZE.body,
|
||||
maxWidth: ctx.contentWidth,
|
||||
});
|
||||
y -= SPACE.md;
|
||||
}
|
||||
if (invoice.terms) {
|
||||
drawText(ctx, 'Zahlungsbedingungen', ctx.leftX, y, {
|
||||
size: FONT_SIZE.small,
|
||||
font: ctx.bold,
|
||||
color: COLORS.muted,
|
||||
});
|
||||
y -= FONT_SIZE.small * LINE_HEIGHT.normal;
|
||||
y = drawBlock(ctx, invoice.terms, ctx.leftX, y, {
|
||||
size: FONT_SIZE.body,
|
||||
maxWidth: ctx.contentWidth,
|
||||
});
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
function renderFooter(ctx: RenderContext, settings: InvoiceSettings): void {
|
||||
if (!settings.footer) return;
|
||||
const y = MARGIN.bottom + ctx.contentWidth * 0; // just above the margin
|
||||
drawBlock(ctx, settings.footer, ctx.leftX, MARGIN.bottom + FONT_SIZE.small * 2.5, {
|
||||
size: FONT_SIZE.small,
|
||||
color: COLORS.muted,
|
||||
maxWidth: ctx.contentWidth,
|
||||
});
|
||||
// Hairline rule above footer
|
||||
ctx.page.drawLine({
|
||||
start: { x: ctx.leftX, y: MARGIN.bottom + FONT_SIZE.small * 3 },
|
||||
end: { x: ctx.rightX, y: MARGIN.bottom + FONT_SIZE.small * 3 },
|
||||
thickness: 0.25,
|
||||
color: toRgb(COLORS.border),
|
||||
});
|
||||
void y;
|
||||
}
|
||||
|
||||
// ─── Public API ───────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Render an invoice to PDF bytes. Call-site is responsible for wrapping the
|
||||
* output into a Blob, iframe URL, or File attachment.
|
||||
*/
|
||||
export async function renderInvoicePdf(
|
||||
invoice: Invoice,
|
||||
settings: InvoiceSettings
|
||||
): Promise<Uint8Array> {
|
||||
const doc = await PDFDocument.create();
|
||||
doc.setTitle(`Rechnung ${invoice.number}`);
|
||||
doc.setAuthor(settings.senderName || 'Mana');
|
||||
doc.setCreator('Mana — Rechnungen');
|
||||
doc.setProducer('pdf-lib');
|
||||
doc.setCreationDate(new Date());
|
||||
|
||||
const [regular, bold] = await Promise.all([
|
||||
doc.embedFont(StandardFonts.Helvetica),
|
||||
doc.embedFont(StandardFonts.HelveticaBold),
|
||||
]);
|
||||
|
||||
const page = doc.addPage([A4.width, A4.height]);
|
||||
const leftX = MARGIN.left;
|
||||
const rightX = A4.width - MARGIN.right;
|
||||
|
||||
let ctx: RenderContext = {
|
||||
doc,
|
||||
page,
|
||||
regular,
|
||||
bold,
|
||||
y: A4.height - MARGIN.top,
|
||||
leftX,
|
||||
rightX,
|
||||
contentWidth: rightX - leftX,
|
||||
};
|
||||
|
||||
ctx.y = renderHeader(ctx, invoice, settings);
|
||||
ctx.y = renderRecipient(ctx, invoice, ctx.y);
|
||||
ctx.y = renderSubject(ctx, invoice, ctx.y);
|
||||
const linesResult = renderLinesTable(ctx, invoice);
|
||||
ctx = linesResult.ctx;
|
||||
ctx.y = linesResult.y;
|
||||
ctx.y = renderTotals(ctx, invoice, ctx.y);
|
||||
ctx.y = renderNotesAndTerms(ctx, invoice, ctx.y);
|
||||
|
||||
// Footer only draws on the *first* page — it contains the sender's
|
||||
// legal line and a separator. Later we'll add page numbers for the
|
||||
// multi-page case.
|
||||
const firstPage = doc.getPage(0);
|
||||
const firstCtx: RenderContext = { ...ctx, page: firstPage };
|
||||
renderFooter(firstCtx, settings);
|
||||
|
||||
return await doc.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: render + wrap into a Blob, ready for object URL / download /
|
||||
* email attachment. The MIME type is `application/pdf`.
|
||||
*/
|
||||
export async function renderInvoicePdfBlob(
|
||||
invoice: Invoice,
|
||||
settings: InvoiceSettings
|
||||
): Promise<Blob> {
|
||||
const bytes = await renderInvoicePdf(invoice, settings);
|
||||
// `.slice(0)` copies into a fresh ArrayBuffer-backed Uint8Array so the
|
||||
// Blob constructor types match (SharedArrayBuffer is not a BlobPart).
|
||||
return new Blob([bytes.slice(0) as BlobPart], { type: 'application/pdf' });
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Default PDF layout constants — A4, margins chosen so a Swiss QR-Bill
|
||||
* (M5) will fit in the bottom ~105mm without further layout changes.
|
||||
*
|
||||
* pdf-lib coordinates: origin is BOTTOM-LEFT, Y increases upward. All
|
||||
* "top-of-page" anchors therefore compute `pageHeight - offset`.
|
||||
* Units are PDF points (1pt = 1/72 inch ≈ 0.353mm).
|
||||
*/
|
||||
|
||||
// ─── Page & units ─────────────────────────────────────────
|
||||
|
||||
/** A4 in points (210 × 297 mm). */
|
||||
export const A4 = { width: 595.28, height: 841.89 } as const;
|
||||
|
||||
export const MM_TO_PT = 72 / 25.4;
|
||||
|
||||
export function mm(millimetres: number): number {
|
||||
return millimetres * MM_TO_PT;
|
||||
}
|
||||
|
||||
// ─── Margins ─────────────────────────────────────────────
|
||||
|
||||
export const MARGIN = {
|
||||
top: mm(20),
|
||||
right: mm(20),
|
||||
bottom: mm(20),
|
||||
left: mm(20),
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Reserved vertical space at the bottom of the invoice page for the QR-Bill
|
||||
* (M5). Covers the whole Zahlteil + Empfangsschein strip including the
|
||||
* perforation indicator. The renderer must never draw into this region.
|
||||
* If the invoice body overflows, it paginates to a continuation page.
|
||||
*/
|
||||
export const QR_BILL_RESERVED = mm(105);
|
||||
|
||||
/** Minimum Y below which the first-page body must not descend. */
|
||||
export const BODY_MIN_Y = MARGIN.bottom + QR_BILL_RESERVED;
|
||||
|
||||
// ─── Typography ───────────────────────────────────────────
|
||||
|
||||
export const FONT_SIZE = {
|
||||
brand: 14,
|
||||
h1: 18,
|
||||
h2: 11,
|
||||
body: 10,
|
||||
small: 8.5,
|
||||
tableHeader: 9,
|
||||
tableCell: 9.5,
|
||||
} as const;
|
||||
|
||||
export const LINE_HEIGHT = {
|
||||
tight: 1.25,
|
||||
normal: 1.4,
|
||||
loose: 1.6,
|
||||
} as const;
|
||||
|
||||
// ─── Colors (RGB 0..1 for pdf-lib) ────────────────────────
|
||||
|
||||
export const COLORS = {
|
||||
text: { r: 0.06, g: 0.09, b: 0.16 }, // slate-900
|
||||
muted: { r: 0.39, g: 0.45, b: 0.55 }, // slate-500
|
||||
border: { r: 0.89, g: 0.91, b: 0.94 }, // slate-200
|
||||
accent: { r: 0.02, g: 0.59, b: 0.41 }, // emerald-600 — matches app icon
|
||||
danger: { r: 0.73, g: 0.11, b: 0.11 },
|
||||
surfaceMuted: { r: 0.97, g: 0.98, b: 0.99 }, // slate-50
|
||||
} as const;
|
||||
|
||||
// ─── Lines table geometry ─────────────────────────────────
|
||||
// Column widths are fractions of the content width; sum must equal 1.
|
||||
|
||||
export const LINE_COLS = {
|
||||
title: 0.46,
|
||||
qty: 0.09,
|
||||
unit: 0.08,
|
||||
unitPrice: 0.14,
|
||||
vat: 0.08,
|
||||
total: 0.15,
|
||||
} as const;
|
||||
|
||||
// ─── Spacing scale ────────────────────────────────────────
|
||||
|
||||
export const SPACE = {
|
||||
xs: mm(1),
|
||||
sm: mm(2),
|
||||
md: mm(4),
|
||||
lg: mm(8),
|
||||
xl: mm(12),
|
||||
} as const;
|
||||
|
|
@ -6,8 +6,10 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import StatusBadge from '../components/StatusBadge.svelte';
|
||||
import { invoicesStore } from '../stores/invoices.svelte';
|
||||
import { invoiceSettingsStore } from '../stores/settings.svelte';
|
||||
import { renderInvoicePdfBlob } from '../pdf/renderer';
|
||||
import { formatAmount } from '../queries';
|
||||
import type { Invoice } from '../types';
|
||||
import type { Invoice, InvoiceSettings } from '../types';
|
||||
import { STATUS_LABELS } from '../constants';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -19,6 +21,62 @@
|
|||
let actionError = $state<string | null>(null);
|
||||
let busy = $state(false);
|
||||
|
||||
// ─── PDF preview ─────────────────────────────────────────
|
||||
// Render lazily on mount, whenever the invoice content changes, or after
|
||||
// a mutation (status transitions don't re-render the PDF body but do
|
||||
// change the output — e.g. paid watermark in M4+). Revoke the previous
|
||||
// blob URL before creating a new one so we don't leak memory.
|
||||
let pdfUrl = $state<string | null>(null);
|
||||
let pdfError = $state<string | null>(null);
|
||||
let renderingPdf = $state(false);
|
||||
|
||||
async function renderPdf() {
|
||||
renderingPdf = true;
|
||||
pdfError = null;
|
||||
try {
|
||||
const settings: InvoiceSettings = await invoiceSettingsStore.get();
|
||||
const blob = await renderInvoicePdfBlob(invoice, settings);
|
||||
if (pdfUrl) URL.revokeObjectURL(pdfUrl);
|
||||
pdfUrl = URL.createObjectURL(blob);
|
||||
} catch (e) {
|
||||
pdfError = e instanceof Error ? e.message : 'PDF-Rendering fehlgeschlagen';
|
||||
} finally {
|
||||
renderingPdf = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-render when invoice id or updatedAt changes (mutations bump updatedAt).
|
||||
$effect(() => {
|
||||
void invoice.id;
|
||||
void invoice.updatedAt;
|
||||
renderPdf();
|
||||
});
|
||||
|
||||
// Blob URLs leak memory until revoked. Clean up on unmount.
|
||||
$effect(() => {
|
||||
return () => {
|
||||
if (pdfUrl) URL.revokeObjectURL(pdfUrl);
|
||||
};
|
||||
});
|
||||
|
||||
async function downloadPdf() {
|
||||
try {
|
||||
const settings: InvoiceSettings = await invoiceSettingsStore.get();
|
||||
const blob = await renderInvoicePdfBlob(invoice, settings);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `Rechnung-${invoice.number}.pdf`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
// Revoke after the browser has started the download.
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
} catch (e) {
|
||||
pdfError = e instanceof Error ? e.message : 'Download fehlgeschlagen';
|
||||
}
|
||||
}
|
||||
|
||||
async function run(label: string, fn: () => Promise<void>) {
|
||||
actionError = null;
|
||||
busy = true;
|
||||
|
|
@ -91,6 +149,7 @@
|
|||
Als bezahlt markieren
|
||||
</button>
|
||||
{/if}
|
||||
<button class="btn" onclick={downloadPdf}>PDF herunterladen</button>
|
||||
<button class="btn" onclick={onDuplicate} disabled={busy}>Duplizieren</button>
|
||||
{#if invoice.status !== 'paid' && invoice.status !== 'void'}
|
||||
<button class="btn btn-danger" onclick={onVoid} disabled={busy}> Stornieren </button>
|
||||
|
|
@ -104,80 +163,103 @@
|
|||
<div class="error">{actionError}</div>
|
||||
{/if}
|
||||
|
||||
<section class="block">
|
||||
<h3>Empfänger</h3>
|
||||
<div class="client">
|
||||
<div class="client-name">{invoice.clientSnapshot.name}</div>
|
||||
{#if invoice.clientSnapshot.address}
|
||||
<pre class="client-address">{invoice.clientSnapshot.address}</pre>
|
||||
{/if}
|
||||
{#if invoice.clientSnapshot.email}
|
||||
<div class="client-meta">{invoice.clientSnapshot.email}</div>
|
||||
{/if}
|
||||
{#if invoice.clientSnapshot.vatNumber}
|
||||
<div class="client-meta">MwSt-Nr.: {invoice.clientSnapshot.vatNumber}</div>
|
||||
<section class="block pdf-preview-block">
|
||||
<div class="preview-head">
|
||||
<h3>Vorschau</h3>
|
||||
{#if renderingPdf}
|
||||
<span class="preview-status">Rendert …</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if pdfError}
|
||||
<div class="error">PDF-Fehler: {pdfError}</div>
|
||||
{:else if pdfUrl}
|
||||
<iframe
|
||||
class="pdf-frame"
|
||||
src={pdfUrl}
|
||||
title="Vorschau Rechnung {invoice.number}"
|
||||
loading="lazy"
|
||||
></iframe>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="block">
|
||||
<h3>Positionen</h3>
|
||||
<table class="lines">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Position</th>
|
||||
<th>Menge</th>
|
||||
<th>Einzelpreis</th>
|
||||
<th>MwSt.</th>
|
||||
<th class="right">Netto</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each invoice.lines as line (line.id)}
|
||||
<details class="raw-details">
|
||||
<summary>Strukturierte Daten anzeigen</summary>
|
||||
|
||||
<section class="block">
|
||||
<h3>Empfänger</h3>
|
||||
<div class="client">
|
||||
<div class="client-name">{invoice.clientSnapshot.name}</div>
|
||||
{#if invoice.clientSnapshot.address}
|
||||
<pre class="client-address">{invoice.clientSnapshot.address}</pre>
|
||||
{/if}
|
||||
{#if invoice.clientSnapshot.email}
|
||||
<div class="client-meta">{invoice.clientSnapshot.email}</div>
|
||||
{/if}
|
||||
{#if invoice.clientSnapshot.vatNumber}
|
||||
<div class="client-meta">MwSt-Nr.: {invoice.clientSnapshot.vatNumber}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="block">
|
||||
<h3>Positionen</h3>
|
||||
<table class="lines">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<div>{line.title}</div>
|
||||
{#if line.description}<div class="muted">{line.description}</div>{/if}
|
||||
</td>
|
||||
<td>{line.quantity}{line.unit ? ` ${line.unit}` : ''}</td>
|
||||
<td>{formatAmount(line.unitPrice, invoice.currency)}</td>
|
||||
<td>{line.vatRate}%</td>
|
||||
<td class="right">
|
||||
{formatAmount(line.quantity * line.unitPrice, invoice.currency)}
|
||||
</td>
|
||||
<th>Position</th>
|
||||
<th>Menge</th>
|
||||
<th>Einzelpreis</th>
|
||||
<th>MwSt.</th>
|
||||
<th class="right">Netto</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each invoice.lines as line (line.id)}
|
||||
<tr>
|
||||
<td>
|
||||
<div>{line.title}</div>
|
||||
{#if line.description}<div class="muted">{line.description}</div>{/if}
|
||||
</td>
|
||||
<td>{line.quantity}{line.unit ? ` ${line.unit}` : ''}</td>
|
||||
<td>{formatAmount(line.unitPrice, invoice.currency)}</td>
|
||||
<td>{line.vatRate}%</td>
|
||||
<td class="right">
|
||||
{formatAmount(line.quantity * line.unitPrice, invoice.currency)}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="block totals-block">
|
||||
<h3>Summe</h3>
|
||||
<dl class="totals">
|
||||
<dt>Netto</dt>
|
||||
<dd>{formatAmount(invoice.totals.net, invoice.currency)}</dd>
|
||||
{#each invoice.totals.vatBreakdown as b (b.rate)}
|
||||
<dt>MwSt. {b.rate}%</dt>
|
||||
<dd>{formatAmount(b.tax, invoice.currency)}</dd>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="block totals-block">
|
||||
<h3>Summe</h3>
|
||||
<dl class="totals">
|
||||
<dt>Netto</dt>
|
||||
<dd>{formatAmount(invoice.totals.net, invoice.currency)}</dd>
|
||||
{#each invoice.totals.vatBreakdown as b (b.rate)}
|
||||
<dt>MwSt. {b.rate}%</dt>
|
||||
<dd>{formatAmount(b.tax, invoice.currency)}</dd>
|
||||
{/each}
|
||||
<dt class="gross">Total</dt>
|
||||
<dd class="gross">{formatAmount(invoice.totals.gross, invoice.currency)}</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{#if invoice.notes}
|
||||
<section class="block">
|
||||
<h3>Notizen</h3>
|
||||
<p class="prose">{invoice.notes}</p>
|
||||
<dt class="gross">Total</dt>
|
||||
<dd class="gross">{formatAmount(invoice.totals.gross, invoice.currency)}</dd>
|
||||
</dl>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if invoice.terms}
|
||||
<section class="block">
|
||||
<h3>Zahlungsbedingungen</h3>
|
||||
<p class="prose">{invoice.terms}</p>
|
||||
</section>
|
||||
{/if}
|
||||
{#if invoice.notes}
|
||||
<section class="block">
|
||||
<h3>Notizen</h3>
|
||||
<p class="prose">{invoice.notes}</p>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if invoice.terms}
|
||||
<section class="block">
|
||||
<h3>Zahlungsbedingungen</h3>
|
||||
<p class="prose">{invoice.terms}</p>
|
||||
</section>
|
||||
{/if}
|
||||
</details>
|
||||
|
||||
<footer class="meta">
|
||||
<div>Status: {STATUS_LABELS[invoice.status].de}</div>
|
||||
|
|
@ -269,6 +351,48 @@
|
|||
border-color: #fecaca;
|
||||
}
|
||||
|
||||
.pdf-preview-block {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.preview-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.preview-status {
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-text-muted, #64748b);
|
||||
}
|
||||
|
||||
.pdf-frame {
|
||||
width: 100%;
|
||||
height: 860px;
|
||||
border: 1px solid var(--color-border, #e2e8f0);
|
||||
border-radius: 0.4rem;
|
||||
background: var(--color-surface-muted, #f8fafc);
|
||||
}
|
||||
|
||||
.raw-details {
|
||||
border: 1px solid var(--color-border, #e2e8f0);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.raw-details summary {
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-muted, #64748b);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.raw-details[open] summary {
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--color-border, #e2e8f0);
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
|
|
|
|||
390
pnpm-lock.yaml
generated
390
pnpm-lock.yaml
generated
|
|
@ -138,14 +138,14 @@ importers:
|
|||
version: link:../../../../packages/shared-landing-ui
|
||||
astro:
|
||||
specifier: ^5.16.0
|
||||
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
typescript:
|
||||
specifier: ^5.9.2
|
||||
version: 5.9.3
|
||||
devDependencies:
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.18
|
||||
version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
|
@ -154,13 +154,13 @@ importers:
|
|||
version: 20.19.39
|
||||
eslint:
|
||||
specifier: ^9.0.0
|
||||
version: 9.39.4(jiti@1.21.7)
|
||||
version: 9.39.4(jiti@2.6.1)
|
||||
eslint-config-prettier:
|
||||
specifier: ^9.1.0
|
||||
version: 9.1.2(eslint@9.39.4(jiti@1.21.7))
|
||||
version: 9.1.2(eslint@9.39.4(jiti@2.6.1))
|
||||
eslint-plugin-astro:
|
||||
specifier: ^1.0.0
|
||||
version: 1.6.0(eslint@9.39.4(jiti@1.21.7))
|
||||
version: 1.6.0(eslint@9.39.4(jiti@2.6.1))
|
||||
prettier:
|
||||
specifier: ^3.6.2
|
||||
version: 3.8.1
|
||||
|
|
@ -253,10 +253,10 @@ importers:
|
|||
version: 3.7.2
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
astro:
|
||||
specifier: ^5.16.11
|
||||
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
tailwindcss:
|
||||
specifier: ^3.4.17
|
||||
version: 3.4.19(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
|
@ -594,6 +594,9 @@ importers:
|
|||
pako:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
pdf-lib:
|
||||
specifier: ^1.17.1
|
||||
version: 1.17.1
|
||||
rrule:
|
||||
specifier: ^2.8.1
|
||||
version: 2.8.1
|
||||
|
|
@ -6378,6 +6381,12 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@pdf-lib/standard-fonts@1.0.0':
|
||||
resolution: {integrity: sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==}
|
||||
|
||||
'@pdf-lib/upng@1.0.1':
|
||||
resolution: {integrity: sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==}
|
||||
|
||||
'@petamoriken/float16@3.9.3':
|
||||
resolution: {integrity: sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==}
|
||||
|
||||
|
|
@ -13403,6 +13412,9 @@ packages:
|
|||
resolution: {integrity: sha512-7vQ2xh0ZmjPjsuWONR68nqzb+QNfpPh7pdT6n6YDAthWAQiUkSACVegSswY5zPNONGYFWebFVgdnS5/m/Qqn+w==}
|
||||
hasBin: true
|
||||
|
||||
pako@1.0.11:
|
||||
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||
|
||||
pako@2.1.0:
|
||||
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
|
||||
|
||||
|
|
@ -13503,6 +13515,9 @@ packages:
|
|||
pathe@2.0.3:
|
||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||
|
||||
pdf-lib@1.17.1:
|
||||
resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==}
|
||||
|
||||
pend@1.2.0:
|
||||
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||
|
||||
|
|
@ -15301,6 +15316,9 @@ packages:
|
|||
resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tslib@1.14.1:
|
||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
|
|
@ -16668,16 +16686,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
astro: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
autoprefixer: 10.4.27(postcss@8.5.8)
|
||||
postcss: 8.5.8
|
||||
postcss-load-config: 4.0.2(postcss@8.5.8)
|
||||
tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
astro: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
|
|
@ -16698,6 +16706,16 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
astro: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
autoprefixer: 10.4.27(postcss@8.5.8)
|
||||
postcss: 8.5.8
|
||||
postcss-load-config: 4.0.2(postcss@8.5.8)
|
||||
tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
astro: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
|
|
@ -18857,11 +18875,6 @@ snapshots:
|
|||
'@esbuild/win32-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@1.21.7))':
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@1.21.7)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))':
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
|
@ -20581,6 +20594,14 @@ snapshots:
|
|||
'@pagefind/windows-x64@1.5.0':
|
||||
optional: true
|
||||
|
||||
'@pdf-lib/standard-fonts@1.0.0':
|
||||
dependencies:
|
||||
pako: 1.0.11
|
||||
|
||||
'@pdf-lib/upng@1.0.1':
|
||||
dependencies:
|
||||
pako: 1.0.11
|
||||
|
||||
'@petamoriken/float16@3.9.3': {}
|
||||
|
||||
'@pixi/colord@2.9.6': {}
|
||||
|
|
@ -23135,7 +23156,7 @@ snapshots:
|
|||
obug: 2.1.1
|
||||
std-env: 4.0.0
|
||||
tinyrainbow: 3.1.0
|
||||
vitest: 4.1.3(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(jsdom@29.0.2(@noble/hashes@2.0.1))(vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
vitest: 4.1.3(@opentelemetry/api@1.9.1)(@types/node@22.19.17)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(jsdom@29.0.2(@noble/hashes@2.0.1))(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
||||
'@vitest/expect@4.1.3':
|
||||
dependencies:
|
||||
|
|
@ -23197,7 +23218,7 @@ snapshots:
|
|||
sirv: 3.0.2
|
||||
tinyglobby: 0.2.15
|
||||
tinyrainbow: 3.1.0
|
||||
vitest: 4.1.3(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(jsdom@29.0.2(@noble/hashes@2.0.1))(vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
vitest: 4.1.3(@opentelemetry/api@1.9.1)(@types/node@22.19.17)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(jsdom@29.0.2(@noble/hashes@2.0.1))(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
||||
'@vitest/utils@4.1.3':
|
||||
dependencies:
|
||||
|
|
@ -23628,108 +23649,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.1
|
||||
'@astrojs/internal-helpers': 0.7.6
|
||||
'@astrojs/markdown-remark': 6.3.11
|
||||
'@astrojs/telemetry': 3.3.0
|
||||
'@capsizecss/unpack': 4.0.0
|
||||
'@oslojs/encoding': 1.1.0
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
|
||||
acorn: 8.16.0
|
||||
aria-query: 5.3.2
|
||||
axobject-query: 4.1.0
|
||||
boxen: 8.0.1
|
||||
ci-info: 4.4.0
|
||||
clsx: 2.1.1
|
||||
common-ancestor-path: 1.0.1
|
||||
cookie: 1.1.1
|
||||
cssesc: 3.0.0
|
||||
debug: 4.4.3
|
||||
deterministic-object-hash: 2.0.2
|
||||
devalue: 5.7.0
|
||||
diff: 8.0.4
|
||||
dlv: 1.1.3
|
||||
dset: 3.1.4
|
||||
es-module-lexer: 1.7.0
|
||||
esbuild: 0.27.7
|
||||
estree-walker: 3.0.3
|
||||
flattie: 1.1.1
|
||||
fontace: 0.4.1
|
||||
github-slugger: 2.0.0
|
||||
html-escaper: 3.0.3
|
||||
http-cache-semantics: 4.2.0
|
||||
import-meta-resolve: 4.2.0
|
||||
js-yaml: 4.1.1
|
||||
magic-string: 0.30.21
|
||||
magicast: 0.5.2
|
||||
mrmime: 2.0.1
|
||||
neotraverse: 0.6.18
|
||||
p-limit: 6.2.0
|
||||
p-queue: 8.1.1
|
||||
package-manager-detector: 1.6.0
|
||||
piccolore: 0.1.3
|
||||
picomatch: 4.0.4
|
||||
prompts: 2.4.2
|
||||
rehype: 13.0.2
|
||||
semver: 7.7.4
|
||||
shiki: 3.23.0
|
||||
smol-toml: 1.6.1
|
||||
svgo: 4.0.1
|
||||
tinyexec: 1.0.4
|
||||
tinyglobby: 0.2.15
|
||||
tsconfck: 3.1.6(typescript@5.9.3)
|
||||
ultrahtml: 1.6.0
|
||||
unifont: 0.7.4
|
||||
unist-util-visit: 5.1.0
|
||||
unstorage: 1.17.5(@azure/storage-blob@12.31.0)(ioredis@5.10.1)
|
||||
vfile: 6.0.3
|
||||
vite: 6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vitefu: 1.1.3(vite@6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
xxhash-wasm: 1.1.0
|
||||
yargs-parser: 21.1.1
|
||||
yocto-spinner: 0.2.3
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.25.2(zod@3.25.76)
|
||||
zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76)
|
||||
optionalDependencies:
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
- '@azure/data-tables'
|
||||
- '@azure/identity'
|
||||
- '@azure/keyvault-secrets'
|
||||
- '@azure/storage-blob'
|
||||
- '@capacitor/preferences'
|
||||
- '@deno/kv'
|
||||
- '@netlify/blobs'
|
||||
- '@planetscale/database'
|
||||
- '@types/node'
|
||||
- '@upstash/redis'
|
||||
- '@vercel/blob'
|
||||
- '@vercel/functions'
|
||||
- '@vercel/kv'
|
||||
- aws4fetch
|
||||
- db0
|
||||
- idb-keyval
|
||||
- ioredis
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- rollup
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- typescript
|
||||
- uploadthing
|
||||
- yaml
|
||||
|
||||
astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.1
|
||||
|
|
@ -23934,6 +23853,108 @@ snapshots:
|
|||
- uploadthing
|
||||
- yaml
|
||||
|
||||
astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.1
|
||||
'@astrojs/internal-helpers': 0.7.6
|
||||
'@astrojs/markdown-remark': 6.3.11
|
||||
'@astrojs/telemetry': 3.3.0
|
||||
'@capsizecss/unpack': 4.0.0
|
||||
'@oslojs/encoding': 1.1.0
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
|
||||
acorn: 8.16.0
|
||||
aria-query: 5.3.2
|
||||
axobject-query: 4.1.0
|
||||
boxen: 8.0.1
|
||||
ci-info: 4.4.0
|
||||
clsx: 2.1.1
|
||||
common-ancestor-path: 1.0.1
|
||||
cookie: 1.1.1
|
||||
cssesc: 3.0.0
|
||||
debug: 4.4.3
|
||||
deterministic-object-hash: 2.0.2
|
||||
devalue: 5.7.0
|
||||
diff: 8.0.4
|
||||
dlv: 1.1.3
|
||||
dset: 3.1.4
|
||||
es-module-lexer: 1.7.0
|
||||
esbuild: 0.27.7
|
||||
estree-walker: 3.0.3
|
||||
flattie: 1.1.1
|
||||
fontace: 0.4.1
|
||||
github-slugger: 2.0.0
|
||||
html-escaper: 3.0.3
|
||||
http-cache-semantics: 4.2.0
|
||||
import-meta-resolve: 4.2.0
|
||||
js-yaml: 4.1.1
|
||||
magic-string: 0.30.21
|
||||
magicast: 0.5.2
|
||||
mrmime: 2.0.1
|
||||
neotraverse: 0.6.18
|
||||
p-limit: 6.2.0
|
||||
p-queue: 8.1.1
|
||||
package-manager-detector: 1.6.0
|
||||
piccolore: 0.1.3
|
||||
picomatch: 4.0.4
|
||||
prompts: 2.4.2
|
||||
rehype: 13.0.2
|
||||
semver: 7.7.4
|
||||
shiki: 3.23.0
|
||||
smol-toml: 1.6.1
|
||||
svgo: 4.0.1
|
||||
tinyexec: 1.0.4
|
||||
tinyglobby: 0.2.15
|
||||
tsconfck: 3.1.6(typescript@5.9.3)
|
||||
ultrahtml: 1.6.0
|
||||
unifont: 0.7.4
|
||||
unist-util-visit: 5.1.0
|
||||
unstorage: 1.17.5(@azure/storage-blob@12.31.0)(ioredis@5.10.1)
|
||||
vfile: 6.0.3
|
||||
vite: 6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vitefu: 1.1.3(vite@6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
xxhash-wasm: 1.1.0
|
||||
yargs-parser: 21.1.1
|
||||
yocto-spinner: 0.2.3
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.25.2(zod@3.25.76)
|
||||
zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76)
|
||||
optionalDependencies:
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
- '@azure/data-tables'
|
||||
- '@azure/identity'
|
||||
- '@azure/keyvault-secrets'
|
||||
- '@azure/storage-blob'
|
||||
- '@capacitor/preferences'
|
||||
- '@deno/kv'
|
||||
- '@netlify/blobs'
|
||||
- '@planetscale/database'
|
||||
- '@types/node'
|
||||
- '@upstash/redis'
|
||||
- '@vercel/blob'
|
||||
- '@vercel/functions'
|
||||
- '@vercel/kv'
|
||||
- aws4fetch
|
||||
- db0
|
||||
- idb-keyval
|
||||
- ioredis
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- rollup
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- typescript
|
||||
- uploadthing
|
||||
- yaml
|
||||
|
||||
astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.1
|
||||
|
|
@ -25737,11 +25758,6 @@ snapshots:
|
|||
eslint: 9.39.4(jiti@2.6.1)
|
||||
semver: 7.7.4
|
||||
|
||||
eslint-compat-utils@0.6.5(eslint@9.39.4(jiti@1.21.7)):
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@1.21.7)
|
||||
semver: 7.7.4
|
||||
|
||||
eslint-compat-utils@0.6.5(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
|
@ -25751,10 +25767,6 @@ snapshots:
|
|||
dependencies:
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
||||
eslint-config-prettier@9.1.2(eslint@9.39.4(jiti@1.21.7)):
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@1.21.7)
|
||||
|
||||
eslint-config-prettier@9.1.2(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
|
@ -25799,20 +25811,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-astro@1.6.0(eslint@9.39.4(jiti@1.21.7)):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7))
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@typescript-eslint/types': 8.58.0
|
||||
astro-eslint-parser: 1.4.0
|
||||
eslint: 9.39.4(jiti@1.21.7)
|
||||
eslint-compat-utils: 0.6.5(eslint@9.39.4(jiti@1.21.7))
|
||||
globals: 16.5.0
|
||||
postcss: 8.5.8
|
||||
postcss-selector-parser: 7.1.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-astro@1.6.0(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
|
||||
|
|
@ -25986,47 +25984,6 @@ snapshots:
|
|||
|
||||
eslint-visitor-keys@5.0.1: {}
|
||||
|
||||
eslint@9.39.4(jiti@1.21.7):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7))
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@eslint/config-array': 0.21.2
|
||||
'@eslint/config-helpers': 0.4.2
|
||||
'@eslint/core': 0.17.0
|
||||
'@eslint/eslintrc': 3.3.5
|
||||
'@eslint/js': 9.39.4
|
||||
'@eslint/plugin-kit': 0.4.1
|
||||
'@humanfs/node': 0.16.7
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@humanwhocodes/retry': 0.4.3
|
||||
'@types/estree': 1.0.8
|
||||
ajv: 6.14.0
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.3
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 8.4.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
espree: 10.4.0
|
||||
esquery: 1.7.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
file-entry-cache: 8.0.0
|
||||
find-up: 5.0.0
|
||||
glob-parent: 6.0.2
|
||||
ignore: 5.3.2
|
||||
imurmurhash: 0.1.4
|
||||
is-glob: 4.0.3
|
||||
json-stable-stringify-without-jsonify: 1.0.1
|
||||
lodash.merge: 4.6.2
|
||||
minimatch: 3.1.5
|
||||
natural-compare: 1.4.0
|
||||
optionator: 0.9.4
|
||||
optionalDependencies:
|
||||
jiti: 1.21.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint@9.39.4(jiti@2.6.1):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
|
||||
|
|
@ -30221,6 +30178,8 @@ snapshots:
|
|||
'@pagefind/windows-arm64': 1.5.0
|
||||
'@pagefind/windows-x64': 1.5.0
|
||||
|
||||
pako@1.0.11: {}
|
||||
|
||||
pako@2.1.0: {}
|
||||
|
||||
parent-module@1.0.1:
|
||||
|
|
@ -30333,6 +30292,13 @@ snapshots:
|
|||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
pdf-lib@1.17.1:
|
||||
dependencies:
|
||||
'@pdf-lib/standard-fonts': 1.0.0
|
||||
'@pdf-lib/upng': 1.0.1
|
||||
pako: 1.0.11
|
||||
tslib: 1.14.1
|
||||
|
||||
pend@1.2.0: {}
|
||||
|
||||
performance-now@2.1.0:
|
||||
|
|
@ -32548,6 +32514,8 @@ snapshots:
|
|||
minimist: 1.2.8
|
||||
strip-bom: 3.0.0
|
||||
|
||||
tslib@1.14.1: {}
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tsm@2.3.0:
|
||||
|
|
@ -32986,23 +32954,6 @@ snapshots:
|
|||
lightningcss: 1.32.0
|
||||
terser: 5.46.1
|
||||
|
||||
vite@6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.8
|
||||
rollup: 4.60.1
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 20.19.39
|
||||
fsevents: 2.3.3
|
||||
jiti: 1.21.7
|
||||
lightningcss: 1.32.0
|
||||
terser: 5.46.1
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
|
|
@ -33037,6 +32988,23 @@ snapshots:
|
|||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vite@6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.8
|
||||
rollup: 4.60.1
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 24.12.2
|
||||
fsevents: 2.3.3
|
||||
jiti: 1.21.7
|
||||
lightningcss: 1.32.0
|
||||
terser: 5.46.1
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
|
|
@ -33054,10 +33022,6 @@ snapshots:
|
|||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vitefu@1.1.3(vite@6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
vitefu@1.1.3(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
|
@ -33066,6 +33030,10 @@ snapshots:
|
|||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
vitefu@1.1.3(vite@6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
vitefu@1.1.3(vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue