first version of autocomplete. see #157
This commit is contained in:
parent
ed596041ed
commit
4230318b10
3 changed files with 160 additions and 4 deletions
|
|
@ -163,6 +163,44 @@ table .sort.desc {
|
|||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.autocomplete-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.autocomplete-list {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
z-index: 1000;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.autocomplete-list li {
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.autocomplete-list li:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.autocomplete-list li.active {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
|
|
@ -196,6 +234,18 @@ table .sort.desc {
|
|||
|
||||
.storage_space{
|
||||
color: rgb(182, 182, 182);
|
||||
}
|
||||
}
|
||||
|
||||
.autocomplete-list {
|
||||
background-color: #2E2F36;
|
||||
border: 1px solid #2E2F36;
|
||||
}
|
||||
|
||||
.autocomplete-list li:hover {
|
||||
background-color: #4c4e58;
|
||||
}
|
||||
|
||||
.autocomplete-list li.active {
|
||||
background-color: #4c4e58;
|
||||
}
|
||||
}
|
||||
|
|
@ -34,7 +34,10 @@
|
|||
<td rowspan="2" class="w25">
|
||||
<label for="txtTextNew">*__MSG_customPrompts_form_label_Text__:</label>
|
||||
<br>
|
||||
<textarea id="txtTextNew" name="text" class="input_new text_output" tabindex="3"></textarea>
|
||||
<div class="autocomplete-container">
|
||||
<textarea id="txtTextNew" name="text" class="input_new text_output editor" tabindex="3"></textarea>
|
||||
<ul class="autocomplete-list hidden"></ul>
|
||||
</div>
|
||||
</td>
|
||||
<td class="w19">
|
||||
<label for="selectTypeNew">__MSG_customPrompts_add_to_menu__:</label>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
import { getPrompts, setDefaultPromptsProperties, setCustomPrompts, preparePromptsForExport, preparePromptsForImport } from "../js/mzta-prompts.js";
|
||||
import { isThunderbird128OrGreater, getCustomPromptsUsedSpace } from "../js/mzta-utils.js";
|
||||
import { taLogger } from "../js/mzta-logger.js";
|
||||
import { getPlaceholders } from "../js/mzta-placeholders.js";
|
||||
|
||||
var promptsList = null;
|
||||
var somethingChanged = false;
|
||||
|
|
@ -27,6 +28,7 @@ var positionMax_display = 0;
|
|||
var idnumMax = 0;
|
||||
var msgTimeout = null;
|
||||
let taLog = null;
|
||||
let autocompleteSuggestions = [];
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
|
|
@ -111,6 +113,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
});
|
||||
}
|
||||
|
||||
const textareas = document.querySelectorAll('.editor');
|
||||
autocompleteSuggestions = (await getPlaceholders(true)).map(p => '{%'+p.id+'%}');
|
||||
|
||||
// console.log('>>>>>>>>>>> suggestions: ' + JSON.stringify(suggestions));
|
||||
|
||||
textareas.forEach(textarea => textareaAutocomplete(textarea, autocompleteSuggestions));
|
||||
|
||||
i18n.updateDocument();
|
||||
|
||||
//To add a new item
|
||||
|
|
@ -274,7 +283,9 @@ function showItemRowEditor(tr) {
|
|||
tr.querySelector('.id_show').style.display = 'none';
|
||||
tr.querySelector('.name_output').style.display = 'inline';
|
||||
tr.querySelector('.name_show').style.display = 'none';
|
||||
tr.querySelector('.text_output').style.display = 'inline';
|
||||
const text_output = tr.querySelector('.text_output');
|
||||
text_output.style.display = 'inline';
|
||||
textareaAutocomplete(text_output, autocompleteSuggestions)
|
||||
tr.querySelector('.text_show').style.display = 'none';
|
||||
tr.querySelector('.type_output').style.display = 'inline';
|
||||
tr.querySelector('.type_show').style.display = 'none';
|
||||
|
|
@ -399,7 +410,7 @@ function loadPromptsList(values){
|
|||
let output = `<tr ` + ((values.is_default == 1) ? 'class="is_default"':'') + `>
|
||||
<td class="w08"><span class="id id_show"></span><input type="text" class="hiddendata id_output" value="` + values.id + `" /></td>
|
||||
<td class="w08"><span class="name name_show"></span><input type="text" class="hiddendata name_output" value="` + values.name + `" /></td>
|
||||
<td class="w40"><span class="text text_show"></span><textarea class="hiddendata text_output">` + values.text + `</textarea></td>
|
||||
<td class="w40"><span class="text text_show"></span><div class="autocomplete-container"><textarea class="hiddendata text_output editor">` + values.text + `</textarea><ul class="autocomplete-list hidden"></ul></div></td>
|
||||
<td class="w08"><span class="type_show">` + type_output + `</span>
|
||||
<select class="type_output hiddendata">
|
||||
<option value="0"` + ((values.type == "0") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_always__</option>
|
||||
|
|
@ -642,3 +653,95 @@ if(await isThunderbird128OrGreater()){
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
function textareaAutocomplete(textarea, suggestions) {
|
||||
const container = textarea.closest('.autocomplete-container');
|
||||
const autocompleteList = container.querySelector('.autocomplete-list');
|
||||
let activeIndex = -1;
|
||||
|
||||
textarea.addEventListener('input', () => {
|
||||
const cursorPosition = textarea.selectionStart;
|
||||
const text = textarea.value.substring(0, cursorPosition);
|
||||
const match = text.match(/{%[^\s]*$/);
|
||||
|
||||
if (match) {
|
||||
const lastWord = match[0];
|
||||
const matches = suggestions.filter(s => s.startsWith(lastWord));
|
||||
showSuggestions(matches, autocompleteList);
|
||||
} else {
|
||||
hideSuggestions(autocompleteList);
|
||||
}
|
||||
});
|
||||
|
||||
textarea.addEventListener('keydown', (e) => {
|
||||
const items = autocompleteList.querySelectorAll('li');
|
||||
if (items.length > 0) {
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
activeIndex = (activeIndex + 1) % items.length;
|
||||
updateActiveSuggestion(items, activeIndex);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
activeIndex = (activeIndex - 1 + items.length) % items.length;
|
||||
updateActiveSuggestion(items, activeIndex);
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (activeIndex >= 0 && activeIndex < items.length) {
|
||||
insertAutocomplete(items[activeIndex].textContent, textarea);
|
||||
hideSuggestions(autocompleteList);
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
hideSuggestions(autocompleteList);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function showSuggestions(matches, autocompleteList) {
|
||||
autocompleteList.innerHTML = '';
|
||||
activeIndex = -1;
|
||||
if (matches.length === 0) {
|
||||
hideSuggestions(autocompleteList);
|
||||
return;
|
||||
}
|
||||
matches.forEach(match => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = match;
|
||||
li.addEventListener('click', () => {
|
||||
insertAutocomplete(match, textarea);
|
||||
hideSuggestions(autocompleteList);
|
||||
});
|
||||
autocompleteList.appendChild(li);
|
||||
});
|
||||
autocompleteList.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function hideSuggestions(autocompleteList) {
|
||||
autocompleteList.classList.add('hidden');
|
||||
activeIndex = -1;
|
||||
}
|
||||
|
||||
function updateActiveSuggestion(items, activeIndex) {
|
||||
items.forEach((item, index) => {
|
||||
item.classList.remove('active');
|
||||
if (index === activeIndex) {
|
||||
item.classList.add('active');
|
||||
item.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function insertAutocomplete(suggestion, textarea) {
|
||||
const cursorPosition = textarea.selectionStart;
|
||||
const textBefore = textarea.value.substring(0, cursorPosition);
|
||||
const textAfter = textarea.value.substring(cursorPosition);
|
||||
const match = textBefore.match(/{%[^\s]*$/);
|
||||
if (match) {
|
||||
const lastWord = match[0];
|
||||
const completion = suggestion.substring(lastWord.length);
|
||||
const newText = textBefore + completion + textAfter;
|
||||
textarea.value = newText;
|
||||
const newCursorPosition = cursorPosition + completion.length;
|
||||
textarea.setSelectionRange(newCursorPosition, newCursorPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue