Add Outline stage, parallel reading, offline saves, backup, sync-conflict handling and more
Study - New Outline stage (hierarchical I./A./1. points, linked to chunks, in all exports); the chunk stage is now "Chunks" - Home cards: passages shown as ranges, tags capped with "+N more" Reader - Side-by-side translations and a Compare tab per verse - Typeface, line spacing, verse-number settings; text and column scale with the screen - Highlights/bookmarks sheet with named colours (synced); BSB footnote spacing fix - Cmd-K palette (passages, studies, notes, marks), keyboard shortcuts and "?" help Flow and safety - Toasts with Undo replace most confirm()/alert() - Back/Forward move between pages - Server refuses saves over a newer copy (409); keep-mine / use-other banner; opening a study no longer re-stamps it - Latest chapter request wins when flipping quickly Offline and data - Save books, plan days or a whole BSB/NET for offline - Service worker fixes: helloao /api/ URLs were skipped and responses cloned too late - Settings: download/restore a full backup Accessibility - Sheets trap focus and close on Escape, skip link, focus ring, reduced motion, theme contrast raised to 4.5:1 (with a test) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1edbd364ea
commit
d13f55faf7
65 files changed
+2947
-208
No files matched your search
@@ -2,6 +2,7 @@
|
||||
// active chapter tab, and every way of creating chunks (click, shift-click,
|
||||
// typed range, bulk ranges, cross-chapter spans), plus reorder and delete.
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useToast } from '../components/Toasts.jsx';
|
||||
import { bookOptions, CHAPTER_COUNTS, makeId, parseBibleChapter } from '../utils/bibleUtils.jsx';
|
||||
|
||||
// Every chunk starts blank; all note fields exist from the start.
|
||||
@@ -99,6 +100,7 @@ function initialChapterIndex(project) {
|
||||
}
|
||||
|
||||
export function useChunkBuilder({ project, setProject, updateProject, allChunks, setCurrentPage }) {
|
||||
const { toast } = useToast();
|
||||
const [setup, setSetup] = useState(() => initialSetupForm(project));
|
||||
const [titleEdited, setTitleEdited] = useState(false);
|
||||
const [activeChapterIndex, setActiveChapterIndex] = useState(() => initialChapterIndex(project));
|
||||
@@ -531,7 +533,33 @@ export function useChunkBuilder({ project, setProject, updateProject, allChunks,
|
||||
}));
|
||||
};
|
||||
|
||||
// Deleting a chunk also deletes its notes, so it can be taken back for a few seconds.
|
||||
const deleteChunk = (chunkId) => {
|
||||
let removed = null;
|
||||
(project?.chapters ?? []).forEach((ch, chapterIndex) => {
|
||||
const index = ch.chunks.findIndex((c) => c.id === chunkId);
|
||||
if (index >= 0) removed = { chunk: ch.chunks[index], chapterIndex, index, wasSelected: project.selectedChunkId === chunkId };
|
||||
});
|
||||
if (removed) {
|
||||
toast('Chunk deleted', {
|
||||
action: {
|
||||
label: 'Undo',
|
||||
onClick: () => updateProject((current) => {
|
||||
if (current.chapters.some((ch) => ch.chunks.some((c) => c.id === chunkId))) return current;
|
||||
return {
|
||||
...current,
|
||||
selectedChunkId: removed.wasSelected ? chunkId : current.selectedChunkId,
|
||||
chapters: current.chapters.map((ch, i) => {
|
||||
if (i !== removed.chapterIndex) return ch;
|
||||
const chunks = [...ch.chunks];
|
||||
chunks.splice(Math.min(removed.index, chunks.length), 0, removed.chunk);
|
||||
return { ...ch, chunks };
|
||||
}),
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
updateProject((current) => {
|
||||
const newAllChunks = current.chapters
|
||||
.flatMap((ch) => ch.chunks)
|
||||
|
||||
Reference in new issue
Block a user