diff --git a/app/javascript/src/javascripts/post_tooltips.js b/app/javascript/src/javascripts/post_tooltips.js index 4be1754ec..dca379fd8 100644 --- a/app/javascript/src/javascripts/post_tooltips.js +++ b/app/javascript/src/javascripts/post_tooltips.js @@ -1,28 +1,75 @@ -import CurrentUser from './current_user' -import Utility from './utility' - -require('qtip2'); -require('qtip2/dist/jquery.qtip.css'); +import CurrentUser from './current_user'; +import Utility from './utility'; +import { delegate, hideAll } from 'tippy.js'; +import 'tippy.js/dist/tippy.css'; let PostTooltip = {}; -PostTooltip.render_tooltip = async function (event, qtip) { +PostTooltip.POST_SELECTOR = "*:not(.ui-sortable-handle) > article.post-preview img, .dtext-post-id-link"; +PostTooltip.SHOW_DELAY = 500; +PostTooltip.HIDE_DELAY = 125; +PostTooltip.DURATION = 250; +PostTooltip.MAX_WIDTH = 400; + +PostTooltip.initialize = function () { + if (PostTooltip.disabled()) { + return; + } + + delegate("body", { + allowHTML: true, + appendTo: document.body, + delay: [PostTooltip.SHOW_DELAY, PostTooltip.HIDE_DELAY], + duration: PostTooltip.DURATION, + interactive: true, + maxWidth: PostTooltip.MAX_WIDTH, + target: PostTooltip.POST_SELECTOR, + theme: "post-tooltip", + touch: false, + + onCreate: PostTooltip.on_create, + onShow: PostTooltip.on_show, + onHide: PostTooltip.on_hide, + }); + + $(document).on("click.danbooru.postTooltip", ".post-tooltip-disable", PostTooltip.on_disable_tooltips); +}; + +PostTooltip.on_create = function (instance) { + let title = instance.reference.getAttribute("title"); + + if (title) { + instance.reference.setAttribute("data-title", title); + instance.reference.setAttribute("title", ""); + } +}; + +PostTooltip.on_show = async function (instance) { let post_id = null; let preview = false; + let $target = $(instance.reference); + let $tooltip = $(instance.popper); - if ($(this).is(".dtext-post-id-link")) { + // skip if tooltip has already been rendered. + if ($tooltip.has(".post-tooltip-body").length) { + return; + } + + if ($target.is(".dtext-post-id-link")) { preview = true; - post_id = /\/posts\/(\d+)/.exec($(this).attr("href"))[1]; + post_id = /\/posts\/(\d+)/.exec($target.attr("href"))[1]; } else { - post_id = $(this).parents("[data-id]").data("id"); + post_id = $target.parents("[data-id]").data("id"); } try { - qtip.cache.request = $.get(`/posts/${post_id}`, { variant: "tooltip", preview: preview }); - let html = await qtip.cache.request; + $tooltip.addClass("post-tooltip-loading"); - qtip.set("content.text", html); - qtip.elements.tooltip.removeClass("post-tooltip-loading"); + instance._request = $.get(`/posts/${post_id}`, { variant: "tooltip", preview: preview }); + let html = await instance._request; + instance.setContent(html); + + $tooltip.removeClass("post-tooltip-loading"); } catch (error) { if (error.status !== 0 && error.statusText !== "abort") { Utility.error(`Error displaying tooltip for post #${post_id} (error: ${error.status} ${error.statusText})`); @@ -30,98 +77,19 @@ PostTooltip.render_tooltip = async function (event, qtip) { } }; -// Hide the tooltip the first time it is shown, while we wait on the ajax call to complete. -PostTooltip.on_show = function (event, qtip) { - if (!qtip.cache.hasBeenShown) { - qtip.elements.tooltip.addClass("post-tooltip-loading"); - qtip.cache.hasBeenShown = true; +PostTooltip.on_hide = function (instance) { + if (instance._request?.state() === "pending") { + instance._request.abort(); } -}; - -PostTooltip.POST_SELECTOR = "*:not(.ui-sortable-handle) > .post-preview img, .dtext-post-id-link"; - -// http://qtip2.com/options -PostTooltip.QTIP_OPTIONS = { - style: { - classes: "qtip-light post-tooltip", - tip: false - }, - content: PostTooltip.render_tooltip, - overwrite: false, - position: { - viewport: true, - my: "bottom left", - at: "top left", - effect: false, - adjust: { - y: -2, - method: "shift", - }, - }, - show: { - solo: true, - delay: 750, - effect: false, - ready: true, - event: "mouseenter", - }, - hide: { - delay: 250, - fixed: true, - effect: false, - event: "unfocus click mouseleave", - }, - events: { - show: PostTooltip.on_show, - }, -}; - -PostTooltip.initialize = function () { - $(document).on("mouseenter.danbooru.postTooltip", PostTooltip.POST_SELECTOR, function (event) { - if (PostTooltip.disabled()) { - $(this).qtip("disable"); - } else { - $(this).qtip(PostTooltip.QTIP_OPTIONS, event); - } - }); - - // Cancel pending ajax requests when we mouse out of the thumbnail. - $(document).on("mouseleave.danbooru.postTooltip", PostTooltip.POST_SELECTOR, function (event) { - let qtip = $(event.target).qtip("api"); - - if (qtip && qtip.cache && qtip.cache.request && qtip.cache.request.state() === "pending") { - qtip.cache.request.abort(); - } - }); - - $(document).on("click.danbooru.postTooltip", ".post-tooltip-disable", PostTooltip.on_disable_tooltips); - - // Hide tooltips when pressing keys or clicking thumbnails. - $(document).on("keydown.danbooru.postTooltip", PostTooltip.hide); - $(document).on("click.danbooru.postTooltip", PostTooltip.POST_SELECTOR, PostTooltip.hide); - - // Disable tooltips on touch devices. https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent - PostTooltip.isTouching = false; - $(document).on("touchstart.danbooru.postTooltip", function (event) { PostTooltip.isTouching = true; }); - $(document).on("touchend.danbooru.postTooltip", function (event) { PostTooltip.isTouching = false; }); -}; - -PostTooltip.hide = function (event) { - // Hide on any key except control (user may be control-clicking link inside tooltip). - if (event.type === "keydown" && event.ctrlKey === true) { - return; - } - - $(".post-tooltip:visible").qtip("hide"); -}; +} PostTooltip.disabled = function (event) { - return PostTooltip.isTouching || CurrentUser.data("disable-post-tooltips"); + return CurrentUser.data("disable-post-tooltips"); }; PostTooltip.on_disable_tooltips = async function (event) { event.preventDefault(); - $(event.target).parents(".qtip").qtip("hide"); + hideAll(); if (CurrentUser.data("is-anonymous")) { Utility.notice('You must login to disable tooltips'); diff --git a/app/javascript/src/styles/base/040_colors.css b/app/javascript/src/styles/base/040_colors.css index ab97b39f3..561f86e60 100644 --- a/app/javascript/src/styles/base/040_colors.css +++ b/app/javascript/src/styles/base/040_colors.css @@ -72,7 +72,8 @@ --comment-sticky-background-color: var(--subnav-menu-background-color); --post-tooltip-background-color: var(--body-background-color); - --post-tooltip-border-color: #767676; + --post-tooltip-border-color: hsla(210, 100%, 3%, 0.15); + --post-tooltip-box-shadow: 0 4px 14px -2px hsla(210, 100%, 3%, 0.10); --post-tooltip-header-background-color: var(--subnav-menu-background-color); --post-tooltip-info-color: #555; --post-tooltip-scrollbar-background: #999999; diff --git a/app/javascript/src/styles/specific/post_tooltips.scss b/app/javascript/src/styles/specific/post_tooltips.scss index 2a98dfc1c..fa843763a 100644 --- a/app/javascript/src/styles/specific/post_tooltips.scss +++ b/app/javascript/src/styles/specific/post_tooltips.scss @@ -1,6 +1,5 @@ $tooltip-line-height: 16px; -$tooltip-body-height: $tooltip-line-height * 6; // 6 lines high. -$tooltip-width: 164px * 3 - 10; // 3 thumbnails wide. +$tooltip-body-height: $tooltip-line-height * 4; // 4 lines high. @mixin thin-scrollbar { &::-webkit-scrollbar { @@ -46,16 +45,20 @@ $tooltip-width: 164px * 3 - 10; // 3 thumbnails wide. } } -.post-tooltip { - max-width: $tooltip-width; - min-width: $tooltip-width; +.tippy-box[data-theme~="post-tooltip"] { + min-width: 200px; box-sizing: border-box; font-size: 11px; line-height: $tooltip-line-height; - border-color: var(--post-tooltip-border-color); - background-color: var(--post-tooltip-background-color); - .qtip-content { + border: 1px solid var(--post-tooltip-border-color); + border-radius: 4px; + + background-color: var(--post-tooltip-background-color); + background-clip: padding-box; + box-shadow: var(--post-tooltip-box-shadow); + + .tippy-content { padding: 0; > * { @@ -116,7 +119,43 @@ $tooltip-width: 164px * 3 - 10; // 3 thumbnails wide. } } - &.post-tooltip-loading { - visibility: hidden; + /* bordered arrow styling; see https://github.com/atomiks/tippyjs/blob/master/src/scss/themes/light-border.scss */ + &[data-placement^=bottom] { + > .tippy-arrow:before { + border-bottom-color: var(--post-tooltip-background-color); + bottom: 16px; + } + + > .tippy-arrow:after { + border-bottom-color: var(--post-tooltip-border-color); + border-width: 0 7px 7px; + top: -8px; + left: 1px; + } + } + + &[data-placement^=top] { + > .tippy-arrow:before { + border-top-color: var(--post-tooltip-background-color); + } + + > .tippy-arrow:after { + border-top-color: var(--post-tooltip-border-color); + border-width: 7px 7px 0; + top: 17px; + left: 1px; + } + } + + > .tippy-arrow:after { + border-color: transparent; + border-style: solid; + content: ""; + position: absolute; + z-index: -1; } } + +div[data-tippy-root].post-tooltip-loading { + visibility: hidden !important; +} diff --git a/app/views/posts/update.js.erb b/app/views/posts/update.js.erb index 1da54c638..606380c4a 100644 --- a/app/views/posts/update.js.erb +++ b/app/views/posts/update.js.erb @@ -1,8 +1,5 @@ <% if @post.valid? %> var $post = $("#post_<%= @post.id %>"); - <% if !CurrentUser.disable_post_tooltips %> - $post.find("img").qtip("destroy", true); - <% end %> var $new_post = $("<%= j PostPresenter.preview(@post, show_deleted: true) %>"); Danbooru.Blacklist.apply_post($new_post.get(0)); $("#post_<%= @post.id %>").replaceWith($new_post); diff --git a/package.json b/package.json index e715e9198..1d7b508c4 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ "hammerjs": "^2.0.8", "jquery-hotkeys": "^0.2.2", "jquery-ui": "^1.12.1", - "qtip2": "^3.0.3", "rails-erb-loader": "^5.5.0", "script-loader": "^0.7.2", "spark-md5": "^3.0.0", "stupid-table-plugin": "^1.1.3", + "tippy.js": "^6.2.3", "typeface-anton": "^0.0.72", "typeface-archivo-narrow": "^1.0.0", "typeface-ibm-plex-mono": "^0.0.61", diff --git a/yarn.lock b/yarn.lock index b28f64d11..544a14be1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -830,6 +830,11 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" +"@popperjs/core@^2.3.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.2.tgz#7c6dc4ecef16149fd7a736710baa1b811017fdca" + integrity sha512-JlGTGRYHC2QK+DDbePyXdBdooxFq2+noLfWpRqJtkxcb/oYWzOF0kcbfvvbWrwevCC1l6hLUg1wHYT+ona5BWQ== + "@rails/ujs@^6.0.2-1": version "6.0.3" resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.0.3.tgz#e68a03278e30daea6a110aac5dfa33c60c53055d" @@ -3212,11 +3217,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -ev-emitter@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ev-emitter/-/ev-emitter-1.1.1.tgz#8f18b0ce5c76a5d18017f71c0a795c65b9138f2a" - integrity sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q== - eventemitter3@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" @@ -4196,13 +4196,6 @@ ignore@^5.1.4, ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== -imagesloaded@>=3.0.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/imagesloaded/-/imagesloaded-4.1.4.tgz#1376efcd162bb768c34c3727ac89cc04051f3cc7" - integrity sha512-ltiBVcYpc/TYTF5nolkMNsnREHW+ICvfQ3Yla2Sgr71YFwQ86bDwV9hgpFhFtrGPuwEx5+LqOHIrdXBdoWwwsA== - dependencies: - ev-emitter "^1.0.0" - import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -4718,11 +4711,6 @@ jquery-ui@^1.12.1: resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51" integrity sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE= -jquery@>=1.6.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" - integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== - js-base64@^2.1.8: version "2.6.2" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.2.tgz#cf9301bc5cc756892a9a6c8d7138322e5944fb0d" @@ -7020,14 +7008,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -qtip2@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/qtip2/-/qtip2-3.0.3.tgz#7df088ae4412c24a4064de69e824cb3cf76210dc" - integrity sha1-ffCIrkQSwkpAZN5p6CTLPPdiENw= - dependencies: - imagesloaded ">=3.0.0" - jquery ">=1.6.0" - query-string@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" @@ -8529,6 +8509,13 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tippy.js@^6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.2.3.tgz#0a5db67dc6bd9129233b26052b7ae2b2047fd73e" + integrity sha512-MzqHMrr2C0IC8ZUnG5kLQPxonWJ7V+Usqiy2W5b+dCvAfousio0mA85h+Ea5wRq94AQGd8mbFGeciRgkP+F+7w== + dependencies: + "@popperjs/core" "^2.3.2" + to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"