kreftjs 0.0.19-beta → 0.0.20-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/bin/build.js +13 -2
- package/bin/kreftjs.js +1 -0
- package/book/ar/01-getting-started.md +169 -0
- package/book/ar/02-language.md +330 -0
- package/book/ar/03-components-and-state.md +258 -0
- package/book/ar/04-router-data-forms.md +333 -0
- package/book/ar/05-arabic-rtl.md +175 -0
- package/book/ar/06-backend.md +247 -0
- package/book/ar/07-auth-and-security.md +157 -0
- package/book/ar/08-background-work.md +250 -0
- package/book/ar/09-testing-and-deploy.md +234 -0
- package/book/ar/10-electron-desktop.md +258 -0
- package/book/ar/11-advanced-blog.md +370 -0
- package/book/ar/12-advanced-shop.md +426 -0
- package/book/ar/13-advanced-ui.md +440 -0
- package/book/ar/14-advanced-ops.md +167 -0
- package/book/ar/15-appendix.md +81 -0
- package/book/ar/README.md +62 -0
- package/book/kreft-book-ar.docx +0 -0
- package/docs/README.md +2 -2
- package/kreftjs.d.ts +1 -1
- package/kreftjs.js +436 -74
- package/package.json +4 -3
- package/templates/app/frontend/errors/ErrorMonitor.kreft +2 -2
- package/templates/app/frontend/kreftjs.d.ts +1 -1
- package/templates/app/frontend/kreftjs.js +436 -74
- package/templates/app/frontend/pages/Dashboard.kreft +1 -1
- package/templates/errors/ErrorMonitor.kreft +1 -1
package/README.md
CHANGED
|
@@ -65,6 +65,7 @@ The app icon is `build/icon.png`, and `ELECTRON_MENU=show|auto|hide` controls th
|
|
|
65
65
|
|
|
66
66
|
| I want to… | Read |
|
|
67
67
|
|---|---|
|
|
68
|
+
| Learn Kreft in Arabic, from basics to advanced tested examples | [كتاب Kreft بالعربية](book/ar/README.md) |
|
|
68
69
|
| Build my first page | [Your first app](docs/README.md#your-first-app) and [Components](docs/README.md#components-and-the-kreft-file) |
|
|
69
70
|
| Fetch data and handle forms | [Fetching data](docs/README.md#fetching-data), [Forms](docs/README.md#forms-and-validation) |
|
|
70
71
|
| Add an API and a database | [Routing](docs/README.md#routing), [Models](docs/README.md#models-and-the-database), [Migrations](docs/README.md#migrations-seeders-and-factories) |
|
package/bin/build.js
CHANGED
|
@@ -34,12 +34,13 @@ const SERVER_DIRS = new Set();
|
|
|
34
34
|
const SKIP = new Set(['node_modules', 'dist', 'dist-electron', '.git', '.vscode', '.idea', 'package.json', 'package-lock.json', '.DS_Store']);
|
|
35
35
|
|
|
36
36
|
module.exports = function build(args) {
|
|
37
|
-
let folder = null, out = null, base = null, prerender = null, urlsFile = null, htaccess = true;
|
|
37
|
+
let folder = null, out = null, base = null, prerender = null, urlsFile = null, htaccess = true, minify = true;
|
|
38
38
|
const include = [];
|
|
39
39
|
for (let i = 0; i < args.length; i++) {
|
|
40
40
|
if (args[i] === '--out' || args[i] === '-o') out = args[++i];
|
|
41
41
|
else if (args[i] === '--base') base = args[++i];
|
|
42
42
|
else if (args[i] === '--no-htaccess') htaccess = false;
|
|
43
|
+
else if (args[i] === '--no-minify') minify = false; // keep kreftjs.js readable (for debugging)
|
|
43
44
|
else if (args[i] === '--include') include.push(args[++i]); // another folder to copy in, like the docs
|
|
44
45
|
else if (args[i] === '--prerender') prerender = args[i + 1] && !args[i + 1].startsWith('-') && args[i + 1].includes('/') ? args[++i].split(',') : [];
|
|
45
46
|
else if (args[i] === '--urls') urlsFile = args[++i]; // a file with more addresses to prerender (pages from your data)
|
|
@@ -154,7 +155,17 @@ ${indent(components.join('\n\n'))}
|
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
|
-
|
|
158
|
+
// the framework file is minified when esbuild is installed (about 40% smaller); without it, it is copied as it is
|
|
159
|
+
const runtimeSrc = path.join(__dirname, '..', 'kreftjs.js'), runtimeOut = path.join(dist, 'kreftjs.js');
|
|
160
|
+
const esb = minify ? deps.loadEsbuild(root) : null;
|
|
161
|
+
let minified = false;
|
|
162
|
+
if (esb) {
|
|
163
|
+
try {
|
|
164
|
+
fs.writeFileSync(runtimeOut, esb.transformSync(fs.readFileSync(runtimeSrc, 'utf8'), { minify: true, target: 'es2020', legalComments: 'none' }).code);
|
|
165
|
+
minified = true;
|
|
166
|
+
} catch (_) { /* fall back to the plain copy */ }
|
|
167
|
+
}
|
|
168
|
+
if (!minified) fs.copyFileSync(runtimeSrc, runtimeOut);
|
|
158
169
|
for (const dir of include) {
|
|
159
170
|
const from = path.resolve(dir);
|
|
160
171
|
if (!fs.existsSync(from)) fail(`--include: ${dir} does not exist.`);
|
package/bin/kreftjs.js
CHANGED
|
@@ -165,6 +165,7 @@ function help() {
|
|
|
165
165
|
--prerender [paths] Also save every page as ready HTML (needs: npm i -D jsdom)
|
|
166
166
|
--include <folder> Also copy this folder into the output (for example docs)
|
|
167
167
|
--no-htaccess Do not write dist/.htaccess (page-address rules for Apache and Hostinger)
|
|
168
|
+
--no-minify Copy kreftjs.js as it is (by default it is minified when esbuild is installed)
|
|
168
169
|
kreft add <packages...> Install npm packages with strict security gatekeeper (alias: kreft install, kreft i)
|
|
169
170
|
-D, --dev Install as devDependency
|
|
170
171
|
-y, --yes Auto-confirm unverified community packages in CI/CD
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# الفصل الأول: البداية
|
|
2
|
+
|
|
3
|
+
## ما هو Kreft؟
|
|
4
|
+
|
|
5
|
+
Kreft (يُكتب `kreftjs`) إطار عمل صغير، تفاعلي (reactive)، له لغة مكوّنات خاصة به: ملفات `.kreft`. تكتب في الملف الواحد حالتك وقيمك المشتقة وأفعالك بكلمات بسيطة، ثم تصف الصفحة في `<view>`. ويأتي مع واجهة خلفية كاملة على Node.js (مسارات، ومتحكمات، ونماذج، وترحيلات، وطوابير، وبريد)، فيغطي المشروع الواحد التطبيق بأكمله.
|
|
6
|
+
|
|
7
|
+
> **ملاحظة:** الإصدار الحالي تجريبي (`0.0.x-beta`). كل ما في هذا الكتاب جُرِّب على الإصدار الذي صدر معه، لكن واجهات الإطار قد تتغير بين الإصدارات التجريبية. راجع `kreft --version` وملف `docs/README.md` (بالإنجليزية) عند الشك.
|
|
8
|
+
|
|
9
|
+
لماذا قد تختاره؟
|
|
10
|
+
|
|
11
|
+
- **لغة واحدة للمكوّن:** HTML ومنطق وتنسيق في ملف واحد، بلا ديكورات ولا وحدات (modules) تتعلمها.
|
|
12
|
+
- **بدون إعداد:** `kreft new` ثم `kreft dev`. لا ملف إعداد لأداة تجميع.
|
|
13
|
+
- **واجهة خلفية جاهزة:** تسجيل دخول، وتحقق من البريد، واستعادة كلمة المرور، ولوحة إدارة، ومصادقة ثنائية.
|
|
14
|
+
- **تطبيقات سطح المكتب:** `kreft new --electron` ثم تبني مثبّتات لـ Windows وmacOS وLinux.
|
|
15
|
+
- **دعم RTL من الأساس:** تبديل اللغة واتجاه الصفحة من دالة واحدة (مهم لنا نحن العرب، وله فصل كامل).
|
|
16
|
+
|
|
17
|
+
## التثبيت
|
|
18
|
+
|
|
19
|
+
المتطلبات: Node.js (يُفضَّل 22 أو أحدث) وnpm.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g kreftjs
|
|
23
|
+
kreft --version # مثال: 0.0.19-beta
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
للتحقق من أن كل شيء سليم:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
kreft about
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
وللحصول على تلوين الملفات وملفات المقتطفات (snippets) في VS Code أو Cursor:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
kreft vscode
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## أول تطبيق
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
kreft new my-app # يسألك: JavaScript أم TypeScript؟
|
|
42
|
+
cd my-app
|
|
43
|
+
npm run dev
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
افتح العنوان الذي يطبعه الطرفية (عادة `http://localhost:8000`؛ وإن كان المنفذ مشغولًا يُستخدم التالي).
|
|
47
|
+
|
|
48
|
+
خيارات مفيدة عند الإنشاء:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
kreft new shop --db mysql --ts # TypeScript وقاعدة MySQL
|
|
52
|
+
kreft new site --ui tailwind --no-auth # Tailwind بدون صفحات تسجيل الدخول
|
|
53
|
+
kreft new desk --electron # مع دعم تطبيقات سطح المكتب
|
|
54
|
+
kreft new app-name --ui bootstrap --no-auth
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> **تنبيه:** اكتب قيم الخيارات كما هي بالضبط: `bootstrap` و`tailwind` و`bulma` و`pico`. أي قيمة أخرى توقف الأمر مع رسالة تعرض القيم المسموحة.
|
|
58
|
+
|
|
59
|
+
يأتي المشروع الجديد (إن لم تستعمل `--no-auth`) بصفحات الدخول والتسجيل واستعادة كلمة المرور وتأكيد البريد والملف الشخصي ولوحة الإدارة. وعند تشغيل `npm run dev` تُنشأ الجداول ومستخدم تجريبي:
|
|
60
|
+
|
|
61
|
+
- البريد: `test@example.com`
|
|
62
|
+
- كلمة المرور: `password`
|
|
63
|
+
|
|
64
|
+
> **تحذير:** هذا المستخدم للتطوير المحلي فقط. احذفه أو لا تشغّل `--seed` أبدًا على قاعدة بيانات حقيقية.
|
|
65
|
+
|
|
66
|
+
رسائل البريد (تأكيد، استعادة) تُطبع في الطرفية إلى أن تضبط `MAIL_URL`.
|
|
67
|
+
|
|
68
|
+
## أصغر تطبيق ممكن
|
|
69
|
+
|
|
70
|
+
يمكنك استعمال Kreft بلا مشروع كامل. التطبيق صفحة HTML وملفات `.kreft`:
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<!-- index.html -->
|
|
74
|
+
<div id="app"></div>
|
|
75
|
+
<script src="kreftjs.js"></script>
|
|
76
|
+
<script>
|
|
77
|
+
Kreftjs.app('App.kreft').mount('#app')
|
|
78
|
+
</script>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<!-- App.kreft -->
|
|
83
|
+
state name = 'العالم'
|
|
84
|
+
|
|
85
|
+
<view>
|
|
86
|
+
<h1>مرحبًا يا {name}</h1>
|
|
87
|
+
<input k-model="name">
|
|
88
|
+
</view>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
> **تحذير:** ملفات `.kreft` تحتاج خادمًا. المتصفحات ترفض تحميل الملفات من `file://`، فافتح التطبيق دائمًا عبر `npm run dev` أو أي خادم.
|
|
92
|
+
|
|
93
|
+
## بنية المشروع
|
|
94
|
+
|
|
95
|
+
ينشئ `kreft new` مجلدين لا يختلطان أبدًا: `frontend/` هو ما يصل المتصفح، و`backend/` هو الخادم ولا يمكن الوصول إليه عبر HTTP.
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
my-app/
|
|
99
|
+
├── frontend/
|
|
100
|
+
│ ├── index.html نقطة الدخول
|
|
101
|
+
│ ├── App.kreft المكوّن الجذر وحاوية الموجِّه
|
|
102
|
+
│ ├── routes/web.kreft مسارات الصفحات
|
|
103
|
+
│ ├── layouts/ app.kreft (بعد الدخول)، guest.kreft (صفحات الدخول)
|
|
104
|
+
│ ├── pages/ ملف .kreft لكل شاشة
|
|
105
|
+
│ ├── components/ مكوّنات قابلة لإعادة الاستخدام
|
|
106
|
+
│ ├── ui.css نظام التصميم
|
|
107
|
+
│ └── tests/ اختبارات المكوّنات
|
|
108
|
+
├── backend/
|
|
109
|
+
│ ├── config/ app.js, auth.js, database.js, redis.js, session.js
|
|
110
|
+
│ ├── app/
|
|
111
|
+
│ │ ├── Http/ Controllers, Middleware, Requests, Resources
|
|
112
|
+
│ │ ├── Models/ User, PersonalAccessToken
|
|
113
|
+
│ │ └── Auth/ مساعدات الجلسات
|
|
114
|
+
│ ├── database/ migrations, seeders, factories
|
|
115
|
+
│ └── routes/ api.js, auth.js, console.js
|
|
116
|
+
├── .env الأسرار والإعدادات (لا ترفعه إلى Git أبدًا)
|
|
117
|
+
├── package.json
|
|
118
|
+
└── kreftjs.config.json خيارات سطر الأوامر
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
لماذا الفصل مهم؟
|
|
122
|
+
|
|
123
|
+
- `kreft dev` و`kreft build` يقدّمان `frontend/` فقط. أما `backend/` وملفات قاعدة البيانات و`.env` فلا عنوان ويب لها.
|
|
124
|
+
- على macOS وLinux يمنح `kreft new` المجلد `backend/` والملف `.env` صلاحيات يقرؤها المالك فقط.
|
|
125
|
+
- ملفات مثل `.env` و`*.db` و`*.pem` و`package.json` مرفوضة حتى لو طُلبت بالاسم.
|
|
126
|
+
|
|
127
|
+
## إضافة مكتبة CSS
|
|
128
|
+
|
|
129
|
+
أي مكتبة تعمل. أضف `<link>` في `index.html`، أو ثبّتها واستورد ملفها من ملف `.kreft`:
|
|
130
|
+
|
|
131
|
+
```html
|
|
132
|
+
import 'bootstrap/dist/css/bootstrap.min.css' // npm i bootstrap
|
|
133
|
+
import './theme.css' // ملفك الخاص بجانب هذا الملف
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## حزم npm في المتصفح
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
kreft add dayjs nanoid lodash
|
|
140
|
+
kreft add -D esbuild # مرة واحدة لكل مشروع: يجهّز الحزم للمتصفح
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
import dayjs from 'dayjs'
|
|
145
|
+
import { nanoid } from 'nanoid'
|
|
146
|
+
|
|
147
|
+
state id = nanoid()
|
|
148
|
+
|
|
149
|
+
<view>
|
|
150
|
+
<p>{dayjs().format('YYYY-MM-DD')}</p>
|
|
151
|
+
</view>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
الأمر `kreft add` نسخة أكثر أمانًا من `npm install`: يمنع سكربتات التثبيت افتراضيًا، ويتحقق من أخطاء الكتابة المتعمدة في أسماء الحزم (typosquatting)، وينبّه إلى الحزم التي نُشرت قبل أقل من 14 يومًا، ثم يشغّل `npm audit`.
|
|
155
|
+
|
|
156
|
+
## أوامر ستستخدمها كل يوم
|
|
157
|
+
|
|
158
|
+
| الأمر | وظيفته |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `kreft new my-app` | ينشئ مشروعًا |
|
|
161
|
+
| `kreft dev` | خادم التطوير مع إعادة تحميل فورية |
|
|
162
|
+
| `kreft build` | يصرّف التطبيق إلى ملفات عادية في `dist/` |
|
|
163
|
+
| `kreft serve dist` | يشغّل التطبيق المبني للمستخدمين الحقيقيين |
|
|
164
|
+
| `kreft list` | يعرض كل أوامر نسختك |
|
|
165
|
+
| `kreft help` | خيارات الأوامر الرئيسية |
|
|
166
|
+
| `kreft tinker` | طرفية تفاعلية مع نماذجك (`await User.count()`) |
|
|
167
|
+
| `kreft update` | يحدّث الحزم وملف `kreftjs.js` |
|
|
168
|
+
|
|
169
|
+
تدريب سريع: أنشئ مشروعًا، وغيّر كلمة في `frontend/pages/` وراقب الصفحة تتبدل في المتصفح دون إعادة تحميل وبدون ضياع الحالة. هذه هي إعادة التحميل الفورية (hot reload).
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# الفصل الثاني: لغة Kreft
|
|
2
|
+
|
|
3
|
+
ملف `.kreft` له ثلاثة أجزاء:
|
|
4
|
+
|
|
5
|
+
1. **السكربت:** كل ما هو خارج `<view>` و`<style>`. كل سطر يبدأ بكلمة مفتاحية.
|
|
6
|
+
2. **`<view>`:** HTML المكوّن.
|
|
7
|
+
3. **`<style>`** (اختياري): CSS. أضف `scoped` ليسري على هذا المكوّن فقط.
|
|
8
|
+
|
|
9
|
+
## الكلمات المفتاحية
|
|
10
|
+
|
|
11
|
+
| الكلمة | مثال | المعنى |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `state` | `state count = 0` | قيمة قابلة للتغيّر. عند تغيّرها تتحدث الصفحة. |
|
|
14
|
+
| `derive` | `derive double = count * 2` | قيمة تُحسب من قيم أخرى وتتحدث وحدها. |
|
|
15
|
+
| `action` | `action add(n) { count += n }` | دالة تستدعيها من الصفحة أو من أفعال أخرى. |
|
|
16
|
+
| `when` | `when count > 10 { … }` | ينفّذ الكتلة كلما صار الشرط صحيحًا. |
|
|
17
|
+
| `watch` | `watch count { … }` | ينفّذ الكتلة كلما تغيّرت القيمة. داخلها `value` الجديدة و`old` السابقة. |
|
|
18
|
+
| `on` | `on ready { … }` | دورة الحياة: `start` و`ready` و`update` و`show` و`hide` و`destroy`. |
|
|
19
|
+
| `prop` | `prop start = 0` | قيمة يمررها الأب. ما بعد `=` هو الافتراضي. |
|
|
20
|
+
| `event` | `event save, remove` | أحداث يرسلها المكوّن لأبيه بـ `send('save', data)`. |
|
|
21
|
+
| `use` | `use cart` | يستعمل مخزنًا مشتركًا (store) أو منطقًا مشتركًا من ملف `.js`. |
|
|
22
|
+
| `share` / `receive` | `share theme = 'dark'` | يعطي قيمة لكل ما بداخل المكوّن / يأخذ قيمة شاركها أب. |
|
|
23
|
+
| `import` | `import Card from './Card.kreft'` | يستعمل مكوّنًا آخر أو وحدة JavaScript. |
|
|
24
|
+
| `saved` | `saved todos = []` | كـ `state` لكن محفوظة في المتصفح (localStorage). |
|
|
25
|
+
| `load` | `load users = api.get('/api/users')` | يجلب بيانات ويعطيك `users.data` و`users.loading` و`users.error` و`users.reload()`. |
|
|
26
|
+
|
|
27
|
+
داخل السكربت تستعمل الأسماء **مباشرة**: اكتب `count++` وليس `this.count++`.
|
|
28
|
+
|
|
29
|
+
## state: الحالة
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
state count = 0
|
|
33
|
+
|
|
34
|
+
<view>
|
|
35
|
+
<button @click="count++">النقر: {count}</button>
|
|
36
|
+
</view>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
كل تغيير على `state` يحدّث الجزء المتأثر فقط من الصفحة، لا الصفحة كلها. التغييرات العميقة تُرصد أيضًا: `todos.push(...)` و`todo.done = true`.
|
|
40
|
+
|
|
41
|
+
## derive: القيم المشتقة
|
|
42
|
+
|
|
43
|
+
`derive` قيمة تُحسب من قيم أخرى. هي ليست دالة تستدعيها: تُعيد حساب نفسها تلقائيًا كلما تغيّرت القيم التي تقرؤها.
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
state price = 10
|
|
47
|
+
state qty = 2
|
|
48
|
+
|
|
49
|
+
derive total = price * qty // 20
|
|
50
|
+
derive label = `الإجمالي: ${total} $` // يستعمل derive آخر
|
|
51
|
+
|
|
52
|
+
action more() { qty++ } // يتحدّث total وlabel وحدهما
|
|
53
|
+
|
|
54
|
+
<view>
|
|
55
|
+
<p>{label}</p>
|
|
56
|
+
<button @click={more}>+1</button>
|
|
57
|
+
</view>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- اكتب `{total}` وليس `{total()}`.
|
|
61
|
+
- لا تعيّن قيمة لـ `derive`. غيّر الـ `state` الذي يقرؤه.
|
|
62
|
+
- يمكنه استعمال مشتقات أخرى.
|
|
63
|
+
|
|
64
|
+
## action: الأفعال
|
|
65
|
+
|
|
66
|
+
الفعل الذي يستخدم `await` يصبح غير متزامن (async) تلقائيًا:
|
|
67
|
+
|
|
68
|
+
```html
|
|
69
|
+
state users = []
|
|
70
|
+
state busy = false
|
|
71
|
+
|
|
72
|
+
action async_example() {
|
|
73
|
+
busy = true
|
|
74
|
+
users = await api.get('/api/users')
|
|
75
|
+
busy = false
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## when و watch و on
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
state todos = []
|
|
83
|
+
derive remaining = todos.filter((t) => !t.done).length
|
|
84
|
+
|
|
85
|
+
// ينفّذ عندما يصير الشرط صحيحًا
|
|
86
|
+
when todos.length && remaining === 0 {
|
|
87
|
+
bus.send('toast', 'أنجزت كل المهام!')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ينفّذ عند كل تغيّر
|
|
91
|
+
watch remaining {
|
|
92
|
+
document.title = `(${value}) مهام`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
on ready { console.log('المكوّن على الشاشة') }
|
|
96
|
+
on destroy { console.log('سيُزال المكوّن: نظّف هنا') }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
متى أستعمل أيًّا منها؟
|
|
100
|
+
|
|
101
|
+
- **`derive`** لحساب قيمة ستعرضها.
|
|
102
|
+
- **`watch`** لتنفيذ عمل جانبي عند التغيّر: حفظ، أو استدعاء API، أو كتابة في `document.title`.
|
|
103
|
+
- **`when`** عندما يهمك لحظة تحوّل الشرط إلى صحيح.
|
|
104
|
+
- **`action`** دالة تستدعيها أنت بنفسك.
|
|
105
|
+
|
|
106
|
+
## مثال كامل
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<!-- TodoPage.kreft -->
|
|
110
|
+
state todos = []
|
|
111
|
+
state text = ''
|
|
112
|
+
|
|
113
|
+
derive remaining = todos.filter((t) => !t.done).length
|
|
114
|
+
|
|
115
|
+
action add() {
|
|
116
|
+
if (!text.trim()) return
|
|
117
|
+
todos.push({ id: Date.now(), text, done: false })
|
|
118
|
+
text = ''
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
action clearDone() {
|
|
122
|
+
todos = todos.filter((t) => !t.done)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
when todos.length && remaining === 0 {
|
|
126
|
+
bus.send('toast', 'All done!')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
<view>
|
|
130
|
+
<form @submit.prevent={add}>
|
|
131
|
+
<input k-model="text" placeholder="مهمة جديدة">
|
|
132
|
+
</form>
|
|
133
|
+
<label k-each="todo in todos" k-key="todo.id">
|
|
134
|
+
<input type="checkbox" k-model="todo.done"> {todo.text}
|
|
135
|
+
</label>
|
|
136
|
+
<p>{remaining} متبقية · <button @click={clearDone}>احذف المنجز</button></p>
|
|
137
|
+
</view>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## ما يجب أن تعرفه
|
|
141
|
+
|
|
142
|
+
- سطر لا يبدأ بكلمة مفتاحية هو JavaScript عادي (`let timer`, `const max = 10`). تستعمله أفعالك لكن الصفحة (`view`) لا تراه. استعمل `state` أو `derive` أو `action` لكل ما تحتاجه الصفحة.
|
|
143
|
+
- كل دوال Kreft متاحة في السكربت: `bus` و`store` و`signal` و`watch` و`tick` و`send` و`props` و`$route` و`$router`.
|
|
144
|
+
- رسائل الخطأ تسمي الملف والسطر، مثل: `"state" needs a name, like: state count = 0 (Bad.kreft, line 3)`.
|
|
145
|
+
|
|
146
|
+
## دورة الحياة
|
|
147
|
+
|
|
148
|
+
| الخطاف | يعمل |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `on start` | قبل أن يظهر المكوّن. مناسب للبيانات الأولى. |
|
|
151
|
+
| `on ready` | المكوّن على الشاشة. |
|
|
152
|
+
| `on update` | بعد تغيّر الصفحة. |
|
|
153
|
+
| `on show` / `on hide` | مكوّن [محتفَظ به](03-components-and-state.md) يعود / يُوضع جانبًا. |
|
|
154
|
+
| `on destroy` | يُزال المكوّن. نظّف هنا. |
|
|
155
|
+
|
|
156
|
+
## القوالب (Templates)
|
|
157
|
+
|
|
158
|
+
### عرض القيم
|
|
159
|
+
|
|
160
|
+
```html
|
|
161
|
+
<h1>أهلًا {name}</h1>
|
|
162
|
+
<p>{count} عنصر · المجموع {total.toFixed(2)}</p>
|
|
163
|
+
<p>{isAdmin ? 'مدير' : 'مستخدم'}</p>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
القيم تُهرَّب (escaped) دائمًا فهي آمنة للعرض. لعرض `{` حرفيًا اكتب `\{`. وفي القوالب اكتب `{a < b}` مع مسافة، لأن `<` متبوعة بحرف مباشرة تبدأ وسمًا.
|
|
167
|
+
|
|
168
|
+
### الخصائص (Attributes)
|
|
169
|
+
|
|
170
|
+
```html
|
|
171
|
+
<img src={user.photo} alt={user.name}> <!-- القيمة كما هي -->
|
|
172
|
+
<button disabled={count === 0}>تصفير</button> <!-- false تزيل الخاصية -->
|
|
173
|
+
<div class="card {active ? 'on' : ''}"> <!-- بناء نص -->
|
|
174
|
+
<a href="/users/{user.id}">الملف الشخصي</a>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`:class` و`:style` يقبلان كائنات أيضًا: `:class="{ done: todo.done }"`.
|
|
178
|
+
|
|
179
|
+
### الأحداث
|
|
180
|
+
|
|
181
|
+
```html
|
|
182
|
+
<button @click={add}>إضافة</button>
|
|
183
|
+
<button @click="add(5)">أضف 5</button>
|
|
184
|
+
<input @input="search($event.target.value)">
|
|
185
|
+
<form @submit.prevent={save}>
|
|
186
|
+
<input @keyup.enter={send}>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
المعدِّلات: `.prevent` و`.stop` و`.self` و`.once` و`.capture` و`.passive`. المفاتيح: `.enter` و`.esc` و`.space` و`.tab` و`.up` و`.down` و`.ctrl` و`.shift` و`.alt` و`.meta` و`.exact`.
|
|
190
|
+
|
|
191
|
+
### التوجيهات (Directives)
|
|
192
|
+
|
|
193
|
+
| التوجيه | ماذا يفعل |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `k-if` / `k-elif` / `k-else` | يضيف العنصر أو يزيله. |
|
|
196
|
+
| `k-each="item in list"` + `k-key="item.id"` | يكرر العنصر. أيضًا `(item, i) in list` و`(value, key) in object` و`n in 10`. |
|
|
197
|
+
| `k-model="name"` | ربط ثنائي الاتجاه لـ input وtextarea وselect وcheckbox وradio. المعدِّلات: `.trim` `.number` `.lazy`. |
|
|
198
|
+
| `k-show` | يخفي بـ CSS بدل أن يزيل. |
|
|
199
|
+
| `k-text` / `k-html` | يضبط نصًا أو HTML. `k-html` يُنظَّف من السكربتات لكن اربط HTML تثق به فقط. |
|
|
200
|
+
| `k-ref="box"` | يعطيك العنصر: `state box = null` يستقبله. |
|
|
201
|
+
| `k-skip` | يترك العنصر كما هو (لعينات الشيفرة التي فيها `{ }`). |
|
|
202
|
+
| `k-once` | يرسم العنصر مرة واحدة ولا يحدّثه. |
|
|
203
|
+
| `k-memo="[a, b]"` | يحدّث العنصر فقط إذا تغيّرت `a` أو `b`. |
|
|
204
|
+
| `k-animate="fade"` | يحرّك العنصر عند مجيئه وذهابه. |
|
|
205
|
+
| `<template k-if>` / `<template k-each>` | يجمّع عناصر دون غلاف. |
|
|
206
|
+
| `<kreft-component :is="name">` | يعرض مكوّنًا يُختار أثناء التشغيل. |
|
|
207
|
+
| `<kreft-portal to="body">` | يرسم المحتوى في مكان آخر (نوافذ منبثقة، تنبيهات). |
|
|
208
|
+
|
|
209
|
+
### الحلقات
|
|
210
|
+
|
|
211
|
+
```html
|
|
212
|
+
<ul>
|
|
213
|
+
{for user in users key user.id}
|
|
214
|
+
<li>{user.name}</li>
|
|
215
|
+
{empty}
|
|
216
|
+
<li>لا يوجد مستخدمون بعد</li>
|
|
217
|
+
{/for}
|
|
218
|
+
</ul>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- `key user.id` اختياري؛ يمكّن Kreft من الاحتفاظ بالصفوف الصحيحة ونقلها.
|
|
222
|
+
- `{empty}` اختياري ويظهر عند فراغ القائمة.
|
|
223
|
+
- `{for (user, i) in users}` يعطي الفهرس، و`{for (value, key) in object}` يمر على كائن، و`{for n in 5}` يعدّ من 1 إلى 5.
|
|
224
|
+
- `k-each` على عنصر يفعل الشيء نفسه: `<li k-each="user in users" k-key="user.id">`.
|
|
225
|
+
|
|
226
|
+
### الحركات (Animations)
|
|
227
|
+
|
|
228
|
+
```html
|
|
229
|
+
<p k-if="open" k-animate="fade">مرحبًا</p>
|
|
230
|
+
<div k-show="menu" k-animate="slide">…</div>
|
|
231
|
+
|
|
232
|
+
<ul>
|
|
233
|
+
{for item in items key item.id animate slide}
|
|
234
|
+
<li>{item.name}</li>
|
|
235
|
+
{/for}
|
|
236
|
+
</ul>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
الجاهز: `fade` و`slide` و`scale` و`pop`. وحركتك الخاصة هي CSS بهذه الفئات (استبدل `name`):
|
|
240
|
+
|
|
241
|
+
| الفئة | متى |
|
|
242
|
+
|---|---|
|
|
243
|
+
| `name-in` | طوال وقت الدخول (ضع `transition` هنا). |
|
|
244
|
+
| `name-in-from` | اللحظة الأولى: من أين يأتي. |
|
|
245
|
+
| `name-out` | طوال وقت الخروج. |
|
|
246
|
+
| `name-out-to` | إلى أين يذهب. |
|
|
247
|
+
| `name-move` | عنصر قائمة ينتقل إلى مكانه الجديد. |
|
|
248
|
+
|
|
249
|
+
```css
|
|
250
|
+
.drop-in, .drop-out { transition: all .3s ease }
|
|
251
|
+
.drop-in-from { opacity: 0; transform: translateY(-30px) }
|
|
252
|
+
.drop-out-to { opacity: 0; transform: translateX(60px) }
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
- ينتظر Kreft أطول `transition` أو `animation` قبل أن يزيل العنصر.
|
|
256
|
+
- `k-animate.after` يدع العنصر القديم يخرج قبل أن يدخل الجديد.
|
|
257
|
+
- لا شيء يتحرك عند فتح الصفحة أول مرة؛ أضف `.first` ليتحرك حينها أيضًا.
|
|
258
|
+
|
|
259
|
+
### صفحات أسرع
|
|
260
|
+
|
|
261
|
+
Kreft يغيّر أصلًا النص أو الخاصية التي تغيّرت فقط. للصفحات الكبيرة جدًا:
|
|
262
|
+
|
|
263
|
+
```html
|
|
264
|
+
<footer k-once>فُتحت في {openedAt}</footer>
|
|
265
|
+
|
|
266
|
+
{for row in rows key row.id}
|
|
267
|
+
<tr k-memo="[row.id === selected]" class={row.id === selected ? 'on' : ''}>…</tr>
|
|
268
|
+
{/for}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
مع `k-memo`، اختيار صف آخر يعيد رسم صفين فقط لا كل الصفوف.
|
|
272
|
+
|
|
273
|
+
### توجيهاتك الخاصة
|
|
274
|
+
|
|
275
|
+
```js
|
|
276
|
+
const app = Kreftjs.app('App.kreft')
|
|
277
|
+
|
|
278
|
+
app.directive('focus', {
|
|
279
|
+
ready(el, binding) { el.focus() },
|
|
280
|
+
update(el, binding) {},
|
|
281
|
+
destroy(el, binding) {},
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
app.mount('#app')
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
```html
|
|
288
|
+
<input k-focus>
|
|
289
|
+
<div k-tooltip:top={message}></div> <!-- binding.arg = 'top', binding.value = message -->
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## استخدام Kreft من JavaScript
|
|
293
|
+
|
|
294
|
+
يمكنك كتابة المكوّنات ككائنات عادية. القواعد نفسها: القيم حالة، والـ getters قيم مشتقة، والدوال أفعال.
|
|
295
|
+
|
|
296
|
+
```js
|
|
297
|
+
Kreftjs.app({
|
|
298
|
+
count: 0,
|
|
299
|
+
get double() { return this.count * 2 },
|
|
300
|
+
add() { this.count++ },
|
|
301
|
+
template: `<button @click={add}>{count} × 2 = {double}</button>`,
|
|
302
|
+
}).mount('#app')
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
| JavaScript | يقابل في `.kreft` |
|
|
306
|
+
|---|---|
|
|
307
|
+
| `Kreftjs.state({ … })` | `state` + `derive` + `action` معًا، للشيفرة خارج المكوّنات. |
|
|
308
|
+
| `Kreftjs.signal(0)` | قيمة واحدة تُقرأ وتُكتب بـ `.value`. |
|
|
309
|
+
| `Kreftjs.derived(() => …)` | `derive` |
|
|
310
|
+
| `Kreftjs.watch(source, (value, old) => …)` | `watch` |
|
|
311
|
+
| `Kreftjs.effect(() => …)` | يعمل مجددًا كلما تغيّر ما يقرؤه. |
|
|
312
|
+
| `Kreftjs.tick()` | ينتظر حتى تتحدث الصفحة. |
|
|
313
|
+
|
|
314
|
+
```js
|
|
315
|
+
const price = Kreftjs.signal(10)
|
|
316
|
+
const qty = Kreftjs.signal(2)
|
|
317
|
+
const total = Kreftjs.derived(() => price.value * qty.value)
|
|
318
|
+
|
|
319
|
+
console.log(total.value) // 20
|
|
320
|
+
qty.value = 3
|
|
321
|
+
console.log(total.value) // 30
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
وهناك `Kreftjs.render(view, data)` يحوّل قالبًا وبيانات إلى نص HTML، مع عمل كل التوجيهات وتهريب كل القيمة. استعمله لرسائل البريد ونسخ الطباعة.
|
|
325
|
+
|
|
326
|
+
## تمارين
|
|
327
|
+
|
|
328
|
+
1. اكتب عدّادًا مع زري زيادة ونقصان، وقيمة مشتقة `double`، و`watch` يطبع القيمة عند تغيّرها.
|
|
329
|
+
2. اكتب قائمة مهام تحفظ نفسها عند إعادة التحميل (تلميح: `saved` بدل `state`).
|
|
330
|
+
3. اكتب مكوّن بحث يرشّح قائمة أسماء وهو تكتب، باستعمال `derive` و`k-model`.
|