mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08: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