mirror of
https://github.com/zadam/trilium.git
synced 2025-12-04 22:44:25 +01:00
capitalize "close" and "cancel" in translation strings
feat(gallery): improve image loading performance
This commit is contained in:
parent
6512d03c11
commit
4779492bb7
@ -2131,10 +2131,10 @@
|
|||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
"close": "close",
|
"close": "Close",
|
||||||
"goto_note": "Go To Parent Note",
|
"goto_note": "Go To Parent Note",
|
||||||
"warning_delete_other_note": "This image belongs to another note, deleting it will remove it from that note!",
|
"warning_delete_other_note": "This image belongs to another note, deleting it will remove it from that note!",
|
||||||
"cancel": "cancel",
|
"cancel": "Cancel",
|
||||||
"copy_image_link": "Image Link Copied to Clipboard",
|
"copy_image_link": "Image Link Copied to Clipboard",
|
||||||
"share_copy_failed": "Failed to copy image link to clipboard.",
|
"share_copy_failed": "Failed to copy image link to clipboard.",
|
||||||
"select_image": "Select Image",
|
"select_image": "Select Image",
|
||||||
|
|||||||
@ -126,7 +126,7 @@
|
|||||||
|
|
||||||
.gallery-empty-hint {
|
.gallery-empty-hint {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
margin-bottom: 1.5rem !important;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Lightbox */
|
/* Lightbox */
|
||||||
@ -256,6 +256,7 @@
|
|||||||
left: 0.5rem;
|
left: 0.5rem;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
-webkit-backdrop-filter: blur(4px);
|
||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
@ -279,6 +280,24 @@
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gallery-item-copy-link {
|
||||||
|
background: rgba(13, 110, 253, 0.9);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.375rem 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item-copy-link:hover {
|
||||||
|
background: rgba(13, 110, 253, 1);
|
||||||
|
}
|
||||||
|
|
||||||
.gallery-item-delete {
|
.gallery-item-delete {
|
||||||
background: rgba(220, 53, 69, 0.9);
|
background: rgba(220, 53, 69, 0.9);
|
||||||
color: white;
|
color: white;
|
||||||
@ -320,6 +339,29 @@
|
|||||||
background: rgba(220, 53, 69, 1);
|
background: rgba(220, 53, 69, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gallery-lightbox-copy-link {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 9rem;
|
||||||
|
background: rgba(13, 110, 253, 0.9);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
z-index: 10001;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-lightbox-copy-link:hover {
|
||||||
|
background: rgba(13, 110, 253, 1);
|
||||||
|
}
|
||||||
|
|
||||||
/* Toolbar Styles */
|
/* Toolbar Styles */
|
||||||
.gallery-toolbar-left {
|
.gallery-toolbar-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -339,6 +381,7 @@
|
|||||||
.btn-danger:hover:not(:disabled) {
|
.btn-danger:hover:not(:disabled) {
|
||||||
background: rgba(220, 53, 69, 0.2);
|
background: rgba(220, 53, 69, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-item-link {
|
.gallery-item-link {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0.75rem;
|
bottom: 0.75rem;
|
||||||
|
|||||||
@ -51,6 +51,7 @@ export default function Gallery({ note }: TypeWidgetProps) {
|
|||||||
const directAttachments = await note.getAttachments();
|
const directAttachments = await note.getAttachments();
|
||||||
const directImageAttachments = directAttachments.filter(a => a.role === "image");
|
const directImageAttachments = directAttachments.filter(a => a.role === "image");
|
||||||
|
|
||||||
|
// Process direct attachments
|
||||||
for (const attachment of directImageAttachments) {
|
for (const attachment of directImageAttachments) {
|
||||||
const size = attachment.contentLength || 0;
|
const size = attachment.contentLength || 0;
|
||||||
calculatedTotalSize += size;
|
calculatedTotalSize += size;
|
||||||
@ -71,40 +72,57 @@ export default function Gallery({ note }: TypeWidgetProps) {
|
|||||||
const childNotes = await note.getChildNotes();
|
const childNotes = await note.getChildNotes();
|
||||||
const imageNotes = childNotes.filter(n => n.type === "image");
|
const imageNotes = childNotes.filter(n => n.type === "image");
|
||||||
|
|
||||||
// Convert image notes to ImageItem format
|
// Process image notes in parallel
|
||||||
for (const imageNote of imageNotes) {
|
const imageBlobPromises = imageNotes.map(async (imageNote) => {
|
||||||
const blob = await imageNote.getBlob();
|
const blob = await imageNote.getBlob();
|
||||||
const size = blob?.contentLength || 0;
|
const size = blob?.contentLength || 0;
|
||||||
calculatedTotalSize += size;
|
calculatedTotalSize += size;
|
||||||
|
|
||||||
imageItems.push({
|
return {
|
||||||
id: imageNote.noteId,
|
id: imageNote.noteId,
|
||||||
url: `api/images/${imageNote.noteId}/${encodeURIComponent(imageNote.title)}`,
|
url: `api/images/${imageNote.noteId}/${encodeURIComponent(imageNote.title)}`,
|
||||||
title: imageNote.title,
|
title: imageNote.title,
|
||||||
type: 'note' as const,
|
type: 'note' as const,
|
||||||
size
|
size
|
||||||
});
|
};
|
||||||
}
|
});
|
||||||
|
|
||||||
// Also check for notes with image attachments
|
const imageNoteItems = await Promise.all(imageBlobPromises);
|
||||||
for (const childNote of childNotes) {
|
imageItems.push(...imageNoteItems);
|
||||||
|
|
||||||
|
// Recalculate total size from image note items
|
||||||
|
imageNoteItems.forEach(item => {
|
||||||
|
calculatedTotalSize += item.size || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process child note attachments in parallel
|
||||||
|
const attachmentPromises = childNotes.map(async (childNote) => {
|
||||||
const attachments = await childNote.getAttachments();
|
const attachments = await childNote.getAttachments();
|
||||||
const imageAttachments = attachments.filter(a => a.role === "image");
|
const imageAttachments = attachments.filter(a => a.role === "image");
|
||||||
|
|
||||||
for (const attachment of imageAttachments) {
|
return imageAttachments.map(attachment => {
|
||||||
const size = attachment.contentLength || 0;
|
const size = attachment.contentLength || 0;
|
||||||
calculatedTotalSize += size;
|
|
||||||
|
|
||||||
imageItems.push({
|
return {
|
||||||
id: attachment.attachmentId,
|
id: attachment.attachmentId,
|
||||||
url: `api/attachments/${attachment.attachmentId}/image/${encodeURIComponent(attachment.title)}`,
|
url: `api/attachments/${attachment.attachmentId}/image/${encodeURIComponent(attachment.title)}`,
|
||||||
title: attachment.title,
|
title: attachment.title,
|
||||||
type: 'attachment' as const,
|
type: 'attachment' as const,
|
||||||
noteId: attachment.ownerId,
|
noteId: attachment.ownerId,
|
||||||
size
|
size
|
||||||
});
|
};
|
||||||
}
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
|
const attachmentArrays = await Promise.all(attachmentPromises);
|
||||||
|
const allAttachments = attachmentArrays.flat();
|
||||||
|
|
||||||
|
imageItems.push(...allAttachments);
|
||||||
|
|
||||||
|
// Calculate total size from attachments
|
||||||
|
allAttachments.forEach(item => {
|
||||||
|
calculatedTotalSize += item.size || 0;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setImages(imageItems);
|
setImages(imageItems);
|
||||||
@ -118,32 +136,14 @@ export default function Gallery({ note }: TypeWidgetProps) {
|
|||||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||||
const childNoteIds = images.map(img => img.noteId).filter(Boolean);
|
const childNoteIds = images.map(img => img.noteId).filter(Boolean);
|
||||||
|
|
||||||
// Reload if branches change (child notes added/removed or share status changed)
|
const shouldReload =
|
||||||
const branchRows = loadResults.getBranchRows();
|
loadResults.getBranchRows().some(b => b.parentNoteId === note.noteId || b.noteId === note.noteId) ||
|
||||||
if (branchRows.some(b => b.parentNoteId === note.noteId || b.noteId === note.noteId)) {
|
loadResults.getNoteIds().some(id => id === note.noteId || childNoteIds.includes(id)) ||
|
||||||
loadImages();
|
loadResults.getAttachmentRows().some(att => att.ownerId === note.noteId || childNoteIds.includes(att.ownerId)) ||
|
||||||
return;
|
loadResults.getAttributeRows().some(attr => attr.noteId === note.noteId && attr.name === "hideChildAttachments");
|
||||||
}
|
|
||||||
|
|
||||||
// Reload if any child note changes
|
if (shouldReload) {
|
||||||
const noteIds = loadResults.getNoteIds();
|
|
||||||
if (noteIds.some(id => id === note.noteId || childNoteIds.includes(id))) {
|
|
||||||
loadImages();
|
loadImages();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload if attachments change for this note or its children
|
|
||||||
const attachmentRows = loadResults.getAttachmentRows();
|
|
||||||
if (attachmentRows.some(att => att.ownerId === note.noteId || childNoteIds.includes(att.ownerId))) {
|
|
||||||
loadImages();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload if attributes change (for hideChildImages label)
|
|
||||||
const attributeRows = loadResults.getAttributeRows();
|
|
||||||
if (attributeRows.some(attr => attr.noteId === note.noteId && attr.name === "hideChildAttachments")) {
|
|
||||||
loadImages();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -175,24 +175,6 @@ export default function Gallery({ note }: TypeWidgetProps) {
|
|||||||
// Note: No else clause - we don't do anything for gallery note attachments
|
// Note: No else clause - we don't do anything for gallery note attachments
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleToggleShare() {
|
|
||||||
if (isGalleryShared) {
|
|
||||||
const shareBranch = note.getParentBranches().find((b) => b.parentNoteId === "_share");
|
|
||||||
if (shareBranch?.branchId) {
|
|
||||||
await server.remove(`branches/${shareBranch.branchId}`);
|
|
||||||
toast.showMessage(t("gallery.unshared_success"));
|
|
||||||
sync.syncNow(true);
|
|
||||||
await loadImages();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Share the gallery
|
|
||||||
await branches.cloneNoteToParentNote(note.noteId, "_share");
|
|
||||||
toast.showMessage(t("gallery.shared_success"));
|
|
||||||
sync.syncNow(true);
|
|
||||||
await loadImages();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Your current handleCopyImageLink is actually fine for both Electron and web
|
// Your current handleCopyImageLink is actually fine for both Electron and web
|
||||||
async function handleCopyImageLink(img: ImageItem, e: MouseEvent) {
|
async function handleCopyImageLink(img: ImageItem, e: MouseEvent) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -245,20 +227,6 @@ export default function Gallery({ note }: TypeWidgetProps) {
|
|||||||
await loadImages();
|
await loadImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getShareUrl(noteId: string, syncServerHost: string | null): string | null {
|
|
||||||
const shareId = note.getOwnedLabelValue("shareAlias") || noteId;
|
|
||||||
|
|
||||||
if (syncServerHost) {
|
|
||||||
return new URL(`/share/${shareId}`, syncServerHost).href;
|
|
||||||
} else {
|
|
||||||
let host = location.host;
|
|
||||||
if (host.endsWith("/")) {
|
|
||||||
host = host.substring(0, host.length - 1);
|
|
||||||
}
|
|
||||||
return `${location.protocol}//${host}${location.pathname}share/${shareId}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAbsoluteUrl(path: string): string {
|
function getAbsoluteUrl(path: string): string {
|
||||||
if (syncServerHost) {
|
if (syncServerHost) {
|
||||||
return new URL(path, syncServerHost).href;
|
return new URL(path, syncServerHost).href;
|
||||||
@ -369,22 +337,28 @@ export default function Gallery({ note }: TypeWidgetProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const imagesToDelete = images.filter(img => selectedImages.has(img.id));
|
const imagesToDelete = images.filter(img => selectedImages.has(img.id));
|
||||||
|
|
||||||
|
const deletePromises = imagesToDelete.map(img => {
|
||||||
|
if (img.type === 'note') {
|
||||||
|
return server.remove(`notes/${img.id}`);
|
||||||
|
} else {
|
||||||
|
return server.remove(`attachments/${img.id}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await Promise.allSettled(deletePromises);
|
||||||
|
|
||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
let errorCount = 0;
|
let errorCount = 0;
|
||||||
|
|
||||||
for (const img of imagesToDelete) {
|
results.forEach((result, index) => {
|
||||||
try {
|
if (result.status === 'fulfilled') {
|
||||||
if (img.type === 'note') {
|
|
||||||
await server.remove(`notes/${img.id}`);
|
|
||||||
} else {
|
|
||||||
await server.remove(`attachments/${img.id}`);
|
|
||||||
}
|
|
||||||
successCount++;
|
successCount++;
|
||||||
} catch (error) {
|
} else {
|
||||||
console.error(`Failed to delete image ${img.id}:`, error);
|
|
||||||
errorCount++;
|
errorCount++;
|
||||||
|
console.error(`Failed to delete image ${imagesToDelete[index].id}:`, result.reason);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
if (successCount > 0) {
|
if (successCount > 0) {
|
||||||
toast.showMessage(t("gallery.delete_multiple_success", { count: successCount }));
|
toast.showMessage(t("gallery.delete_multiple_success", { count: successCount }));
|
||||||
@ -483,7 +457,6 @@ export default function Gallery({ note }: TypeWidgetProps) {
|
|||||||
<span className="bx bx-image-alt"></span>
|
<span className="bx bx-image-alt"></span>
|
||||||
</div>
|
</div>
|
||||||
<p>{t("gallery.no_images")}</p>
|
<p>{t("gallery.no_images")}</p>
|
||||||
<p className="gallery-empty-hint">{t("gallery.upload_hint")}</p>
|
|
||||||
<Button
|
<Button
|
||||||
icon="bx bx-upload"
|
icon="bx bx-upload"
|
||||||
text={t("gallery.upload_first_image")}
|
text={t("gallery.upload_first_image")}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user