Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion com.woltlab.wcf/templates/attachments.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{foreach from=$attachmentList->getGroupedObjects($objectID) item=attachment}
{if $attachment->showAsImage() && !$attachment->isEmbedded()}
<li class="attachmentThumbnail" data-attachment-id="{$attachment->attachmentID}">
<a href="{$attachment->getLink()}"{if $attachment->canDownload()} data-fancybox="message-{$attachmentList->getObjectTypeName()}-{$objectID}" data-caption="{$attachment->filename}" aria-title="{lang}wcf.attachment.image.title{/lang}"{/if}>
<a href="{$attachment->getLink()}"{if $attachment->canDownload()} data-type="image" data-fancybox="message-{$attachmentList->getObjectTypeName()}-{$objectID}" data-caption="{$attachment->filename}" aria-title="{lang}wcf.attachment.image.title{/lang}"{/if}>
<div class="attachmentThumbnailContainer">
<span class="attachmentThumbnailImage">
{if $attachment->hasThumbnail()}
Expand Down
2 changes: 1 addition & 1 deletion com.woltlab.wcf/templates/entryAttachments.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{foreach from=$attachmentList->getGroupedObjects($objectID) item=attachment}
{if $attachment->showAsImage() && !$attachment->isEmbedded()}
<li class="attachmentThumbnail" data-attachment-id="{$attachment->attachmentID}">
<a href="{$attachment->getLink()}"{if $attachment->canDownload()} data-fancybox="message-{$attachmentList->getObjectTypeName()}-{$objectID}" data-caption="{$attachment->filename}" aria-title="{lang}wcf.attachment.image.title{/lang}"{/if}>
<a href="{$attachment->getLink()}"{if $attachment->canDownload()} data-type="image" data-fancybox="message-{$attachmentList->getObjectTypeName()}-{$objectID}" data-caption="{$attachment->filename}" aria-title="{lang}wcf.attachment.image.title{/lang}"{/if}>
<div class="attachmentThumbnailContainer">
<span class="attachmentThumbnailImage">
{if $attachment->hasThumbnail()}
Expand Down
2 changes: 1 addition & 1 deletion com.woltlab.wcf/templates/shared_bbcode_wsm.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{if $media->isImage}
{if $thumbnailSize != 'original'}
{if !$removeLinks}
<a href="{$mediaLink}" data-caption="{$media->title}" class="embeddedAttachmentLink" data-fancybox="message-{$activeMessageObjectType}-{$activeMessageID}">
<a href="{$mediaLink}" data-caption="{$media->title}" class="embeddedAttachmentLink" data-type="image" data-fancybox="message-{$activeMessageObjectType}-{$activeMessageID}">
{/if}
<img src="{$thumbnailLink}" alt="{$media->altText}" title="{$media->title}" width="{@$media->getThumbnailWidth($thumbnailSize)}" height="{@$media->getThumbnailHeight($thumbnailSize)}" loading="lazy">
{if !$removeLinks}
Expand Down
36 changes: 36 additions & 0 deletions com.woltlab.wcf/templates/shared_imageViewer.tpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
{if !$__imageViewerLoaded|isset}
<script data-eager="true">
{jsphrase name='wcf.fancybox.imageError'}
{jsphrase name='wcf.fancybox.moveUp'}
{jsphrase name='wcf.fancybox.moveDown'}
{jsphrase name='wcf.fancybox.moveLeft'}
{jsphrase name='wcf.fancybox.moveRight'}
{jsphrase name='wcf.fancybox.zoomIn'}
{jsphrase name='wcf.fancybox.zoomOut'}
{jsphrase name='wcf.fancybox.toggleFull'}
{jsphrase name='wcf.fancybox.toggle1to1'}
{jsphrase name='wcf.fancybox.iterateZoom'}
{jsphrase name='wcf.fancybox.rotateCcw'}
{jsphrase name='wcf.fancybox.rotateCw'}
{jsphrase name='wcf.fancybox.flipX'}
{jsphrase name='wcf.fancybox.flipY'}
{jsphrase name='wcf.fancybox.reset'}
{jsphrase name='wcf.fancybox.error'}
{jsphrase name='wcf.fancybox.next'}
{jsphrase name='wcf.fancybox.prev'}
{jsphrase name='wcf.fancybox.goto'}
{jsphrase name='wcf.fancybox.download'}
{jsphrase name='wcf.fancybox.toggleFullscreen'}
{jsphrase name='wcf.fancybox.toggleExpand'}
{jsphrase name='wcf.fancybox.toggleThumbs'}
{jsphrase name='wcf.fancybox.toggleAutoplay'}
{jsphrase name='wcf.fancybox.close'}
{jsphrase name='wcf.fancybox.next'}
{jsphrase name='wcf.fancybox.prev'}
{jsphrase name='wcf.fancybox.modal'}
{jsphrase name='wcf.fancybox.elementNotFound'}
{jsphrase name='wcf.fancybox.iframeError'}

{
let stylesheet = document.getElementById("fancybox-stylesheet");
if (stylesheet === null) {
Expand All @@ -13,6 +44,11 @@
}
}
</script>
{if MESSAGE_ENABLE_USER_CONSENT}
<template id="consentImageViewer" data-show-all-media="{if $__wcf->getUser()->userID && $__wcf->getUser()->getUserOption('enableEmbeddedMedia')}1{else}0{/if}">
{include file="messageUserConsent" target='' host='' url='' sandbox=true}
</template>
{/if}

{assign var=__imageViewerLoaded value=true}
{/if}
2 changes: 1 addition & 1 deletion com.woltlab.wcf/templates/shared_uploadFieldComponent.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
{foreach from=$uploadFieldFiles item=file}
<li class="box64 uploadedFile" data-unique-file-id="{$file->getUniqueFileId()}">
{if $file->isImage()}
<a href="{$file->getImage()}" data-fancybox data-caption="{$file->getFilename()}">
<a href="{$file->getImage()}" data-type="image" data-fancybox data-caption="{$file->getFilename()}">
<img src="{$file->getImage()}" width="{$file->getWidth()}" height="{$file->getHeight()}" loading="lazy" alt="" class="formUploadHandlerContentListImage">
</a>
{else}
Expand Down
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"dependencies": {
"@ckeditor/ckeditor5-inspector": "^4.1.0",
"@fancyapps/ui": "^5.0.36",
"@fancyapps/ui": "^6.0.5",
"@googlemaps/markerclusterer": "2.5.3",
"@types/facebook-js-sdk": "^3.3.12",
"@types/google.maps": "^3.58.1",
Expand Down
107 changes: 107 additions & 0 deletions ts/WoltLabSuite/Core/Component/Image/Fancybox/ConsentPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* @author Olaf Braun
* @copyright 2001-2025 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
*/

import { CarouselSlide, FancyboxInstance } from "@fancyapps/ui";
import { CarouselInstance } from "@fancyapps/ui/dist/carousel/carousel";
import { allExternalMediaEnabled } from "WoltLabSuite/Core/Ui/Message/UserConsent";
import { stringToBool } from "WoltLabSuite/Core/Core";

export class ConsentPlugin {
#api: FancyboxInstance | undefined = undefined;
#sliderToType = new Map<CarouselSlide, string | undefined>();
#templateNode: HTMLTemplateElement | undefined;
#onAddSlideEvent = this.#onAddSlide.bind(this);
#onAttachSlideElEvent = this.#onAttachSlideEl.bind(this);

constructor() {
this.#templateNode = document.getElementById("consentImageViewer") as HTMLTemplateElement;
}

init(api: FancyboxInstance): void {
if (this.#showAllMedia()) {
return;
}

this.#api = api;

api.on("Carousel.addSlide", this.#onAddSlideEvent).on("Carousel.attachSlideEl", this.#onAttachSlideElEvent);
}

destroy(): void {
this.#api
?.off("Carousel.addSlide", this.#onAddSlideEvent)
?.off("Carousel.attachSlideEl", this.#onAttachSlideElEvent);
}

#onAddSlide(_: FancyboxInstance, __: CarouselInstance, slide: CarouselSlide): void {
if (!slide.src) {
return;
}

if (this.#isExternalURL(slide.src)) {
this.#sliderToType.set(slide, slide.type);
slide.type = "consent";
}
}

#onAttachSlideEl(_: FancyboxInstance, carousel: CarouselInstance, slide: CarouselSlide): void {
if (slide.type !== "consent") {
return;
}

slide.el!.innerHTML = "";
slide.el!.append(this.getConsentHTML(carousel, slide));
}

private getConsentHTML(carousel: CarouselInstance, slide: CarouselSlide): DocumentFragment {
const clone = this.#templateNode!.content.cloneNode(true) as DocumentFragment;

const messageUserConsent = clone.querySelector(".messageUserConsent") as HTMLElement;
messageUserConsent.dataset.payload = "";

const externalURL = clone.querySelector(".externalURL") as HTMLAnchorElement;
const url = new URL(slide.src!);
externalURL.href = slide.src!;
externalURL.innerText = url.host;

const button = clone.querySelector(".jsButtonMessageUserConsentEnable") as HTMLButtonElement;
button.addEventListener("click", () => {
this.destroy();

this.#templateNode!.dataset.showAllMedia = "1";

carousel
.getSlides()
.filter((slide) => slide.type === "consent")
.forEach((slide) => {
slide.type = this.#sliderToType.get(slide);
slide.el!.innerHTML = "";
});

// refresh current slide content
carousel.emit("detachSlideEl", slide);
carousel.emit("attachSlideEl", slide);
});

return clone;
}

#showAllMedia(): boolean {
if (!this.#templateNode) {
return true;
}

if (stringToBool(this.#templateNode.dataset.showAllMedia!)) {
return true;
}

return allExternalMediaEnabled();
}

#isExternalURL(url: string): boolean {
return new URL(url).origin !== window.location.origin;
}
}
89 changes: 61 additions & 28 deletions ts/WoltLabSuite/Core/Component/Image/Viewer.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,78 @@
import { Fancybox } from "@fancyapps/ui";
import { userSlideType } from "@fancyapps/ui/types/Carousel/types";
import { OptionsType } from "@fancyapps/ui/types/Fancybox/options";
/**
* @author Olaf Braun
* @copyright 2001-2025 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
*/

import { Fancybox, CarouselSlide, FancyboxInstance } from "@fancyapps/ui";
import { getPageOverlayContainer } from "WoltLabSuite/Core/Helper/PageOverlay";
import { getPhrase } from "WoltLabSuite/Core/Language";
import { ConsentPlugin } from "./Fancybox/ConsentPlugin";

const LOCALES = ["cs", "de", "en", "es", "fr", "it", "lv", "pl", "sk"];
setDefaultConfig();

export function setup() {
void getDefaultConfig().then((config) => {
Fancybox.bind("[data-fancybox]", config);
});
Fancybox.bind(
'[data-fancybox]:is([data-type="image"],[data-type="youtube"],[data-type="vimeo"],[data-type="video"])',
);
}

export function setupLegacy() {
void getDefaultConfig().then((config) => {
Fancybox.bind(".jsImageViewer", {
...config,
groupAll: true,
});
Fancybox.bind(".jsImageViewer", {
groupAll: true,
});
}

export async function createFancybox(userSlides?: Array<userSlideType>): Promise<Fancybox> {
return new Fancybox(userSlides, await getDefaultConfig());
export function showFancybox(userSlides?: Array<CarouselSlide>): FancyboxInstance {
return Fancybox.show(userSlides);
}

async function getDefaultConfig(): Promise<Partial<OptionsType>> {
return {
l10n: await getLocalization(),
parentEl: getPageOverlayContainer(),
Html: {
videoAutoplay: false,
function setDefaultConfig(): void {
const defaultConfig = Fancybox.getDefaults();
defaultConfig.l10n = getLocalization();
defaultConfig.parentEl = getPageOverlayContainer();
defaultConfig.Carousel = {
Video: {
autoplay: false,
},
};
}

export async function getLocalization(): Promise<Record<string, string>> {
let locale = document.documentElement.lang;

if (!LOCALES.includes(locale)) {
locale = "en";
if (!defaultConfig.plugins) {
defaultConfig.plugins = {};
}
defaultConfig.plugins.consent = () => {
return new ConsentPlugin();
};
}

return (await import(`@fancyapps/ui/l10n/${locale}`))[locale];
export function getLocalization(): Record<string, string> {
return {
IMAGE_ERROR: getPhrase("wcf.fancybox.imageError"),
MOVE_UP: getPhrase("wcf.fancybox.moveUp"),
MOVE_DOWN: getPhrase("wcf.fancybox.moveDown"),
MOVE_LEFT: getPhrase("wcf.fancybox.moveLeft"),
MOVE_RIGHT: getPhrase("wcf.fancybox.moveRight"),
ZOOM_IN: getPhrase("wcf.fancybox.zoomIn"),
ZOOM_OUT: getPhrase("wcf.fancybox.zoomOut"),
TOGGLE_FULL: getPhrase("wcf.fancybox.toggleFull"),
TOGGLE_1TO1: getPhrase("wcf.fancybox.toggle1to1"),
ITERATE_ZOOM: getPhrase("wcf.fancybox.iterateZoom"),
ROTATE_CCW: getPhrase("wcf.fancybox.rotateCcw"),
ROTATE_CW: getPhrase("wcf.fancybox.rotateCw"),
FLIP_X: getPhrase("wcf.fancybox.flipX"),
FLIP_Y: getPhrase("wcf.fancybox.flipY"),
RESET: getPhrase("wcf.fancybox.reset"),
ERROR: getPhrase("wcf.fancybox.error"),
GOTO: getPhrase("wcf.fancybox.goto"),
DOWNLOAD: getPhrase("wcf.fancybox.download"),
TOGGLE_EXPAND: getPhrase("wcf.fancybox.toggleExpand"),
TOGGLE_FULLSCREEN: getPhrase("wcf.fancybox.toggleFullscreen"),
TOGGLE_THUMBS: getPhrase("wcf.fancybox.toggleThumbs"),
TOGGLE_AUTOPLAY: getPhrase("wcf.fancybox.toggleAutoplay"),
CLOSE: getPhrase("wcf.fancybox.close"),
NEXT: getPhrase("wcf.fancybox.next"),
PREV: getPhrase("wcf.fancybox.prev"),
MODAL: getPhrase("wcf.fancybox.modal"),
ELEMENT_NOT_FOUND: getPhrase("wcf.fancybox.elementNotFound"),
IFRAME_ERROR: getPhrase("wcf.fancybox.iframeError"),
};
}
13 changes: 8 additions & 5 deletions ts/WoltLabSuite/Core/Ui/Message/UserConsent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ import DomUtil from "../../Dom/Util";
import User from "../../User";

class UserConsent {
private enableAll = false;

constructor() {
if (window.sessionStorage.getItem(`${Core.getStoragePrefix()}user-consent`) === "all") {
this.enableAll = true;
enableAll = true;
}

this.registerEventListeners();
Expand All @@ -27,7 +25,7 @@ class UserConsent {
}

private registerEventListeners(): void {
if (this.enableAll) {
if (enableAll) {
this.enableAllExternalMedia();
} else {
wheneverFirstSeen(".jsButtonMessageUserConsentEnable", (button) => {
Expand All @@ -39,7 +37,7 @@ class UserConsent {
private click(event: MouseEvent): void {
event.preventDefault();

this.enableAll = true;
enableAll = true;

this.enableAllExternalMedia();

Expand Down Expand Up @@ -72,10 +70,15 @@ class UserConsent {
}
}

let enableAll = false;
let userConsent: UserConsent;

export function init(): void {
if (!userConsent) {
userConsent = new UserConsent();
}
}

export function allExternalMediaEnabled(): boolean {
return enableAll;
}
11 changes: 10 additions & 1 deletion wcfsetup/install/files/js/3rdParty/fancybox/fancybox.umd.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion wcfsetup/install/files/js/3rdParty/fancybox/l10n/cs.js

This file was deleted.

1 change: 0 additions & 1 deletion wcfsetup/install/files/js/3rdParty/fancybox/l10n/de.js

This file was deleted.

1 change: 0 additions & 1 deletion wcfsetup/install/files/js/3rdParty/fancybox/l10n/en.js

This file was deleted.

1 change: 0 additions & 1 deletion wcfsetup/install/files/js/3rdParty/fancybox/l10n/es.js

This file was deleted.

Loading