diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 77555bc151ac3..d291b7de9151e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -67,7 +67,6 @@ updates: open-pull-requests-limit: 20 versioning-strategy: increase ignore: # These are all vendored so need to be updated manually. See lib/tasks/javascript.rake - - dependency-name: "magnific-popup" - dependency-name: "moment" - dependency-name: "moment-timezone" - dependency-name: "@discourse/moment-timezone-names-translations" diff --git a/app/assets/stylesheets/common/base/_index.scss b/app/assets/stylesheets/common/base/_index.scss index a00e08164f29b..188b81ff12cfa 100644 --- a/app/assets/stylesheets/common/base/_index.scss +++ b/app/assets/stylesheets/common/base/_index.scss @@ -31,7 +31,6 @@ @import "list-controls"; @import "login"; @import "login-signup-page"; -@import "magnific-popup"; @import "photoswipe"; @import "menu-panel"; @import "modal"; diff --git a/app/assets/stylesheets/common/base/lightbox.scss b/app/assets/stylesheets/common/base/lightbox.scss index 0f3e839cdd40d..e35d050b92ad0 100644 --- a/app/assets/stylesheets/common/base/lightbox.scss +++ b/app/assets/stylesheets/common/base/lightbox.scss @@ -111,10 +111,6 @@ } } -.mfp-preloader .spinner { - margin: auto; -} - .discourse-no-touch { a.lightbox { transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1); diff --git a/app/assets/stylesheets/common/base/magnific-popup.scss b/app/assets/stylesheets/common/base/magnific-popup.scss deleted file mode 100644 index 1e67521a5d9ae..0000000000000 --- a/app/assets/stylesheets/common/base/magnific-popup.scss +++ /dev/null @@ -1,673 +0,0 @@ -/* Magnific Popup CSS */ - -//////////////////////// -// -// Contents: -// -// 1. Default Settings -// 2. General styles -// - Translucent overlay -// - Containers, wrappers -// - Cursors -// - Helper classes -// 3. Appearance -// - Preloader & text that displays error messages -// - CSS reset for buttons -// - Close icon -// - "1 of X" counter -// - Navigation (left/right) arrows -// - Iframe content type styles -// - Image content type styles -// - Media query where size of arrows is reduced -// - IE7 support -// -//////////////////////// - -//////////////////////// -// 1. Default Settings -//////////////////////// - -@use "sass:math"; - -$overlay-color: #000 !default; -$overlay-opacity: 0.8 !default; -$shadow: 0 0 8px rgb(0, 0, 0, 0.6) !default; // shadow on image or iframe -$popup-padding-left: 8px !default; // Padding from left and from right side -$popup-padding-left-mobile: 6px !default; // Same as above, but is applied when width of window is less than 800px - -$z-index-base: 1040 !default; // Base z-index of popup -$include-arrows: true !default; // include styles for nav arrows -$controls-opacity: 0.65 !default; -$controls-color: #fff !default; -$inner-close-icon-color: #333 !default; -$controls-text-color: #ccc !default; // Color of preloader and "1 of X" indicator -$controls-text-color-hover: #fff !default; - -// Iframe-type options -$include-iframe-type: true !default; -$iframe-padding-top: 40px !default; -$iframe-background: #000 !default; -$iframe-max-width: 900px !default; -$iframe-ratio: math.div(9, 16) !default; - -// Image-type options -$include-image-type: true !default; -$image-background: linear-gradient(45deg, #111 0%, #333 100%) !default; -$image-padding-top: 40px !default; -$image-padding-bottom: 40px !default; -$include-mobile-layout-for-image: true !default; // Removes paddings from top and bottom - -// Image caption options -$caption-title-color: #f3f3f3 !default; -$caption-subtitle-color: #bdbdbd !default; - -// A11y -$use-visuallyhidden: false !default; // Hide content from browsers, but make it available for screen readers - -//////////////////////// -// 2. General styles -//////////////////////// - -// Translucent overlay -.mfp-bg { - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: z("modal", "popover"); - overflow: hidden; - position: fixed; - background: $overlay-color; - animation: fade 0.3s alternate; -} - -// Wrapper for popup -.mfp-wrap { - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: z("modal", "popover") + 1; - position: fixed; - outline: 0 !important; - backface-visibility: hidden; // fixes webkit bug that can cause "false" scrollbar -} - -// Root container -.mfp-container { - text-align: center; - position: absolute; - width: 100%; - height: 100%; - left: 0; - top: 0; - padding: 0 $popup-padding-left; - box-sizing: border-box; - - // Vertical centerer helper - &::before { - content: ""; - display: inline-block; - height: 100%; - vertical-align: middle; - } -} - -// Remove vertical centering when popup has class `mfp-align-top` -.mfp-align-top { - .mfp-container { - &::before { - display: none; - } - } -} - -// Popup content holder -.mfp-content { - position: relative; - display: inline-block; - vertical-align: middle; - margin: 0 auto; - text-align: left; - z-index: $z-index-base + 5; -} - -.mfp-inline-holder, -.mfp-ajax-holder { - .mfp-content { - width: 100%; - cursor: auto; - } -} - -// Cursors -.mfp-ajax-cur { - cursor: progress; -} - -.mfp-zoom-out-cur { - &, - .mfp-image-holder .mfp-close { - cursor: zoom-out; - } -} - -.mfp-zoom { - cursor: zoom-in; -} - -.mfp-auto-cursor { - .mfp-content { - cursor: auto; - } -} - -.mfp-close, -.mfp-arrow, -.mfp-preloader, -.mfp-counter { - @include unselectable; -} - -// Hide the image during the loading -.mfp-loading { - &.mfp-figure { - display: none; - } -} - -// Helper class that hides stuff -@if $use-visuallyhidden { - // From HTML5 Boilerplate https://github.com/h5bp/html5-boilerplate/blob/v4.2.0/doc/css.md#visuallyhidden - .mfp-hide { - border: 0 !important; - clip-path: rect(0 0 0 0) !important; - height: 1px !important; - margin: -1px !important; - overflow: hidden !important; - padding: 0 !important; - position: absolute !important; - width: 1px !important; - } -} @else { - .mfp-hide { - display: none !important; - } -} - -//////////////////////// -// 3. Appearance -//////////////////////// - -// Preloader and text that displays error messages -.mfp-preloader { - color: $controls-text-color; - position: absolute; - top: 50%; - width: auto; - text-align: center; - margin-top: -0.8em; - left: 8px; - right: 8px; - z-index: $z-index-base + 4; - - a { - color: $controls-text-color; - - &:hover { - color: $controls-text-color-hover; - } - } -} - -// Hide preloader when content successfully loaded -.mfp-s-ready { - .mfp-preloader { - display: none; - } -} - -// Hide content when it was not loaded -.mfp-s-error { - .mfp-content { - display: none; - } -} - -// CSS-reset for buttons -button { - &.mfp-close, - &.mfp-arrow { - overflow: visible; - cursor: pointer; - background: transparent; - border: 0; - appearance: none; - display: block; - padding: 0; - z-index: $z-index-base + 6; - box-shadow: none; - } - - &::-moz-focus-inner { - padding: 0; - border: 0; - } -} - -// Close icon -.mfp-close { - width: 44px; - height: 44px; - line-height: var(--line-height-large); - position: absolute; - right: 0; - top: 0; - text-decoration: none; - text-align: center; - opacity: $controls-opacity; - padding: 0 0 18px 10px; - color: $controls-color; - font-style: normal; - font-size: var(--font-up-5); - font-family: Arial, Baskerville, monospace; - - &:hover, - &:focus { - opacity: 1; - outline: 0; - } - - &:active { - top: 1px; - } -} - -.mfp-close-btn-in { - .mfp-close { - color: $inner-close-icon-color; - } -} - -.mfp-image-holder, -.mfp-iframe-holder { - .mfp-close { - color: $controls-color; - right: -6px; - text-align: right; - padding-right: 6px; - width: 100%; - } -} - -// "1 of X" counter -.mfp-counter { - position: absolute; - top: 0; - right: 0; - color: $controls-text-color; - font-size: var(--font-down-1); - line-height: var(--line-height-medium); -} - -// Navigation arrows -@if $include-arrows { - .mfp-arrow { - position: absolute; - opacity: $controls-opacity; - top: 50%; - margin: -55px 0 0; - padding: 0; - width: 90px; - height: 110px; - -webkit-tap-highlight-color: rgb(0, 0, 0, 0); - - &:active { - margin-top: -54px; - } - - &:hover, - &:focus { - outline: 0; - opacity: 1; - } - - &::before, - &::after, - .mfp-b, - .mfp-a { - content: ""; - display: block; - width: 0; - height: 0; - position: absolute; - left: 0; - top: 0; - margin-top: 35px; - margin-left: 35px; - border: medium inset transparent; - } - - &::after, - .mfp-a { - border-top-width: 13px; - border-bottom-width: 13px; - top: 8px; - } - - &::before, - .mfp-b { - border-top-width: 21px; - border-bottom-width: 21px; - } - } - - .mfp-arrow-left { - left: 0; - - &::after, - .mfp-a { - border-right: 17px solid #fff; - margin-left: 31px; - } - - &::before, - .mfp-b { - margin-left: 25px; - border-right: 27px solid #3f3f3f; - } - } - - .mfp-arrow-right { - right: 0; - - &::after, - .mfp-a { - border-left: 17px solid #fff; - margin-left: 39px; - } - - &::before, - .mfp-b { - border-left: 27px solid #3f3f3f; - } - } -} - -// Iframe content type -@if $include-iframe-type { - .mfp-iframe-holder { - padding-top: $iframe-padding-top; - padding-bottom: $iframe-padding-top; - - .mfp-content { - line-height: 0; - width: 100%; - max-width: $iframe-max-width; - } - - .mfp-close { - top: -40px; - } - } - - .mfp-iframe-scaler { - width: 100%; - height: 0; - overflow: hidden; - padding-top: $iframe-ratio * 100%; - - iframe { - position: absolute; - display: block; - top: 0; - left: 0; - width: 100%; - height: 100%; - box-shadow: $shadow; - background: $iframe-background; - } - } -} - -// Image content type -@if $include-image-type { - /* Main image in popup */ - img { - &.mfp-img { - width: auto; - max-width: 100%; - height: auto; - display: block; - line-height: 0; - box-sizing: border-box; - padding: $image-padding-top 0 $image-padding-bottom; - margin: 0 auto; - } - } - - /* The shadow behind the image */ - .mfp-figure { - line-height: 0; - - &::after { - content: ""; - position: absolute; - left: 0; - top: $image-padding-top; - bottom: $image-padding-bottom; - display: block; - right: 0; - width: auto; - height: auto; - z-index: -1; - box-shadow: $shadow; - background: $image-background; - } - - small { - color: $caption-subtitle-color; - display: block; - font-size: var(--font-down-1); - line-height: var(--line-height-medium); - } - - figure { - margin: 0; - } - } - - .mfp-bottom-bar { - margin-top: -$image-padding-bottom + 4; - position: absolute; - top: 100%; - left: 0; - width: 100%; - cursor: auto; - } - - .mfp-title { - text-align: left; - line-height: var(--line-height-medium); - color: $caption-title-color; - overflow-wrap: break-word; - padding-right: 36px; // leave some space for counter at right side - max-width: 100%; - - // add the download icon - a.image-source-link .d-icon { - padding-right: 5px; - } - - a { - color: var(--tertiary-medium); - } - } - - .mfp-ready { - .mfp-content { - min-width: 300px; - } - } - - .mfp-title, - .mfp-title .image-source-link { - display: inline-block; - } - - .mfp-image-holder { - .mfp-content { - max-width: 100%; - } - } - - .mfp-gallery { - .mfp-image-holder { - .mfp-figure { - cursor: pointer; - } - } - } - - @if $include-mobile-layout-for-image { - @media all and (width <= 800px) and (orientation: landscape), - screen and (height <= 300px) { - /** - * Remove all paddings around the image on small screen - */ - - .mfp-img-mobile { - .mfp-image-holder { - padding-left: 0; - padding-right: 0; - } - - img { - &.mfp-img { - padding: 0; - } - } - - .mfp-figure { - /* The shadow behind the image */ - &::after { - top: 0; - bottom: 0; - } - - small { - display: inline; - margin-left: 5px; - } - } - - .mfp-bottom-bar { - background: rgb(0, 0, 0, 0.6); - bottom: 0; - margin: 0; - top: auto; - padding: 3px 5px; - position: fixed; - box-sizing: border-box; - - &:empty { - padding: 0; - } - } - - .mfp-counter { - right: 5px; - top: 3px; - } - - .mfp-close { - top: 0; - right: 0; - width: 35px; - height: 35px; - line-height: var(--line-height-large); - background: rgb(0, 0, 0, 0.6); - position: fixed; - text-align: center; - padding: 0; - } - } - } - } -} - -// Scale navigation arrows and reduce padding from sides -@media all and (width <= 900px) { - .mfp-arrow { - transform: scale(0.75); - } - - .mfp-arrow-left { - transform-origin: 0; - } - - .mfp-arrow-right { - transform-origin: 100%; - } - - .mfp-container { - padding-left: $popup-padding-left-mobile; - padding-right: $popup-padding-left-mobile; - } -} - -.mfp-zoom-in { - /* start state */ - .mfp-content { - opacity: 0; - transform: scale(0.8); - - @media screen and (prefers-reduced-motion: no-preference) { - transition: all 0.2s; - } - } - - &.mfp-bg { - opacity: 0; - transition: all 0.3s ease-out; - } - - /* animate in */ - &.mfp-ready { - .mfp-content { - opacity: 1; - transform: scale(1); - } - - &.mfp-bg { - opacity: 0.7; - } - } - - /* animate out */ - &.mfp-removing { - .mfp-content { - transform: scale(0.8); - opacity: 0; - } - - &.mfp-bg { - opacity: 0; - } - } -} - -.mfp-force-scrollbars { - &.mfp-wrap { - overflow-y: auto !important; - overflow-x: auto !important; - } - - .mfp-figure { - overflow: auto; - } - - .mfp-img { - max-width: none; - } -} diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 760f910726ab9..af1a309cd5f12 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2752,7 +2752,6 @@ en: experimental_auto_grid_images: "Automatically wraps images in [grid] tags when 3 or more images are uploaded in the composer." experimental_impersonation: "Impersonate a user without having to log out of your admin account." experimental_impersonation_time_limit_minutes: "How long before an impersonation session is automatically ended." - experimental_lightbox: "Use PhotoSwipe to lightbox larger images." page_loading_indicator: "Configure the loading indicator which appears during page navigations within Discourse. 'Spinner' is a full page indicator. 'Slider' shows a narrow bar at the top of the screen." show_user_menu_avatars: "Show user avatars in the user menu" about_page_hidden_groups: "Do not show members of specific groups on the /about page." diff --git a/config/site_settings.yml b/config/site_settings.yml index ae9f91598e87e..5f9f983abb9a9 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -4260,10 +4260,6 @@ experimental: default: 15 hidden: true area: "experimental" - experimental_lightbox: - client: true - default: true - area: "experimental" show_preview_for_form_templates: client: true default: true diff --git a/frontend/discourse/admin/components/admin-config-areas/upcoming-change-item.gjs b/frontend/discourse/admin/components/admin-config-areas/upcoming-change-item.gjs index ecb5e4f284333..f8acb590743d6 100644 --- a/frontend/discourse/admin/components/admin-config-areas/upcoming-change-item.gjs +++ b/frontend/discourse/admin/components/admin-config-areas/upcoming-change-item.gjs @@ -25,7 +25,6 @@ import { i18n } from "discourse-i18n"; export default class UpcomingChangeItem extends Component { @service toasts; - @service siteSettings; @tracked bufferedGroups = this.args.change.groups; @tracked bufferedEnabledFor = this.args.change.upcoming_change.enabled_for; @@ -34,7 +33,7 @@ export default class UpcomingChangeItem extends Component { registeredMenu = null; - applyLightbox = modifier((element) => lightbox(element, this.siteSettings)); + applyLightbox = modifier((element) => lightbox(element)); willDestroy() { super.willDestroy(...arguments); diff --git a/frontend/discourse/app/components/uppy-image-uploader.gjs b/frontend/discourse/app/components/uppy-image-uploader.gjs index 6a94ae6cb8d78..80bab33da85e2 100644 --- a/frontend/discourse/app/components/uppy-image-uploader.gjs +++ b/frontend/discourse/app/components/uppy-image-uploader.gjs @@ -8,7 +8,6 @@ import { service } from "@ember/service"; import { htmlSafe } from "@ember/template"; import { isEmpty } from "@ember/utils"; import { modifier } from "ember-modifier"; -import $ from "jquery"; import DButton from "discourse/components/d-button"; import PickFilesButton from "discourse/components/pick-files-button"; import concatClass from "discourse/helpers/concat-class"; @@ -60,12 +59,7 @@ export default class UppyImageUploader extends Component { willDestroy() { super.willDestroy(...arguments); - - if (this.siteSettings.experimental_lightbox) { - window.pswp?.close(); - } else { - $.magnificPopup?.instance.close(); - } + window.pswp?.close(); } get disabled() { @@ -147,7 +141,6 @@ export default class UppyImageUploader extends Component { @action toggleLightbox() { - // Only allow lightbox for images, not videos if (this.isVideoFile) { return; } @@ -158,12 +151,8 @@ export default class UppyImageUploader extends Component { return; } - if (this.siteSettings.experimental_lightbox) { - lightbox(lightboxImage, this.siteSettings); - lightboxImage.click(); - } else { - $(lightboxImage).magnificPopup("open"); - } + lightbox(lightboxImage); + lightboxImage.click(); } @action diff --git a/frontend/discourse/app/instance-initializers/clean-dom-on-route-change.js b/frontend/discourse/app/instance-initializers/clean-dom-on-route-change.js index d7de8dee1340d..f9b0220565247 100644 --- a/frontend/discourse/app/instance-initializers/clean-dom-on-route-change.js +++ b/frontend/discourse/app/instance-initializers/clean-dom-on-route-change.js @@ -1,5 +1,4 @@ import { scheduleOnce } from "@ember/runloop"; -import $ from "jquery"; function _clean(transition) { if (window.MiniProfiler && transition.from) { @@ -18,12 +17,6 @@ function _clean(transition) { // Close PhotoSwipe window.pswp?.close(); - // Close the lightbox - if ($.magnificPopup?.instance) { - $.magnificPopup.instance.close(); - document.body.classList.remove("mfp-zoom-out-cur"); - } - // Remove any link focus const { activeElement } = document; if (activeElement && !activeElement.classList.contains("no-blur")) { diff --git a/frontend/discourse/app/instance-initializers/post-decorations.js b/frontend/discourse/app/instance-initializers/post-decorations.js index f54de44d8f312..d6f18ae66a997 100644 --- a/frontend/discourse/app/instance-initializers/post-decorations.js +++ b/frontend/discourse/app/instance-initializers/post-decorations.js @@ -28,7 +28,7 @@ export default { }); api.decorateCookedElement((elem, helper) => { - return lightbox(elem, siteSettings, { post: helper.model }); + return lightbox(elem, { post: helper.model }); }); api.decorateCookedElement((elem) => { diff --git a/frontend/discourse/app/lib/lightbox.js b/frontend/discourse/app/lib/lightbox.js index 02f99378fb2c3..b588070c63b83 100644 --- a/frontend/discourse/app/lib/lightbox.js +++ b/frontend/discourse/app/lib/lightbox.js @@ -1,470 +1,354 @@ -import { waitForPromise } from "@ember/test-waiters"; -import $ from "jquery"; -import { spinnerHTML } from "discourse/helpers/loading-spinner"; import { isRailsTesting, isTesting } from "discourse/lib/environment"; import { helperContext } from "discourse/lib/helpers"; -import { renderIcon } from "discourse/lib/icon-library"; import { SELECTORS } from "discourse/lib/lightbox/constants"; import quoteImage, { canBuildImageQuote, } from "discourse/lib/lightbox/quote-image"; import { isDocumentRTL } from "discourse/lib/text-direction"; -import { - escapeExpression, - postRNWebviewMessage, -} from "discourse/lib/utilities"; +import { escapeExpression } from "discourse/lib/utilities"; import { i18n } from "discourse-i18n"; -export async function loadMagnificPopup() { - await waitForPromise(import("magnific-popup")); -} - -export default async function lightbox( - elem, - siteSettings, - additionalData = {} -) { +export default async function lightbox(elem, additionalData = {}) { if (!elem) { return; } const currentUser = helperContext()?.currentUser; + const siteSettings = helperContext().siteSettings; const caps = helperContext().capabilities; - const imageClickNavigation = caps.touch; + + const { default: PhotoSwipeLightbox } = await import("photoswipe/lightbox"); + const isTestEnv = isTesting() || isRailsTesting(); const canDownload = !siteSettings.prevent_anons_from_downloading_files || !!currentUser; const canQuoteImage = !!currentUser; + const rtl = isDocumentRTL(); + const items = [...elem.querySelectorAll(SELECTORS.DEFAULT_ITEM_SELECTOR)]; - if (siteSettings.experimental_lightbox) { - const { default: PhotoSwipeLightbox } = await import("photoswipe/lightbox"); - const isTestEnv = isTesting() || isRailsTesting(); - - const rtl = isDocumentRTL(); - const items = [...elem.querySelectorAll(SELECTORS.DEFAULT_ITEM_SELECTOR)]; - - if (rtl) { - items.reverse(); - } - - items.forEach((el, index) => { - el.addEventListener("click", (e) => { - e.preventDefault(); - - lightboxEl.loadAndOpen(index); - }); - }); - - const lightboxEl = new PhotoSwipeLightbox({ - dataSource: items, - arrowPrevTitle: i18n("lightbox.previous"), - arrowNextTitle: i18n("lightbox.next"), - closeTitle: i18n("lightbox.close"), - zoomTitle: i18n("lightbox.zoom"), - errorMsg: i18n("lightbox.error"), - showHideAnimationType: isTestEnv ? "none" : "zoom", - counter: false, - escKey: false, - tapAction, - paddingFn, - pswpModule: async () => await import("photoswipe"), - appendToEl: isTesting() && document.getElementById("ember-testing"), - }); - - const keyDownHandler = function (event) { - if (event.key !== "Escape") { - return; - } - - event.stopPropagation(); - event.preventDefault(); - - lightboxEl.pswp.close(); - }; + if (rtl) { + items.reverse(); + } - lightboxEl.on("afterInit", () => { - const el = lightboxEl.pswp.currSlide.data.element; - el.querySelector(".meta")?.classList.add("open"); + items.forEach((el, index) => { + el.addEventListener("click", (e) => { + e.preventDefault(); - lightboxEl.pswp.element.addEventListener("keydown", (event) => - keyDownHandler(event) - ); + lightboxEl.loadAndOpen(index); }); + }); + + const lightboxEl = new PhotoSwipeLightbox({ + dataSource: items, + arrowPrevTitle: i18n("lightbox.previous"), + arrowNextTitle: i18n("lightbox.next"), + closeTitle: i18n("lightbox.close"), + zoomTitle: i18n("lightbox.zoom"), + errorMsg: i18n("lightbox.error"), + showHideAnimationType: isTestEnv ? "none" : "zoom", + counter: false, + escKey: false, + tapAction, + paddingFn, + pswpModule: async () => await import("photoswipe"), + appendToEl: isTesting() && document.getElementById("ember-testing"), + }); + + const keyDownHandler = function (event) { + if (event.key !== "Escape") { + return; + } - lightboxEl.on("close", function () { - lightboxEl.pswp.element.classList.add("pswp--behind-header"); - lightboxEl.pswp.element.removeEventListener("keydown", keyDownHandler); - }); + event.stopPropagation(); + event.preventDefault(); - lightboxEl.on("destroy", () => { - const el = lightboxEl.pswp.currSlide.data.element; - el.querySelector(".meta")?.classList.remove("open"); - }); + lightboxEl.pswp.close(); + }; - lightboxEl.on("uiRegister", function () { - // adds a custom caption to lightbox - lightboxEl.pswp.ui.registerElement({ - name: "caption", - order: 11, - isButton: false, - appendTo: "root", - html: "", - onInit: (caption, pswp) => { - pswp.on("change", () => { - const { element, title, inlineSVG } = pswp.currSlide.data; + lightboxEl.on("afterInit", () => { + const el = lightboxEl.pswp.currSlide.data.element; + el.querySelector(".meta")?.classList.add("open"); - if (!element || !title || inlineSVG) { - return; - } - - const captionTitle = escapeExpression(title); - const captionDetails = - element.querySelector(".informations")?.textContent; - const titleEl = captionTitle - ? `
${captionTitle}
` - : null; - const detailsEl = captionDetails - ? `
${captionDetails}
` - : null; - - caption.innerHTML = [titleEl, detailsEl].filter(Boolean).join(""); - }); - }, - }); + lightboxEl.pswp.element.addEventListener("keydown", (event) => + keyDownHandler(event) + ); + }); + + lightboxEl.on("close", function () { + lightboxEl.pswp.element.classList.add("pswp--behind-header"); + lightboxEl.pswp.element.removeEventListener("keydown", keyDownHandler); + }); + + lightboxEl.on("destroy", () => { + const el = lightboxEl.pswp.currSlide.data.element; + el.querySelector(".meta")?.classList.remove("open"); + }); + + lightboxEl.on("uiRegister", function () { + lightboxEl.pswp.ui.registerElement({ + name: "caption", + order: 11, + isButton: false, + appendTo: "root", + html: "", + onInit: (caption, pswp) => { + pswp.on("change", () => { + const { element, title, inlineSVG } = pswp.currSlide.data; + + if (!element || !title || inlineSVG) { + return; + } - // adds a download button - if (canDownload) { - lightboxEl.pswp.ui.registerElement({ - name: "download-image", - order: 7, - isButton: true, - tagName: "a", - title: i18n("lightbox.download"), - html: { - isCustomSVG: true, - inner: - '', - outlineID: "pswp__icn-download", - }, - onInit: (el, pswp) => { - el.setAttribute("download", ""); - el.setAttribute("target", "_blank"); - el.setAttribute("rel", "noopener"); - - pswp.on("change", () => { - const href = pswp.currSlide.data.element.dataset.downloadHref; - if (!href) { - el.style.display = "none"; - return; - } - el.href = href; - }); - }, + const captionTitle = escapeExpression(title); + const captionDetails = + element.querySelector(".informations")?.innerHTML; + const titleEl = captionTitle + ? `
${captionTitle}
` + : null; + const detailsEl = captionDetails + ? `
${captionDetails}
` + : null; + + caption.innerHTML = [titleEl, detailsEl].filter(Boolean).join(""); }); - } + }, + }); - // adds a view original image button + if (canDownload) { lightboxEl.pswp.ui.registerElement({ - name: "original-image", - order: 8, + name: "download-image", + order: 7, isButton: true, tagName: "a", - title: i18n("lightbox.open"), + title: i18n("lightbox.download"), html: { isCustomSVG: true, inner: - '', - outlineID: "pswp__icn-image", + '', + outlineID: "pswp__icn-download", }, onInit: (el, pswp) => { + el.setAttribute("download", ""); el.setAttribute("target", "_blank"); el.setAttribute("rel", "noopener"); pswp.on("change", () => { - el.href = pswp.currSlide.data.src; + const href = pswp.currSlide.data.element.dataset.downloadHref; + if (!href) { + el.style.display = "none"; + return; + } + el.href = href; }); }, }); + } + + lightboxEl.pswp.ui.registerElement({ + name: "original-image", + order: 8, + isButton: true, + tagName: "a", + title: i18n("lightbox.open"), + html: { + isCustomSVG: true, + inner: + '', + outlineID: "pswp__icn-image", + }, + onInit: (el, pswp) => { + el.setAttribute("target", "_blank"); + el.setAttribute("rel", "noopener"); + + pswp.on("change", () => { + el.href = pswp.currSlide.data.src; + }); + }, + }); + + lightboxEl.pswp.ui.registerElement({ + name: "image-info", + order: 9, + isButton: true, + tagName: "a", + title: i18n("lightbox.image_info"), + html: { + isCustomSVG: true, + inner: + '', + outlineID: "pswp__icn-info", + }, + onInit: (el, pswp) => { + pswp.on("change", () => { + el.style.display = pswp.currSlide.data.details ? "block" : "none"; + }); + }, + onClick: () => { + lightboxEl.pswp.element.classList.toggle("pswp--caption-expanded"); + }, + }); + if (canQuoteImage) { lightboxEl.pswp.ui.registerElement({ - name: "image-info", - order: 9, + name: "quote-image", + order: 10, isButton: true, - tagName: "a", - title: i18n("lightbox.image_info"), + title: i18n("lightbox.quote"), html: { isCustomSVG: true, inner: - '', - outlineID: "pswp__icn-info", + '', + outlineID: "pswp__icn-quote", }, onInit: (el, pswp) => { pswp.on("change", () => { - el.style.display = pswp.currSlide.data.details ? "block" : "none"; + const slideData = pswp.currSlide?.data; + const slideElement = slideData?.element; + el.style.display = canBuildImageQuote(slideElement, slideData) + ? "" + : "none"; }); }, onClick: () => { - lightboxEl.pswp.element.classList.toggle("pswp--caption-expanded"); + const slideData = lightboxEl.pswp.currSlide?.data; + const slideElement = slideData?.element; + quoteImage(slideElement, slideData).then((didQuote) => { + if (didQuote) { + lightboxEl.pswp.close(); + } + }); }, }); + } - if (canQuoteImage) { - lightboxEl.pswp.ui.registerElement({ - name: "quote-image", - order: 10, - isButton: true, - title: i18n("lightbox.quote"), - html: { - isCustomSVG: true, - inner: - '', - outlineID: "pswp__icn-quote", - }, - onInit: (el, pswp) => { - pswp.on("change", () => { - const slideData = pswp.currSlide?.data; - const slideElement = slideData?.element; - el.style.display = canBuildImageQuote(slideElement, slideData) - ? "" - : "none"; - }); - }, - onClick: () => { - const slideData = lightboxEl.pswp.currSlide?.data; - const slideElement = slideData?.element; - quoteImage(slideElement, slideData).then((didQuote) => { - if (didQuote) { - lightboxEl.pswp.close(); - } - }); - }, + lightboxEl.pswp.ui.registerElement({ + name: "custom-counter", + order: 6, + isButton: false, + appendTo: "bar", + onInit: (el, pswp) => { + pswp.on("change", () => { + const total = pswp.getNumItems(); + const index = rtl ? total - pswp.currIndex : pswp.currIndex + 1; + el.textContent = `${index} / ${total}`; }); - } - - lightboxEl.pswp.ui.registerElement({ - name: "custom-counter", - order: 6, - isButton: false, - appendTo: "bar", - onInit: (el, pswp) => { - pswp.on("change", () => { - const total = pswp.getNumItems(); - const index = rtl ? total - pswp.currIndex : pswp.currIndex + 1; - el.textContent = `${index} / ${total}`; - }); - }, - }); + }, }); + }); - lightboxEl.addFilter("itemData", (data) => { - const el = data.element; - - if (!el) { - return data; - } - - // use data attributes for width/height when available - let width = el.getAttribute("data-target-width"); - let height = el.getAttribute("data-target-height"); - const isSVG = el.querySelector("svg[viewBox]") !== null; - - if (isSVG) { - const encodedSVG = encodeURIComponent(el.innerHTML.trim()); - data.src = `data:image/svg+xml,${encodedSVG}`; - data.inlineSVG = true; - width = (el.clientWidth || 400) * 10; - height = (el.clientHeight || 300) * 10; - - // 1x1 placeholder to prevent background flicker on inline SVGs - data.msrc = - "data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20width%3D%271%27%20height%3D%271%27/%3E"; - } - - const imgInfo = el.querySelector(".informations")?.textContent || ""; - const imgEl = el.tagName === "IMG" ? el : el.querySelector("img"); - - if (!width || !height) { - const dimensions = imgInfo.trim().split(" ")[0]; - [width, height] = dimensions.split(/x|×/).map(Number); - } - - // this ensures that cropped images (eg: grid) do not cause jittering when closing - data.thumbCropped = true; - - data.src = data.src || el.getAttribute("data-large-src"); - data.origSrc = - imgEl?.getAttribute("data-orig-src") || - el.getAttribute("data-orig-src") || - null; - data.title = el.title || imgEl?.alt || imgEl?.title || null; - data.base62SHA1 = - imgEl?.getAttribute("data-base62-sha1") || - el.getAttribute("data-base62-sha1") || - null; - data.details = imgInfo; - data.w = data.width = width; - data.h = data.height = height; - data.targetWidth = - el.getAttribute("data-target-width") || - imgEl?.getAttribute("width") || - null; - data.targetHeight = - el.getAttribute("data-target-height") || - imgEl?.getAttribute("height") || - null; - - // So we can attach things like a Post model from the caller. - Object.keys(additionalData).forEach((key) => { - data[key] = additionalData[key]; - }); + lightboxEl.addFilter("itemData", (data) => { + const el = data.element; + if (!el) { return data; - }); - - const itemsToPreload = items.filter((item) => { - const { largeSrc, targetWidth, targetHeight } = item.dataset; - const hasImageSrc = largeSrc || item.getAttribute("href"); - const missingDimensions = !targetWidth || !targetHeight; - const imgDimensions = item - .querySelector(".informations") - ?.textContent.trim() - .split(" ")[0]; - const missingMetaData = !imgDimensions?.split(/x|×/).every((d) => !!d); - - return hasImageSrc && missingDimensions && missingMetaData; - }); - - await Promise.all( - itemsToPreload.map( - (item) => - new Promise((resolve) => { - const img = new Image(); - img.src = - item.getAttribute("data-large-src") || item.getAttribute("href"); - img.onload = () => { - item.setAttribute("data-target-width", img.naturalWidth); - item.setAttribute("data-target-height", img.naturalHeight); - resolve(); - }; - img.onerror = resolve; - }) - ) - ); - function tapAction(pt, event) { - const pswp = lightboxEl.pswp; - if (event.target.classList.contains("pswp__img")) { - pswp?.element?.classList.toggle("pswp--ui-visible"); - } else { - pswp?.close(); - } } - function paddingFn(viewportSize, itemData) { - if (viewportSize.x < 1200 || caps.isMobileDevice) { - return { top: 0, bottom: 0, left: 0, right: 0 }; - } - return { - top: 20, - bottom: itemData.title ? 75 : 20, - left: 20, - right: 20, - }; + // use data attributes for width/height when available + let width = el.getAttribute("data-target-width"); + let height = el.getAttribute("data-target-height"); + const isSVG = el.querySelector("svg[viewBox]") !== null; + + if (isSVG) { + const encodedSVG = encodeURIComponent(el.innerHTML.trim()); + data.src = `data:image/svg+xml,${encodedSVG}`; + data.inlineSVG = true; + width = (el.clientWidth || 400) * 10; + height = (el.clientHeight || 300) * 10; + + // 1x1 placeholder to prevent background flicker on inline SVGs + data.msrc = + "data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20width%3D%271%27%20height%3D%271%27/%3E"; } - lightboxEl.init(); - } else { - // Magnific lightbox - const images = elem.querySelectorAll(SELECTORS.DEFAULT_ITEM_SELECTOR); + const imgInfo = el.querySelector(".informations")?.textContent || ""; + const imgEl = el.tagName === "IMG" ? el : el.querySelector("img"); - if (!images.length) { - return; + if (!width || !height) { + const dimensions = imgInfo.trim().split(" ")[0]; + [width, height] = dimensions.split(/x|×/).map(Number); } - await loadMagnificPopup(); - - $(images).magnificPopup({ - type: "image", - closeOnContentClick: false, - removalDelay: isTesting() ? 0 : 300, - mainClass: "mfp-zoom-in", - tClose: i18n("lightbox.close"), - tLoading: spinnerHTML, - prependTo: isTesting() && document.getElementById("ember-testing"), - - gallery: { - enabled: true, - tPrev: i18n("lightbox.previous"), - tNext: i18n("lightbox.next"), - tCounter: i18n("lightbox.counter"), - navigateByImgClick: imageClickNavigation, - }, - - ajax: { - tError: i18n("lightbox.content_load_error"), - }, - - callbacks: { - open() { - if (!imageClickNavigation) { - const wrap = this.wrap, - img = this.currItem.img, - maxHeight = img.css("max-height"); - - wrap.on("click.pinhandler", "img", function () { - wrap.toggleClass("mfp-force-scrollbars"); - img.css( - "max-height", - wrap.hasClass("mfp-force-scrollbars") ? "none" : maxHeight - ); - }); - } + // this ensures that cropped images (eg: grid) do not cause jittering when closing + data.thumbCropped = true; + + data.src = data.src || el.getAttribute("data-large-src"); + data.origSrc = + imgEl?.getAttribute("data-orig-src") || + el.getAttribute("data-orig-src") || + null; + data.title = el.title || imgEl?.alt || imgEl?.title || null; + data.base62SHA1 = + imgEl?.getAttribute("data-base62-sha1") || + el.getAttribute("data-base62-sha1") || + null; + data.details = imgInfo; + data.w = data.width = width; + data.h = data.height = height; + data.targetWidth = + el.getAttribute("data-target-width") || + imgEl?.getAttribute("width") || + null; + data.targetHeight = + el.getAttribute("data-target-height") || + imgEl?.getAttribute("height") || + null; + + // So we can attach things like a Post model from the caller. + Object.keys(additionalData).forEach((key) => { + data[key] = additionalData[key]; + }); - if (caps.isAppWebview) { - postRNWebviewMessage( - "headerBg", - $(".mfp-bg").css("background-color") - ); - } - }, - change() { - this.wrap.removeClass("mfp-force-scrollbars"); - }, - beforeClose() { - this.wrap.off("click.pinhandler"); - this.wrap.removeClass("mfp-force-scrollbars"); - if (caps.isAppWebview) { - postRNWebviewMessage( - "headerBg", - $(".d-header").css("background-color") - ); - } - }, - }, + return data; + }); + + const itemsToPreload = items.filter((item) => { + const { largeSrc, targetWidth, targetHeight } = item.dataset; + const hasImageSrc = largeSrc || item.getAttribute("href"); + const missingDimensions = !targetWidth || !targetHeight; + const imgDimensions = item + .querySelector(".informations") + ?.textContent.trim() + .split(" ")[0]; + const missingMetaData = !imgDimensions?.split(/x|×/).every((d) => !!d); + + return hasImageSrc && missingDimensions && missingMetaData; + }); + + await Promise.all( + itemsToPreload.map( + (item) => + new Promise((resolve) => { + const img = new Image(); + img.src = + item.getAttribute("data-large-src") || item.getAttribute("href"); + img.onload = () => { + item.setAttribute("data-target-width", img.naturalWidth); + item.setAttribute("data-target-height", img.naturalHeight); + resolve(); + }; + img.onerror = resolve; + }) + ) + ); + function tapAction(pt, event) { + const pswp = lightboxEl.pswp; + if (event.target.classList.contains("pswp__img")) { + pswp?.element?.classList.toggle("pswp--ui-visible"); + } else { + pswp?.close(); + } + } - image: { - tError: i18n("lightbox.image_load_error"), - titleSrc(item) { - const href = item.el.data("download-href") || item.src; - const downloadText = - renderIcon("string", "download") + i18n("lightbox.download"); - const origImgText = - renderIcon("string", "image") + i18n("lightbox.open"); - - let src = [ - escapeExpression(item.el.attr("title")), - $("span.informations", item.el).text(), - ]; - - if (canDownload) { - src.push( - `${downloadText}` - ); - } - src.push( - `${origImgText}` - ); - return src.join(" · "); - }, - }, - }); + function paddingFn(viewportSize, itemData) { + if (viewportSize.x < 1200 || caps.isMobileDevice) { + return { top: 0, bottom: 0, left: 0, right: 0 }; + } + return { + top: 20, + bottom: itemData.title ? 75 : 20, + left: 20, + right: 20, + }; } + + lightboxEl.init(); } diff --git a/frontend/discourse/package.json b/frontend/discourse/package.json index 3f4071e7ecdea..732b40040b4ed 100644 --- a/frontend/discourse/package.json +++ b/frontend/discourse/package.json @@ -55,7 +55,6 @@ "highlight.js": "11.11.1", "immer": "^11.0.1", "jspreadsheet-ce": "^4.15.0", - "magnific-popup": "1.1.0", "moment": "2.30.1", "moment-timezone": "0.5.45", "morphlex": "^0.0.16", diff --git a/frontend/discourse/tests/acceptance/lightbox-test.js b/frontend/discourse/tests/acceptance/lightbox-test.js index 9769e0da551c9..81e16451ae5b4 100644 --- a/frontend/discourse/tests/acceptance/lightbox-test.js +++ b/frontend/discourse/tests/acceptance/lightbox-test.js @@ -26,45 +26,7 @@ acceptance("Lightbox", function (needs) { ); }); - test("Shows download and direct URL", async function (assert) { - this.siteSettings.experimental_lightbox = false; - - await visit("/t/internationalization-localization/280"); - await click(".lightbox"); - - assert - .dom(".mfp-title") - .hasText( - " · 1500×842 234 KB · Download · Original image" - ); - - assert - .dom(".image-source-link:nth-child(1)") - .hasAttribute( - "href", - "//discourse.local/uploads/default/ad768537789cdf4679a18161ac0b0b6f0f4ccf9e" - ); - - assert - .dom(".image-source-link:nth-child(2)") - .hasAttribute("href", `/images/d-logo-sketch.png`); - - await click(".mfp-close"); - }); - - test("Correctly escapes image caption", async function (assert) { - this.siteSettings.experimental_lightbox = false; - - await visit("/t/internationalization-localization/280"); - await click(".lightbox"); - - assert.dom(".mfp-title").hasHtml(/^<script>image<\/script> · /); - }); - - // PhotoSwipe test("Shows 'download' and 'original image' buttons", async function (assert) { - this.siteSettings.experimental_lightbox = true; - await visit("/t/internationalization-localization/280"); await click(".lightbox"); @@ -92,8 +54,6 @@ acceptance("Lightbox", function (needs) { }); test("Correctly escapes image caption", async function (assert) { - this.siteSettings.experimental_lightbox = true; - await visit("/t/internationalization-localization/280"); await click(".lightbox"); diff --git a/frontend/discourse/tests/integration/components/uppy-image-uploader-test.gjs b/frontend/discourse/tests/integration/components/uppy-image-uploader-test.gjs index dda96f47f60e3..d6d9dd151fbcb 100644 --- a/frontend/discourse/tests/integration/components/uppy-image-uploader-test.gjs +++ b/frontend/discourse/tests/integration/components/uppy-image-uploader-test.gjs @@ -1,4 +1,4 @@ -import { click, render, triggerEvent } from "@ember/test-helpers"; +import { click, render, triggerEvent, waitFor } from "@ember/test-helpers"; import { module, test } from "qunit"; import UppyImageUploader from "discourse/components/uppy-image-uploader"; import { setupRenderingTest } from "discourse/tests/helpers/component-test"; @@ -7,8 +7,6 @@ module("Integration | Component | uppy-image-uploader", function (hooks) { setupRenderingTest(hooks); test("with image", async function (assert) { - this.siteSettings.experimental_lightbox = false; - await render(