Dynamically add items to TipTap toolbar menu items

Hi,

I’m using a custom extension to wrap selected text in a span, with custom attributes. These items can be defined in the manifest:

{
	type: 'tiptapToolbarExtension',
	kind: 'menu',
	alias: 'test.tiptap.toolbar.languageMenu',
	name: 'Language Menu',
	js: () => import('./language-menu.tiptap-toolbar-api.js'),
	forExtensions: ['test.tiptap.languageMark'],
	meta: {
		alias: 'languageMenu',
		icon: 'icon-globe',
		label: 'Language',
	},
	items: [
		{
			label: 'πŸ‡¬πŸ‡§ EN',
			value: 'en',
		},
	],
},

The problem is, i want to populate the menu dynamically. I tried the following (and variants of it):

export default class UmbTiptapToolbarLanguageMenuExtensionApi extends UmbTiptapToolbarElementApiBase {
	private _languages: LanguageItem[] = [];

	constructor(host: any, elementName?: string) {
		super(host, elementName);
		
		// ...
		this.manifest.items = [
			{
				label: 'πŸ‡³πŸ‡± NL',
				value: 'nl',
			},
			{
				label: 'πŸ‡¬πŸ‡§ EN',
				value: 'en',
			},
			{
				label: 'πŸ‡©πŸ‡ͺ DE',
				value: 'de',
			},
		];
	}

	// ...
}

That does set the manifest items, but these items are not shown in the Language Menu.

Is it at all possible to dynamically update the items in a tiptapToolbarExtension (type β€˜menu’)? Or is there a different approach that i could try?

Hi Richard

any luck getting this to work?
Umbraco 17.1 now
no clear documentation sadly

If by dynamic you mean observe something else at runtime then I had a thought that blocks for tiptap might have something similar..

Umbraco-CMS/src/Umbraco.Web.UI.Client/src/packages/tiptap/extensions/block/block.tiptap-toolbar-api.ts at main Β· umbraco/Umbraco-CMS

Maybe something like this simply observing the languages in the backoffice (generated and not checked)

import type { Editor } from '../../externals.js';
import { UmbTiptapToolbarElementApiBase } from '../tiptap-toolbar-element-api-base.js';
import { UMB_APP_LANGUAGE_CONTEXT } from '@umbraco-cms/backoffice/language';
import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';

export default class UmbTiptapToolbarLanguageMenuExtensionApi extends UmbTiptapToolbarElementApiBase {
    #languages: Array<UmbLanguageDetailModel> = [];

    constructor(host: UmbControllerHost) {
        super(host);

        this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, (context) => {
            this.observe(
                context?.languages,
                (languages) => {
                    this.#languages = languages ?? [];
                    this.manifest.items = this.#languages.map((lang) => ({
                        label: lang.name ?? lang.isoCode ?? '',
                        value: lang.isoCode ?? '',
                    }));
                },
                'observeLanguages',
            );
        });
    }

    override isActive(editor?: Editor) {
        // check if cursor is currently inside a language mark
        return editor?.isActive('languageMark') === true;
    }

    override async execute(editor?: Editor) {
        // no-op at top level β€” individual item execution
        // is handled by the toolbar element's item click
    }

    executeItem(editor?: Editor, value?: string) {
        if (!editor || !value) return;

        editor
            .chain()
            .focus()
            .setMark('languageMark', { lang: value })
            .run();
    }
}