在我正在建構的系統中,我需要能夠在網站的文字中提及 Umbraco 會員。為此,我需要建立 Umbraco 富文本編輯器的擴充:TinyMCE。
作為內容編輯者,我想在訊息或文章中標記成員,以便他們收到有關其新內容的通知。
我研究了類似的實現,例如 Slack 或 X 上的實現。 Slack 在編寫過程中使用特殊的 html 標籤進行提及,然後使用特定格式的令牌將資料傳送到後端。我決定採取類似的方法,但現在忘記翻譯步驟。在內容中,提及將如下所示:
@D_Inventor
在開始建置之前,我一直在尋找連接 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): 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 類型。現在我將保留any並盡可能避免TinyMCE。
我決定使用 jest 進行測試。我發現了一個簡單的入門方法,很快就成功了。
✅ 成功 |
---|
我學習了一種用於前端程式碼單元測試的新工具。我成功地應用該工具編寫了帶有單元測試的前端 |
我寫了我的第一個測試:
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); }); });
打字稿編譯器的嚴格性讓我有些驚訝。此處的分步操作實際上意味著不添加任何您尚未實際使用的內容。例如,我想添加對“UI”的引用,因為我知道稍後會使用它,但在使用構造函數中放入的所有內容之前我無法實際編譯 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); }); });
有了這個邏輯,我可以從任何來源獲取提及並透過「fetch」掛鉤在 RTE 中顯示它們。
我使用相同的方法建立一個“pick”方法來獲取選定的成員並將提及插入到編輯器中。這是我最終得到的代碼:
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 介面是一種內建類型,確實很難模擬,而該介面將實現細節洩漏到我的業務邏輯中。我覺得可能有更好的方法來做到這一點。 |
總的來說,我認為我最終得到了易於更改的簡單程式碼。這段程式碼中仍有一些部分我不太喜歡。我希望業務邏輯來驅動 UI,但程式碼最終更像是一個簡單的商店,它也對 UI 進行了一次呼叫。我想知道是否可以更牢固地包裝 UI,以便更好地利用管理器。
免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。
Copyright© 2022 湘ICP备2022001581号-3