صفحهٔ اصلی: تفاوت میان نسخه‌ها

از shakiba
پرش به ناوبری پرش به جستجو
بدون خلاصۀ ویرایش
 
برچسب: برگردانده‌شده
خط ۳: خط ۳:
برای اطلاعات بیشتر در مورد به‌کارگیری نرم‌افزار ویکی به [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents فهرست راهنمای کاربر] مراجعه کنید.
برای اطلاعات بیشتر در مورد به‌کارگیری نرم‌افزار ویکی به [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents فهرست راهنمای کاربر] مراجعه کنید.


== آغاز به کار ==
<!DOCTYPE html>
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings فهرست تنظیمات پیکربندی]
<html lang="fa" dir="rtl">
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ پرسش‌های متداول مدیاویکی]
<head>
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ فهرست ایمیلی نسخه‌های مدیاویکی]
  <meta charset="UTF-8">
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources بومی‌سازی مدیاویکی برای زبان شما]
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam چگونگی مقابله با اسپم در ویکی خودتان را یاد بگیرید]
  <title>ویکی شخصی من</title>
  <style>
    @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700&display=swap');
 
    * { margin: 0; padding: 0; box-sizing: border-box; }
 
    :root {
      --bg: #f8f9fa;
      --surface: #ffffff;
      --sidebar-bg: #f0f2f5;
      --border: #a2a9b1;
      --border-light: #c8ccd1;
      --text: #202122;
      --text-muted: #54595d;
      --text-light: #72777d;
      --accent: #3366cc;
      --accent-hover: #2a4b8d;
      --accent-visited: #795cb2;
      --link: #0645ad;
      --link-hover: #0b0080;
      --link-visited: #0b0080;
      --tab-bg: #eaecf0;
      --tab-active: #ffffff;
      --toc-bg: #f8f9fa;
      --toc-border: #a2a9b1;
      --danger: #b32424;
      --success: #14866d;
      --warning: #e07d10;
      --tag-green: #14866d;
      --tag-blue: #3366cc;
      --tag-orange: #b7570a;
      --tag-purple: #795cb2;
      --shadow: 0 1px 3px rgba(0,0,0,0.08);
    }
 
    body {
      font-family: 'Vazirmatn', sans-serif;
      background: var(--bg);
      color: var(--text);
      font-size: 14px;
      line-height: 1.7;
    }
 
    a { color: var(--link); text-decoration: none; }
    a:hover { text-decoration: underline; color: var(--link-hover); }
 
    /* === Personal Bar (top) === */
    .personal-bar {
      background: var(--surface);
      border-bottom: 1px solid var(--border-light);
      padding: 0.25rem 1rem;
      font-size: 0.82rem;
      display: flex;
      justify-content: flex-start;
      align-items: center;
      gap: 1rem;
      color: var(--text-muted);
    }
 
    .personal-bar a { color: var(--link); font-size: 0.82rem; }
 
    /* === Main Header === */
    .wiki-header {
      background: var(--surface);
      padding: 0.8rem 1.5rem;
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 3px solid var(--accent);
    }
 
    .wiki-logo {
      display: flex;
      align-items: center;
      gap: 0.75rem;
    }
 
    .wiki-logo-icon {
      width: 50px;
      height: 50px;
      background: var(--accent);
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
      font-size: 1.6rem;
      font-weight: 700;
    }
 
    .wiki-logo h1 {
      font-size: 1.5rem;
      font-weight: 400;
      color: var(--text);
    }
 
    .wiki-logo h1 span {
      font-weight: 700;
    }
 
    .header-search {
      display: flex;
      align-items: center;
      gap: 0.4rem;
    }
 
    .header-search input {
      padding: 0.45rem 0.8rem;
      border: 1px solid var(--border);
      border-radius: 2px;
      font-family: inherit;
      font-size: 0.9rem;
      width: 300px;
      direction: rtl;
    }
 
    .header-search input:focus {
      outline: none;
      border-color: var(--accent);
      box-shadow: 0 0 0 2px rgba(51,102,204,0.15);
    }
 
    .header-search button {
      padding: 0.45rem 0.8rem;
      background: var(--accent);
      color: white;
      border: 1px solid var(--accent);
      font-family: inherit;
      font-size: 0.85rem;
      cursor: pointer;
      border-radius: 2px;
    }
 
    .header-search button:hover { background: var(--accent-hover); }
 
    /* === Body Layout === */
    .wiki-body {
      display: flex;
      max-width: 1400px;
      margin: 0 auto;
      min-height: calc(100vh - 120px);
    }
 
    /* === Sidebar === */
    .wiki-sidebar {
      width: 220px;
      min-width: 220px;
      background: var(--surface);
      border-left: 1px solid var(--border-light);
      padding: 1rem 0;
      font-size: 0.88rem;
    }
 
    .sidebar-section {
      margin-bottom: 0.5rem;
    }
 
    .sidebar-section h4 {
      padding: 0.4rem 1rem;
      font-size: 0.78rem;
      font-weight: 700;
      color: var(--text-muted);
      text-transform: uppercase;
      letter-spacing: 0.05em;
      background: var(--tab-bg);
      border-top: 1px solid var(--border-light);
      border-bottom: 1px solid var(--border-light);
    }
 
    .sidebar-section ul {
      list-style: none;
      padding: 0.3rem 0;
    }
 
    .sidebar-section li a {
      display: block;
      padding: 0.3rem 1rem;
      color: var(--accent);
      transition: background 0.15s;
    }
 
    .sidebar-section li a:hover {
      background: var(--tab-bg);
      text-decoration: none;
    }
 
    .sidebar-section li a.active-page {
      background: var(--accent);
      color: white;
      font-weight: 500;
    }
 
    .sidebar-search {
      padding: 0.5rem 1rem;
    }
 
    .sidebar-search input {
      width: 100%;
      padding: 0.35rem 0.6rem;
      border: 1px solid var(--border);
      font-family: inherit;
      font-size: 0.85rem;
      direction: rtl;
    }
 
    .sidebar-search input:focus {
      outline: none;
      border-color: var(--accent);
    }
 
    .sidebar-search button {
      width: 100%;
      margin-top: 0.3rem;
      padding: 0.3rem;
      background: var(--tab-bg);
      border: 1px solid var(--border);
      font-family: inherit;
      font-size: 0.82rem;
      cursor: pointer;
      color: var(--text-muted);
    }
 
    .sidebar-search button:hover { background: var(--border-light); }
 
    /* === Content Area === */
    .wiki-content {
      flex: 1;
      min-width: 0;
    }
 
    /* Tabs */
    .content-tabs {
      display: flex;
      background: var(--tab-bg);
      border-bottom: 1px solid var(--border-light);
      padding: 0 1rem;
      gap: 0;
    }
 
    .content-tab {
      padding: 0.5rem 1rem;
      font-size: 0.88rem;
      color: var(--text-muted);
      cursor: pointer;
      border: 1px solid transparent;
      border-bottom: none;
      background: none;
      font-family: inherit;
      border-radius: 2px 2px 0 0;
      position: relative;
      top: 1px;
      transition: background 0.15s;
    }
 
    .content-tab:hover {
      background: var(--surface);
      color: var(--text);
    }
 
    .content-tab.active {
      background: var(--surface);
      color: var(--text);
      border-color: var(--border-light);
      border-bottom-color: var(--surface);
      font-weight: 500;
    }
 
    /* Article Content */
    .article-body {
      background: var(--surface);
      border: 1px solid var(--border-light);
      border-top: none;
      padding: 1.5rem 2rem;
      min-height: 400px;
    }
 
    .article-body h1.article-title {
      font-size: 1.8rem;
      font-weight: 400;
      border-bottom: 1px solid var(--border-light);
      padding-bottom: 0.3rem;
      margin-bottom: 1rem;
      line-height: 1.4;
    }
 
    .article-intro {
      margin-bottom: 1.5rem;
    }
 
    /* Table of Contents */
    .toc {
      background: var(--toc-bg);
      border: 1px solid var(--toc-border);
      padding: 0.8rem 1rem;
      display: inline-block;
      margin: 0 0 1.5rem;
      min-width: 200px;
      font-size: 0.9rem;
    }
 
    .toc-title {
      font-weight: 700;
      text-align: center;
      margin-bottom: 0.3rem;
    }
 
    .toc-toggle {
      font-size: 0.82rem;
      color: var(--link);
      cursor: pointer;
      margin-right: 0.3rem;
    }
 
    .toc ol {
      margin: 0;
      padding-right: 1.2rem;
      list-style-type: decimal;
    }
 
    .toc ol ol {
      list-style-type: decimal;
    }
 
    .toc li {
      margin: 0.2rem 0;
    }
 
    .toc li a {
      color: var(--link);
    }
 
    /* Section Headings */
    .wiki-heading {
      display: flex;
      align-items: baseline;
      border-bottom: 1px solid var(--border-light);
      margin: 1.5rem 0 0.8rem;
      padding-bottom: 0.2rem;
    }
 
    .wiki-heading h2 {
      font-size: 1.4rem;
      font-weight: 400;
      flex: 1;
    }
 
    .wiki-heading .edit-section {
      font-size: 0.82rem;
      color: var(--text-light);
    }
 
    .wiki-heading h3 {
      font-size: 1.15rem;
      font-weight: 700;
      flex: 1;
    }
 
    .wiki-heading h3::after {
      content: '';
      display: none;
    }
 
    .article-body p {
      margin-bottom: 0.8rem;
    }
 
    .article-body ul, .article-body ol {
      margin: 0.5rem 0 0.8rem 2rem;
    }
 
    .article-body li {
      margin-bottom: 0.3rem;
    }
 
    /* Category Box */
    .catlinks {
      background: var(--toc-bg);
      border: 1px solid var(--border-light);
      padding: 0.5rem 1rem;
      margin-top: 2rem;
      font-size: 0.85rem;
      display: flex;
      gap: 0.5rem;
      flex-wrap: wrap;
      align-items: center;
    }
 
    .catlinks strong { margin-left: 0.3rem; }
 
    .catlinks a {
      background: var(--tab-bg);
      padding: 0.15rem 0.5rem;
      border-radius: 2px;
      font-size: 0.82rem;
    }
 
    /* Page Footer */
    .page-footer {
      background: var(--surface);
      border-top: 1px solid var(--border-light);
      padding: 1rem 2rem;
      font-size: 0.82rem;
      color: var(--text-light);
      text-align: center;
    }
 
    /* === Wiki Text Formatting === */
    .wiki-bold { font-weight: 700; }
    .wiki-italic { font-style: italic; }
 
    code {
      background: var(--toc-bg);
      padding: 0.1rem 0.35rem;
      border: 1px solid var(--border-light);
      font-size: 0.92em;
      border-radius: 2px;
      font-family: monospace;
    }
 
    pre {
      background: var(--toc-bg);
      border: 1px solid var(--border-light);
      padding: 0.8rem 1rem;
      overflow-x: auto;
      font-size: 0.9rem;
      margin: 0.8rem 0;
      border-radius: 2px;
    }
 
    pre code {
      background: none;
      border: none;
      padding: 0;
    }
 
    blockquote {
      border-right: 3px solid var(--accent);
      padding-right: 1rem;
      margin: 0.8rem 0;
      color: var(--text-muted);
      background: var(--toc-bg);
      padding: 0.5rem 1rem;
    }
 
    /* Info Box */
    .infobox {
      float: left;
      clear: right;
      margin: 0 0 1rem 1.5rem;
      border: 1px solid var(--border-light);
      border-collapse: collapse;
      background: var(--surface);
      font-size: 0.88rem;
      width: 280px;
    }
 
    .infobox-header {
      background: var(--accent);
      color: white;
      padding: 0.5rem 0.8rem;
      text-align: center;
      font-weight: 700;
      font-size: 1rem;
    }
 
    .infobox td, .infobox th {
      padding: 0.3rem 0.6rem;
      border: 1px solid var(--border-light);
      vertical-align: top;
    }
 
    .infobox th {
      background: var(--toc-bg);
      text-align: right;
      font-weight: 500;
      width: 35%;
    }
 
    /* Alert Boxes */
    .ambox {
      border: 1px solid var(--border-light);
      background: var(--toc-bg);
      padding: 0.6rem 1rem;
      margin: 0.8rem 0;
      font-size: 0.88rem;
      display: flex;
      align-items: center;
      gap: 0.6rem;
    }
 
    .ambox-icon { font-size: 1.3rem; }
 
    /* Editor (Modal) */
    .modal-overlay {
      display: none;
      position: fixed;
      inset: 0;
      background: rgba(0,0,0,0.5);
      z-index: 200;
      align-items: center;
      justify-content: center;
      padding: 1rem;
    }
 
    .modal-overlay.show { display: flex; }
 
    .modal {
      background: var(--surface);
      border: 1px solid var(--border);
      width: 100%;
      max-width: 800px;
      max-height: 90vh;
      overflow-y: auto;
      border-radius: 2px;
    }
 
    .modal-header {
      background: var(--tab-bg);
      padding: 0.6rem 1rem;
      border-bottom: 1px solid var(--border-light);
      display: flex;
      align-items: center;
      justify-content: space-between;
    }
 
    .modal-header h3 { font-size: 1rem; font-weight: 500; }
 
    .modal-body { padding: 1.2rem; }
 
    .form-group { margin-bottom: 1rem; }
 
    .form-group label {
      display: block;
      font-size: 0.85rem;
      font-weight: 500;
      margin-bottom: 0.3rem;
      color: var(--text-muted);
    }
 
    .form-group input,
    .form-group textarea,
    .form-group select {
      width: 100%;
      padding: 0.5rem 0.7rem;
      border: 1px solid var(--border);
      font-family: inherit;
      font-size: 0.95rem;
      direction: rtl;
      border-radius: 2px;
    }
 
    .form-group textarea {
      min-height: 300px;
      resize: vertical;
      font-family: monospace;
      font-size: 0.9rem;
      line-height: 1.8;
      direction: ltr;
      text-align: left;
    }
 
    .form-group input:focus,
    .form-group textarea:focus,
    .form-group select:focus {
      outline: none;
      border-color: var(--accent);
    }
 
    .modal-actions {
      display: flex;
      gap: 0.5rem;
      padding: 0.6rem 1rem;
      background: var(--tab-bg);
      border-top: 1px solid var(--border-light);
    }
 
    .modal-actions button {
      padding: 0.4rem 1rem;
      font-family: inherit;
      font-size: 0.85rem;
      cursor: pointer;
      border-radius: 2px;
    }
 
    .btn-save {
      background: var(--accent);
      color: white;
      border: 1px solid var(--accent);
    }
 
    .btn-save:hover { background: var(--accent-hover); }
 
    .btn-cancel {
      background: var(--tab-bg);
      color: var(--text);
      border: 1px solid var(--border);
    }
 
    .btn-cancel:hover { background: var(--border-light); }
 
    .btn-delete {
      background: var(--danger);
      color: white;
      border: 1px solid var(--danger);
      margin-right: auto;
    }
 
    .btn-delete:hover { background: #9b1b1b; }
 
    /* Footer nav */
    .footer-nav {
      background: var(--surface);
      border-top: 1px solid var(--border-light);
      padding: 0.5rem 1rem;
      display: flex;
      justify-content: center;
      gap: 1.5rem;
      font-size: 0.82rem;
      color: var(--text-light);
    }
 
    .footer-nav a { color: var(--link); }
 
    /* Special pages list */
    .special-list {
      list-style: none;
      padding: 0;
    }
 
    .special-list li {
      padding: 0.4rem 0;
      border-bottom: 1px dotted var(--border-light);
    }
 
    .special-list li:last-child { border-bottom: none; }
 
    .special-list a {
      font-weight: 500;
    }
 
    .special-list .desc {
      color: var(--text-muted);
      font-size: 0.88rem;
      margin-right: 0.5rem;
    }
 
    /* Responsive */
    @media (max-width: 900px) {
      .wiki-sidebar { display: none; }
      .header-search input { width: 180px; }
      .article-body { padding: 1rem; }
      .infobox { float: none; width: 100%; margin: 0 0 1rem; }
    }
  </style>
</head>
<body>
 
  <!-- Personal Toolbar -->
  <div class="personal-bar">
    <span>👤 کاربر مهمان</span>
    <a href="#" onclick="openCreateModal(); return false;">صفحه جدید</a>
    <a href="#" onclick="showAllPages(); return false;">همه صفحات</a>
    <a href="#" onclick="exportData(); return false;">خروجی JSON</a>
    <a href="#" onclick="importData(); return false;">ورودی JSON</a>
  </div>
 
  <!-- Header -->
  <header class="wiki-header">
    <div class="wiki-logo">
      <div class="wiki-logo-icon">W</div>
      <h1><span>ویکی</span> شخصی من</h1>
    </div>
    <div class="header-search">
      <input type="text" id="globalSearch" placeholder="جستجو..." oninput="searchPages()">
      <button onclick="searchPages()">جستجو</button>
    </div>
  </header>
 
  <!-- Body -->
  <div class="wiki-body">
    <!-- Sidebar -->
    <nav class="wiki-sidebar" id="sidebar">
      <div class="sidebar-section">
        <h4>ابزارها</h4>
        <div class="sidebar-search">
          <input type="text" id="sidebarSearch" placeholder="جستجو در صفحات..." oninput="searchPages()">
          <button onclick="searchPages()">برو</button>
        </div>
      </div>
      <div class="sidebar-section">
        <h4>ناوبری</h4>
        <ul id="navPages">
          <li><a href="#" class="active-page" onclick="showMainPage(); return false;">صفحه اصلی</a></li>
        </ul>
      </div>
      <div class="sidebar-section">
        <h4>صفحات ویژه</h4>
        <ul>
          <li><a href="#" onclick="showAllPages(); return false;">همه صفحات</a></li>
          <li><a href="#" onclick="openCreateModal(); return false;">ایجاد صفحه جدید</a></li>
        </ul>
      </div>
      <div class="sidebar-section" id="categorySidebar">
        <h4>رده‌ها</h4>
        <ul id="categoryList"></ul>
      </div>
    </nav>
 
    <!-- Content -->
    <div class="wiki-content">
      <div class="content-tabs" id="contentTabs">
        <button class="content-tab active" id="tab-read" onclick="switchTab('read')"><strong>خواندن</strong></button>
        <button class="content-tab" id="tab-edit" onclick="switchTab('edit')"><strong>ویرایش</strong></button>
      </div>
 
      <!-- Read View -->
      <div class="article-body" id="readView"></div>
 
      <!-- Edit View (hidden) -->
      <div class="article-body" id="editView" style="display:none;"></div>
 
      <!-- Footer -->
      <div class="page-footer">
        صفحه آخرین بار در <span id="footerDate"></span> ویرایش شده است.
      </div>
    </div>
  </div>
 
  <!-- Bottom nav -->
  <div class="footer-nav">
    <a href="#" onclick="showMainPage(); return false;">صفحه اصلی</a>
    <span>•</span>
    <a href="#" onclick="showAllPages(); return false;">فهرست صفحات</a>
    <span>•</span>
    <a href="#" onclick="openCreateModal(); return false;">صفحه جدید</a>
    <span>•</span>
    <span>ذخیره شده در مرورگر شما ❤️</span>
  </div>
 
  <!-- Create/Edit Modal -->
  <div class="modal-overlay" id="editorModal">
    <div class="modal">
      <div class="modal-header">
        <h3 id="modalTitle">ایجاد صفحه جدید</h3>
        <button class="btn-cancel" onclick="closeModal('editorModal')" style="border:none;background:none;font-size:1.2rem;cursor:pointer;">✕</button>
      </div>
      <div class="modal-body">
        <div class="form-group">
          <label>نام صفحه (عنوان)</label>
          <input type="text" id="pageTitle" placeholder="مثال: React Hooks">
        </div>
        <div class="form-group">
          <label>رده (دسته‌بندی)</label>
          <input type="text" id="pageCategory" placeholder="مثال: برنامه‌نویسی، طراحی، یادداشت">
        </div>
        <div class="form-group">
          <label>محتوای ویکی (Wiki markup ساده)</label>
          <textarea id="pageContent" placeholder="== عنوان بخش ==&#10;&#10;متن مقاله اینجا...&#10;&#10;=== زیرعنوان ===&#10;&#10;* مورد اول&#10;* مورد دوم&#10;&#10;{{یادآوری|متن مهم}}&#10;&#10;[[پیوند به صفحه دیگر]]&#10;&#10;---"></textarea>
        </div>
      </div>
      <div class="modal-actions">
        <button class="btn-save" onclick="savePage()">ذخیره صفحه</button>
        <button class="btn-cancel" onclick="closeModal('editorModal')">لغو</button>
        <button class="btn-delete" id="modalDeleteBtn" style="display:none;" onclick="deleteCurrentPage()">حذف صفحه</button>
      </div>
    </div>
  </div>
 
  <!-- Hidden file input for import -->
  <input type="file" id="importInput" accept=".json" style="display:none;" onchange="handleImport(event)">
 
  <script>
    // ===== Data Layer =====
    let pages = JSON.parse(localStorage.getItem('mediawiki_pages') || '[]');
    let currentPageId = null;
    let editingPageId = null;
 
    function save() {
      localStorage.setItem('mediawiki_pages', JSON.stringify(pages));
    }
 
    function genId() {
      return Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
    }
 
    function getPageBySlug(slug) {
      return pages.find(p => p.slug === slug);
    }
 
    function slugify(text) {
      return text.trim().replace(/\s+/g, '-').replace(/[^\w\-\u0600-\u06FF]/g, '').toLowerCase();
    }
 
    // ===== Wiki Markup Parser =====
    function parseWikiMarkup(text) {
      let html = text;
 
      // Escape HTML first
      html = html.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
 
      // Headings: == Title == → <h2>, === Title === → <h3>, etc.
      html = html.replace(/^======\s*(.+?)\s*======/gm, '<h5>$1</h5>');
      html = html.replace(/^=====\s*(.+?)\s*=====/gm, '<h4>$1</h4>');
      html = html.replace(/^====\s*(.+?)\s*====/gm, '<h4>$1</h4>');
      html = html.replace(/^===\s*(.+?)\s*===/gm, '<h3>$1</h3>');
      html = html.replace(/^==\s*(.+?)\s*==/gm, '</div><div class="wiki-heading"><h2>$1</h2></div><div class="wiki-para">');
 
      // Bold & Italic
      html = html.replace(/'{5}(.+?)'{5}/g, '<b><i>$1</i></b>');
      html = html.replace(/'{3}(.+?)'{3}/g, '<b>$1</b>');
      html = html.replace(/'{2}(.+?)'{2}/g, '<i>$1</i>');
 
      // Internal links: [[Page Name]] or [[Page Name|display text]]
      html = html.replace(/\[\[([^\]|]+)\|([^\]]+)\]\]/g, '<a href="#" onclick="navigateToPage(\'$1\'); return false;" class="wiki-link">$2</a>');
      html = html.replace(/\[\[([^\]]+)\]\]/g, '<a href="#" onclick="navigateToPage(\'$1\'); return false;" class="wiki-link">$1</a>');
 
      // External links: [http://... text]
      html = html.replace(/\[(https?:\/\/[^\s]+)\s+([^\]]+)\]/g, '<a href="$1" target="_blank">$2</a>');
      // Bare external: [http://...]
      html = html.replace(/\[(https?:\/\/[^\s]+)\]/g, '<a href="$1" target="_blank">$1</a>');
 
      // Templates: {{یادآوری|text}} or {{tip|text}}
      html = html.replace(/\{\{یادآوری\|(.+?)\}\}/g, '<div class="ambox"><span class="ambox-icon">💡</span><span>$1</span></div>');
      html = html.replace(/\{\{tip\|(.+?)\}\}/g, '<div class="ambox"><span class="ambox-icon">✅</span><span>$1</span></div>');
      html = html.replace(/\{\{هشدار\|(.+?)\}\}/g, '<div class="ambox" style="border-color:var(--danger);"><span class="ambox-icon">⚠️</span><span>$1</span></div>');
      html = html.replace(/\{\{note\|(.+?)\}\}/g, '<div class="ambox"><span class="ambox-icon">📝</span><span>$1</span></div>');
 
      // Code blocks: <code>...</code> or indented lines
      html = html.replace(/<code>([\s\S]*?)<\/code>/g, '<pre><code>$1</code></pre>');
 
      // Blockquote: lines starting with :
      html = html.replace(/^:\s*(.+)$/gm, '<blockquote>$1</blockquote>');
 
      // Horizontal rule: ----
      html = html.replace(/^----+$/gm, '<hr style="border:none;border-top:1px solid var(--border-light);margin:1rem 0;">');
 
      // Unordered lists
      html = html.replace(/^\* (.+)$/gm, '<li style="list-style:disc;margin-right:1.5rem;">$1</li>');
 
      // Ordered lists
      html = html.replace(/^# (.+)$/gm, '<li style="list-style:decimal;margin-right:1.5rem;">$1</li>');
 
      // Wrap consecutive <li> in <ul>
      html = html.replace(/(<li[^>]*>.*?<\/li>\n?)+/g, (match) => {
        if (match.includes('list-style:decimal')) return '<ol style="margin:0.5rem 0;">' + match + '</ol>';
        return '<ul style="margin:0.5rem 0;">' + match + '</ul>';
      });
 
      // Line breaks: double newline = paragraph
      html = html.replace(/\n\n+/g, '</p><p>');
      html = html.replace(/\n/g, '<br>');
 
      // Wrap in paragraphs
      html = '<div class="wiki-para"><p>' + html + '</p></div>';
 
      // Remove empty paragraphs
      html = html.replace(/<p><\/p>/g, '');
      html = html.replace(/<p>\s*<\/p>/g, '');
 
      return html;
    }
 
    // ===== Rendering =====
    function showMainPage() {
      currentPageId = null;
      updateSidebarActive('صفحه اصلی');
 
      const defaultContent = pages.length === 0
        ? getWelcomeContent()
        : getRecentChangesContent();
 
      document.getElementById('readView').innerHTML = defaultContent;
      document.getElementById('tab-read').click();
      document.getElementById('footerDate').textContent = formatDate(new Date().toISOString());
      window.scrollTo(0, 0);
    }
 
    function getWelcomeContent() {
      return `
        <h1 class="article-title">صفحه اصلی</h1>
        <div class="ambox">
          <span class="ambox-icon">📖</span>
          <span>به ویکی شخصی خود خوش آمدید! اینجا می‌توانید هر چیزی که یاد می‌گیرید را ذخیره کنید.</span>
        </div>
        <h2 style="font-weight:400;font-size:1.4rem;margin:1.5rem 0 0.5rem;border-bottom:1px solid var(--border-light);padding-bottom:0.2rem;">نحوه استفاده</h2>
        <p>برای شروع کار با این ویکی، از امکانات زیر استفاده کنید:</p>
        <ul style="margin:0.5rem 0 1rem 2rem;">
          <li><b>ایجاد صفحه جدید:</b> از لینک «صفحه جدید» در بالای صفحه استفاده کنید.</li>
          <li><b>ویرایش صفحه:</b> روی تب «ویرایش» کلیک کنید.</li>
          <li><b>پیوند بین صفحات:</b> از <code>[[نام صفحه]]</code> استفاده کنید.</li>
          <li><b>قالب‌بندی:</b> از <code>== عنوان ==</code> برای تیتر و <code>'''متن'''</code> برای بولد استفاده کنید.</li>
        </ul>
        <h2 style="font-weight:400;font-size:1.4rem;margin:1.5rem 0 0.5rem;border-bottom:1px solid var(--border-light);padding-bottom:0.2rem;">شروع کنید</h2>
        <p>اکنون <a href="#" onclick="openCreateModal(); return false;"><b>اولین صفحه خود را بسازید</b></a> و شروع به یادداشت کنید!</p>
        <h2 style="font-weight:400;font-size:1.4rem;margin:1.5rem 0 0.5rem;border-bottom:1px solid var(--border-light);padding-bottom:0.2rem;">راهنمای Wiki markup</h2>
        <table class="infobox" style="width:100%;float:none;">
          <tr><td class="infobox-header" colspan="2">نمونه‌های قالب‌بندی</td></tr>
          <tr><th>عنوان بخش</th><td><code>== عنوان ==</code> یا <code>=== زیرعنوان ===</code></td></tr>
          <tr><th>متن بولد</th><td><code>'''متن بولد'''</code></td></tr>
          <tr><th>متن ایتالیک</th><td><code>''متن ایتالیک''</code></td></tr>
          <tr><th>لیست</th><td><code>* مورد اول</code> (ترتیبی: <code># مورد اول</code>)</td></tr>
          <tr><th>پیوند داخلی</th><td><code>[[نام صفحه]]</code> یا <code>[[نام|متن نمایشی]]</code></td></tr>
          <tr><th>پیوند خارجی</th><td><code>[https://example.com متن لینک]</code></td></tr>
          <tr><th>یادآوری</th><td><code>{{یادآوری|متن مهم}}</code></td></tr>
          <tr><th>کد</th><td><code>&lt;code&gt;کد&lt;/code&gt;</code></td></tr>
          <tr><th>خط جداکننده</th><td><code>----</code></td></tr>
        </table>
        <div style="clear:both;"></div>`;
    }
 
    function getRecentChangesContent() {
      const sorted = [...pages].sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
      const recent = sorted.slice(0, 10);
 
      let html = `
        <h1 class="article-title">صفحه اصلی</h1>
        <div class="ambox">
          <span class="ambox-icon">🏠</span>
          <span>به ویکی شخصی خود خوش آمدید. <a href="#" onclick="openCreateModal(); return false;">صفحه جدید ایجاد کنید</a>.</span>
        </div>
        <h2 style="font-weight:400;font-size:1.4rem;margin:1.5rem 0 0.5rem;border-bottom:1px solid var(--border-light);padding-bottom:0.2rem;">تغییرات اخیر</h2>
        <p>آخرین صفحات ویرایش شده:</p>
        <table style="width:100%;border-collapse:collapse;margin:0.5rem 0;">
          <tr style="background:var(--tab-bg);">
            <th style="padding:0.4rem 0.6rem;border:1px solid var(--border-light);text-align:right;">صفحه</th>
            <th style="padding:0.4rem 0.6rem;border:1px solid var(--border-light);text-align:right;">رده</th>
            <th style="padding:0.4rem 0.6rem;border:1px solid var(--border-light);text-align:right;">آخرین ویرایش</th>
          </tr>`;
 
      recent.forEach(p => {
        const cats = p.categories.length > 0 ? p.categories.join(', ') : '—';
        html += `
          <tr style="cursor:pointer;" onclick="navigateToPage('${p.title}')">
            <td style="padding:0.4rem 0.6rem;border:1px solid var(--border-light);"><a href="#" onclick="event.preventDefault();navigateToPage('${p.title}')">${escapeHtml(p.title)}</a></td>
            <td style="padding:0.4rem 0.6rem;border:1px solid var(--border-light);color:var(--text-muted);">${escapeHtml(cats)}</td>
            <td style="padding:0.4rem 0.6rem;border:1px solid var(--border-light);color:var(--text-light);">${formatDate(p.updatedAt)}</td>
          </tr>`;
      });
 
      html += `</table>`;
 
      // Stats
      html += `
        <h2 style="font-weight:400;font-size:1.4rem;margin:1.5rem 0 0.5rem;border-bottom:1px solid var(--border-light);padding-bottom:0.2rem;">آمار ویکی</h2>
        <ul style="margin:0.5rem 0 1rem 2rem;">
          <li>تعداد کل صفحات: <b>${pages.length}</b></li>
          <li>تعداد رده‌ها: <b>${new Set(pages.flatMap(p => p.categories)).size}</b></li>
          <li>بزرگ‌ترین صفحه: <b>${pages.length > 0 ? pages.reduce((a, b) => a.content.length > b.content.length ? a : b).title : '—'}</b></li>
        </ul>`;
 
      return html;
    }
 
    function showAllPages() {
      currentPageId = null;
      updateSidebarActive('همه صفحات');
 
      const sorted = [...pages].sort((a, b) => a.title.localeCompare(b.title, 'fa'));
 
      let html = `<h1 class="article-title">همه صفحات</h1>`;
 
      if (sorted.length === 0) {
        html += `
          <div class="ambox">
            <span class="ambox-icon">📭</span>
            <span>هنوز هیچ صفحه‌ای وجود ندارد. <a href="#" onclick="openCreateModal(); return false;">اولین صفحه را بسازید</a>.</span>
          </div>`;
      } else {
        html += `<p>تعداد کل صفحات: <b>${sorted.length}</b></p>`;
        html += `<ul class="special-list">`;
        sorted.forEach(p => {
          const catText = p.categories.length > 0 ? ` — ${p.categories.join(', ')}` : '';
          html += `<li><a href="#" onclick="navigateToPage('${escapeHtml(p.title)}'); return false;">${escapeHtml(p.title)}</a> <span class="desc">${catText} (${formatDate(p.updatedAt)})</span></li>`;
        });
        html += `</ul>`;
      }
 
      document.getElementById('readView').innerHTML = html;
      document.getElementById('tab-read').click();
      document.getElementById('footerDate').textContent = formatDate(new Date().toISOString());
      window.scrollTo(0, 0);
    }
 
    function navigateToPage(title) {
      const page = pages.find(p => p.title === title || p.slug === slugify(title));
      if (page) {
        showPage(page.id);
      } else {
        // Page doesn't exist — offer to create
        if (confirm(`صفحه «${title}}» وجود ندارد. آیا می‌خواهید آن را بسازید؟`)) {
          editingPageId = null;
          document.getElementById('modalTitle').textContent = `ایجاد صفحه: ${title}`;
          document.getElementById('pageTitle').value = title;
          document.getElementById('pageCategory').value = '';
          document.getElementById('pageContent').value = '';
          document.getElementById('modalDeleteBtn').style.display = 'none';
          document.getElementById('editorModal').classList.add('show');
        }
      }
    }
 
    function showPage(id) {
      const page = pages.find(p => p.id === id);
      if (!page) return showMainPage();
 
      currentPageId = id;
      updateSidebarActive(page.title);
 
      const parsedContent = parseWikiMarkup(page.content);
 
      // Build TOC from headings
      const headingMatches = page.content.match(/^==+\s*(.+?)\s*==+$/gm) || [];
      let tocHtml = '';
      if (headingMatches.length >= 2) {
        tocHtml = `<div class="toc"><div class="toc-title">فهرست مطالب <span class="toc-toggle" onclick="this.parentElement.querySelector('ol').classList.toggle('hidden');">[隐蔽]</span></div><ol>`;
        headingMatches.forEach((h, i) => {
          const depth = (h.match(/^=+/) || ['=='])[0].length - 2;
          const text = h.replace(/^=+\s*/, '').replace(/\s*=+$/, '');
          const indent = depth > 0 ? `style="margin-right:${depth * 1.5}rem;"` : '';
          tocHtml += `<li ${indent}><a href="#section-${i}">${text}</a></li>`;
        });
        tocHtml += `</ol></div>`;
      }
 
      // Build categories
      let catHtml = '';
      if (page.categories.length > 0) {
        catHtml = `<div class="catlinks"><strong>رده‌ها:</strong> ${page.categories.map(c => `<a href="#" onclick="filterByCategory('${escapeHtml(c)}'); return false;">${escapeHtml(c)}</a>`).join(' ')}</div>`;
      }
 
      // Infobox
      const wordCount = page.content.split(/\s+/).length;
      const charCount = page.content.length;
 
      const infobox = `
        <div class="infobox">
          <div class="infobox-header">${escapeHtml(page.title)}</div>
          <table>
            <tr><th>تاریخ ایجاد</th><td>${formatDate(page.createdAt)}</td></tr>
            <tr><th>آخرین ویرایش</th><td>${formatDate(page.updatedAt)}</td></tr>
            <tr><th>تعداد کلمات</th><td>${wordCount.toLocaleString('fa-IR')}</td></tr>
            <tr><th>تعداد حروف</th><td>${charCount.toLocaleString('fa-IR')}</td></tr>
            ${page.categories.length > 0 ? `<tr><th>رده‌ها</th><td>${page.categories.join('، ')}</td></tr>` : ''}
          </table>
        </div>`;
 
      // Assemble
      let fullHtml = `<h1 class="article-title">${escapeHtml(page.title)}</h1>`;
      fullHtml += infobox;
      fullHtml += tocHtml;
      fullHtml += parsedContent;
      fullHtml += `<div style="clear:both;"></div>`;
      fullHtml += catHtml;
 
      // See also — links to other pages
      const otherPages = pages.filter(p => p.id !== page.id).slice(0, 5);
      if (otherPages.length > 0) {
        fullHtml += `<h2 style="font-weight:400;font-size:1.3rem;margin:1.5rem 0 0.5rem;border-bottom:1px solid var(--border-light);padding-bottom:0.2rem;">همچنین ببینید</h2>`;
        fullHtml += `<ul style="margin:0.5rem 0;">`;
        otherPages.forEach(p => {
          fullHtml += `<li><a href="#" onclick="navigateToPage('${escapeHtml(p.title)}'); return false;">${escapeHtml(p.title)}</a></li>`;
        });
        fullHtml += `</ul>`;
      }
 
      document.getElementById('readView').innerHTML = fullHtml;
      document.getElementById('tab-read').click();
      document.getElementById('footerDate').textContent = formatDate(page.updatedAt);
      window.scrollTo(0, 0);
 
      updateSidebar();
    }
 
    function renderEditView(id) {
      const page = id ? pages.find(p => p.id === id) : null;
 
      let html = `<h1 class="article-title" style="margin-bottom:1rem;">${page ? 'ویرایش: ' + escapeHtml(page.title) : 'ایجاد صفحه جدید'}</h1>`;
 
      html += `
        <div style="background:var(--toc-bg);border:1px solid var(--border-light);padding:0.8rem;margin-bottom:1rem;font-size:0.88rem;">
          <b>راهنمای سریع Wiki markup:</b><br>
          <code>== عنوان ==</code> · <code>'''بولد'''</code> · <code>''ایتالیک''</code> · <code>[[پیوند]]</code> · <code>{{یادآوری|متن}}</code> · <code>* لیست</code> · <code>&lt;code&gt;kod&lt;/code&gt;</code>
        </div>
        <div class="form-group">
          <label>عنوان صفحه</label>
          <input type="text" id="editTitle" value="${page ? escapeHtml(page.title) : ''}" placeholder="عنوان صفحه">
        </div>
        <div class="form-group">
          <label>رده‌ها (با کاما جدا کنید)</label>
          <input type="text" id="editCategories" value="${page ? page.categories.join(', ') : ''}" placeholder="برنامه‌نویسی, یادداشت">
        </div>
        <div class="form-group">
          <label>محتوا</label>
          <textarea id="editContent" placeholder="محتوای صفحه را اینجا بنویسید...">${page ? page.content : ''}</textarea>
        </div>
        <div style="display:flex;gap:0.5rem;">
          <button class="btn-save" onclick="saveFromEditor(${id ? `'${id}'` : 'null'})">ذخیره صفحه</button>
          <button class="btn-cancel" onclick="cancelEdit()">لغو</button>
          ${page ? `<button class="btn-delete" onclick="deletePage('${id}')">حذف صفحه</button>` : ''}
        </div>`;
 
      document.getElementById('editView').innerHTML = html;
    }
 
    function switchTab(tab) {
      if (tab === 'read') {
        document.getElementById('tab-read').classList.add('active');
        document.getElementById('tab-edit').classList.remove('active');
        document.getElementById('readView').style.display = '';
        document.getElementById('editView').style.display = 'none';
      } else {
        document.getElementById('tab-edit').classList.add('active');
        document.getElementById('tab-read').classList.remove('active');
        document.getElementById('readView').style.display = 'none';
        document.getElementById('editView').style.display = '';
        renderEditView(currentPageId);
      }
    }
 
    function cancelEdit() {
      if (currentPageId) {
        switchTab('read');
      } else {
        showMainPage();
      }
    }
 
    function saveFromEditor(id) {
      const title = document.getElementById('editTitle').value.trim();
      const cats = document.getElementById('editCategories').value.split(',').map(c => c.trim()).filter(Boolean);
      const content = document.getElementById('editContent').value;
 
      if (!title) { document.getElementById('editTitle').focus(); return; }
      if (!content) { document.getElementById('editContent').focus(); return; }
 
      if (id) {
        const page = pages.find(p => p.id === id);
        if (page) {
          page.title = title;
          page.slug = slugify(title);
          page.categories = cats;
          page.content = content;
          page.updatedAt = new Date().toISOString();
        }
      } else {
        const newPage = {
          id: genId(),
          title,
          slug: slugify(title),
          categories: cats,
          content,
          createdAt: new Date().toISOString(),
          updatedAt: new Date().toISOString(),
        };
        pages.push(newPage);
        currentPageId = newPage.id;
      }
 
      save();
      showPage(currentPageId);
      updateSidebar();
    }
 
    // ===== Modal helpers =====
    function openCreateModal() {
      editingPageId = null;
      document.getElementById('modalTitle').textContent = 'ایجاد صفحه جدید';
      document.getElementById('pageTitle').value = '';
      document.getElementById('pageCategory').value = '';
      document.getElementById('pageContent').value = '';
      document.getElementById('modalDeleteBtn').style.display = 'none';
      document.getElementById('editorModal').classList.add('show');
      document.getElementById('pageTitle').focus();
    }
 
    function savePage() {
      const title = document.getElementById('pageTitle').value.trim();
      const cats = document.getElementById('pageCategory').value.split(',').map(c => c.trim()).filter(Boolean);
      const content = document.getElementById('pageContent').value;
 
      if (!title) { document.getElementById('pageTitle').focus(); return; }
      if (!content) { document.getElementById('pageContent').focus(); return; }
 
      const newPage = {
        id: genId(),
        title,
        slug: slugify(title),
        categories: cats,
        content,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
      };
 
      pages.push(newPage);
      save();
      closeModal('editorModal');
      currentPageId = newPage.id;
      showPage(newPage.id);
      updateSidebar();
    }
 
    function deletePage(id) {
      if (!confirm('آیا مطمئن هستید که می‌خواهید این صفحه را حذف کنید؟')) return;
      pages = pages.filter(p => p.id !== id);
      save();
      currentPageId = null;
      showMainPage();
      updateSidebar();
    }
 
    function deleteCurrentPage() {
      if (currentPageId) deletePage(currentPageId);
    }
 
    function closeModal(id) {
      document.getElementById(id).classList.remove('show');
    }
 
    // ===== Sidebar =====
    function updateSidebar() {
      const navPages = document.getElementById('navPages');
      const sorted = [...pages].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
      const recent = sorted.slice(0, 8);
 
      let html = `<li><a href="#" class="${!currentPageId ? 'active-page' : ''}" onclick="showMainPage(); return false;">صفحه اصلی</a></li>`;
      recent.forEach(p => {
        html += `<li><a href="#" class="${currentPageId === p.id ? 'active-page' : ''}" onclick="navigateToPage('${escapeHtml(p.title)}'); return false;">${escapeHtml(p.title)}</a></li>`;
      });
      navPages.innerHTML = html;
 
      // Categories
      const allCats = [...new Set(pages.flatMap(p => p.categories))].sort();
      const catList = document.getElementById('categoryList');
      catList.innerHTML = allCats.map(c =>
        `<li><a href="#" onclick="filterByCategory('${escapeHtml(c)}'); return false;">${escapeHtml(c)}</a></li>`
      ).join('');
    }
 
    function updateSidebarActive(title) {
      document.querySelectorAll('.sidebar-section li a').forEach(a => {
        a.classList.remove('active-page');
        if (a.textContent.trim() === title) a.classList.add('active-page');
      });
    }
 
    function filterByCategory(cat) {
      const matching = pages.filter(p => p.categories.includes(cat));
      let html = `<h1 class="article-title">رده: ${escapeHtml(cat)}</h1>`;
      html += `<p>تعداد صفحات در این رده: <b>${matching.length}</b></p>`;
 
      if (matching.length > 0) {
        html += `<ul class="special-list">`;
        matching.forEach(p => {
          html += `<li><a href="#" onclick="navigateToPage('${escapeHtml(p.title)}'); return false;">${escapeHtml(p.title)}</a> <span class="desc">(${formatDate(p.updatedAt)})</span></li>`;
        });
        html += `</ul>`;
      }
 
      document.getElementById('readView').innerHTML = html;
      document.getElementById('tab-read').click();
      window.scrollTo(0, 0);
    }
 
    // ===== Search =====
    function searchPages() {
      const query = document.getElementById('globalSearch').value.trim().toLowerCase() ||
                    document.getElementById('sidebarSearch').value.trim().toLowerCase();
 
      if (!query) {
        if (currentPageId) return showPage(currentPageId);
        return showMainPage();
      }
 
      const results = pages.filter(p =>
        p.title.toLowerCase().includes(query) ||
        p.content.toLowerCase().includes(query) ||
        p.categories.some(c => c.toLowerCase().includes(query))
      );
 
      let html = `<h1 class="article-title">نتایج جستجو: «${escapeHtml(query)}»</h1>`;
      html += `<p>${results.length} نتیجه یافت شد.</p>`;
 
      if (results.length > 0) {
        html += `<ul class="special-list">`;
        results.forEach(p => {
          // Show context snippet
          const idx = p.content.toLowerCase().indexOf(query);
          let snippet = '';
          if (idx !== -1) {
            const start = Math.max(0, idx - 40);
            const end = Math.min(p.content.length, idx + query.length + 60);
            snippet = (start > 0 ? '...' : '') + p.content.substring(start, end) + (end < p.content.length ? '...' : '');
          }
          html += `<li><a href="#" onclick="navigateToPage('${escapeHtml(p.title)}'); return false;">${escapeHtml(p.title)}</a>`;
          if (snippet) html += `<br><span class="desc">${escapeHtml(snippet)}</span>`;
          html += `</li>`;
        });
        html += `</ul>`;
      }
 
      document.getElementById('readView').innerHTML = html;
      document.getElementById('tab-read').click();
      window.scrollTo(0, 0);
    }
 
    // ===== Import/Export =====
    function exportData() {
      const data = JSON.stringify(pages, null, 2);
      const blob = new Blob([data], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `wiki-backup-${new Date().toISOString().slice(0, 10)}.json`;
      a.click();
      URL.revokeObjectURL(url);
    }
 
    function importData() {
      document.getElementById('importInput').click();
    }
 
    function handleImport(event) {
      const file = event.target.files[0];
      if (!file) return;
      const reader = new FileReader();
      reader.onload = (e) => {
        try {
          const data = JSON.parse(e.target.result);
          if (!Array.isArray(data)) throw new Error('Invalid format');
          if (confirm(`آیا می‌خواهید ${data.length} صفحه را وارد کنید؟ (صفحات فعلی حفظ می‌شوند)`)) {
            data.forEach(p => {
              if (p.title && p.content) {
                pages.push({
                  id: p.id || genId(),
                  title: p.title,
                  slug: p.slug || slugify(p.title),
                  categories: p.categories || [],
                  content: p.content,
                  createdAt: p.createdAt || new Date().toISOString(),
                  updatedAt: p.updatedAt || new Date().toISOString(),
                });
              }
            });
            save();
            showMainPage();
            updateSidebar();
            alert(`${data.length} صفحه با موفقیت وارد شد.`);
          }
        } catch (err) {
          alert('خطا در خواندن فایل. لطفاً فایل JSON معتبر وارد کنید.');
        }
      };
      reader.readAsText(file);
      event.target.value = '';
    }
 
    // ===== Helpers =====
    function formatDate(iso) {
      if (!iso) return '—';
      return new Date(iso).toLocaleDateString('fa-IR', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
    }
 
    function escapeHtml(text) {
      const d = document.createElement('div');
      d.textContent = text;
      return d.innerHTML;
    }
 
    // ===== Keyboard Shortcuts =====
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') {
        document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));
      }
      if (e.key === 'n' && (e.ctrlKey || e.metaKey)) {
        e.preventDefault();
        openCreateModal();
      }
    });
 
    // Close modal on overlay click
    document.querySelectorAll('.modal-overlay').forEach(o => {
      o.addEventListener('click', (e) => { if (e.target === o) o.classList.remove('show'); });
    });
 
    // ===== Init =====
    updateSidebar();
    showMainPage();
  </script>
</body>
</html>

نسخهٔ ۵ سپتامبر ۲۰۲۶، ساعت ۰۸:۵۴

مدیاویکی با موفقیت نصب شد.

برای اطلاعات بیشتر در مورد به‌کارگیری نرم‌افزار ویکی به فهرست راهنمای کاربر مراجعه کنید.

<!DOCTYPE html> <html lang="fa" dir="rtl"> <head>

 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>ویکی شخصی من</title>
 <style>
   @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700&display=swap');
   * { margin: 0; padding: 0; box-sizing: border-box; }
   :root {
     --bg: #f8f9fa;
     --surface: #ffffff;
     --sidebar-bg: #f0f2f5;
     --border: #a2a9b1;
     --border-light: #c8ccd1;
     --text: #202122;
     --text-muted: #54595d;
     --text-light: #72777d;
     --accent: #3366cc;
     --accent-hover: #2a4b8d;
     --accent-visited: #795cb2;
     --link: #0645ad;
     --link-hover: #0b0080;
     --link-visited: #0b0080;
     --tab-bg: #eaecf0;
     --tab-active: #ffffff;
     --toc-bg: #f8f9fa;
     --toc-border: #a2a9b1;
     --danger: #b32424;
     --success: #14866d;
     --warning: #e07d10;
     --tag-green: #14866d;
     --tag-blue: #3366cc;
     --tag-orange: #b7570a;
     --tag-purple: #795cb2;
     --shadow: 0 1px 3px rgba(0,0,0,0.08);
   }
   body {
     font-family: 'Vazirmatn', sans-serif;
     background: var(--bg);
     color: var(--text);
     font-size: 14px;
     line-height: 1.7;
   }
   a { color: var(--link); text-decoration: none; }
   a:hover { text-decoration: underline; color: var(--link-hover); }
   /* === Personal Bar (top) === */
   .personal-bar {
     background: var(--surface);
     border-bottom: 1px solid var(--border-light);
     padding: 0.25rem 1rem;
     font-size: 0.82rem;
     display: flex;
     justify-content: flex-start;
     align-items: center;
     gap: 1rem;
     color: var(--text-muted);
   }
   .personal-bar a { color: var(--link); font-size: 0.82rem; }
   /* === Main Header === */
   .wiki-header {
     background: var(--surface);
     padding: 0.8rem 1.5rem;
     display: flex;
     align-items: center;
     justify-content: space-between;
     border-bottom: 3px solid var(--accent);
   }
   .wiki-logo {
     display: flex;
     align-items: center;
     gap: 0.75rem;
   }
   .wiki-logo-icon {
     width: 50px;
     height: 50px;
     background: var(--accent);
     border-radius: 50%;
     display: flex;
     align-items: center;
     justify-content: center;
     color: white;
     font-size: 1.6rem;
     font-weight: 700;
   }
   .wiki-logo h1 {
     font-size: 1.5rem;
     font-weight: 400;
     color: var(--text);
   }
   .wiki-logo h1 span {
     font-weight: 700;
   }
   .header-search {
     display: flex;
     align-items: center;
     gap: 0.4rem;
   }
   .header-search input {
     padding: 0.45rem 0.8rem;
     border: 1px solid var(--border);
     border-radius: 2px;
     font-family: inherit;
     font-size: 0.9rem;
     width: 300px;
     direction: rtl;
   }
   .header-search input:focus {
     outline: none;
     border-color: var(--accent);
     box-shadow: 0 0 0 2px rgba(51,102,204,0.15);
   }
   .header-search button {
     padding: 0.45rem 0.8rem;
     background: var(--accent);
     color: white;
     border: 1px solid var(--accent);
     font-family: inherit;
     font-size: 0.85rem;
     cursor: pointer;
     border-radius: 2px;
   }
   .header-search button:hover { background: var(--accent-hover); }
   /* === Body Layout === */
   .wiki-body {
     display: flex;
     max-width: 1400px;
     margin: 0 auto;
     min-height: calc(100vh - 120px);
   }
   /* === Sidebar === */
   .wiki-sidebar {
     width: 220px;
     min-width: 220px;
     background: var(--surface);
     border-left: 1px solid var(--border-light);
     padding: 1rem 0;
     font-size: 0.88rem;
   }
   .sidebar-section {
     margin-bottom: 0.5rem;
   }
   .sidebar-section h4 {
     padding: 0.4rem 1rem;
     font-size: 0.78rem;
     font-weight: 700;
     color: var(--text-muted);
     text-transform: uppercase;
     letter-spacing: 0.05em;
     background: var(--tab-bg);
     border-top: 1px solid var(--border-light);
     border-bottom: 1px solid var(--border-light);
   }
   .sidebar-section ul {
     list-style: none;
     padding: 0.3rem 0;
   }
   .sidebar-section li a {
     display: block;
     padding: 0.3rem 1rem;
     color: var(--accent);
     transition: background 0.15s;
   }
   .sidebar-section li a:hover {
     background: var(--tab-bg);
     text-decoration: none;
   }
   .sidebar-section li a.active-page {
     background: var(--accent);
     color: white;
     font-weight: 500;
   }
   .sidebar-search {
     padding: 0.5rem 1rem;
   }
   .sidebar-search input {
     width: 100%;
     padding: 0.35rem 0.6rem;
     border: 1px solid var(--border);
     font-family: inherit;
     font-size: 0.85rem;
     direction: rtl;
   }
   .sidebar-search input:focus {
     outline: none;
     border-color: var(--accent);
   }
   .sidebar-search button {
     width: 100%;
     margin-top: 0.3rem;
     padding: 0.3rem;
     background: var(--tab-bg);
     border: 1px solid var(--border);
     font-family: inherit;
     font-size: 0.82rem;
     cursor: pointer;
     color: var(--text-muted);
   }
   .sidebar-search button:hover { background: var(--border-light); }
   /* === Content Area === */
   .wiki-content {
     flex: 1;
     min-width: 0;
   }
   /* Tabs */
   .content-tabs {
     display: flex;
     background: var(--tab-bg);
     border-bottom: 1px solid var(--border-light);
     padding: 0 1rem;
     gap: 0;
   }
   .content-tab {
     padding: 0.5rem 1rem;
     font-size: 0.88rem;
     color: var(--text-muted);
     cursor: pointer;
     border: 1px solid transparent;
     border-bottom: none;
     background: none;
     font-family: inherit;
     border-radius: 2px 2px 0 0;
     position: relative;
     top: 1px;
     transition: background 0.15s;
   }
   .content-tab:hover {
     background: var(--surface);
     color: var(--text);
   }
   .content-tab.active {
     background: var(--surface);
     color: var(--text);
     border-color: var(--border-light);
     border-bottom-color: var(--surface);
     font-weight: 500;
   }
   /* Article Content */
   .article-body {
     background: var(--surface);
     border: 1px solid var(--border-light);
     border-top: none;
     padding: 1.5rem 2rem;
     min-height: 400px;
   }
   .article-body h1.article-title {
     font-size: 1.8rem;
     font-weight: 400;
     border-bottom: 1px solid var(--border-light);
     padding-bottom: 0.3rem;
     margin-bottom: 1rem;
     line-height: 1.4;
   }
   .article-intro {
     margin-bottom: 1.5rem;
   }
   /* Table of Contents */
   .toc {
     background: var(--toc-bg);
     border: 1px solid var(--toc-border);
     padding: 0.8rem 1rem;
     display: inline-block;
     margin: 0 0 1.5rem;
     min-width: 200px;
     font-size: 0.9rem;
   }
   .toc-title {
     font-weight: 700;
     text-align: center;
     margin-bottom: 0.3rem;
   }
   .toc-toggle {
     font-size: 0.82rem;
     color: var(--link);
     cursor: pointer;
     margin-right: 0.3rem;
   }
   .toc ol {
     margin: 0;
     padding-right: 1.2rem;
     list-style-type: decimal;
   }
   .toc ol ol {
     list-style-type: decimal;
   }
   .toc li {
     margin: 0.2rem 0;
   }
   .toc li a {
     color: var(--link);
   }
   /* Section Headings */
   .wiki-heading {
     display: flex;
     align-items: baseline;
     border-bottom: 1px solid var(--border-light);
     margin: 1.5rem 0 0.8rem;
     padding-bottom: 0.2rem;
   }
   .wiki-heading h2 {
     font-size: 1.4rem;
     font-weight: 400;
     flex: 1;
   }
   .wiki-heading .edit-section {
     font-size: 0.82rem;
     color: var(--text-light);
   }
   .wiki-heading h3 {
     font-size: 1.15rem;
     font-weight: 700;
     flex: 1;
   }
   .wiki-heading h3::after {
     content: ;
     display: none;
   }
   .article-body p {
     margin-bottom: 0.8rem;
   }
   .article-body ul, .article-body ol {
     margin: 0.5rem 0 0.8rem 2rem;
   }
   .article-body li {
     margin-bottom: 0.3rem;
   }
   /* Category Box */
   .catlinks {
     background: var(--toc-bg);
     border: 1px solid var(--border-light);
     padding: 0.5rem 1rem;
     margin-top: 2rem;
     font-size: 0.85rem;
     display: flex;
     gap: 0.5rem;
     flex-wrap: wrap;
     align-items: center;
   }
   .catlinks strong { margin-left: 0.3rem; }
   .catlinks a {
     background: var(--tab-bg);
     padding: 0.15rem 0.5rem;
     border-radius: 2px;
     font-size: 0.82rem;
   }
   /* Page Footer */
   .page-footer {
     background: var(--surface);
     border-top: 1px solid var(--border-light);
     padding: 1rem 2rem;
     font-size: 0.82rem;
     color: var(--text-light);
     text-align: center;
   }
   /* === Wiki Text Formatting === */
   .wiki-bold { font-weight: 700; }
   .wiki-italic { font-style: italic; }
   code {
     background: var(--toc-bg);
     padding: 0.1rem 0.35rem;
     border: 1px solid var(--border-light);
     font-size: 0.92em;
     border-radius: 2px;
     font-family: monospace;
   }
   pre {
     background: var(--toc-bg);
     border: 1px solid var(--border-light);
     padding: 0.8rem 1rem;
     overflow-x: auto;
     font-size: 0.9rem;
     margin: 0.8rem 0;
     border-radius: 2px;
   }
   pre code {
     background: none;
     border: none;
     padding: 0;
   }
   blockquote {
     border-right: 3px solid var(--accent);
     padding-right: 1rem;
     margin: 0.8rem 0;
     color: var(--text-muted);
     background: var(--toc-bg);
     padding: 0.5rem 1rem;
   }
   /* Info Box */
   .infobox {
     float: left;
     clear: right;
     margin: 0 0 1rem 1.5rem;
     border: 1px solid var(--border-light);
     border-collapse: collapse;
     background: var(--surface);
     font-size: 0.88rem;
     width: 280px;
   }
   .infobox-header {
     background: var(--accent);
     color: white;
     padding: 0.5rem 0.8rem;
     text-align: center;
     font-weight: 700;
     font-size: 1rem;
   }
   .infobox td, .infobox th {
     padding: 0.3rem 0.6rem;
     border: 1px solid var(--border-light);
     vertical-align: top;
   }
   .infobox th {
     background: var(--toc-bg);
     text-align: right;
     font-weight: 500;
     width: 35%;
   }
   /* Alert Boxes */
   .ambox {
     border: 1px solid var(--border-light);
     background: var(--toc-bg);
     padding: 0.6rem 1rem;
     margin: 0.8rem 0;
     font-size: 0.88rem;
     display: flex;
     align-items: center;
     gap: 0.6rem;
   }
   .ambox-icon { font-size: 1.3rem; }
   /* Editor (Modal) */
   .modal-overlay {
     display: none;
     position: fixed;
     inset: 0;
     background: rgba(0,0,0,0.5);
     z-index: 200;
     align-items: center;
     justify-content: center;
     padding: 1rem;
   }
   .modal-overlay.show { display: flex; }
   .modal {
     background: var(--surface);
     border: 1px solid var(--border);
     width: 100%;
     max-width: 800px;
     max-height: 90vh;
     overflow-y: auto;
     border-radius: 2px;
   }
   .modal-header {
     background: var(--tab-bg);
     padding: 0.6rem 1rem;
     border-bottom: 1px solid var(--border-light);
     display: flex;
     align-items: center;
     justify-content: space-between;
   }
   .modal-header h3 { font-size: 1rem; font-weight: 500; }
   .modal-body { padding: 1.2rem; }
   .form-group { margin-bottom: 1rem; }
   .form-group label {
     display: block;
     font-size: 0.85rem;
     font-weight: 500;
     margin-bottom: 0.3rem;
     color: var(--text-muted);
   }
   .form-group input,
   .form-group textarea,
   .form-group select {
     width: 100%;
     padding: 0.5rem 0.7rem;
     border: 1px solid var(--border);
     font-family: inherit;
     font-size: 0.95rem;
     direction: rtl;
     border-radius: 2px;
   }
   .form-group textarea {
     min-height: 300px;
     resize: vertical;
     font-family: monospace;
     font-size: 0.9rem;
     line-height: 1.8;
     direction: ltr;
     text-align: left;
   }
   .form-group input:focus,
   .form-group textarea:focus,
   .form-group select:focus {
     outline: none;
     border-color: var(--accent);
   }
   .modal-actions {
     display: flex;
     gap: 0.5rem;
     padding: 0.6rem 1rem;
     background: var(--tab-bg);
     border-top: 1px solid var(--border-light);
   }
   .modal-actions button {
     padding: 0.4rem 1rem;
     font-family: inherit;
     font-size: 0.85rem;
     cursor: pointer;
     border-radius: 2px;
   }
   .btn-save {
     background: var(--accent);
     color: white;
     border: 1px solid var(--accent);
   }
   .btn-save:hover { background: var(--accent-hover); }
   .btn-cancel {
     background: var(--tab-bg);
     color: var(--text);
     border: 1px solid var(--border);
   }
   .btn-cancel:hover { background: var(--border-light); }
   .btn-delete {
     background: var(--danger);
     color: white;
     border: 1px solid var(--danger);
     margin-right: auto;
   }
   .btn-delete:hover { background: #9b1b1b; }
   /* Footer nav */
   .footer-nav {
     background: var(--surface);
     border-top: 1px solid var(--border-light);
     padding: 0.5rem 1rem;
     display: flex;
     justify-content: center;
     gap: 1.5rem;
     font-size: 0.82rem;
     color: var(--text-light);
   }
   .footer-nav a { color: var(--link); }
   /* Special pages list */
   .special-list {
     list-style: none;
     padding: 0;
   }
   .special-list li {
     padding: 0.4rem 0;
     border-bottom: 1px dotted var(--border-light);
   }
   .special-list li:last-child { border-bottom: none; }
   .special-list a {
     font-weight: 500;
   }
   .special-list .desc {
     color: var(--text-muted);
     font-size: 0.88rem;
     margin-right: 0.5rem;
   }
   /* Responsive */
   @media (max-width: 900px) {
     .wiki-sidebar { display: none; }
     .header-search input { width: 180px; }
     .article-body { padding: 1rem; }
     .infobox { float: none; width: 100%; margin: 0 0 1rem; }
   }
 </style>

</head> <body>

   👤 کاربر مهمان
   <a href="#" onclick="openCreateModal(); return false;">صفحه جدید</a>
   <a href="#" onclick="showAllPages(); return false;">همه صفحات</a>
   <a href="#" onclick="exportData(); return false;">خروجی JSON</a>
   <a href="#" onclick="importData(); return false;">ورودی JSON</a>
 <header class="wiki-header">
 </header>
   <nav class="wiki-sidebar" id="sidebar">
   </nav>
       <button class="content-tab active" id="tab-read" onclick="switchTab('read')">خواندن</button>
       <button class="content-tab" id="tab-edit" onclick="switchTab('edit')">ویرایش</button>
 <input type="file" id="importInput" accept=".json" style="display:none;" onchange="handleImport(event)">
 <script>
   // ===== Data Layer =====
   let pages = JSON.parse(localStorage.getItem('mediawiki_pages') || '[]');
   let currentPageId = null;
   let editingPageId = null;
   function save() {
     localStorage.setItem('mediawiki_pages', JSON.stringify(pages));
   }
   function genId() {
     return Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
   }
   function getPageBySlug(slug) {
     return pages.find(p => p.slug === slug);
   }
   function slugify(text) {
     return text.trim().replace(/\s+/g, '-').replace(/[^\w\-\u0600-\u06FF]/g, ).toLowerCase();
   }
   // ===== Wiki Markup Parser =====
   function parseWikiMarkup(text) {
     let html = text;
     // Escape HTML first
     html = html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');

// Headings: == Title == →

, === Title === →

, etc. html = html.replace(/^======\s*(.+?)\s*======/gm, '

$1

'); html = html.replace(/^=====\s*(.+?)\s*=====/gm, '

$1

'); html = html.replace(/^====\s*(.+?)\s*====/gm, '

$1

'); html = html.replace(/^===\s*(.+?)\s*===/gm, '

$1

'); html = html.replace(/^==\s*(.+?)\s*==/gm, '

$1

');
     // Bold & Italic
     html = html.replace(/'{5}(.+?)'{5}/g, '$1');
     html = html.replace(/'{3}(.+?)'{3}/g, '$1');
     html = html.replace(/'{2}(.+?)'{2}/g, '$1');
     // Internal links: Page Name or display text
     html = html.replace(/\[\[([^\]|]+)\|([^\]]+)\]\]/g, '<a href="#" onclick="navigateToPage(\'$1\'); return false;" class="wiki-link">$2</a>');
     html = html.replace(/\[\[([^\]]+)\]\]/g, '<a href="#" onclick="navigateToPage(\'$1\'); return false;" class="wiki-link">$1</a>');
     // External links: text
     html = html.replace(/\[(https?:\/\/[^\s]+)\s+([^\]]+)\]/g, '<a href="$1" target="_blank">$2</a>');
     // Bare external: [۱]
     html = html.replace(/\[(https?:\/\/[^\s]+)\]/g, '<a href="$1" target="_blank">$1</a>');
     // Templates: الگو:یادآوری or الگو:Tip
html = html.replace(/\{\{یادآوری\|(.+?)\}\}/g, '
💡$1
'); html = html.replace(/\{\{tip\|(.+?)\}\}/g, '
✅$1
'); html = html.replace(/\{\{هشدار\|(.+?)\}\}/g, '
⚠️$1
'); html = html.replace(/\{\{note\|(.+?)\}\}/g, '
📝$1
');
     // Code blocks: ... or indented lines
html = html.replace(/([\s\S]*?)<\/code>/g, '
<code>$1</code>
');
     // Blockquote: lines starting with :
html = html.replace(/^:\s*(.+)$/gm, '

$1

');
     // Horizontal rule: ----
html = html.replace(/^----+$/gm, '
');
     // Unordered lists
html = html.replace(/^\* (.+)$/gm, '
  • $1
  • '); // Ordered lists html = html.replace(/^# (.+)$/gm, '
  • $1
  • '); // Wrap consecutive
  • in
      html = html.replace(/(<li[^>]*>.*?<\/li>\n?)+/g, (match) => { if (match.includes('list-style:decimal')) return '
        ' + match + '
      '; return '
        ' + match + '
      ';
           });
      
           // Line breaks: double newline = paragraph
      
      html = html.replace(/\n\n+/g, '

      ');

           html = html.replace(/\n/g, '
      ');
           // Wrap in paragraphs
      
      html = '

      ' + html + '

      ';
           // Remove empty paragraphs
      
      html = html.replace(/

      <\/p>/g, ); html = html.replace(/

      \s*<\/p>/g, ); return html; } // ===== Rendering ===== function showMainPage() { currentPageId = null; updateSidebarActive('صفحه اصلی'); const defaultContent = pages.length === 0  ? getWelcomeContent()  : getRecentChangesContent(); document.getElementById('readView').innerHTML = defaultContent; document.getElementById('tab-read').click(); document.getElementById('footerDate').textContent = formatDate(new Date().toISOString()); window.scrollTo(0, 0); } function getWelcomeContent() { return `

      صفحه اصلی

               📖
               به ویکی شخصی خود خوش آمدید! اینجا می‌توانید هر چیزی که یاد می‌گیرید را ذخیره کنید.
      

      نحوه استفاده

      برای شروع کار با این ویکی، از امکانات زیر استفاده کنید:

      • ایجاد صفحه جدید: از لینک «صفحه جدید» در بالای صفحه استفاده کنید.
      • ویرایش صفحه: روی تب «ویرایش» کلیک کنید.
      • پیوند بین صفحات: از نام صفحه استفاده کنید.
      • قالب‌بندی: از == عنوان == برای تیتر و متن برای بولد استفاده کنید.

      شروع کنید

      اکنون <a href="#" onclick="openCreateModal(); return false;">اولین صفحه خود را بسازید</a> و شروع به یادداشت کنید!

      راهنمای Wiki markup

      نمونه‌های قالب‌بندی
      عنوان بخش== عنوان == یا === زیرعنوان ===
      متن بولدمتن بولد
      متن ایتالیکمتن ایتالیک
      لیست* مورد اول (ترتیبی: # مورد اول)
      پیوند داخلینام صفحه یا متن نمایشی
      پیوند خارجیمتن لینک
      یادآوریالگو:یادآوری
      کد<code>کد</code>
      خط جداکننده----
      `;
         }
      
         function getRecentChangesContent() {
           const sorted = [...pages].sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
           const recent = sorted.slice(0, 10);
      
           let html = `
      

      صفحه اصلی

               🏠
               به ویکی شخصی خود خوش آمدید. <a href="#" onclick="openCreateModal(); return false;">صفحه جدید ایجاد کنید</a>.
      

      تغییرات اخیر

      آخرین صفحات ویرایش شده:

      `; recent.forEach(p => { const cats = p.categories.length > 0 ? p.categories.join(', ') : '—'; html += ` `; }); html += `
      صفحه رده آخرین ویرایش
      <a href="#" onclick="event.preventDefault();navigateToPage('${p.title}')">${escapeHtml(p.title)}</a> ${escapeHtml(cats)} ${formatDate(p.updatedAt)}
      `;
           // Stats
           html += `
      

      آمار ویکی

      • تعداد کل صفحات: ${pages.length}
      • تعداد رده‌ها: ${new Set(pages.flatMap(p => p.categories)).size}
      • بزرگ‌ترین صفحه: ${pages.length > 0 ? pages.reduce((a, b) => a.content.length > b.content.length ? a : b).title : '—'}
      `;
           return html;
         }
      
         function showAllPages() {
           currentPageId = null;
           updateSidebarActive('همه صفحات');
      
           const sorted = [...pages].sort((a, b) => a.title.localeCompare(b.title, 'fa'));
      
      let html = `

      همه صفحات

      `;
           if (sorted.length === 0) {
             html += `
      
                 📭
                 هنوز هیچ صفحه‌ای وجود ندارد. <a href="#" onclick="openCreateModal(); return false;">اولین صفحه را بسازید</a>.
      
      `;
           } else {
      
      html += `

      تعداد کل صفحات: ${sorted.length}

      `; html += `
        `; sorted.forEach(p => { const catText = p.categories.length > 0 ? ` — ${p.categories.join(', ')}` : ; html += `
      • <a href="#" onclick="navigateToPage('${escapeHtml(p.title)}'); return false;">${escapeHtml(p.title)}</a> ${catText} (${formatDate(p.updatedAt)})
      • `; }); html += `
      `;
           }
      
           document.getElementById('readView').innerHTML = html;
           document.getElementById('tab-read').click();
           document.getElementById('footerDate').textContent = formatDate(new Date().toISOString());
           window.scrollTo(0, 0);
         }
      
         function navigateToPage(title) {
           const page = pages.find(p => p.title === title || p.slug === slugify(title));
           if (page) {
             showPage(page.id);
           } else {
             // Page doesn't exist — offer to create
             if (confirm(`صفحه «${title}}» وجود ندارد. آیا می‌خواهید آن را بسازید؟`)) {
               editingPageId = null;
               document.getElementById('modalTitle').textContent = `ایجاد صفحه: ${title}`;
               document.getElementById('pageTitle').value = title;
               document.getElementById('pageCategory').value = ;
               document.getElementById('pageContent').value = ;
               document.getElementById('modalDeleteBtn').style.display = 'none';
               document.getElementById('editorModal').classList.add('show');
             }
           }
         }
      
         function showPage(id) {
           const page = pages.find(p => p.id === id);
           if (!page) return showMainPage();
      
           currentPageId = id;
           updateSidebarActive(page.title);
      
           const parsedContent = parseWikiMarkup(page.content);
      
           // Build TOC from headings
           const headingMatches = page.content.match(/^==+\s*(.+?)\s*==+$/gm) || [];
           let tocHtml = ;
           if (headingMatches.length >= 2) {
      
      tocHtml = `
      فهرست مطالب [隐蔽]
        `;
               headingMatches.forEach((h, i) => {
                 const depth = (h.match(/^=+/) || ['=='])[0].length - 2;
                 const text = h.replace(/^=+\s*/, ).replace(/\s*=+$/, );
                 const indent = depth > 0 ? `style="margin-right:${depth * 1.5}rem;"` : ;
        
        tocHtml += `
      1. <a href="#section-${i}">${text}</a>
      2. `; }); tocHtml += `
      `;
           }
      
           // Build categories
           let catHtml = ;
           if (page.categories.length > 0) {
      
      catHtml = ``;
           }
      
           // Infobox
           const wordCount = page.content.split(/\s+/).length;
           const charCount = page.content.length;
      
           const infobox = `
      
      ${escapeHtml(page.title)}
      ${page.categories.length > 0 ? `` : }
      تاریخ ایجاد${formatDate(page.createdAt)}
      آخرین ویرایش${formatDate(page.updatedAt)}
      تعداد کلمات${wordCount.toLocaleString('fa-IR')}
      تعداد حروف${charCount.toLocaleString('fa-IR')}
      رده‌ها${page.categories.join('، ')}
      `;
           // Assemble
      
      let fullHtml = `

      ${escapeHtml(page.title)}

      `;
           fullHtml += infobox;
           fullHtml += tocHtml;
           fullHtml += parsedContent;
      
      fullHtml += `
      `;
           fullHtml += catHtml;
      
           // See also — links to other pages
           const otherPages = pages.filter(p => p.id !== page.id).slice(0, 5);
           if (otherPages.length > 0) {
      
      fullHtml += `

      همچنین ببینید

      `; fullHtml += `
        `; otherPages.forEach(p => { fullHtml += `
      • <a href="#" onclick="navigateToPage('${escapeHtml(p.title)}'); return false;">${escapeHtml(p.title)}</a>
      • `; }); fullHtml += `
      `;
           }
      
           document.getElementById('readView').innerHTML = fullHtml;
           document.getElementById('tab-read').click();
           document.getElementById('footerDate').textContent = formatDate(page.updatedAt);
           window.scrollTo(0, 0);
      
           updateSidebar();
         }
      
         function renderEditView(id) {
           const page = id ? pages.find(p => p.id === id) : null;
      
      let html = `

      ${page ? 'ویرایش: ' + escapeHtml(page.title) : 'ایجاد صفحه جدید'}

      `;
           html += `
      
               راهنمای سریع Wiki markup:
      == عنوان == · بولد · ایتالیک · پیوند · الگو:یادآوری · * لیست · <code>kod</code>
               <label>عنوان صفحه</label>
               <input type="text" id="editTitle" value="${page ? escapeHtml(page.title) : }" placeholder="عنوان صفحه">
      
               <label>رده‌ها (با کاما جدا کنید)</label>
               <input type="text" id="editCategories" value="${page ? page.categories.join(', ') : }" placeholder="برنامه‌نویسی, یادداشت">
      
               <label>محتوا</label>
               <textarea id="editContent" placeholder="محتوای صفحه را اینجا بنویسید...">${page ? page.content : }</textarea>
      
               <button class="btn-save" onclick="saveFromEditor(${id ? `'${id}'` : 'null'})">ذخیره صفحه</button>
               <button class="btn-cancel" onclick="cancelEdit()">لغو</button>
               ${page ? `<button class="btn-delete" onclick="deletePage('${id}')">حذف صفحه</button>` : }
      
      `;
           document.getElementById('editView').innerHTML = html;
         }
      
         function switchTab(tab) {
           if (tab === 'read') {
             document.getElementById('tab-read').classList.add('active');
             document.getElementById('tab-edit').classList.remove('active');
             document.getElementById('readView').style.display = ;
             document.getElementById('editView').style.display = 'none';
           } else {
             document.getElementById('tab-edit').classList.add('active');
             document.getElementById('tab-read').classList.remove('active');
             document.getElementById('readView').style.display = 'none';
             document.getElementById('editView').style.display = ;
             renderEditView(currentPageId);
           }
         }
      
         function cancelEdit() {
           if (currentPageId) {
             switchTab('read');
           } else {
             showMainPage();
           }
         }
      
         function saveFromEditor(id) {
           const title = document.getElementById('editTitle').value.trim();
           const cats = document.getElementById('editCategories').value.split(',').map(c => c.trim()).filter(Boolean);
           const content = document.getElementById('editContent').value;
      
           if (!title) { document.getElementById('editTitle').focus(); return; }
           if (!content) { document.getElementById('editContent').focus(); return; }
      
           if (id) {
             const page = pages.find(p => p.id === id);
             if (page) {
               page.title = title;
               page.slug = slugify(title);
               page.categories = cats;
               page.content = content;
               page.updatedAt = new Date().toISOString();
             }
           } else {
             const newPage = {
               id: genId(),
               title,
               slug: slugify(title),
               categories: cats,
               content,
               createdAt: new Date().toISOString(),
               updatedAt: new Date().toISOString(),
             };
             pages.push(newPage);
             currentPageId = newPage.id;
           }
      
           save();
           showPage(currentPageId);
           updateSidebar();
         }
      
         // ===== Modal helpers =====
         function openCreateModal() {
           editingPageId = null;
           document.getElementById('modalTitle').textContent = 'ایجاد صفحه جدید';
           document.getElementById('pageTitle').value = ;
           document.getElementById('pageCategory').value = ;
           document.getElementById('pageContent').value = ;
           document.getElementById('modalDeleteBtn').style.display = 'none';
           document.getElementById('editorModal').classList.add('show');
           document.getElementById('pageTitle').focus();
         }
      
         function savePage() {
           const title = document.getElementById('pageTitle').value.trim();
           const cats = document.getElementById('pageCategory').value.split(',').map(c => c.trim()).filter(Boolean);
           const content = document.getElementById('pageContent').value;
      
           if (!title) { document.getElementById('pageTitle').focus(); return; }
           if (!content) { document.getElementById('pageContent').focus(); return; }
      
           const newPage = {
             id: genId(),
             title,
             slug: slugify(title),
             categories: cats,
             content,
             createdAt: new Date().toISOString(),
             updatedAt: new Date().toISOString(),
           };
      
           pages.push(newPage);
           save();
           closeModal('editorModal');
           currentPageId = newPage.id;
           showPage(newPage.id);
           updateSidebar();
         }
      
         function deletePage(id) {
           if (!confirm('آیا مطمئن هستید که می‌خواهید این صفحه را حذف کنید؟')) return;
           pages = pages.filter(p => p.id !== id);
           save();
           currentPageId = null;
           showMainPage();
           updateSidebar();
         }
      
         function deleteCurrentPage() {
           if (currentPageId) deletePage(currentPageId);
         }
      
         function closeModal(id) {
           document.getElementById(id).classList.remove('show');
         }
      
         // ===== Sidebar =====
         function updateSidebar() {
           const navPages = document.getElementById('navPages');
           const sorted = [...pages].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
           const recent = sorted.slice(0, 8);
      
      let html = `
    • <a href="#" class="${!currentPageId ? 'active-page' : }" onclick="showMainPage(); return false;">صفحه اصلی</a>
    • `; recent.forEach(p => { html += `
    • <a href="#" class="${currentPageId === p.id ? 'active-page' : }" onclick="navigateToPage('${escapeHtml(p.title)}'); return false;">${escapeHtml(p.title)}</a>
    • `; }); navPages.innerHTML = html; // Categories const allCats = [...new Set(pages.flatMap(p => p.categories))].sort(); const catList = document.getElementById('categoryList'); catList.innerHTML = allCats.map(c => `
    • <a href="#" onclick="filterByCategory('${escapeHtml(c)}'); return false;">${escapeHtml(c)}</a>
    • ` ).join(); } function updateSidebarActive(title) { document.querySelectorAll('.sidebar-section li a').forEach(a => { a.classList.remove('active-page'); if (a.textContent.trim() === title) a.classList.add('active-page'); }); } function filterByCategory(cat) { const matching = pages.filter(p => p.categories.includes(cat)); let html = `

      رده: ${escapeHtml(cat)}

      `; html += `

      تعداد صفحات در این رده: ${matching.length}

      `;
           if (matching.length > 0) {
      
      html += `
        `; matching.forEach(p => { html += `
      • <a href="#" onclick="navigateToPage('${escapeHtml(p.title)}'); return false;">${escapeHtml(p.title)}</a> (${formatDate(p.updatedAt)})
      • `; }); html += `
      `;
           }
      
           document.getElementById('readView').innerHTML = html;
           document.getElementById('tab-read').click();
           window.scrollTo(0, 0);
         }
      
         // ===== Search =====
         function searchPages() {
           const query = document.getElementById('globalSearch').value.trim().toLowerCase() ||
                         document.getElementById('sidebarSearch').value.trim().toLowerCase();
      
           if (!query) {
             if (currentPageId) return showPage(currentPageId);
             return showMainPage();
           }
      
           const results = pages.filter(p =>
             p.title.toLowerCase().includes(query) ||
             p.content.toLowerCase().includes(query) ||
             p.categories.some(c => c.toLowerCase().includes(query))
           );
      
      let html = `

      نتایج جستجو: «${escapeHtml(query)}»

      `; html += `

      ${results.length} نتیجه یافت شد.

      `;
           if (results.length > 0) {
      
      html += `
        `; results.forEach(p => { // Show context snippet const idx = p.content.toLowerCase().indexOf(query); let snippet = ; if (idx !== -1) { const start = Math.max(0, idx - 40); const end = Math.min(p.content.length, idx + query.length + 60); snippet = (start > 0 ? '...' : ) + p.content.substring(start, end) + (end < p.content.length ? '...' : ); } html += `
      • <a href="#" onclick="navigateToPage('${escapeHtml(p.title)}'); return false;">${escapeHtml(p.title)}</a>`; if (snippet) html += `
        ${escapeHtml(snippet)}`; html += `
      • `; }); html += `
      `;
           }
      
           document.getElementById('readView').innerHTML = html;
           document.getElementById('tab-read').click();
           window.scrollTo(0, 0);
         }
      
         // ===== Import/Export =====
         function exportData() {
           const data = JSON.stringify(pages, null, 2);
           const blob = new Blob([data], { type: 'application/json' });
           const url = URL.createObjectURL(blob);
           const a = document.createElement('a');
           a.href = url;
           a.download = `wiki-backup-${new Date().toISOString().slice(0, 10)}.json`;
           a.click();
           URL.revokeObjectURL(url);
         }
      
         function importData() {
           document.getElementById('importInput').click();
         }
      
         function handleImport(event) {
           const file = event.target.files[0];
           if (!file) return;
           const reader = new FileReader();
           reader.onload = (e) => {
             try {
               const data = JSON.parse(e.target.result);
               if (!Array.isArray(data)) throw new Error('Invalid format');
               if (confirm(`آیا می‌خواهید ${data.length} صفحه را وارد کنید؟ (صفحات فعلی حفظ می‌شوند)`)) {
                 data.forEach(p => {
                   if (p.title && p.content) {
                     pages.push({
                       id: p.id || genId(),
                       title: p.title,
                       slug: p.slug || slugify(p.title),
                       categories: p.categories || [],
                       content: p.content,
                       createdAt: p.createdAt || new Date().toISOString(),
                       updatedAt: p.updatedAt || new Date().toISOString(),
                     });
                   }
                 });
                 save();
                 showMainPage();
                 updateSidebar();
                 alert(`${data.length} صفحه با موفقیت وارد شد.`);
               }
             } catch (err) {
               alert('خطا در خواندن فایل. لطفاً فایل JSON معتبر وارد کنید.');
             }
           };
           reader.readAsText(file);
           event.target.value = ;
         }
      
         // ===== Helpers =====
         function formatDate(iso) {
           if (!iso) return '—';
           return new Date(iso).toLocaleDateString('fa-IR', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
         }
      
         function escapeHtml(text) {
           const d = document.createElement('div');
           d.textContent = text;
           return d.innerHTML;
         }
      
         // ===== Keyboard Shortcuts =====
         document.addEventListener('keydown', (e) => {
           if (e.key === 'Escape') {
             document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show'));
           }
           if (e.key === 'n' && (e.ctrlKey || e.metaKey)) {
             e.preventDefault();
             openCreateModal();
           }
         });
      
         // Close modal on overlay click
         document.querySelectorAll('.modal-overlay').forEach(o => {
           o.addEventListener('click', (e) => { if (e.target === o) o.classList.remove('show'); });
         });
      
         // ===== Init =====
         updateSidebar();
         showMainPage();
       </script>
      

      </body> </html>

  • منوی ناوبری