1
0
Fork 0
forked from forgejo/forgejo

Replace tribute with text-expander-element for textarea (#23985)

The completion popup now behaves now much more as expected than before
for the raw textarea:
- You can press <kbd>Tab</kbd> or <kbd>Enter</kbd> once the completion
popup is open to accept the selected item
- The menu does not close automatically when moving the cursor
- When you delete text, previously correct suggestions are shown again
- If you delete all text until the opening char (`@` or `:`) after
applying a suggestion, the popup reappears again
- Menu UI has been improved

<img width="278" alt="Screenshot 2023-04-07 at 19 43 42"
src="https://user-images.githubusercontent.com/115237/230653601-d6517b9f-0988-445e-aa57-5ebfaf5039f3.png">
This commit is contained in:
silverwind 2023-04-09 18:18:45 +02:00 committed by GitHub
parent 8bc8ca1953
commit 9f6bc7c6f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 172 additions and 5 deletions

View file

@ -1,4 +1,5 @@
import '@github/markdown-toolbar-element';
import '@github/text-expander-element';
import $ from 'jquery';
import {attachTribute} from '../tribute.js';
import {hideElem, showElem, autosize} from '../../utils/dom.js';
@ -6,8 +7,10 @@ import {initEasyMDEImagePaste, initTextareaImagePaste} from './ImagePaste.js';
import {initMarkupContent} from '../../markup/content.js';
import {handleGlobalEnterQuickSubmit} from './QuickSubmit.js';
import {attachRefIssueContextPopup} from '../contextpopup.js';
import {emojiKeys, emojiString} from '../emoji.js';
let elementIdCounter = 0;
const maxExpanderMatches = 6;
/**
* validate if the given textarea is non-empty.
@ -40,13 +43,10 @@ class ComboMarkdownEditor {
async init() {
this.prepareEasyMDEToolbarActions();
this.setupTab();
this.setupDropzone();
this.setupTextarea();
await attachTribute(this.textarea, {mentions: true, emoji: true});
this.setupExpander();
if (this.userPreferredEditor === 'easymde') {
await this.switchToEasyMDE();
@ -83,6 +83,76 @@ class ComboMarkdownEditor {
}
}
setupExpander() {
const expander = this.container.querySelector('text-expander');
expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}) => {
if (key === ':') {
const matches = [];
for (const name of emojiKeys) {
if (name.includes(text)) {
matches.push(name);
if (matches.length >= maxExpanderMatches) break;
}
}
if (!matches.length) return provide({matched: false});
const ul = document.createElement('ul');
ul.classList.add('suggestions');
for (const name of matches) {
const emoji = emojiString(name);
const li = document.createElement('li');
li.setAttribute('role', 'option');
li.setAttribute('data-value', emoji);
li.textContent = `${emoji} ${name}`;
ul.append(li);
}
provide({matched: true, fragment: ul});
} else if (key === '@') {
const matches = [];
for (const obj of window.config.tributeValues) {
if (obj.key.includes(text)) {
matches.push(obj);
if (matches.length >= maxExpanderMatches) break;
}
}
if (!matches.length) return provide({matched: false});
const ul = document.createElement('ul');
ul.classList.add('suggestions');
for (const {value, name, fullname, avatar} of matches) {
const li = document.createElement('li');
li.setAttribute('role', 'option');
li.setAttribute('data-value', `${key}${value}`);
const img = document.createElement('img');
img.src = avatar;
li.append(img);
const nameSpan = document.createElement('span');
nameSpan.textContent = name;
li.append(nameSpan);
if (fullname && fullname.toLowerCase() !== name) {
const fullnameSpan = document.createElement('span');
fullnameSpan.classList.add('fullname');
fullnameSpan.textContent = fullname;
li.append(fullnameSpan);
}
ul.append(li);
}
provide({matched: true, fragment: ul});
}
});
expander?.addEventListener('text-expander-value', ({detail}) => {
if (detail?.item) {
detail.value = detail.item.getAttribute('data-value');
}
});
}
setupDropzone() {
const dropzoneParentContainer = this.container.getAttribute('data-dropzone-parent-container');
if (dropzoneParentContainer) {