From 53b2c9b4f8f041554ffb2a715877a4428c269087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 12 Mar 2026 17:03:17 +0400 Subject: [PATCH] WIP Add lists --- src/places.ts | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/src/places.ts b/src/places.ts index cb691e4..7940048 100644 --- a/src/places.ts +++ b/src/places.ts @@ -31,6 +31,32 @@ const placeSchema = { required: ['id', 'title', 'lat', 'lon', 'geohash', 'createdAt'], } as const; +const listSchema = { + type: 'object', + properties: { + id: { type: 'string' }, + title: { type: 'string' }, + color: { type: 'string' }, + placeRefs: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'string' }, + geohash: { type: 'string' }, + }, + required: ['id', 'geohash'], + }, + default: [], + }, + createdAt: { type: 'string', format: 'date-time' }, + updatedAt: { type: 'string', format: 'date-time' }, + }, + required: ['id', 'title', 'placeRefs', 'createdAt'], +} as const; + +export type List = FromSchema & { [key: string]: any }; + /** * Represents a Place object. * @@ -101,6 +127,46 @@ export interface PlacesClient { * @returns An array of places. */ getPlaces(prefixes?: string[]): Promise; + + lists: { + /** + * Get all lists. + * @returns Array of List objects. + */ + getAll(): Promise; + + /** + * Get a single list by ID (slug). + * @param id - The slug ID of the list. + */ + get(id: string): Promise; + + /** + * Create or update a list. + * @param id - The slug ID (e.g., "to-go"). + * @param title - Human readable title. + * @param color - Optional hex color code. + */ + create(id: string, title: string, color?: string): Promise; + + /** + * Delete a list. + * @param id - The slug ID of the list. + */ + delete(id: string): Promise; + + /** + * Add or remove a place from a list. + * @param listId - The slug ID of the list. + * @param placeId - The ID of the place. + * @param geohash - The geohash of the place (needed for reference). + */ + togglePlace( + listId: string, + placeId: string, + geohash: string + ): Promise; + }; } const Places = function ( @@ -108,6 +174,7 @@ const Places = function ( ): { exports: PlacesClient } { // Define Schema privateClient.declareType('place', placeSchema as any); + privateClient.declareType('list', listSchema as any); // Helper to normalize place object function preparePlace(data: Partial): Place { @@ -153,7 +220,93 @@ const Places = function ( return `${p1}/${p2}/${id}`; } + const lists = { + async getAll(): Promise { + const result = await privateClient.getAll('_lists/'); + if (!result) return []; + // Normalize result: remoteStorage.getAll returns { 'slug': object } + return Object.values(result); + }, + + async get(id: string): Promise { + const path = `_lists/${id}`; + return privateClient.getObject(path) as Promise; + }, + + async create(id: string, title: string, color?: string): Promise { + const path = `_lists/${id}`; + let list = (await privateClient.getObject(path)) as List; + const now = new Date().toISOString(); + + if (list) { + // Update existing + list.title = title; + if (color) list.color = color; + list.updatedAt = now; + } else { + // Create new + list = { + id, + title, + color, + placeRefs: [], + createdAt: now, + updatedAt: now, + } as List; + } + + await privateClient.storeObject('list', path, list); + return list; + }, + + async delete(id: string): Promise { + await privateClient.remove(`_lists/${id}`); + }, + + async togglePlace( + listId: string, + placeId: string, + geohash: string + ): Promise { + const path = `_lists/${listId}`; + const list = (await privateClient.getObject(path)) as List; + + if (!list) { + throw new Error(`List not found: ${listId}`); + } + + const index = list.placeRefs.findIndex((ref: any) => ref.id === placeId); + + if (index !== -1) { + // Remove + list.placeRefs.splice(index, 1); + } else { + // Add + list.placeRefs.push({ id: placeId, geohash }); + } + + list.updatedAt = new Date().toISOString(); + await privateClient.storeObject('list', path, list); + return list; + }, + + async initDefaults(): Promise { + const defaults = [ + { id: 'to-go', title: 'Want to go', color: '#ff00ff' }, // Magenta + { id: 'to-do', title: 'To do', color: '#008000' }, // Green + ]; + + for (const def of defaults) { + const existing = await this.get(def.id); + if (!existing) { + await this.create(def.id, def.title, def.color); + } + } + }, + }; + const places = { + lists, /** * Store a place. * Generates ID and Geohash if missing.