diff --git a/dist/places.d.ts b/dist/places.d.ts index d127c6a..924cc7d 100644 --- a/dist/places.d.ts +++ b/dist/places.d.ts @@ -57,6 +57,48 @@ declare const placeSchema: { }; readonly required: readonly ["id", "title", "lat", "lon", "geohash", "createdAt"]; }; +declare const listSchema: { + readonly type: "object"; + readonly properties: { + readonly id: { + readonly type: "string"; + }; + readonly title: { + readonly type: "string"; + }; + readonly color: { + readonly type: "string"; + }; + readonly placeRefs: { + readonly type: "array"; + readonly items: { + readonly type: "object"; + readonly properties: { + readonly id: { + readonly type: "string"; + }; + readonly geohash: { + readonly type: "string"; + }; + }; + readonly required: readonly ["id", "geohash"]; + }; + readonly default: readonly []; + }; + readonly createdAt: { + readonly type: "string"; + readonly format: "date-time"; + }; + readonly updatedAt: { + readonly type: "string"; + readonly format: "date-time"; + }; + }; + readonly required: readonly ["id", "title", "placeRefs", "createdAt"]; +}; +export type List = FromSchema & { + [key: string]: any; +}; /** * Represents a Place object. * @@ -126,6 +168,43 @@ 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 a place to a list. + * @param listId - The slug ID of the list. + * @param placeId - The ID of the place. + * @param geohash - The geohash of the place. + */ + addPlace(listId: string, placeId: string, geohash: string): Promise; + /** + * Remove a place from a list. + * @param listId - The slug ID of the list. + * @param placeId - The ID of the place. + */ + removePlace(listId: string, placeId: string): Promise; + }; } declare const _default: { name: string; diff --git a/dist/places.js b/dist/places.js index a552e0c..9755e65 100644 --- a/dist/places.js +++ b/dist/places.js @@ -27,9 +27,33 @@ const placeSchema = { }, required: ['id', 'title', 'lat', 'lon', 'geohash', 'createdAt'], }; +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'], +}; const Places = function (privateClient /*, publicClient: BaseClient */) { // Define Schema privateClient.declareType('place', placeSchema); + privateClient.declareType('list', listSchema); // Helper to normalize place object function preparePlace(data) { const now = new Date().toISOString(); @@ -67,7 +91,91 @@ const Places = function (privateClient /*, publicClient: BaseClient */) { const p2 = geohash.substring(2, 4); return `${p1}/${p2}/${id}`; } + const lists = { + async getAll() { + const result = await privateClient.getAll('_lists/'); + if (!result) + return []; + // Normalize result: remoteStorage.getAll returns { 'slug': object } + return Object.values(result); + }, + async get(id) { + const path = `_lists/${id}`; + return privateClient.getObject(path); + }, + async create(id, title, color) { + const path = `_lists/${id}`; + let list = (await privateClient.getObject(path)); + 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, + }; + } + await privateClient.storeObject('list', path, list); + return list; + }, + async delete(id) { + await privateClient.remove(`_lists/${id}`); + }, + async addPlace(listId, placeId, geohash) { + const path = `_lists/${listId}`; + const list = (await privateClient.getObject(path)); + if (!list) { + throw new Error(`List not found: ${listId}`); + } + const index = list.placeRefs.findIndex((ref) => ref.id === placeId); + if (index === -1) { + // Add only if not present + list.placeRefs.push({ id: placeId, geohash }); + list.updatedAt = new Date().toISOString(); + await privateClient.storeObject('list', path, list); + } + return list; + }, + async removePlace(listId, placeId) { + const path = `_lists/${listId}`; + const list = (await privateClient.getObject(path)); + if (!list) { + throw new Error(`List not found: ${listId}`); + } + const index = list.placeRefs.findIndex((ref) => ref.id === placeId); + if (index !== -1) { + // Remove only if present + list.placeRefs.splice(index, 1); + list.updatedAt = new Date().toISOString(); + await privateClient.storeObject('list', path, list); + } + return list; + }, + async initDefaults() { + const defaults = [ + { id: 'to-go', title: 'Want to go', color: '#2e9e4f' }, // Green + { id: 'to-do', title: 'To do', color: '#2a7fff' }, // Blue + ]; + 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. @@ -87,6 +195,16 @@ const Places = function (privateClient /*, publicClient: BaseClient */) { if (!id || !geohash) { throw new Error('Both id and geohash are required to remove a place'); } + // Cleanup: Remove this place from all lists + const allLists = await lists.getAll(); + await Promise.all(allLists.map(async (list) => { + const index = list.placeRefs.findIndex((ref) => ref.id === id); + if (index !== -1) { + list.placeRefs.splice(index, 1); + list.updatedAt = new Date().toISOString(); + await privateClient.storeObject('list', `_lists/${list.id}`, list); + } + })); const path = getPath(geohash, id); return privateClient.remove(path); }, diff --git a/package-lock.json b/package-lock.json index 41c7b1e..e7e2392 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@remotestorage/module-places", - "version": "1.1.3", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@remotestorage/module-places", - "version": "1.1.3", + "version": "1.2.0", "license": "MIT", "dependencies": { "latlon-geohash": "^2.0.0", diff --git a/package.json b/package.json index 62f80cc..a28783b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@remotestorage/module-places", - "version": "1.1.3", + "version": "1.2.0", "description": "Manage favorite/saved places", "homepage": "https://gitea.kosmos.org/raucao/remotestorage-module-places#remotestoragemodule-places", "repository": {