السياق والمتغيرات في مولد موقع Hugo Static
نشرت: 2022-03-10في هذه المقالة ، سنلقي نظرة فاحصة على كيفية عمل السياق في منشئ موقع Hugo الثابت. سنقوم بفحص كيفية تدفق البيانات من المحتوى إلى القوالب ، وكيف تغير بعض البنى البيانات المتاحة ، وكيف يمكننا تمرير هذه البيانات إلى الأجزاء والقوالب الأساسية.
هذه المقالة ليست مقدمة لهوجو . من المحتمل أن تحصل على أقصى استفادة منه إذا كان لديك بعض الخبرة مع Hugo ، لأننا لن نتناول كل مفهوم من البداية ، بل نركز على الموضوع الرئيسي للسياق والمتغيرات. ومع ذلك ، إذا أشرت إلى وثائق Hugo طوال الوقت ، فقد تتمكن من المتابعة حتى بدون خبرة سابقة!
سوف ندرس مفاهيم مختلفة من خلال بناء صفحة نموذجية. لن تتم تغطية كل ملف مطلوب لموقع مثال بالتفصيل ، ولكن المشروع الكامل متاح على GitHub. إذا كنت تريد أن تفهم كيف تتناسب القطع معًا ، فهذه نقطة انطلاق جيدة. يرجى أيضًا ملاحظة أننا لن نغطي كيفية إعداد موقع Hugo أو تشغيل خادم التطوير - تعليمات تشغيل المثال موجودة في المستودع.
ما هو مولد الموقع الثابت؟
إذا كان مفهوم مولدات المواقع الثابتة جديدًا بالنسبة لك ، فإليك مقدمة سريعة! ربما يتم وصف مولدات المواقع الثابتة بشكل أفضل من خلال مقارنتها بالمواقع الديناميكية. يقوم موقع ديناميكي مثل CMS بشكل عام بتجميع صفحة من البداية لكل زيارة ، وربما يجلب البيانات من قاعدة بيانات ويجمع بين القوالب المختلفة للقيام بذلك. من الناحية العملية ، يعني استخدام التخزين المؤقت أن الصفحة لا يتم إعادة إنشائها كثيرًا ، ولكن لغرض هذه المقارنة ، يمكننا التفكير في الأمر بهذه الطريقة. يعد الموقع الديناميكي مناسبًا تمامًا للمحتوى الديناميكي : المحتوى الذي يتغير كثيرًا ، والمحتوى الذي يتم تقديمه في الكثير من التكوينات المختلفة اعتمادًا على المدخلات ، والمحتوى الذي يمكن أن يتلاعب به زائر الموقع.
في المقابل ، نادرًا ما تتغير العديد من المواقع وتقبل القليل من المدخلات من الزوار. يمكن أن يكون قسم "المساعدة" لأحد التطبيقات أو قائمة المقالات أو الكتاب الإلكتروني أمثلة على هذه المواقع. في هذه الحالة ، من المنطقي تجميع الصفحات النهائية مرة واحدة عندما يتغير المحتوى ، وبعد ذلك يتم تقديم نفس الصفحات لكل زائر حتى يتغير المحتوى مرة أخرى.
تتمتع المواقع الديناميكية بقدر أكبر من المرونة ، ولكنها تفرض مزيدًا من الطلب على الخادم الذي تعمل عليه. قد يكون من الصعب أيضًا توزيعها جغرافيًا ، خاصةً إذا كانت قواعد البيانات متضمنة. يمكن استضافة مولدات المواقع الثابتة على أي خادم قادر على تسليم ملفات ثابتة ، ويسهل توزيعها.
الحل الشائع اليوم ، والذي يمزج بين هذه الأساليب ، هو JAMstack. يرمز "JAM" إلى JavaScript و APIs و markup ويصف الكتل الإنشائية لتطبيق JAMstack: يُنشئ مُنشئ الموقع الثابت ملفات ثابتة لتسليمها إلى العميل ، لكن المكدس يحتوي على مكون ديناميكي في شكل JavaScript يعمل على العميل - يمكن لمكون العميل هذا بعد ذلك استخدام واجهات برمجة التطبيقات لتوفير وظائف ديناميكية للمستخدم.
هوغو
Hugo هو مولد موقع ثابت مشهور. إنه مكتوب بلغة Go ، وحقيقة أن Go هي لغة برمجة مجمعة تلمح إلى بعض مزايا Hugos وعيوبه. أولاً ، Hugo سريع جدًا ، مما يعني أنه ينشئ مواقع ثابتة بسرعة كبيرة. بالطبع ، هذا ليس له أي تأثير على مدى سرعة أو بطء المواقع التي تم إنشاؤها باستخدام Hugo بالنسبة للمستخدم النهائي ، ولكن بالنسبة للمطور ، فإن حقيقة أن Hugo تجمع حتى المواقع الكبيرة في غمضة عين تعتبر ذات قيمة كبيرة.
ومع ذلك ، نظرًا لأن Hugo مكتوب بلغة مجمعة ، فإن توسيعها أمر صعب . تسمح لك بعض أدوات إنشاء المواقع الأخرى بإدخال التعليمات البرمجية الخاصة بك - بلغات مثل Ruby أو Python أو JavaScript - في عملية التجميع. لتوسيع نطاق Hugo ، ستحتاج إلى إضافة الكود الخاص بك إلى Hugo نفسه وإعادة تجميعه - وإلا ، فأنت عالق في وظائف القالب التي يأتي Hugo بها خارج الصندوق.
في حين أنه يوفر مجموعة متنوعة غنية من الوظائف ، يمكن أن تصبح هذه الحقيقة محدودة إذا كان إنشاء صفحاتك يتضمن بعض المنطق المعقد. كما وجدنا ، وجود موقع تم تطويره في الأصل يعمل على نظام أساسي ديناميكي ، فإن الحالات التي تأخذ فيها القدرة على إسقاط رمزك المخصص كأمر مسلم به تميل إلى التراكم.
يحتفظ فريقنا بمجموعة متنوعة من مواقع الويب المتعلقة بمنتجنا الرئيسي ، عميل Tower Git ، وقد نظرنا مؤخرًا في نقل بعضها إلى مولد موقع ثابت. بدا أحد مواقعنا ، وهو موقع "Learn" ، مناسبًا بشكل خاص لمشروع تجريبي. يحتوي هذا الموقع على مجموعة متنوعة من المواد التعليمية المجانية بما في ذلك مقاطع الفيديو والكتب الإلكترونية والأسئلة الشائعة على Git ، بالإضافة إلى موضوعات تقنية أخرى.
محتواه ثابت إلى حد كبير ، ومهما كانت الميزات التفاعلية الموجودة (مثل الاشتراكات في الرسائل الإخبارية) كانت مدعومة بالفعل بواسطة JavaScript. في نهاية عام 2020 ، قمنا بتحويل هذا الموقع من CMS السابق إلى Hugo ، واليوم يعمل كموقع ثابت. بطبيعة الحال ، تعلمنا الكثير عن Hugo خلال هذه العملية. هذه المقالة هي وسيلة لمشاركة بعض الأشياء التي تعلمناها.
مثالنا
نظرًا لأن هذه المقالة انبثقت عن عملنا على تحويل صفحاتنا إلى Hugo ، يبدو من الطبيعي وضع صفحة مقصودة افتراضية مبسطة (جدًا!) كمثال. سينصب تركيزنا الأساسي على ما يسمى بقالب "قائمة" القابل لإعادة الاستخدام.
باختصار ، سيستخدم Hugo قالب قائمة لأي صفحة تحتوي على صفحات فرعية. هناك ما هو أكثر من التسلسل الهرمي لقالب Hugos أكثر من ذلك ، لكن ليس عليك تنفيذ كل قالب ممكن. قالب قائمة واحد يقطع شوطًا طويلاً. سيتم استخدامه في أي موقف يستدعي قالب قائمة حيث لا يتوفر المزيد من النماذج المتخصصة.
تتضمن حالات الاستخدام المحتملة صفحة رئيسية أو فهرس مدونة أو قائمة بالأسئلة الشائعة. سيكون قالب القائمة القابل لإعادة الاستخدام الخاص بنا موجودًا في layouts/_default/list.html
في مشروعنا. مرة أخرى ، تتوفر باقي الملفات اللازمة لتجميع مثالنا على GitHub ، حيث يمكنك أيضًا إلقاء نظرة أفضل على كيفية ملائمة القطع معًا. يأتي مستودع GitHub أيضًا مع قالب واحد single.html
- كما يوحي الاسم ، يُستخدم هذا القالب للصفحات التي لا تحتوي على صفحات فرعية ، ولكنها تعمل كقطع فردية من المحتوى في حد ذاتها.
الآن بعد أن قمنا بإعداد المسرح وشرحنا ما سنفعله ، فلنبدأ!
السياق أو "النقطة"
كل شيء يبدأ بالنقطة. في نموذج Hugo ، يكون الكائن .
- "النقطة" - تشير إلى السياق الحالي. ماذا يعني هذا؟ كل قالب يتم تقديمه في Hugo لديه حق الوصول إلى مجموعة من البيانات - سياقها . يتم تعيين هذا مبدئيًا على كائن يمثل الصفحة التي يتم عرضها حاليًا ، بما في ذلك محتواها وبعض البيانات الوصفية. يتضمن السياق أيضًا متغيرات على مستوى الموقع مثل خيارات التكوين ومعلومات حول البيئة الحالية. يمكنك الوصول إلى .Hugo.Version
مثل عنوان الصفحة الحالية باستخدام .Title
.
هيكل.
الأهم من ذلك ، يمكن أن يتغير هذا السياق ، مما يجعل مرجعًا مثل ".Title" أعلى نقطة في شيء آخر أو حتى جعله غير صالح. يحدث هذا ، على سبيل المثال ، عندما تقوم بالتكرار على مجموعة من نوع ما باستخدام range
، أو عندما تقوم بتقسيم القوالب إلى أجزاء وقوالب أساسية . سننظر في هذا بالتفصيل لاحقًا!
يستخدم Hugo حزمة Go "قوالب" ، لذلك عندما نشير إلى قوالب Hugo في هذه المقالة ، نتحدث حقًا عن قوالب Go. يضيف Hugo وظائف قالب دفعة غير متوفرة في قوالب Go القياسية.
في رأيي ، يعد السياق وإمكانية إعادة ربطه من أفضل ميزات Hugos. بالنسبة لي ، من المنطقي أن تكون "النقطة" دائمًا تمثل أي كائن هو المحور الرئيسي لنموذجي في نقطة معينة ، مع إعادة ربطه عند الضرورة كلما تقدمت في العمل. بالطبع ، من الممكن أن تدخل نفسك في فوضى متشابكة أيضًا ، لكنني كنت سعيدًا بها حتى الآن ، لدرجة أنني سرعان ما بدأت في فقدانها في أي مولد موقع ثابت آخر نظرت إليه.
مع هذا ، نحن على استعداد لإلقاء نظرة على نقطة البداية المتواضعة لمثالنا - القالب أدناه ، المقيم في layouts/_default/list.html
في مشروعنا:
<html> <head> <title>{{ .Title }} | {{ .Site.Title }}</title> <link rel="stylesheet" href="/css/style.css"> </head> <body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> </ul> </nav> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> </body> </html>
يتكون معظم القالب من بنية HTML بسيطة ، مع رابط ورقة أنماط ، وقائمة للتنقل وبعض العناصر والفئات الإضافية المستخدمة في التصميم. الأشياء المثيرة للاهتمام هي بين الأقواس المتعرجة ، والتي تشير إلى Hugo للتدخل والقيام بسحرها ، واستبدال كل ما هو بين الأقواس بنتيجة تقييم بعض التعبيرات والتلاعب المحتمل في السياق أيضًا.
كما يمكنك التخمين ، {{ .Title }}
في علامة العنوان إلى عنوان الصفحة الحالية ، بينما يشير {{ .Site.Title }}
إلى عنوان الموقع بالكامل المحدد في تهيئة Hugo . علامة مثل {{ .Title }}
تخبر Hugo ببساطة أن يستبدل تلك العلامة بمحتويات حقل Title
في السياق الحالي.
لذلك ، وصلنا إلى بعض البيانات التي تنتمي إلى الصفحة في قالب. من أين تأتي هذه البيانات؟ هذا هو موضوع القسم التالي.
المحتوى والشيء الأمامي
يتم توفير بعض المتغيرات المتوفرة في السياق تلقائيًا بواسطة Hugo. يتم تحديد الآخرين من قبلنا ، بشكل أساسي في ملفات المحتوى . هناك أيضًا مصادر أخرى للبيانات مثل ملفات التكوين ومتغيرات البيئة وملفات البيانات وحتى واجهات برمجة التطبيقات. في هذه المقالة سيكون تركيزنا على ملفات المحتوى كمصدر للبيانات.
بشكل عام ، يمثل ملف المحتوى الواحد صفحة واحدة. يتضمن ملف المحتوى النموذجي المحتوى الرئيسي لتلك الصفحة وكذلك البيانات الوصفية حول الصفحة ، مثل عنوانها أو تاريخ إنشائها. يدعم Hugo العديد من التنسيقات لكل من المحتوى الرئيسي والبيانات الوصفية. في هذه المقالة ، سنختار التركيبة الأكثر شيوعًا: يتم تقديم المحتوى كـ Markdown في ملف يحتوي على البيانات الوصفية مثل YAML الأمامية.
في الممارسة العملية ، هذا يعني أن ملف المحتوى يبدأ بقسم محدد بسطر يحتوي على ثلاث شرطات في كل نهاية. يشكل هذا القسم المقدمة ، وهنا يتم تعريف البيانات الوصفية باستخدام key: value
(كما سنرى قريبًا ، يدعم YAML هياكل بيانات أكثر تفصيلاً أيضًا). الجزء الأمامي متبوع بالمحتوى الفعلي المحدد باستخدام لغة Markdown.
دعونا نجعل الأمور أكثر واقعية من خلال النظر إلى مثال. هذا ملف محتوى بسيط للغاية به حقل أمامي واحد وفقرة واحدة من المحتوى:
--- title: Home --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
(يوجد هذا الملف في content/_index.md
في مشروعنا ، حيث يشير _index.md
إلى ملف المحتوى لصفحة تحتوي على صفحات فرعية. مرة أخرى ، يوضح مستودع GitHub أين من المفترض أن ينتقل الملف.)
تم تقديمه باستخدام القالب السابق ، جنبًا إلى جنب مع بعض الأنماط والملفات الطرفية (جميعها موجودة على GitHub) ، تبدو النتيجة كما يلي:
قد تتساءل عما إذا كانت أسماء الحقول الموجودة في مقدمة ملف المحتوى الخاصة بنا محددة مسبقًا ، أو ما إذا كان بإمكاننا إضافة أي حقل نريده. الجواب "كلاهما". توجد قائمة بالحقول المحددة مسبقًا ، ولكن يمكننا أيضًا إضافة أي حقل آخر يمكننا التوصل إليه. ومع ذلك ، يتم الوصول إلى هذه الحقول بشكل مختلف قليلاً في النموذج. بينما يتم الوصول إلى حقل محدد مسبقًا مثل title
ببساطة .Title
، يتم الوصول إلى حقل مخصص مثل author
باستخدام. .Params.author
.
(للحصول على مرجع سريع للحقول المحددة مسبقًا ، جنبًا إلى جنب مع أشياء مثل الوظائف ومعلمات الوظيفة ومتغيرات الصفحة ، راجع ورقة غش Hugo الخاصة بنا!)
.Content
المحتوى. ، المستخدم للوصول إلى المحتوى الرئيسي من ملف المحتوى في القالب الخاص بك ، هو متغير خاص. يحتوي Hugo على ميزة " رمز قصير" تتيح لك استخدام بعض العلامات الإضافية في محتوى Markdown الخاص بك. يمكنك أيضا تحديد الخاص بك. لسوء الحظ ، ستعمل هذه الرموز القصيرة فقط من خلال متغير المحتوى - بينما يمكنك تشغيل أي جزء آخر من البيانات من خلال مرشح .Content
، فلن يعالج هذا الرموز القصيرة في المحتوى.
ملاحظة هنا حول المتغيرات غير المحددة: الوصول إلى حقل محدد مسبقًا مثل .Date
يعمل دائمًا ، على الرغم من أنك لم تقم بتعيينه - سيتم إرجاع قيمة فارغة في هذه الحالة. الوصول إلى حقل مخصص غير محدد ، مثل .Params.thisHasNotBeenSet
، يعمل أيضًا ، ويعيد قيمة فارغة. ومع ذلك ، فإن الوصول إلى حقل المستوى الأعلى غير المحدد مسبقًا مثل .thisDoesNotExist
سيمنع الموقع من التجميع.
كما هو موضح بواسطة .Params.author
وكذلك .Hugo.version
و .Site.title
سابقًا ، يمكن استخدام الاستدعاءات المتسلسلة للوصول إلى حقل متداخل في بعض هياكل البيانات الأخرى. يمكننا تحديد مثل هذه الهياكل في المقدمة لدينا. لنلقِ نظرة على مثال ، حيث نحدد خريطة أو قاموسًا ، ونحدد بعض خصائص لافتة على الصفحة في ملف المحتوى الخاص بنا. هنا هو content/_index.md
:
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
الآن ، دعنا نضيف لافتة إلى نموذجنا ، بالإشارة إلى بيانات الشعار باستخدام .Params
بالطريقة الموضحة أعلاه:
<html> ... <body> ... <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> </body> </html>
هذا ما يبدو عليه موقعنا الآن:
على ما يرام! في الوقت الحالي ، نقوم بالوصول إلى حقول السياق الافتراضي دون أي مشاكل. ومع ذلك ، كما ذكرنا سابقًا ، لم يتم إصلاح هذا السياق ، ولكن يمكن تغييره.
دعونا نلقي نظرة على كيفية حدوث ذلك.
التحكم في التدفق
تعد عبارات التحكم في التدفق جزءًا مهمًا من لغة النماذج ، مما يتيح لك القيام بأشياء مختلفة اعتمادًا على قيمة المتغيرات والتكرار عبر البيانات والمزيد. توفر قوالب Hugo المجموعة المتوقعة من التركيبات ، بما في ذلك if/else
للمنطق الشرطي range
التكرار الحلقي. هنا ، لن نغطي التحكم في التدفق في Hugo بشكل عام (لمزيد من المعلومات حول هذا ، راجع التوثيق) ، لكن ركز على كيفية تأثير هذه العبارات على السياق. في هذه الحالة ، تكون العبارات الأكثر إثارة للاهتمام with
و range
.
لنبدأ with
. تتحقق هذه العبارة مما إذا كان لبعض التعبيرات قيمة "غير فارغة" ، وإذا كانت كذلك ، فإنها تعيد ربط السياق للإشارة إلى قيمة هذا التعبير . تشير علامة end
إلى النقطة التي يتوقف فيها تأثير العبارة with
، ويعود السياق إلى ما كان عليه من قبل. تحدد وثائق Hugo القيمة غير الفارغة على أنها false ، 0 ، وأي صفيف أو شريحة أو خريطة أو سلسلة صفرية الطول.
حاليًا ، لا يقوم قالب قائمتنا بعمل قوائم كثيرة على الإطلاق. قد يكون من المنطقي أن يعرض قالب القائمة بالفعل بعض صفحاته الفرعية بطريقة ما. يمنحنا هذا فرصة مثالية للحصول على أمثلة لبيانات التحكم في التدفق.
ربما نريد عرض بعض المحتويات المميزة في الجزء العلوي من صفحتنا. يمكن أن يكون هذا أي جزء من المحتوى - منشور مدونة أو مقالة مساعدة أو وصفة ، على سبيل المثال. في الوقت الحالي ، لنفترض أن موقع مثال البرج الخاص بنا يحتوي على بعض الصفحات التي تسلط الضوء على ميزاته ، وحالات الاستخدام ، وصفحة المساعدة ، وصفحة المدونة ، وصفحة "النظام الأساسي للتعلم". كل هذه موجودة في content/
الدليل. نقوم بتكوين أي جزء من المحتوى يتم تمييزه عن طريق إضافة حقل في ملف المحتوى لصفحتنا الرئيسية ، content/_index.md
. يُشار إلى الصفحة من خلال مسارها ، بافتراض أن دليل المحتوى هو الجذر ، على النحو التالي:
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days without limitations featured: /features.md ... --- ...
بعد ذلك ، يجب تعديل قالب قائمتنا لعرض هذا الجزء من المحتوى. يمتلك Hugo وظيفة نموذجية ، .GetPage
، والتي ستسمح لنا بالإشارة إلى كائنات الصفحة بخلاف الكائن الذي نعرضه حاليًا. أذكر كيف السياق ، .
، كان مرتبطًا في البداية بكائن يمثل الصفحة التي يتم عرضها؟ باستخدام .GetPage
و with
، يمكننا إعادة ربط السياق مؤقتًا بصفحة أخرى ، بالإشارة إلى حقول تلك الصفحة عند عرض المحتوى المميز لدينا:
<nav> ... </nav> <section class="featured"> <div class="container"> {{ with .GetPage .Params.featured }} <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} </div> </section>
هنا ، تشير {{ .Title }}
{ end
{{ .Summary }}
with
{{ .Permalink }}
.
بالإضافة إلى وجود جزء مميز من المحتوى ، دعنا ندرج المزيد من أجزاء المحتوى في الأسفل. تمامًا مثل المحتوى المميز ، سيتم تحديد أجزاء المحتوى المدرجة في content/_index.md
، ملف المحتوى لصفحتنا الرئيسية. سنضيف قائمة بمسارات المحتوى إلى موضوعنا الأمامي مثل هذا (في هذه الحالة نحدد أيضًا عنوان القسم):
--- ... listing_headline: Featured Pages listing: - /help.md - /use-cases.md - /blog/_index.md - /learn.md ---
السبب وراء احتواء صفحة المدونة على دليل خاص بها وملف _index.md
هو أن المدونة ستحتوي على صفحات فرعية خاصة بها - منشورات المدونة.
لعرض هذه القائمة في نموذجنا ، سنستخدم range
. ليس من المستغرب أن تتكرر هذه العبارة على قائمة ، لكنها ستعيد أيضًا ربط السياق بكل عنصر من عناصر القائمة بدوره. هذا مناسب جدًا لقائمة المحتوى الخاصة بنا.
لاحظ أنه من منظور Hugo ، فإن "القائمة" تحتوي فقط على بعض السلاسل. لكل تكرار لحلقة "النطاق" ، سيتم ربط السياق بأحد هذه السلاسل . للوصول إلى كائن الصفحة الفعلي ، نوفر سلسلة مساره (الآن قيمة .
) كوسيطة لـ .GetPage
. بعد ذلك ، سنستخدم تعليمة with
مرة أخرى لإعادة ربط السياق بكائن الصفحة المدرج بدلاً من سلسلة المسار الخاصة به. الآن ، من السهل عرض محتوى كل صفحة مدرجة على حدة:
<aside> ... </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} {{ end }} </div> </div> </section>
إليك ما يبدو عليه الموقع في هذه المرحلة:
لكن انتظر ، هناك شيء غريب في القالب أعلاه - بدلاً من استدعاء .GetPage
، نحن $.GetPage
. هل يمكنك تخمين لماذا لا يعمل .GetPage
؟
تدوين .GetPage
يشير إلى أن دالة GetPage
هي أسلوب السياق الحالي. في الواقع ، في السياق الافتراضي ، توجد مثل هذه الطريقة ، لكننا قمنا للتو بتغيير السياق ! عندما نطلق على .GetPage
، يكون السياق مرتبطًا بسلسلة لا تحتوي على هذه الطريقة. الطريقة التي نتعامل بها مع هذا هو موضوع القسم التالي.
السياق العالمي
كما رأينا أعلاه ، هناك حالات تم فيها تغيير السياق ، لكننا ما زلنا نرغب في الوصول إلى السياق الأصلي. هنا ، لأننا نريد استدعاء طريقة موجودة في السياق الأصلي - هناك موقف شائع آخر عندما نريد الوصول إلى بعض خصائص الصفحة الرئيسية التي يتم عرضها. لا مشكلة ، هناك طريقة سهلة للقيام بذلك.
في نموذج Hugo ، يشير $
، المعروف باسم السياق العام ، إلى القيمة الأصلية للسياق - السياق كما كان عندما بدأت معالجة النموذج. في القسم السابق ، تم استخدامه لاستدعاء طريقة .GetPage
على الرغم من أننا قمنا بإعادة السياق إلى سلسلة. الآن ، سنستخدمه أيضًا للوصول إلى حقل الصفحة التي يتم عرضها.
في بداية هذه المقالة ، ذكرت أن قالب قائمتنا قابل لإعادة الاستخدام. حتى الآن ، استخدمناها فقط للصفحة الرئيسية ، وعرض ملف محتوى موجود في content/_index.md
. في نموذج المستودع ، يوجد ملف محتوى آخر سيتم تقديمه باستخدام هذا النموذج: content/blog/_index.md
. هذه صفحة فهرس للمدونة ، تمامًا مثل الصفحة الرئيسية ، فإنها تعرض جزءًا مميزًا من المحتوى وتسرد المزيد - منشورات المدونة ، في هذه الحالة.
الآن ، لنفترض أننا نريد إظهار المحتوى المدرج بشكل مختلف قليلاً على الصفحة الرئيسية - لا يكفي لضمان نموذج منفصل ، ولكن يمكننا القيام به باستخدام بيان شرطي في القالب نفسه. كمثال ، سنعرض المحتوى المدرج في شبكة من عمودين ، على عكس قائمة العمود الواحد ، إذا اكتشفنا أننا نعرض الصفحة الرئيسية.
يأتي Hugo مع طريقة الصفحة ، .IsHome
، والتي توفر بالضبط الوظائف التي نحتاجها. سنتعامل مع التغيير الفعلي في العرض التقديمي عن طريق إضافة فصل دراسي إلى الأجزاء الفردية من المحتوى عندما نجد أننا في الصفحة الرئيسية ، مما يتيح لملف CSS الخاص بنا القيام بالباقي.
يمكننا بالطبع إضافة الفصل إلى عنصر الجسم أو بعض العناصر المحتوية بدلاً من ذلك ، لكن هذا لن يتيح عرضًا جيدًا للسياق العالمي. بحلول الوقت الذي نكتب فيه HTML للمحتوى المدرج ، .
يشير إلى الصفحة المدرجة ، ولكن يجب IsHome
في الصفحة الرئيسية التي يتم عرضها. يأتي السياق العالمي لإنقاذنا:
<section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} <article{{ if $.IsHome }} class="home"{{ end }}> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} {{ end }} </div> </div> </section>
يبدو فهرس المدونة تمامًا كما فعلت صفحتنا الرئيسية ، وإن كان بمحتوى مختلف:
... لكن صفحتنا الرئيسية تعرض الآن محتواها المميز في شبكة:
قوالب جزئية
عند إنشاء موقع ويب حقيقي ، يصبح من المفيد بسرعة تقسيم القوالب الخاصة بك إلى أجزاء. ربما تريد إعادة استخدام جزء معين من القالب ، أو ربما تريد فقط تقسيم قالب ضخم غير عملي إلى أجزاء متماسكة. لهذا الغرض ، فإن قوالب Hugo الجزئية هي السبيل للذهاب.
من منظور السياق ، الشيء المهم هنا هو أنه عندما نقوم بتضمين قالب جزئي ، فإننا نمرره صراحةً السياق الذي نريد إتاحته له. من الممارسات الشائعة أن تمر في السياق كما هو عندما يتم تضمين الجزئية ، مثل هذا: {{ partial "my/partial.html" . }}
{{ partial "my/partial.html" . }}
. إذا كانت النقطة هنا تشير إلى الصفحة التي يتم عرضها ، فهذا ما سيتم تمريره إلى الجزء ؛ إذا كان السياق قد ارتد إلى شيء آخر ، فهذا هو ما تم نقله.
يمكنك بالطبع إعادة إنشاء السياق في قوالب جزئية كما هو الحال في النماذج العادية. في هذه الحالة ، يشير السياق العام $
، إلى السياق الأصلي الذي تم تمريره إلى الجزء الجزئي ، وليس الصفحة الرئيسية التي يتم عرضها (ما لم يكن هذا هو ما تم تمريره).
إذا أردنا أن يكون للقالب الجزئي حق الوصول إلى جزء معين من البيانات ، فقد نواجه مشاكل إذا مررنا هذا فقط إلى الجزء الجزئي. هل تذكر مشكلتنا مسبقًا في الوصول إلى طرق الصفحة بعد إعادة ربط السياق؟ ينطبق الأمر نفسه على الأجزاء الجزئية ، لكن في هذه الحالة لا يمكن للسياق العام مساعدتنا - إذا مررنا ، على سبيل المثال ، سلسلة إلى قالب جزئي ، فإن السياق العام في الجزء سيشير إلى تلك السلسلة ، وقد فزنا لن تكون قادرًا على استدعاء الأساليب المحددة في سياق الصفحة.
يكمن حل هذه المشكلة في تمرير أكثر من جزء من البيانات عند تضمين الجزء الجزئي. ومع ذلك ، يُسمح لنا فقط بتقديم وسيطة واحدة للمكالمة الجزئية. ومع ذلك ، يمكننا أن نجعل هذه الوسيطة نوع بيانات مركَّبًا ، عادةً خريطة (تُعرف باسم القاموس أو التجزئة في لغات البرمجة الأخرى).
في هذه الخريطة ، يمكننا ، على سبيل المثال ، تعيين مفتاح Page
على كائن الصفحة الحالية ، جنبًا إلى جنب مع مفاتيح أخرى لأية بيانات مخصصة لتمريرها. سيكون كائن الصفحة .Page
على هيئة. يتم الوصول إلى قيم الخريطة بالمثل. يتم إنشاء الخريطة باستخدام وظيفة dict
template ، والتي تأخذ عددًا زوجيًا من الوسائط ، ويتم تفسيرها بالتناوب على أنها مفتاح وقيمته ومفتاح وقيمته وما إلى ذلك.
في النموذج الخاص بنا ، دعنا ننقل الكود الخاص بالمحتوى المميز والمدرج إلى أجزاء. بالنسبة للمحتوى المميز ، يكفي تمرير كائن الصفحة المميز. ومع ذلك ، يحتاج المحتوى المدرج إلى الوصول إلى طريقة .IsHome
بالإضافة إلى المحتوى المُدرج المعين الذي يتم عرضه. كما ذكرنا سابقًا ، بينما يتوفر .IsHome
في كائن الصفحة للصفحة المدرجة أيضًا ، لن يمنحنا ذلك الإجابة الصحيحة - نريد معرفة ما إذا كانت الصفحة الرئيسية التي يتم عرضها هي الصفحة الرئيسية.
يمكننا بدلاً من ذلك تمرير مجموعة منطقية إلى نتيجة استدعاء .IsHome
، ولكن ربما يحتاج الجزئي إلى الوصول إلى طرق أخرى للصفحة في المستقبل - دعنا ننتقل إلى تمرير كائن الصفحة الرئيسية بالإضافة إلى كائن الصفحة المدرج. في مثالنا ، تم العثور على الصفحة الرئيسية في $
والصفحة المدرجة في .
. لذلك ، في الخريطة التي تم تمريرها إلى الجزء listed
، تحصل Page
الرئيسية على القيمة $
بينما يحصل المفتاح "المدرج" على القيمة .
. هذا هو النموذج الرئيسي المحدث:
<body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> <li><a href="/blog/">Blog</a></li> </ul> </nav> <section class="featured"> <div class="container"> {{ with .GetPage .Params.featured }} {{ partial "partials/featured.html" . }} {{ end }} </div> </section> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ partial "partials/listed.html" (dict "Page" $ "Listed" .) }} {{ end }} {{ end }} </div> </div> </section> </body>
لا يتغير محتوى الجزء "المميز" مقارنةً بالوقت الذي كان جزءًا من نموذج القائمة:
<article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article>
ومع ذلك ، يعكس الجزء الخاص بنا للمحتوى المدرج حقيقة أن كائن الصفحة الأصلي موجود الآن في. Page بينما تم العثور على جزء المحتوى .Listed
.Page
<article{{ if .Page.IsHome }} class="home"{{ end }}> <h2>{{ .Listed.Title }}</h2> {{ .Listed.Summary }} <p><a href="{{ .Listed.Permalink }}">Read more →</a></p> </article>
يوفر Hugo أيضًا وظيفة القالب الأساسي التي تتيح لك توسيع قالب أساسي مشترك ، بدلاً من تضمين قوالب فرعية. في هذه الحالة ، يعمل السياق بشكل مشابه: عند توسيع قالب أساسي ، فإنك توفر البيانات التي ستشكل السياق الأصلي في هذا القالب.
المتغيرات المخصصة
من الممكن أيضًا تعيين وإعادة تعيين المتغيرات المخصصة الخاصة بك في قالب Hugo. ستكون متاحة في القالب حيث تم الإعلان عنها ، لكنها لن تشق طريقها إلى أي أجزاء أو قوالب أساسية ما لم نمررها صراحة. المتغير المخصص المُعلن داخل "block" مثل المتغير المحدد بواسطة عبارة if
سيكون متاحًا فقط داخل تلك الكتلة - إذا أردنا الرجوع إليه خارج الكتلة ، نحتاج إلى الإعلان عنه خارج الكتلة ، ثم تعديله داخل الكتلة منع كما هو مطلوب.
المتغيرات المخصصة لها أسماء مسبوقة بعلامة الدولار ( $
). للإعلان عن متغير وإعطائه قيمة في نفس الوقت ، استخدم عامل التشغيل :=
. تستخدم التخصيصات اللاحقة للمتغير عامل التشغيل =
(بدون النقطتين). لا يمكن إسناد متغير إلى قبل التصريح عنه ، ولا يمكن التصريح عنه دون إعطائه قيمة.
حالة استخدام واحدة للمتغيرات المخصصة هي تبسيط استدعاءات الوظائف الطويلة عن طريق تعيين بعض النتائج الوسيطة لمتغير مسمى بشكل مناسب. على سبيل المثال ، يمكننا إسناد كائن الصفحة المميز إلى متغير باسم $featured
ثم توفير هذا المتغير لتعليمة with
. يمكننا أيضًا وضع البيانات لتزويد الجزء "المُدرج" في متغير وإعطاء ذلك للمكالمة الجزئية.
إليك الشكل الذي سيبدو عليه نموذجنا مع هذه التغييرات:
<section class="featured"> <div class="container"> {{ $featured := .GetPage .Params.featured }} {{ with $featured }} {{ partial "partials/featured.html" . }} {{ end }} </div> </section> <section class="content"> ... </section> <aside> ... </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ $context := (dict "Page" $ "Listed" .) }} {{ partial "partials/listed.html" $context }} {{ end }} {{ end }} </div> </div> </section>
بناءً على تجربتي مع Hugo ، أوصي باستخدام المتغيرات المخصصة بحرية بمجرد محاولتك تنفيذ بعض المنطق المتضمن في قالب ما. في حين أنه من الطبيعي أن تحاول الإبقاء على شفرتك موجزة ، إلا أن هذا قد يجعل الأمور أقل وضوحًا بسهولة مما قد تكون عليه ، مما يتسبب في إرباكك أنت والآخرين.
بدلاً من ذلك ، استخدم متغيرات مسماة وصفية لكل خطوة ولا تقلق بشأن استخدام سطرين (أو ثلاثة أو أربعة ، إلخ) حيث سيفعل المرء.
.خدش
أخيرًا ، دعنا .Scratch
آلية الخدش. في الإصدارات السابقة من Hugo ، كان من الممكن تعيين المتغيرات المخصصة لمرة واحدة فقط ؛ لم يكن من الممكن إعادة تعريف متغير مخصص. في الوقت الحاضر ، يمكن إعادة تعريف المتغيرات المخصصة ، مما يجعل .Scratch
أقل أهمية ، على الرغم من أنه لا يزال له استخداماته.
باختصار ، تعد .Scratch
منطقة خدش تتيح لك تعيين المتغيرات الخاصة بك وتعديلها ، مثل المتغيرات المخصصة. على عكس المتغيرات المخصصة ، ينتمي .Scratch
إلى سياق الصفحة ، لذا فإن تمرير هذا السياق إلى جزء جزئي ، على سبيل المثال ، سيجلب متغيرات الخدش معه تلقائيًا.
يمكنك تعيين واسترداد المتغيرات على .Scratch
عن طريق استدعاء أساليبها Set
and Get
. هناك طرق أكثر من هذه ، على سبيل المثال لإعداد وتحديث أنواع البيانات المركبة ، ولكن هذين النوعين سيكونان كافيين لاحتياجاتنا هنا. تأخذ Set
معلمتين : المفتاح وقيمة البيانات التي تريد تعيينها. Get
واحد يأخذ فقط: مفتاح البيانات التي تريد استردادها.
في وقت سابق ، استخدمنا dict
لإنشاء هيكل بيانات خريطة لتمرير أجزاء متعددة من البيانات إلى جزء. تم إجراء ذلك حتى يتمكن الجزء الخاص بصفحة مدرجة من الوصول إلى كل من سياق الصفحة الأصلي وكائن الصفحة المحدد المدرج. استخدام .Scratch
ليس بالضرورة أفضل أو أسوأ طريقة للقيام بذلك - أيهما أفضل قد يعتمد على الموقف.
دعونا نرى كيف سيبدو قالب قائمتنا باستخدام .Scratch
بدلاً من dict
لتمرير البيانات إلى الجزء. نسمي $.Scratch.Get
(مرة أخرى باستخدام السياق العام) لتعيين متغير التسويد "مدرج" إلى .
- في هذه الحالة ، كائن الصفحة المدرج. ثم نقوم بتمرير كائن الصفحة فقط ، $
، إلى الجزء. ستتبع متغيرات الخدش تلقائيًا.
<section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ $.Scratch.Set "listed" . }} {{ partial "partials/listed.html" $ }} {{ end }} {{ end }} </div> </div> </section>
قد يتطلب ذلك بعض التعديلات على ملف listed.html
الجزئي المدرج أيضًا - حيث أصبح سياق الصفحة الأصلي متاحًا الآن باسم "النقطة" بينما يتم استرداد الصفحة المدرجة من كائن .Scratch
. سنستخدم متغيرًا مخصصًا لتبسيط الوصول إلى الصفحة المدرجة:
<article{{ if .IsHome }} class="home"{{ end }}> {{ $listed := .Scratch.Get "listed" }} <h2>{{ $listed.Title }}</h2> {{ $listed.Summary }} <p><a href="{{ $listed.Permalink }}">Read more →</a></p> </article>
إحدى الحجج لفعل الأشياء بهذه الطريقة هي الاتساق. باستخدام .Scratch
، يمكنك أن تجعل من المعتاد دائمًا تمرير كائن الصفحة الحالية إلى أي جزء ، مع إضافة أي بيانات إضافية كمتغيرات خدش. ثم ، عندما تكتب أو تعدل جزئياتك ، فأنت تعرف ذلك .
هو كائن صفحة. بالطبع ، يمكنك إنشاء اصطلاح لنفسك باستخدام الخريطة التي تم تمريرها أيضًا: الإرسال دائمًا على طول كائن الصفحة مثل .Page
، على سبيل المثال.
خاتمة
عندما يتعلق الأمر بالسياق والبيانات ، فإن منشئ الموقع الثابت يجلب الفوائد والقيود. من ناحية أخرى ، قد تكون العملية غير الفعالة للغاية عند إجرائها لكل زيارة للصفحة جيدة تمامًا عند تشغيلها مرة واحدة فقط أثناء تجميع الصفحة. من ناحية أخرى ، قد يفاجئك عدد المرات التي قد يكون من المفيد الوصول فيها إلى جزء من طلب الشبكة حتى على موقع ساكن في الغالب.
للتعامل مع معلمات سلسلة الاستعلام ، على سبيل المثال ، في موقع ثابت ، يجب عليك اللجوء إلى JavaScript أو بعض الحلول الاحتكارية مثل عمليات إعادة توجيه Netlify. النقطة المهمة هنا هي أنه في حين أن الانتقال من موقع ديناميكي إلى موقع ثابت بسيط من الناحية النظرية ، إلا أنه يأخذ تحولًا في العقلية. في البداية ، من السهل أن تتراجع عن عاداتك القديمة ، لكن الممارسة ستجعلها مثالية.
مع ذلك ، نختتم نظرتنا إلى إدارة البيانات في منشئ موقع Hugo الثابت. Even though we focused only on a narrow sector of its functionality, there are certainly things we didn't cover that could have been included. Nevertheless, I hope this article gave you some added insight into how data flows from content files, to templates, to subtemplates and how it can be modified along the way.
Note : If you already have some Hugo experience, we have a nice resource for you, quite appropriately residing on our aforementioned, Hugo-driven “Learn” site! When you just need to check the order of the arguments to the replaceRE
function, how to retrieve the next page in a section, or what the “expiration date” front matter field is called, a cheat sheet comes in handy. We've put together just such a reference, so download a Hugo cheat sheet, in a package also featuring a host of other cheat sheets on everything from Git to the Visual Studio Code editor.
قراءة متعمقة
If you're looking for more information on Hugo, here are some nice resources:
- The official Hugo documentation is always a good place to start!
- A great series of in-depth posts on Hugo on Regis Philibert's blog.