Implementing a contextmenu with wikitext

You really do know all

Alrighty, after spending a few days fiddling with context menus (and definitely too much time playing around with CSS transition effects that were ultimately scrapped), I’m in a spot where I’m quite happy with how they turned out. After some refactoring there is not much code to it, and I even got submenus to work.

The $eventcatcher and macro are pretty much as above.

The template for the popup also is not very complicated and mostly sets all the passed variables:

\whitespace trim

<$let stateTiddlerBase="$:/state/popup/contextmenu"
			contextmenu={{{ [<stateTiddlerBase>addsuffix[/dom-data-contextmenu]get[text]else[Tiddler]] }}}
			contextmenutiddler={{{ [<stateTiddlerBase>addsuffix[/dom-data-contextmenutiddler]get[text]] }}}
			menuItemsTag={{{ [[$:/mwi/tags/ContextMenu]addsuffix<contextmenu>] }}}
			auxData={{{ [<stateTiddlerBase>addsuffix[/dom-data-contextmenuaux]get[text]] }}}
			parentTiddler={{{ [<stateTiddlerBase>addsuffix[/dom-data-contextmenuparent]get[text]] }}}
			tv-config-toolbar-text="yes" >
	<$reveal type="popup" state=<<stateTiddlerBase>> updatePopupPosition="yes">
		<div class="tc-drop-down mwi-contextmenu">
			<$tiddler tiddler=<<contextmenutiddler>> >
				<$reveal tag="button" class="tc-btn-invisible mwi-contextmenu-heading" type="nomatch" text="TOCItem" default=<<contextmenu>> >
					<span class="tc-btn-text">
						<$text text=<<currentTiddler>> />
					</span>
				</$reveal>
				<$list filter="[all[shadows+tiddlers]tag<menuItemsTag>!has[draft.of]]" variable="listItem">
					<$transclude tiddler=<<listItem>>/>
				</$list>
			</$tiddler>
		</div>
	</$reveal>
</$let>

The identifier passed from the DOM attribute data-contextmenu via a state tiddler into the variable contextmenu is used to build the tag name menuItemsTag which controls which buttons appear in the context menu for that identifier.
The buttons don’t have to be very complicated, e.g. an edit-button:

\define button-actions()
<!-- wenn Tiddler nur transcluded ist, muss er erst geöffnet werden -->
<$list filter="[<parentTiddler>!is[blank]]" variable="void">
	<$list filter="[<tv-story-list>!contains<currentTiddler>]" variable="void">
		<$action-listops $tiddler=<<tv-story-list>> $subfilter="+[insertbefore:parentTiddler<currentTiddler>move<currentTiddler>]"/>
	</$list>
	<$action-navigate />
</$list>
<$action-sendmessage $message="tm-edit-tiddler" />
\end

<$button class="tc-btn-invisible mwi-btn-meta-toolbar" actions=<<button-actions>> >
	{{$:/core/images/edit-button}}
	<$list filter="[<tv-config-toolbar-text>match[yes]]">
		<span class="tc-btn-text">
			Tiddler bearbeiten
		</span>
	</$list>
</$button>

Most of the buttons do somewhat more complicated actions unique to this wiki. But, they were already implemented and their buttons were littered around the UI. All it took (mostly) was to assign them the respective tag(s).

If there are relevant tiddlers higher up in the hierarchy, a submenu for those is also shown, implemented as just another context menu button with the respective tag:

<!-- wenn es einen parentTiddler gibt, zeigen wir das Menü für den Haupt-Tiddler an (wenn wir nicht schon in einem Untermenüpunkt sind) -->
<$reveal type="nomatch" text="2" default=<<menuLevel>> tag="button" class="tc-btn-invisible">
	<$tiddler tiddler={{$:/HistoryList!!current-tiddler}} >
		<$let parentTiddler=""
					contextmenu="Tiddler"
					menuItemsTag={{{ [[$:/mwi/tags/ContextMenu]addsuffix<contextmenu>] }}}
					menuLevel="2" >
			{{$:/mwi/images/escalator-warning}}
			<span class="tc-btn-text">
				<$text text=<<currentTiddler>> />
			</span>
			<span class="mwi-hotkey">
				{{$:/core/images/right-arrow}}
			</span>
			<div class="mwi-contextsubmenu">
				<$list filter="[all[shadows+tiddlers]tag<menuItemsTag>!has[draft.of]]" variable="listItem">
					<$transclude tiddler=<<listItem>>/>
				</$list>
			</div>
		</$let>
	</$tiddler>
</$reveal>

To complete the UI cleanup, I will consider if it makes sense to move the whole View Toolbar into the context menu (except for the Close button). Some of it is already there, and the rest is rarely used.

Maybe this will inspire someone to try their own hand at crafting a bespoke context menu (except maybe for @saqimtiaz who probably does stuff like this before breakfast).

Have a nice day
Yaisog

PS: Sorry for all the German text. I don’t have the time right now for a translation. Pretty much all of it should not be relevant to understand the principle of the thing.
PPS: The submenus maybe only make sense when considering the general tiddler structure in the wiki: A major topic can have a tabbed table of contents, and each of those TOC entries may have a list of journal tiddlers which are collected together in a nested tabbed TOC. So, a journal tiddler may have a parent that is a contents pane of a toc-tabbed, which again then has a parent tiddler that is the root tiddler of the zoomin view. Nothing for small screens, but very efficient for finding information quickly.

9 Likes

@Yaisog it is extremely pleasing to see your explorations with wikitext and thank you for sharing back with the wider community. A bit of a crunch week for me at the moment but I’ll reply in more detail when the opportunity allows.

2 Likes

Turns out, adding the View Toolbar to the context menu is very simple. It is just another “button” with a submenu, just a few lines of code changed from what I posted above:

\import [[$:/core/ui/ViewTemplate]]
<!-- import is needed for the macros defined there -->
<$reveal type="nomatch" text="2" default=<<menuLevel>> tag="button" class="tc-btn-invisible">
	<$tiddler tiddler={{$:/HistoryList!!current-tiddler}} >
		<$let menuLevel="2" >
			{{$:/core/images/options-button}}
			<span class="tc-btn-text">
				Toolbar Buttons
			</span>
			<span class="mwi-hotkey">
				{{$:/core/images/right-arrow}}
			</span>
			<div class="mwi-contextsubmenu">
				<$list filter="[all[shadows+tiddlers]tag[$:/tags/ViewToolbar]!has[draft.of]] -[[$:/core/ui/Buttons/more-tiddler-actions]]" variable="listItem">
					<$transclude tiddler=<<listItem>>/>
				</$list>
			</div>
		</$let>
	</$tiddler>
</$reveal>

Mostly, it’s only a change of the $list filter expression that was simply copied from $:/core/ui/Buttons/more-tiddler-actions.


Have a nice day
Yaisog

PS: Not all of it works fully. Because the context menu is defined in the page template and not in the view template, e.g. Info that relies on the variable tiddlerInfoState that is computed via qualify in the view template does not work. If it really has to go into the context menu, I think it would have to be passed via the DOM into the context menu. The rest of the items work as far as I can tell.

2 Likes

Hi @Yaisog

It would be great if you bundle this amazing code as a JSON, let one test on tiddlywiki.com

I’ll try to get around to do that, but it’s a few tiddlers to collect, including a custom PageTemplate, and I’d have to translate some text first to make it useful.
Currently I’m way to busy adding context menus everywhere. Just finished one for tasks:

This one is special as I can directly enter an assignee (“Zuweisung ändern”) and a due date without opening extra menus. Before, this was a collection of buttons that appeared next to a task when hovering over it.

I have to say, these context menus are awesome, better than sliced bread. Going back to a regular TW now feels more like it’s a TW4½ instead of a TW5. :wink:

Have a nice day
Yaisog
PS: For the curious: There is a “Meta” tab in my sidebar which lists the tasks (among other things) related to the main tiddler in a zoomin story view. Many of the buttons that are now in the context menu used to be bunched there, also. Not no more!

5 Likes

WOW!
Great job! This worth packing as a plugin!

If you like to pack it as plugin, you may have a look at Gatha Studio which allow you to create plugins on the fly! No learning curve! :sweat_smile:

1 Like

I agree with @Mohammad. This would be a great plugin! I can see some possibilities with my railroad wiki.

2 Likes

Hi @Yaisog

This is very interesting! Is there any demo online to give a try!

Thank you!

I noticed that I still owe you a demo implementation (sorry, have been playing Hard West 2 too much).
Import these tiddlers on tiddlywiki.com:

mwi-context-menu.json (12.6 KB)

Since it’s all wikitext, there is no save/reload needed.
To enable the context menu, select the PageTemplate MWi in $:/ControlPanel → Appearance → Layout.
There should also appear a toggle in the PageControls to switch between custom and browser context menu.

For tiddlywiki.com, I implemented only the ViewToolbar buttons on the whole tiddler frame, but the structure is there for more complex stuff, i.e. by adding the HTML attribute dom-data-contextmenu to any element will give that element its own context menu. The value of that attribute determines the tag that is used to determine which buttons will appear in the menu, e.g. dom-data-contextmenu="Link" will include any button defined in a tiddler tagged with $:/mwi/tags/ContextMenuLink.
The ViewToolbar buttons are defined in $:/mwi/ui/Buttons/view-toolbar-contextmenu-buttons which is tagged $:/mwi/tags/ContextMenuTiddler (standard tag used for the tiddler frame context menu).

While it is purely WikiText, it does require selecting the modified PageTemplate.

Have a nice day
Yaisog

8 Likes

Sounds great. I am looking forward to play with it.

Very nice!
This also has a lot of potentilal for creating other tools.

Thank you for sharing!

@Yaisog Thanks for this!

It doesn’t work if you have Tobi Beer’s appear plugin installed - RSOD. I wasn’t using it and disabling it allowed your context menu to work.

But…

What’s that input-looking box? Tooltip says “Show links”…

Will it only work via the ViewTemplate? I have many tiddlers displayed outside of the ViewTemplate – I truly want it to work there.

This context menu addin by @fastfreddy will work on all pagetemplates and is also very good

Thanks @arunnbabu81 but I think what I’m looking for (a configurable context menu devoid of preconceived ideas about both its location and purpose) is not available. I need it to trigger by HTML node (irrespective of where that node appears) and be populated by actionable items of my choosing. Something like…

<$eventcatcher selector="li" ...>
<ul data-id="my-menu">
  <li>item 1</li>
  ...
  <li>item N</li>
</ul>
</$eventcatcher>

I imagine the trigger that shows the menu would come from another $eventcatcher wrapped over arbitrary tiddler text (blocks of HTML).

I have no idea, currently. When I find the time, I’ll have a look.

A custom PageTemplate is required to include the necessary $eventcatchers at a level high enough so that I can put a context menu on anything, e.g. sidebar, etc. One day the default PageTemplate may be extended to dynamically include widgets via parameterized transclusions. Then it would only be a matter of tagging the corresponding tiddlers correctly. Until then I need to modify the PageTemplate itself.

Yaisog

I know I am a Jonny come lately, I put off researching context menus until now.

Context menus are certainly a fantastic way to “surface options” in tiddlywiki without adding complexity to the User interface. Thanks all for your effort so far.

Here are a few ideas based on my own interest that perhaps we could consider.

  • Different configurable menus for the text editor and the view body
  • Codemirror compatibility inc codemiror 6 (I have not tested)
  • Perhaps we could build additional Control Panel > Appearance > Toolbars to select between buttons on the context menu(s) or behind a more cascade?
  • Perhaps users could select to use the middle button, often also the mouse roller button

I am very interested in;

  • A workaround is using a new page layout
  • @saqimtiaz have you gotten anywhere with this?. I have come up against this limitation quite a few times and it is a weakness in tiddlywikis hackability.
    • I just came up with a possible solution I am doing a quick POC of now

Also for those passing by;

  • Of course this “overriding the link widget via wikitext” is now possible with custom widgets and the genesis widget.
  • The idea of an additional r-click context menu on links is also a possibilitry

@Yaisog How to create nested context menu like shown in this image ?

The wikitext for the submenu is at the bottom of this earlier comment.

Here’s another (simplified) example that I am currently using:

<$button class="tc-btn-invisible mwi-btn-contextmenu" disabled="yes">
	<div>
		{{$:/mwi/images-lucide/list-plus}}
		<span class="tc-btn-text">neues Thema in Besprechung</span>
		<span class="mwi-hotkey">
			{{$:/core/images/right-arrow}}
		</span>
	</div>
	<div class="mwi-contextsubmenu">
		<$list filter="[tag[$:/tags/CompoundJournal]!sortsub<date-sort>first[10]]" variable="besprechung">
			<$button class="tc-btn-invisible mwi-btn-contextmenu" actions="<<actions>>" >
				<span class="tc-btn-text">
					<$text text=<<besprechung>> />
				</span>
			</$button>
		</$list>
	</div>
</$button>

The $list widget generates the list of items that will be shown as buttons in the submenu, here a list of meeting minute tiddlers to which a topic related to the current tiddler will be added.

There is some CSS that I didn’t post originally, though, that makes it appear at the right spot and only when hovering over the corresponding entry in the context menu:

.mwi-contextsubmenu {
    position: absolute;
    left: 100%;
    background: white;
    border: 1px solid #bbb;
    box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px;
    display: none;
    top: -0.5em;
    padding: 0.5em 0;
}

.mwi-contextmenu button:hover .mwi-contextsubmenu {
    display: block;
}

Hope this helps,
Yaisog

1 Like