diff options
author | silverwind <me@silverwind.io> | 2022-08-09 14:37:34 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-09 14:37:34 +0200 |
commit | 1b2cd4c4e19c78390be329b4a3ad50ff8857ca8d (patch) | |
tree | c3c9af67b599f92af60c9cd5bb7feee056d97734 /web_src | |
parent | 36f9ee5813beba0fc4b394a5db636f76afc5cc38 (diff) | |
download | gitea-1b2cd4c4e19c78390be329b4a3ad50ff8857ca8d.tar.gz gitea-1b2cd4c4e19c78390be329b4a3ad50ff8857ca8d.zip |
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
Diffstat (limited to 'web_src')
-rw-r--r-- | web_src/fomantic/build/semantic.css | 421 | ||||
-rw-r--r-- | web_src/fomantic/build/semantic.js | 1542 | ||||
-rw-r--r-- | web_src/fomantic/semantic.json | 1 | ||||
-rw-r--r-- | web_src/js/components/DashboardRepoList.js | 5 | ||||
-rw-r--r-- | web_src/js/features/clipboard.js | 37 | ||||
-rw-r--r-- | web_src/js/features/common-global.js | 31 | ||||
-rw-r--r-- | web_src/js/features/comp/ReactionSelector.js | 12 | ||||
-rw-r--r-- | web_src/js/features/repo-code.js | 50 | ||||
-rw-r--r-- | web_src/js/features/repo-commit.js | 15 | ||||
-rw-r--r-- | web_src/js/features/repo-diff.js | 4 | ||||
-rw-r--r-- | web_src/js/features/repo-issue.js | 13 | ||||
-rw-r--r-- | web_src/js/features/stopwatch.js | 21 | ||||
-rw-r--r-- | web_src/js/index.js | 4 | ||||
-rw-r--r-- | web_src/js/modules/tippy.js | 48 | ||||
-rw-r--r-- | web_src/less/_base.less | 39 | ||||
-rw-r--r-- | web_src/less/modules/tippy.less | 37 |
16 files changed, 179 insertions, 2101 deletions
diff --git a/web_src/fomantic/build/semantic.css b/web_src/fomantic/build/semantic.css index e60d06b1ff..6ea20c3a8c 100644 --- a/web_src/fomantic/build/semantic.css +++ b/web_src/fomantic/build/semantic.css @@ -34447,427 +34447,6 @@ Floated Menu / Item Site Overrides *******************************/ /*! - * # Fomantic-UI - Popup - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/******************************* - Popup -*******************************/ - -.ui.popup { - display: none; - position: absolute; - top: 0; - right: 0; - /* Fixes content being squished when inline (moz only) */ - min-width: -webkit-min-content; - min-width: -moz-min-content; - min-width: min-content; - z-index: 1900; - border: 1px solid #D4D4D5; - line-height: 1.4285em; - max-width: 250px; - background: #FFFFFF; - padding: 0.833em 1em; - font-weight: normal; - font-style: normal; - color: rgba(0, 0, 0, 0.87); - border-radius: 0.28571429rem; - box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15); -} - -.ui.popup > .header { - padding: 0; - font-family: var(--fonts-regular); - font-size: 1.14285714em; - line-height: 1.2; - font-weight: 500; -} - -.ui.popup > .header + .content { - padding-top: 0.5em; -} - -.ui.popup:before { - position: absolute; - content: ''; - width: 0.71428571em; - height: 0.71428571em; - background: #FFFFFF; - transform: rotate(45deg); - z-index: 1901; - box-shadow: 1px 1px 0 0 #bababc; -} - -/******************************* - Types -*******************************/ - -/*-------------- - Spacing ----------------*/ - -.ui.popup { - margin: 0; -} - -/* Extending from Top */ - -.ui.top.popup { - margin: 0 0 0.71428571em; -} - -.ui.top.left.popup { - transform-origin: left bottom; -} - -.ui.top.center.popup { - transform-origin: center bottom; -} - -.ui.top.right.popup { - transform-origin: right bottom; -} - -/* Extending from Vertical Center */ - -.ui.left.center.popup { - margin: 0 0.71428571em 0 0; - transform-origin: right 50%; -} - -.ui.right.center.popup { - margin: 0 0 0 0.71428571em; - transform-origin: left 50%; -} - -/* Extending from Bottom */ - -.ui.bottom.popup { - margin: 0.71428571em 0 0; -} - -.ui.bottom.left.popup { - transform-origin: left top; -} - -.ui.bottom.center.popup { - transform-origin: center top; -} - -.ui.bottom.right.popup { - transform-origin: right top; -} - -/*-------------- - Pointer - ---------------*/ - -/*--- Below ---*/ - -.ui.bottom.center.popup:before { - margin-left: -0.30714286em; - top: -0.30714286em; - left: 50%; - right: auto; - bottom: auto; - box-shadow: -1px -1px 0 0 #bababc; -} - -.ui.bottom.left.popup { - margin-left: 0; -} - -/*rtl:rename*/ - -.ui.bottom.left.popup:before { - top: -0.30714286em; - left: 1em; - right: auto; - bottom: auto; - margin-left: 0; - box-shadow: -1px -1px 0 0 #bababc; -} - -.ui.bottom.right.popup { - margin-right: 0; -} - -/*rtl:rename*/ - -.ui.bottom.right.popup:before { - top: -0.30714286em; - right: 1em; - bottom: auto; - left: auto; - margin-left: 0; - box-shadow: -1px -1px 0 0 #bababc; -} - -/*--- Above ---*/ - -.ui.top.center.popup:before { - top: auto; - right: auto; - bottom: -0.30714286em; - left: 50%; - margin-left: -0.30714286em; -} - -.ui.top.left.popup { - margin-left: 0; -} - -/*rtl:rename*/ - -.ui.top.left.popup:before { - bottom: -0.30714286em; - left: 1em; - top: auto; - right: auto; - margin-left: 0; -} - -.ui.top.right.popup { - margin-right: 0; -} - -/*rtl:rename*/ - -.ui.top.right.popup:before { - bottom: -0.30714286em; - right: 1em; - top: auto; - left: auto; - margin-left: 0; -} - -/*--- Left Center ---*/ - -/*rtl:rename*/ - -.ui.left.center.popup:before { - top: 50%; - right: -0.30714286em; - bottom: auto; - left: auto; - margin-top: -0.30714286em; - box-shadow: 1px -1px 0 0 #bababc; -} - -/*--- Right Center ---*/ - -/*rtl:rename*/ - -.ui.right.center.popup:before { - top: 50%; - left: -0.30714286em; - bottom: auto; - right: auto; - margin-top: -0.30714286em; - box-shadow: -1px 1px 0 0 #bababc; -} - -.ui.right.center.popup:before, -.ui.left.center.popup:before { - background: #FFFFFF; -} - -/* Arrow Color By Location */ - -.ui.bottom.popup:before { - background: #FFFFFF; -} - -.ui.top.popup:before { - background: #FFFFFF; -} - -/* Inverted Arrow Color */ - -.ui.inverted.bottom.popup:before { - background: #1B1C1D; -} - -.ui.inverted.right.center.popup:before, -.ui.inverted.left.center.popup:before { - background: #1B1C1D; -} - -.ui.inverted.top.popup:before { - background: #1B1C1D; -} - -/******************************* - Coupling -*******************************/ - -/* Immediate Nested Grid */ - -.ui.popup > .ui.grid:not(.padded) { - width: calc(100% + 1.75rem); - margin: -0.7rem -0.875rem; -} - -/******************************* - States -*******************************/ - -.ui.loading.popup { - display: block; - visibility: hidden; - z-index: -1; -} - -.ui.animating.popup, -.ui.visible.popup { - display: block; -} - -.ui.visible.popup { - transform: translateZ(0); - -webkit-backface-visibility: hidden; - backface-visibility: hidden; -} - -/******************************* - Variations -*******************************/ - -/*-------------- - Basic - ---------------*/ - -.ui.basic.popup:before { - display: none; -} - -.ui.fixed.popup { - width: 250px; -} - -/*-------------- - Wide - ---------------*/ - -.ui.wide.popup { - max-width: 350px; -} - -.ui.wide.popup.fixed { - width: 350px; -} - -.ui[class*="very wide"].popup { - max-width: 550px; -} - -.ui[class*="very wide"].popup.fixed { - width: 550px; -} - -@media only screen and (max-width: 767.98px) { - .ui.wide.popup, - .ui[class*="very wide"].popup { - max-width: 250px; - } - - .ui.wide.popup.fixed, - .ui[class*="very wide"].popup.fixed { - width: 250px; - } -} - -/*-------------- - Fluid - ---------------*/ - -.ui.fluid.popup { - width: 100%; - max-width: none; -} - -/*-------------- - Colors - ---------------*/ - -/* Inverted colors */ - -.ui.inverted.popup { - background: #1B1C1D; - color: #FFFFFF; - border: none; - box-shadow: none; -} - -.ui.inverted.popup .header { - background-color: none; - color: #FFFFFF; -} - -.ui.inverted.popup:before { - background-color: #1B1C1D; - box-shadow: none !important; -} - -/*-------------- - Flowing - ---------------*/ - -.ui.flowing.popup { - max-width: none; -} - -/*-------------- - Sizes ----------------*/ - -.ui.popup { - font-size: 1rem; -} - -.ui.mini.popup { - font-size: 0.78571429rem; -} - -.ui.tiny.popup { - font-size: 0.85714286rem; -} - -.ui.small.popup { - font-size: 0.92857143rem; -} - -.ui.large.popup { - font-size: 1.14285714rem; -} - -.ui.big.popup { - font-size: 1.28571429rem; -} - -.ui.huge.popup { - font-size: 1.42857143rem; -} - -.ui.massive.popup { - font-size: 1.71428571rem; -} - -/******************************* - Theme Overrides -*******************************/ - -/******************************* - User Overrides -*******************************/ -/*! * # Fomantic-UI - Reset * http://github.com/fomantic/Fomantic-UI/ * diff --git a/web_src/fomantic/build/semantic.js b/web_src/fomantic/build/semantic.js index dcf99410c2..77c82ca6fd 100644 --- a/web_src/fomantic/build/semantic.js +++ b/web_src/fomantic/build/semantic.js @@ -10301,1548 +10301,6 @@ $.fn.modal.settings = { })( jQuery, window, document ); /*! - * # Fomantic-UI - Popup - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -;(function ($, window, document, undefined) { - -'use strict'; - -$.isFunction = $.isFunction || function(obj) { - return typeof obj === "function" && typeof obj.nodeType !== "number"; -}; - -window = (typeof window != 'undefined' && window.Math == Math) - ? window - : (typeof self != 'undefined' && self.Math == Math) - ? self - : Function('return this')() -; - -$.fn.popup = function(parameters) { - var - $allModules = $(this), - $document = $(document), - $window = $(window), - $body = $('body'), - - moduleSelector = $allModules.selector || '', - - clickEvent = ('ontouchstart' in document.documentElement) - ? 'touchstart' - : 'click', - - time = new Date().getTime(), - performance = [], - - query = arguments[0], - methodInvoked = (typeof query == 'string'), - queryArguments = [].slice.call(arguments, 1), - - returnedValue - ; - $allModules - .each(function() { - var - settings = ( $.isPlainObject(parameters) ) - ? $.extend(true, {}, $.fn.popup.settings, parameters) - : $.extend({}, $.fn.popup.settings), - - selector = settings.selector, - className = settings.className, - error = settings.error, - metadata = settings.metadata, - namespace = settings.namespace, - - eventNamespace = '.' + settings.namespace, - moduleNamespace = 'module-' + namespace, - - $module = $(this), - $context = $(settings.context), - $scrollContext = $(settings.scrollContext), - $boundary = $(settings.boundary), - $target = (settings.target) - ? $(settings.target) - : $module, - - $popup, - $offsetParent, - - searchDepth = 0, - triedPositions = false, - openedWithTouch = false, - - element = this, - instance = $module.data(moduleNamespace), - - documentObserver, - elementNamespace, - id, - module - ; - - module = { - - // binds events - initialize: function() { - module.debug('Initializing', $module); - module.createID(); - module.bind.events(); - if(!module.exists() && settings.preserve) { - module.create(); - } - if(settings.observeChanges) { - module.observeChanges(); - } - module.instantiate(); - }, - - instantiate: function() { - module.verbose('Storing instance', module); - instance = module; - $module - .data(moduleNamespace, instance) - ; - }, - - observeChanges: function() { - if('MutationObserver' in window) { - documentObserver = new MutationObserver(module.event.documentChanged); - documentObserver.observe(document, { - childList : true, - subtree : true - }); - module.debug('Setting up mutation observer', documentObserver); - } - }, - - refresh: function() { - if(settings.popup) { - $popup = $(settings.popup).eq(0); - } - else { - if(settings.inline) { - $popup = $target.nextAll(selector.popup).eq(0); - settings.popup = $popup; - } - } - if(settings.popup) { - $popup.addClass(className.loading); - $offsetParent = module.get.offsetParent(); - $popup.removeClass(className.loading); - if(settings.movePopup && module.has.popup() && module.get.offsetParent($popup)[0] !== $offsetParent[0]) { - module.debug('Moving popup to the same offset parent as target'); - $popup - .detach() - .appendTo($offsetParent) - ; - } - } - else { - $offsetParent = (settings.inline) - ? module.get.offsetParent($target) - : module.has.popup() - ? module.get.offsetParent($popup) - : $body - ; - } - if( $offsetParent.is('html') && $offsetParent[0] !== $body[0] ) { - module.debug('Setting page as offset parent'); - $offsetParent = $body; - } - if( module.get.variation() ) { - module.set.variation(); - } - }, - - reposition: function() { - module.refresh(); - module.set.position(); - }, - - destroy: function() { - module.debug('Destroying previous module'); - if(documentObserver) { - documentObserver.disconnect(); - } - // remove element only if was created dynamically - if($popup && !settings.preserve) { - module.removePopup(); - } - // clear all timeouts - clearTimeout(module.hideTimer); - clearTimeout(module.showTimer); - // remove events - module.unbind.close(); - module.unbind.events(); - $module - .removeData(moduleNamespace) - ; - }, - - event: { - start: function(event) { - var - delay = ($.isPlainObject(settings.delay)) - ? settings.delay.show - : settings.delay - ; - clearTimeout(module.hideTimer); - if(!openedWithTouch || (openedWithTouch && settings.addTouchEvents) ) { - module.showTimer = setTimeout(module.show, delay); - } - }, - end: function() { - var - delay = ($.isPlainObject(settings.delay)) - ? settings.delay.hide - : settings.delay - ; - clearTimeout(module.showTimer); - module.hideTimer = setTimeout(module.hide, delay); - }, - touchstart: function(event) { - openedWithTouch = true; - if(settings.addTouchEvents) { - module.show(); - } - }, - resize: function() { - if( module.is.visible() ) { - module.set.position(); - } - }, - documentChanged: function(mutations) { - [].forEach.call(mutations, function(mutation) { - if(mutation.removedNodes) { - [].forEach.call(mutation.removedNodes, function(node) { - if(node == element || $(node).find(element).length > 0) { - module.debug('Element removed from DOM, tearing down events'); - module.destroy(); - } - }); - } - }); - }, - hideGracefully: function(event) { - var - $target = $(event.target), - isInDOM = $.contains(document.documentElement, event.target), - inPopup = ($target.closest(selector.popup).length > 0) - ; - // don't close on clicks inside popup - if(event && !inPopup && isInDOM) { - module.debug('Click occurred outside popup hiding popup'); - module.hide(); - } - else { - module.debug('Click was inside popup, keeping popup open'); - } - } - }, - - // generates popup html from metadata - create: function() { - var - html = module.get.html(), - title = module.get.title(), - content = module.get.content() - ; - - if(html || content || title) { - module.debug('Creating pop-up html'); - if(!html) { - html = settings.templates.popup({ - title : title, - content : content - }); - } - $popup = $('<div/>') - .addClass(className.popup) - .data(metadata.activator, $module) - .html(html) - ; - if(settings.inline) { - module.verbose('Inserting popup element inline', $popup); - $popup - .insertAfter($module) - ; - } - else { - module.verbose('Appending popup element to body', $popup); - $popup - .appendTo( $context ) - ; - } - module.refresh(); - module.set.variation(); - - if(settings.hoverable) { - module.bind.popup(); - } - settings.onCreate.call($popup, element); - } - else if(settings.popup) { - $(settings.popup).data(metadata.activator, $module); - module.verbose('Used popup specified in settings'); - module.refresh(); - if(settings.hoverable) { - module.bind.popup(); - } - } - else if($target.next(selector.popup).length !== 0) { - module.verbose('Pre-existing popup found'); - settings.inline = true; - settings.popup = $target.next(selector.popup).data(metadata.activator, $module); - module.refresh(); - if(settings.hoverable) { - module.bind.popup(); - } - } - else { - module.debug('No content specified skipping display', element); - } - }, - - createID: function() { - id = (Math.random().toString(16) + '000000000').substr(2, 8); - elementNamespace = '.' + id; - module.verbose('Creating unique id for element', id); - }, - - // determines popup state - toggle: function() { - module.debug('Toggling pop-up'); - if( module.is.hidden() ) { - module.debug('Popup is hidden, showing pop-up'); - module.unbind.close(); - module.show(); - } - else { - module.debug('Popup is visible, hiding pop-up'); - module.hide(); - } - }, - - show: function(callback) { - callback = callback || function(){}; - module.debug('Showing pop-up', settings.transition); - if(module.is.hidden() && !( module.is.active() && module.is.dropdown()) ) { - if( !module.exists() ) { - module.create(); - } - if(settings.onShow.call($popup, element) === false) { - module.debug('onShow callback returned false, cancelling popup animation'); - return; - } - else if(!settings.preserve && !settings.popup) { - module.refresh(); - } - if( $popup && module.set.position() ) { - module.save.conditions(); - if(settings.exclusive) { - module.hideAll(); - } - module.animate.show(callback); - } - } - }, - - - hide: function(callback) { - callback = callback || function(){}; - if( module.is.visible() || module.is.animating() ) { - if(settings.onHide.call($popup, element) === false) { - module.debug('onHide callback returned false, cancelling popup animation'); - return; - } - module.remove.visible(); - module.unbind.close(); - module.restore.conditions(); - module.animate.hide(callback); - } - }, - - hideAll: function() { - $(selector.popup) - .filter('.' + className.popupVisible) - .each(function() { - $(this) - .data(metadata.activator) - .popup('hide') - ; - }) - ; - }, - exists: function() { - if(!$popup) { - return false; - } - if(settings.inline || settings.popup) { - return ( module.has.popup() ); - } - else { - return ( $popup.closest($context).length >= 1 ) - ? true - : false - ; - } - }, - - removePopup: function() { - if( module.has.popup() && !settings.popup) { - module.debug('Removing popup', $popup); - $popup.remove(); - $popup = undefined; - settings.onRemove.call($popup, element); - } - }, - - save: { - conditions: function() { - module.cache = { - title: $module.attr('title') - }; - if (module.cache.title) { - $module.removeAttr('title'); - } - module.verbose('Saving original attributes', module.cache.title); - } - }, - restore: { - conditions: function() { - if(module.cache && module.cache.title) { - $module.attr('title', module.cache.title); - module.verbose('Restoring original attributes', module.cache.title); - } - return true; - } - }, - supports: { - svg: function() { - return (typeof SVGGraphicsElement !== 'undefined'); - } - }, - animate: { - show: function(callback) { - callback = $.isFunction(callback) ? callback : function(){}; - if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { - module.set.visible(); - $popup - .transition({ - animation : settings.transition + ' in', - queue : false, - debug : settings.debug, - verbose : settings.verbose, - duration : settings.duration, - onComplete : function() { - module.bind.close(); - callback.call($popup, element); - settings.onVisible.call($popup, element); - } - }) - ; - } - else { - module.error(error.noTransition); - } - }, - hide: function(callback) { - callback = $.isFunction(callback) ? callback : function(){}; - module.debug('Hiding pop-up'); - if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { - $popup - .transition({ - animation : settings.transition + ' out', - queue : false, - duration : settings.duration, - debug : settings.debug, - verbose : settings.verbose, - onComplete : function() { - module.reset(); - callback.call($popup, element); - settings.onHidden.call($popup, element); - } - }) - ; - } - else { - module.error(error.noTransition); - } - } - }, - - change: { - content: function(html) { - $popup.html(html); - } - }, - - get: { - html: function() { - $module.removeData(metadata.html); - return $module.data(metadata.html) || settings.html; - }, - title: function() { - $module.removeData(metadata.title); - return $module.data(metadata.title) || settings.title; - }, - content: function() { - $module.removeData(metadata.content); - return $module.data(metadata.content) || settings.content || $module.attr('title'); - }, - variation: function() { - $module.removeData(metadata.variation); - return $module.data(metadata.variation) || settings.variation; - }, - popup: function() { - return $popup; - }, - popupOffset: function() { - return $popup.offset(); - }, - calculations: function() { - var - $popupOffsetParent = module.get.offsetParent($popup), - targetElement = $target[0], - isWindow = ($boundary[0] == window), - targetOffset = $target.offset(), - parentOffset = settings.inline || (settings.popup && settings.movePopup) - ? $target.offsetParent().offset() - : { top: 0, left: 0 }, - screenPosition = (isWindow) - ? { top: 0, left: 0 } - : $boundary.offset(), - calculations = {}, - scroll = (isWindow) - ? { top: $window.scrollTop(), left: $window.scrollLeft() } - : { top: 0, left: 0}, - screen - ; - calculations = { - // element which is launching popup - target : { - element : $target[0], - width : $target.outerWidth(), - height : $target.outerHeight(), - top : targetOffset.top - parentOffset.top, - left : targetOffset.left - parentOffset.left, - margin : {} - }, - // popup itself - popup : { - width : $popup.outerWidth(), - height : $popup.outerHeight() - }, - // offset container (or 3d context) - parent : { - width : $offsetParent.outerWidth(), - height : $offsetParent.outerHeight() - }, - // screen boundaries - screen : { - top : screenPosition.top, - left : screenPosition.left, - scroll: { - top : scroll.top, - left : scroll.left - }, - width : $boundary.width(), - height : $boundary.height() - } - }; - - // if popup offset context is not same as target, then adjust calculations - if($popupOffsetParent.get(0) !== $offsetParent.get(0)) { - var - popupOffset = $popupOffsetParent.offset() - ; - calculations.target.top -= popupOffset.top; - calculations.target.left -= popupOffset.left; - calculations.parent.width = $popupOffsetParent.outerWidth(); - calculations.parent.height = $popupOffsetParent.outerHeight(); - } - - // add in container calcs if fluid - if( settings.setFluidWidth && module.is.fluid() ) { - calculations.container = { - width: $popup.parent().outerWidth() - }; - calculations.popup.width = calculations.container.width; - } - - // add in margins if inline - calculations.target.margin.top = (settings.inline) - ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-top'), 10) - : 0 - ; - calculations.target.margin.left = (settings.inline) - ? module.is.rtl() - ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-right'), 10) - : parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-left'), 10) - : 0 - ; - // calculate screen boundaries - screen = calculations.screen; - calculations.boundary = { - top : screen.top + screen.scroll.top, - bottom : screen.top + screen.scroll.top + screen.height, - left : screen.left + screen.scroll.left, - right : screen.left + screen.scroll.left + screen.width - }; - return calculations; - }, - id: function() { - return id; - }, - startEvent: function() { - if(settings.on == 'hover') { - return 'mouseenter'; - } - else if(settings.on == 'focus') { - return 'focus'; - } - return false; - }, - scrollEvent: function() { - return 'scroll'; - }, - endEvent: function() { - if(settings.on == 'hover') { - return 'mouseleave'; - } - else if(settings.on == 'focus') { - return 'blur'; - } - return false; - }, - distanceFromBoundary: function(offset, calculations) { - var - distanceFromBoundary = {}, - popup, - boundary - ; - calculations = calculations || module.get.calculations(); - - // shorthand - popup = calculations.popup; - boundary = calculations.boundary; - - if(offset) { - distanceFromBoundary = { - top : (offset.top - boundary.top), - left : (offset.left - boundary.left), - right : (boundary.right - (offset.left + popup.width) ), - bottom : (boundary.bottom - (offset.top + popup.height) ) - }; - module.verbose('Distance from boundaries determined', offset, distanceFromBoundary); - } - return distanceFromBoundary; - }, - offsetParent: function($element) { - var - element = ($element !== undefined) - ? $element[0] - : $target[0], - parentNode = element.parentNode, - $node = $(parentNode) - ; - if(parentNode) { - var - is2D = ($node.css('transform') === 'none'), - isStatic = ($node.css('position') === 'static'), - isBody = $node.is('body') - ; - while(parentNode && !isBody && isStatic && is2D) { - parentNode = parentNode.parentNode; - $node = $(parentNode); - is2D = ($node.css('transform') === 'none'); - isStatic = ($node.css('position') === 'static'); - isBody = $node.is('body'); - } - } - return ($node && $node.length > 0) - ? $node - : $() - ; - }, - positions: function() { - return { - 'top left' : false, - 'top center' : false, - 'top right' : false, - 'bottom left' : false, - 'bottom center' : false, - 'bottom right' : false, - 'left center' : false, - 'right center' : false - }; - }, - nextPosition: function(position) { - var - positions = position.split(' '), - verticalPosition = positions[0], - horizontalPosition = positions[1], - opposite = { - top : 'bottom', - bottom : 'top', - left : 'right', - right : 'left' - }, - adjacent = { - left : 'center', - center : 'right', - right : 'left' - }, - backup = { - 'top left' : 'top center', - 'top center' : 'top right', - 'top right' : 'right center', - 'right center' : 'bottom right', - 'bottom right' : 'bottom center', - 'bottom center' : 'bottom left', - 'bottom left' : 'left center', - 'left center' : 'top left' - }, - adjacentsAvailable = (verticalPosition == 'top' || verticalPosition == 'bottom'), - oppositeTried = false, - adjacentTried = false, - nextPosition = false - ; - if(!triedPositions) { - module.verbose('All available positions available'); - triedPositions = module.get.positions(); - } - - module.debug('Recording last position tried', position); - triedPositions[position] = true; - - if(settings.prefer === 'opposite') { - nextPosition = [opposite[verticalPosition], horizontalPosition]; - nextPosition = nextPosition.join(' '); - oppositeTried = (triedPositions[nextPosition] === true); - module.debug('Trying opposite strategy', nextPosition); - } - if((settings.prefer === 'adjacent') && adjacentsAvailable ) { - nextPosition = [verticalPosition, adjacent[horizontalPosition]]; - nextPosition = nextPosition.join(' '); - adjacentTried = (triedPositions[nextPosition] === true); - module.debug('Trying adjacent strategy', nextPosition); - } - if(adjacentTried || oppositeTried) { - module.debug('Using backup position', nextPosition); - nextPosition = backup[position]; - } - return nextPosition; - } - }, - - set: { - position: function(position, calculations) { - - // exit conditions - if($target.length === 0 || $popup.length === 0) { - module.error(error.notFound); - return; - } - var - offset, - distanceAway, - target, - popup, - parent, - positioning, - popupOffset, - distanceFromBoundary - ; - - calculations = calculations || module.get.calculations(); - position = position || $module.data(metadata.position) || settings.position; - - offset = $module.data(metadata.offset) || settings.offset; - distanceAway = settings.distanceAway; - - // shorthand - target = calculations.target; - popup = calculations.popup; - parent = calculations.parent; - - if(module.should.centerArrow(calculations)) { - module.verbose('Adjusting offset to center arrow on small target element'); - if(position == 'top left' || position == 'bottom left') { - offset += (target.width / 2); - offset -= settings.arrowPixelsFromEdge; - } - if(position == 'top right' || position == 'bottom right') { - offset -= (target.width / 2); - offset += settings.arrowPixelsFromEdge; - } - } - - if(target.width === 0 && target.height === 0 && !module.is.svg(target.element)) { - module.debug('Popup target is hidden, no action taken'); - return false; - } - - if(settings.inline) { - module.debug('Adding margin to calculation', target.margin); - if(position == 'left center' || position == 'right center') { - offset += target.margin.top; - distanceAway += -target.margin.left; - } - else if (position == 'top left' || position == 'top center' || position == 'top right') { - offset += target.margin.left; - distanceAway -= target.margin.top; - } - else { - offset += target.margin.left; - distanceAway += target.margin.top; - } - } - - module.debug('Determining popup position from calculations', position, calculations); - - if (module.is.rtl()) { - position = position.replace(/left|right/g, function (match) { - return (match == 'left') - ? 'right' - : 'left' - ; - }); - module.debug('RTL: Popup position updated', position); - } - - // if last attempt use specified last resort position - if(searchDepth == settings.maxSearchDepth && typeof settings.lastResort === 'string') { - position = settings.lastResort; - } - - switch (position) { - case 'top left': - positioning = { - top : 'auto', - bottom : parent.height - target.top + distanceAway, - left : target.left + offset, - right : 'auto' - }; - break; - case 'top center': - positioning = { - bottom : parent.height - target.top + distanceAway, - left : target.left + (target.width / 2) - (popup.width / 2) + offset, - top : 'auto', - right : 'auto' - }; - break; - case 'top right': - positioning = { - bottom : parent.height - target.top + distanceAway, - right : parent.width - target.left - target.width - offset, - top : 'auto', - left : 'auto' - }; - break; - case 'left center': - positioning = { - top : target.top + (target.height / 2) - (popup.height / 2) + offset, - right : parent.width - target.left + distanceAway, - left : 'auto', - bottom : 'auto' - }; - break; - case 'right center': - positioning = { - top : target.top + (target.height / 2) - (popup.height / 2) + offset, - left : target.left + target.width + distanceAway, - bottom : 'auto', - right : 'auto' - }; - break; - case 'bottom left': - positioning = { - top : target.top + target.height + distanceAway, - left : target.left + offset, - bottom : 'auto', - right : 'auto' - }; - break; - case 'bottom center': - positioning = { - top : target.top + target.height + distanceAway, - left : target.left + (target.width / 2) - (popup.width / 2) + offset, - bottom : 'auto', - right : 'auto' - }; - break; - case 'bottom right': - positioning = { - top : target.top + target.height + distanceAway, - right : parent.width - target.left - target.width - offset, - left : 'auto', - bottom : 'auto' - }; - break; - } - if(positioning === undefined) { - module.error(error.invalidPosition, position); - } - - module.debug('Calculated popup positioning values', positioning); - - // tentatively place on stage - $popup - .css(positioning) - .removeClass(className.position) - .addClass(position) - .addClass(className.loading) - ; - - popupOffset = module.get.popupOffset(); - - // see if any boundaries are surpassed with this tentative position - distanceFromBoundary = module.get.distanceFromBoundary(popupOffset, calculations); - - if(!settings.forcePosition && module.is.offstage(distanceFromBoundary, position) ) { - module.debug('Position is outside viewport', position); - if(searchDepth < settings.maxSearchDepth) { - searchDepth++; - position = module.get.nextPosition(position); - module.debug('Trying new position', position); - return ($popup) - ? module.set.position(position, calculations) - : false - ; - } - else { - if(settings.lastResort) { - module.debug('No position found, showing with last position'); - } - else { - module.debug('Popup could not find a position to display', $popup); - module.error(error.cannotPlace, element); - module.remove.attempts(); - module.remove.loading(); - module.reset(); - settings.onUnplaceable.call($popup, element); - return false; - } - } - } - module.debug('Position is on stage', position); - module.remove.attempts(); - module.remove.loading(); - if( settings.setFluidWidth && module.is.fluid() ) { - module.set.fluidWidth(calculations); - } - return true; - }, - - fluidWidth: function(calculations) { - calculations = calculations || module.get.calculations(); - module.debug('Automatically setting element width to parent width', calculations.parent.width); - $popup.css('width', calculations.container.width); - }, - - variation: function(variation) { - variation = variation || module.get.variation(); - if(variation && module.has.popup() ) { - module.verbose('Adding variation to popup', variation); - $popup.addClass(variation); - } - }, - - visible: function() { - $module.addClass(className.visible); - } - }, - - remove: { - loading: function() { - $popup.removeClass(className.loading); - }, - variation: function(variation) { - variation = variation || module.get.variation(); - if(variation) { - module.verbose('Removing variation', variation); - $popup.removeClass(variation); - } - }, - visible: function() { - $module.removeClass(className.visible); - }, - attempts: function() { - module.verbose('Resetting all searched positions'); - searchDepth = 0; - triedPositions = false; - } - }, - - bind: { - events: function() { - module.debug('Binding popup events to module'); - if(settings.on == 'click') { - $module - .on(clickEvent + eventNamespace, module.toggle) - ; - } - if(settings.on == 'hover') { - $module - .on('touchstart' + eventNamespace, module.event.touchstart) - ; - } - if( module.get.startEvent() ) { - $module - .on(module.get.startEvent() + eventNamespace, module.event.start) - .on(module.get.endEvent() + eventNamespace, module.event.end) - ; - } - if(settings.target) { - module.debug('Target set to element', $target); - } - $window.on('resize' + elementNamespace, module.event.resize); - }, - popup: function() { - module.verbose('Allowing hover events on popup to prevent closing'); - if( $popup && module.has.popup() ) { - $popup - .on('mouseenter' + eventNamespace, module.event.start) - .on('mouseleave' + eventNamespace, module.event.end) - ; - } - }, - close: function() { - if(settings.hideOnScroll === true || (settings.hideOnScroll == 'auto' && settings.on != 'click')) { - module.bind.closeOnScroll(); - } - if(module.is.closable()) { - module.bind.clickaway(); - } - else if(settings.on == 'hover' && openedWithTouch) { - module.bind.touchClose(); - } - }, - closeOnScroll: function() { - module.verbose('Binding scroll close event to document'); - $scrollContext - .one(module.get.scrollEvent() + elementNamespace, module.event.hideGracefully) - ; - }, - touchClose: function() { - module.verbose('Binding popup touchclose event to document'); - $document - .on('touchstart' + elementNamespace, function(event) { - module.verbose('Touched away from popup'); - module.event.hideGracefully.call(element, event); - }) - ; - }, - clickaway: function() { - module.verbose('Binding popup close event to document'); - $document - .on(clickEvent + elementNamespace, function(event) { - module.verbose('Clicked away from popup'); - module.event.hideGracefully.call(element, event); - }) - ; - } - }, - - unbind: { - events: function() { - $window - .off(elementNamespace) - ; - $module - .off(eventNamespace) - ; - }, - close: function() { - $document - .off(elementNamespace) - ; - $scrollContext - .off(elementNamespace) - ; - }, - }, - - has: { - popup: function() { - return ($popup && $popup.length > 0); - } - }, - - should: { - centerArrow: function(calculations) { - return !module.is.basic() && calculations.target.width <= (settings.arrowPixelsFromEdge * 2); - }, - }, - - is: { - closable: function() { - if(settings.closable == 'auto') { - if(settings.on == 'hover') { - return false; - } - return true; - } - return settings.closable; - }, - offstage: function(distanceFromBoundary, position) { - var - offstage = [] - ; - // return boundaries that have been surpassed - $.each(distanceFromBoundary, function(direction, distance) { - if(distance < -settings.jitter) { - module.debug('Position exceeds allowable distance from edge', direction, distance, position); - offstage.push(direction); - } - }); - if(offstage.length > 0) { - return true; - } - else { - return false; - } - }, - svg: function(element) { - return module.supports.svg() && (element instanceof SVGGraphicsElement); - }, - basic: function() { - return $module.hasClass(className.basic); - }, - active: function() { - return $module.hasClass(className.active); - }, - animating: function() { - return ($popup !== undefined && $popup.hasClass(className.animating) ); - }, - fluid: function() { - return ($popup !== undefined && $popup.hasClass(className.fluid)); - }, - visible: function() { - return ($popup !== undefined && $popup.hasClass(className.popupVisible)); - }, - dropdown: function() { - return $module.hasClass(className.dropdown); - }, - hidden: function() { - return !module.is.visible(); - }, - rtl: function () { - return $module.attr('dir') === 'rtl' || $module.css('direction') === 'rtl'; - } - }, - - reset: function() { - module.remove.visible(); - if(settings.preserve) { - if($.fn.transition !== undefined) { - $popup - .transition('remove transition') - ; - } - } - else { - module.removePopup(); - } - }, - - setting: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, settings, name); - } - else if(value !== undefined) { - settings[name] = value; - } - else { - return settings[name]; - } - }, - internal: function(name, value) { - if( $.isPlainObject(name) ) { - $.extend(true, module, name); - } - else if(value !== undefined) { - module[name] = value; - } - else { - return module[name]; - } - }, - debug: function() { - if(!settings.silent && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.debug.apply(console, arguments); - } - } - }, - verbose: function() { - if(!settings.silent && settings.verbose && settings.debug) { - if(settings.performance) { - module.performance.log(arguments); - } - else { - module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); - module.verbose.apply(console, arguments); - } - } - }, - error: function() { - if(!settings.silent) { - module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); - module.error.apply(console, arguments); - } - }, - performance: { - log: function(message) { - var - currentTime, - executionTime, - previousTime - ; - if(settings.performance) { - currentTime = new Date().getTime(); - previousTime = time || currentTime; - executionTime = currentTime - previousTime; - time = currentTime; - performance.push({ - 'Name' : message[0], - 'Arguments' : [].slice.call(message, 1) || '', - 'Element' : element, - 'Execution Time' : executionTime - }); - } - clearTimeout(module.performance.timer); - module.performance.timer = setTimeout(module.performance.display, 500); - }, - display: function() { - var - title = settings.name + ':', - totalTime = 0 - ; - time = false; - clearTimeout(module.performance.timer); - $.each(performance, function(index, data) { - totalTime += data['Execution Time']; - }); - title += ' ' + totalTime + 'ms'; - if(moduleSelector) { - title += ' \'' + moduleSelector + '\''; - } - if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { - console.groupCollapsed(title); - if(console.table) { - console.table(performance); - } - else { - $.each(performance, function(index, data) { - console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); - }); - } - console.groupEnd(); - } - performance = []; - } - }, - invoke: function(query, passedArguments, context) { - var - object = instance, - maxDepth, - found, - response - ; - passedArguments = passedArguments || queryArguments; - context = element || context; - if(typeof query == 'string' && object !== undefined) { - query = query.split(/[\. ]/); - maxDepth = query.length - 1; - $.each(query, function(depth, value) { - var camelCaseValue = (depth != maxDepth) - ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) - : query - ; - if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { - object = object[camelCaseValue]; - } - else if( object[camelCaseValue] !== undefined ) { - found = object[camelCaseValue]; - return false; - } - else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { - object = object[value]; - } - else if( object[value] !== undefined ) { - found = object[value]; - return false; - } - else { - return false; - } - }); - } - if ( $.isFunction( found ) ) { - response = found.apply(context, passedArguments); - } - else if(found !== undefined) { - response = found; - } - if(Array.isArray(returnedValue)) { - returnedValue.push(response); - } - else if(returnedValue !== undefined) { - returnedValue = [returnedValue, response]; - } - else if(response !== undefined) { - returnedValue = response; - } - return found; - } - }; - - if(methodInvoked) { - if(instance === undefined) { - module.initialize(); - } - module.invoke(query); - } - else { - if(instance !== undefined) { - instance.invoke('destroy'); - } - module.initialize(); - } - }) - ; - - return (returnedValue !== undefined) - ? returnedValue - : this - ; -}; - -$.fn.popup.settings = { - - name : 'Popup', - - // module settings - silent : false, - debug : false, - verbose : false, - performance : true, - namespace : 'popup', - - // whether it should use dom mutation observers - observeChanges : true, - - // callback only when element added to dom - onCreate : function(){}, - - // callback before element removed from dom - onRemove : function(){}, - - // callback before show animation - onShow : function(){}, - - // callback after show animation - onVisible : function(){}, - - // callback before hide animation - onHide : function(){}, - - // callback when popup cannot be positioned in visible screen - onUnplaceable : function(){}, - - // callback after hide animation - onHidden : function(){}, - - // when to show popup - on : 'hover', - - // element to use to determine if popup is out of boundary - boundary : window, - - // whether to add touchstart events when using hover - addTouchEvents : true, - - // default position relative to element - position : 'top left', - - // if given position should be used regardless if popup fits - forcePosition : false, - - // name of variation to use - variation : '', - - // whether popup should be moved to context - movePopup : true, - - // element which popup should be relative to - target : false, - - // jq selector or element that should be used as popup - popup : false, - - // popup should remain inline next to activator - inline : false, - - // popup should be removed from page on hide - preserve : false, - - // popup should not close when being hovered on - hoverable : false, - - // explicitly set content - content : false, - - // explicitly set html - html : false, - - // explicitly set title - title : false, - - // whether automatically close on clickaway when on click - closable : true, - - // automatically hide on scroll - hideOnScroll : 'auto', - - // hide other popups on show - exclusive : false, - - // context to attach popups - context : 'body', - - // context for binding scroll events - scrollContext : window, - - // position to prefer when calculating new position - prefer : 'opposite', - - // specify position to appear even if it doesn't fit - lastResort : false, - - // number of pixels from edge of popup to pointing arrow center (used from centering) - arrowPixelsFromEdge: 20, - - // delay used to prevent accidental refiring of animations due to user error - delay : { - show : 50, - hide : 70 - }, - - // whether fluid variation should assign width explicitly - setFluidWidth : true, - - // transition settings - duration : 200, - transition : 'scale', - - // distance away from activating element in px - distanceAway : 0, - - // number of pixels an element is allowed to be "offstage" for a position to be chosen (allows for rounding) - jitter : 2, - - // offset on aligning axis from calculated position - offset : 0, - - // maximum times to look for a position before failing (9 positions total) - maxSearchDepth : 15, - - error: { - invalidPosition : 'The position you specified is not a valid position', - cannotPlace : 'Popup does not fit within the boundaries of the viewport', - method : 'The method you called is not defined.', - noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>', - notFound : 'The target or popup you specified does not exist on the page' - }, - - metadata: { - activator : 'activator', - content : 'content', - html : 'html', - offset : 'offset', - position : 'position', - title : 'title', - variation : 'variation' - }, - - className : { - active : 'active', - basic : 'basic', - animating : 'animating', - dropdown : 'dropdown', - fluid : 'fluid', - loading : 'loading', - popup : 'ui popup', - position : 'top left center bottom right', - visible : 'visible', - popupVisible : 'visible' - }, - - selector : { - popup : '.ui.popup' - }, - - templates: { - escape: function(string) { - var - badChars = /[<>"'`]/g, - shouldEscape = /[&<>"'`]/, - escape = { - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" - }, - escapedChar = function(chr) { - return escape[chr]; - } - ; - if(shouldEscape.test(string)) { - string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); - return string.replace(badChars, escapedChar); - } - return string; - }, - popup: function(text) { - var - html = '', - escape = $.fn.popup.settings.templates.escape - ; - if(typeof text !== undefined) { - if(typeof text.title !== undefined && text.title) { - text.title = escape(text.title); - html += '<div class="header">' + text.title + '</div>'; - } - if(typeof text.content !== undefined && text.content) { - text.content = escape(text.content); - html += '<div class="content">' + text.content + '</div>'; - } - } - return html; - } - } - -}; - - -})( jQuery, window, document ); - -/*! * # Fomantic-UI - Search * http://github.com/fomantic/Fomantic-UI/ * diff --git a/web_src/fomantic/semantic.json b/web_src/fomantic/semantic.json index ff45cc465c..a94adab85c 100644 --- a/web_src/fomantic/semantic.json +++ b/web_src/fomantic/semantic.json @@ -44,7 +44,6 @@ "menu", "message", "modal", - "popup", "reset", "search", "segment", diff --git a/web_src/js/components/DashboardRepoList.js b/web_src/js/components/DashboardRepoList.js index 36caaf2f5b..cbbc12c2c4 100644 --- a/web_src/js/components/DashboardRepoList.js +++ b/web_src/js/components/DashboardRepoList.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import $ from 'jquery'; import {initVueSvg, vueDelimiters} from './VueComponentLoader.js'; +import {initTooltip} from '../modules/tippy.js'; const {appSubUrl, assetUrlPrefix, pageData} = window.config; @@ -138,7 +139,9 @@ function initVueComponents() { mounted() { this.changeReposFilter(this.reposFilter); - $(this.$el).find('.tooltip').popup(); + for (const el of this.$el.querySelectorAll('.tooltip')) { + initTooltip(el); + } $(this.$el).find('.dropdown').dropdown(); this.setCheckboxes(); Vue.nextTick(() => { diff --git a/web_src/js/features/clipboard.js b/web_src/js/features/clipboard.js index e4a5c4f448..85324303e3 100644 --- a/web_src/js/features/clipboard.js +++ b/web_src/js/features/clipboard.js @@ -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; diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index 025b44d87d..1776f6577d 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -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(); diff --git a/web_src/js/features/comp/ReactionSelector.js b/web_src/js/features/comp/ReactionSelector.js index 272ea45cdd..26c9af2ff3 100644 --- a/web_src/js/features/comp/ReactionSelector.js +++ b/web_src/js/features/comp/ReactionSelector.js @@ -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; diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js index 8562ba0072..002a25f6ed 100644 --- a/web_src/js/features/repo-code.js +++ b/web_src/js/features/repo-code.js @@ -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(); + }); } diff --git a/web_src/js/features/repo-commit.js b/web_src/js/features/repo-commit.js index 94fca7a9c2..aac734de26 100644 --- a/web_src/js/features/repo-commit.js +++ b/web_src/js/features/repo-commit.js @@ -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, + }); }); } diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js index 92d8ecfc86..59e0c147d9 100644 --- a/web_src/js/features/repo-diff.js +++ b/web_src/js/features/repo-diff.js @@ -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'); diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 12900c2455..9dbe78edf5 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -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'); diff --git a/web_src/js/features/stopwatch.js b/web_src/js/features/stopwatch.js index c3aa79b767..ffa2ad4189 100644 --- a/web_src/js/features/stopwatch.js +++ b/web_src/js/features/stopwatch.js @@ -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. diff --git a/web_src/js/index.js b/web_src/js/index.js index 6f872b5353..b96e79c3c8 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -56,7 +56,7 @@ import { initGlobalFormDirtyLeaveConfirm, initGlobalLinkActions, initHeadNavbarContentToggle, - initGlobalPopups, + initGlobalTooltips, } from './features/common-global.js'; import {initRepoTopicBar} from './features/repo-home.js'; import {initAdminEmails} from './features/admin-emails.js'; @@ -100,7 +100,7 @@ initVueEnv(); $(document).ready(() => { initGlobalCommon(); - initGlobalPopups(); + initGlobalTooltips(); initGlobalButtonClickOnEnter(); initGlobalButtons(); initGlobalCopyToClipboardListener(); diff --git a/web_src/js/modules/tippy.js b/web_src/js/modules/tippy.js index 6fd466cd92..87f9e8a4b0 100644 --- a/web_src/js/modules/tippy.js +++ b/web_src/js/modules/tippy.js @@ -1,12 +1,56 @@ import tippy from 'tippy.js'; -export function createTippy(target, opts) { - return tippy(target, { +export function createTippy(target, opts = {}) { + const instance = tippy(target, { appendTo: document.body, placement: 'top-start', animation: false, allowHTML: true, + maxWidth: 500, // increase over default 350px arrow: `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`, + ...(opts?.role && {theme: opts.role}), ...opts, }); + + // for popups where content refers to a DOM element, we use the 'hide' class to initially hide + // the content, now we can remove it as the content has been removed from the DOM by tippy + if (opts.content instanceof Element) { + opts.content.classList.remove('hide'); + } + + return instance; +} + +export function initTooltip(el, props = {}) { + const content = el.getAttribute('data-content') || props.content; + if (!content) return null; + return createTippy(el, { + content, + delay: 100, + role: 'tooltip', + ...props, + }); +} + +export function showTemporaryTooltip(target, content) { + let tippy, oldContent; + if (target._tippy) { + tippy = target._tippy; + oldContent = tippy.props.content; + } else { + tippy = initTooltip(target, {content}); + } + + tippy.setContent(content); + tippy.show(); + tippy.setProps({ + onHidden: (tippy) => { + if (oldContent) { + tippy.setContent(oldContent); + } else { + tippy.destroy(); + } + tippy.setProps({onHidden: undefined}); + }, + }); } diff --git a/web_src/less/_base.less b/web_src/less/_base.less index dc518eea95..f2711c4482 100644 --- a/web_src/less/_base.less +++ b/web_src/less/_base.less @@ -155,6 +155,8 @@ --color-caret: var(--color-text-dark); --color-reaction-bg: #0000000a; --color-reaction-active-bg: var(--color-primary-alpha-20); + --color-tooltip-bg: #000000f0; + --color-tooltip-text: #ffffff; /* backgrounds */ --checkbox-mask-checked: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 18 18" width="16" height="16"><path fill-rule="evenodd" d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg>'); --checkbox-mask-indeterminate: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2 7.75A.75.75 0 012.75 7h10a.75.75 0 010 1.5h-10A.75.75 0 012 7.75z"></path></svg>'); @@ -1313,7 +1315,7 @@ footer { } .hide { - display: none; + display: none !important; &.show-outdated { display: none !important; @@ -1873,41 +1875,6 @@ a.ui.basic.label:hover { color: #f05133; /* from https://upload.wikimedia.org/wikipedia/commons/e/e0/Git-logo.svg */ } -.ui.popup { - background-color: var(--color-body); - color: var(--color-secondary-dark-6); - border-color: var(--color-secondary); -} - -.ui.popup::before { - box-shadow: 1px 1px 0 0 var(--color-secondary); -} - -.ui.bottom.popup::before, -.ui.top.popup::before, -.ui.right.center.popup::before, -.ui.left.center.popup::before { - background-color: var(--color-body); -} - -.ui.bottom.left.popup::before, -.ui.bottom.right.popup::before, -.ui.bottom.center.popup::before { - box-shadow: -1px -1px 0 0 var(--color-secondary); -} - -.ui.left.center.popup::before { - box-shadow: 1px -1px 0 0 var(--color-secondary); -} - -.ui.right.center.popup::before { - box-shadow: -1px 1px 0 0 var(--color-secondary); -} - -.ui.popup .ui.label { - margin-bottom: .4em; -} - .color-icon { display: inline-block; border-radius: 100%; diff --git a/web_src/less/modules/tippy.less b/web_src/less/modules/tippy.less index aa2aed6ce2..1fcd0372ce 100644 --- a/web_src/less/modules/tippy.less +++ b/web_src/less/modules/tippy.less @@ -1,9 +1,5 @@ /* styles are based on node_modules/tippy.js/dist/tippy.css */ -.tippy-box[data-animation="fade"][data-state="hidden"] { - opacity: 0; -} - [data-tippy-root] { max-width: calc(100vw - 10px); } @@ -15,7 +11,21 @@ border: 1px solid var(--color-secondary); border-radius: var(--border-radius); font-size: 1rem; - transition-property: transform, visibility, opacity; +} + +.tippy-box[data-theme="tooltip"] { + background-color: var(--color-tooltip-bg); + color: var(--color-tooltip-text); + border: none; +} + +.tippy-box[data-theme="menu"] { + background-color: none; + color: var(--color-tooltip-text); +} + +.tippy-box[data-theme="menu"] .ui.menu { + border: none; } .tippy-content { @@ -24,6 +34,14 @@ z-index: 1; } +.tippy-box[data-theme="tooltip"] .tippy-content { + padding: .5rem 1rem; +} + +.tippy-box[data-theme="menu"] .tippy-content { + padding: 0; +} + .tippy-box[data-placement^="top"] > .tippy-svg-arrow { bottom: 0; } @@ -82,3 +100,12 @@ .tippy-svg-arrow-inner { fill: var(--color-body); } + +.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-inner, +.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-outer { + fill: var(--color-tooltip-bg); +} + +.tippy-box[data-theme="menu"] .tippy-svg-arrow-inner { + fill: var(--color-menu); +} |