wekan/client/lib/textComplete.js
2026-01-21 07:03:46 +02:00

87 lines
3 KiB
JavaScript

// We use @textcomplete packages to integrate with our EscapeActions system.
// You should always use `createEscapeableTextComplete` or the jQuery extension
// `escapeableTextComplete` instead of the vanilla textcomplete.
import { Textcomplete } from '@textcomplete/core';
import { TextareaEditor } from '@textcomplete/textarea';
import { ContenteditableEditor } from '@textcomplete/contenteditable';
let dropdownMenuIsOpened = false;
/**
* Create an escapeable textcomplete instance for a textarea or contenteditable element
* @param {HTMLTextAreaElement|HTMLElement} element - The target element
* @param {Array} strategies - Array of strategy objects
* @param {Object} options - Additional options
* @returns {Textcomplete} The textcomplete instance
*/
export function createEscapeableTextComplete(element, strategies, options = {}) {
// Determine the appropriate editor based on element type
const isContentEditable = element.isContentEditable || element.contentEditable === 'true';
const Editor = isContentEditable ? ContenteditableEditor : TextareaEditor;
const editor = new Editor(element);
// Merge default options
const mergedOptions = {
dropdown: {
className: 'textcomplete-dropdown',
maxCount: 10,
placement: 'bottom',
...options.dropdown,
},
};
const textcomplete = new Textcomplete(editor, strategies, mergedOptions);
// When the autocomplete menu is shown we want both a press of both `Tab`
// or `Enter` to validate the auto-completion. We also need to stop the
// event propagation to prevent EscapeActions side effect, for instance the
// minicard submission (on `Enter`) or going on the next column (on `Tab`).
element.addEventListener('keydown', (evt) => {
if (dropdownMenuIsOpened && (evt.keyCode === 9 || evt.keyCode === 13)) {
evt.stopPropagation();
}
});
// Track dropdown state for EscapeActions integration
// Since @textcomplete automatically closes when Escape is pressed, we
// integrate with our EscapeActions system by tracking open/close state.
textcomplete.on('show', () => {
dropdownMenuIsOpened = true;
});
textcomplete.on('selected', () => {
EscapeActions.preventNextClick();
});
textcomplete.on('hidden', () => {
Tracker.afterFlush(() => {
// XXX Hack. We unfortunately need to set a setTimeout here to make the
// `noClickEscapeOn` work below, otherwise clicking on a autocomplete
// item will close both the autocomplete menu (as expected) but also the
// next item in the stack (for example the minicard editor) which we
// don't want.
setTimeout(() => {
dropdownMenuIsOpened = false;
}, 100);
});
});
return textcomplete;
}
// jQuery extension for backward compatibility
$.fn.escapeableTextComplete = function(strategies, options = {}) {
return this.each(function() {
createEscapeableTextComplete(this, strategies, options);
});
};
EscapeActions.register(
'textcomplete',
() => {},
() => dropdownMenuIsOpened,
{
noClickEscapeOn: '.textcomplete-dropdown',
},
);