aboutsummaryrefslogtreecommitdiffstats
path: root/web_src
diff options
context:
space:
mode:
authorsilverwind <me@silverwind.io>2023-04-09 18:18:45 +0200
committerGitHub <noreply@github.com>2023-04-09 12:18:45 -0400
commit9f6bc7c6f4f657c3245db7cea44c85454d5d8606 (patch)
treeb76705594765c611586d98e1976c90300438fbf9 /web_src
parent8bc8ca1953e9edfe1677378472c321b3d33875fd (diff)
downloadgitea-9f6bc7c6f4f657c3245db7cea44c85454d5d8606.tar.gz
gitea-9f6bc7c6f4f657c3245db7cea44c85454d5d8606.zip
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">
Diffstat (limited to 'web_src')
-rw-r--r--web_src/css/editor-markdown.css63
-rw-r--r--web_src/css/form.css17
-rw-r--r--web_src/js/features/comp/ComboMarkdownEditor.js78
3 files changed, 154 insertions, 4 deletions
diff --git a/web_src/css/editor-markdown.css b/web_src/css/editor-markdown.css
index da64164aec..1a09b5d596 100644
--- a/web_src/css/editor-markdown.css
+++ b/web_src/css/editor-markdown.css
@@ -30,3 +30,66 @@
.combo-markdown-editor .CodeMirror-scroll {
max-height: calc(100vh - 200px);
}
+
+text-expander {
+ display: block;
+ position: relative;
+}
+
+text-expander .suggestions {
+ position: absolute;
+ min-width: 180px;
+ padding: 0;
+ margin-top: 24px;
+ list-style: none;
+ background: var(--color-box-body);
+ border-radius: 5px;
+ border: 1px solid var(--color-secondary);
+ box-shadow: 0 .5rem 1rem var(--color-shadow);
+}
+
+text-expander .suggestions li {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ padding: 4px 8px;
+ font-weight: 500;
+}
+
+text-expander .suggestions li + li {
+ border-top: 1px solid var(--color-secondary-alpha-40);
+}
+
+text-expander .suggestions li:first-child {
+ border-radius: 4px 4px 0 0;
+}
+
+text-expander .suggestions li:last-child {
+ border-radius: 0 0 4px 4px;
+}
+
+text-expander .suggestions li:only-child {
+ border-radius: 4px;
+}
+
+text-expander .suggestions li:hover {
+ background: var(--color-hover);
+}
+
+text-expander .suggestions .fullname {
+ font-weight: normal;
+ margin-left: 4px;
+ color: var(--color-text-light-1);
+}
+
+text-expander .suggestions li[aria-selected="true"],
+text-expander .suggestions li[aria-selected="true"] span {
+ background: var(--color-primary);
+ color: var(--color-primary-contrast);
+}
+
+text-expander .suggestions img {
+ width: 24px;
+ height: 24px;
+ margin-right: 8px;
+}
diff --git a/web_src/css/form.css b/web_src/css/form.css
index ffcf5794b9..85d1136de8 100644
--- a/web_src/css/form.css
+++ b/web_src/css/form.css
@@ -1,3 +1,20 @@
+.ui.input textarea,
+.ui.form textarea,
+.ui.form input:not([type]),
+.ui.form input[type="date"],
+.ui.form input[type="datetime-local"],
+.ui.form input[type="email"],
+.ui.form input[type="number"],
+.ui.form input[type="password"],
+.ui.form input[type="search"],
+.ui.form input[type="tel"],
+.ui.form input[type="time"],
+.ui.form input[type="text"],
+.ui.form input[type="file"],
+.ui.form input[type="url"] {
+ transition: none;
+}
+
input,
textarea,
.ui.input > input,
diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js
index c1607a1da8..13b28da828 100644
--- a/web_src/js/features/comp/ComboMarkdownEditor.js
+++ b/web_src/js/features/comp/ComboMarkdownEditor.js
@@ -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) {