From 47f94617d7ac80bb65a78e8b00a2ec3d0afd6f13 Mon Sep 17 00:00:00 2001 From: Mads Taanquist Date: Thu, 30 Jul 2026 19:46:11 +0200 Subject: [PATCH 1/2] Move story import into a modal with drag-and-drop Importing from the library no longer lands on the settings page next to the danger zone. The New menus and the settings Import block open a modal instead: pick or drop a file (zip, docx, epub), the preview runs at once, then confirm. The form posts to the target universe's existing actions from wherever the modal is mounted, so the server flow is unchanged. Co-Authored-By: Claude Fable 5 --- e2e/import-story.spec.ts | 19 +- src/lib/components/ImportStoryModal.svelte | 354 ++++++++++++++++++ src/lib/docs/getting-started.md | 4 +- src/routes/+page.svelte | 18 +- .../universes/[id]/[[section]]/+page.svelte | 153 +------- 5 files changed, 398 insertions(+), 150 deletions(-) create mode 100644 src/lib/components/ImportStoryModal.svelte diff --git a/e2e/import-story.spec.ts b/e2e/import-story.spec.ts index c5ad9a1..12c788f 100644 --- a/e2e/import-story.spec.ts +++ b/e2e/import-story.spec.ts @@ -3,7 +3,7 @@ import { gotoReady } from './navigate'; import { pickFromLibraryMenu, startStoryInUniverse } from './library'; // The story import round trip: write a story, download its export zip, and -// import the zip back through the universe settings preview flow. +// import the zip back through the library's import modal preview flow. test('story import: preview and import a story export zip', async ({ page }) => { await gotoReady(page, '/'); @@ -63,16 +63,23 @@ test('story import: preview and import a story export zip', async ({ page }) => expect(archive.status()).toBe(200); const zipBytes = await archive.body(); - await gotoReady(page, `/universes/importland-${stamp}`); - await page.getByRole('link', { name: 'Import and export' }).click(); - await page.locator('input[name="archive"]').setInputFiles({ + // Feed it back through the import modal, opened from the universe's New + // story menu on the library. Picking a file previews it at once. + await gotoReady(page, '/'); + await page + .locator('.universe-section', { hasText: universeName }) + .getByRole('button', { name: 'New story', exact: true }) + .click(); + await page.getByRole('menuitem', { name: 'Import a story into this universe...' }).click(); + const dialog = page.getByRole('dialog', { name: 'Import a story' }); + await expect(dialog).toBeVisible(); + await dialog.locator('input[name="archive"]').setInputFiles({ name: 'roundtrip.zip', mimeType: 'application/zip', buffer: zipBytes }); - await page.getByRole('button', { name: 'Preview' }).click(); - const report = page.locator('.import-report'); + const report = dialog.locator('.import-report'); await expect(report).toContainText('"Roundtrip": 1 chapter, 1 scene'); await expect(report).toContainText('A story named "Roundtrip" already exists'); await expect(report).toContainText('Bram'); diff --git a/src/lib/components/ImportStoryModal.svelte b/src/lib/components/ImportStoryModal.svelte new file mode 100644 index 0000000..dea7c77 --- /dev/null +++ b/src/lib/components/ImportStoryModal.svelte @@ -0,0 +1,354 @@ + + +{#if universe} + +{/if} + + diff --git a/src/lib/docs/getting-started.md b/src/lib/docs/getting-started.md index 49e6c9f..23b88a8 100644 --- a/src/lib/docs/getting-started.md +++ b/src/lib/docs/getting-started.md @@ -26,9 +26,9 @@ Right-click a scene in a story's sidebar to delete it; it sits in the story's "D Everything you write can leave as markdown: each story's settings offer a zip of its scenes, a universe's settings bundle the whole world, and your account page has a download of everything you own. -A story export zip can also come back in. Open the universe's settings, go to "Import and export", and upload the zip under "Import a story". You get a preview first: how many chapters, scenes, and words are coming, and how each story note will be handled. A note whose character, place, or lore entry already exists in the universe (matched by name) attaches to it; a name with no match creates a new entry; a name shared by more than one entry is skipped, and the preview says so. Nothing is written until you press "Import story", and the import always creates a new story, even if one with the same title exists. +A story export zip can also come back in. Choose "Import a story" from a New menu on the library, or from the universe's settings under "Import and export". A dialog opens: pick the zip or drag it in, and a preview appears at once - how many chapters, scenes, and words are coming, and how each story note will be handled. A note whose character, place, or lore entry already exists in the universe (matched by name) attaches to it; a name with no match creates a new entry; a name shared by more than one entry is skipped, and the preview says so. Nothing is written until you press "Import story", and the import always creates a new story, even if one with the same title exists. -You can bring in a manuscript written elsewhere the same way. Upload a Word document (.docx) or an EPUB (.epub) in that same "Import a story" box, or choose "Import a story from a file..." from a New menu on the library, which opens that same box. Codex reads the chapters and scenes from the way the document marks them: chapter headings, "Chapter 1" lines, and scene breaks such as a centered row of asterisks. When a document has no such markers, the whole text comes in as a single scene that you can split up afterward. Pictures embedded in the document come along when image storage is set up; the preview tells you if any will be left out. As with a zip, you check the preview before anything is written. +You can bring in a manuscript written elsewhere the same way. Pick a Word document (.docx) or an EPUB (.epub) in that same "Import a story" dialog. Codex reads the chapters and scenes from the way the document marks them: chapter headings, "Chapter 1" lines, and scene breaks such as a centered row of asterisks. When a document has no such markers, the whole text comes in as a single scene that you can split up afterward. Pictures embedded in the document come along when image storage is set up; the preview tells you if any will be left out. As with a zip, you check the preview before anything is written. ## Jumping around diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 8d46c6b..cadab9c 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,6 +1,7 @@