Dans le système que je construis, j'ai besoin de pouvoir mentionner les membres d'Umbraco dans le texte du site Web. Pour ce faire, je dois créer une extension pour l'éditeur de texte enrichi d'Umbraco : TinyMCE.
En tant qu'éditeur de contenu, je souhaite identifier les membres dans un message ou un article afin qu'ils soient informés du nouveau contenu les concernant.
J'ai regardé des implémentations similaires, comme dans Slack ou sur X. Slack utilise une balise html spéciale pour les mentions lors de l'écriture, mais envoie ensuite les données au backend avec un jeton avec un format spécifique. J'ai décidé d'adopter une approche similaire, mais pour l'instant, oubliez l'étape de traduction. Dans le contenu, une mention ressemblera à ceci :
@D_Inventor
Avant de commencer à construire, je cherchais des moyens de me connecter à TinyMCE à Umbraco. C'est l'une des choses que j'aime le moins étendre dans le backoffice d'Umbraco. Cependant, je l'ai déjà fait et j'ai trouvé plus facile d'étendre l'éditeur si je crée un décorateur sur tinyMceService d'Umbraco dans AngularJS. Dans la documentation de TinyMCE, j'ai trouvé une fonctionnalité appelée « autoCompleters », qui faisait exactement ce dont j'avais besoin, ce qui me permettait de me connecter à l'éditeur. Mon code initial (sans aucun test encore) ressemblait à ceci :
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; }
J'utilise vite et typescript dans ce projet, mais je n'ai installé aucun type pour TinyMCE. Pour l'instant, je vais garder le any et essayer d'éviter TinyMCE autant que possible.
J'ai décidé d'utiliser Jest pour les tests. J'ai trouvé un démarrage facile et j'ai rapidement réussi à faire fonctionner quelque chose.
✅ Succès |
---|
J'ai appris un nouvel outil pour les tests unitaires dans le code frontend. J'ai appliqué avec succès l'outil pour écrire un frontend avec des tests unitaires |
J'ai écrit mon premier test :
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); }); });
J'ai été quelque peu surpris par la rigueur du compilateur dactylographié. Travailler par étapes ici signifiait vraiment ne rien ajouter que vous n’utilisiez pas encore. Par exemple, je voulais ajouter une référence à "l'interface utilisateur", parce que je savais que j'allais l'utiliser plus tard, mais je ne pouvais pas réellement compiler le MentionsManager avant d'avoir utilisé tout ce que j'avais mis dans le constructeur.
Après quelques tours de rouge, vert et refactor, je me suis retrouvé avec ces tests :
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); }); });
Avec cette logique en place, je pourrais récupérer les mentions de n'importe quelle source et les afficher dans le RTE via le hook « fetch ».
J'ai utilisé la même approche pour créer une méthode « pick » pour prendre le membre sélectionné et insérer la mention dans l'éditeur. Voici le code avec lequel je me suis retrouvé :
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); } }
❓ Incertitude |
---|
L'interface Range est un type intégré qui est vraiment difficile à simuler et cette interface divulgue un détail d'implémentation dans ma logique métier. J'ai l'impression qu'il aurait pu y avoir une meilleure façon de procéder. |
Dans l'ensemble, je pense que je me suis retrouvé avec un code simple et facile à modifier. Il y a encore des parties de ce code que je n'aime pas vraiment. Je voulais que la logique métier pilote l'interface utilisateur, mais le code ressemblait davantage à un simple magasin qui effectue également un seul appel à l'interface utilisateur. Je me demande si je pourrais envelopper plus fortement l'interface utilisateur pour mieux utiliser le gestionnaire.
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3