84 lines
3.5 KiB
TypeScript
84 lines
3.5 KiB
TypeScript
// The notes module's pages, as the one server component the web shell mounts under
|
|
// `/<org>/<workspace>/<project>/notes` (apps/web's module mount reads it from `modules/index.ts`):
|
|
//
|
|
// /notes the list and the form that adds a note
|
|
// /notes/<noteId> one note: edit it, attach files, delete it
|
|
//
|
|
// Every read is a procedure through the same caller the API uses (spec §6.2: one API path); every
|
|
// write is a client component that calls a procedure through the shell's transport.
|
|
|
|
import { type Container, notFound, type TransportCaller } from "@conusai-kit/core"
|
|
import type { I18n } from "@conusai-kit/i18n"
|
|
import type { ProjectPage } from "@conusai-kit/ui"
|
|
import type { ReactElement } from "react"
|
|
import { ATTACHMENT_CONTENT_TYPES, noteGet, noteList } from "../contract/index.js"
|
|
import { FeedbackButton } from "../guides/feedback-button.js" // kit:guides
|
|
import { feedbackLabels } from "../guides/feedback-labels.js" // kit:guides
|
|
import { Attachments } from "./attachments.js"
|
|
import { DeleteNote } from "./delete-note.js"
|
|
import { attachmentLabels, createFormLabels, deleteLabels, editFormLabels, listLabels, sizeLabel } from "./labels.js"
|
|
import { NoteForm } from "./note-form.js"
|
|
import { NoteList } from "./note-list.js"
|
|
|
|
export type NotesPage = ProjectPage<TransportCaller, I18n, Container>
|
|
|
|
export const notesPage: NotesPage = async ({ slugs, segments, caller, i18n }): Promise<ReactElement> => {
|
|
const t = (key: string, values?: Record<string, string | number>) => i18n.message(`notes.${key}`, values)
|
|
const failed = i18n.message("app.failed")
|
|
const listHref = `/${slugs.org}/${slugs.workspace}/${slugs.project}/notes`
|
|
|
|
if (segments.length === 0) {
|
|
const { notes } = await caller.call(noteList, slugs)
|
|
return (
|
|
<>
|
|
<h1>{t("title")}</h1>
|
|
<p>{t("intro")}</p>
|
|
<FeedbackButton labels={feedbackLabels(t)} /* kit:guides */ />
|
|
{/* guide: The form that adds a note */}
|
|
<div data-guide="notes.new-note">
|
|
<NoteForm slugs={slugs} target={{ mode: "create", listHref }} labels={createFormLabels(t, failed)} />
|
|
</div>
|
|
<NoteList
|
|
notes={notes}
|
|
hrefFor={(noteId) => `${listHref}/${noteId}`}
|
|
labels={listLabels(t)}
|
|
formatDate={(iso) => i18n.date(new Date(iso))}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
const [noteId, ...rest] = segments
|
|
if (noteId === undefined || rest.length > 0) throw notFound("no such page")
|
|
const { note, attachments, attachmentsEnabled } = await caller.call(noteGet, { ...slugs, noteId })
|
|
return (
|
|
<>
|
|
<p>
|
|
<a href={listHref}>{t("detail.back")}</a>
|
|
</p>
|
|
<h1>{note.title}</h1>
|
|
<p>{t("detail.changed", { date: i18n.date(new Date(note.updatedAt)) })}</p>
|
|
<NoteForm
|
|
slugs={slugs}
|
|
target={{ mode: "edit", noteId: note.noteId, version: note.version, title: note.title, body: note.body }}
|
|
labels={editFormLabels(t, failed)}
|
|
/>
|
|
<Attachments
|
|
slugs={slugs}
|
|
noteId={note.noteId}
|
|
rows={attachments.map((attachment) => ({
|
|
attachmentId: attachment.attachmentId,
|
|
fileId: attachment.fileId,
|
|
filename: attachment.filename,
|
|
contentType: attachment.contentType,
|
|
size: sizeLabel(t, attachment.size),
|
|
}))}
|
|
enabled={attachmentsEnabled}
|
|
accept={ATTACHMENT_CONTENT_TYPES}
|
|
labels={attachmentLabels(t, failed, attachments.length)}
|
|
/>
|
|
<DeleteNote slugs={slugs} noteId={note.noteId} listHref={listHref} labels={deleteLabels(t, failed)} />
|
|
</>
|
|
)
|
|
}
|