يستخدم Discourse محرك Markdown يسمى Markdown-it.
إليك بعض ملاحظات المطورين التي ستساعدك إما في إصلاح الأخطاء في النواة أو إنشاء المكونات الإضافية الجديدة.
الأساسيات
يحتوي Discourse على عدد قليل فقط من المساعدين فوق المحرك، لذا فإن الغالبية العظمى من التعلم الذي يجب القيام به هو فهم Markdown It.
يحتوي دليل docs directory على التوثيق الحالي.
أوصي بشدة بقراءة:
-
وثيقة architecture document لفهم كيفية عمل المحرك على مستوى عالٍ.
-
Development لإرشادات التطوير الأساسية
-
API documentation للحصول على مرجع مفصل للغاية
-
وأخيرًا، source code الموثق جيدًا والواضح.
أثناء تطوير امتدادات للمحرك، عادةً ما أفتح محررًا ثانيًا وألقي نظرة على القواعد الموجودة. يتكون المحرك من قائمة طويلة من القواعد، وكل قاعدة موجودة في ملف مخصص يسهل تتبعه إلى حد ما.
إذا كنت أعمل على قاعدة داخلية (inline rule)، فسأفكر في أي قاعدة داخلية موجودة تعمل بشكل مشابه لها وأبني عملي عليها.
مع الأخذ في الاعتبار، يمكنك أحيانًا اكتشاف كيفية الحصول على الوظيفة المطلوبة بمجرد تغيير أداة عرض (renderer)، وهو ما يكون أسهل عادةً.
كيف يتم هيكلة الامتداد؟
عند تهيئة محرك Markdown، فإنه يبحث في جميع الوحدات (modules).
إذا كانت أي وحدة تسمى /discourse-markdown\/|markdown-it\// (مما يعني أنها موجودة في دليل discourse-markdown أو markdown-it) فسيتم اعتبارها مرشحة للتهيئة.
إذا كانت الوحدة تُصدر (exports) دالة تسمى 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 المخصصة. قاعدة مستوى داخلي (inline) وقاعدة مستوى كتلة (block).
القواعد الداخلية لـ 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"} -
الرمز (token) الذي يبدأ الداخلي
-
الرمز الذي ينهي الداخلي
-
محتوى الداخلي لـ 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.