@meeovi/layer-lists 1.0.6 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/features/archived.vue +64 -0
- package/app/components/features/bookmarks.vue +64 -0
- package/app/components/features/lists.vue +61 -0
- package/app/components/features/starred.vue +64 -0
- package/app/components/lists/ListItemCard.vue +190 -0
- package/app/components/lists/add-bookmark.vue +52 -0
- package/app/components/lists/add-list-item.vue +88 -0
- package/app/components/lists/add-list.vue +57 -0
- package/app/components/lists/lists.vue +6 -0
- package/app/components/lists/listsettings.vue +145 -0
- package/app/components/lists/update-bookmark.vue +267 -0
- package/app/components/lists/update-list.vue +192 -0
- package/app/components/media/MediaPlayer.vue +302 -0
- package/app/components/partials/addtolist.vue +233 -0
- package/app/components/partials/createListBtn.vue +95 -0
- package/app/components/partials/listBtn.vue +35 -0
- package/app/components/related/list.vue +33 -0
- package/app/components/related/relatedlists.vue +43 -0
- package/app/components/tasks/TaskItem.vue +204 -0
- package/app/composables/bookmarks/createBookmark.js +30 -0
- package/app/composables/bookmarks/deleteBookmark.js +15 -0
- package/app/composables/bookmarks/updateBookmark.js +15 -0
- package/app/composables/config.ts +17 -0
- package/app/composables/content/uploadFiles.js +41 -0
- package/app/composables/lists/createList.js +25 -0
- package/app/composables/lists/deleteList.js +14 -0
- package/app/composables/lists/updateList.js +20 -0
- package/app/composables/lists/useBookmarks.js +69 -0
- package/app/composables/lists/useLists.js +120 -0
- package/app/composables/lists/usePlaylist.js +64 -0
- package/app/composables/lists/useSaved.js +29 -0
- package/app/composables/lists/useTasks.js +86 -0
- package/app/composables/lists/useWishlist.js +51 -0
- package/app/composables/module.ts +75 -0
- package/app/composables/providers/directus.ts +145 -0
- package/app/composables/providers/memory.ts +127 -0
- package/app/composables/registry.ts +18 -0
- package/app/composables/types.ts +44 -0
- package/app/composables/useLists.ts +20 -0
- package/app/composables/utils/health.ts +16 -0
- package/app/composables/utils/transforms.ts +42 -0
- package/app/composables/utils/validation.ts +21 -0
- package/app/pages/lists/bookmark/[id].vue +76 -0
- package/app/pages/lists/index.vue +152 -0
- package/app/pages/lists/list/[...slug].vue +233 -0
- package/package.json +5 -13
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const useSaved = () => {
|
|
2
|
+
const { $directus } = useNuxtApp();
|
|
3
|
+
const user = useSupabaseUser();
|
|
4
|
+
|
|
5
|
+
const isSaved = async (productId) => {
|
|
6
|
+
if (!user.value) return false;
|
|
7
|
+
|
|
8
|
+
const lists = await $directus.items('lists').readItems({
|
|
9
|
+
filter: { user: { _eq: user.value.id } },
|
|
10
|
+
fields: ['id']
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const listIds = lists.map((l) => l.id);
|
|
14
|
+
|
|
15
|
+
if (listIds.length === 0) return false;
|
|
16
|
+
|
|
17
|
+
const saved = await $directus.items('list_items').readItems({
|
|
18
|
+
filter: {
|
|
19
|
+
list: { _in: listIds },
|
|
20
|
+
product: { _eq: productId }
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return saved.length > 0;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return { isSaved };
|
|
28
|
+
};
|
|
29
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const useTasks = () => {
|
|
2
|
+
const { createList, addToList, getUserLists, updateListItem } = useLists()
|
|
3
|
+
|
|
4
|
+
const createTaskList = async (name, description = '') => {
|
|
5
|
+
return await createList({
|
|
6
|
+
name,
|
|
7
|
+
description,
|
|
8
|
+
type: 'tasks',
|
|
9
|
+
visibility: 'private'
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const addTask = async (listId, taskData) => {
|
|
14
|
+
const { title, description, priority, due_date, labels } = taskData
|
|
15
|
+
|
|
16
|
+
return await addToList(listId, {
|
|
17
|
+
type: 'task',
|
|
18
|
+
title,
|
|
19
|
+
description,
|
|
20
|
+
priority: priority || 'medium',
|
|
21
|
+
due_date,
|
|
22
|
+
labels: labels || [],
|
|
23
|
+
completed: false,
|
|
24
|
+
date_created: new Date().toISOString(),
|
|
25
|
+
subtasks: []
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const completeTask = async (itemId) => {
|
|
30
|
+
return await updateListItem(itemId, {
|
|
31
|
+
'content.completed': true,
|
|
32
|
+
'content.date_completed': new Date().toISOString()
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const uncompleteTask = async (itemId) => {
|
|
37
|
+
return await updateListItem(itemId, {
|
|
38
|
+
'content.completed': false,
|
|
39
|
+
'content.date_completed': null
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const updateTaskPriority = async (itemId, priority) => {
|
|
44
|
+
return await updateListItem(itemId, {
|
|
45
|
+
'content.priority': priority
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const updateTaskDueDate = async (itemId, dueDate) => {
|
|
50
|
+
return await updateListItem(itemId, {
|
|
51
|
+
'content.due_date': dueDate
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const addSubtask = async (itemId, subtaskTitle) => {
|
|
56
|
+
const { getListById } = useLists()
|
|
57
|
+
const item = await getListById(itemId)
|
|
58
|
+
const subtasks = item.content.subtasks || []
|
|
59
|
+
|
|
60
|
+
subtasks.push({
|
|
61
|
+
id: Date.now(),
|
|
62
|
+
title: subtaskTitle,
|
|
63
|
+
completed: false,
|
|
64
|
+
date_created: new Date().toISOString()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return await updateListItem(itemId, {
|
|
68
|
+
'content.subtasks': subtasks
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const getUserTaskLists = async () => {
|
|
73
|
+
return await getUserLists('tasks')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
createTaskList,
|
|
78
|
+
addTask,
|
|
79
|
+
completeTask,
|
|
80
|
+
uncompleteTask,
|
|
81
|
+
updateTaskPriority,
|
|
82
|
+
updateTaskDueDate,
|
|
83
|
+
addSubtask,
|
|
84
|
+
getUserTaskLists
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const useWishlist = () => {
|
|
2
|
+
const { createList, addToList, getUserLists, removeFromList } = useLists()
|
|
3
|
+
|
|
4
|
+
const createWishlist = async (name = 'My Wishlist', description = '') => {
|
|
5
|
+
return await createList({
|
|
6
|
+
name,
|
|
7
|
+
description,
|
|
8
|
+
type: 'wishlist',
|
|
9
|
+
visibility: 'private'
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const addToWishlist = async (wishlistId, itemData) => {
|
|
14
|
+
const { title, url, price, image, description, category } = itemData
|
|
15
|
+
|
|
16
|
+
return await addToList(wishlistId, {
|
|
17
|
+
type: 'product',
|
|
18
|
+
title,
|
|
19
|
+
url,
|
|
20
|
+
price,
|
|
21
|
+
image,
|
|
22
|
+
description,
|
|
23
|
+
category,
|
|
24
|
+
date_added: new Date().toISOString(),
|
|
25
|
+
priority: itemData.priority || 'medium'
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const getUserWishlists = async () => {
|
|
30
|
+
return await getUserLists('wishlist')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const removeFromWishlist = async (itemId) => {
|
|
34
|
+
return await removeFromList(itemId)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const updateWishlistItemPriority = async (itemId, priority) => {
|
|
38
|
+
const { updateListItem } = useLists()
|
|
39
|
+
return await updateListItem(itemId, {
|
|
40
|
+
'content.priority': priority
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
createWishlist,
|
|
46
|
+
addToWishlist,
|
|
47
|
+
getUserWishlists,
|
|
48
|
+
removeFromWishlist,
|
|
49
|
+
updateWishlistItemPriority
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineAlternateModule,
|
|
3
|
+
useAlternateEventBus,
|
|
4
|
+
useAlternateContext,
|
|
5
|
+
type AlternateContext,
|
|
6
|
+
type ListsAdapter
|
|
7
|
+
} from '@meeovi/core'
|
|
8
|
+
|
|
9
|
+
import { getListsConfig } from './config'
|
|
10
|
+
import { registerListsProviderRuntime } from './registry'
|
|
11
|
+
|
|
12
|
+
export default defineAlternateModule({
|
|
13
|
+
id: 'lists',
|
|
14
|
+
adapters: {},
|
|
15
|
+
|
|
16
|
+
async setup(ctx: AlternateContext) {
|
|
17
|
+
const bus = useAlternateEventBus()
|
|
18
|
+
const config = getListsConfig()
|
|
19
|
+
|
|
20
|
+
// If a core adapter was registered under the `lists` key, register it
|
|
21
|
+
// into the local provider registry so UI code can consume it via `useLists()`.
|
|
22
|
+
// If a core adapter was registered under the `lists` key, adapt it
|
|
23
|
+
// into the `ListsProvider` shape and register it into the local registry.
|
|
24
|
+
try {
|
|
25
|
+
const runtimeAdapter = ctx.getAdapter('lists' as any) as ListsAdapter | undefined
|
|
26
|
+
if (runtimeAdapter) {
|
|
27
|
+
const provider = {
|
|
28
|
+
getList: (id: string) => runtimeAdapter.getList(id),
|
|
29
|
+
listLists: (params?: Record<string, unknown>) => runtimeAdapter.listLists(params),
|
|
30
|
+
createList: (data: Partial<Record<string, unknown>>) => runtimeAdapter.createList(data),
|
|
31
|
+
updateList: (id: string, data: Partial<Record<string, unknown>>) => runtimeAdapter.updateList(id, data),
|
|
32
|
+
deleteList: (id: string) => runtimeAdapter.deleteList(id),
|
|
33
|
+
|
|
34
|
+
addItem: (listId: string, item: Partial<Record<string, unknown>>) => runtimeAdapter.addItem(listId, item),
|
|
35
|
+
updateItem: (listId: string, itemId: string, data: Partial<Record<string, unknown>>) => runtimeAdapter.updateItem(listId, itemId, data),
|
|
36
|
+
deleteItem: (listId: string, itemId: string) => runtimeAdapter.deleteItem(listId, itemId),
|
|
37
|
+
|
|
38
|
+
reorderItems: runtimeAdapter.reorderItems ? (listId: string, itemIds: string[]) => runtimeAdapter.reorderItems!(listId, itemIds) : undefined
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
registerListsProviderRuntime('core', provider)
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// noop
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Listen for runtime adapter registrations from ModuleRegistry
|
|
48
|
+
bus.on('adapter:registered' as any, (payload: any) => {
|
|
49
|
+
try {
|
|
50
|
+
if (payload?.key === 'lists') {
|
|
51
|
+
const runtimeAdapter = ctx.getAdapter('lists' as any) as ListsAdapter | undefined
|
|
52
|
+
if (runtimeAdapter) {
|
|
53
|
+
const provider = {
|
|
54
|
+
getList: (id: string) => runtimeAdapter.getList(id),
|
|
55
|
+
listLists: (params?: Record<string, unknown>) => runtimeAdapter.listLists(params),
|
|
56
|
+
createList: (data: Partial<Record<string, unknown>>) => runtimeAdapter.createList(data),
|
|
57
|
+
updateList: (id: string, data: Partial<Record<string, unknown>>) => runtimeAdapter.updateList(id, data),
|
|
58
|
+
deleteList: (id: string) => runtimeAdapter.deleteList(id),
|
|
59
|
+
|
|
60
|
+
addItem: (listId: string, item: Partial<Record<string, unknown>>) => runtimeAdapter.addItem(listId, item),
|
|
61
|
+
updateItem: (listId: string, itemId: string, data: Partial<Record<string, unknown>>) => runtimeAdapter.updateItem(listId, itemId, data),
|
|
62
|
+
deleteItem: (listId: string, itemId: string) => runtimeAdapter.deleteItem(listId, itemId),
|
|
63
|
+
|
|
64
|
+
reorderItems: runtimeAdapter.reorderItems ? (listId: string, itemIds: string[]) => runtimeAdapter.reorderItems!(listId, itemIds) : undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
registerListsProviderRuntime('core', provider)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
/* noop */
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
})
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { registerListsProvider } from '../registry'
|
|
2
|
+
import type { ListsProvider } from '../types'
|
|
3
|
+
import { getListsConfig } from '../config'
|
|
4
|
+
|
|
5
|
+
async function directusFetch(path: string, options: RequestInit = {}) {
|
|
6
|
+
const cfg = getListsConfig()
|
|
7
|
+
const base = cfg.baseUrl?.replace(/\/$/, '') || ''
|
|
8
|
+
|
|
9
|
+
const res = await fetch(`${base}${path}`, {
|
|
10
|
+
...options,
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
...(cfg.apiKey ? { Authorization: `Bearer ${cfg.apiKey}` } : {}),
|
|
14
|
+
...(options.headers || {})
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
const err: any = new Error(`Directus error: ${res.status}`)
|
|
20
|
+
err.status = res.status
|
|
21
|
+
err.response = res
|
|
22
|
+
throw err
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return res.json()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DirectusListsProvider: ListsProvider = {
|
|
29
|
+
async getList(id) {
|
|
30
|
+
const json = await directusFetch(`/lists/${id}`)
|
|
31
|
+
return json.list ?? json
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async listLists() {
|
|
35
|
+
const json = await directusFetch(`/lists`)
|
|
36
|
+
return json.lists ?? json
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async createList(data) {
|
|
40
|
+
const json = await directusFetch(`/lists`, { method: 'POST', body: JSON.stringify(data) })
|
|
41
|
+
return json.list ?? json
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
async updateList(id, data) {
|
|
45
|
+
const json = await directusFetch(`/lists/${id}`, { method: 'PUT', body: JSON.stringify(data) })
|
|
46
|
+
return json.list ?? json
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
async deleteList(id) {
|
|
50
|
+
await directusFetch(`/lists/${id}`, { method: 'DELETE' })
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async addItem(listId, item) {
|
|
54
|
+
const json = await directusFetch(`/lists/${listId}/items`, { method: 'POST', body: JSON.stringify(item) })
|
|
55
|
+
return json.item ?? json
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
async updateItem(listId, itemId, data) {
|
|
59
|
+
const json = await directusFetch(`/lists/${listId}/items/${itemId}`, { method: 'PUT', body: JSON.stringify(data) })
|
|
60
|
+
return json.item ?? json
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
async deleteItem(listId, itemId) {
|
|
64
|
+
await directusFetch(`/lists/${listId}/items/${itemId}`, { method: 'DELETE' })
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
async reorderItems(listId, itemIds) {
|
|
68
|
+
await directusFetch(`/lists/${listId}/reorder`, { method: 'POST', body: JSON.stringify({ items: itemIds }) })
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
async toggleComplete(listId, itemId, completed = true) {
|
|
72
|
+
try {
|
|
73
|
+
const json = await directusFetch(`/lists/${listId}/items/${itemId}/toggle`, { method: 'POST', body: JSON.stringify({ completed }) })
|
|
74
|
+
return json.item ?? json
|
|
75
|
+
} catch (e) {
|
|
76
|
+
const json = await directusFetch(`/lists/${listId}/items/${itemId}`, { method: 'PUT', body: JSON.stringify({ completed }) })
|
|
77
|
+
return json.item ?? json
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async setDueDate(listId, itemId, dueDate) {
|
|
82
|
+
try {
|
|
83
|
+
const json = await directusFetch(`/lists/${listId}/items/${itemId}/due`, { method: 'POST', body: JSON.stringify({ dueDate }) })
|
|
84
|
+
return json.item ?? json
|
|
85
|
+
} catch (e) {
|
|
86
|
+
const json = await directusFetch(`/lists/${listId}/items/${itemId}`, { method: 'PUT', body: JSON.stringify({ metadata: { dueDate } }) })
|
|
87
|
+
return json.item ?? json
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
async setReminder(listId, itemId, reminder) {
|
|
92
|
+
try {
|
|
93
|
+
const json = await directusFetch(`/lists/${listId}/items/${itemId}/reminder`, { method: 'POST', body: JSON.stringify({ reminder }) })
|
|
94
|
+
return json.item ?? json
|
|
95
|
+
} catch (e) {
|
|
96
|
+
const json = await directusFetch(`/lists/${listId}/items/${itemId}`, { method: 'PUT', body: JSON.stringify({ metadata: { reminder } }) })
|
|
97
|
+
return json.item ?? json
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async setPriority(listId, itemId, priority) {
|
|
102
|
+
try {
|
|
103
|
+
const json = await directusFetch(`/lists/${listId}/items/${itemId}/priority`, { method: 'POST', body: JSON.stringify({ priority }) })
|
|
104
|
+
return json.item ?? json
|
|
105
|
+
} catch (e) {
|
|
106
|
+
const json = await directusFetch(`/lists/${listId}/items/${itemId}`, { method: 'PUT', body: JSON.stringify({ metadata: { priority } }) })
|
|
107
|
+
return json.item ?? json
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
async shareList(listId, userId, role = 'editor') {
|
|
112
|
+
try {
|
|
113
|
+
await directusFetch(`/lists/${listId}/share`, { method: 'POST', body: JSON.stringify({ userId, role }) })
|
|
114
|
+
} catch (e) {
|
|
115
|
+
const json = await directusFetch(`/lists/${listId}`)
|
|
116
|
+
const list = json.list ?? json
|
|
117
|
+
const collaborators = (list.metadata?.collaborators || []).concat({ userId, role })
|
|
118
|
+
await directusFetch(`/lists/${listId}`, { method: 'PUT', body: JSON.stringify({ metadata: { ...(list.metadata || {}), collaborators } }) })
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
async searchItems(listId, query) {
|
|
123
|
+
try {
|
|
124
|
+
const json = await directusFetch(`/lists/${listId}/search?q=${encodeURIComponent(String(query))}`)
|
|
125
|
+
return json.items ?? json
|
|
126
|
+
} catch (e) {
|
|
127
|
+
const json = await directusFetch(`/lists/${listId}`)
|
|
128
|
+
const list = json.list ?? json
|
|
129
|
+
const q = String(query).toLowerCase()
|
|
130
|
+
return list.items.filter((i: any) => (i.title || '').toLowerCase().includes(q) || (i.description || '').toLowerCase().includes(q))
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
async archiveList(listId) {
|
|
135
|
+
try {
|
|
136
|
+
await directusFetch(`/lists/${listId}/archive`, { method: 'POST' })
|
|
137
|
+
} catch (e) {
|
|
138
|
+
const json = await directusFetch(`/lists/${listId}`)
|
|
139
|
+
const list = json.list ?? json
|
|
140
|
+
await directusFetch(`/lists/${listId}`, { method: 'PUT', body: JSON.stringify({ metadata: { ...(list.metadata || {}), archived: true } }) })
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
registerListsProvider('directus', DirectusListsProvider)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { registerListsProvider } from '../registry'
|
|
2
|
+
import type { List, ListItem, ListsProvider } from '../types'
|
|
3
|
+
import { nanoid } from 'nanoid'
|
|
4
|
+
|
|
5
|
+
const lists = new Map<string, List>()
|
|
6
|
+
|
|
7
|
+
const MemoryListsProvider: ListsProvider = {
|
|
8
|
+
async getList(id) {
|
|
9
|
+
const list = lists.get(id)
|
|
10
|
+
if (!list) throw new Error(`List ${id} not found`)
|
|
11
|
+
return list
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
async listLists() {
|
|
15
|
+
return Array.from(lists.values())
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
async createList(data) {
|
|
19
|
+
const id = nanoid()
|
|
20
|
+
const list: List = {
|
|
21
|
+
id,
|
|
22
|
+
title: data.title || 'Untitled List',
|
|
23
|
+
type: data.type || 'list',
|
|
24
|
+
items: [],
|
|
25
|
+
metadata: data.metadata || {},
|
|
26
|
+
createdAt: new Date().toISOString(),
|
|
27
|
+
updatedAt: new Date().toISOString()
|
|
28
|
+
}
|
|
29
|
+
lists.set(id, list)
|
|
30
|
+
return list
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async updateList(id, data) {
|
|
34
|
+
const list = await this.getList(id)
|
|
35
|
+
const updated = {
|
|
36
|
+
...list,
|
|
37
|
+
...data,
|
|
38
|
+
updatedAt: new Date().toISOString()
|
|
39
|
+
}
|
|
40
|
+
lists.set(id, updated)
|
|
41
|
+
return updated
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
async deleteList(id) {
|
|
45
|
+
lists.delete(id)
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
async addItem(listId, item) {
|
|
49
|
+
const list = await this.getList(listId)
|
|
50
|
+
const newItem: ListItem = {
|
|
51
|
+
id: nanoid(),
|
|
52
|
+
title: item.title || '',
|
|
53
|
+
description: item.description,
|
|
54
|
+
completed: item.completed || false,
|
|
55
|
+
position: list.items.length,
|
|
56
|
+
parentId: item.parentId,
|
|
57
|
+
metadata: item.metadata || {},
|
|
58
|
+
createdAt: new Date().toISOString(),
|
|
59
|
+
updatedAt: new Date().toISOString()
|
|
60
|
+
}
|
|
61
|
+
list.items.push(newItem)
|
|
62
|
+
return newItem
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async updateItem(listId, itemId, data) {
|
|
66
|
+
const list = await this.getList(listId)
|
|
67
|
+
const item = list.items.find(i => i.id === itemId)
|
|
68
|
+
if (!item) throw new Error(`Item ${itemId} not found`)
|
|
69
|
+
Object.assign(item, data, { updatedAt: new Date().toISOString() })
|
|
70
|
+
return item
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async deleteItem(listId, itemId) {
|
|
74
|
+
const list = await this.getList(listId)
|
|
75
|
+
list.items = list.items.filter(i => i.id !== itemId)
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
async reorderItems(listId, itemIds) {
|
|
79
|
+
const list = await this.getList(listId)
|
|
80
|
+
const newOrder = itemIds.map(id => list.items.find(i => i.id === id)!)
|
|
81
|
+
list.items = newOrder.map((item, index) => ({
|
|
82
|
+
...item,
|
|
83
|
+
position: index
|
|
84
|
+
}))
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
async toggleComplete(listId, itemId, completed = true) {
|
|
88
|
+
const item = await this.updateItem(listId, itemId, { completed })
|
|
89
|
+
return item
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
async setDueDate(listId, itemId, dueDate) {
|
|
93
|
+
const item = await this.updateItem(listId, itemId, { metadata: { ...( (await this.getList(listId)).items.find(i => i.id === itemId)?.metadata || {} ), dueDate } })
|
|
94
|
+
return item
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async setReminder(listId, itemId, reminder) {
|
|
98
|
+
const item = await this.updateItem(listId, itemId, { metadata: { ...( (await this.getList(listId)).items.find(i => i.id === itemId)?.metadata || {} ), reminder } })
|
|
99
|
+
return item
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
async setPriority(listId, itemId, priority) {
|
|
103
|
+
const item = await this.updateItem(listId, itemId, { metadata: { ...( (await this.getList(listId)).items.find(i => i.id === itemId)?.metadata || {} ), priority } })
|
|
104
|
+
return item
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
async shareList(listId, userId, role = 'editor') {
|
|
108
|
+
// memory store: attach collaborators in list.metadata.collaborators
|
|
109
|
+
const list = await this.getList(listId)
|
|
110
|
+
const collaborators = (list.metadata?.collaborators as any[]) || []
|
|
111
|
+
collaborators.push({ userId, role })
|
|
112
|
+
list.metadata = { ...(list.metadata || {}), collaborators }
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async searchItems(listId, query) {
|
|
116
|
+
const list = await this.getList(listId)
|
|
117
|
+
const q = String(query).toLowerCase()
|
|
118
|
+
return list.items.filter(i => (i.title || '').toLowerCase().includes(q) || (i.description || '').toLowerCase().includes(q))
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
async archiveList(listId) {
|
|
122
|
+
const list = await this.getList(listId)
|
|
123
|
+
list.metadata = { ...(list.metadata || {}), archived: true }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
registerListsProvider('memory', MemoryListsProvider)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ListsProvider } from './types'
|
|
2
|
+
|
|
3
|
+
const providers: Record<string, ListsProvider> = {}
|
|
4
|
+
|
|
5
|
+
export function registerListsProvider(name: string, provider: ListsProvider) {
|
|
6
|
+
providers[name] = provider
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Allow runtime registration from other modules (e.g. via core ModuleRegistry adapters)
|
|
10
|
+
export function registerListsProviderRuntime(name: string, provider: ListsProvider) {
|
|
11
|
+
registerListsProvider(name, provider)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getListsProvider(name: string): ListsProvider {
|
|
15
|
+
const provider = providers[name]
|
|
16
|
+
if (!provider) throw new Error(`Lists provider "${name}" not found`)
|
|
17
|
+
return provider
|
|
18
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface ListItem {
|
|
2
|
+
id: string
|
|
3
|
+
title: string
|
|
4
|
+
description?: string
|
|
5
|
+
completed?: boolean
|
|
6
|
+
position?: number
|
|
7
|
+
parentId?: string
|
|
8
|
+
metadata?: Record<string, any>
|
|
9
|
+
createdAt?: string
|
|
10
|
+
updatedAt?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface List {
|
|
14
|
+
id: string
|
|
15
|
+
title: string
|
|
16
|
+
type: 'checklist' | 'kanban' | 'list' | string
|
|
17
|
+
items: ListItem[]
|
|
18
|
+
metadata?: Record<string, any>
|
|
19
|
+
createdAt?: string
|
|
20
|
+
updatedAt?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ListsProvider {
|
|
24
|
+
getList(id: string): Promise<List>
|
|
25
|
+
listLists(params?: Record<string, any>): Promise<List[]>
|
|
26
|
+
createList(data: Partial<List>): Promise<List>
|
|
27
|
+
updateList(id: string, data: Partial<List>): Promise<List>
|
|
28
|
+
deleteList(id: string): Promise<void>
|
|
29
|
+
|
|
30
|
+
addItem(listId: string, item: Partial<ListItem>): Promise<ListItem>
|
|
31
|
+
updateItem(listId: string, itemId: string, data: Partial<ListItem>): Promise<ListItem>
|
|
32
|
+
deleteItem(listId: string, itemId: string): Promise<void>
|
|
33
|
+
|
|
34
|
+
reorderItems?(listId: string, itemIds: string[]): Promise<void>
|
|
35
|
+
|
|
36
|
+
// Optional advanced features commonly found in task apps
|
|
37
|
+
toggleComplete?(listId: string, itemId: string, completed: boolean): Promise<ListItem>
|
|
38
|
+
setDueDate?(listId: string, itemId: string, dueDate: string | null): Promise<ListItem>
|
|
39
|
+
setReminder?(listId: string, itemId: string, reminder: string | null): Promise<ListItem>
|
|
40
|
+
setPriority?(listId: string, itemId: string, priority: number | null): Promise<ListItem>
|
|
41
|
+
shareList?(listId: string, userId: string, role?: string): Promise<void>
|
|
42
|
+
searchItems?(listId: string, query: string): Promise<ListItem[]>
|
|
43
|
+
archiveList?(listId: string): Promise<void>
|
|
44
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getListsConfig } from './config'
|
|
2
|
+
import { getListsProvider } from './registry'
|
|
3
|
+
|
|
4
|
+
export function useLists() {
|
|
5
|
+
const { provider } = getListsConfig()
|
|
6
|
+
const lists = getListsProvider(provider)
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
getList: lists.getList,
|
|
10
|
+
listLists: lists.listLists,
|
|
11
|
+
createList: lists.createList,
|
|
12
|
+
updateList: lists.updateList,
|
|
13
|
+
deleteList: lists.deleteList,
|
|
14
|
+
|
|
15
|
+
addItem: lists.addItem,
|
|
16
|
+
updateItem: lists.updateItem,
|
|
17
|
+
deleteItem: lists.deleteItem,
|
|
18
|
+
reorderItems: lists.reorderItems
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getListsProvider } from '../registry'
|
|
2
|
+
import { getListsConfig } from '../config'
|
|
3
|
+
|
|
4
|
+
export async function checkListsProviderHealth(providerName?: string) {
|
|
5
|
+
try {
|
|
6
|
+
const cfg = getListsConfig()
|
|
7
|
+
const name = providerName || cfg.provider || 'directus'
|
|
8
|
+
const provider = getListsProvider(name)
|
|
9
|
+
const lists = await provider.listLists()
|
|
10
|
+
return { ok: true, count: Array.isArray(lists) ? lists.length : null }
|
|
11
|
+
} catch (e: any) {
|
|
12
|
+
return { ok: false, error: e?.message || String(e) }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default checkListsProviderHealth
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { List, ListItem } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize a raw provider list into Meeovi's List shape.
|
|
5
|
+
*/
|
|
6
|
+
export function transformList(raw: any): List {
|
|
7
|
+
return {
|
|
8
|
+
id: raw.id,
|
|
9
|
+
title: raw.title ?? raw.name ?? 'Untitled',
|
|
10
|
+
type: raw.type ?? 'list',
|
|
11
|
+
items: Array.isArray(raw.items)
|
|
12
|
+
? raw.items.map(transformItem)
|
|
13
|
+
: [],
|
|
14
|
+
metadata: raw.metadata ?? {},
|
|
15
|
+
createdAt: raw.createdAt ?? raw.created_at ?? null,
|
|
16
|
+
updatedAt: raw.updatedAt ?? raw.updated_at ?? null
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Normalize a raw provider item into Meeovi's ListItem shape.
|
|
22
|
+
*/
|
|
23
|
+
export function transformItem(raw: any): ListItem {
|
|
24
|
+
return {
|
|
25
|
+
id: raw.id,
|
|
26
|
+
title: raw.title ?? raw.name ?? '',
|
|
27
|
+
description: raw.description ?? raw.body ?? '',
|
|
28
|
+
completed: raw.completed ?? raw.done ?? false,
|
|
29
|
+
position: raw.position ?? raw.order ?? 0,
|
|
30
|
+
parentId: raw.parentId ?? raw.parent_id ?? null,
|
|
31
|
+
metadata: raw.metadata ?? {},
|
|
32
|
+
createdAt: raw.createdAt ?? raw.created_at ?? null,
|
|
33
|
+
updatedAt: raw.updatedAt ?? raw.updated_at ?? null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Normalize arrays safely.
|
|
39
|
+
*/
|
|
40
|
+
export function transformListArray(raw: any[]): List[] {
|
|
41
|
+
return raw.map(transformList)
|
|
42
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { List, ListItem } from '../types'
|
|
2
|
+
|
|
3
|
+
export function validateListInput(data: Partial<List>) {
|
|
4
|
+
if (!data.title || typeof data.title !== 'string') {
|
|
5
|
+
throw new Error('List title is required and must be a string')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (data.type && typeof data.type !== 'string') {
|
|
9
|
+
throw new Error('List type must be a string')
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function validateItemInput(data: Partial<ListItem>) {
|
|
14
|
+
if (!data.title || typeof data.title !== 'string') {
|
|
15
|
+
throw new Error('Item title is required and must be a string')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (data.completed !== undefined && typeof data.completed !== 'boolean') {
|
|
19
|
+
throw new Error('Item completed must be a boolean')
|
|
20
|
+
}
|
|
21
|
+
}
|