دليل المطور لإضافات Markdown

يستخدم Discourse محرك Markdown يسمى Markdown-it.

فيما يلي بعض ملاحظات المطور التي ستساعدك إما في إصلاح الأخطاء في النواة أو إنشاء إضافاتك الجديدة.

الأساسيات

يحتوي Discourse على عدد قليل من الأدوات المساعدة فوق المحرك، لذا فإن الغالبية العظمى من التعلم الذي يجب القيام به هو فهم Markdown It.

يحتوي دليل docs على التوثيق الحالي.

أوصي بشدة بقراءة:

  • وثيقة architecture لفهم كيفية عمل المحرك على مستوى عالٍ.

  • Development لإرشادات التطوير الأساسية

  • API documentation للحصول على مرجع مفصل للغاية

  • وأخيرًا، source code الموثق جيدًا والواضح.

أثناء تطويري ملحقات للمحرك، أعتاد فتح محرر ثانٍ ألقي فيه نظرة على القواعد الحالية. يتكون المحرك من قائمة طويلة من القواعد ويكون كل قاعدة في ملف مخصص يسهل تتبعه إلى حد ما.

إذا كنت أعمل على قاعدة داخلية، فسأفكر في القاعدة الداخلية الموجودة التي تعمل بشكل مشابه لها وأبني عملي عليها.

ضع في اعتبارك أنه يمكنك في بعض الأحيان الاكتفاء بتغيير عارض للحصول على الوظيفة المطلوبة، وهو ما يكون أسهل عادةً.

كيف يتم هيكلة الامتداد؟

عندما يتم تهيئة محرك markdown، فإنه يبحث في جميع الوحدات.

إذا كانت أي وحدة تسمى /discourse-markdown\/|markdown-it\// (مما يعني أنها موجودة في دليل discourse-markdown أو markdown-it) فستكون مرشحة للتهيئة.

إذا كانت الوحدة تُصدّر دالة تسمى setup فسيتم استدعاؤها بواسطة المحرك أثناء التهيئة.

بروتوكول الإعداد (setup protocol)

/my-plugins/assets/javascripts/discourse-markdown/awesome-extension.js

export function setup(helper) {
  // ... يذهب الكود الخاص بك هنا
}

تمنح طريقة setup إمكانية الوصول إلى كائن مساعد يمكن استخدامه للتهيئة. يحتوي هذا على الطرق والمتغيرات التالية:

  • bool markdownIt: يتم تعيين هذه الخاصية على true عند استخدام المحرك الجديد. للحصول على توافق صحيح مع الإصدارات السابقة، يجب عليك التحقق منها.

  • registerOptions(cb(opts,siteSettings,state)): يتم استدعاء الدالة المقدمة قبل تهيئة محرك markdown، ويمكنك استخدامها لتحديد ما إذا كنت ستقوم بتمكين المحرك أو تعطيله.

  • allowList([spec, ...]): تُستخدم هذه الطريقة للسماح بقائمة HTML مع أداة التنقية الخاصة بنا.

  • registerPlugin(func(md)): تُستخدم هذه الطريقة لتسجيل Markdown It plugin.

تجميع كل شيء معًا

function amazingMarkdownItInline(state, silent) {
   // امتداد Markdown It الداخلي القياسي يذهب هنا.
   return false;
}

export function setup(helper) {
   if(!helper.markdownIt) { return; }

   helper.registerOptions((opts,siteSettings)=>{
      opts.features.['my_extension'] = !!siteSettings.my_extension_enabled;
   });

   helper.allowList(['span.amazing', 'div.amazing']);

   helper.registerPlugin(md=>{
      md.inline.push('amazing', amazingMarkdownItInline);
   });
}

ملحقات خاصة بـ Discourse

BBCode

يحتوي Discourse على قاعدتين (rulers) يمكنك استخدامهما لعلامات BBCode المخصصة. قاعدة للمستوى الداخلي وقاعدة للمستوى الكتلي.

قواعد bbcode الداخلية هي تلك التي تعيش في فقرة داخلية مثل [b]bold[/b]

تُطبق قواعد المستوى الكتلي على أسطر متعددة من النص مثل:

[poll]
- option 1

- options 2
[/poll]

md.inline.bbcode.ruler يحتفظ بقائمة من القواعد الداخلية التي يتم تطبيقها بالترتيب.

md.block.bbcode.ruler يحتفظ بقائمة من القواعد على مستوى الكتلة

هناك العديد من الأمثلة للقواعد الداخلية في: bbcode-inline.js

Quotes والاستطلاعات هي أمثلة جيدة لقواعد bbcode الكتلي.

قواعد BBCode الداخلية

قواعد BBCode الداخلية هي كائن يحتوي على معلومات حول كيفية التعامل مع علامة.

على سبيل المثال:

md.inline.bbcode.ruler.push("underline", {
  tag: "u",
  wrap: "span.bbcode-u",
});

سيؤدي إلى

test [u]test[/u]

يتم تحويله إلى:

test <span>test</span>

يمكن للقواعد الداخلية إما أن تغلف النص أو تستبدله. عند التغليف، يمكنك أيضًا تمرير دالة لاكتساب مرونة إضافية.

md.inline.bbcode.ruler.push("url", {
  tag: "url",
  wrap: function (startToken, endToken, tagInfo, content) {
    const url = (tagInfo.attrs["_default"] || content).trim();

    if (simpleUrlRegex.test(url)) {
      startToken.type = "link_open";
      startToken.tag = "a";
      startToken.attrs = [
        ["href", url],
        ["data-bbcode", "true"],
      ];
      startToken.content = "";
      startToken.nesting = 1;

      endToken.type = "link_close";
      endToken.tag = "a";
      endToken.content = "";
      endToken.nesting = -1;
    } else {
      // فقط قم بإزالة علامة bbcode
      endToken.content = "";
      startToken.content = "";

      // حالة حافة، لا نريد أن يتم اكتشاف هذا كـ onebox إذا كان مرتبطًا تلقائيًا
      // هذا يضمن عدم إزالته
      startToken.type = "html_inline";
    }

    return false;
  },
});

توفر دالة التغليف إمكانية الوصول إلى:

  • tagInfo، وهو قاموس من المفاتيح/القيم المحددة عبر bbcode.

    [test=testing]{_default: "testing"}
    [test a=1]{a: "1"}

  • الرمز المميز الذي يبدأ العنصر الداخلي

  • الرمز المميز الذي ينهي العنصر الداخلي

  • محتوى العنصر الداخلي لـ bbcode

باستخدام هذه المعلومات، يمكنك التعامل مع جميع أنواع احتياجات التغليف.

قد ترغب أحيانًا في استبدال كتلة BBCode بأكملها، ولهذا يمكنك استخدام replace

md.inline.bbcode.ruler.push("code", {
  tag: "code",
  replace: function (state, tagInfo, content) {
    let token;
    token = state.push("code_inline", "code", 0);
    token.content = content;
    return true;
  },
});

في هذه الحالة، نستبدل كتلة [code]code block[code] بأكملها برمز مميز واحد code_inline.

قواعد BBCode الكتلي

تسمح لك قواعد bbcode الكتلي باستبدال كتلة كاملة. واجهات برمجة تطبيقات الكتلة هي نفسها للحالات البسيطة:

md.block.bbcode.ruler.push("happy", {
  tag: "happy",
  wrap: "div.happy",
});
[happy]
hello
[/happy]

سيصبح

<div>hello</div>

تحتوي دالة التغليف على واجهة برمجة تطبيقات مختلفة قليلاً لأنه لا توجد رموز تغليف.

md.block.bbcode.ruler.push("money", {
  tag: "money",
  wrap: function (token, tagInfo) {
    token.attrs = [["data-money", tagInfo.attrs["_default"]]];
    return true;
  },
});
[money=100]
**test**
[/money]

سيصبح

<div data-money="100">
  <b>test</b>
</div>

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

md.block.bbcode.ruler.push("ddiv", {
  tag: "ddiv",
  before: function (state, tagInfo) {
    state.push("div_open", "div", 1);
    state.push("div_open", "div", 1);
  },
  after: function (state) {
    state.push("div_close", "div", -1);
    state.push("div_close", "div", -1);
  },
});
[ddiv]
test
[/ddiv]

سيصبح

<div>
  <div>test</div>
</div>

معالجة استبدالات النص

يشحن Discourse بقاعدة أساسية خاصة إضافية لتطبيق التعبيرات العادية على النص.

md.core.textPostProcess.ruler

للاستخدام:

md.core.textPostProcess.ruler.push("onlyfastcars", {
  matcher: /(car)|(bus)/, // لا يتم دعم علامات regex
  onMatch: function (buffer, matches, state) {
    let token = new state.Token("text", "", 0);
    token.content = "fast " + matches[0];
    buffer.push(token);
  },
});
I like cars and buses

سيصبح

<p>I like fast cars and fast buses</p>

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

35 إعجابًا