import fs from "fs";
import { marked } from "marked";
import puppeteer from "puppeteer-core";
// ---- Locate a Chrome/Chromium/Edge binary for PDF rendering ----
export function findChrome() {
const candidates = [
process.env.CHROME_PATH,
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
"/Applications/Chromium.app/Contents/MacOS/Chromium",
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
"/usr/bin/google-chrome",
"/usr/bin/chromium-browser",
"/usr/bin/chromium",
].filter(Boolean);
return candidates.find((p) => {
try { return fs.existsSync(p); } catch { return false; }
});
}
// ---- Safe file name for downloads / zip entries ----
export function safeName(s, fallback = "not") {
const clean = String(s || "")
.replace(/[\/\\?%*:|"<>]/g, "-")
.replace(/\s+/g, " ")
.trim();
return clean || fallback;
}
// Build "Parent/Child" folder path for a category id
export function categoryPath(catId, flatCats) {
const byId = new Map(flatCats.map((c) => [c.id, c]));
const parts = [];
let cur = byId.get(catId);
let guard = 0;
while (cur && guard++ < 10) {
parts.unshift(safeName(cur.name));
cur = cur.parent_id ? byId.get(cur.parent_id) : null;
}
return parts.join("/");
}
// ---- One note -> Markdown ----
export function noteToMarkdown(note, path = "") {
const lines = [`# ${note.title}`, ""];
const meta = [];
if (path) meta.push(`**Klasör:** ${path}`);
if (note.pinned) meta.push("**📌 Sabitlenmiş**");
meta.push(`**Güncelleme:** ${fmt(note.updated_at)}`);
lines.push(meta.join(" \n"), "", "---", "");
lines.push(note.content || "_(boş)_", "");
return lines.join("\n") + "\n";
}
function fmt(d) {
if (!d) return "-";
return new Date(d.replace(" ", "T")).toLocaleString("tr-TR", {
day: "2-digit", month: "long", year: "numeric", hour: "2-digit", minute: "2-digit",
});
}
function escapeHtml(s) {
return String(s ?? "").replace(/&/g, "&").replace(//g, ">");
}
// ---- Notes -> printable HTML (markdown content rendered) ----
export function notesToHtml(notes, pathFn, title = "Bilgi Merkezi") {
const body = notes
.map((n) => {
const path = pathFn(n.category_id);
return `
${escapeHtml(n.title)}${n.pinned ? ' 📌' : ""}
Not bulunamadı.
"} `; } // ---- Minimal dependency-free ZIP (stored / no compression) ---- const CRC_TABLE = (() => { const t = new Uint32Array(256); for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; t[n] = c >>> 0; } return t; })(); function crc32(buf) { let c = 0xffffffff; for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8); return (c ^ 0xffffffff) >>> 0; } // files: [{ name, data: string|Buffer }] -> Buffer (valid .zip, UTF-8 names) export function makeZip(files) { const parts = []; const central = []; let offset = 0; for (const f of files) { const nameBuf = Buffer.from(f.name, "utf8"); const data = Buffer.isBuffer(f.data) ? f.data : Buffer.from(f.data, "utf8"); const crc = crc32(data); const local = Buffer.alloc(30); local.writeUInt32LE(0x04034b50, 0); local.writeUInt16LE(20, 4); local.writeUInt16LE(0x0800, 6); // UTF-8 flag local.writeUInt16LE(0, 8); // store local.writeUInt32LE(0, 10); // time+date local.writeUInt32LE(crc, 14); local.writeUInt32LE(data.length, 18); local.writeUInt32LE(data.length, 22); local.writeUInt16LE(nameBuf.length, 26); local.writeUInt16LE(0, 28); parts.push(local, nameBuf, data); const cd = Buffer.alloc(46); cd.writeUInt32LE(0x02014b50, 0); cd.writeUInt16LE(20, 4); cd.writeUInt16LE(20, 6); cd.writeUInt16LE(0x0800, 8); cd.writeUInt16LE(0, 10); cd.writeUInt32LE(0, 12); cd.writeUInt32LE(crc, 16); cd.writeUInt32LE(data.length, 20); cd.writeUInt32LE(data.length, 24); cd.writeUInt16LE(nameBuf.length, 28); cd.writeUInt32LE(0, 30); // extra+comment len cd.writeUInt16LE(0, 34); // disk cd.writeUInt16LE(0, 36); // internal attr cd.writeUInt32LE(0, 38); // external attr cd.writeUInt32LE(offset, 42); central.push(cd, nameBuf); offset += local.length + nameBuf.length + data.length; } const centralBuf = Buffer.concat(central); const end = Buffer.alloc(22); end.writeUInt32LE(0x06054b50, 0); end.writeUInt16LE(files.length, 8); end.writeUInt16LE(files.length, 10); end.writeUInt32LE(centralBuf.length, 12); end.writeUInt32LE(offset, 16); return Buffer.concat([...parts, centralBuf, end]); } // ---- HTML -> PDF buffer ---- export async function htmlToPdf(html) { const executablePath = findChrome(); if (!executablePath) { throw new Error("PDF için Chrome bulunamadı. .env içine CHROME_PATH ekleyin."); } const browser = await puppeteer.launch({ executablePath, headless: "new", args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"], }); try { const page = await browser.newPage(); await page.setContent(html, { waitUntil: "networkidle0" }); return await page.pdf({ format: "A4", printBackground: true, margin: { top: "16mm", bottom: "16mm", left: "14mm", right: "14mm" }, }); } finally { await browser.close(); } }