En el sistema que estoy creando, necesito poder mencionar a los miembros de Umbraco en texto en el sitio web. Para hacer eso, necesito crear una extensión para el editor de texto enriquecido de Umbraco: TinyMCE.
Como editor de contenido, quiero etiquetar a los miembros en un mensaje o artículo para que reciban notificaciones sobre contenido nuevo sobre ellos.
Miré implementaciones similares, como en Slack o en X. Slack usa una etiqueta html especial para las menciones durante la escritura, pero luego envía los datos al backend con un token con un formato específico. Decidí adoptar un enfoque similar, pero por ahora olvídate del paso de traducción. En el contenido, una mención se verá así:
@D_Inventor
Antes de comenzar a construir, estaba buscando formas de conectarme a TinyMCE en Umbraco. Esta es una de las cosas que menos me gusta ampliar en el backoffice de Umbraco. Sin embargo, ya hice esto antes y me resultó más fácil ampliar el editor si creo un decorador en tinyMceService de Umbraco en AngularJS. En la documentación de TinyMCE, encontré una función llamada 'autoCompleters', que hacía exactamente lo que necesitaba, así que ahí estaba mi enlace al editor. Mi código inicial (sin ninguna prueba todavía) tenía este aspecto:
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; }
Estoy usando vite y mecanografiado en este proyecto, pero no tengo ningún tipo para TinyMCE instalado. Por ahora me quedaré con any y trataré de evitar TinyMCE tanto como sea posible.
Decidí usar Jest para realizar pruebas. Encontré un comienzo fácil y rápidamente logré que algo funcionara.
✅ Éxito |
---|
Aprendí una nueva herramienta para pruebas unitarias en código frontend. Apliqué con éxito la herramienta para escribir un frontend con pruebas unitarias |
Escribí mi primera prueba:
mencionar-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); }); });
Me sorprendió un poco el rigor del compilador mecanografiado. Trabajar en pasos aquí realmente significó no agregar nada que no estés usando todavía. Por ejemplo, quería agregar una referencia a la "UI", porque sabía que la iba a usar más adelante, pero en realidad no podía compilar MentionsManager hasta que usara todo lo que puse en el constructor.
Después de algunas rondas de rojo, verde y refactorización, terminé con estas pruebas:
mencionar-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); }); });
Con esta lógica implementada, podría recuperar menciones de cualquier fuente y mostrarlas en el RTE a través del enlace "buscar".
Utilicé el mismo enfoque para crear un método de "selección" para tomar al miembro seleccionado e insertar la mención en el editor. Este es el código con el que terminé:
mencionar-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); } }
❓ Incertidumbre |
---|
La interfaz Range es un tipo integrado que es realmente difícil de burlar y esta interfaz filtra un detalle de implementación en mi lógica de negocios. Siento que podría haber habido una mejor manera de hacer esto. |
En general, creo que terminé con un código simple que es fácil de cambiar. Todavía hay partes de este código que realmente no me gustan. Quería que la lógica empresarial controlara la interfaz de usuario, pero el código terminó más bien como una tienda simple que también realiza una única llamada a la interfaz de usuario. Me pregunto si podría ajustar mejor la interfaz de usuario para aprovechar más el administrador.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3