diff --git a/apps/client/src/services/ckeditor_photoswipe_integration.ts b/apps/client/src/services/ckeditor_photoswipe_integration.ts index 2db9b7f08..d326ee668 100644 --- a/apps/client/src/services/ckeditor_photoswipe_integration.ts +++ b/apps/client/src/services/ckeditor_photoswipe_integration.ts @@ -5,6 +5,7 @@ import mediaViewer from './media_viewer.js'; import galleryManager from './gallery_manager.js'; +import appContext from '../components/app_context.js'; import type { MediaItem } from './media_viewer.js'; import type { GalleryItem } from './gallery_manager.js'; @@ -97,9 +98,11 @@ class CKEditorPhotoSwipeIntegration { return; } - // Make image clickable + // Make image clickable and mark it as PhotoSwipe-enabled img.style.cursor = 'zoom-in'; img.style.transition = 'opacity 0.2s'; + img.classList.add('photoswipe-enabled'); + img.setAttribute('data-photoswipe', 'true'); // Store event handlers for cleanup const mouseEnterHandler = () => { @@ -121,6 +124,24 @@ class CKEditorPhotoSwipeIntegration { // Store handlers for cleanup (img as any)._photoswipeHandlers = { mouseEnterHandler, mouseLeaveHandler }; + // Add double-click handler to prevent default navigation behavior + const dblClickHandler = (e: MouseEvent) => { + // Only prevent double-click in specific contexts to avoid breaking other features + if (img.closest('.attachment-detail-wrapper') || + img.closest('.note-detail-editable-text') || + img.closest('.note-detail-readonly-text')) { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + + // Trigger the same behavior as single click (open lightbox) + img.click(); + } + }; + + img.addEventListener('dblclick', dblClickHandler, true); // Use capture phase to ensure we get it first + (img as any)._photoswipeHandlers.dblClickHandler = dblClickHandler; + // Add click handler img.addEventListener('click', (e) => { e.preventDefault(); @@ -194,6 +215,27 @@ class CKEditorPhotoSwipeIntegration { } }, { onClose: () => { + // Check if we're in attachment detail view and need to reset viewScope + const activeContext = appContext.tabManager.getActiveContext(); + if (activeContext?.viewScope?.viewMode === 'attachments') { + // Get the note ID from the image source + const attachmentMatch = img.src.match(/\/api\/attachments\/([A-Za-z0-9_]+)\/image\//); + if (attachmentMatch) { + const currentAttachmentId = activeContext.viewScope.attachmentId; + if (currentAttachmentId === attachmentMatch[1]) { + // Actually reset the viewScope instead of just logging + try { + if (activeContext.note) { + activeContext.setNote(activeContext.note.noteId, { + viewScope: { viewMode: 'default' } + }); + } + } catch (error) { + console.error('Failed to reset viewScope after PhotoSwipe close:', error); + } + } + } + } // Restore focus to the image img.focus(); } @@ -429,6 +471,9 @@ class CKEditorPhotoSwipeIntegration { if (handlers) { img.removeEventListener('mouseenter', handlers.mouseEnterHandler); img.removeEventListener('mouseleave', handlers.mouseLeaveHandler); + if (handlers.dblClickHandler) { + img.removeEventListener('dblclick', handlers.dblClickHandler, true); + } delete (img as any)._photoswipeHandlers; } diff --git a/apps/client/src/widgets/attachment_detail.ts b/apps/client/src/widgets/attachment_detail.ts index 9bdcd1ce2..a55938e40 100644 --- a/apps/client/src/widgets/attachment_detail.ts +++ b/apps/client/src/widgets/attachment_detail.ts @@ -9,6 +9,7 @@ import contentRenderer from "../services/content_renderer.js"; import toastService from "../services/toast.js"; import type FAttachment from "../entities/fattachment.js"; import type { EventData } from "../components/app_context.js"; +import appContext from "../components/app_context.js"; import mediaViewer from "../services/media_viewer.js"; import type { MediaItem } from "../services/media_viewer.js"; @@ -114,7 +115,9 @@ const TPL = /*html*/`
- + @@ -150,6 +153,14 @@ export default class AttachmentDetailWidget extends BasicWidget { this.$widget.find(".attachment-detail-wrapper").empty().append($(TPL).find(".attachment-detail-wrapper").html()); this.$wrapper = this.$widget.find(".attachment-detail-wrapper"); this.$wrapper.addClass(this.isFullDetail ? "full-detail" : "list-view"); + + // Setup back to note button (only show in full detail mode) + if (this.isFullDetail) { + const $backBtn = this.$wrapper.find('.back-to-note-btn'); + $backBtn.on('click', () => this.handleBackToNote()); + } else { + this.$wrapper.find('.back-to-note-btn').hide(); + } if (!this.isFullDetail) { const $link = await linkService.createLink(this.attachment.ownerId, { @@ -255,6 +266,15 @@ export default class AttachmentDetailWidget extends BasicWidget { console.log('Attachment image opened in lightbox'); }, onClose: () => { + // Check if we're in attachment detail view and reset viewScope if needed + const activeContext = appContext.tabManager.getActiveContext(); + if (activeContext?.viewScope?.viewMode === 'attachments' && + activeContext?.viewScope?.attachmentId === this.attachment.attachmentId) { + // Reset to normal note view when closing lightbox from attachment detail + activeContext.setNote(this.attachment.ownerId, { + viewScope: { viewMode: 'default' } + }); + } // Restore focus to the image $img.focus(); } @@ -307,6 +327,28 @@ export default class AttachmentDetailWidget extends BasicWidget { } } + async handleBackToNote() { + try { + const activeContext = appContext.tabManager.getActiveContext(); + if (!activeContext) { + console.warn('No active context available for navigation'); + return; + } + + if (!this.attachment.ownerId) { + console.error('Cannot navigate back: no owner ID available'); + return; + } + + await activeContext.setNote(this.attachment.ownerId, { + viewScope: { viewMode: 'default' } + }); + } catch (error) { + console.error('Failed to navigate back to note:', error); + toastService.showError('Failed to navigate back to note'); + } + } + cleanup() { // Remove all event handlers before cleanup const $contentWrapper = this.$wrapper?.find('.attachment-content-wrapper'); @@ -318,6 +360,9 @@ export default class AttachmentDetailWidget extends BasicWidget { } } + // Remove back button handler + this.$wrapper?.find('.back-to-note-btn').off('click'); + super.cleanup(); } } diff --git a/apps/client/src/widgets/type_widgets/abstract_text_type_widget.ts b/apps/client/src/widgets/type_widgets/abstract_text_type_widget.ts index 5061d3b57..836fcba6d 100644 --- a/apps/client/src/widgets/type_widgets/abstract_text_type_widget.ts +++ b/apps/client/src/widgets/type_widgets/abstract_text_type_widget.ts @@ -6,6 +6,7 @@ import contentRenderer from "../../services/content_renderer.js"; import utils from "../../services/utils.js"; import options from "../../services/options.js"; import attributes from "../../services/attributes.js"; +import ckeditorPhotoswipeIntegration from "../../services/ckeditor_photoswipe_integration.js"; export default class AbstractTextTypeWidget extends TypeWidget { doRender() { @@ -35,7 +36,29 @@ export default class AbstractTextTypeWidget extends TypeWidget { const parsedImage = await this.parseFromImage($img); if (parsedImage) { - appContext.tabManager.getActiveContext()?.setNote(parsedImage.noteId, { viewScope: parsedImage.viewScope }); + // Check if this is an attachment image and PhotoSwipe is available + if (parsedImage.viewScope?.attachmentId) { + // Instead of navigating to attachment detail, trigger PhotoSwipe + // Check if the image is already processed by PhotoSwipe + const imgElement = $img[0] as HTMLImageElement; + + // Check if PhotoSwipe is integrated with this image using multiple reliable indicators + const hasPhotoSwipe = imgElement.classList.contains('photoswipe-enabled') || + imgElement.hasAttribute('data-photoswipe') || + imgElement.style.cursor === 'zoom-in'; + + if (hasPhotoSwipe) { + // Image has PhotoSwipe integration, trigger click to open lightbox + $img.trigger('click'); + return; + } + + // Otherwise, fall back to opening attachment detail (but with improved navigation) + appContext.tabManager.getActiveContext()?.setNote(parsedImage.noteId, { viewScope: parsedImage.viewScope }); + } else { + // Regular note image, navigate normally + appContext.tabManager.getActiveContext()?.setNote(parsedImage.noteId, { viewScope: parsedImage.viewScope }); + } } else { window.open($img.prop("src"), "_blank"); }