Files
InkNote/frontend/src/types.ts
T
marcandClaude Opus 5 3b7958b713 A chapter with an order of its own
A chapter orders its own notes now, chosen in the same dialog as its name,
mark and colour -- one decision about one thing. The notebook's setting
governs the notebook's own list; a chapter is a place someone made and
filled, so a reference chapter can stay alphabetical while the notebook
around it stays as it was arranged. It starts hand-made, and a chapter put
in one of the other orders loses its lines and its handles exactly as the
notebook does: neither means anything against an order the app is choosing.

A line still would not go under another line, and the comparator was only
half of it. The write was then thrown away by the "did anything change?"
test, which sorted the separators before comparing them -- to ignore the
other chapters' lines riding along in the same field -- and a sorted
comparison cannot see a sequence. Two lines in one gap carry the same
position, so swapping them changes nothing else. It compares this list's own
lines, in order.

An open chapter swelled while rearranging: the strips at the two ends of its
list stood open for the whole of the mode, adding half a finger of empty
space inside every chapter. They are thin now, and only there while
something is actually being carried -- which is also why the suite presses
first and aims afterwards.

Server: sort_mode on chapters, validated against the same five modes as a
notebook's, and set outside the COALESCE so null means the hand-made order
rather than "leave it alone".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 16:45:05 +02:00

269 lines
7.6 KiB
TypeScript

/**
* The shapes that travel between the server, IndexedDB and the UI.
*
* Most bugs in this app have been shape bugs: a field that is sometimes absent,
* a body cached in the wrong format, a stale object adopted over a fresh one.
* These types exist to make that class of mistake fail at build time.
*/
/* ── Drawing ── */
export type StrokeTool = "pen" | "highlighter" | "eraser";
export interface Stroke {
/** Tool the stroke was drawn with. */
t: StrokeTool;
/** Colour, `#rrggbb`. */
c: string;
/** Width in canonical page units, not pixels. */
s: number;
/** Flat `[x0, y0, x1, y1, …]` in canonical page space. */
p: number[];
}
export interface StrokeDoc {
v: number;
/** Canonical page width the points are expressed in. */
w: number;
/** Canonical page height. */
h: number;
strokes: Stroke[];
/** Save token — lets the editor recognise its own write coming back. */
t?: string;
}
/* ── Notes ── */
export type NoteKind = "text" | "pdf";
/** Note metadata, as held in the notes list. */
export interface NoteMeta {
id: string;
title: string;
/** "pdf" notes render a document instead of the text editor. */
type?: NoteKind;
/** Attachment holding the PDF, for notes of type "pdf". */
pdfAttachmentId?: string | null;
folderId: string | null;
/** Chapter within the notebook; null means the notebook itself. */
chapterId?: string | null;
/** Its own mark beside the title, and the colour that mark is drawn in. */
icon?: string | null;
iconColor?: string | null;
starred: boolean;
trashed: boolean;
createdAt: string;
updatedAt: string;
/** Position in the notebook's own order, used when sorting is set to manual. */
sortOrder?: number;
/** Set while a note created offline has not reached the server yet. */
_pendingCreate?: boolean;
}
/** The lazily loaded body of a note. */
export interface NoteContent {
/** Serialised document: `{"v":2,"doc":…}`, or a v1 `{html,tasks}` string. */
content: string;
/** Legacy raster drawing as a data URL, for notes predating vector strokes. */
drawingData: string | null;
drawingStrokes: StrokeDoc | null;
/** Server revision this copy came from, used to spot a stale cache. */
updatedAt?: string | null;
}
/**
* How a notebook's notes are ordered.
*
* "manual" is the user's own arrangement, kept in each note's `sortOrder`; the
* rest are derived from the notes themselves and need nothing stored.
*/
export type SortMode = "manual" | "updated" | "oldest" | "created" | "title";
/**
* A line drawn between notes in a notebook's own order.
*
* `order` is "how many notes come before me", so it stays meaningful as the
* notes around it are renamed, edited or opened.
*/
/** A mark a row can wear beside its name, with a colour of its own. */
export interface RowIcon {
icon?: string | null;
iconColor?: string | null;
}
export interface Separator {
id: string;
label: string;
order: number;
/**
* The list it was drawn in: a chapter of the notebook, or null for the
* notebook itself. `order` counts the notes before it, which only means
* anything within one list.
*/
chapterId?: string | null;
/** Its own colour, `#rrggbb`, or null for the list's own faint grey. */
color?: string | null;
/** How the rule is drawn: solid, dashed, dotted, double or a thick band. */
style?: SeparatorStyle;
}
/** The ways a separator's rule can be drawn. */
export type SeparatorStyle = "solid" | "dashed" | "dotted" | "double" | "thick";
/**
* A folder inside a notebook.
*
* A note is in exactly one chapter, or in none — which means the notebook
* itself. Deleting a chapter never deletes its notes; they come back to the
* notebook, where they can be seen and filed again.
*/
export interface Chapter {
id: string;
notebookId: string;
name: string;
/** `#rrggbb` for its icon, so a shelf of chapters can be told apart. */
color?: string | null;
/** An emoji standing in for the default folder mark. */
icon?: string | null;
sortOrder: number;
/* How its own notes are ordered — the notebook's setting governs the
notebook's own list, not what is inside a chapter. Null is the hand-made
order, which is what a chapter is in until it is told otherwise. */
sortMode?: SortMode | null;
/** Set while a chapter created offline has not reached the server yet. */
_pendingCreate?: boolean;
}
export interface Notebook {
id: string;
name: string;
cover: number;
/** Pattern laid over the colour, chosen separately from it. */
coverPattern?: number;
sortOrder: number;
/** Versions kept per note here; null means the server's default. */
maxVersions?: number | null;
/** Null means the app default, newest first. */
sortMode?: SortMode | null;
/** Lines drawn between notes in the manual order. */
separators?: Separator[];
_pendingCreate?: boolean;
}
/** How the note body is set. Presentation only — the export is unaffected. */
export type NoteFont = "serif" | "sans" | "mono";
/**
* How a chapter opens.
*
* "column" puts its notes in a further column beside the list, the way OneNote
* lays out its pages; "inline" unfolds them under the chapter's own row. A
* phone has room for neither and fills the screen either way, with a way back.
*/
export type ChapterView = "column" | "inline";
export interface UserPreferences {
noteFont: NoteFont;
chapterView?: ChapterView;
}
export interface User {
id: string;
username: string;
displayName: string;
/** Admins may create, delete and reset the password of other accounts. */
isAdmin?: boolean;
/** Interface settings, kept on the account so they follow you to the phone. */
preferences?: UserPreferences;
/**
* How many versions a notebook keeps when it names no number of its own.
*
* Sent by the server rather than written into the interface: it is set with
* `MAX_VERSIONS_PER_NOTE`, and a dialog that said "50" would start lying the
* day that is changed.
*/
defaultMaxVersions?: number;
}
/** A user row as the admin panel sees it. */
export interface ManagedUser extends User {
createdAt: string;
noteCount: number;
/** True for the admin doing the looking — they may not delete themselves. */
isSelf: boolean;
}
/* ── Version history ── */
export type VersionReason = "edit" | "large-deletion" | "drawing" | "drawing-cleared" | "before-restore";
export interface NoteVersionSummary {
id: number;
title: string | null;
device_id: string | null;
reason: VersionReason | string;
created_at: string;
content_size: number | null;
has_drawing: boolean;
}
export interface NoteVersion {
id: number;
title: string | null;
created_at: string;
device_id: string | null;
reason: string;
content: string;
drawingStrokes: StrokeDoc | null;
}
/* ── Links ── */
export interface Backlink {
id: string;
title: string;
notebook_id: string | null;
notebook_name: string | null;
updated_at: string;
}
export type LinkKind = "note" | "notebook";
export interface LinkTarget {
id: string;
label: string;
kind: LinkKind;
}
/* ── Sync ── */
export type SyncStatus = "synced" | "syncing" | "pending" | "offline" | "error";
/** A change waiting to be pushed to the server. */
export interface QueueEntry {
queueId: number;
action: string;
data: Record<string, unknown>;
timestamp: number;
attempts?: number;
lastError?: string;
}
/* ── Attachments ── */
export interface AttachmentInfo {
id: string;
mime: string;
size: number;
deduplicated: boolean;
}
/* ── Search ── */
export interface SearchHit {
id: string;
score: number;
/** Excerpt around the first matching term. */
snippet: string;
}