Invite-only sign-up, present mode, series, handouts, templates, dictation, progress, sharing
Build and Push Docker Image / build-and-push (push) Successful in 6m47s

- Admin: sign-up Open / Invite only / Closed with single-use invite links
- Present mode for scripts (auto-scroll) and outlines (step through, pace vs planned minutes)
- Series: group studies on the dashboard, filter by series, calendar file for a study's sessions
- Lesson handout: printable page per session (passage, outline, questions, writing lines)
- Save a chunk's notes as your own study template (synced)
- Voice dictation for notes and scripts (browser speech recognition)
- Reading progress: map of the 66 books and a calendar of days read, synced and merged across devices
- Export highlights and bookmarks as Markdown or Word, grouped by named colour
- Verse of the day push to the ntfy topic (needs IQBIBLE_API_KEY)
- Sharing: co-teacher editing via single-use invite links; read-only links can expire and take a passcode

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-06 10:26:43 -04:00
1 parent 88bc59bac0
commit 54cb1c217d
73 files changed
+2739 -134

No files matched your search

+14 -2
View File
@@ -1,6 +1,10 @@
// The Export stage: take the finished study out as a file, or share it read-only.
import { useApp } from '../context/AppContext.js';
import { useShareLink } from '../hooks/useShareLink.js';
import HandoutExport from '../components/HandoutExport.jsx';
import SessionSchedule from '../components/SessionSchedule.jsx';
import CoTeachers from '../components/CoTeachers.jsx';
import LinkRules from '../components/LinkRules.jsx';
import StudioShell from '../components/study/StudioShell.jsx';
import { exportProjectDocx, exportProjectHtml, exportProjectMarkdown, printProjectPdf } from '../utils/projectExport.js';
@@ -17,7 +21,7 @@ function Row({ title, note, button, onClick }) {
}
export default function ExportPage() {
const { project, allChunks, setCurrentPage } = useApp();
const { project, allChunks, setCurrentPage, sharedStudies, deleteProject } = useApp();
const share = useShareLink(project?.id);
const filled = allChunks.filter((c) => (c.finalScript ?? '').trim()).length;
@@ -47,13 +51,21 @@ export default function ExportPage() {
className="min-w-0 flex-1 truncate rounded-lg border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 text-xs text-[var(--r-sub)]" />
<button type="button" onClick={share.copy} className="shrink-0 rounded-lg bg-[var(--r-accent)] px-4 py-2 text-xs font-medium text-[var(--r-on)]">{share.shareCopied ? 'Copied!' : 'Copy'}</button>
</div>
<LinkRules expiresAt={share.expiresAt} hasPasscode={share.hasPasscode} busy={share.shareBusy} onSave={share.enable} />
{share.shareError && <p role="alert" className="text-xs text-[var(--r-danger,#b42318)]">{share.shareError}</p>}
<button type="button" onClick={share.revoke} disabled={share.shareBusy} className="text-xs text-[var(--r-danger,#b42318)] hover:underline">{share.shareBusy ? 'Revoking…' : 'Revoke link'}</button>
</div>
) : (
<button type="button" onClick={share.enable} disabled={share.shareBusy} className={btn}>{share.shareBusy ? 'Enabling…' : 'Enable sharing'}</button>
<button type="button" onClick={() => share.enable()} disabled={share.shareBusy} className={btn}>{share.shareBusy ? 'Enabling…' : 'Enable sharing'}</button>
)}
</section>
<HandoutExport project={project} />
<SessionSchedule project={project} />
<CoTeachers projectId={project?.id} sharedBy={project && sharedStudies?.[project.id]} onLeave={() => { deleteProject(project.id); setCurrentPage('home'); }} />
<section className={card} aria-label="Pronunciation">
<Row title="Pronunciation guide" note="Every Greek and Hebrew word in this study, with transliterations." button="Open guide" onClick={() => setCurrentPage('pronunciation')} />
</section>