إعادة تغليف إضافة markdown-it كإضافة Discourse

markdown.it وهو محرك CommonMark الذي يستخدمه Discourse، لديه مجموعة واسعة من المكونات الإضافية

مراسي الرؤوس، وقوائم التعريفات، والسهام الذكية، والقائمة تطول وتطول.

أولاً تحذير

:warning: يُقصد بـ CommonMark أن يكون… شائعًا. كلما ابتعدت عن المواصفات، أصبحت Markdown الخاصة بك أقل شيوعًا. قد يجعل هذا من الصعب النقل إلى حلول أخرى، وإذا لم تتوخ الحذر، فقد يتسبب في تناقضات داخلية في التحليل. قبل إعادة تجميع أي شيء، تأكد من الإجابة على السؤال “هل أريد حقًا إعادة تجميع هذا؟”

لقد انتهيت للتو من إعادة تجميع Discourse Footnote ولدي بعض الدروس لمشاركتها حول كيفية القيام بذلك بشكل صحيح.

خطوات للكسول

إذا كنت كسولًا وتريد فقط البدء، فإن أسهل طريقة هي ببساطة استنساخ الحواشي السفلية وتبديل الملفات وأسماء المتغيرات. لقد كنت حريصًا جدًا على التأكد من أنه يتبع أفضل الممارسات، لذلك يجب أن يكون لديك مثال جيد.

الحركات الافتتاحية، إعادة تجميع بسيطة

مما أستطيع أن أراه، يتم شحن غالبية المكونات الإضافية لـ markdown.it كملفات JavaScript عادية. في كثير من الحالات، يتم شحن المكونات الإضافية كملف js واحد فقط، مثل هذا: markdown-it-mark.js.

من الناحية المثالية، تريد ترك الأصل سليمًا، وهذا يعني أنه يمكنك ببساطة نسخ نسخة محدثة من الملف إلى المكون الإضافي الخاص بك دون الحاجة إلى العبث بالمكون الإضافي الحالي.

المشكلة الأولى التي ستصادفها هي أنه يتعين عليك تعليم المكون الإضافي الخاص بك تحميل هذا JavaScript على الخادم لأن محرك Markdown يعمل على الخادم أيضًا. للقيام بذلك، يمكنك ببساطة نسخ الملف كما هو إلى assets/javascripts/vendor/original-plugin.js ثم في ملف plugin.rb الخاص بك ستضيف:

# هذا يعلم محرك markdown الخاص بنا تحميل ملف js العادي الخاص بك
register_asset "javascripts/vendor/original-plugin.js", :vendored_pretty_text

بمجرد تضمين نص المكون الإضافي الفعلي، تحتاج إلى تعليم خط الأنابيب الخاص بنا كيفية تحميله وتهيئته:

أنشئ ملفًا يسمى assets/javascripts/lib/discourse-markdown/your-extension.js

سيتم تحميل هذا الملف تلقائيًا لأنه ينتهي بـ .js وفي دليل discourse-markdown.

مثال بسيط يمكن أن يكون:

export function setup(helper) {
  // هذا يسمح لك فقط بتحميل الإضافة الخاصة بك إذا كان إعداد الموقع ممكّنًا
  helper.registerOptions((opts, siteSettings) => {
    opts.features["your-extension"] = !!siteSettings.enable_my_plugin;
  });

  // ضع في القائمة البيضاء أي سمات تحتاج إلى دعمها،
  // وإلا فسيقوم المنظف لدينا بإزالتها
  helper.whiteList(["div.amazingness"]);

  // يمكنك أيضًا القيام بأشياء رائعة مثل هذا
  helper.whiteList({
    custom(tag, name, value) {
      if ((tag === "a" || tag === "li") && name === "id") {
        return !!value.match(/^fn(ref)?\d+$/);
      }
    },
  });

  // أخيرًا، هذا هو السحر الذي ستستخدمه لتسجيل الامتداد في
  // خط الأنابيب الخاص بنا. whateverGlobal هو اسم المتغير العام الذي يكشف عنه المكون الإضافي
  // يأخذ متغيرًا واحدًا (md) يتم استخدامه بعد ذلك لتعديل خط الأنابيب
  helper.registerPlugin(window.whateverGlobal);
}

كن دائمًا في حالة اختبار

أداة bin/rake autospec الخاصة بـ Discourse تدرك المكونات الإضافية :innocent:

هذا يعني أنه عند إضافة الملف spec/pretty_text_spec.rb في كل مرة تحفظه فيها، سيتم تشغيل ملف اختبار المكون الإضافي.

أنا أستخدم هذا بشكل مكثف لأنه يجعل العمل أسرع بكثير.

لنفترض أنك أضفت مكونًا إضافيًا يغير كل رقم في منشور إلى 8 دوائر، يمكنك تسميته discourse-magic-8-ball.

إليك كيف سأقوم بهيكلة الاختبارات:

require "rails_helper"

describe PrettyText do
  it "can be disabled" do
    SiteSetting.enable_magic_8_ball = false

    markdown = <<-MD
      1 thing
    MD

    html = <<-HTML
      <p>1 thing</p>
    HTML

    cooked = PrettyText.cook markdown.strip
    expect(cooked).to eq(html.strip)
  end

  it "supports magic 8 ball" do
    markdown = <<-MD
      1 thing
    MD

    html = <<-HTML
      <p>8 circle thing</p>
    HTML

    cooked = PrettyText.cook markdown.strip
    expect(cooked).to eq(html.strip)
  end
end

قد تحتاج إلى “تزيين المنشورات”

في بعض الحالات، تعمل المكونات الإضافية بشكل أفضل عندما تضيف ميزات “ديناميكية” إضافية إلى منشوراتك. من الأمثلة على ذلك المكون الإضافي poll أو المكون الإضافي footnotes الذي يضيف “…” يعرض تلميحًا مرئيًا ديناميكيًا.

إذا كنت بحاجة إلى “تزيين” المنشورات، أضف assets/javascripts/api-initializers/your-initializer.js

import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
  const siteSettings = api.container.lookup("service:site-settings");
  if (!siteSettings.enable_magic_8_ball) {
    return;
  }

  api.decorateCookedElement((elem) => {
    // سحرك المذهل يذهب هنا
  });
});

قد تحتاج إلى “معالجة لاحقة” للمنشورات

في بعض الحالات، قد تحتاج إلى “معالجة لاحقة” للمنشورات، ومحرك عرض markdown، بحكم تصميمه، لا يدرك معلومات معينة مثل، على سبيل المثال، post_id. في بعض الحالات، قد ترغب في الوصول من جانب الخادم إلى المنشور و HTML “المطبوخ”، يمكن أن يتيح لك ذلك القيام بأشياء مثل تشغيل مهام في الخلفية، أو مزامنة الحقول المخصصة، أو “تصحيح” HTML الذي تم إنشاؤه تلقائيًا.

بالنسبة للحواشي السفلية، احتجت إلى id مميز لكل حاشية سفلية، مما يعني أنني احتجت إلى الوصول إلى post_id، لذلك اضطررت إلى إجراء تغييرات على HTML في معالج المنشور (الذي يعمل في sidekiq)

للتوصيل، ستضيف ما يلي إلى ملف plugin.rb الخاص بك:

DiscourseEvent.on(:before_post_process_cooked) do |doc, post|
  doc.css("a.always-bing").each do |a|
    # هذا يجب أن يذهب دائمًا إلى بينج
    a["href"] = "https://bing.com"
  end
end

قد تحتاج إلى بعض CSS المخصص

إذا كنت تريد شحن CSS مخصص، فتأكد من تسجيل الملف في plugin.rb

أضف ملف css الخاص بك إلى assets/stylesheets/magic.scss ثم قم بتشغيل

register_asset "stylesheets/magic.scss"

تذكر أننا “نعيد تحميل” التغييرات حتى تتمكن من تعديل CSS المكون الإضافي الخاص بك ورؤية التغييرات أثناء التنقل في التطوير.

حظًا سعيدًا في مغامرات إعادة التجميع الخاصة بك :four_leaf_clover:


يتم التحكم في إصدار هذا المستند - اقترح تغييرات على github.

29 إعجابًا

أرى بعض أدوات superset التي توسع Markdown، مثل Quarkdown تبدو قوية جدًا.

هل هناك طريقة لاستبدال امتداد markdown-it بأدوات مثل Quarkdown؟