قالب البرنامج المنشور
نسخة مجمّدة من برنامج كامل تنشرها المنصة أو مسجد آخر: قوالبه ومناهجه وسلوكياته معاً.
أسرع طريق إلى إعدادٍ صحيح: تثبّت القالب فيصلك السكربت مربوطاً مضبوطاً بلا أن تكتب سطراً.
سلوكيات الربط المكتوبة بسكربت، من مادة منشورة تعمل في مساجد حقيقية: «المسار التقليدي» و«موازنة تلقائية» بقوالبهما وروابطهما وسكربتاتهما كاملةً، مع حالات اختبار جاهزة.
دوال النظام الخمس تنقل موضعاً أو تضبط حدّاً، ولا تعرف شرطاً ولا تتكيّف مع طالب. فإذا كانت قاعدة المسجد التثبيت أربعة أضعاف مقدار الحفظ
أو حدّد مستوى الطالب من أول تسميعاته
، فالجواب سكربت.
هذه الصفحة أمثلة لا مرجع لغة. اقرأ سلوكيات الربط أولاً لتفهم المصدر والهدف والحقول المراقبة والمستهدفة؛ ثم عُد إلى هنا لترى القواعد مكتوبةً بالفعل.
نسخة مجمّدة من برنامج كامل تنشرها المنصة أو مسجد آخر: قوالبه ومناهجه وسلوكياته معاً.
أسرع طريق إلى إعدادٍ صحيح: تثبّت القالب فيصلك السكربت مربوطاً مضبوطاً بلا أن تكتب سطراً.
دالة واحدة اسمها onPlanChanged تستقبل ما تغيّر وتعيد { updates }.
كل السكربتات التالية على هذا الشكل، فمتى قرأت واحداً قرأتها كلها.
مصفوفة configFields يعلنها السكربت، فتظهر للمشرف حقولاً عربية في صف الربط.
بها يعدّل المشرف قاعدةً بلا كود: يكتب ثلاثة بدل أربعة في عدد الأيام فيتغيّر سلوك عشرات الخطط.
مصفوفة testCases تصف خططاً وهمية وما يُتوقَّع أن يكتبه السكربت عليها.
هي الفرق بين تجربةٍ على الورق وتجربةٍ على أربعين طالباً.
الأمثلة التالية ليست تمارين: اثنان منها قالبان منشوران مثبَّتان في مساجد، والثالث أدوات صغيرة تُركَّب على ما عندك.
اختره حينتريد البنية المعتادة: حفظ وتثبيت ومراجعة، بمقدارٍ يحدّده المشرف لكل مستوى.
قالب منشور مثبَّت أكثر من تسعين مرة، بأربعة مناهج جاهزة للسرعات: وجه، نصف وجه، وجه ونصف، وجهان.
يفترض أنّك تعرف مستوى الطالب مسبقاً وتضعه في المنهج المناسب.
اختره حينلا تعرف مستوى الطلاب، وتريد أن يستنتجه النظام من أول تسميعاتهم ثم يوازن المسارات الثلاثة بلا تدخّل.
إنشاء برنامجك في دقيقتين
: تبدأ الخطط الثلاث حرّة، ثم يحوّلها السكربت إلى ثابتة بمقدارٍ مقيس من أداء الطالب نفسه.
يحتاج تسميعات أولى نظيفة؛ وقراءات متذبذبة تؤخّر التسكين. ولا يظهر للمشرف قرارٌ يراجعه قبل وقوعه.
اختره حينبنيتك قائمة وتريد إضافة قاعدة واحدة: تحديد مقدار من متوسط التسميعات، أو قلب الاتجاه بالتقييم.
قصيرة يسهل فهمها وتعديلها، وتُركَّب على قالبٍ موجود دون إعادة بناء البرنامج.
تفعل شيئاً واحداً؛ ودمج عدّة قواعد يحتاج انتباهاً لترتيب الروابط.
قالب برنامج موثّق مثبَّت ثلاثاً وتسعين مرة، وصفه: نظام متكامل يدمج بين الحفظ، التثبيت، والمراجعة مع ميزة الربط التلقائي
. وقاعدته الأساسية معلنة في وصفه: التثبيت يتبع مسار الحفظ ولا يتجاوزه أبداً
.
في الحلقة التقليدية يسمّع الطالب ثلاثة أشياء كل يوم: جديدَه، وما قرب منه، وما بعد. وموضع التثبيت ليس اختياراً مستقلاً، بل مسافةٌ خلف الحفظ تتحرّك معه. فإن ضبطه المشرف يدوياً، لزمه أن يعيد ضبطه لكل طالب بعد كل جلسة — وهذا ما يجعل الحلقة تتوقّف عن استعمال النظام بعد أسبوعين.
| الحالة | مقدار الحفظ اليومي | موضع الحفظ | التأخير | موضع بدء التثبيت |
|---|---|---|---|---|
| المثال الأول | وجه واحد | الوجه ١٠ | ٤ أوجه | الوجه ٦ |
| المثال الثاني | وجهان | الوجه ٢٠ | ٨ أوجه | الوجه ١٢ |
| بعد التسميع | وجهان | الوجه ٢٢ | ٨ أوجه | الوجه ١٤ |
| القالب | النمط | المقدار | إعدادات القفز | وحدة النقاط |
|---|---|---|---|---|
| حفظ | حصة ثابتة صعوداً من الناس | سطر واحد — قيمةٌ يُفترض أن يتجاوزها المنهج | بناءً على آخر تقدم | ذهب |
| تثبيت | حصة ثابتة صعوداً | ٦٠ سطراً — يعيد السكربت حسابها | قفز مقدار محدد بخمسة عشر سطراً | فضة |
| مراجعة | حصة ثابتة صعوداً | ٣٠ سطراً | بناءً على آخر تقدم | بلا وحدة |
// قالب «تثبيت» — يراقب قالب «حفظ» (الفهرس 0)
{
"watchedTemplateIndex": 0,
"behaviorIndex": 0,
"enabled": true
}
// قالب «مراجعة» — يراقب قالب «حفظ» بدالة نظام جاهزة
{
"watchedTemplateIndex": 0,
"platformBehaviorId": "ربط الحد الأعلى",
"enabled": true
}قالب حفظ بلا روابط: هو المصدر لا الهدف. وقالب تثبيت يربط سكربت تثبيت بالحفظ. وقالب مراجعة يستعمل دالة نظام جاهزة اسمها المنشور ربط الحد الأعلى، ووصفها: يقوم هذا السلوك بمراقبة تقدم أو الموضع الحالي للخطة المراقبة ويقوم بتحديث الحد الأعلى أو الحد الأدنى للخطة المستهدفة
— أي أنّ المراجعة لا تحتاج سكربتاً أصلاً.
progress وamount وdirection — أي موضع الحفظ ومقداره واتجاهه.progress وamount وdirection وstartSurah وstartAya وحدّا التنقل navigationSettings.upperBound وnavigationSettings.lowerBound وmode.MULTIPLIER)، رقم، افتراضه أربعة وأدناه واحد. وهو ما يضبطه المشرف في صف الربط أو في إعدادات الربط داخل المنهج./**
* link-behavior-config
* watched: progress, amount, direction
* target: progress, amount, direction, startSurah, startAya, navigationSettings.upperBound, navigationSettings.lowerBound, mode
*/
function onPlanChanged(input) {
if (input.updatedPlan.type !== "quran" || input.targetPlan.type !== "quran") {
return { updates: {} };
}
var updates = {};
var dir = Number(input.updatedPlan.direction) || 1;
// 1. مزامنة الاتجاه: التأكد من مطابقة اتجاه الهدف لمصدر التغيير
if (input.targetPlan.direction !== dir) {
updates.direction = dir;
}
var fullMushafNav = buildNavSettings(null);
// 2. جلب قيمة التكوين الخاصة بـ "عدد الأيام" من الإعدادات
var MULTIPLIER = (typeof config !== "undefined" && config && config.MULTIPLIER !== undefined)
? Number(config.MULTIPLIER)
: 4;
var hifzAmount = Number(input.updatedPlan.amount) || 15;
// 3. مزامنة المقدار: مقدار الهدف = عدد الأيام × مقدار المصدر
var targetAmount = MULTIPLIER * hifzAmount;
if (!Number.isFinite(targetAmount) || targetAmount < 1) {
return { updates: {}, errors: ["مقدار التثبيت غير صالح"] };
}
if (Number(input.targetPlan.amount) !== targetAmount) {
updates.amount = targetAmount;
}
var newHifzPos = {
sura: input.updatedPlan.progress.currentSurah,
aya: input.updatedPlan.progress.currentAya
};
var currentTathbeetPos = {
sura: input.targetPlan.progress.currentSurah,
aya: input.targetPlan.progress.currentAya
};
var finalTathbeetPos = currentTathbeetPos;
var forceReposition = false;
var prevVerseObj = mushaf.previousVerse(newHifzPos, dir, fullMushafNav);
var endBoundPos = prevVerseObj ? { sura: prevVerseObj.sura, aya: prevVerseObj.number } : newHifzPos;
var minAllowedDist = MULTIPLIER * hifzAmount;
var maxAllowedDist = (MULTIPLIER + 1) * hifzAmount;
var isOut = mushaf.isOutOfBounds(currentTathbeetPos, dir, {
upperBound: dir === 1 ? fullMushafNav.upperBound : endBoundPos,
lowerBound: dir === 1 ? endBoundPos : fullMushafNav.lowerBound
});
if (isOut) {
forceReposition = true;
} else {
var currentDist = mushaf.calculateLines(currentTathbeetPos, newHifzPos, dir, fullMushafNav);
if (currentDist < minAllowedDist || currentDist > maxAllowedDist) {
forceReposition = true;
}
}
if (forceReposition) {
var targetRes = mushaf.reverseNavigate({
from: newHifzPos,
lines: minAllowedDist,
direction: dir,
settings: fullMushafNav
});
if (!targetRes || !targetRes.verse) {
return { updates: {}, errors: ["تعذر حساب موضع التثبيت"] };
}
finalTathbeetPos = {
sura: targetRes.verse.sura,
aya: targetRes.verse.number
};
updates.progress = {
currentSurah: finalTathbeetPos.sura,
currentAya: finalTathbeetPos.aya
};
}
var isTargetFirst = finalTathbeetPos.sura < endBoundPos.sura ||
(finalTathbeetPos.sura === endBoundPos.sura && finalTathbeetPos.aya <= endBoundPos.aya);
var newUpper = isTargetFirst ? { sura: finalTathbeetPos.sura, aya: finalTathbeetPos.aya } : { sura: endBoundPos.sura, aya: endBoundPos.aya };
var newLower = isTargetFirst ? { sura: endBoundPos.sura, aya: endBoundPos.aya } : { sura: finalTathbeetPos.sura, aya: finalTathbeetPos.aya };
var oldUpper = input.targetPlan.navigationSettings && input.targetPlan.navigationSettings.upperBound;
var oldLower = input.targetPlan.navigationSettings && input.targetPlan.navigationSettings.lowerBound;
if (!oldUpper || oldUpper.sura !== newUpper.sura || oldUpper.aya !== newUpper.aya) {
updates["navigationSettings.upperBound"] = newUpper;
}
if (!oldLower || oldLower.sura !== newLower.sura || oldLower.aya !== newLower.aya) {
updates["navigationSettings.lowerBound"] = newLower;
}
var keys = getDirectionalBoundKeys(dir);
var startBound = keys.startBound === "upperBound" ? newUpper : newLower;
if (Number(input.targetPlan.startSurah) !== startBound.sura) {
updates.startSurah = startBound.sura;
}
if (Number(input.targetPlan.startAya) !== startBound.aya) {
updates.startAya = startBound.aya;
}
// تحويل حرّة/ديناميكية → ثابتة؛ لا يُستدعى إن كانت ثابتة أصلاً
var modeTrans = undefined;
if (input.targetPlan.mode !== "static") {
var r = PlanConverter.toStatic(input.targetPlan, {
amount: targetAmount,
reason: "sync:tathbeet-made-static",
progress: {
currentSurah: finalTathbeetPos.sura,
currentAya: finalTathbeetPos.aya
},
startSurah: startBound.sura,
startAya: startBound.aya,
direction: dir,
navigationSettings: { upperBound: newUpper, lowerBound: newLower }
});
if (!r.ok) {
return { updates: {}, errors: r.errors };
}
modeTrans = r.modeTransition;
}
if (Object.keys(updates).length === 0 && !modeTrans) {
return { updates: {} };
}
var output = { updates: updates };
if (modeTrans) {
output.modeTransition = modeTrans;
}
return output;
}اقرأه في خمس خطوات: يزامن الاتجاه، ثم يحسب مقدار التثبيت بضرب عدد الأيام في مقدار الحفظ، ثم يقيس المسافة الحالية بين التثبيت والحفظ فإن خرجت عن المدى المسموح أعاد وضعه بالرجوع من موضع الحفظ، ثم يضبط حدّي التنقل وموضع البداية، ثم يحوّل الخطة إلى ثابتة إن لم تكن كذلك.
مخرجات هذا السكربت تعتمد على حساب المصحف، فابدأ بالحالة التي لا تحتاج حساباً — خطة ليست قرآنية يجب ألا يكتب فيها شيئاً — ثم أضف حالةً حقيقية بعد أن تقرأ ما كتبه فعلاً في نافذة الاختبار.
التثبيت خلف الحفظ بأربعة أيام.
قالب موثّق مثبَّت إحدى وعشرين مرة، وصفه: برنامج يتيح لك إنشاء برنامجك في دقيقتين فقط؛ ليتولى تلقائياً معادلة مسارات الحفظ والتثبيت والمراجعة دون أي تدخل إداري، مع تحديد مستوى الطالب بدقة عالية
.
المسار التقليدي يفترض أنّك تعرف مقدار الطالب اليومي قبل أن يسمّع. وفي الواقع لا يُعرف مستوى الطالب الجديد إلا بعد جلسات، فيضع المشرف تقديراً ثم يعيد ضبطه لكل طالب على حدة. هذا القالب يقلب الترتيب: تبدأ الخطط الثلاث حرّة بلا مقدار، ويقيس السكربت أوّل تسميعات الطالب، فإذا استقرّت حوّل خطته إلى ثابتة بمقدارٍ مأخوذ من أدائه هو.
| القالب | النمط عند الإنشاء | من يراقب ماذا | وحدة النقاط |
|---|---|---|---|
| حفظ | حصة حرة صعوداً من الناس | يراقب نفسه بسلوك تحديد المستوى التلقائي | ذهب |
| تثبيت | حصة حرة | يراقب حفظ بسلوك تثبيت وعدد الأيام ثلاثة | فضة |
| مراجعة | حصة حرة | رابطان: ربط الحد الأعلى على حفظ، وتحديد المستوى التلقائي على نفسها | فضة |
// قالب «حفظ» (الفهرس 0) — يراقب نفسه
{
"watchedTemplateIndex": 0,
"platformBehaviorId": "تحديد المستوى التلقائي",
"enabled": true,
"config": { "observationCount": 2, "tolerancePercent": 10 }
}
// قالب «تثبيت» (الفهرس 1) — يراقب «حفظ»
{
"watchedTemplateIndex": 0,
"platformBehaviorId": "تثبيت",
"enabled": true,
"config": { "MULTIPLIER": 3 }
}
// قالب «مراجعة» (الفهرس 2) — رابطان
{
"watchedTemplateIndex": 0,
"platformBehaviorId": "ربط الحد الأعلى",
"enabled": true,
"config": { "syncDirection": true }
}
{
"watchedTemplateIndex": 2,
"platformBehaviorId": "تحديد المستوى التلقائي",
"enabled": true,
"config": { "observationCount": 2, "tolerancePercent": 10 }
}progress وnumberOfActivities — يكفي أن يسجَّل نشاط جديد.amount وmode، إضافةً إلى مفاتيح metadata التي يخزّن فيها القراءات المتراكمة.observationCount — افتراضه خمسة، وقالب موازنة تلقائية يخفضه إلى اثنين ليسكّن الطالب بسرعة.نسبة التفاوت المقبولة (±%)
tolerancePercent — افتراضه عشرة. كلّما رفعته قبِلت قراءاتٍ أبعد عن الوسيط، فاستقرّ المقدار أسرع وأقلّ دقة./**
* link-behavior-config
* watched: progress, numberOfActivities
* target: amount, metadata, mode
*/function onPlanChanged(input) {
if (input.changeKind !== "activity") return { updates: {} };
if (input.trigger.action !== "created") return { updates: {} };
var meta = input.targetPlan.metadata || {};
if (meta.evaluationDone) return { updates: {} };
var updates = {};
var modeTrans = undefined;
// التأكد من أن الخطة حرة أثناء التقييم
if (input.targetPlan.mode !== "free") {
var freeResult = PlanConverter.toFree(input.targetPlan, { reason: "adaptive:evaluation-phase" });
if (freeResult.ok) modeTrans = freeResult.modeTransition;
}
var amounts = meta.recitationAmounts || [];
var currentAmount = input.trigger.activity.amount;
if (currentAmount > 0) amounts.push(currentAmount);
if (amounts.length < config.observationCount) {
updates["metadata.recitationAmounts"] = amounts;
var out = { updates: updates };
if (modeTrans) out.modeTransition = modeTrans;
return out;
}
// حساب الوسيط
var sorted = amounts.slice().sort(function(a, b) { return a - b; });
var mid = Math.floor(sorted.length / 2);
var median = sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2.0;
var tolerance = config.tolerancePercent / 100.0;
var minAccepted = median * (1 - tolerance);
var maxAccepted = median * (1 + tolerance);
var validAmounts = [];
var sum = 0;
for (var i = 0; i < amounts.length; i++) {
if (amounts[i] >= minAccepted && amounts[i] <= maxAccepted) {
validAmounts.push(amounts[i]);
sum += amounts[i];
}
}
// --- شرط الاستقرار الجديد ---
// يجب أن يكون 60% على الأقل من القراءات (3 من 5) ضمن النطاق المقبول
var requiredStableCount = Math.ceil(config.observationCount * 0.6);
if (validAmounts.length < requiredStableCount) {
// القراءات متذبذبة! نحذف أقدم قراءة وننتظر تلاوة جديدة (Rolling Window)
amounts.shift();
updates["metadata.recitationAmounts"] = amounts;
var pendingOut = { updates: updates };
if (modeTrans) pendingOut.modeTransition = modeTrans;
return pendingOut;
}
// القراءات مستقرة! نحسب المتوسط ونحول الخطة
var average = sum / validAmounts.length;
var finalAmount = Math.round(average);
var staticResult = PlanConverter.toStatic(input.targetPlan, {
amount: finalAmount,
reason: "adaptive:evaluation-finished"
});
if (!staticResult.ok) return { updates: {}, errors: staticResult.errors };
updates["metadata.recitationAmounts"] = null;
updates["metadata.evaluationDone"] = true;
return {
updates: updates,
modeTransition: staticResult.modeTransition
};
}[
{
"name": "لا يعمل إلا مع نشاط جديد",
"input": {
"changeKind": "manual",
"updatedFields": ["progress"],
"oldPlan": { "id": "w", "type": "quran" },
"updatedPlan": { "id": "w", "type": "quran" },
"targetPlan": { "id": "t", "type": "quran", "mode": "free", "metadata": {} }
},
"expectedOutput": {}
},
{
"name": "يخزّن أول تلاوة ولا يحوّل الخطة بعد",
"input": {
"changeKind": "activity",
"trigger": { "action": "created", "activity": { "amount": 15 } },
"updatedFields": ["progress", "numberOfActivities"],
"oldPlan": { "id": "w", "type": "quran" },
"updatedPlan": { "id": "w", "type": "quran" },
"targetPlan": { "id": "t", "type": "quran", "mode": "free", "metadata": {} }
},
"expectedOutput": { "metadata.recitationAmounts": [15] }
}
]ليس كل سكربت خمسة آلاف حرف. هذان مثالان منشوران لا يتجاوز أحدهما صفحةً، ويصلحان قاعدةً تبني عليها سكربتك الأول.
وصفه المنشور: يقوم السلوك بتحليل أول 4 جلسات تسميع للطالب تلقائياً، مع استبعاد النتائج الشاذة، لتحديد متوسط أداء الطالب واعتماده كمقدار يومي مقترح للخطة
. يراقب totalGainedAmount وحده، ويكتب amount وحده، وحقل إعداده عدد التسميعات المطلوبة افتراضه أربعة.
/**
* link-behavior-config
* watched: totalGainedAmount
* target: amount, metadata.initialRecitations, metadata.hasDeterminedLevel, metadata.lastTotalGained
*/function onPlanChanged(input) {
if (input.updatedPlan.type !== "quran") return { updates: {} };
var meta = input.targetPlan.metadata || {};
if (meta.hasDeterminedLevel) {
return { updates: { "metadata.lastTotalGained": input.updatedPlan.totalGainedAmount } };
}
var gained = input.updatedPlan.totalGainedAmount - (meta.lastTotalGained || 0);
if (gained <= 0) return { updates: {} };
var recitations = meta.initialRecitations || [];
recitations.push(gained);
var updates = {
"metadata.lastTotalGained": input.updatedPlan.totalGainedAmount,
"metadata.initialRecitations": recitations
};
var targetCount = (typeof config !== 'undefined' && config.targetSessions !== undefined) ? config.targetSessions : 4;
// التحقق من التقارب فقط عند الوصول للعدد المطلوب أو أكثر
if (recitations.length >= targetCount) {
// نأخذ آخر 4 تسميعات للتحقق من تقاربها
var lastFour = recitations.slice(-targetCount);
var min = Math.min.apply(null, lastFour);
var max = Math.max.apply(null, lastFour);
var sum = 0;
for (var k = 0; k < lastFour.length; k++) sum += lastFour[k];
var avg = sum / lastFour.length;
// شرط التقارب: الفرق بين الأكبر والأصغر لا يتجاوز 40% من المتوسط (قابل للتعديل)
if ((max - min) <= (avg * 0.4)) {
updates.amount = Math.round(avg);
updates["metadata.hasDeterminedLevel"] = true;
}
}
return { updates: updates };
}الفرق بينه وبين تحديد المستوى التلقائي: هذا يقيس الفارق بين الأكبر والأصغر ولا يحوّل نمط الخطة، وذاك يستعمل الوسيط ونافذةً متدحرجة ويحوّل الخطة إلى ثابتة. فاختر الأول إن كانت خططك ثابتة أصلاً وتريد ضبط مقدارها فحسب.
أقصر مثال مفيد: يراقب الموضع، ويكتب direction وحده، وبلا حقول إعداد. يقرأ تقييم الجلسة، فإن كان ضعيفاً قلب المسار صعوداً نحو قصار السور، وإن كان ممتازاً قلبه نزولاً.
/**
* link-behavior-config
* watched: progress
* target: direction
*/
function onPlanChanged(input) {
// التأكد من أن التغيير ناتج عن نشاط مسجل
if (input.changeKind !== "activity" || input.trigger.action !== "created") {
return { updates: {} };
}
var act = input.trigger.activity;
// التأكد من وجود تقييم للنشاط
if (typeof act.rating !== "number") {
return { updates: {} };
}
var currentDirection = input.targetPlan.direction;
var newDirection = currentDirection;
// تقييم 90 أو أقل (خصم 10 فأكثر) -> من الناس للفاتحة
if (act.rating <= 90) {
newDirection = -1;
}
// تقييم 107 أو أعلى (زيادة 7 فأكثر) -> من الفاتحة للناس
else if (act.rating >= 107) {
newDirection = 1;
}
// تحديث الاتجاه فقط إذا كان هناك تغيير فعلي
if (newDirection !== currentDirection) {
return { updates: { direction: newDirection } };
}
return { updates: {} };
}[
{
"name": "تقييم ضعيف يقلب الاتجاه صعوداً",
"input": {
"changeKind": "activity",
"trigger": { "action": "created", "activity": { "rating": 85 } },
"updatedFields": ["progress"],
"oldPlan": { "id": "w", "type": "quran" },
"updatedPlan": { "id": "w", "type": "quran" },
"targetPlan": { "id": "t", "type": "quran", "direction": 1 }
},
"expectedOutput": { "direction": -1 }
},
{
"name": "تقييم متوسط لا يغيّر شيئاً",
"input": {
"changeKind": "activity",
"trigger": { "action": "created", "activity": { "rating": 100 } },
"updatedFields": ["progress"],
"oldPlan": { "id": "w", "type": "quran" },
"updatedPlan": { "id": "w", "type": "quran" },
"targetPlan": { "id": "t", "type": "quran", "direction": 1 }
},
"expectedOutput": {}
}
]كل سكربت في هذه الصفحة — ومنها الخمسة آلاف حرف — مبنيٌّ على الهيكل نفسه. متى عرفت أجزاءه الأربعة، صرت تقرأ أي سلوك وتحكم عليه.
/**
* link-behavior-config
* watched: progress
* target: progress, navigationSettings.lowerBound
*/
const configFields = [
{
"id": "sessionWindow",
"name": "عدد جلسات الحفظ الأخيرة",
"type": "number",
"required": false,
"default": 3,
"validation": { "min": 1 }
}
];
function onPlanChanged(input) {
if (input.updatedPlan.type !== "quran") return { updates: {} };
if (!input.updatedFields.includes("progress")) return { updates: {} };
// أسطر التأخير = عدد الجلسات × مقدار جلسة الحفظ
var offset = config.sessionWindow * (input.updatedPlan.amount || 15);
return COPY_PROGRESS({ positionOffset: offset, stepBackward: true });
}| الجزء | وظيفته | قاعدة لا تُخالَف |
|---|---|---|
تعليق link-behavior-config | يعلن الحقول المراقبة والمستهدفة، فتؤشّرها الواجهة تلقائياً. | يجب أن يذكر كل مفتاح يكتبه السكربت، وإلا أُهمل عند التشغيل. |
configFields | يعرّف حقول المشرف العربية وقيمها الافتراضية. | الاسم configFields حرفياً، وما بعد المساواة JSON صارم بلا تعليقات ولا فاصلة زائدة، ولكل حقل قيمة افتراضية. |
onPlanChanged(input) | الدالة التي تعمل عند كل تغيّر مراقَب. | تبدأ بحُرّاس: نوع الخطة، ونوع التغيير، وهل الحقل المعني ضمن ما تغيّر. |
testCases | حالات تُلصق في نافذة الاختبار، ولا تُحفظ مع السلوك. | حالةٌ ناجحة، وحالةٌ لا يعمل فيها السلوك، وحالةٌ حدّية — ثلاثٌ تكفي. |
input.changeKindactivity لنشاط، أو manual لتعديل يدوي، أو link_propagation لانتشار من سلوك آخر، أو land عند إنشاء الخطة أو إعادة تطبيق المنهج.input.updatedFieldsland — والسلوك يعمل رغم ذلك، فاحترس من حارسٍ يعتمد عليها وحدها.input.oldPlan وinput.updatedPlaninput.targetPlaninput.trigger.activityamount وrequiredAmount وrating، ومع التسميع مواضع البداية والنهاية.config{ updates: {} } تعني: لا شيء يتغيّر. وهي الجواب الصحيح في أكثر الاستدعاءات.updates تقبل المفاتيح المعلنة في target وحدها، ومنها مفاتيح metadata.* لحفظ الحالة.errors مصفوفة رسائل تُسجَّل حين يتعذّر الحساب.modeTransition هو الطريق الوحيد لتغيير نمط الخطة، ولا يأتي إلا من PlanConverter.لا. المسار التقليدي وموازنة تلقائية قالبا برنامج منشوران: أنشئ برنامجاً من قالب فيصلك كل شيء مربوطاً. ولا تحتاج الكود إلا إن أردت تعديل القاعدة نفسها.
تعليق link-behavior-config في أعلى السكربت. إن كتبت مفتاحاً غير مذكور في سطر target أُهمل في صمت بلا رسالة خطأ.
لا تمسّ السكربت. غيّر قيمة عدد الأيام في صف الربط داخل قالب تثبيت، أو في إعدادات الربط داخل المنهج إن أردت قيمةً لمستوى بعينه.
السلوك العام تنشره المنصة ويتاح لكل المساجد للاستعمال في روابط القوالب، وسلوك المسجد ينشئه مسؤولوه ويعدّلونه محلياً. والقوالب المنشورة تشير إلى السلوكيات العامة بمعرّفاتها، فتصلك مع القالب.
نعم، السكربتات هنا كاملة كما تعمل. والشرط أن تلصقه كاملاً — فالسكربت المقتطع أخطر من الخطأ الصريح — وأن تُعلن حقوله المراقبة والمستهدفة كما في تعليقه، ثم تجرّبه في نافذة الاختبار قبل ربطه.