في النظام الذي أقوم بإنشائه، أحتاج إلى القدرة على ذكر أعضاء Umbraco في النص في موقع الويب. وللقيام بذلك، أحتاج إلى إنشاء امتداد لمحرر النصوص الغنية الخاص بشركة Umbraco: TinyMCE.
بصفتي محرر محتوى، أريد الإشارة إلى الأعضاء في رسالة أو مقالة حتى يتم إعلامهم بالمحتوى الجديد الخاص بهم.
لقد نظرت إلى تطبيقات مماثلة، كما هو الحال في Slack أو X. يستخدم Slack علامة html خاصة للإشارات أثناء الكتابة، ولكنه يرسل بعد ذلك البيانات إلى الواجهة الخلفية باستخدام رمز مميز بتنسيق محدد. قررت أن أتبع نهجًا مشابهًا، لكن في الوقت الحالي انسَ خطوة الترجمة. في المحتوى، ستبدو الإشارة كما يلي:
@D_Inventor
قبل أن أبدأ في البناء، كنت أبحث عن طرق للتواصل مع TinyMCE في Umbraco. يعد هذا أحد الأشياء الأقل تفضيلاً للتوسيع في مكتب Umbraco الخلفي. لقد قمت بذلك من قبل، ووجدت أنه من الأسهل توسيع المحرر إذا قمت بإنشاء مصمم ديكور على tinyMceService الخاص بـ Umbraco في AngularJS. في وثائق TinyMCE، وجدت ميزة تسمى "autoCompleters"، والتي فعلت ما أحتاجه بالضبط، لذلك كان هناك ربط في المحرر. بدا الكود الأولي الخاص بي (بدون أي اختبار حتى الآن) كما يلي:
rtedecorator.$inject = ["$delegate"]; export function rtedecorator($delegate: any) { const original = $delegate.initializeEditor; $delegate.initializeEditor = function (args: any) { original.apply($delegate, arguments); args.editor.contentStyles.push("mention { background-color: #f7f3c1; }"); args.editor.ui.registry.addAutocompleter("mentions", { trigger: "@", fetch: ( pattern: string, maxResults: number, _fetchOptions: Record): Promise // TODO: fetch from backend => Promise.resolve([{ type: "autocompleteitem", value: "1234", text: "D_Inventor" }]), onAction: (api: any, rng: Range, value: string): void => { // TODO: business logic api.hide(); }, }); }; return $delegate; }
أنا أستخدم vite وtypescript في هذا المشروع، لكن ليس لدي أي أنواع مثبتة لـ TinyMCE. في الوقت الحالي سأحتفظ بأي شيء وأحاول فقط تجنب TinyMCE قدر الإمكان.
قررت استخدام الدعابة للاختبار. لقد وجدت بداية سهلة وتمكنت بسرعة من إنجاز شيء ما.
✅ النجاح |
---|
تعلمت أداة جديدة لاختبار الوحدة في كود الواجهة الأمامية. لقد قمت بتطبيق الأداة بنجاح لكتابة واجهة أمامية باستخدام اختبارات الوحدة |
لقد كتبت أول اختبار لي:
mention-manager.test.ts
describe("MentionsManager.fetch", () => { let sut: MentionsManager; let items: IMention[]; beforeEach(() => { items = []; sut = new MentionsManager(); }); test("should be able to fetch one result", async () => { items.push({ userId: "1234", userName: "D_Inventor" }); const result = await sut.fetch(1); expect(result).toHaveLength(1); }); });
لقد فوجئت إلى حد ما بمدى صرامة مترجم الآلة الكاتبة. إن العمل بالخطوات هنا يعني حقًا عدم إضافة أي شيء لا تستخدمه فعليًا بعد. على سبيل المثال، أردت إضافة مرجع إلى "واجهة المستخدم"، لأنني كنت أعرف أنني سأستخدم ذلك لاحقًا، لكنني لم أتمكن فعليًا من تجميع MentionsManager حتى استخدمت كل ما أضعه في المُنشئ.
بعد بضع جولات من اللون الأحمر والأخضر وإعادة البناء، انتهى بي الأمر بهذه الاختبارات:
mention-manager.test.ts
describe("MentionsManager.fetch", () => { let sut: MentionsManager; let items: IMention[]; beforeEach(() => { items = []; sut = new MentionsManager(() => Promise.resolve(items)); }); test("should be able to fetch one result", async () => { items.push({ userId: "1234", userName: "D_Inventor" }); const result = await sut.fetch(1); expect(result).toHaveLength(1); }); test("should be able to fetch empty result", async () => { const result = await sut.fetch(1); expect(result).toHaveLength(0); }); test("should be able to fetch many results", async () => { items.push({ userId: "1324", userName: "D_Inventor" }, { userId: "3456", userName: "D_Inventor2" }); const result = await sut.fetch(2); expect(result).toHaveLength(2); }); test("should return empty list upon error", () => { const sut = new MentionsManager(() => { throw new Error("Something went wrong while fetching"); }, {} as IMentionsUI); return expect(sut.fetch(1)).resolves.toHaveLength(0); }); });
باستخدام هذا المنطق، يمكنني جلب الإشارات من أي مصدر وإظهارها في RTE من خلال خطاف "الجلب".
لقد استخدمت نفس الأسلوب لإنشاء طريقة "اختيار" لأخذ العضو المحدد وإدراج الإشارة في المحرر. هذا هو الكود الذي انتهيت به:
mention-manager.ts
export class MentionsManager { private mentions: IMention[] = []; constructor( private source: MentionsAPI, private ui: IMentionsUI ) {} async fetch(take: number, query?: string): Promise{ try { const result = await this.source(take, query); if (result.length === 0) return []; this.mentions = result; return result; } catch { return []; } } pick(id: string, location: Range): void { const mention = this.mentions.find((m) => m.userId === id); if (!mention) return; this.ui.insertMention(mention, location); } }
❓ عدم اليقين |
---|
واجهة Range هي نوع مدمج يصعب الاستهزاء به حقًا وتسرب هذه الواجهة تفاصيل التنفيذ إلى منطق عملي. أشعر أنه ربما كانت هناك طريقة أفضل للقيام بذلك. |
بشكل عام، أعتقد أنني انتهيت من الحصول على رمز بسيط يسهل تغييره. لا تزال هناك أجزاء من هذا الرمز لا أحبها حقًا. كنت أرغب في أن يقوم منطق الأعمال بقيادة واجهة المستخدم، ولكن انتهى الأمر بالرمز إلى حد كبير مثل متجر بسيط يقوم أيضًا بإجراء مكالمة واحدة لواجهة المستخدم. أتساءل عما إذا كان بإمكاني تغليف واجهة المستخدم بقوة أكبر للاستفادة بشكل أكبر من المدير.
تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.
Copyright© 2022 湘ICP备2022001581号-3