在我正在建立的系統中,我需要能夠在網站的文字中提及 Umbraco 成員。為此,我需要建立 Umbraco 富文本編輯器的擴充:TinyMCE。
作為內容編輯者,我想在訊息或文章中標記成員,以便他們收到有關他們的新內容的通知。
我研究了類似的實現,例如 Slack 或 X 上的實現。 Slack 在編寫過程中使用特殊的 html 標籤進行提及,然後使用特定格式的令牌將資料傳送到後端。我決定採取類似的方法,但現在忘記翻譯步驟。在內容中,提及將如下所示:
<mention user-id="1324" class="mceNonEditable">@D_Inventor</mention>
在開始建置之前,我一直在尋找連接 Umbraco 中的 TinyMCE 的方法。這是我最不喜歡在 Umbraco 後台擴展的事情之一。不過我之前已經這樣做過,我發現如果我在 AngularJS 中的 Umbraco 的tinyMceService 上建立一個裝飾器,擴充編輯器是最簡單的。在 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<string, unknown> ): Promise<IMceAutocompleteItem[]> // 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 類型。現在我會保留any並盡可能避免TinyMCE。
我決定使用 jest 來測試。我發現入門很簡單,很快就成功了。
✅ Success |
---|
I learned a new tool for unit testing in frontend code. I succesfully applied the tool to write a frontend with unit tests |
我寫了第一個測驗:
提及-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); }); });
Typescript 編譯器的嚴格性讓我有些驚訝。此處的分步操作實際上意味著不添加任何您尚未實際使用的內容。例如,我想新增對「UI」的引用,因為我知道稍後會使用它,但在使用建構函式中放入的所有內容之前,我實際上無法編譯 MentionsManager。
經過幾輪紅、綠和重構,我最終得到了這些測試:
提及-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); }); });
有了這個邏輯,我就可以從任何來源獲取提及並透過「fetch」掛鉤在 RTE 中顯示它們。
我使用相同的方法建立一個“pick”方法來獲取選定的成員並將提及插入到編輯器中。這是我最終得到的程式碼:
提及-manager.ts
export class MentionsManager { private mentions: IMention[] = []; constructor( private source: MentionsAPI, private ui: IMentionsUI ) {} async fetch(take: number, query?: string): Promise<IMention[]> { 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); } }
❓ Uncertainty |
---|
The Range interface is a built-in type that is really difficult to mock and this interface leaks an implementation detail into my business logic. I feel like there might've been a better way to do this. |
總的來說,我認為我最終得到了易於更改的簡單程式碼。這段程式碼中仍有一些部分我不太喜歡。我希望業務邏輯來驅動 UI,但程式碼最終更像是一個簡單的商店,它也對 UI 進行了一次呼叫。我想知道是否可以更牢固地包裝 UI,以便更好地利用管理器。
以上是透過實作學習 TDD:在 Umbraco 的富文本編輯器中標記成員的詳細內容。更多資訊請關注PHP中文網其他相關文章!