سکشنساز مقاله — تم Elara
پیشنمایش زنده
\n ' +
html0.slice(A.j);
fs.writeFileSync(SELF, html1);
console.log(` CSS تم : ${(themeCss.length / 1024).toFixed(0)} KB`);
console.log(` CSS آیکونها : ${(iconCss.length / 1024).toFixed(0)} KB · آیکونها: ${icons.length}`);
console.log(` فونتها : ${Object.keys(fonts).join(' + ')} (${(Object.values(fonts).reduce((a, b) => a + b.length, 0) / 1024).toFixed(0)} KB)`);
console.log(` index.html : ${(fs.statSync(SELF).size / 1024 / 1024).toFixed(2)} MB`);
/* ── ۲) safelist ──────────────────────────────────────────────────────── */
/* هر ترکیبِ ممکن از فیلدهای انتخابی رندر میشود تا هیچ کلاسی جا نماند */
function renderAll() {
const out = [];
for (const def of registry) {
const fields = def.fields || [];
const base = {};
fields.forEach((f) => { base[f.name] = f.default !== undefined ? f.default : (f.type === 'switch' ? false : ''); });
const variants = fields.filter((f) => ['select', 'seg', 'switch'].includes(f.type));
const combos = variants.reduce((acc, f) => {
const vals = f.type === 'switch' ? [true, false] : (f.options || []).map((o) => o.value);
const next = [];
for (const a of acc) for (const v of vals) next.push({ ...a, [f.name]: v });
return next.length > 400 ? acc : next;
}, [{}]);
const set = new Set();
for (const c of [{}, ...combos]) {
try { set.add(def.render(({ ...base, ...c }), h)); }
catch (e) { console.warn(` ! خطا در ${def.id}: ${e.message}`); }
}
out.push({ def, html: [...set] });
}
return out;
}
const rendered = renderAll();
const allHtml = rendered.flatMap((r) => r.html);
const used = new Set();
for (const x of allHtml) for (const m of x.matchAll(/class="([^"]*)"/g))
m[1].split(/\s+/).filter(Boolean).forEach((c) => used.add(c));
const classList = [...used].sort();
const lines = [];
for (let i = 0; i < classList.length; i += 5)
lines.push(' ' + classList.slice(i, i + 5).map((c) => `class="${c}"`).join(' '));
const samples = rendered.map((r) =>
` ┌─ ${r.def.id} · ${r.def.title}\n` + r.html[0].split('\n').map((l) => ' ' + l).join('\n')
).join('\n\n');
const blade =
`{{--\n` +
` ساختهشده بهصورت خودکار توسط سکشنساز مقاله — دستی ویرایش نکنید.\n` +
` تاریخ ساخت: ${new Date().toISOString()}\n\n` +
` این فایل هیچ خروجیِ HTML ندارد؛ کل محتوایش داخل کامنتِ blade است و موقع\n` +
` رندر حذف میشود. تنها کارش این است که Tailwind هنگام بیلد، کلاسهای\n` +
` سکشنهای مقاله را ببیند و purge نکند — چون متنِ مقاله داخل دیتابیس است\n` +
` و در content تیلویند اسکن نمیشود.\n\n` +
` همان الگویی که تم برای کلاسهای CKEditor در\n` +
` assets/css/components/ck-content/base.scss بهکار برده، فقط اینجا خودکار.\n` +
`--}}\n\n` +
`{{-- ── نمونهی هر سکشن (فقط برای مرور) ─────────────────────────────\n\n${samples}\n\n--}}\n\n` +
`{{-- ── فهرست کاملِ کلاسها (${classList.length}) — این بخش را Tailwind میخواند ──\n\n${lines.join('\n')}\n\n--}}\n`;
const bladeDest = path.join(JUP, CFG.safelistTarget);
fs.mkdirSync(path.dirname(bladeDest), { recursive: true });
fs.writeFileSync(bladeDest, blade);
/* کدام کلاسها بدون این فایل میشکستند؟ */
const bare = await postcss([tailwindcss({
...themeConfig,
content: { files: [...themeContent, '!' + bladeDest] },
})]).process(scss.css, { from: undefined });
const MARKERS = new Set(['not-prose', 'group', 'peer']);
const esc = (c) => '.' + c.replace(/[!/:\[\]().%#,]/g, (m) => '\\' + m);
const missing = classList.filter((c) => !MARKERS.has(c) &&
!new RegExp(esc(c).replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[\\s{,:]').test(bare.css));
console.log(` کلاسهای سکشنها : ${classList.length} (بدون safelist ${missing.length} تایشان میشکست)`);
/* ── ۳) نصب وصلهی آیکون در تم ────────────────────────────────────────── */
const patchDest = path.join(JUP, CFG.iconPatchTarget);
fs.mkdirSync(path.dirname(patchDest), { recursive: true });
fs.writeFileSync(patchDest, ICON_PATCH);
const baseScss = path.join(JUP, CFG.componentsBase);
const useLine = '@use "./article-icons/base.scss" as component-article-icons-base;';
let useNote = 'از قبل بود';
if (!fs.existsSync(baseScss)) {
useNote = `✗ پیدا نشد — این خط را دستی اضافه کنید: ${useLine}`;
} else {
const cur = fs.readFileSync(baseScss, 'utf8');
if (!cur.includes('article-icons')) {
fs.writeFileSync(baseScss, cur.replace(/\s*$/, '\n') + '\n' + useLine + '\n');
useNote = 'اضافه شد';
}
}
console.log(`\n نوشته شد در پروژه:`);
console.log(` ${CFG.safelistTarget}`);
console.log(` ${CFG.iconPatchTarget}`);
console.log(` ${CFG.componentsBase} (@use: ${useNote})`);
console.log(`\n حالا در پروژه بیلد بگیرید: cd ${JUP} && npm run build:all:site`);
console.log(` ─────────────────────────────────────────────\n`);
// ESB-BUILD-END