diff options
author | silverwind <me@silverwind.io> | 2023-04-09 18:18:45 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-09 12:18:45 -0400 |
commit | 9f6bc7c6f4f657c3245db7cea44c85454d5d8606 (patch) | |
tree | b76705594765c611586d98e1976c90300438fbf9 /web_src | |
parent | 8bc8ca1953e9edfe1677378472c321b3d33875fd (diff) | |
download | gitea-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.css | 63 | ||||
-rw-r--r-- | web_src/css/form.css | 17 | ||||
-rw-r--r-- | web_src/js/features/comp/ComboMarkdownEditor.js | 78 |
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) { |