diff --git a/app/content/blog/how-to-do-meeting-minutes.md b/app/content/blog/how-to-do-meeting-minutes.md index feae5b1..2567a0b 100644 --- a/app/content/blog/how-to-do-meeting-minutes.md +++ b/app/content/blog/how-to-do-meeting-minutes.md @@ -22,6 +22,8 @@ source_knowcap_ids: [] read_minutes: 13 lang: en dir: ltr +template_download_href: "/downloads/meeting-minutes-template.docx" +template_download_label: "Free meeting minutes template (.docx)" --- # How to Do Meeting Minutes in 2026: Steps + Free Template diff --git a/app/content/blog/namuthaj-mahdar-ijtimaa-ar.md b/app/content/blog/namuthaj-mahdar-ijtimaa-ar.md index a3358e9..ce79a47 100644 --- a/app/content/blog/namuthaj-mahdar-ijtimaa-ar.md +++ b/app/content/blog/namuthaj-mahdar-ijtimaa-ar.md @@ -22,40 +22,15 @@ source_knowcap_ids: [] read_minutes: 8 lang: ar dir: rtl +template_download_href: "/downloads/namuthaj-mahdar-ijtimaa-template.docx" +template_download_label: "نموذج محضر اجتماع جاهز (.docx)" --- محضر الاجتماع هو الوثيقة الرسمية التي تُدوَّن فيها أحداث الاجتماع: مكانه وزمانه، وأسماء الحاضرين والغائبين، والبنود التي نوقشت، والقرارات التي اتُّخذت، والمهام المُسندة لكل شخص. الغرض منه ليس تلخيص ما قيل، بل إثبات ما تم الاتفاق عليه حتى لا تختلف الذاكرة حوله بعد فترة. محضرٌ مكتوب جيداً يجيب عن ثلاثة أسئلة بوضوح: ماذا قرّرنا؟ ومن المسؤول عن كل قرار؟ وما الموعد النهائي؟ أما المحاضر الضعيفة فتسجّل النقاش وتغفل القرار، فتصبح بلا قيمة عند أول خلاف. في هذا الدليل نموذج محضر اجتماع جاهز يمكنك نسخه واستخدامه فوراً، ثم شرح عملي لكيفية كتابة محضر اجتماع خطوة بخطوة، ومن يكتبه ومتى، والعناصر التي يجب أن يتضمنها كل محضر رسمي. ## نموذج محضر اجتماع جاهز للنسخ -انسخ النموذج التالي واملأ الفراغات. صالح للاجتماعات الإدارية ومجالس الإدارة ولجان التدقيق: - -``` -محضر اجتماع: [اسم الجهة / اللجنة] -رقم الاجتماع: [ ] التاريخ: [ / / ] الوقت: من [ ] إلى [ ] -مكان الانعقاد: [حضوري / عن بُعد عبر … ] - -الحاضرون: [الاسم — الصفة] -الغائبون (بعذر / بدون): [الاسم — الصفة] -رئيس الجلسة: [ ] مُحرِّر المحضر: [ ] - -جدول الأعمال: -١. [البند الأول] -٢. [البند الثاني] -٣. [البند الثالث] - -المناقشات والقرارات: -البند ١: [ملخّص النقاش] — القرار: [نص القرار] — التصويت: [بالإجماع / الأغلبية] -البند ٢: [ملخّص النقاش] — القرار: [نص القرار] - -المهام والمسؤوليات: -| المهمة | المسؤول | الموعد النهائي | -| [ ] | [ ] | [ / / ] | - -موعد الاجتماع القادم: [ / / ] -تم إقفال الجلسة الساعة: [ ] -التوقيعات: رئيس الجلسة [ ] — مُحرِّر المحضر [ ] -``` +حمّل النموذج الجاهز أدناه (.docx — يفتح في Word أو Google Docs) واملأ الفراغات، أو انسخ عناصره من هنا: ترويسة، الحاضرون، جدول الأعمال، المناقشات والقرارات، المهام والمسؤوليات، موعد الاجتماع القادم والتوقيعات. هذا نموذج أولي صالح لأغلب الاجتماعات؛ أما مجالس الإدارة واللجان النظامية فقد تحتاج حقولاً إضافية بحسب لائحتها — مثل النصاب، وصفة الحضور (أصالةً أو إنابةً)، والتحفّظات على القرارات. احفظ نسخة موحّدة واستخدمها في كل اجتماع؛ فالتنسيق الثابت يجعل المحاضر قابلة للمقارنة والمراجعة، وهو ما يسهّل التدقيق والحوكمة. diff --git a/app/public/downloads/namuthaj-mahdar-ijtimaa-template.docx b/app/public/downloads/namuthaj-mahdar-ijtimaa-template.docx new file mode 100644 index 0000000..d90de2e Binary files /dev/null and b/app/public/downloads/namuthaj-mahdar-ijtimaa-template.docx differ diff --git a/scripts/gen-template-docx.py b/scripts/gen-template-docx.py index 76dfc47..a56863f 100644 --- a/scripts/gen-template-docx.py +++ b/scripts/gen-template-docx.py @@ -25,18 +25,42 @@ from docx import Document from docx.shared import Pt, RGBColor from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.oxml.ns import qn ACCENT = RGBColor(0x1F, 0x6B, 0x3A) INK = RGBColor(0x18, 0x18, 0x1B) INK_2 = RGBColor(0x4A, 0x4F, 0x5A) -def is_section_heading(line: str) -> bool: - letters = [c for c in line if c.isalpha()] - return bool(letters) and line == line.upper() and len(line.strip()) > 1 +def apply_rtl(paragraph): + """Right-align + set bidi paragraph direction (python-docx has no high-level API for this).""" + paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT + pPr = paragraph._p.get_or_add_pPr() + bidi = pPr.makeelement(qn('w:bidi'), {}) + pPr.append(bidi) + for run in paragraph.runs: + rPr = run._r.get_or_add_rPr() + rtl = rPr.makeelement(qn('w:rtl'), {}) + rPr.append(rtl) -def build(title: str, subtitle: str, text: str, out_path: str, source_url: str): +def is_section_heading(line: str) -> bool: + s = line.strip() + if len(s) <= 1: + return False + # Bare label ending in a colon (e.g. Arabic "جدول الأعمال:") — no cased + # script needed, works for any language. + if s.endswith(':') or s.endswith(':'): + return True + # ALL-CAPS heading in a cased script (e.g. "MEETING MINUTES"). str.isupper() + # is False for scripts with no case distinction (Arabic, CJK, digits-only), + # so this never false-positives on non-Latin text the way `s == s.upper()` + # would (that comparison is a no-op — and therefore always true — on + # uncased text). + return s.isupper() + + +def build(title: str, subtitle: str, text: str, out_path: str, source_url: str, rtl: bool = False): doc = Document() style = doc.styles["Normal"] @@ -48,6 +72,8 @@ def build(title: str, subtitle: str, text: str, out_path: str, source_url: str): run.bold = True run.font.size = Pt(22) run.font.color.rgb = INK + if rtl: + apply_rtl(h) if subtitle: sub = doc.add_paragraph() @@ -55,6 +81,8 @@ def build(title: str, subtitle: str, text: str, out_path: str, source_url: str): srun.italic = True srun.font.size = Pt(10.5) srun.font.color.rgb = INK_2 + if rtl: + apply_rtl(sub) doc.add_paragraph() @@ -70,13 +98,19 @@ def build(title: str, subtitle: str, text: str, out_path: str, source_url: str): r.font.size = Pt(13) r.font.color.rgb = ACCENT p.space_before = Pt(10) + if rtl: + apply_rtl(p) continue if line.strip().startswith("- "): - doc.add_paragraph(line.strip()[2:], style="List Bullet") + p = doc.add_paragraph(line.strip()[2:], style="List Bullet") + if rtl: + apply_rtl(p) continue p = doc.add_paragraph() r = p.add_run(line) r.font.color.rgb = INK_2 + if rtl: + apply_rtl(p) doc.add_paragraph() foot = doc.add_paragraph() @@ -97,12 +131,13 @@ def main(): ap.add_argument("--text-file", required=True) ap.add_argument("--out", required=True) ap.add_argument("--source-url", default="knowcap.ai/blog") + ap.add_argument("--rtl", action="store_true", help="right-align + bidi paragraph direction (Arabic/Hebrew)") args = ap.parse_args() with open(args.text_file, "r", encoding="utf-8") as f: text = f.read() - build(args.title, args.subtitle, text, args.out, args.source_url) + build(args.title, args.subtitle, text, args.out, args.source_url, rtl=args.rtl) if __name__ == "__main__": diff --git a/scripts/publish-draft.mjs b/scripts/publish-draft.mjs index 3b7b9e2..d012dc3 100644 --- a/scripts/publish-draft.mjs +++ b/scripts/publish-draft.mjs @@ -125,6 +125,12 @@ function buildPublishedFrontmatter(meta, body) { pub.lang = meta.lang || 'en' pub.dir = meta.dir || 'ltr' + // Free downloadable template (tracked TemplateDownload component) — passthrough + if (meta.template_download_href) { + pub.template_download_href = meta.template_download_href + pub.template_download_label = meta.template_download_label || 'Download the template' + } + return pub }