@miadi/episode-ui
v0.2.2
Published
The episode-vessel files workspace — browse, read, preview markdown, edit in CodeMirror with a full-screen mode and a conflict that never loses the draft — framework-free so a plain page and a React app mount the same one. Plus React components for a cere
Downloads
870
Readme
@miadi/episode-ui
The episode-vessel experience: browse an episode's files, read one, preview its markdown, edit it in CodeMirror, go full screen with only Save and Back, and survive a conflict without losing the draft. Plus React components for a ceremony's working notes, its attachments, and the episode shelf.
npm i @miadi/episode-uiOne files workspace, two kinds of host
mountEpisodeFiles is framework-free, so a plain page and a React app mount the
same implementation.
React (Miadi app/chronicle):
import { EpisodeFiles, httpEpisodeFiles } from "@miadi/episode-ui"
import "@miadi/episode-ui/styles.css"
const service = httpEpisodeFiles({ fileUrl: (file) => `/api/files/${file.relativePath}` })
<EpisodeFiles files={files} capabilities={capabilities} service={service} />A page with no bundler (gmtermux): serve dist/browser/episode-files.js
(CodeMirror, marked and DOMPurify inlined) and src/episode-ui.css.
<div id="files"></div>
<script type="module">
import { mountEpisodeFiles, httpEpisodeFiles } from "/vendor/episode-ui/episode-files.js"
mountEpisodeFiles(document.getElementById("files"), {
files,
service: httpEpisodeFiles({
fileUrl: (file) => `/api/episode-file?file=${encodeURIComponent(file.relativePath)}`,
mediaUrl: (file) => `/api/episode-media?file=${encodeURIComponent(file.relativePath)}`,
}),
})
</script>What it does
- A filterable file list, with guidance files dimmed and media and documents marked.
- Markdown opens in a sanitised preview (
marked+ DOMPurify);Sourceshows CodeMirror. - CodeMirror 6 with markdown, JSON and YAML colouring, undo, search (Ctrl/Cmd-F), line wrapping, and Ctrl/Cmd-S to save.
⛶opens the viewer full screen with only the file name, its state, Save and×. Esc returns.- Audio and video play inline; documents are a download.
- Leaving a file or the page with unsaved changes asks first.
The conflict path
A save carries the revision the file was read at. When someone else wrote in
between, the host answers 409 with currentRevision: the draft stays in the
editor, the message says what is on disk now, and Reload is a choice the person
makes. A save is never retried automatically.
The contract
No component holds a URL or a token, knows a chronicle root, or decides what a
person is allowed to do. The host passes a service; httpEpisodeFiles is the
one module that fetches, and only from URLs the host gives it. The host resolves
capabilities (@miadi/episode-vessel/permissions) and stays the authority; a
capability here decides only what is offered. test/contract.test.mjs asserts it.
| export | what it is for |
|---|---|
| mountEpisodeFiles(el, options) | the files workspace; returns { update, destroy } |
| EpisodeFiles | the same, as a React component |
| httpEpisodeFiles({ fileUrl, mediaUrl?, headers? }) | the service for a host that answers GET/PUT JSON |
| CeremonyNoteEditor | a ceremony's working notes (React) |
| CeremonyAttachments | what a circle was held about (React) |
| EpisodeShelfFilter | find an episode by number, title or slug (React) |
Styling
One stylesheet of plain class names over CSS custom properties, set from an
EpisodeUITheme. Touch targets are 44px because the first surface this runs on
is a phone.
Ref jgwill/Miadi#651, jgwill/Miadi#653.
