HowTo: Simple popups in CodeMirror editor

Update (the next day):

Of course there is a better way to do this.
And it involves setting up a keyboard shortcut for toolbar buttons, like hinted at before. Instead of plugging into CodeMirror directly, an action-widget accesses the CodeMirror object and grabs the cursor position data, then triggers the popup. However, this way we can also execute other actions like setting the tiddler that is to be loaded into the popup, so we don’t need a unique widget for each type of popup content. We can also have a parameter for the popup state tiddler title, but that’s not really necessary for everyday operation.

The toolbar button definition tiddler looks like this:

actions: <$action-setfield $tiddler="$:/state/popup/editor/content" text="$:/yaisog/ui/EditorToolbar/external-link-dropdown" /><$action-editorpopup />
caption: 
condition: [<targetTiddler>!has[type]] [<targetTiddler>type[text/vnd.tiddlywiki]] [<targetTiddler>type[text/x-markdown]] [<targetTiddler>type[text/markdown]]
description: insert external link
icon: $:/yaisog/images-lucide/external-link
shortcuts: ((editor-popup-link))
tags: $:/tags/EditorToolbar
title: $:/yaisog/ui/EditorToolbar/external-link
type: text/vnd.tiddlywiki

where the $action-editorpopup widget is called via the actions field, after another state tiddler has been set to the tiddler containing the popup content.

In the action widget it is also possible to determine whether the button was clicked or the keyboard shortcut was used. The popup appears below the button when it was clicked and at the cursor position when the keyboard was used (to minimize mouse movement in both cases).

$action-editorpopup.js.json (2.3 KB)

The content tiddler is basically the same as no. 3 below, except that its content part now transcludes the tiddler that was set in the respective toolbar button action.

The advantage is that the TW shortcut mechanism can be used which is well integrated into the editor toolbar button code, so that basically only a single toolbar button (and shortcut) must be defined for each popup content type.

Ye olde way

With CodeMirror, it is pretty easy to leverage its extensibility to show a popup at the cursor position with the press of some key combination*. This is probably mostly cosmetic, and maybe reduces or eliminates mouse movement to access the editor toolbar (though this could be done with keyboard shortcuts for toolbar buttons). The result is something like this, here to insert an external link at the cursor position. The cursor is at the beginning of line 3:
image

The method needs 3 tiddlers.

  1. A config tiddler $:/config/codemirror/popup-link for the shortcut that points to a CodeMirror command that we will create in tiddler 2. This tiddler needs to be of type json and have the field extend with value extraKeys. Best to clone and modify some existing tiddler like $:/config/codemirror/autocomplete. The name must start with $:/config/codemirror/ to be recognized by the CodeMirror plugin.
{
	"Alt-L": "popup-link"
}
  1. The command and extension for CodeMirror. This can be named anything, must be of type application/javascript and have the field module-type with value codemirror. Basically, it defines the CM command popup-link that gets the cursor position and simply triggers a popup at that position via the state tiddler $:/temp/codemirror-popup-link.
!function(mod) {
	"object" == typeof exports && "object" == typeof module ? mod(require("$:/plugins/tiddlywiki/codemirror/lib/codemirror")) : "function" == typeof define && define.amd ? define(["$:/plugins/tiddlywiki/codemirror/lib/codemirror"], mod) : mod(CodeMirror)
}
(function(CodeMirror) {
	"use strict";
    CodeMirror["popup-link"] = function(cm) {
        return cm["popup-link"]();
    },
	CodeMirror.defineExtension("popup-link", function() {
		var pos = this.cursorCoords();
		var left = pos.left, top = pos.bottom;
		$tw.popup.triggerPopup({
			domNodeRect: {
				left: left,
				top: top,
				width: 0,
				height: 0
			},
			title: "$:/temp/codemirror-popup-link",
			wiki: $tw.wiki,
			absolute: true
		});
	}),
  	CodeMirror.commands["popup-link"] = CodeMirror["popup-link"];
});
  1. A tiddler that defines the popup and its content, using the state tiddler. This tiddler can be named anything. Tagging it with $:/tags/EditorTools makes sure it is rendered inside the $edit-text widget so that tm-edit-text-operation messages work as expected.
<style>
.yaisog-editor-popup {
	background: white;
	border: 1px solid black;
	width: 500px;
}
</style>

<$let dropdown-state="$:/temp/codemirror-popup-link">
	<$reveal tag="div" type="popup" state=<<dropdown-state>> position="below" class="tc-popup-keep yaisog-editor-popup">
		‹popup content goes here›
	</$reveal>
</$let>

Unfortunately, all three tiddlers must be created for each type of popup content, as I have found no way of accessing the actually used keyboard combination inside the CM extension code (this way I could have used the same CM command for multiple key combinations and then loaded different popup content for each, obviating the need for multiple JavaScript tiddlers).

Note that unlike autocomplete functionality, these popups are triggered by a key combination (here Alt+L) instead of typed text.

*This bypasses the TW shortcut mechanism and uses that of CM, which is needed to access the cursor coordinates.

6 Likes

Sleeping on it, I found a much better and more flexible way to do this (see top of original post).