Giter Club home page Giter Club logo

tw-commandpalette's People

Contributors

adam4242 avatar makiaea avatar souk21 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

tw-commandpalette's Issues

Feature Request: New Journal Tiddler

Hello, Thanks for this great plugin. I think it would be great if it was possible to create a new Journal tiddler using the + syntax, where the timestamp title is automatically generated for you. Something like: +j or something?

What is the RoadMap?

I was using telmiger/simple-search in https://tid.li/tw5/plugins.html , and was about to add some feature to it, make it a search widget like the one in Notion.so.

But I think you seem are trying to create a search Omnibox as powerful as the one in Notion.so. I want to know about your develop roadmap, maybe I can join you, refine this search plugin, make it as beautiful and powerful as search/command popup in Notion and VSCode.

Suggested improvement with sample code - multiple search types

Hello, I absolutely love the TiddlyWiki command palette.
The customizable search steps are invaluable and very cleverly programmed. I wanted to take it to the next level and allow different search types invoked with different keyboard shortcuts or prefixes. This lets the user customize the search filters used, based on the shortcut used (openPalette parameter).

This modified $:/core/modules/widgets/commandpalettewidget.js tiddler achieves this goal, for your consideration; sample search step dictionary included in the comment.

happy to submit a pull request if desired.


/*\
title: $:/core/modules/widgets/commandpalettewidget.js
type: application/javascript
module-type: widget

Command Palette Widget modified and annotated to support multiple search patterns
first problem is to support different keystrokes to enable different search modes (using parameters)
second problem is to extract the correct search settings with a modified format
e.g.
{
    "default": [
          {"filter": "[!is[system]!tag[todo]search:title[]]", "hint": "in title", "caret": "35"},
          {"filter": "[!is[system]!tag[todo]search:text[]]", "hint": "all", "caret": "34"}
    ],
	"/": [
          {"filter": "[!is[system]tag[todo]search:title[]]", "hint": "in title", "caret": "34"},
          {"filter": "[!is[system]tag[todo]search:text[]]", "hint": "all", "caret": "33"}
    ],
	"~": [
          {"filter": "[!is[system]tag[monster]search:title[]]", "hint": "in title", "caret": "37"},
          {"filter": "[!is[system]tag[monster]search:text[]]", "hint": "all", "caret": "36"}
    ]
}

\*/
(function () {

	/*jslint node: true, browser: true */
	/*global $tw: false */
	'use strict';

	var Widget = require('$:/core/modules/widgets/widget.js').widget;

	class CommandPaletteWidget extends Widget {
		constructor(parseTreeNode, options) {
			super(parseTreeNode, options);
			this.initialise(parseTreeNode, options);
			this.currentSelection = 0; //0 is nothing selected, 1 is first result,...
			this.symbolProviders = {};
			this.actions = [];
			this.triggers = [];
			this.searchType = 'default'; //
			this.blockProviderChange = false;
			this.defaultSettings = {
				maxResults: 15,
				maxResultHintSize: 45,
				neverBasic: false,
				showHistoryOnOpen: true,
				escapeGoesBack: true,
				alwaysPassSelection: true,
				theme: '$:/plugins/souk21/commandpalette/Compact.css',
			};
			this.settings = {};
			this.commandHistoryPath = '$:/plugins/souk21/commandpalette/CommandPaletteHistory';
			this.settingsPath = '$:/plugins/souk21/commandpalette/CommandPaletteSettings';
			this.searchStepsPath = '$:/plugins/souk21/commandpalette/CommandPaletteSearchSteps';
			this.customCommandsTag = '$:/tags/CommandPaletteCommand';
			this.themesTag = '$:/tags/CommandPaletteTheme';
			this.typeField = 'command-palette-type';
			this.nameField = 'command-palette-name';
			this.hintField = 'command-palette-hint';
			this.modeField = 'command-palette-mode';
			this.userInputField = 'command-palette-user-input';
			this.caretField = 'command-palette-caret';
			this.immediateField = 'command-palette-immediate';
			this.triggerField = 'command-palette-trigger';
		}

		actionStringBuilder(text) {
			return (e) => this.invokeActionString(text, this, e);
		}

		actionStringInput(action, hint, e) {
			this.blockProviderChange = true;
			this.allowInputFieldSelection = true;
			this.hint.innerText = hint;
			this.input.value = '';
			this.currentProvider = () => { };
			this.currentResolver = (e) => {
				this.invokeActionString(action, this, e, { 'commandpaletteinput': this.input.value });
				this.closePalette();
			}
			this.showResults([]);
			this.onInput(this.input.value);
		}

		invokeFieldMangler(tiddler, message, param, e) {
			let action = `<$fieldmangler tiddler="${tiddler}">
			<$action-sendmessage $message="${message}" $param="${param}"/>
			</$fieldmangler>`;
			this.invokeActionString(action, this, e);
		}

		//filter = (tiddler, terms) => [tiddlers]
		tagOperation(e, hintTiddler, hintTag, filter, allowNoSelection, message) {
			this.blockProviderChange = true;
			if (allowNoSelection) this.allowInputFieldSelection = true;
			this.currentProvider = this.historyProviderBuilder(hintTiddler);
			this.currentResolver = (e) => {
				if (this.currentSelection === 0) return;
				let tiddler = this.currentResults[this.currentSelection - 1].result.name;
				this.currentProvider = (terms) => {
					this.currentSelection = 0;
					this.hint.innerText = hintTag;
					let searches = filter(tiddler, terms);
					searches = searches.map(s => { return { name: s }; });
					this.showResults(searches);
				}
				this.input.value = "";
				this.onInput(this.input.value);
				this.currentResolver = (e) => {
					if (!allowNoSelection && this.currentSelection === 0) return;
					let tag = this.input.value;
					if (this.currentSelection !== 0) {
						tag = this.currentResults[this.currentSelection - 1].result.name;
					}
					this.invokeFieldMangler(tiddler, message, tag, e);
					if (!e.getModifierState('Shift')) {
						this.closePalette();
					} else {
						this.onInput(this.input.value);
					}
				}
			}
			this.input.value = "";
			this.onInput(this.input.value);
		}

		refreshThemes(e) {
			this.themes = this.getTiddlersWithTag(this.themesTag);
			let found = false;
			for (let theme of this.themes) {
				let themeName = theme.fields.title;
				if (themeName === this.settings.theme) {
					found = true;
					this.addTagIfNecessary(themeName, '$:/tags/Stylesheet', e);
				} else {
					this.invokeFieldMangler(themeName, 'tm-remove-tag', '$:/tags/Stylesheet', e);
				}
			}
			if (found) return;
			this.addTagIfNecessary(this.defaultSettings.theme, '$:/tags/Stylesheet', e);
		}

		//Re-adding an existing tag changes modification date
		addTagIfNecessary(tiddler, tag, e) {
			if (this.hasTag(tiddler, tag)) return;
			this.invokeFieldMangler(tiddler, 'tm-add-tag', tag, e);
		}

		hasTag(tiddler, tag) {
			return $tw.wiki.getTiddler(tiddler).fields.tags.includes(tag);
		}

		refreshCommands() {
			this.actions = [];
			this.actions.push({ name: "Refresh Command Palette", action: (e) => { this.refreshCommandPalette(); this.promptCommand('') }, keepPalette: true });
			this.actions.push({ name: "Explorer", action: (e) => this.explorer(e), keepPalette: true });
			this.actions.push({ name: "See History", action: (e) => this.showHistory(e), keepPalette: true });
			this.actions.push({ name: "New Command Wizard", action: (e) => this.newCommandWizard(e), keepPalette: true });
			this.actions.push({
				name: "Add tag to tiddler",
				action: (e) => this.tagOperation(e, 'Pick tiddler to tag', 'Pick tag to add (⇧⏎ to add multiple)',
					(tiddler, terms) => $tw.wiki.filterTiddlers(`[!is[system]tags[]] [is[system]tags[]] -[[${tiddler}]tags[]] +[search[${terms}]]`),
					true,
					'tm-add-tag'),
				keepPalette: true
			});
			this.actions.push({
				name: "Remove tag",
				action: (e) => this.tagOperation(e, 'Pick tiddler to untag', 'Pick tag to remove (⇧⏎ to remove multiple)',
					(tiddler, terms) => $tw.wiki.filterTiddlers(`[[${tiddler}]tags[]] +[search[${terms}]]`),
					false,
					'tm-remove-tag'),
				keepPalette: true
			});

			//load up custom commands defined in tiddlers
			let commandTiddlers = this.getTiddlersWithTag(this.customCommandsTag);
			for (let tiddler of commandTiddlers) {
				if (!tiddler.fields[this.typeField] === undefined) continue;
				let name = tiddler.fields[this.nameField];
				let type = tiddler.fields[this.typeField];
				let text = tiddler.fields.text;
				if (text === undefined) text = '';
				let textFirstLine = text.match(/^.*/)[0];
				let hint = tiddler.fields[this.hintField];
				if (hint === undefined) hint = tiddler.fields[this.nameField];
				if (hint === undefined) hint = '';
				if (type === 'shortcut') {
					let trigger = tiddler.fields[this.triggerField];
					if (trigger === undefined) continue;
					this.triggers.push({ name, trigger, text, hint });
					continue;
				}
				if (!tiddler.fields[this.nameField] === undefined) continue;
				if (type === 'prompt') {
					let immediate = !!tiddler.fields[this.immediateField];
					let caret = tiddler.fields[this.caretField];
					let action = { name: name, action: () => this.promptCommand(textFirstLine, caret), keepPalette: !immediate, immediate: immediate };
					this.actions.push(action);
					continue;
				}
				if (type === 'prompt-basic') {
					let caret = tiddler.fields[this.caretField];
					let action = { name: name, action: () => this.promptCommandBasic(textFirstLine, caret, hint), keepPalette: true };
					this.actions.push(action);
					continue;
				}
				if (type === 'message') {
					this.actions.push({ name: name, action: (e) => this.tmMessageBuilder(textFirstLine)(e) });
					continue;
				}
				if (type === 'actionString') {
					let userInput = tiddler.fields[this.userInputField] !== undefined && tiddler.fields[this.userInputField] === 'true';
					if (userInput) {
						this.actions.push({ name: name, action: (e) => this.actionStringInput(text, hint, e), keepPalette: true });
					} else {
						this.actions.push({ name: name, action: (e) => this.actionStringBuilder(text)(e) });
					}
					continue;
				}
				if (type === 'history') {
					let mode = tiddler.fields[this.modeField];
					this.actions.push({ name: name, action: (e) => this.commandWithHistoryPicker(textFirstLine, hint, mode).handler(e), keepPalette: true });
					continue;
				}
			}
		}

		newCommandWizard() {
			this.blockProviderChange = true;
			this.input.value = '';
			this.hint.innerText = 'Command Name';
			let name = '';
			let type = '';
			let hint = '';

			let messageStep = () => {
				this.input.value = '';
				this.hint.innerText = 'Enter Message';
				this.currentResolver = (e) => {
					this.tmMessageBuilder('tm-new-tiddler',
						{
							title: '$:/' + name,
							tags: this.customCommandsTag,
							[this.typeField]: type,
							[this.nameField]: name,
							[this.hintField]: hint,
							text: this.input.value
						})(e);
					this.closePalette();
				}
			}

			let hintStep = () => {
				this.input.value = '';
				this.hint.innerText = 'Enter hint';
				this.currentResolver = (e) => {
					hint = this.input.value;
					messageStep();
				}
			}


			let typeStep = () => {
				this.input.value = '';
				this.hint.innerText = 'Enter type (prompt, prompt-basic, message, actionString, history)'
				this.currentResolver = (e) => {
					type = this.input.value;
					if (type === 'history') {
						hintStep();
					} else {
						this.tmMessageBuilder('tm-new-tiddler',
							{
								title: '$:/' + name,
								tags: this.customCommandsTag,
								[this.typeField]: type,
								[this.nameField]: name
							})(e);
						this.closePalette();
					}
				}
			}

			this.currentProvider = (terms) => { }
			this.currentResolver = (e) => {
				if (this.input.value.length === 0) return;
				name = this.input.value;
				typeStep();
			}
			this.showResults([]);
		}

		explorer(e) {
			this.blockProviderChange = true;
			this.input.value = '$:/';
			this.lastExplorerInput = '$:/';
			this.hint.innerText = 'Explorer (⇧⏎ to add multiple)';
			this.currentProvider = (terms) => this.explorerProvider('$:/', terms);
			this.currentResolver = (e) => {
				if (this.currentSelection === 0) return;
				this.currentResults[this.currentSelection - 1].result.action(e);
			}
			this.onInput();
		}

		explorerProvider(url, terms) {
			let switchFolder = (url) => {
				this.input.value = url;
				this.lastExplorerInput = this.input.value;
				this.currentProvider = (terms) => this.explorerProvider(url, terms);
				this.onInput();
			};
			if (!this.input.value.startsWith(url)) {
				this.input.value = this.lastExplorerInput;
			}
			this.lastExplorerInput = this.input.value;
			this.currentSelection = 0;
			let search = this.input.value.substr(url.length);
			let tiddlers = $tw.wiki.filterTiddlers(`[removeprefix[${url}]splitbefore[/]sort[]search[${search}]]`);
			let folders = [];
			let files = [];
			for (let tiddler of tiddlers) {
				if (tiddler.endsWith('/')) {
					folders.push({ name: tiddler, action: (e) => switchFolder(`${url}${tiddler}`) });
				} else {
					files.push({
						name: tiddler, action: (e) => {
							this.navigateTo(`${url}${tiddler}`);
							if (!e.getModifierState('Shift')) {
								this.closePalette();
							}
						}
					});
				}
			}
			let topResult;
			if (url !== '$:/') {
				let splits = url.split('/');
				splits.splice(splits.length - 2);
				let parent = splits.join('/') + '/';
				topResult = { name: '..', action: (e) => switchFolder(parent) };
				this.showResults([topResult, ...folders, ...files]);
				return;
			}
			this.showResults([...folders, ...files]);
		}

		setSetting(name, value) {
			//doing the validation here too (it's also done in refreshSettings, so you can load you own settings with a json file)
			if (typeof value === 'string') {
				if (value === 'true') value = true;
				if (value === 'false') value = false;
			}
			this.settings[name] = value;
			$tw.wiki.setTiddlerData(this.settingsPath, this.settings);
		}

		//loadSettings?
		refreshSettings() {
			//get user or default settings
			this.settings = $tw.wiki.getTiddlerData(this.settingsPath, { ...this.defaultSettings });
			//Adding eventual missing properties to current user settings
			for (let prop in this.defaultSettings) {
				if (!this.defaultSettings.hasOwnProperty(prop)) continue;
				if (this.settings[prop] === undefined) {
					this.settings[prop] = this.defaultSettings[prop];
				}
			}
			//cast all booleans
			for (let prop in this.settings) {
				if (!this.settings.hasOwnProperty(prop)) continue;
				if (typeof this.settings[prop] !== 'string') continue;
				if (this.settings[prop].toLowerCase() === 'true') this.settings[prop] = true;
				if (this.settings[prop].toLowerCase() === 'false') this.settings[prop] = false;
			}
		}

		//helper function to retrieve all tiddlers (+ their fields) with a tag
		getTiddlersWithTag(tag) {
			let tiddlers = $tw.wiki.getTiddlersWithTag(tag);
			return tiddlers.map(t => $tw.wiki.getTiddler(t));
		}

		//akin to a constructor, only is invoked upon widget creation
		render(parent, nextSibling) {
			this.parentDomNode = parent;
			this.execute();
			this.history = $tw.wiki.getTiddlerData(this.commandHistoryPath, { history: [] }).history;

			$tw.rootWidget.addEventListener('open-command-palette', (e) => this.openPalette(e));
			$tw.rootWidget.addEventListener('open-command-palette-selection', (e) => this.openPaletteSelection(e));
			$tw.rootWidget.addEventListener('insert-command-palette-result', (e) => this.insertSelectedResult(e));

			let inputAndMainHintWrapper = this.createElement('div', { className: 'inputhintwrapper' });
			this.div = this.createElement('div', { className: 'commandpalette' }, { display: 'none' });
			this.input = this.createElement('input', { type: 'text' });
			this.hint = this.createElement('div', { className: 'commandpalettehint commandpalettehintmain' });
			inputAndMainHintWrapper.append(this.input, this.hint);
			this.scrollDiv = this.createElement('div', { className: 'cp-scroll' });
			this.div.append(inputAndMainHintWrapper, this.scrollDiv);
			this.input.addEventListener('keydown', (e) => this.onKeyDown(e));
			this.input.addEventListener('input', () => this.onInput(this.input.value));
			window.addEventListener('click', (e) => this.onClick(e));
			parent.insertBefore(this.div, nextSibling);

			//loads the settings and filter steps
			this.refreshCommandPalette();

			//symbolProviders is a defined here as a dictionary of dictionaries (why here, and not in the constructor?)
			// the key is a character prefix and the value is a dictionary itself of two key value pairs for keys: "searcher" and "resolver"; 
			// The values are functions!! mapping an argument (or two) to a function call
			//the parseCommand(text) returns two functions (searcher, resolver) based on a dictionary lookup in symbolProviers, and a terms text string
			//searcher and resolver functions are assigned by key lookup in this dictionary if find() on the _array_ created from the dictionary keys is not undefined
			// the defaultProvider and defaultResolver are returned when find() is undefined
			this.symbolProviders['>'] = { searcher: (terms) => this.actionProvider(terms), resolver: (e) => this.actionResolver(e) };
			this.symbolProviders['#'] = { searcher: (terms) => this.tagListProvider(terms), resolver: (e) => this.tagListResolver(e) };
			this.symbolProviders['@'] = { searcher: (terms) => this.tagProvider(terms), resolver: (e) => this.defaultResolver(e) };
			this.symbolProviders['?'] = { searcher: (terms) => this.helpProvider(terms), resolver: (e) => this.helpResolver(e) };
			this.symbolProviders['['] = { searcher: (terms, hint) => this.filterProvider(terms, hint), resolver: (e) => this.filterResolver(e) };
			this.symbolProviders['+'] = { searcher: (terms) => this.createTiddlerProvider(terms), resolver: (e) => this.createTiddlerResolver() };
			this.symbolProviders['|'] = { searcher: (terms) => this.settingsProvider(terms), resolver: (e) => this.settingsResolver() };
			this.currentResults = [];
			this.currentProvider = {};
		}
		
		//loads json data structure that includes array of search steps, that is 
		//filter expressions with a caret position at which search terms are entered
		//this function loads the array of filter expressions as an array of functions provided by searchStepBuilder
		refreshSearchSteps() {
			this.searchSteps = [];
			
			let steps = $tw.wiki.getTiddlerData(this.searchStepsPath);
			//steps = steps[this.searchtype];
			//searchtype is set to "steps" by default, based on the format of 
			// $:/plugins/souk21/commandpalette/CommandPaletteSearchSteps in the original plugin
			//The Object.entries() method returns an array of a given object's own enumerable string-keyed property [key, value] pairs, 
			
			//for (const [key, value] of Object.entries(steps)) {
			//console.log("key="+key);
			//console.log("steps="+steps);
			//steps = steps.steps;
			//this pushes the correct searchSteps to the stack, each being a function that is generated by searchStepBuilder
			//that places the search term within the filter at the caret position
			for (var type in steps) {
				//console.log("+++++loading type "+type);
				for (let step of steps[type]) {
					//console.log("-----> with step "+step.hint);
					this.searchSteps.push(this.searchStepBuilder(step.filter, step.caret, step.hint, type)); //store the type associated with each step (like it is done for hints)
				}
			}
		}

		refreshCommandPalette() {
			this.refreshSettings();
			this.refreshThemes();
			this.refreshCommands();
			this.refreshSearchSteps();
		}

		updateCommandHistory(command) {
			this.history = Array.from(new Set([command.name, ...this.history]));
			$tw.wiki.setTiddlerData(this.commandHistoryPath, { history: this.history });
		}

		historyProviderBuilder(hint, mode) {
			return (terms) => {
				this.currentSelection = 0;
				this.hint.innerText = hint;
				let results;
				if (mode !== undefined && mode === 'drafts') {
					results = $tw.wiki.filterTiddlers('[has:field[draft.of]]');
				} else if (mode !== undefined && mode === 'story') {
					results = $tw.wiki.filterTiddlers('[list[$:/StoryList]]');
				} else {
					results = this.getHistory();
				}
				results = results.map(r => { return { name: r } });
				this.showResults(results);
			};
		}

		commandWithHistoryPicker(message, hint, mode) {
			let handler = (e) => {
				this.blockProviderChange = true;
				this.allowInputFieldSelection = true;
				this.currentProvider = provider;
				this.currentResolver = resolver;
				this.input.value = '';
				this.onInput(this.input.value);
			}
			let provider = this.historyProviderBuilder(hint, mode);
			let resolver = (e) => {
				if (this.currentSelection === 0) return;
				let title = this.currentResults[this.currentSelection - 1].result.name;
				this.parentWidget.dispatchEvent({
					type: message,
					param: title,
					tiddlerTitle: title,
				});
				this.closePalette();
			}
			return {
				handler,
				provider,
				resolver
			}
		}
		
		//is triggered when the palette first opens, and then for every key strokes
		onInput(text) { //reparses the command on every key strokes
		
			//prevent provider changes -> this is set in a bunch of places; maybe whenever we've started adding to the initial & already parsed text?
			if (this.blockProviderChange) { 
				this.currentProvider(text);
				this.setSelectionToFirst();
				return;
			}
			//repoints the resolvers and providers based on the input text
			let { resolver, provider, terms } = this.parseCommand(text);
			this.currentResolver = resolver;
			this.currentProvider = provider;
			this.currentProvider(terms);
			this.setSelectionToFirst();
		}
		
		
		parseCommand(text) {
			let terms = "";
			//prefix is the first character of the input, which can either be typed,
			//or passed in using the parameter of the openPalette call (see openPalette(e) {)
			let prefix = text.substr(0, 1);  
			let resolver;
			let provider;
			
			//this looks through the triggers table to see if the command text starts with any of the trigger characters
			//triggers seem to be only when the users define custom commands with triggers
			let shortcut = this.triggers.find(t => text.startsWith(t.trigger));
			
			//there is a trigger match ! yay!
			if (shortcut !== undefined) {
				resolver = (e) => {
					let inputWithoutShortcut = this.input.value.substr(shortcut.trigger.length);
					this.invokeActionString(shortcut.text, this, e, { 'commandpaletteinput': inputWithoutShortcut });
					this.closePalette();
				}
				//this is a function that is (probably) activate with a reduce call?
				provider = (terms) => {
					//adjusting the hint variable (for display) based on information in the triggers table
					this.hint.innerText = shortcut.hint;
					this.showResults([]);
				}
				
			//no trigger match
			} else {
				
				//tries to match the prefix in the array of dictionary keys
				//prefix is the first character of the input, which can either be typed,
				//or passed in using the parameter of the openPalette call (see openPalette(e) {)
				let providerSymbol = Object.keys(this.symbolProviders).find(p => p === prefix);
				
				//no match
				if (providerSymbol === undefined) {
					
					//check to see if the prefix matches a key from the searchSteps tiddler's dictionary, and set the this.searchType accordingly
					let steps = $tw.wiki.getTiddlerData(this.searchStepsPath);
					var result = steps[prefix];
					console.log("trying to find the prefix in the search steps perhaps: "+result);
					
					//we have a match, enable search steps filtering by setting this.searchType
					if (result !== undefined) {
						this.searchType = prefix;
						terms = text.substring(1); //ignores the prefix (stays displayed, but isn't used for searching)
					} else {
						this.searchType = "default"; //default search steps
						terms = text;
					}
					
					resolver = this.defaultResolver; //search resolver
					provider = this.defaultProvider; //search provider
					
				}
				else {
					//two dictionary key lookup when (array).find() above is defined
					provider = this.symbolProviders[providerSymbol].searcher; //this is a function
					resolver = this.symbolProviders[providerSymbol].resolver; //this is a function
					terms = text.substring(1); //ignores the prefix (stays displayed, but isn't used for searching)
				}
			}
			return { resolver, provider, terms }
		}
		
		onClick(e) {
			if (this.isOpened && !this.div.contains(e.target)) {
				this.closePalette();
			}
		}
		openPaletteSelection(e) {
			let selection = this.getCurrentSelection();
			e.param = selection;
			this.openPalette(e);
		}
		openPalette(e) {
			this.isOpened = true;
			this.allowInputFieldSelection = false;
			this.goBack = undefined;
			this.blockProviderChange = false;
			let activeElement = this.getActiveElement();
			this.previouslyFocused = { element: activeElement, start: activeElement.selectionStart, end: activeElement.selectionEnd, caretPos: activeElement.selectionEnd };
			this.input.value = '';
			
			//was there a parameter provided when calling the openPalette function
			//for example, mapping a different keyboad shortcut to trigger a different behaviour
			//$tw.rootWidget.invokeActionString('<$action-sendmessage $message="open-command-palette" $param="Z"/>',$tw.rootWidget);
			if (e.param !== undefined) {
				this.input.value = e.param;
			}
			//this appends the selected text (at the time the palette is open) to the input value
			if (this.settings.alwaysPassSelection) {
				this.input.value += this.getCurrentSelection();
			}
			this.currentSelection = 0; //?
			
			//behave as if someone had typed what is in input.value)
			this.onInput(this.input.value); //Trigger results on open 
			this.div.style.display = 'flex'; //display the palette bar?
			this.input.focus();
		}

		insertSelectedResult() {
			if (!this.isOpened) return;
			if (this.currentSelection === 0) return; //TODO: what to do here?
			let previous = this.previouslyFocused;
			let previousValue = previous.element.value;
			if (previousValue === undefined) return;
			let selection = this.currentResults[this.currentSelection - 1].result.name;
			if (previous.start !== previous.end) {
				this.previouslyFocused.element.value = previousValue.substring(0, previous.start) + selection + previousValue.substring(previous.end);
			} else {
				this.previouslyFocused.element.value = previousValue.substring(0, previous.start) + selection + previousValue.substring(previous.start);
			}
			this.previouslyFocused.caretPos = previous.start + selection.length;
			this.closePalette();
		}

		closePalette() {
			this.div.style.display = 'none';
			this.isOpened = false;
			this.focusAtCaretPosition(this.previouslyFocused.element, this.previouslyFocused.caretPos);
		}
		onKeyDown(e) {
			if (e.key === 'Escape') {
				//									\/ There's no previous state
				if (!this.settings.escapeGoesBack || this.goBack === undefined) {
					this.closePalette();
				} else {
					this.goBack();
					this.goBack = undefined;
				}
			}
			else if (e.key === 'ArrowUp') {
				e.preventDefault();
				e.stopPropagation();
				let sel = this.currentSelection - 1;

				if (sel === 0) {
					if (!this.allowInputFieldSelection) {
						sel = this.currentResults.length;
					}
				} else if (sel < 0) {
					sel = this.currentResults.length;
				}
				this.setSelection(sel);
			}
			else if (e.key === 'ArrowDown') {
				e.preventDefault();
				e.stopPropagation();
				let sel = (this.currentSelection + 1) % (this.currentResults.length + 1);
				if (!this.allowInputFieldSelection && sel === 0 && this.currentResults.length !== 0) {
					sel = 1;
				}
				this.setSelection(sel);
			}
			else if (e.key === 'Enter') {
				e.preventDefault();
				e.stopPropagation();
				this.validateSelection(e);
			}
		}
		addResult(result, id) {
			let resultDiv = this.createElement('div', { className: 'commandpaletteresult', innerText: result.name });
			if (result.hint !== undefined) {
				let hint = this.createElement('div', { className: 'commandpalettehint', innerText: result.hint });
				resultDiv.append(hint);
			}
			resultDiv.result = result;
			this.currentResults.push(resultDiv);
			resultDiv.addEventListener('click', (e) => { this.setSelection(id + 1); this.validateSelection(e); });
			this.scrollDiv.append(resultDiv);
		}
		validateSelection(e) {
			this.currentResolver(e);
		}
		
		//a resolver seems to be the handler when a selection is made/action is triggered;
		//the default resolver navigates to the tiddler shown in the provider's result list
		//or creates the tiddler matching the terms (text) if the shift key is pressed when the resolver is triggered (enter key or click?)
		defaultResolver(e) {
			if (e.getModifierState('Shift')) {
				this.input.value = '+' + this.input.value; //this resolver expects that the input starts with +
				this.createTiddlerResolver(e);
				return;
			}
			if (this.currentSelection === 0) return;
			let selectionTitle = this.currentResults[this.currentSelection - 1].result.name;
			this.closePalette();
			this.navigateTo(selectionTitle);
		}
		navigateTo(title) {
			this.parentWidget.dispatchEvent({
				type: 'tm-navigate',
				param: title,
				navigateTo: title
			});
		}

		showHistory() {
			this.hint.innerText = 'History';
			this.currentProvider = (terms) => {
				let results;
				if (terms.length === 0) {
					results = this.getHistory();
				} else {
					results = this.getHistory().filter(h => h.includes(terms));
				}
				results = results.map(r => { return { name: r, action: () => { this.navigateTo(r); this.closePalette(); } } });
				this.showResults(results);
			};
			this.currentResolver = (e) => {
				if (this.currentSelection === 0) return;
				this.currentResults[this.currentSelection - 1].result.action(e);
			};
			this.input.value = '';
			this.blockProviderChange = true;
			this.onInput(this.input.value);
		}

		setSelectionToFirst() {
			let sel = 1;
			if (this.allowInputFieldSelection || this.currentResults.length === 0) {
				sel = 0;
			}
			this.setSelection(sel)
		}

		setSelection(id) {
			this.currentSelection = id;
			for (let i = 0; i < this.currentResults.length; i++) {
				let selected = this.currentSelection === i + 1;
				this.currentResults[i].className = selected ? 'commandpaletteresult commandpaletteresultselected' : 'commandpaletteresult';
			}
			if (this.currentSelection === 0) {
				this.scrollDiv.scrollTop = 0;
				return;
			}
			let scrollHeight = this.scrollDiv.offsetHeight;
			let scrollPos = this.scrollDiv.scrollTop;
			let selectionPos = this.currentResults[this.currentSelection - 1].offsetTop;
			let selectionHeight = this.currentResults[this.currentSelection - 1].offsetHeight;

			if (selectionPos < scrollPos || selectionPos >= scrollPos + scrollHeight) {
				//select the closest scrolling position showing the selection
				let a = selectionPos;
				let b = selectionPos - scrollHeight + selectionHeight;
				a = Math.abs(a - scrollPos);
				b = Math.abs(b - scrollPos);
				if (a < b) {
					this.scrollDiv.scrollTop = selectionPos;
				} else {
					this.scrollDiv.scrollTop = selectionPos - scrollHeight + selectionHeight;
				}
			}
		}

		getHistory() {
			let history = $tw.wiki.getTiddlerData('$:/HistoryList');
			if (history === undefined) {
				history = [];
			}
			history = [...history.reverse().map(x => x.title), ...$tw.wiki.filterTiddlers('[list[$:/StoryList]]')];
			return Array.from(new Set(history.filter(t => this.tiddlerOrShadowExists(t))));
		}

		tiddlerOrShadowExists(title) {
			return $tw.wiki.tiddlerExists(title) || $tw.wiki.isShadowTiddler(title);
		}

		//a provider is simply a lister of search results
		//default provider is a search provider; 
		defaultProvider(terms) {
			this.hint.innerText = 'Search tiddlers (⇧⏎ to create)';
			let searches;
			if (terms.startsWith('\\')) terms = terms.substr(1); //user was escaping first character to avoid triggering a special functionality, so we ignore it
			if (terms.length === 0) {
				if (this.settings.showHistoryOnOpen) {
					searches = this.getHistory().map(s => { return { name: s, hint: 'history' } });
				} else {
					searches = [];
				}
			}
			else {
				//this magic calls upon the searchSteps functions by providing the terms !?!
				//array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
				
				// is this the spread operator?
				//the initial set is empty []
				//then the a variable designates the accumulator?
				//to which is appended the next result set (computed by the function that c represents, with the terms argument
				searches = this.searchSteps.reduce((a, c) => [...a, ...c(terms)], []).filter((step) => step.type === this.searchType); //magic? turns searchSteps into results
				
				searches = Array.from(new Set(searches));
			}
			this.showResults(searches);
		}


		//returns an instance of a filterTiddler function with the user supplied filter expression, dependent on a terms parameter
		//neither the searchSterpBuilder nor the function returned by searchStepBuilder return "search results" at this stage, not until 
		//the map reduce is invoked supplying the search term parameters
		searchStepBuilder(filter, caret, hint, type) {
			return (terms) => {
				//substitutes search term into filter expression for that search step
				let search = filter.substr(0, caret) + terms + filter.substr(caret);
				//takes a filter expression and turn it into a TW filterTiddler function call using map
				//map prototype
				let results = $tw.wiki.filterTiddlers(search).map(s => { return { name: s, hint: hint, type: type } }); //retun the type 
				return results;
			}
		}

		tagListProvider(terms) {
			this.currentSelection = 0;
			this.hint.innerText = 'Search tags';
			let searches;
			if (terms.length === 0) {
				searches = $tw.wiki.filterTiddlers('[!is[system]tags[]][is[system]tags[]][all[shadows]tags[]]');
			}
			else {
				searches = $tw.wiki.filterTiddlers('[all[]tags[]!is[system]search[' + terms + ']][all[]tags[]is[system]search[' + terms + ']][all[shadows]tags[]search[' + terms + ']]');
			}
			searches = searches.map(s => { return { name: s }; });
			this.showResults(searches);
		}
		tagListResolver(e) {
			if (this.currentSelection === 0) {
				let input = this.input.value.substr(1);
				let exist = $tw.wiki.filterTiddlers('[tag[' + input + ']]');
				if (!exist)
					return;
				this.input.value = '@' + input;
				return;
			}
			let result = this.currentResults[this.currentSelection - 1];
			this.input.value = '@' + result.innerText;
			this.onInput(this.input.value);
		}
		tagProvider(terms) {
			this.currentSelection = 0;
			this.hint.innerText = 'Search tiddlers with @tag(s)';
			let searches = [];
			if (terms.length !== 0) {
				let { tags, searchTerms, tagsFilter } = this.parseTags(this.input.value);
				let taggedTiddlers = $tw.wiki.filterTiddlers(tagsFilter);

				if (taggedTiddlers.length !== 0) {
					if (tags.length === 1) {
						let tag = tags[0];
						let tagTiddlerExists = this.tiddlerOrShadowExists(tag);
						if (tagTiddlerExists && searchTerms.some(s => tag.includes(s))) searches.push(tag);
					}
					searches = [...searches, ...taggedTiddlers];
				}
			}
			searches = searches.map(s => { return { name: s } });
			this.showResults(searches);
		}

		parseTags(input) {
			let splits = input.split(' ').filter(s => s !== '');
			let tags = [];
			let searchTerms = [];
			for (let i = 0; i < splits.length; i++) {
				if (splits[i].startsWith('@')) {
					tags.push(splits[i].substr(1));
					continue;
				}
				searchTerms.push(splits[i]);
			}
			let tagsFilter = `[all[tiddlers+system+shadows]${tags.reduce((a, c) => { return a + 'tag[' + c + ']' }, '')}]`;
			if (searchTerms.length !== 0) {
				tagsFilter = tagsFilter.substr(0, tagsFilter.length - 1); //remove last ']'
				tagsFilter += `search[${searchTerms.join(' ')}]]`;
			}
			return { tags, searchTerms, tagsFilter };
		}

		settingsProvider(terms) {
			this.currentSelection = 0;
			this.hint.innerText = 'Select the setting you want to change';
			let isNumerical = (terms) => terms.length !== 0 && terms.match(/\D/gm) === null;
			let isBoolean = (terms) => terms.length !== 0 && terms.match(/(true\b)|(false\b)/gmi) !== null;
			this.showResults([
				{ name: 'Theme (currently ' + this.settings.theme.match(/[^\/]*$/) + ')', action: () => this.promptForThemeSetting() },
				this.settingResultBuilder('Max results', 'maxResults', 'Choose the maximum number of results', isNumerical, 'Error: value must be a positive integer'),
				this.settingResultBuilder('Show history on open', 'showHistoryOnOpen', 'Chose whether to show the history when you open the palette', isBoolean, 'Error: value must be \'true\' or \'false\''),
				this.settingResultBuilder('Escape to go back', 'escapeGoesBack', 'Chose whether ESC should go back when possible', isBoolean, 'Error: value must be \'true\' or \'false\''),
				this.settingResultBuilder('Use selection as search query', 'alwaysPassSelection', 'Chose your current selection is passed to the command palette', isBoolean, 'Error: value must be \'true\' or \'false\''),
				this.settingResultBuilder('Never Basic', 'neverBasic', 'Chose whether to override basic prompts to show filter operation', isBoolean, 'Error: value must be \'true\' or \'false\''),
				this.settingResultBuilder('Field preview max size', 'maxResultHintSize', 'Choose the maximum hint length for field preview', isNumerical, 'Error: value must be a positive integer'),
			]);
		}

		settingResultBuilder(name, settingName, hint, validator, errorMsg) {
			return { name: name + ' (currently ' + this.settings[settingName] + ')', action: () => this.promptForSetting(settingName, hint, validator, errorMsg) }
		}

		settingsResolver(e) {
			if (this.currentSelection === 0) return;
			this.goBack = () => {
				this.input.value = '|';
				this.blockProviderChange = false;
				this.onInput(this.input.value);
			}
			this.currentResults[this.currentSelection - 1].result.action();
		}

		promptForThemeSetting() {
			this.blockProviderChange = true;
			this.allowInputFieldSelection = false;
			this.currentProvider = (terms) => {
				this.currentSelection = 0;
				this.hint.innerText = 'Choose a theme';
				let defaultValue = this.defaultSettings['theme'];
				let results = [{ name: 'Revert to default value: ' + defaultValue.match(/[^\/]*$/), action: () => { this.setSetting('theme', defaultValue); this.refreshThemes(); } }];
				for (let theme of this.themes) {
					let name = theme.fields.title;
					let shortName = name.match(/[^\/]*$/);
					let action = () => { this.setSetting('theme', name); this.refreshThemes(); }
					results.push({ name: shortName, action: action });
				}
				this.showResults(results);
			}
			this.currentResolver = (e) => {
				this.currentResults[this.currentSelection - 1].result.action(e);
			}
			this.input.value = '';
			this.onInput(this.input.value);
		}

		//Validator = (terms) => bool
		promptForSetting(settingName, hint, validator, errorMsg) {
			this.blockProviderChange = true;
			this.allowInputFieldSelection = true;
			this.currentProvider = (terms) => {
				this.currentSelection = 0;
				this.hint.innerText = hint;
				let defaultValue = this.defaultSettings[settingName];
				let results = [{ name: 'Revert to default value: ' + defaultValue, action: () => this.setSetting(settingName, defaultValue) }];
				if (!validator(terms)) {
					results.push({ name: errorMsg });
				}
				this.showResults(results);
			};
			this.currentResolver = (e) => {
				if (this.currentSelection === 0) {
					let input = this.input.value;
					if (validator(input)) {
						this.setSetting(settingName, input);
						this.goBack = undefined;
						this.blockProviderChange = false;
						this.allowInputFieldSelection = false;
						this.promptCommand('|');
					}
				} else {
					let action = this.currentResults[this.currentSelection - 1].result.action;
					if (action) {
						action();
						this.goBack = undefined;
						this.blockProviderChange = false;
						this.allowInputFieldSelection = false;
						this.promptCommand('|');
					}
				}
			}
			this.input.value = this.settings[settingName];
			this.onInput(this.input.value);
		}

		showResults(results) {
			for (let cur of this.currentResults) {
				cur.remove();
			}
			this.currentResults = [];
			let resultCount = 0;
			for (let result of results) {
				this.addResult(result, resultCount);
				resultCount++;
				if (resultCount >= this.settings.maxResults)
					break;
			}
		}

		tmMessageBuilder(message, params = {}) {
			return (e) => {
				let event = {
					type: message,
					paramObject: params,
					event: e,
				};
				this.parentWidget.dispatchEvent(event);
			};
		}
		actionProvider(terms) {
			this.currentSelection = 0;
			this.hint.innerText = 'Search commands';
			let results;
			if (terms.length === 0) {
				results = this.getCommandHistory();
			}
			else {
				results = this.actions.filter(a => a.name.toLowerCase().includes(terms.toLowerCase()));
			}
			this.showResults(results);
		}

		helpProvider(terms) { //TODO: tiddlerify?
			this.currentSelection = 0;
			this.hint.innerText = 'Help';
			let searches = [
				{ name: '... Search', action: () => this.promptCommand('') },
				{ name: '> Commands', action: () => this.promptCommand('>') },
				{ name: '+ Create tiddler with title', action: () => this.promptCommand('+') },
				{ name: '# Search tags', action: () => this.promptCommand('#') },
				{ name: '@ List tiddlers with tag', action: () => this.promptCommand('@') },
				{ name: '[ Filter operation', action: () => this.promptCommand('[') },
				{ name: '| Command Palette Settings', action: () => this.promptCommand('|') },
				{ name: '\\ Escape first character', action: () => this.promptCommand('\\') },
				{ name: '? Help', action: () => this.promptCommand('?') },
			];
			this.showResults(searches);
		}

		filterProvider(terms, hint) {
			this.currentSelection = 0;
			this.hint.innerText = hint === undefined ? 'Filter operation' : hint;
			terms = '[' + terms;
			let fields = $tw.wiki.filterTiddlers('[fields[]]');
			let results = $tw.wiki.filterTiddlers(terms).map(r => { return { name: r } });
			let insertResult = (i, result) => results.splice(i + 1, 0, result);
			for (let i = 0; i < results.length; i++) {
				let initialResult = results[i];
				let alreadyMatched = false;
				let date = 'Invalid Date';
				if (initialResult.name.length === 17) { //to be sure to only match tiddly dates (17 char long)
					date = $tw.utils.parseDate(initialResult.name).toLocaleString();
				}
				if (date !== "Invalid Date") {
					results[i].hint = date;
					results[i].action = () => { };
					alreadyMatched = true;
				}
				let isTag = $tw.wiki.getTiddlersWithTag(initialResult.name).length !== 0;
				if (isTag) {
					if (alreadyMatched) {
						insertResult(i, { ...results[i] });
						i += 1;
					}
					results[i].action = () => this.promptCommand('@' + initialResult.name);
					results[i].hint = 'Tag'; //Todo more info?
					alreadyMatched = true;
				}
				let isTiddler = this.tiddlerOrShadowExists(initialResult.name);
				if (isTiddler) {
					if (alreadyMatched) {
						insertResult(i, { ...results[i] });
						i += 1;
					}
					results[i].action = () => { this.navigateTo(initialResult.name); this.closePalette() }
					results[i].hint = 'Tiddler';
					alreadyMatched = true;
				}
				let isField = fields.includes(initialResult.name);
				if (isField) {
					if (alreadyMatched) {
						insertResult(i, { ...results[i] });
						i += 1;
					}
					let parsed;
					try {
						parsed = $tw.wiki.parseFilter(this.input.value)
					} catch (e) { } //The error is already displayed to the user
					let foundTitles = [];
					for (let node of parsed || []) {
						if (node.operators.length !== 2) continue;
						if (node.operators[0].operator === 'title' && node.operators[1].operator === 'fields') {
							foundTitles.push(node.operators[0].operand);
						}
					}
					let hint = 'Field';
					if (foundTitles.length === 1) {
						hint = $tw.wiki.getTiddler(foundTitles[0]).fields[initialResult.name];
						if (hint instanceof Date) {
							hint = hint.toLocaleString();
						}
						hint = hint.toString().replace(/(\r\n|\n|\r)/gm, '');
						let maxSize = this.settings.maxResultHintSize - 3;
						if (hint.length > maxSize) {
							hint = hint.substring(0, maxSize);
							hint += '...';
						}
					}
					results[i].hint = hint;
					results[i].action = () => { };
					alreadyMatched = true;
				}
				// let isContentType = terms.includes('content-type');
			}
			this.showResults(results);
		}

		filterResolver(e) {
			if (this.currentSelection === 0) return;
			this.currentResults[this.currentSelection - 1].result.action();
			e.stopPropagation();
		}

		helpResolver(e) {
			if (this.currentSelection === 0) return;
			this.currentResults[this.currentSelection - 1].result.action();
			e.stopPropagation();
		}

		createTiddlerProvider(terms) {
			this.currentSelection = 0;
			this.hint.innerText = 'Create new tiddler with title @tag(s)';
			this.showResults([]);
		}

		createTiddlerResolver(e) {
			let { tags, searchTerms } = this.parseTags(this.input.value.substr(1));
			let title = searchTerms.join(' ');
			tags = tags.join(' ');
			this.tmMessageBuilder('tm-new-tiddler', { title: title, tags: tags })(e);
			this.closePalette();
		}

		promptCommand(value, caret) {
			this.blockProviderChange = false;
			this.input.value = value;
			this.input.focus();
			if (caret !== undefined) {
				this.input.setSelectionRange(caret, caret);
			}
			this.onInput(this.input.value);
		}

		promptCommandBasic(value, caret, hint) {
			if (this.settings.neverBasic === 'true' || this.settings.neverBasic === true) { //TODO: validate settings to avoid unnecessary checks
				this.promptCommand(value, caret);
				return;
			}
			this.input.value = "";
			this.blockProviderChange = true;
			this.currentProvider = this.basicProviderBuilder(value, caret, hint);
			this.onInput(this.input.value);
		}

		basicProviderBuilder(value, caret, hint) {
			let start = value.substr(0, caret);
			let end = value.substr(caret);
			return (input) => {
				let { resolver, provider, terms } = this.parseCommand(start + input + end);
				let backgroundProvider = provider;
				backgroundProvider(terms, hint);
				this.currentResolver = resolver;
			}
		}

		getCommandHistory() {
			this.history = this.history.filter(h => this.actions.some(a => a.name === h)); //get rid of deleted command that are still in history;
			let results = this.history.map(h => this.actions.find(a => a.name === h));
			while (results.length <= this.settings.maxResults) {
				let nextDefaultAction = this.actions.find(a => !results.includes(a));
				if (nextDefaultAction === undefined)
					break;
				results.push(nextDefaultAction);
			}
			return results;
		}
		actionResolver(e) {
			if (this.currentSelection === 0)
				return;
			let result = this.actions.find(a => a.name === this.currentResults[this.currentSelection - 1].innerText);
			if (result.keepPalette) {
				let curInput = this.input.value;
				this.goBack = () => {
					this.input.value = curInput;
					this.blockProviderChange = false;
					this.onInput(this.input.value);
				};
			}
			this.updateCommandHistory(result);
			result.action(e);
			e.stopPropagation();
			if (result.immediate) {
				this.validateSelection(e);
				return;
			}
			if (!result.keepPalette) {
				this.closePalette();
			}
		}

		getCurrentSelection() {
			let selection = window.getSelection().toString();
			if (selection !== '') return selection;
			let activeElement = this.getActiveElement();
			if (activeElement === undefined || activeElement.selectionStart === undefined) return '';
			if (activeElement.selectionStart > activeElement.selectionEnd) {
				return activeElement.value.substring(activeElement.selectionStart, activeElement.selectionEnd);
			} else {
				return activeElement.value.substring(activeElement.selectionEnd, activeElement.selectionStart);
			}
		}
		getActiveElement(element = document.activeElement) {
			const shadowRoot = element.shadowRoot
			const contentDocument = element.contentDocument

			if (shadowRoot && shadowRoot.activeElement) {
				return this.getActiveElement(shadowRoot.activeElement)
			}

			if (contentDocument && contentDocument.activeElement) {
				return this.getActiveElement(contentDocument.activeElement)
			}

			return element
		}
		focusAtCaretPosition(el, caretPos) {
			if (el !== null) {
				el.value = el.value;
				// ^ this is used to not only get "focus", but
				// to make sure we don't have it everything -selected-
				// (it causes an issue in chrome, and having it doesn't hurt any other browser)
				if (el.createTextRange) {
					var range = el.createTextRange();
					range.move('character', caretPos);
					range.select();
					return true;
				}
				else {
					// (el.selectionStart === 0 added for Firefox bug)
					if (el.selectionStart || el.selectionStart === 0) {
						el.focus();
						el.setSelectionRange(caretPos, caretPos);
						return true;
					}

					else { // fail city, fortunately this never happens (as far as I've tested) :)
						el.focus();
						return false;
					}
				}
			}
		}
		createElement(name, proprieties, styles) {
			let el = this.document.createElement(name);
			for (let [propriety, value] of Object.entries(proprieties || {})) {
				el[propriety] = value;
			}
			for (let [style, value] of Object.entries(styles || {})) {
				el.style[style] = value;
			}
			return el;
		}
		/*
			Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
			*/
		refresh() {
			return false;
		}
	}

	exports.commandpalettewidget = CommandPaletteWidget;

})();

in my case, I use the mousetrap plugin for single key (plus modifier) to invoke one of the three defined search types, very fast and convenient

...
	$tw.Mousetrap.bind("/",
		function() {
            $tw.rootWidget.invokeActionString('<$action-sendmessage $message="open-command-palette"/>',$tw.rootWidget);
		},
		"keyup"
	);
	$tw.Mousetrap.bind("shift+/",
		function() {
            $tw.rootWidget.invokeActionString('<$action-sendmessage $message="open-command-palette" $param="/"/>',$tw.rootWidget);
		},
		"keyup"
	);
	$tw.Mousetrap.bind("ctrl+/",
		function() {
            $tw.rootWidget.invokeActionString('<$action-sendmessage $message="open-command-palette" $param="~"/>',$tw.rootWidget);
		},
		"keyup"
	);
...

Enquiry regarding current status of this plug in

I have been using this plug in for the last few months regularly without much problems in both TW 5.1.22 and 5.1.23. It has speed up my workflow very much. Very thankful to you for developing command palette.

I would like to know about the current status of the plug in development since I couldn't find much activity in this GitHub page for the last few months.

$:/plugins/souk21/commandpalette/Compact.css always change it self

This file:

created: 20200603190000307
modified: 20200626024603871
tags: $:/tags/CommandPaletteTheme $:/tags/Stylesheet
title: $:/plugins/souk21/commandpalette/Compact.css
type: text/vnd.tiddlywiki

will modify it self, causing fake modifications, this is quite annoying:

Screen Shot 2020-06-26 at 10 50 18 AM

Screen Shot 2020-06-26 at 10 50 15 AM

Bug: IME's "Enter" trigger the "open tiddler"

If the user is using an input method for example Chinese IME or Japanese IME, typing "Enter" normally means commit what is being compsed, but in commandpalette it will trigger an action.

Can you wait until IME submit, then allow listener of "Enter" to work?

  document.addEventListener('compositionstart', () => {
    isTypingUsingIME = true;
  });
  document.addEventListener('compositionend', () => {
    isTypingUsingIME = false;
  });

open-command-palette-selection with additional parameters - improvement suggested

recommend this function in $:/core/modules/widgets/commandpalettewidget.js is modified to:

		openPaletteSelection(e) {
			let selection = this.getCurrentSelection();
			if (e.param !== undefined) {
				e.param += selection;
			} else {
				e.param = selection;
			}
			
			this.openPalette(e);
		}

which allows parameters to be passed in addition to the selection. This enables rapid keyboard shortcuts that yield, for example, +selection, by passing + as a parameter to the open-command-palette-selection command. As an example, this improvement lets the user create a new tiddler from the selected text in two steps (key sequence to obtain +selection), followed by the enter key.

Without this modification, the parameter passed to the open-command-palette-selection command is ignored and overriden by the text selection.

Lastly, if the option to always pass selection is enabled, using the open-command-palette-selection command results in the selection text to be duplicated, which appears to be undesirable to me.

For your consideration....

Thank you

Question: How to trigger command palette using js?

I'd like to make a plugin that add a context menu item to trigger command palette.

How to do this? Is there a js API call to do so? Or is there a tw-message to do so? And how to fill palette with text with such an API call or message?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.