forked from gitea/gitea
		
	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:
		
							parent
							
								
									8bc8ca1953
								
							
						
					
					
						commit
						9f6bc7c6f4
					
				
							
								
								
									
										14
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -13,6 +13,7 @@ | ||||
|         "@citation-js/plugin-software-formats": "0.6.1", | ||||
|         "@claviska/jquery-minicolors": "2.3.6", | ||||
|         "@github/markdown-toolbar-element": "2.1.1", | ||||
|         "@github/text-expander-element": "2.3.0", | ||||
|         "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", | ||||
|         "@primer/octicons": "18.3.0", | ||||
|         "@vue/compiler-sfc": "3.2.47", | ||||
| @ -840,11 +841,24 @@ | ||||
|         "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@github/combobox-nav": { | ||||
|       "version": "2.1.7", | ||||
|       "resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.1.7.tgz", | ||||
|       "integrity": "sha512-Webx0W5iTpkk5Chy9dB/1BEUORQ0qrwui8HaaVBiy75W2VOJg96WTuKj1rXENAJ3XTMhdEF53bn0LYfvP0EKvg==" | ||||
|     }, | ||||
|     "node_modules/@github/markdown-toolbar-element": { | ||||
|       "version": "2.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-2.1.1.tgz", | ||||
|       "integrity": "sha512-J++rpd5H9baztabJQB82h26jtueOeBRSTqetk9Cri+Lj/s28ndu6Tovn0uHQaOKtBWDobFunk9b5pP5vcqt7cA==" | ||||
|     }, | ||||
|     "node_modules/@github/text-expander-element": { | ||||
|       "version": "2.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.3.0.tgz", | ||||
|       "integrity": "sha512-E1KCxTOA/7Y4dP5g7vXbfRDFU6/SjU0TuJxx6JMwvxzI/NlBrXyXsx/fjFskD2T/un6i6CNKnXu1ZwExDLjcqw==", | ||||
|       "dependencies": { | ||||
|         "@github/combobox-nav": "^2.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@humanwhocodes/config-array": { | ||||
|       "version": "0.11.8", | ||||
|       "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", | ||||
|  | ||||
| @ -13,6 +13,7 @@ | ||||
|     "@citation-js/plugin-software-formats": "0.6.1", | ||||
|     "@claviska/jquery-minicolors": "2.3.6", | ||||
|     "@github/markdown-toolbar-element": "2.1.1", | ||||
|     "@github/text-expander-element": "2.3.0", | ||||
|     "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", | ||||
|     "@primer/octicons": "18.3.0", | ||||
|     "@vue/compiler-sfc": "3.2.47", | ||||
|  | ||||
| @ -39,7 +39,9 @@ Template Attributes: | ||||
| 				<span class="markdown-toolbar-button markdown-switch-easymde">{{svg "octicon-arrow-switch"}}</span> | ||||
| 			</div> | ||||
| 		</markdown-toolbar> | ||||
| 		<textarea class="markdown-text-editor js-quick-submit" name="{{.TextareaName}}" placeholder="{{.TextareaPlaceholder}}">{{.TextareaContent}}</textarea> | ||||
| 		<text-expander keys=": @"> | ||||
| 			<textarea class="markdown-text-editor js-quick-submit" name="{{.TextareaName}}" placeholder="{{.TextareaPlaceholder}}">{{.TextareaContent}}</textarea> | ||||
| 		</text-expander> | ||||
| 	</div> | ||||
| 	<div class="ui tab markup" data-tab-panel="markdown-previewer"> | ||||
| 		{{.locale.Tr "loading"}} | ||||
|  | ||||
| @ -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; | ||||
| } | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 silverwind
						silverwind