منشئ العروض التقديمية
تُحرَّر العروض التقديمية في أداة وتُقدَّم في أخرى، وفي الرحلة ذهابًا وإيابًا تموت الشرائح: تتغير الخطوط، وتنكسر التخطيطات، وتحاربك عروض keynote الغنية بالنقاط على كل بكسل. باني Markdown أولًا يتجاوز كل ذلك ، تكتب الشرائح كنص عادي مع --- بينها، ويحوّلها أمر واحد إلى ملف HTML مكتفٍ بذاته يُفتح في أي مكان وأي متصفح دون أي تطبيق. يبني هذا المشروع ذلك الباني: يحلل عرضًا Markdown، ويعرض كل شريحة، ويطبّق سمة، ويرفع ملاحظات المتحدث، ويشحن deck.html واحدًا.
يفترض هذا Python 101 ، إدخال/إخراج الملفات، والسلاسل، والدوال. لا شيء بعده: لا إطار عمل JavaScript، ولا قاعدة بيانات، ولا أداة بناء. هذا اختياري وغير مُقيَّم؛ راجع مشاريع من العالم الحقيقي للاطلاع على القائمة الكاملة.
🎯 ما ستفعله
- حدّد صيغة عرض ، شرائح مفصولة بـ
---، وعنوانًا كأول#، وملاحظات في تعليقات<!-- -->. - قسّم سلاسل العرض إلى شرائح نظيفة، حتى عندما يحتوي جسم الشريحة على أسطر تبدو فواصل.
- اعرض Markdown كل شريحة بصيغة HTML واشتقّ عنوانها.
- سمة العرض باستبدال سلسلة CSS واحدة، وارفع ملاحظات المتحدث من الصفحة المرئية.
- ألّف
deck.htmlمكتفيًا بذاته وافتحه في متصفح.
أين تُشغّل هذا
محليًا باستخدام uv هو المسار الأساسي ، المكافأة deck.html حقيقي تفتحه في نافذة متصفح، وحلقة “عدّل deck.md، شغّل الأمر، حدّث النافذة” هي صلب فكرة الأداة. تفترض الخطوات مجلدًا صغيرًا مع uv ومكتبة Markdown واحدة.
GitHub Codespaces التجربة نفسها: افتح codespaces.new/abderrahim-lectures/python-data-analysis-course وتُشغَّل الأوامر نفسها في نافذة متصفح مع Node وPython وuv مثبّتة مسبقًا ، ويفتح HTML المبنى حديثًا مباشرة في لوحة معاينة.
Google Colab وKaggle Notebooks وBinder يشغّلون خط أنابيب التحليل والعرض بالكامل فعليًا ، لا يحتاج شيء هنا مفتاحًا أو GPU. التحفظ الصادق في الميل الأخير: يطبع الدفتر HTML المولّد ويمكنه إغراقه في ملف للتنزيل، لكن حلقة تحديث نافذة المتصفح هي حيث يستحق باني شرائح مكانته، وهي تجربة ملفات محلية. استخدم الدفتر لتعلم الآلية؛ وابنِ عرضك الفعلي محليًا.
الإعداد
كل ما تحتاجه قبل عرض أول شريحة: uv، ومكتبة markdown، وعرض بداية من شريحتين.
ثبّت uv والاعتماد الواحد
macOS / Linux (الطرفية):
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows (PowerShell):
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
أغلق طرفيتك وأعد فتحها، ثم:
uv --version
mkdir presentation-builder && cd presentation-builder
uv init --bare
uv add markdown
اكتب عرض بداية
الصق هذا في deck.md:
# Why short talks beat long decks
Short talks respect attention. The audience recovers mid-talk,
and you are forced to say the one thing you actually know.
<!-- Note: open with the "two-minute" icebreaker, then start the timer. -->
---
## The three-slide rule
1. One problem.
2. One change.
3. One next step.
Slides are a scaffold, not the talk. <!-- Note: the scaffold line lands best after a pause. -->
uv run python -c "import markdown; print('markdown ready')"
✅ قائمة التحقق
- ✅
uv --versionيطبع رقم إصدار. - ✅
markdownمثبّتة عبرuv add markdown. - ✅
deck.mdموجود: شريحتان مفصولتان بسطر يحتوي---حصرًا، ولكل منهما ترويسة، وتعليقا HTML مخفيان يعملان كملاحظتين.
الخطوة 1: قسّم عرضًا إلى شرائح
العرض ملف فيه --- فاصل بين الشرائح. يعيش خط الأنابيب كله أو يموت على تقسيم صحيح، والفهمان الخاطئان عند الناس موجودان هنا معًا: سطر --- داخل كتلة تعليمات برمجية مسوّرة يجب ألا يقسم العرض، ويجب ألا تصبح الأسطر الفارغة البادئة/الخاتمة شرائح شبحية.
1.1 اكتب مُقسِّم الشرائح
# slides.py
from pathlib import Path
SEPARATOR = "---"
def split_deck(text: str) -> list[str]:
slides, current, in_fence = [], [], False
for line in text.splitlines():
if line.strip().startswith("```"):
in_fence = not in_fence
current.append(line)
elif line.strip() == SEPARATOR and not in_fence:
slides.append("\n".join(current))
current = []
else:
current.append(line)
if current:
slides.append("\n".join(current))
return [s.strip() for s in slides]
if __name__ == "__main__":
deck = Path("deck.md").read_text(encoding="utf-8")
slides = split_deck(deck)
print(f"{len(slides)} slides")
for i, s in enumerate(slides, 1):
print(f" slide {i} starts: {s.splitlines()[0]!r}")
علم السور هو القطعة التي تفصل الباني الحقيقي عن خدعة الخمس عشرة ثانية: أسطر ``` تقلب in_fence، و--- لا تنهي شريحة إلا خارج كتلة الكود ، فشريحة تعرض لك --- حرفيًّا في مثال مسوَّر تبقى شريحة واحدة. [s.strip() for s in slides] الخيتامي يزيل لفّ الفراغ حول كل شريحة بهدوء دون لمس تخطيطها الداخلي.
👟 تلميح البداية : شغّل المقسِّم وعدّ ، شريحتان لعرضنا. ثم لفّ --- مؤقتًا داخل كتلة مسوَّرة في الشريحة الثانية وأعد التشغيل: يجب أن يبقى العد 2، وهذه التجربة هي درس الخطوة كلها.
🎯 الناتج المتوقع : 2 slides، مع slide 1 starts: '# Why short talks beat long decks' وslide 2 starts: '## The three-slide rule'.
🩹 إذا لم يعمل : إذا أبلغ 3+ شرائح، ف--- داخل سور قسم العرض ، تحقق أن تبديل السور مفاتيحه startswith("```") لا == "```" (المسافات الخاتمة تكسر التطابق الصارم). إذا كانت الشريحة الأخيرة مفقودة، فالملحق الخيتامي if current: مطوَى ، عرض ينتهي عند --- مباشرة له شريحة أخيرة فارغة يتجاهلها if بشكل صحيح، ويجب أن يعمل بعد الحلقة.
1.2 تحقّق من التقسيم
✅ قائمة التحقق
- ✅ يُرجع
split_deckشريحتين2بالضبط لـdeck.md، كلٌّ منها يبدأ بسطر الترويسة. - ✅ إضافة كتلة مسوَّرة تحتوي
---إلى شريحة لا تقسمها؛ حذف السور يقسمها. - ✅ تجرَّد الأسطر الفارغة البادئة/الخاتمة حول الشريحة دون إزالة الأسطر الفارغة الداخلية.
- ✅ عرض ينتهي بـ
---يتجاهل الشريحة الفارغة الخيتامية بدلًا من إنتاج شريحة لا تعرض شيئًا.
🤔 سؤال (أسئلة) سقراطي(ة)
- الفاصل سطر محتواه بعد التجريد يساوي
---. اكتب أصغر سطر من شأنه أن يطلق إشارة كاذبة (-- -؟---؟ مسافة خاتمة؟) وأخبرني هل يحميكstrip()أم يفعّلها ، ثم قرّر هل الصرامة هي ما تريد. - ثلاثة شرطات داخل سور بيانات (أنت تعرض فاصلًا)، وخارج السور تحكم. ما حالة محتوى تريد فيها فعليًا
---غير مسوَّر داخل شريحة ألا يقسم ، وهل يجادل ذلك لصالح اشتراط علامة<!-- slide →صريحة بدلًا من ذلك؟ سمِّ المقايضة.
الخطوة 2: اعرض شريحة واسحب عنوانها
كل شريحة Markdown في طريقها إلى HTML، وكلٌّ منها يحتاج عنوانًا لنقاط تنقل المقدم ولـh1. القاعدة بسيطة وتستحق التوضيح: أول سطر ترويسة في الشريحة هو العنوان مهما كان مستواه (# أو ##)، والجسم كل ما بقي ، يُعرض بمكتبة markdown نفسها، دون تكرار الترويسة العنوانية في الجسم.
2.1 اعرض الجسم واشتقّ العنوان
# render.py
import re
import markdown as md
def render_slide(slide: str) -> dict:
lines = slide.splitlines()
title = "Untitled slide"
body_lines = []
for i, line in enumerate(lines):
m = re.match(r"^(#{1,6})\s+(.+)", line)
if m and not body_lines:
title = m.group(2)
else:
body_lines.append(line)
body_html = md.markdown("\n".join(body_lines), extensions=["fenced_code"])
return {"title": title, "body_html": body_html}
if __name__ == "__main__":
slide = "## Two words\n\nEverything else on the slide."
print(render_slide(slide))
حارس if m and not body_lines هو قاعدة “أول ترويسة تكسب” كاملة مضغوطة: تصبح أول ترويسة العنوان، وبمجرد جمع أي نص غير ترويسة، تكون التروسيس اللاحقة محتوى عاديًّا. التعبير النمطي أوسع عمدًا مما يحتاج ، #{1,6} يطابق مستويات الترويسة 1–6 ، لكن جملة not body_lines تعني أن أولها فقط هو المهم هنا، ويبقي الجسم المعروض التروسيس الباقية محتوى في موضعها الصحيح.
👟 تلميح البداية : اعرض شريحة 1 من العرض وانظر إلى المفتاحين ، العنوان Why short talks beat long decks (لاحظ: اختفى #)، وجسم HTML مع <p> وتعليق الملاحظة ما يزال بعيدًا.
🎯 الناتج المتوقع : لشريحة العرض 1: {'title': 'Why short talks beat long decks', 'body_html': '<p>Short talks respect attention...</p>'} ، أي تروسيسة ## على شرائح أخرى تبقى في الجسم كـ<h2>.
🩹 إذا لم يعمل : إذا كان title هو Untitled slide، فالتعبير النمطي لم يطابق ، تحقق من ترويسة مثل #Title (بلا مسافة، تفشل \s+) أو حرف تشكيل في سطر الترويسة؛ يجب التقاط نص الترويسة عبر (.+) حرفيًّا. إذا ظهرت الترويسة العنوانية أيضًا في الجسم، فحارس not body_lines لا يوقفها ، أعد قراءة منطق if/else: يجب أن يأخذ فرع الترويسة فقط عندما لا يُجمع شيء بعد.
2.2 تحقّق من العرض
✅ قائمة التحقق
- ✅ أول ترويسة في كل شريحة تصبح
titleمع تجريد علامات#. - ✅ التروسيس غير الأولى تُعرض في الجسم كـ
<h2>/<h3>، لا كعناوين. - ✅ الكتل البرمجية المسوَّرة تنجو عبر
fenced_codeوتظهر كـ<pre><code>. - ✅ شريحة بلا ترويسة نهائيًّا تهبط إلى
Untitled slideدون انهيار.
🤔 سؤال (أسئلة) سقراطي(ة)
- تتجاهل قاعدتنا “أول ترويسة تكسب” المستوى:
## Two wordsو# Two Wordsيسمّيان الشريحة معًا. هل يهمّ أن تصبح##عنوان شرائح بينما تعني#عادة عنوان العرض ، وما القاعدة التي تميّز “عنوان العرض” عن “عنوان الشريحة” بلا غموض؟ - العنوان مشتق من المحتوى، فنص الشريحة يحدد تسمية تنقلها. ماذا يحدث إذا بدأ شريحتان بنفس الترويسة ، وهل هي مشكلة تجميلية أم مشكلة صحة في نقاط العرض؟
الخطوة 3: سمة العرض
العرض محتوى بالإضافة إلى هيئة. الهيئة هنا سلسلة CSS واحدة تُقحم في رأس الصفحة ، فـ”السمة” ببساطة استبدال السلسلة ، والواجهة التي تجعل هذا الإبدال آمنًا قاموس صغير من سمات مسماة يمكن لعلم --theme الاختيار منها.
3.1 عرّف السمات ومُعرِضًا
# theme.py
THEMES = {
"light": ("#f7f7f5", "#222", "Georgia, serif", "Helvetica, Arial, sans-serif"),
"ink": ("#14161a", "#e8e6e3", "Georgia, serif", "Helvetica, Arial, sans-serif"),
"paper": ("#fdf6e3", "#073642", "Comic Sans MS, monospace", "monospace"),
}
def css_for(theme: str) -> str:
bg, fg, heading_font, body_font = THEMES[theme]
return f"""
<style>
body {{ margin: 0; background: {bg}; color: {fg};
font-family: {body_font}; }}
section {{ min-height: 90vh; padding: 2.5em;
border-bottom: 1px solid {fg}; }}
h1, h2, h3 {{ font-family: {heading_font}; }}
code {{ background: {fg}22; padding: 0 0.3em; }}
</style>"""
if __name__ == "__main__":
for name in THEMES:
print(name, "->", css_for(name)[:40], "...")
السمة بيانات ، أربع قيم لكل مدخل، تُفكّ في f-string CSS ، فإضافة سمة إضافة tuple لا تحرير ترميز. يحملان تفصيلا CSS الإحساس: min-height: 90vh يجعل كل شريحة كتلة منفذ كامل (انضباط “فكرة واحدة في الشاشة”)، و{{/}} المضاعفان في f-string قوسان حرفيان، وهو ما ينساه كل شخص ملمّ بـPython مرة واحدة ، حيث يثير {} دون اقتران KeyError على السلسلة.
👟 تلميح البداية : نادِ css_for("light") واقرأ CSS كمَن لم يرَ CSS يومًا ، كل شريحة كتلة کاملة الارتفاع، والتروسيس بخط serif، والكود بشريحة شبه شفافة. ثم نادِ css_for("nope") وراقب KeyError يثبت أن القاموس هو المدخل الوحيد.
🎯 الناتج المتوقع : ثلاثة أسطر (light ->، ink ->، paper ->)، يطبع كلٌّ منها سلسلة CSS مقصوصة؛ يعرض css_for("ink") الإبدال الخلفي #14161a.
🩹 إذا لم يعمل : إذا كان KeyError: 'nope'، فهذا صحيح ، قاموس السمات قائمة مسموح بها. إذا كان هناك { حرفي في المخرج، فهيّأت قوسًا مفردًا لـf-string ، كل {/} بنيوي يجب مضاعفته ({{ background: ... }}). إذا لم تعرض الألوان في المتصفح، فـhex بنمط 8px مثل {fg}22 يستخدم hex ثمانيًا تتجاهله المتصفحات القديمة ، إما استخدم 6 خانات أرقام أو أبقِ div تغطية شبه شفاف.
3.2 تحقّق من السمة
✅ قائمة التحقق
- ✅ يُرجع
css_forنص أسلوب صالحًا للسمات الثلاث، كلٌّ منها بخلفيته ومقدمته الخاصة. - ✅ أسماء السمات المجهولة تثير
KeyError، القاموس المجموعة الكاملة المسموح بها. - ✅ الأقواس البنيوية للـf-string تُعرض كـ
{...}حقيقي في المخرج، لا أخطاء Python ولا{حرفي. - ✅ يظهر
min-height: 90vhفي كل سمة ، كل شريحة كتلة بحجم منفذ عرض.
🤔 سؤال (أسئلة) سقراطي(ة)
- السمات tuples مشفّرة. ما الذي يغيّره علم
--theme-file custom.cssفي إمكانية تسمية أحدهم للعرض ، وما زاوية أمان نصyour.cssالمحقون فيdeck.htmlحرفيًّا (تلميح:expression()في CSS يكاد ميت، لكن مبدأ الإقحام من مدخل لا يُوثَق به حي)؟ - نضع سلسلة السمة في
<style>للمستند. إذا احتاج عرض زميل خطًّا من CDN (<link rel="stylesheet" href="...">)، فهل يستوعبcss_forذلك اليوم، أم يحتاج آلية ثانية؟ هل يستحق الإصلاح قبل العرض أم بعد طلب الميزة؟
الخطوة 4: ارفع ملاحظات المتحدث من الشرائح
يرى الجمهور الشرائح؛ ويرى المقدم الملاحظات. اتفاقية ملاحظاتنا تعليق HTML ، <!-- Note: ... --> ، مطوي في أي مكان في مصدر الشريحة، و”الرفع” يعني: رصد التعليقات أثناء العرض، وجمعها في حقل notes، وإزالتها من جسم HTML المرئي حتى لا يرى الجمهور نص <!-- ... -->.
4.1 استخرج التعليقات واجرّدها
# notes.py
import re
from render import render_slide
COMMENT = re.compile(r"<!--\s*(.*?)\s*-->", re.DOTALL)
def extract_notes(slide: str) -> tuple[str, list[str]]:
cleaned, notes = [], []
for line in slide.splitlines():
for match in COMMENT.finditer(line):
notes.append(match.group(1))
partial, n = COMMENT.subn("", line)
cleaned.append(partial if not n else "")
return "\n".join(cleaned), notes
if __name__ == "__main__":
slide = "Visible line. <!-- Note: say this slowly -->"
body, notes = extract_notes(slide)
print("body:", repr(body))
print("notes:", notes)
يجد التعبير النمطي <!--\s*(.*?)\s*--> التعليق عبر .*? الكسول (يتوقف عند أول --> خاتم)، ويكتسح مسح finditer كل ملاحظة بينما يزيل subn كل تعليق في نفس المرحلة. سطر partial if not n else "" الغريب باعتدال مهم: سطر كان كليًّا تعليقًا يجب أن يختفي كليًّا (يصبح سلسلة فارغة)، بينما سطر بتعليق مطوي يحافظ على نصه المرئي مجرّدًا بنظافة.
👟 تلميح البداية : شغّله على شريحة العرض 1 ، يجب أن ينتج سطر <!-- Note: open with... --> ملاحظة واحدة بالضبط وسلسلة فارغة في مكانه، تاركًا <p>Short talks... نظيفًا.
🎯 الناتج المتوقع : body: يطبع سطرًا اختفى منه <!-- ... --> وبقي Visible line.، وnotes: يطبع ['say this slowly'].
🩹 إذا لم يعمل : إذا نجا التعليق إلى الجسم، فالتعبير النمطي لم يطابق شيئًا ، تحقق أنك استخدمت re.DOTALL (وإلا فلن يطابق تعليق متعدد الأسطر يمتد سطرين في مرحلة لكل سطر). إذا التُقطت ملاحظة مرتين، فـfinditer + subn يعدّان التعليق نفسه مرتين ، يجب أن يعملا على السلسلة نفسها مرة واحدة لكل منهما؛ تعليق مكرر يعني أن حلقة الباحث دارت مرتين.
4.2 تحقّق من استخراج الملاحظات
✅ قائمة التحقق
- ✅ تعليق سطر واحد يصبح ملاحظة واحدة ويختفي من الجسم المرئي.
- ✅ تعليق متعدد الأسطر (
<!--\nnote\nmore note\n-->) يصبح ملاحظة واحدة متعددة الأجزاء، محذوفًا بالكامل. - ✅ النص قبل وبعد تعليق مطوي في السطر نفسه ينجو معًا، بمغادرة التعليق وحده.
- ✅ عرض بلا تعليقات نهائيًّا ما يزال يعرض ،
notesقائمة فارغة، والجسم دون تغيير.
🤔 سؤال (أسئلة) سقراطي(ة)
- اخترنا
HTML commentsحاملًا للملاحظات. ما الذي تضمنه بهذا الاختيار عن الملاحظات (إنها خفية في المتصفح حتى تفحص المصدر) وما الذي تخسره (ملاحظات منظّمة كموجّه للشريحة)؟ هل توجد علامة أصلية في Markdown (::notes::) تنجو من العرض وتكون قابلة للبحث ، وما الذي ستنكسر بإضافتها؟ - تُلتقط الملاحظات بشراهة (
.*?يتوقف عند أول-->). اكتب التعليق الذي يجعل المطابقة الكسولة تنتج ملاحظة جزئية ، هل-->داخل ملاحظة مشروع يومًا، وهل يجب أن تحظر الاتفاقية ذلك؟
الخطوة 5: ألّف العرض وشحنه
كل شيء موجود أجزاء؛ الخطوة 5 فعل صنع ملف يُفتح في متصفح. قالب الصفحة الكاملة يقحم العنوان، وسمة CSS، والشرائح المعروضة ، بما فيها كتلة ملاحظات متحدث لا يراها الجمهور ، ويكتب deck.html واحدًا مكتفيًا بذاته.
5.1 ألّف الصفحة الكاملة
# build.py
from pathlib import Path
from slides import split_deck
from render import render_slide
from notes import extract_notes
from theme import css_for
PAGE = """<!doctype html><html><head><meta charset="utf-8">
<title>{deck_title}</title>{css}</head><body>{slides}{notes}</body></html>"""
def build(deck_path: str, theme: str = "light") -> str:
slides = [render_slide(s) for s in split_deck(Path(deck_path).read_text())]
rendered = []
raw_notes = []
for s in slides:
body, notes = extract_notes(s["body_html"] if False else "")
# simpler path: render content, then lift notes from the raw slide text
rendered.append(f'<section><h1>{s["title"]}</h1>{s["body_html"]}</section>')
return PAGE.format(
deck_title="My deck",
css=css_for(theme),
slides="\n".join(rendered),
notes="",
)
if __name__ == "__main__":
Path("deck.html").write_text(build("deck.md", "ink"), encoding="utf-8")
print("wrote deck.html")
هاكم اللحظة الصادقة في هذا التجميع: لا يمكن رفع الملاحظات من body_html ، فقد جُرّدت أثناء العرض سلفًا، لذا يرفعها خط الأنابيب الصحيح من نص الشريحة الخام بدلًا من ذلك. ولذلك يجب تغذية خطوة render_slide بالشريحة المطهرة من الملاحظات كليًّا. تصحيح التصميم هذا هو نقطة تدريس الخطوة: عند تركيب أدوات حقيقية، يُحسم ترتيب التحويلات بتبعيات البيانات، لا بالترتيب الذي تخيّلته أولًا ، وفرع ميت متروك كتعليق هو الرواسب الصادقة لذلك التصحيح.
# build.py — the corrected pipeline
def build(deck_path: str, theme: str = "light", deck_title: str = "My deck") -> str:
slides = []
all_notes = []
for raw in split_deck(Path(deck_path).read_text()):
clean, notes = extract_notes(raw)
s = render_slide(clean)
slides.append(f'<section><h1>{s["title"]}</h1>{s["body_html"]}</section>')
all_notes.extend(notes)
notes_html = "\n".join(f"<p hidden>Note: {n}</p>" for n in all_notes)
return PAGE.format(deck_title=deck_title, css=css_for(theme),
slides="\n".join(slides), notes=notes_html)
if __name__ == "__main__":
Path("deck.html").write_text(build("deck.md", "ink"), encoding="utf-8")
print("wrote deck.html")
الحلقة المصححة هي خط الأنابيب الحقيقي الوحيد: استخراج ← تطهير ← عرض ← تغليف. تصبح كل شريحة <section> تحمل <h1> (العنوان) مع جسمها، وتهبط الملاحظات المرفوعة في عناصر <p hidden> في نهاية الصفحة ، حاضرة في المصدر للمقدّم، وdisplay:none للجمهور. استدعاء PAGE.format(...) يسمّي كل موضع، ودوال غرض واحد بنمط Flask تُبقي كل تحويل منفصلًا وقابلًا للاختبار.
👟 تلميح البداية : ابنِ العرض المسماة ink، وافتح deck.html في متصفح، وتصفح ، شريحتان بملء الارتفاع، وعنوان Why short talks فوق الأولى، وخلفية داكنة، وملاحظات <p hidden> مرئية فقط إذا فحصت المصدر.
🎯 الناتج المتوقع : wrote deck.html؛ يُفتح الملف صفحة من شريحتين ، خلفية داكنة (ink)، وتروسيس serif، وجسم بنمط Visible line.، ورقاقات كود على أي شريحة كود، وكتلة ملاحظات خفية في النهاية.
🩹 إذا لم يعمل : إذا ظهرت الملاحظات مرئية في الصفحة، فـhidden لا يُصدَر لملف الملاحظات ، <p hidden> يُعرض كـdisplay:none في كل المتصفحات الحديثة؛ انظر السلسلة الحرفية في ربط الملاحظات. إذا ظهرت ترويسة الشريحة مرتين (مرة في h1 ومرة في الجسم)، فحصل render_slide على شريحة غير مطهّرة وتكرار الترويسة في الجسم لم يُجرّد ، تأكد أن extract_notes عملت قبل render_slide، كما في الحلقة المصححة.
5.2 تحقّق من العرض المشحون
✅ قائمة التحقق
- ✅ يُفتح
deck.htmlفي متصفح كشريحتين بالضبط بأقسام کاملة الارتفاع و<h1>واحدة لكلٍّ منهما. - ✅ سمة
inkمطبّقة مرئيًّا (خلفية داكنة، نص فاتح) ، تبديل--themeوسيطة واحدة. - ✅ تظهر الملاحظات في عرض المصدر تحت
<p hidden>وليس في أي موضع من الصفحة المرئية. - ✅ إعادة البناء بعد تحرير
deck.mdتنتج ملفًا محدّثًا ، المخرج مشتق، لا يُدار يدويًا.
🤔 سؤال (أسئلة) سقراطي(ة)
- تشغّل الحلقة المصححة
extract_notesقبلrender_slide. حدّث نمط فشل الترتيب الخطأ واقعيًّا: ماذا تصبح ملاحظة مثلNote: underline the word "trust"إذا نجت إلى الـmarkdown (تلميح:**trust**)، ولماذا يحمي التجريد أولًا خطوة العرض؟ - عناوين الشرائح من المحتوى، والملاحظات مجمّعة مسطّحة ، بلا ارتباط بالشريحة التي أتت منها. ما التغيير (قاموس
slide_index -> notes، أو صفةdata-slideعلى كل<p hidden>) الذي يجعل الملاحظات قابلة للاستخدام بواسطة أداة نص مقدم، وهل تفضّل ذلك الآن أم بعد أول عرض من 20 شريحة؟
⚠️ مآزق شائعة
- تقسيم أعمى أمام السور.
---داخل كتلة مسوَّرة```بيانات (شريحة تعرض فاصلًا حرفيًّا)، لكن مقسِّمًا ساذجًا يحوّلها إلى شريحتين ويبالغ في عدّ العرض بصمت. يجب أن يعيش تبديلin_fenceفي حلقة المقسِّم، بمفاتيح علىstartswith("```")حتى تغلق سور بمسافات خاتمة. - تعليقات تنجو إلى HTML المرئي. تعليق
<!-- Note: ... -->يفلت من خطوة التجريد فيُعرض تعليقًا رماديًّا مرئيًّا في الشرائح ، تحديدًا الملاحظة التي يجب ألا يراها الجمهور. استخرج بنمط المرحلتين (finditerللجمع،subnللإزالة) واختبر أن السطر الذي هو فقط تعليق يصبح فارغًا، لا فارغًا لكن متجسِّدًا. - تمهير f-string بقوس مفرد. يكتب
css_forCSS، وCSS ممتلئ بقوسين حرفيين؛{ background: ... }بقوس مفرد يثيرKeyErrorأو أسوأ يقحم. مضاعفة كل قوس بنيوي ({{ }}) السبيل الوحيد ، أو ابنِ CSS بتسلسل سلاسل وتجاهل طائفة الأخطاء كلها. - عرض الملاحظات في الـmarkdown. إذا عملت
extract_notesبعدrender_slide، فالنص الملاحظاتيthe word **trust**يغذي مُعرِض markdown ويصبح نصًا عريضًا مرئيًا. ترتيب تبعية البيانات ، جرّد التعليقات أولًا، اعرض ثانيًا ، قيد حقيقي لا أسلوب بيت. - إقحام محتوى لا يُوثق به في HTML. يصبح نص الشريحة HTML داخلي في
<section>عبر f-string. محتوى الشريحة ملكك للآن، لكن لحظة مجيء الملاحظات أو العناوين من ملف لا يُوثق به، يكون الإقحام الخام بداية XSS. الخط الصادق: أبقِdecksتحت سيطرتك أو أضف مرحلة تهرّب، ولا “تحسّن” بدفع مصدر جديد دون تحديث ذلك القرار.
ما بنيته للتو
باني عروض يعمل: deck.md واردًا، وdeck.html واحد مكتفٍ بذاته صادرًا ، شرائح مقسومة بنظافة حتى عبر فواصل مسوَّرة، وعناوين مشتقة من أول ترويسة، وسمة تستبدلها بوسيطة واحدة، وملاحظات متحدث مخفية عن الجمهور لكن حاضرة في المصدر. المهارة القابلة للنقل غريزة خط الأنابيب التعريفي: المحتوى كنص عادي بيانات، والعرض كتحويلات مرتّبة، والسمة كإعداد ، الشكل الدقيق خلف كل أداة “اكتب مرة وشحن مرات”، من مولّدي المواقع الثابتة إلى أطر الشرائح إلى محركات التقارير. محاضرتك القادمة ملف .md يقدّم نفسه فعلًا.
شغّل نسخة أكمل دون أي إعداد محلي
examples/presentation-builder/ في مستودع الدورة يضمّ وحدات المقسِّم والمُعرِض والسمة والملاحظات والبناء مع عرض البداية ودفترًا يشغّل كل خطوة بالترتيب. استنسخه، أو افتح المستودع كاملًا في GitHub Codespace، وابنِ عرض العينة في نافذة متصفح.
إلى أين تذهب من هنا
- علم CLI
--themeمبني علىargparse، السمة موجودة أصلًا كـcss_for، فالعلم 4 أسطر، وهو الفرق بين “عدّل السكربت” و”أداة حقيقية”. - نقاط تقدم: صدّر روابط
<a href="#slide-2">في شريط تذييل ، يحصل المقدمون على تنقل قابل للنقر، وما يزال صفر JavaScript إذا اتكأت على المراسي. - عرض المقدم:
<section hidden>ثانية في النهاية تقرن ملاحظات كل شريحة بساعة حيّة، فتبقي عرضك الجاري نص سفره على بُعد تمريرة. - اختصار
--pdf: بعد كتابةdeck.md، نادِ طباعة الشاشة عديمة الرأس في OS (chromium —headless —print-to-pdf) من Python ، يبقى خط الأنابيب ملفًا واحدًا، وتظهر النشرات دون تطبيق.
شارك مشروعك مع الصف
هل بنيت شيئًا تفخر به ، عرضًا قدّمته فعلًا من HTML، سمة سألك عنها زملاؤك؟ examples/student-projects/ معرض لمشاريع قدّمها طلاب آخرون، ويشرح README إضافة مشروعك عبر طلب سحب (pull request) من البداية للنهاية: التفرع، وفرع العمل، والالتزام، وفتح PR. لا يُفترض أي خبرة سابقة بـ git.
مرحبًا بك في كتابة Python خارج المتصفح. 🎓
أكمل كل خطوة ثم حدد المشروع كمكتمل لجمع نقاطه.