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>
76 lines
4.7 KiB
React
76 lines
4.7 KiB
React
// 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';
|
|
|
|
const card = 'rounded-2xl border border-[var(--r-line)] bg-[var(--r-bar)] p-5';
|
|
const btn = 'rounded-xl border border-[var(--r-line)] px-4 py-2 text-sm hover:bg-black/5 disabled:opacity-50';
|
|
|
|
function Row({ title, note, button, onClick }) {
|
|
return (
|
|
<div className="flex items-center justify-between gap-4 border-b border-[var(--r-line)] py-3 last:border-0">
|
|
<div className="min-w-0"><p className="text-sm font-medium">{title}</p><p className="text-xs text-[var(--r-sub)]">{note}</p></div>
|
|
<button type="button" onClick={onClick} className={`${btn} shrink-0`}>{button}</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function ExportPage() {
|
|
const { project, allChunks, setCurrentPage, sharedStudies, deleteProject } = useApp();
|
|
const share = useShareLink(project?.id);
|
|
const filled = allChunks.filter((c) => (c.finalScript ?? '').trim()).length;
|
|
|
|
return (
|
|
<StudioShell stage="export">
|
|
<main className="mx-auto max-w-2xl space-y-5 px-4 py-8">
|
|
<div>
|
|
<h1 className="text-xl font-medium">Export</h1>
|
|
<p className="mt-1 text-sm text-[var(--r-sub)]">{allChunks.length} chunk{allChunks.length === 1 ? '' : 's'} · {filled} script{filled === 1 ? '' : 's'} written</p>
|
|
</div>
|
|
|
|
<section className={card} aria-label="Files">
|
|
<h2 className="mb-1 text-sm font-medium">Files</h2>
|
|
<Row title="Word document" note="Notes, words and scripts, formatted for editing." button="Export DOCX" onClick={() => exportProjectDocx(project)} />
|
|
<Row title="PDF" note="Opens a print-friendly page. Choose “Save as PDF” in the print dialog." button="Print / Save PDF" onClick={() => printProjectPdf(project)} />
|
|
<Row title="Markdown" note="For Obsidian, Notion, or any Markdown-based notes app." button="Export Markdown" onClick={() => exportProjectMarkdown(project)} />
|
|
<Row title="Web page" note="A single HTML file you can open anywhere." button="Export HTML" onClick={() => exportProjectHtml(project)} />
|
|
</section>
|
|
|
|
<section className={card} aria-label="Sharing">
|
|
<h2 className="text-sm font-medium">Read-only share link</h2>
|
|
<p className="mb-3 mt-1 text-xs text-[var(--r-sub)]">Anyone with the link can view (not edit) this study, with no account needed. Good for a co-teacher or a listener following along.</p>
|
|
{share.shareToken ? (
|
|
<div className="space-y-2">
|
|
<div className="flex items-center gap-2">
|
|
<input readOnly value={share.shareUrl} onFocus={(e) => e.target.select()}
|
|
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>
|
|
)}
|
|
</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>
|
|
</main>
|
|
</StudioShell>
|
|
);
|
|
}
|