@notatki/core
Version:
A collection of tools for creating and syncing Anki notes from simple text files.
190 lines (155 loc) • 4.02 kB
text/typescript
import { type FieldNode, type NoteNode, type PropertyNode } from "@notatki/parser";
import { type Model, type ModelField, ModelMap } from "./model.js";
import { loc } from "./nodes.js";
export class NoteList implements Iterable<Note> {
readonly #types: ModelMap;
readonly #notes: Note[];
constructor(types = new ModelMap()) {
this.#types = types;
this.#notes = [];
}
get types(): ModelMap {
return this.#types;
}
[Symbol.iterator](): Iterator<Note> {
return this.#notes[Symbol.iterator]();
}
get length(): number {
return this.#notes.length;
}
add(note: Note): void {
this.#notes.push(note);
}
}
export class Note implements Iterable<NoteField> {
readonly #type: Model;
#deck: string = "";
#tags: string = "";
readonly #id = new NoteField({ name: "ID", required: false });
readonly #fields = new Map<string, NoteField>();
#node: NoteNode | null = null;
constructor(type: Model) {
this.#type = type;
for (const field of type.fields) {
this.#fields.set(field.name.toLowerCase(), new NoteField(field));
}
}
get type(): Model {
return this.#type;
}
get deck(): string {
return this.#deck;
}
set deck(value: string | null) {
this.#deck = value ?? "";
}
get tags(): string {
return this.#tags;
}
set tags(value: string | null) {
this.#tags = value ?? "";
}
get id(): string {
return this.#id.value;
}
set id(value: string | null) {
this.#id.value = value;
}
[Symbol.iterator](): Iterator<NoteField> {
return this.#fields.values();
}
has(fieldName: string): boolean {
const key = fieldName.toLowerCase();
if (key === "id") {
return true;
}
return this.#fields.has(key);
}
get(fieldName: string): NoteField {
return this.#getField(fieldName);
}
set(fieldName: string, value: string | null): void {
this.#getField(fieldName).value = value;
}
#getField(fieldName: string): NoteField {
const key = fieldName.toLowerCase();
if (key === "id") {
return this.#id;
}
const field = this.#fields.get(key);
if (field != null) {
return field;
}
throw new Error(`Unknown field: "${fieldName}"`);
}
get first(): NoteField {
const [first] = this.#fields.values();
if (first != null) {
return first;
}
throw new Error("No fields");
}
get node(): NoteNode | null {
return this.#node;
}
set node(value: NoteNode | null) {
this.#node = value;
}
toNode(): NoteNode {
const properties: PropertyNode[] = [];
properties.push({ name: { text: "type", loc }, value: { text: this.#type.name, loc }, loc });
properties.push({ name: { text: "deck", loc }, value: { text: this.#deck, loc }, loc });
properties.push({ name: { text: "tags", loc }, value: { text: this.#tags, loc }, loc });
const fields: FieldNode[] = [];
if (this.#id.value) {
fields.push(this.#id.toNode());
}
for (const field of this.#fields.values()) {
if (field.value) {
fields.push(field.toNode());
}
}
return {
properties,
fields,
end: { text: "~~~", loc },
loc,
};
}
static isIdField(fieldName: string): boolean {
return fieldName.toLowerCase() === "id";
}
}
export class NoteField {
readonly #type: ModelField;
#value: string = "";
#node: FieldNode | null = null;
constructor(type: ModelField) {
this.#type = type;
}
get name(): string {
return this.#type.name;
}
get required(): boolean {
return this.#type.required ?? false;
}
get value(): string {
return this.#value;
}
set value(value: string | null) {
this.#value = value ?? "";
}
get node(): FieldNode | null {
return this.#node;
}
set node(value: FieldNode | null) {
this.#node = value;
}
toNode(): FieldNode {
return {
name: { text: this.#type.name.toLowerCase(), loc },
value: { text: this.#value, loc },
loc,
};
}
}