افزودن صفحه جدید

سه راه برای اضافه کردن صفحه وجود دارد. برای هر سه، منبع حقیقت tools/manifest.mjs است.

راه اول: افزودن به منو (پیشنهادی)

در tools/manifest.mjs آیتم جدید را در بخش مناسب اضافه کنید:

{
  id: 'marketing',
  label: L('بازاریابی', 'Marketing', 'التسويق'),
  icon: 'megaphone',
  kind: 'list',
  children: [
    { id: 'mkt-campaigns', label: L('کمپین‌ها', 'Campaigns', 'الحملات'), icon: 'send', url: 'marketing/campaigns.html', resource: 'campaigns', kind: 'list' },
    { id: 'mkt-budget', label: L('بودجه', 'Budget', 'الميزانية'), icon: 'wallet2', url: 'marketing/budget.html', kind: 'overview' },
  ],
}

سپس:

npm run gen:pages    # صفحه‌ها ساخته می‌شوند
npm run build        # ساخت کامل

خروجی: src/pages/marketing/campaigns.html و src/pages/marketing/budget.html به‌همراه ورودی منو، breadcrumb، عنوان‌ها در سه زبان و مسیر در sitemap.xml.

کلیدمقدارهای مجازکار
kinddashboard \list \detail \form \app \doc \ui \system \authتعیین پوسته و رفتار پیش‌فرض
resourceنام منبع در src/services/index.jsجدول/داده صفحه از کجا بیاید
urlمسیر نسبی با پسوند .htmlآدرس نهایی صفحه
badge{ text, variant }نشان کنار آیتم منو

راه دوم: صفحه بدون منو

اگر صفحه فقط از یک جای دیگر لینک می‌شود (مثل جزئیات)، از extraPages استفاده کنید:

export const extraPages = [
  { url: 'marketing/campaign-details.html', label: L('جزئیات کمپین', 'Campaign Details', 'تفاصیل'), kind: 'app', resource: 'campaigns' },
];

راه سوم: بدنه اختصاصی

صفحه‌های تولیدشده بدنه‌شان یک جای‌نگهدار است:

<div data-app="overview" data-resource="campaigns" data-title="کمپین‌ها"></div>

برای نوشتن HTML دلخواه، فایل هم‌نام را در src/partials/pages/ بسازید (مسیر = مسیر صفحه):

src/partials/pages/marketing/budget.html

از این لحظه، این فایل به‌جای جای‌نگهدار در صفحه قرار می‌گیرد و در ساخت‌های بعدی حفظ می‌شود. نمونه واقعی در همین قالب: src/partials/pages/preview.html.

نکته: داخل این فایل‌ها می‌توانید از ../../../، NOVAADMIN، 1.1.0 و Modern. Persian-First. Enterprise Ready. استفاده کنید؛ این توکن‌ها در زمان ساخت جای‌گذاری می‌شوند

نوشتن کنترل‌کننده

اگر صفحه رفتار اختصاصی دارد، آن را در src/js/pages/ بنویسید و در src/main.js مسیرش را وصل کنید:

// src/js/pages/marketing.js
import { $, on, render, escapeHtml } from '../core/dom.js';
import * as kit from './kit.js';

export async function initMarketing() {
  const node = $('[data-app="overview"]');
  if (!node) return;
  node.dataset.appClaimed = '1';                    // جای‌نگهدار را از رندرکننده عمومی می‌گیرد
  const items = await kit.services.campaignService.list({ perPage: 12 });
  render(node, `<div class="dashboard-shell">${kit.pageHeader({ title: 'کمپین‌ها', icon: 'megaphone' })}…</div>`);
}
// src/main.js — داخل AREA_CONTROLLERS
'marketing/': async () => (await import('./js/pages/marketing.js')).initMarketing(),

نکته مهم: پرچم appClaimed را پیش از رندر بگذارید؛ وگرنه رندرکننده عمومی همان جای‌نگهدار را پر می‌کند و محتوای شما پاک می‌شود.

ترجمه صفحه

عنوان صفحه به‌صورت خودکار با کلید nav.&lt;id&gt; در src/locales/generated.js ساخته می‌شود و از label منیفست می‌آید. برای متن‌های داخل بدنه، از data-i18n و کلیدهای src/locales/*.js استفاده کنید.

چک‌لیست افزودن صفحه

  1. ورودی منیفست نوشته شد و npm run gen:pages سبز است
  2. مسیر صفحه در مرورگر باز می‌شود و در دو جهت RTL/LTR درست است
  3. در تم روشن و تاریک بررسی شد
  4. بدون خطای کنسول و بدون overflow افقی در ۳۲۰ پیکسل
  5. npm run qa:all سبز است

Adding a new page

There are three ways to add a page. For all three, the source of truth is tools/manifest.mjs.

Option 1: add it to the menu (recommended)

Add the new item to the right section in tools/manifest.mjs:

{
  id: 'marketing',
  label: L('بازاریابی', 'Marketing', 'التسويق'),
  icon: 'megaphone',
  kind: 'list',
  children: [
    { id: 'mkt-campaigns', label: L('کمپین‌ها', 'Campaigns', 'الحملات'), icon: 'send', url: 'marketing/campaigns.html', resource: 'campaigns', kind: 'list' },
    { id: 'mkt-budget', label: L('بودجه', 'Budget', 'الميزانية'), icon: 'wallet2', url: 'marketing/budget.html', kind: 'overview' },
  ],
}

Then:

npm run gen:pages    # pages are generated
npm run build        # full build

Output: src/pages/marketing/campaigns.html and src/pages/marketing/budget.html, together with the menu entry, breadcrumb, titles in three languages and the route in sitemap.xml.

KeyAllowed valuesPurpose
kinddashboard \list \detail \form \app \doc \ui \system \authChooses the shell and default behaviour
resourceA resource name in src/services/index.jsWhere the page's table/data comes from
urlRelative path with the .html extensionFinal page address
badge{ text, variant }Badge next to the menu item

Option 2: a page without a menu entry

If the page is only linked from somewhere else (like a details page), use extraPages:

export const extraPages = [
  { url: 'marketing/campaign-details.html', label: L('جزئیات کمپین', 'Campaign Details', 'تفاصیل'), kind: 'app', resource: 'campaigns' },
];

Option 3: a custom body

Generated pages have a placeholder as their body:

<div data-app="overview" data-resource="campaigns" data-title="Campaigns"></div>

To write your own HTML, create a file with the same name in src/partials/pages/ (path = page path):

src/partials/pages/marketing/budget.html

From then on this file is placed in the page instead of the placeholder and is preserved in later builds. A real example in this template: src/partials/pages/preview.html.

Tip: inside these files you can use ../../../, NOVAADMIN, 1.1.0 and Modern. Persian-First. Enterprise Ready.; these tokens are substituted at build time

Writing a controller

If the page has its own behaviour, write it in src/js/pages/ and wire its route in src/main.js:

// src/js/pages/marketing.js
import { $, on, render, escapeHtml } from '../core/dom.js';
import * as kit from './kit.js';

export async function initMarketing() {
  const node = $('[data-app="overview"]');
  if (!node) return;
  node.dataset.appClaimed = '1';                    // claims the placeholder from the generic renderer
  const items = await kit.services.campaignService.list({ perPage: 12 });
  render(node, `<div class="dashboard-shell">${kit.pageHeader({ title: 'Campaigns', icon: 'megaphone' })}…</div>`);
}
// src/main.js — inside AREA_CONTROLLERS
'marketing/': async () => (await import('./js/pages/marketing.js')).initMarketing(),

Important: set the appClaimed flag before rendering; otherwise the generic renderer fills the same placeholder and your content is wiped.

Translating the page

The page title is generated automatically with the nav.&lt;id&gt; key in src/locales/generated.js and comes from the manifest label. For text inside the body, use data-i18n and the keys in src/locales/*.js.

Checklist for adding a page

  1. The manifest entry is written and npm run gen:pages is green
  2. The page route opens in the browser and is correct in both RTL and LTR
  3. Checked in the light and dark themes
  4. No console errors and no horizontal overflow at 320 pixels
  5. npm run qa:all is green

إضافة صفحة جديدة

توجد ثلاث طرق لإضافة صفحة. ومصدر الحقيقة في الثلاث هو tools/manifest.mjs.

الطريقة الأولى: الإضافة إلى القائمة (موصى بها)

أضف العنصر الجديد في القسم المناسب من tools/manifest.mjs:

{
  id: 'marketing',
  label: L('بازاریابی', 'Marketing', 'التسويق'),
  icon: 'megaphone',
  kind: 'list',
  children: [
    { id: 'mkt-campaigns', label: L('کمپین‌ها', 'Campaigns', 'الحملات'), icon: 'send', url: 'marketing/campaigns.html', resource: 'campaigns', kind: 'list' },
    { id: 'mkt-budget', label: L('بودجه', 'Budget', 'الميزانية'), icon: 'wallet2', url: 'marketing/budget.html', kind: 'overview' },
  ],
}

ثم:

npm run gen:pages    # تُولَّد الصفحات
npm run build        # بناء كامل

المخرجات: src/pages/marketing/campaigns.html وsrc/pages/marketing/budget.html مع عنصر القائمة ومسار التنقل والعناوين بثلاث لغات والمسار في sitemap.xml.

المفتاحالقيم المسموح بهاالوظيفة
kinddashboard \list \detail \form \app \doc \ui \system \authتحديد الهيكل والسلوك الافتراضي
resourceاسم مورد في src/services/index.jsمصدر جدول/بيانات الصفحة
urlمسار نسبي بالامتداد .htmlالعنوان النهائي للصفحة
badge{ text, variant }شارة بجانب عنصر القائمة

الطريقة الثانية: صفحة بلا قائمة

إذا كانت الصفحة تُربط فقط من مكان آخر (مثل صفحة التفاصيل)، فاستخدم extraPages:

export const extraPages = [
  { url: 'marketing/campaign-details.html', label: L('جزئیات کمپین', 'Campaign Details', 'تفاصيل'), kind: 'app', resource: 'campaigns' },
];

الطريقة الثالثة: محتوى مخصص

محتوى الصفحات المولّدة هو موضع محجوز:

<div data-app="overview" data-resource="campaigns" data-title="الحملات"></div>

لكتابة HTML خاص بك، أنشئ ملفاً بالاسم نفسه في src/partials/pages/ (المسار = مسار الصفحة):

src/partials/pages/marketing/budget.html

من هذه اللحظة يوضع هذا الملف في الصفحة بدلاً من الموضع المحجوز ويُحفظ في عمليات البناء اللاحقة. مثال حقيقي في هذا القالب: src/partials/pages/preview.html.

ملاحظة: يمكنك داخل هذه الملفات استخدام ../../../ وNOVAADMIN و1.1.0 وModern. Persian-First. Enterprise Ready.؛ وتُستبدل هذه الرموز وقت البناء

كتابة المتحكم

إذا كان للصفحة سلوك خاص، فاكتبه في src/js/pages/ واربط مساره في src/main.js:

// src/js/pages/marketing.js
import { $, on, render, escapeHtml } from '../core/dom.js';
import * as kit from './kit.js';

export async function initMarketing() {
  const node = $('[data-app="overview"]');
  if (!node) return;
  node.dataset.appClaimed = '1';                    // يستحوذ على الموضع المحجوز من العارض العام
  const items = await kit.services.campaignService.list({ perPage: 12 });
  render(node, `<div class="dashboard-shell">${kit.pageHeader({ title: 'الحملات', icon: 'megaphone' })}…</div>`);
}
// src/main.js — داخل AREA_CONTROLLERS
'marketing/': async () => (await import('./js/pages/marketing.js')).initMarketing(),

مهم: اضبط العلامة appClaimed قبل العرض؛ وإلا فسيملأ العارض العام الموضع نفسه ويُمحى محتواك.

ترجمة الصفحة

يُنشأ عنوان الصفحة تلقائياً بالمفتاح nav.&lt;id&gt; في src/locales/generated.js ويأتي من label في البيان. وللنصوص داخل المحتوى، استخدم data-i18n ومفاتيح src/locales/*.js.

قائمة التحقق لإضافة صفحة

  1. كُتب عنصر البيان وnpm run gen:pages ناجح
  2. يُفتح مسار الصفحة في المتصفح وهو صحيح في الاتجاهين RTL/LTR
  3. فُحصت في السمتين الفاتحة والداكنة
  4. لا أخطاء في وحدة التحكم ولا تجاوز أفقي عند ٣٢٠ بكسل
  5. npm run qa:all ناجح