|
- import { db } from './db';
- import { AnnotationSource, IAnnotationSource } from './AnnotationSource';
- import { targetsUrl } from 'web-annotation-utils';
- import type { WebAnnotation } from 'web-annotation-utils';
-
- // Annotation model in database
- export class IAnnotation {
- constructor(
- public _id: number,
- public annotation: WebAnnotation,
- public source: IAnnotationSource['_id'],
- public dirty?: boolean,
- public toDelete?: boolean,
- public lastModified?: Date,
- ) {}
- }
-
- // Expanded form, i.e. with source nested.
- export type IAnnotationWithSource = Omit<IAnnotation, 'source'> & {
- source: IAnnotationSource;
- };
-
- export class Annotation {
- constructor(public data: IAnnotation) {}
-
- async save() {
- await db.annotations.put({
- ...this.data,
- lastModified: new Date(),
- });
- }
-
- async setDirty(value: boolean, data?: Partial<IAnnotation>) {
- this.data.dirty = value;
- Object.assign(this.data, data);
- await this.save();
- }
-
- async update(webAnnotation: WebAnnotation) {
- await this.setDirty(true, { annotation: webAnnotation });
- const source = await this.source();
- await source.uploadAnnotation(this.data.annotation);
- await this.setDirty(false);
- }
-
- async delete() {
- // Could not delete it upstream. Mark it as waiting for deletion.
- await this.setDirty(true, { toDelete: true });
- const source = await this.source();
- try {
- await source.deleteAnnotationRemotely(this.data.annotation);
- await db.annotations.delete(this.data._id);
- } catch (error: any) {
- throw new Error(
- `Failed to delete: ${error.message} (The annotation should be deleted on a subsequent refresh)`,
- );
- }
- }
-
- async source() {
- return await AnnotationSource.get(this.data.source);
- }
-
- async expand(): Promise<IAnnotationWithSource> {
- return {
- ...this.data,
- source: (await this.source()).data,
- };
- }
-
- static async new(data: Omit<IAnnotation, '_id'>) {
- // @ts-ignore: _id is not needed in put()
- const annotation: IAnnotation = {
- ...data,
- lastModified: new Date(),
- }
- const key = (await db.annotations.put(annotation)) as IAnnotation['_id'];
- return new this({ ...data, _id: key });
- }
-
- static async count() {
- return await db.annotations.count();
- }
-
- static async get(id: IAnnotation['_id']): Promise<Annotation> {
- const data = await db.annotations.get(id);
- if (!data) throw new Error(`No annotation exists with id ${id}.`);
- return new Annotation(data);
- }
-
- static async getAll(): Promise<Annotation[]> {
- const annotations = await db.annotations.toArray();
- return annotations.map((data) => new this(data));
- }
-
- static async getAnnotationsForUrls(urls: string[]) {
- // TODO Use the index again, somehow. May need a separate field with a multiEntry index.
- const matches = await db.annotations
- // .where('annotation.target')
- // .startsWith(pageUrl)
- // .or('annotation.target.source')
- // .startsWith(pageUrl)
- // .or('annotation.target.id')
- // .startsWith(pageUrl)
- .filter((item) =>
- urls.some((url) => targetsUrl(item.annotation.target, url)),
- )
- .toArray();
- return matches.map((data) => new this(data));
- }
-
- static async getAnnotationsFromSource(source: number): Promise<Annotation[]> {
- const annotations = await db.annotations
- .where('source')
- .equals(source)
- .toArray();
- return annotations.map((data) => new this(data));
- }
- }
|