{os.label}
+ {isDetected ? النظام الحالي : null} +{description}
+ + {mobileUnavailable ? ( +diff --git a/docusaurus.config.js b/docusaurus.config.js index 2a6c311..2bfb813 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -104,6 +104,11 @@ deploymentBranch: 'gh-pages', position: 'left', label: 'الدروس', }, + { + to: '/download', + position: 'left', + label: 'تنزيل', + }, // {to: '/blog', label: 'المدونة', position: 'left'}, { href: 'https://github.com/daadLang', diff --git a/src/css/custom.css b/src/css/custom.css index 951974b..0a638cc 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -4,6 +4,38 @@ * work well for content-centric websites. */ +@font-face { + font-family: 'DG Heaven'; + src: url('../../static/font/DG-Heaven-Thin.ttf') format('truetype'); + font-style: normal; + font-weight: 400; + font-display: swap; +} + +@font-face { + font-family: 'DG Heaven'; + src: url('../../static/font/DG-Heaven-Thin.ttf') format('truetype'); + font-style: normal; + font-weight: 700; + font-display: swap; +} + +@font-face { + font-family: 'TheYearofTheCamel'; + src: url('../../static/font/TheYearofTheCamel-Regular.otf') format('opentype'); + font-style: normal; + font-weight: 400; + font-display: swap; +} + +@font-face { + font-family: 'TheYearofTheCamel'; + src: url('../../static/font/TheYearofTheCamel-Regular.otf') format('opentype'); + font-style: normal; + font-weight: 700; + font-display: swap; +} + /* You can override the default Infima variables here. */ :root { --ifm-color-primary: #2e8555; @@ -15,6 +47,9 @@ --ifm-color-primary-lightest: #3cad6e; --ifm-code-font-size: 95%; --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); + --ifm-font-family-base: 'TheYearofTheCamel', 'Segoe UI', Tahoma, sans-serif; + --ifm-heading-font-family: 'DG Heaven', 'Segoe UI', Tahoma, sans-serif; + --ifm-font-family-monospace: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace; } /* For readability concerns, you should choose a lighter palette in dark mode. */ @@ -33,6 +68,30 @@ html, body { direction: rtl !important; text-align: right; + font-family: 'TheYearofTheCamel', 'Segoe UI', Tahoma, sans-serif; +} + +body, +button, +input, +textarea, +select, +p, +a, +li, +label, +span, +div { + font-family: 'TheYearofTheCamel', 'Segoe UI', Tahoma, sans-serif; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: 'DG Heaven', 'Segoe UI', Tahoma, sans-serif; } /* Ensure code blocks preserve direction where needed */ @@ -118,4 +177,4 @@ pre, code, .markdown, .theme-doc-markdown { text-align: center; gap: 0.5rem; } -} \ No newline at end of file +} diff --git a/src/pages/download.js b/src/pages/download.js index 788fe5a..d8b6af6 100644 --- a/src/pages/download.js +++ b/src/pages/download.js @@ -1,244 +1,588 @@ -import React, {useMemo, useState, useEffect} from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; import Layout from '@theme/Layout'; -import useBaseUrl from '@docusaurus/useBaseUrl'; import Link from '@docusaurus/Link'; +import styles from './download.module.css'; -// A modern download page: latest release summary, release selector, platform cards - -const EXAMPLE_RELEASES = [ - { - id: 1, - tag_name: 'v0.2.0', - name: 'إصدار 0.2.0', - published_at: '2026-01-15', - assets: [ - { name: 'daad-0.2.0-windows-installer.exe', browser_download_url: 'https://example.com/downloads/daad-0.2.0-windows-installer.exe' }, - { name: 'daad-0.2.0-mac.dmg', browser_download_url: 'https://example.com/downloads/daad-0.2.0-mac.dmg' }, - { name: 'daad-0.2.0-linux.AppImage', browser_download_url: 'https://example.com/downloads/daad-0.2.0-linux.AppImage' }, - { name: 'daad-0.2.0-lang.zip', browser_download_url: 'https://example.com/downloads/daad-0.2.0-lang.zip' }, - { name: 'daad-0.2.0-ide.zip', browser_download_url: 'https://example.com/downloads/daad-0.2.0-ide.zip' }, - ], - }, - { - id: 2, - tag_name: 'v0.1.0', - name: 'الإصدار التجريبي 0.1.0', - published_at: '2025-12-01', - assets: [ - { name: 'daad-0.1.0.zip', browser_download_url: 'https://example.com/downloads/daad-0.1.0.zip' }, - ], - }, +const GITHUB_API = { + language: 'https://api.github.com/repos/daadLang/daad/releases/latest', + editor: 'https://api.github.com/repos/daadLang/d-editor/releases/latest', +}; + +const RELEASE_LINKS = { + languageAll: 'https://github.com/daadLang/daad/releases', + editorAll: 'https://github.com/daadLang/d-editor/releases', +}; + +const OS_LIST = [ + {key: 'windows', label: 'ويندوز'}, + {key: 'macos', label: 'ماك'}, + {key: 'linux', label: 'لينكس'}, + {key: 'android', label: 'أندرويد'}, + {key: 'ios', label: 'iOS'}, ]; -function detectPlatform() { - if (typeof navigator === 'undefined') return 'linux'; - const p = navigator.platform.toLowerCase(); - if (p.includes('win')) return 'windows'; - if (p.includes('mac') || p.includes('darwin')) return 'mac'; - if (p.includes('linux')) return 'linux'; - return 'linux'; +const DESKTOP_OS_KEYS = new Set(['windows', 'macos', 'linux']); +const MOBILE_OS_KEYS = new Set(['android', 'ios']); + +function prioritizeDetectedOS(osList, detectedKey) { + if (!detectedKey || detectedKey === 'unknown') return osList; + const detected = osList.find((os) => os.key === detectedKey); + if (!detected) return osList; + return [detected, ...osList.filter((os) => os.key !== detectedKey)]; +} + +const OS_TECH_LABEL = { + windows: 'Windows', + macos: 'macOS', + linux: 'Linux', + android: 'Android', + ios: 'iOS', + unknown: 'Unknown', +}; + +function isMobileOS(os) { + return os === 'android' || os === 'ios'; +} + +function normalizePlatform(raw) { + const value = String(raw || '').toLowerCase(); + if (value.includes('win')) return 'windows'; + if (value.includes('mac') || value.includes('darwin')) return 'macos'; + if (value.includes('android')) return 'android'; + if (value.includes('iphone') || value.includes('ipad') || value.includes('ipod') || value.includes('ios')) return 'ios'; + if (value.includes('linux') || value.includes('x11')) return 'linux'; + return 'unknown'; +} + +function normalizeArch(raw) { + const value = String(raw || '').toLowerCase(); + if (value.includes('arm64') || value.includes('aarch64')) return 'arm64'; + if (value.includes('arm')) return 'arm'; + if (value.includes('x64') || value.includes('x86_64') || value.includes('amd64') || value.includes('win64')) return 'x64'; + if (value.includes('x86') || value.includes('i386') || value.includes('i686') || value.includes('win32')) return 'x86'; + return 'unknown'; +} + +function detectClient() { + if (typeof navigator === 'undefined') { + return {os: 'unknown', arch: 'unknown'}; + } + + const osSource = navigator.userAgentData?.platform || `${navigator.platform} ${navigator.userAgent}`; + const archSource = navigator.userAgentData?.architecture || `${navigator.platform} ${navigator.userAgent}`; + + return { + os: normalizePlatform(osSource), + arch: normalizeArch(archSource), + }; +} + +function stageLabel(tag) { + const t = String(tag || '').toLowerCase(); + if (t.includes('beta')) return 'Beta'; + if (t.includes('alpha')) return 'Alpha'; + if (t.includes('rc')) return 'RC'; + return 'Stable'; +} + +function archLabel(value) { + if (value === 'x64') return 'x64'; + if (value === 'x86') return 'x86'; + if (value === 'arm64') return 'ARM64'; + if (value === 'arm') return 'ARM'; + return 'Universal'; +} + +function detectArchFromName(name) { + const n = String(name || '').toLowerCase(); + if (/(x64|x86_64|amd64)/.test(n)) return 'x64'; + if (/(x86|i386|i686)/.test(n)) return 'x86'; + if (/(arm64|aarch64)/.test(n)) return 'arm64'; + if (/\barm\b/.test(n)) return 'arm'; + return 'unknown'; +} + +function fileTypeLabel(name) { + const n = name.toLowerCase(); + if (n.endsWith('.appimage')) return 'AppImage'; + if (n.endsWith('.apk')) return 'APK (Alpine)'; + if (n.endsWith('.deb')) return 'DEB'; + if (n.endsWith('.rpm')) return 'RPM'; + if (n.endsWith('.tar.gz')) return 'TAR.GZ'; + if (n.endsWith('.tar.xz')) return 'TAR.XZ'; + if (n.endsWith('.exe')) return 'EXE'; + if (n.endsWith('.msi')) return 'MSI'; + if (n.endsWith('.dmg')) return 'DMG'; + if (n.endsWith('.pkg')) return 'PKG'; + if (n.endsWith('.ipa')) return 'IPA'; + if (n.endsWith('.zip')) return 'ZIP'; + return 'File'; +} + +function hasAny(text, patterns) { + return patterns.some((p) => text.includes(p)); +} + +function detectAssetPlatform(name) { + const n = name.toLowerCase(); + if (hasAny(n, ['windows_', 'windows-', '_windows', '-windows', 'win-'])) return 'windows'; + if (hasAny(n, ['darwin_', 'darwin-', '_darwin', '-darwin', 'mac', 'osx'])) return 'macos'; + if (hasAny(n, ['linux_', 'linux-', '_linux', '-linux'])) return 'linux'; + if (hasAny(n, ['android', 'aab', '.apk']) && !hasAny(n, ['linux_', 'linux-'])) return 'android'; + if (hasAny(n, ['ios', 'iphone', 'ipad', '.ipa'])) return 'ios'; + return 'unknown'; +} + +function isCompatibleEditorAsset(assetName, os) { + const n = assetName.toLowerCase(); + const platform = detectAssetPlatform(assetName); + + if (/sha256|checksums?|\.sig$|\.txt$/.test(n)) return false; + + if (os === 'windows') return platform === 'windows' && (n.endsWith('.exe') || n.endsWith('.msi')); + + if (os === 'macos') { + return platform === 'macos' && (n.endsWith('.dmg') || n.endsWith('.pkg') || n.endsWith('.zip') || n.endsWith('.tar.gz')); + } + + if (os === 'linux') { + return platform === 'linux' && (n.endsWith('.appimage') || n.endsWith('.deb') || n.endsWith('.rpm') || n.endsWith('.tar.gz') || n.endsWith('.tar.xz')); + } + + if (os === 'android') return platform === 'android' && (n.endsWith('.apk') || n.endsWith('.aab')); + + if (os === 'ios') return platform === 'ios' && n.endsWith('.ipa'); + + return false; +} + +function isCompatibleCliAsset(name, os) { + const n = name.toLowerCase(); + if (/sha256|checksums?|\.sig$|\.txt$/.test(n)) return false; + const platform = detectAssetPlatform(name); + + if (os === 'windows') return platform === 'windows' && n.endsWith('.zip'); + if (os === 'macos') return platform === 'macos' && (n.endsWith('.tar.gz') || n.endsWith('.zip') || n.endsWith('.tar.xz')); + if (os === 'linux') return platform === 'linux' && (n.endsWith('.apk') || n.endsWith('.deb') || n.endsWith('.rpm') || n.endsWith('.tar.gz') || n.endsWith('.tar.xz')); + if (os === 'android') return platform === 'android' && (n.endsWith('.apk') || n.endsWith('.aab')); + if (os === 'ios') return platform === 'ios' && n.endsWith('.ipa'); + + return false; +} + +function archScore(name, arch) { + const n = name.toLowerCase(); + if (arch === 'x64' && /(x64|x86_64|amd64)/.test(n)) return 3; + if (arch === 'x86' && /(x86|i386|i686)/.test(n)) return 3; + if (arch === 'arm64' && /(arm64|aarch64)/.test(n)) return 3; + if (arch === 'arm' && /\barm\b/.test(n)) return 3; + return 0; +} + +function typePriority(name, os) { + const n = name.toLowerCase(); + + if (os === 'windows') { + if (n.endsWith('.exe')) return 12; + if (n.endsWith('.msi')) return 10; + } + + if (os === 'macos') { + if (n.endsWith('.dmg')) return 12; + if (n.endsWith('.pkg')) return 10; + } + + if (os === 'linux') { + if (n.endsWith('.appimage')) return 12; + if (n.endsWith('.deb')) return 10; + if (n.endsWith('.rpm')) return 9; + if (n.endsWith('.apk')) return 9; + if (n.endsWith('.tar.gz')) return 8; + if (n.endsWith('.tar.xz')) return 7; + } + + if (os === 'android' && n.endsWith('.apk')) return 12; + if (os === 'ios' && n.endsWith('.ipa')) return 12; + + return 1; +} + +function collectEditorAssets(release, os, arch) { + if (!release?.assets?.length) return []; + + return release.assets + .filter((asset) => isCompatibleEditorAsset(asset.name, os)) + .map((asset) => { + const detectedArch = detectArchFromName(asset.name); + return { + name: asset.name, + url: asset.browser_download_url, + type: fileTypeLabel(asset.name), + arch: detectedArch !== 'unknown' ? detectedArch : arch, + score: typePriority(asset.name, os) + archScore(asset.name, arch), + }; + }) + .sort((a, b) => b.score - a.score); +} + +function collectCliAssets(release, os, arch) { + if (!release?.assets?.length) return []; + + return release.assets + .filter((asset) => isCompatibleCliAsset(asset.name, os)) + .map((asset) => { + return { + name: asset.name, + url: asset.browser_download_url, + type: fileTypeLabel(asset.name), + arch: detectArchFromName(asset.name), + score: typePriority(asset.name, os) + archScore(asset.name, arch), + }; + }) + .sort((a, b) => b.score - a.score); } -function pickAsset(release, pattern) { - if (!release || !release.assets) return null; - const found = release.assets.find(a => a.name.toLowerCase().includes(pattern)); - return found || null; +async function fetchLatestRelease(url) { + const res = await fetch(url, { + headers: { + Accept: 'application/vnd.github+json', + }, + }); + + if (!res.ok) { + throw new Error(`GitHub API error: ${res.status}`); + } + + const data = await res.json(); + return { + html_url: data.html_url, + tag_name: data.tag_name, + assets: Array.isArray(data.assets) ? data.assets : [], + }; } -function PlatformCard({platform, release, recommended}) { - const primary = pickAsset(release, platform) || pickAsset(release, 'installer') || release?.assets?.[0]; - const lang = pickAsset(release, 'lang') || pickAsset(release, '.zip') || null; - const ide = pickAsset(release, 'ide') || null; +function buildPlan(os, arch, releases) { + const target = os; + + if (target === 'unknown') { + return { + os: 'unknown', + osTechLabel: OS_TECH_LABEL.unknown, + version: releases.editor?.tag_name || releases.language?.tag_name || null, + stage: 'Stable', + editorOptions: [], + cliOptions: [], + editorPrimary: null, + cliPrimary: null, + hasEditorInstall: false, + hasCli: false, + packageMeta: null, + editorTag: releases.editor?.tag_name || null, + languageTag: releases.language?.tag_name || null, + loading: releases.loading, + error: releases.error, + }; + } + + const editorSource = isMobileOS(target) ? releases.language : releases.editor; + + const editorOptions = collectEditorAssets(editorSource, target, arch); + const cliOptions = collectCliAssets(releases.language, target, arch); + + const editorPrimary = editorOptions[0] || null; + const cliPrimary = cliOptions[0] || null; + + const version = editorSource?.tag_name || releases.editor?.tag_name || releases.language?.tag_name || null; + const stage = stageLabel(version); + + return { + os: target, + osTechLabel: OS_TECH_LABEL[target] || OS_TECH_LABEL.unknown, + version, + stage, + editorOptions, + cliOptions, + editorPrimary, + cliPrimary, + hasEditorInstall: editorOptions.length > 0, + hasCli: cliOptions.length > 0, + packageMeta: editorPrimary ? `${editorPrimary.type} • ${archLabel(editorPrimary.arch)} • ${stage}` : null, + editorTag: releases.editor?.tag_name || null, + languageTag: releases.language?.tag_name || null, + loading: releases.loading, + error: releases.error, + }; +} + +function LinuxPackagePicker({options, label, buttonLabel, idPrefix, buttonVariant = 'primary'}) { + const [selectedIndex, setSelectedIndex] = useState(0); + const selected = options[selectedIndex] || null; + + useEffect(() => { + setSelectedIndex(0); + }, [options]); + + const selectId = `${idPrefix}-package`; + const buttonClass = buttonVariant === 'secondary' ? 'button button--secondary button--md' : 'button button--primary button--md'; return ( -
حزمة مُختارة لنظام {platform}.
- -{description}
+ + {mobileUnavailable ? ( +تاريخ الإصدار: {selected.published_at}
-نسخة متوافقة مع {detectedPlan.osTechLabel}
+ +{detectedPlan.packageMeta}
+ ) : null} + + {detectedPlan.loading ?جارِ التحقق من آخر الإصدارات...
: null} + {detectedPlan.error ?{detectedPlan.error}
: null} + {!detectedPlan.loading && !detectedPlan.error ? ( ++ آخر إصدار للمحرر: {detectedPlan.editorTag || '-'} | آخر إصدار للغة: {detectedPlan.languageTag || '-'} +
+ ) : null}قائمة إصدارات من المثال. لاحقاً يمكن سحب هذه القائمة مباشرة من GitHub Releases عبر API.
-تم تحديد النسخة الأنسب لنظام تشغيلك آلياً.
+حمّل ملف التثبيت المتكامل (المحرر + اللغة).
+شغّل الملف وابدأ رحلتك البرمجية فوراً.
+قائمة إصدارات من المثال. لاحقاً يمكن سحب هذه القائمة مباشرة من GitHub Releases عبر API.
- -- اختر ما تريد تنزيله من الروابط الرسمية على GitHub — الحزم الرسمية والـ installers للمشاريع. -
-- هذا المشروع لا يزال قيد التطوير. سنكون سعداء بتلقي ملاحظاتك — يرجى التواصل معنا إذا واجهت أي مشاكل أو كانت لديك اقتراحات. -
- -- تأكد دوماً من تنزيل الملفات من صفحة الإصدارات الرسمية وتحقق من توقيع الإصدار - إذا كان متوفراً. -
-