Bug Fixes + Duplicate Support (#1016)
* Update README.md supporter list * Fix: Multiple bug fixes and features bundle (#888, #991, #617, #984) (#1007) * fix: resolve location creation failures, broken image uploads, and invalid URL handling - Add missing addToast import in LocationDetails.svelte for proper error feedback - Add objectId check and error response handling in ImageManagement.svelte to prevent ghost images - Add Content-Type check in +page.server.ts image action to handle non-JSON backend responses - Add client-side URL validation in LocationDetails.svelte (invalid URLs → null) - Improve Django field error extraction for user-friendly toast messages - Clean up empty description fields (whitespace → null) - Update BUGFIX_DOCUMENTATION.md with detailed fix descriptions * feat: bug fixes and new features bundle Bug fixes: - fix: resolve PATCH location with visits (#888) - fix: Wikipedia/URL image upload via server-side proxy (#991) - fix: private/public toggle race condition (#617) - fix: location creation feedback (addToast import) - fix: invalid URL handling for locations and collections - fix: world map country highlighting (bg-*-200 -> bg-*-400) - fix: clipboard API polyfill for HTTP contexts - fix: MultipleObjectsReturned for duplicate images - fix: SvelteKit proxy sessionid cookie forwarding Features: - feat: duplicate location button (list + detail view) - feat: duplicate collection button - feat: i18n translations for 19 languages - feat: improved error handling and user feedback Technical: - Backend: fetch_from_url endpoint with SSRF protection - Backend: validate_link() for collections - Backend: file_permissions filter() instead of get() - Frontend: copyToClipboard() helper function - Frontend: clipboard polyfill via server-side injection * chore: switch docker-compose from image to build Use local source code builds instead of upstream :latest images to preserve our custom patches and fixes. * fix: lodging save errors, AI language support, and i18n improvements - Fix Lodging save: add res.ok checks, error toasts, isSaving state (#984) - Fix URL validation: silently set invalid URLs to null (Lodging, Transportation) - Fix AI description language: pass user locale to Wikipedia API - Fix missing i18n keys: Strava toggle buttons (show/hide) - Add CHANGELOG.md - Remove internal documentation from public tracking - Update .gitignore for Cursor IDE and internal docs Co-authored-by: Cursor <cursoragent@cursor.com> * feat: update location duplication handling, improve UI feedback, and enhance localization support --------- Co-authored-by: AdventureLog Bugfix <bugfix@adventurelog.local> Co-authored-by: madmp87 <info@so-pa.de> Co-authored-by: Mathias Ponnwitz <devuser@dockge-dev.fritz.box> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Sean Morley <mail@seanmorley.com> * Enhance duplication functionality for collections and locations; update UI to reflect changes * Potential fix for code scanning alert no. 49: Information exposure through an exception Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update Django and Pillow versions in requirements.txt * Fix error logging for image fetch timeout in ContentImageViewSet * Update requirements.txt to include jaraco.context and wheel for security fixes * Update app version and add security vulnerabilities to .trivyignore * Update backend/server/adventures/views/collection_view.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update frontend/src/lib/types.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Reorder build and image directives in docker-compose.yml for clarity * Refactor code structure for improved readability and maintainability * Remove inline clipboard polyfill script injection from server hooks (#1019) * Initial plan * Remove inline clipboard polyfill script injection from hooks.server.ts Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com> * Fix unhandled promise rejections in copyToClipboard click handlers (#1018) * Initial plan * Fix: make copyToClipboard handlers async with try/catch error toast Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com> * Harden `fetch_from_url` image proxy: require auth, rate-limit, and strengthen SSRF protections (#1017) * Initial plan * Harden fetch_from_url: require auth, rate-limit, block non-standard ports, check all IPs, re-validate redirects Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com> * Fix subregion filtering in world travel page to exclude null values * Update package.json to use caret (^) for versioning in overrides * fix: update package dependencies for compatibility and stability - Added cookie dependency with version constraint <0.7.0 - Updated svelte dependency to allow versions <=5.51.4 - Updated @sveltejs/adapter-vercel dependency to allow versions <6.3.2 * Refactor code structure for improved readability and maintainability --------- Co-authored-by: madmp87 <79420509+madmp87@users.noreply.github.com> Co-authored-by: AdventureLog Bugfix <bugfix@adventurelog.local> Co-authored-by: madmp87 <info@so-pa.de> Co-authored-by: Mathias Ponnwitz <devuser@dockge-dev.fritz.box> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
import type { Location, Collection, User, SlimCollection, ContentImage } from '$lib/types';
|
||||
import { addToast } from '$lib/toasts';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { copyToClipboard } from '$lib/index';
|
||||
|
||||
import Plus from '~icons/mdi/plus';
|
||||
import Minus from '~icons/mdi/minus';
|
||||
@@ -27,6 +28,7 @@
|
||||
import MapMarker from '~icons/mdi/map-marker-multiple';
|
||||
import LinkIcon from '~icons/mdi/link';
|
||||
import DownloadIcon from '~icons/mdi/download';
|
||||
import ContentCopy from '~icons/mdi/content-copy';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -39,7 +41,7 @@
|
||||
async function copyLink() {
|
||||
try {
|
||||
const url = `${location.origin}/collections/${collection.id}`;
|
||||
await navigator.clipboard.writeText(url);
|
||||
await copyToClipboard(url);
|
||||
copied = true;
|
||||
setTimeout(() => (copied = false), 2000);
|
||||
} catch (e) {
|
||||
@@ -47,6 +49,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
let isDuplicating = false;
|
||||
|
||||
async function duplicateCollection() {
|
||||
if (isDuplicating) return;
|
||||
isDuplicating = true;
|
||||
try {
|
||||
const res = await fetch(`/api/collections/${collection.id}/duplicate/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
if (res.ok) {
|
||||
const newCollection = await res.json();
|
||||
addToast('success', $t('adventures.collection_duplicate_success'));
|
||||
dispatch('duplicate', newCollection);
|
||||
} else {
|
||||
addToast('error', $t('adventures.collection_duplicate_error'));
|
||||
}
|
||||
} catch (e) {
|
||||
addToast('error', $t('adventures.collection_duplicate_error'));
|
||||
} finally {
|
||||
isDuplicating = false;
|
||||
}
|
||||
}
|
||||
|
||||
function editAdventure() {
|
||||
dispatch('edit', collection);
|
||||
}
|
||||
@@ -368,6 +394,16 @@
|
||||
{$t('adventures.export_zip')}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
class="flex items-center gap-2"
|
||||
on:click={duplicateCollection}
|
||||
disabled={isDuplicating}
|
||||
>
|
||||
<ContentCopy class="w-4 h-4" />
|
||||
{isDuplicating ? '...' : $t('adventures.duplicate')}
|
||||
</button>
|
||||
</li>
|
||||
<div class="divider my-1"></div>
|
||||
<li>
|
||||
<button
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import Launch from '~icons/mdi/launch';
|
||||
import FileDocumentEdit from '~icons/mdi/file-document-edit';
|
||||
import ContentCopy from '~icons/mdi/content-copy';
|
||||
import TrashCan from '~icons/mdi/trash-can-outline';
|
||||
import Calendar from '~icons/mdi/calendar';
|
||||
import Clock from '~icons/mdi/clock-outline';
|
||||
@@ -13,6 +14,7 @@
|
||||
import LinkIcon from '~icons/mdi/link-variant';
|
||||
import Check from '~icons/mdi/check';
|
||||
import { addToast } from '$lib/toasts';
|
||||
import { copyToClipboard } from '$lib/index';
|
||||
import Link from '~icons/mdi/link-variant';
|
||||
import LinkVariantRemove from '~icons/mdi/link-variant-remove';
|
||||
import Plus from '~icons/mdi/plus';
|
||||
@@ -69,7 +71,7 @@
|
||||
async function copyLink() {
|
||||
try {
|
||||
const url = `${location.origin}/locations/${adventure.id}`;
|
||||
await navigator.clipboard.writeText(url);
|
||||
await copyToClipboard(url);
|
||||
copied = true;
|
||||
setTimeout(() => (copied = false), 2000);
|
||||
} catch (e) {
|
||||
@@ -206,6 +208,43 @@
|
||||
}
|
||||
}
|
||||
|
||||
let isDuplicating = false;
|
||||
|
||||
async function duplicateAdventure() {
|
||||
if (isDuplicating) return;
|
||||
isDuplicating = true;
|
||||
try {
|
||||
const duplicatePayload = collection?.id ? { collection_id: collection.id } : null;
|
||||
const res = await fetch(`/api/locations/${adventure.id}/duplicate/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(duplicatePayload ?? {})
|
||||
});
|
||||
if (res.ok) {
|
||||
const newLocation = await res.json();
|
||||
|
||||
// Keep local UI in sync immediately in collection context.
|
||||
if (collection?.id) {
|
||||
const nextCollections = Array.isArray(newLocation.collections)
|
||||
? newLocation.collections
|
||||
: [];
|
||||
if (!nextCollections.includes(collection.id)) {
|
||||
newLocation.collections = [...nextCollections, collection.id];
|
||||
}
|
||||
}
|
||||
|
||||
addToast('success', $t('adventures.location_duplicate_success'));
|
||||
dispatch('duplicate', newLocation);
|
||||
} else {
|
||||
addToast('error', $t('adventures.location_duplicate_error'));
|
||||
}
|
||||
} catch (e) {
|
||||
addToast('error', $t('adventures.location_duplicate_error'));
|
||||
} finally {
|
||||
isDuplicating = false;
|
||||
}
|
||||
}
|
||||
|
||||
function editAdventure() {
|
||||
dispatch('edit', adventure);
|
||||
}
|
||||
@@ -388,6 +427,21 @@
|
||||
{$t('adventures.edit_location')}
|
||||
</button>
|
||||
</li>
|
||||
{#if user?.uuid == adventure.user?.uuid}
|
||||
<li>
|
||||
<button
|
||||
on:click={() => {
|
||||
isActionsMenuOpen = false;
|
||||
duplicateAdventure();
|
||||
}}
|
||||
class="flex items-center gap-2"
|
||||
disabled={isDuplicating}
|
||||
>
|
||||
<ContentCopy class="w-4 h-4" />
|
||||
{isDuplicating ? '...' : $t('adventures.duplicate')}
|
||||
</button>
|
||||
</li>
|
||||
{/if}
|
||||
{#if user?.uuid == adventure.user?.uuid}
|
||||
<li>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user