mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	Replace fomantic popup module with tippy.js (#20428)
- replace fomantic popup module with tippy.js - fix chaining and add comment - add 100ms delay to tooltips - stopwatch improvments, raise default maxWidth - update web_src/js/features/common-global.js - use type=submit instead of js
This commit is contained in:
		| @@ -1,24 +1,15 @@ | ||||
| import $ from 'jquery'; | ||||
| import {showTemporaryTooltip} from '../modules/tippy.js'; | ||||
|  | ||||
| const {copy_success, copy_error} = window.config.i18n; | ||||
|  | ||||
| function onSuccess(btn) { | ||||
|   btn.setAttribute('data-variation', 'inverted tiny'); | ||||
|   $(btn).popup('destroy'); | ||||
|   const oldContent = btn.getAttribute('data-content'); | ||||
|   btn.setAttribute('data-content', copy_success); | ||||
|   $(btn).popup('show'); | ||||
|   btn.setAttribute('data-content', oldContent || ''); | ||||
| export async function copyToClipboard(text) { | ||||
|   try { | ||||
|     await navigator.clipboard.writeText(text); | ||||
|   } catch { | ||||
|     return fallbackCopyToClipboard(text); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| function onError(btn) { | ||||
|   btn.setAttribute('data-variation', 'inverted tiny'); | ||||
|   const oldContent = btn.getAttribute('data-content'); | ||||
|   $(btn).popup('destroy'); | ||||
|   btn.setAttribute('data-content', copy_error); | ||||
|   $(btn).popup('show'); | ||||
|   btn.setAttribute('data-content', oldContent || ''); | ||||
| } | ||||
|  | ||||
|  | ||||
| // Fallback to use if navigator.clipboard doesn't exist. Achieved via creating | ||||
| // a temporary textarea element, selecting the text, and using document.execCommand | ||||
| @@ -60,16 +51,8 @@ export default function initGlobalCopyToClipboardListener() { | ||||
|         e.preventDefault(); | ||||
|  | ||||
|         (async() => { | ||||
|           try { | ||||
|             await navigator.clipboard.writeText(text); | ||||
|             onSuccess(target); | ||||
|           } catch { | ||||
|             if (fallbackCopyToClipboard(text)) { | ||||
|               onSuccess(target); | ||||
|             } else { | ||||
|               onError(target); | ||||
|             } | ||||
|           } | ||||
|           const success = await copyToClipboard(text); | ||||
|           showTemporaryTooltip(target, success ? copy_success : copy_error); | ||||
|         })(); | ||||
|  | ||||
|         break; | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import {initCompColorPicker} from './comp/ColorPicker.js'; | ||||
| import {showGlobalErrorMessage} from '../bootstrap.js'; | ||||
| import {attachDropdownAria} from './aria.js'; | ||||
| import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js'; | ||||
| import {initTooltip} from '../modules/tippy.js'; | ||||
|  | ||||
| const {appUrl, csrfToken} = window.config; | ||||
|  | ||||
| @@ -62,18 +63,10 @@ export function initGlobalButtonClickOnEnter() { | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export function initPopup(target) { | ||||
|   const $el = $(target); | ||||
|   const attr = $el.attr('data-variation'); | ||||
|   const attrs = attr ? attr.split(' ') : []; | ||||
|   const variations = new Set([...attrs, 'inverted', 'tiny']); | ||||
|   $el.attr('data-variation', [...variations].join(' ')).popup(); | ||||
| } | ||||
|  | ||||
| export function initGlobalPopups() { | ||||
|   $('.tooltip').each((_, el) => { | ||||
|     initPopup(el); | ||||
|   }); | ||||
| export function initGlobalTooltips() { | ||||
|   for (const el of document.getElementsByClassName('tooltip')) { | ||||
|     initTooltip(el); | ||||
|   } | ||||
| } | ||||
|  | ||||
| export function initGlobalCommon() { | ||||
| @@ -106,7 +99,12 @@ export function initGlobalCommon() { | ||||
|   $uiDropdowns.filter('.jump').dropdown({ | ||||
|     action: 'hide', | ||||
|     onShow() { | ||||
|       $('.tooltip').popup('hide'); | ||||
|       // hide associated tooltip while dropdown is open | ||||
|       this._tippy?.hide(); | ||||
|       this._tippy?.disable(); | ||||
|     }, | ||||
|     onHide() { | ||||
|       this._tippy?.enable(); | ||||
|     }, | ||||
|     fullTextSearch: 'exact' | ||||
|   }); | ||||
| @@ -122,13 +120,6 @@ export function initGlobalCommon() { | ||||
|  | ||||
|   $('.ui.checkbox').checkbox(); | ||||
|  | ||||
|   $('.top.menu .tooltip').popup({ | ||||
|     onShow() { | ||||
|       if ($('.top.menu .menu.transition').hasClass('visible')) { | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|   $('.tabular.menu .item').tab(); | ||||
|   $('.tabable.menu .item').tab(); | ||||
|  | ||||
|   | ||||
| @@ -1,16 +1,20 @@ | ||||
| import $ from 'jquery'; | ||||
| import {createTippy} from '../../modules/tippy.js'; | ||||
|  | ||||
| const {csrfToken} = window.config; | ||||
|  | ||||
| export function initCompReactionSelector(parent) { | ||||
|   let reactions = ''; | ||||
|   let selector = 'a.label'; | ||||
|   if (!parent) { | ||||
|     parent = $(document); | ||||
|     reactions = '.reactions > '; | ||||
|     selector = `.reactions ${selector}`; | ||||
|   } | ||||
|  | ||||
|   parent.find(`${reactions}a.label`).popup({position: 'bottom left', metadata: {content: 'title', title: 'none'}}); | ||||
|   for (const el of parent[0].querySelectorAll(selector)) { | ||||
|     createTippy(el, {placement: 'bottom-start', content: el.getAttribute('data-title')}); | ||||
|   } | ||||
|  | ||||
|   parent.find(`.select-reaction > .menu > .item, ${reactions}a.label`).on('click', function (e) { | ||||
|   parent.find(`.select-reaction > .menu > .item, ${selector}`).on('click', function (e) { | ||||
|     e.preventDefault(); | ||||
|  | ||||
|     if ($(this).hasClass('disabled')) return; | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| import $ from 'jquery'; | ||||
| import {svg} from '../svg.js'; | ||||
| import {invertFileFolding} from './file-fold.js'; | ||||
| import {createTippy} from '../modules/tippy.js'; | ||||
| import {copyToClipboard} from './clipboard.js'; | ||||
|  | ||||
| function changeHash(hash) { | ||||
|   if (window.history.pushState) { | ||||
| @@ -39,13 +41,13 @@ function selectRange($list, $select, $from) { | ||||
|     $viewGitBlame.attr('href', href); | ||||
|   }; | ||||
|  | ||||
|   const updateCopyPermalinkHref = function(anchor) { | ||||
|   const updateCopyPermalinkUrl = function(anchor) { | ||||
|     if ($copyPermalink.length === 0) { | ||||
|       return; | ||||
|     } | ||||
|     let link = $copyPermalink.attr('data-clipboard-text'); | ||||
|     let link = $copyPermalink.attr('data-url'); | ||||
|     link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`; | ||||
|     $copyPermalink.attr('data-clipboard-text', link); | ||||
|     $copyPermalink.attr('data-url', link); | ||||
|   }; | ||||
|  | ||||
|   if ($from) { | ||||
| @@ -67,7 +69,7 @@ function selectRange($list, $select, $from) { | ||||
|  | ||||
|       updateIssueHref(`L${a}-L${b}`); | ||||
|       updateViewGitBlameFragment(`L${a}-L${b}`); | ||||
|       updateCopyPermalinkHref(`L${a}-L${b}`); | ||||
|       updateCopyPermalinkUrl(`L${a}-L${b}`); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| @@ -76,17 +78,36 @@ function selectRange($list, $select, $from) { | ||||
|  | ||||
|   updateIssueHref($select.attr('rel')); | ||||
|   updateViewGitBlameFragment($select.attr('rel')); | ||||
|   updateCopyPermalinkHref($select.attr('rel')); | ||||
|   updateCopyPermalinkUrl($select.attr('rel')); | ||||
| } | ||||
|  | ||||
| function showLineButton() { | ||||
|   if ($('.code-line-menu').length === 0) return; | ||||
|   $('.code-line-button').remove(); | ||||
|   $('.code-view td.lines-code.active').closest('tr').find('td:eq(0)').first().prepend( | ||||
|     $(`<button class="code-line-button">${svg('octicon-kebab-horizontal')}</button>`) | ||||
|   ); | ||||
|   $('.code-line-menu').appendTo($('.code-view')); | ||||
|   $('.code-line-button').popup({popup: $('.code-line-menu'), on: 'click'}); | ||||
|   const menu = document.querySelector('.code-line-menu'); | ||||
|   if (!menu) return; | ||||
|  | ||||
|   // remove all other line buttons | ||||
|   for (const el of document.querySelectorAll('.code-line-button')) { | ||||
|     el.remove(); | ||||
|   } | ||||
|  | ||||
|   // find active row and add button | ||||
|   const tr = document.querySelector('.code-view td.lines-code.active').closest('tr'); | ||||
|   const td = tr.querySelector('td'); | ||||
|   const btn = document.createElement('button'); | ||||
|   btn.classList.add('code-line-button'); | ||||
|   btn.innerHTML = svg('octicon-kebab-horizontal'); | ||||
|   td.prepend(btn); | ||||
|  | ||||
|   // put a copy of the menu back into DOM for the next click | ||||
|   btn.closest('.code-view').appendChild(menu.cloneNode(true)); | ||||
|  | ||||
|   createTippy(btn, { | ||||
|     trigger: 'click', | ||||
|     content: menu, | ||||
|     placement: 'right-start', | ||||
|     role: 'menu', | ||||
|     interactive: 'true', | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export function initRepoCodeView() { | ||||
| @@ -159,4 +180,9 @@ export function initRepoCodeView() { | ||||
|     const blob = await $.get(`${url}?${query}&anchor=${anchor}`); | ||||
|     currentTarget.closest('tr').outerHTML = blob; | ||||
|   }); | ||||
|   $(document).on('click', '.copy-line-permalink', async (e) => { | ||||
|     const success = await copyToClipboard(e.currentTarget.getAttribute('data-url')); | ||||
|     if (!success) return; | ||||
|     document.querySelector('.code-line-button')?._tippy?.hide(); | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import $ from 'jquery'; | ||||
| import {createTippy} from '../modules/tippy.js'; | ||||
|  | ||||
| const {csrfToken} = window.config; | ||||
|  | ||||
| @@ -58,12 +59,12 @@ export function initRepoCommitLastCommitLoader() { | ||||
| export function initCommitStatuses() { | ||||
|   $('.commit-statuses-trigger').each(function () { | ||||
|     const positionRight = $('.repository.file.list').length > 0 || $('.repository.diff').length > 0; | ||||
|     const popupPosition = positionRight ? 'right center' : 'left center'; | ||||
|     $(this) | ||||
|       .popup({ | ||||
|         on: 'click', | ||||
|         lastResort: popupPosition, // prevent error message "Popup does not fit within the boundaries of the viewport" | ||||
|         position: popupPosition, | ||||
|       }); | ||||
|  | ||||
|     createTippy(this, { | ||||
|       trigger: 'click', | ||||
|       content: this.nextSibling, | ||||
|       placement: positionRight ? 'right' : 'left', | ||||
|       interactive: true, | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import {initCompReactionSelector} from './comp/ReactionSelector.js'; | ||||
| import {initRepoIssueContentHistory} from './repo-issue-content.js'; | ||||
| import {validateTextareaNonEmpty} from './comp/EasyMDE.js'; | ||||
| import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles} from './pull-view-file.js'; | ||||
| import {initPopup} from './common-global.js'; | ||||
| import {initTooltip} from '../modules/tippy.js'; | ||||
|  | ||||
| const {csrfToken} = window.config; | ||||
|  | ||||
| @@ -53,7 +53,7 @@ export function initRepoDiffConversationForm() { | ||||
|     const newConversationHolder = $(await $.post(form.attr('action'), form.serialize())); | ||||
|     const {path, side, idx} = newConversationHolder.data(); | ||||
|  | ||||
|     initPopup(newConversationHolder.find('.tooltip')); | ||||
|     initTooltip(newConversationHolder.find('.tooltip')); | ||||
|     form.closest('.conversation-holder').replaceWith(newConversationHolder); | ||||
|     if (form.closest('tr').data('line-type') === 'same') { | ||||
|       $(`[data-path="${path}"] a.add-code-comment[data-idx="${idx}"]`).addClass('invisible'); | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import attachTribute from './tribute.js'; | ||||
| import {createCommentEasyMDE, getAttachedEasyMDE} from './comp/EasyMDE.js'; | ||||
| import {initEasyMDEImagePaste} from './comp/ImagePaste.js'; | ||||
| import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js'; | ||||
| import {initTooltip, showTemporaryTooltip} from '../modules/tippy.js'; | ||||
|  | ||||
| const {appSubUrl, csrfToken} = window.config; | ||||
|  | ||||
| @@ -278,7 +279,8 @@ export function initRepoPullRequestAllowMaintainerEdit() { | ||||
|  | ||||
|   const promptTip = $checkbox.attr('data-prompt-tip'); | ||||
|   const promptError = $checkbox.attr('data-prompt-error'); | ||||
|   $checkbox.popup({content: promptTip}); | ||||
|  | ||||
|   initTooltip($checkbox[0], {content: promptTip}); | ||||
|   $checkbox.checkbox({ | ||||
|     'onChange': () => { | ||||
|       const checked = $checkbox.checkbox('is checked'); | ||||
| @@ -288,14 +290,7 @@ export function initRepoPullRequestAllowMaintainerEdit() { | ||||
|       $.ajax({url, type: 'POST', | ||||
|         data: {_csrf: csrfToken, allow_maintainer_edit: checked}, | ||||
|         error: () => { | ||||
|           $checkbox.popup({ | ||||
|             content: promptError, | ||||
|             onHidden: () => { | ||||
|               // the error popup should be shown only once, then we restore the popup to the default message | ||||
|               $checkbox.popup({content: promptTip}); | ||||
|             }, | ||||
|           }); | ||||
|           $checkbox.popup('show'); | ||||
|           showTemporaryTooltip($checkbox[0], promptError); | ||||
|         }, | ||||
|         complete: () => { | ||||
|           $checkbox.checkbox('set enabled'); | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import $ from 'jquery'; | ||||
| import prettyMilliseconds from 'pretty-ms'; | ||||
| import {createTippy} from '../modules/tippy.js'; | ||||
|  | ||||
| const {appSubUrl, csrfToken, notificationSettings, enableTimeTracking} = window.config; | ||||
|  | ||||
| @@ -8,21 +9,21 @@ export function initStopwatch() { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   const stopwatchEl = $('.active-stopwatch-trigger'); | ||||
|   const stopwatchEl = document.querySelector('.active-stopwatch-trigger'); | ||||
|   const stopwatchPopup = document.querySelector('.active-stopwatch-popup'); | ||||
|  | ||||
|   if (!stopwatchEl.length) { | ||||
|   if (!stopwatchEl || !stopwatchPopup) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   stopwatchEl.removeAttr('href'); // intended for noscript mode only | ||||
|   stopwatchEl.popup({ | ||||
|     position: 'bottom right', | ||||
|     hoverable: true, | ||||
|   }); | ||||
|   stopwatchEl.removeAttribute('href'); // intended for noscript mode only | ||||
|  | ||||
|   // form handlers | ||||
|   $('form > button', stopwatchEl).on('click', function () { | ||||
|     $(this).parent().trigger('submit'); | ||||
|   createTippy(stopwatchEl, { | ||||
|     content: stopwatchPopup, | ||||
|     placement: 'bottom-end', | ||||
|     trigger: 'click', | ||||
|     maxWidth: 'none', | ||||
|     interactive: true, | ||||
|   }); | ||||
|  | ||||
|   // global stop watch (in the head_navbar), it should always work in any case either the EventSource or the PeriodicPoller is used. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user