5 Commits

Author SHA1 Message Date
6b434adde4 1.2.1 2026-03-13 16:59:06 +04:00
a966df95f0 Add doc for List type definition 2026-03-13 16:58:36 +04:00
90b7b1cf01 1.2.0 2026-03-13 16:41:55 +04:00
1f8c076051 Merge pull request 'Support place lists' (#1) from feature/lists into master
Reviewed-on: #1
2026-03-13 12:40:08 +00:00
ba01a579b6 Update docs 2026-03-13 16:39:37 +04:00
9 changed files with 393 additions and 3 deletions

View File

@@ -41,6 +41,12 @@ console.log(allPlaces);
// List places for specific geohash prefixes (e.g. for a map view) // List places for specific geohash prefixes (e.g. for a map view)
const areaPlaces = await places.getPlaces(['u33d', 'u33e']); const areaPlaces = await places.getPlaces(['u33d', 'u33e']);
console.log(areaPlaces); console.log(areaPlaces);
// Create a list
await places.lists.create('favorites', 'My Favorites');
// Add a place to a list (requires list ID, place ID, and place geohash)
await places.lists.addPlace('favorites', 'place-id-123', 'u33dc0');
``` ```
## API Reference ## API Reference
@@ -52,3 +58,4 @@ console.log(areaPlaces);
### Type Aliases ### Type Aliases
- [Place](docs/type-aliases/Place.md) - [Place](docs/type-aliases/Place.md)
- [List](docs/type-aliases/List.md)

92
dist/places.d.ts vendored
View File

@@ -57,6 +57,61 @@ declare const placeSchema: {
}; };
readonly required: readonly ["id", "title", "lat", "lon", "geohash", "createdAt"]; 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"];
};
/**
* Represents a List object.
*
* Core properties enforced by schema:
* - `id`: Unique identifier (slug)
* - `title`: Human readable title
* - `placeRefs`: Array of place references containing `id` and `geohash`
* - `createdAt`: ISO date string
*
* Optional properties:
* - `color`: Hex color code
* - `updatedAt`: ISO date string
*/
export type List = FromSchema<typeof listSchema> & {
[key: string]: any;
};
/** /**
* Represents a Place object. * Represents a Place object.
* *
@@ -126,6 +181,43 @@ export interface PlacesClient {
* @returns An array of places. * @returns An array of places.
*/ */
getPlaces(prefixes?: string[]): Promise<Place[]>; getPlaces(prefixes?: string[]): Promise<Place[]>;
lists: {
/**
* Get all lists.
* @returns Array of List objects.
*/
getAll(): Promise<List[]>;
/**
* Get a single list by ID (slug).
* @param id - The slug ID of the list.
*/
get(id: string): Promise<List | null>;
/**
* 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<List>;
/**
* Delete a list.
* @param id - The slug ID of the list.
*/
delete(id: string): Promise<void>;
/**
* 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<List>;
/**
* 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<List>;
};
} }
declare const _default: { declare const _default: {
name: string; name: string;

118
dist/places.js vendored
View File

@@ -27,9 +27,33 @@ const placeSchema = {
}, },
required: ['id', 'title', 'lat', 'lon', 'geohash', 'createdAt'], 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 */) { const Places = function (privateClient /*, publicClient: BaseClient */) {
// Define Schema // Define Schema
privateClient.declareType('place', placeSchema); privateClient.declareType('place', placeSchema);
privateClient.declareType('list', listSchema);
// Helper to normalize place object // Helper to normalize place object
function preparePlace(data) { function preparePlace(data) {
const now = new Date().toISOString(); const now = new Date().toISOString();
@@ -67,7 +91,91 @@ const Places = function (privateClient /*, publicClient: BaseClient */) {
const p2 = geohash.substring(2, 4); const p2 = geohash.substring(2, 4);
return `${p1}/${p2}/${id}`; 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 = { const places = {
lists,
/** /**
* Store a place. * Store a place.
* Generates ID and Geohash if missing. * Generates ID and Geohash if missing.
@@ -87,6 +195,16 @@ const Places = function (privateClient /*, publicClient: BaseClient */) {
if (!id || !geohash) { if (!id || !geohash) {
throw new Error('Both id and geohash are required to remove a place'); 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); const path = getPath(geohash, id);
return privateClient.remove(path); return privateClient.remove(path);
}, },

View File

@@ -10,6 +10,7 @@
## Type Aliases ## Type Aliases
- [List](type-aliases/List.md)
- [Place](type-aliases/Place.md) - [Place](type-aliases/Place.md)
## Variables ## Variables

View File

@@ -6,6 +6,144 @@
# Interface: PlacesClient # Interface: PlacesClient
## Properties
### lists
> **lists**: `object`
#### addPlace()
> **addPlace**(`listId`, `placeId`, `geohash`): `Promise`\<[`List`](../type-aliases/List.md)\>
Add a place to a list.
##### Parameters
###### listId
`string`
The slug ID of the list.
###### placeId
`string`
The ID of the place.
###### geohash
`string`
The geohash of the place.
##### Returns
`Promise`\<[`List`](../type-aliases/List.md)\>
#### create()
> **create**(`id`, `title`, `color?`): `Promise`\<[`List`](../type-aliases/List.md)\>
Create or update a list.
##### Parameters
###### id
`string`
The slug ID (e.g., "to-go").
###### title
`string`
Human readable title.
###### color?
`string`
Optional hex color code.
##### Returns
`Promise`\<[`List`](../type-aliases/List.md)\>
#### delete()
> **delete**(`id`): `Promise`\<`void`\>
Delete a list.
##### Parameters
###### id
`string`
The slug ID of the list.
##### Returns
`Promise`\<`void`\>
#### get()
> **get**(`id`): `Promise`\<[`List`](../type-aliases/List.md) \| `null`\>
Get a single list by ID (slug).
##### Parameters
###### id
`string`
The slug ID of the list.
##### Returns
`Promise`\<[`List`](../type-aliases/List.md) \| `null`\>
#### getAll()
> **getAll**(): `Promise`\<[`List`](../type-aliases/List.md)[]\>
Get all lists.
##### Returns
`Promise`\<[`List`](../type-aliases/List.md)[]\>
Array of List objects.
#### removePlace()
> **removePlace**(`listId`, `placeId`): `Promise`\<[`List`](../type-aliases/List.md)\>
Remove a place from a list.
##### Parameters
###### listId
`string`
The slug ID of the list.
###### placeId
`string`
The ID of the place.
##### Returns
`Promise`\<[`List`](../type-aliases/List.md)\>
## Methods ## Methods
### get() ### get()

21
docs/type-aliases/List.md Normal file
View File

@@ -0,0 +1,21 @@
[**@remotestorage/module-places**](../README.md)
***
[@remotestorage/module-places](../README.md) / List
# Type Alias: List
> **List** = `FromSchema`\<*typeof* `listSchema`\> & `object`
Represents a List object.
Core properties enforced by schema:
- `id`: Unique identifier (slug)
- `title`: Human readable title
- `placeRefs`: Array of place references containing `id` and `geohash`
- `createdAt`: ISO date string
Optional properties:
- `color`: Hex color code
- `updatedAt`: ISO date string

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@remotestorage/module-places", "name": "@remotestorage/module-places",
"version": "1.1.3", "version": "1.2.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@remotestorage/module-places", "name": "@remotestorage/module-places",
"version": "1.1.3", "version": "1.2.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"latlon-geohash": "^2.0.0", "latlon-geohash": "^2.0.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@remotestorage/module-places", "name": "@remotestorage/module-places",
"version": "1.1.3", "version": "1.2.1",
"description": "Manage favorite/saved places", "description": "Manage favorite/saved places",
"homepage": "https://gitea.kosmos.org/raucao/remotestorage-module-places#remotestoragemodule-places", "homepage": "https://gitea.kosmos.org/raucao/remotestorage-module-places#remotestoragemodule-places",
"repository": { "repository": {

View File

@@ -55,6 +55,19 @@ const listSchema = {
required: ['id', 'title', 'placeRefs', 'createdAt'], required: ['id', 'title', 'placeRefs', 'createdAt'],
} as const; } as const;
/**
* Represents a List object.
*
* Core properties enforced by schema:
* - `id`: Unique identifier (slug)
* - `title`: Human readable title
* - `placeRefs`: Array of place references containing `id` and `geohash`
* - `createdAt`: ISO date string
*
* Optional properties:
* - `color`: Hex color code
* - `updatedAt`: ISO date string
*/
export type List = FromSchema<typeof listSchema> & { [key: string]: any }; export type List = FromSchema<typeof listSchema> & { [key: string]: any };
/** /**