В системе, которую я создаю, мне нужна возможность упоминать членов 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. На данный момент я оставлю 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); }); });
При наличии этой логики я мог получать упоминания из любого источника и показывать их в RTE с помощью перехватчика fetch.
Я использовал тот же подход для создания метода «выбора», который берет выбранный элемент и вставляет упоминание в редактор. Вот код, который у меня получился:
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