@meeovi/layer-lists 1.0.2 → 1.0.4

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.
Files changed (47) hide show
  1. package/README.md +111 -0
  2. package/package.json +15 -3
  3. package/app/components/features/archived.vue +0 -64
  4. package/app/components/features/bookmarks.vue +0 -64
  5. package/app/components/features/lists.vue +0 -61
  6. package/app/components/features/starred.vue +0 -64
  7. package/app/components/lists/ListItemCard.vue +0 -190
  8. package/app/components/lists/add-bookmark.vue +0 -52
  9. package/app/components/lists/add-list-item.vue +0 -88
  10. package/app/components/lists/add-list.vue +0 -57
  11. package/app/components/lists/lists.vue +0 -6
  12. package/app/components/lists/listsettings.vue +0 -145
  13. package/app/components/lists/update-bookmark.vue +0 -267
  14. package/app/components/lists/update-list.vue +0 -192
  15. package/app/components/media/MediaPlayer.vue +0 -302
  16. package/app/components/partials/addtolist.vue +0 -233
  17. package/app/components/partials/createListBtn.vue +0 -95
  18. package/app/components/partials/listBtn.vue +0 -35
  19. package/app/components/related/list.vue +0 -33
  20. package/app/components/related/relatedlists.vue +0 -43
  21. package/app/components/tasks/TaskItem.vue +0 -204
  22. package/app/composables/bookmarks/createBookmark.js +0 -30
  23. package/app/composables/bookmarks/deleteBookmark.js +0 -15
  24. package/app/composables/bookmarks/updateBookmark.js +0 -15
  25. package/app/composables/config.ts +0 -17
  26. package/app/composables/content/uploadFiles.js +0 -41
  27. package/app/composables/globals/useDirectusForm.ts +0 -1
  28. package/app/composables/lists/createList.js +0 -25
  29. package/app/composables/lists/deleteList.js +0 -14
  30. package/app/composables/lists/updateList.js +0 -20
  31. package/app/composables/lists/useBookmarks.js +0 -69
  32. package/app/composables/lists/useLists.js +0 -120
  33. package/app/composables/lists/usePlaylist.js +0 -64
  34. package/app/composables/lists/useSaved.js +0 -29
  35. package/app/composables/lists/useTasks.js +0 -86
  36. package/app/composables/lists/useWishlist.js +0 -51
  37. package/app/composables/providers/atproto.ts +0 -156
  38. package/app/composables/providers/directus.ts +0 -49
  39. package/app/composables/providers/memory.ts +0 -88
  40. package/app/composables/registry.ts +0 -13
  41. package/app/composables/types.ts +0 -35
  42. package/app/composables/useLists.ts +0 -20
  43. package/app/composables/utils/transforms.ts +0 -42
  44. package/app/composables/utils/validation.ts +0 -21
  45. package/app/pages/lists/bookmark/[id].vue +0 -76
  46. package/app/pages/lists/index.vue +0 -152
  47. package/app/pages/lists/list/[...slug].vue +0 -233
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # @meeovi/layer-lists
2
+
3
+ This layer provides first-class lists and task management functionality for the Alternate Framework. It's provider-driven: you can plug in different backends (Directus, memory, ATProto, etc.) without changing UI code. The default production-ready fallback is the `@meeovi/directus-client` provider.
4
+
5
+ Key goals:
6
+ - Provider-agnostic API used by the UI (`useLists()` composable).
7
+ - Advanced task features (due dates, reminders, priority, sharing, search, archive) exposed as optional provider methods.
8
+ - Production-ready defaults: Directus provider, health checks, graceful fallbacks.
9
+
10
+ Quick start
11
+
12
+ Install the layer into your project (monorepo/local install):
13
+
14
+ ```bash
15
+ npm install @meeovi/layer-lists
16
+ # also install your chosen provider (Directus example)
17
+ npm install @meeovi/directus-client
18
+ ```
19
+
20
+ Register the layer and configure the provider in your Alternate app (Directus example):
21
+
22
+ ```ts
23
+ import lists from '@meeovi/layer-lists'
24
+
25
+ const app = createAlternateApp({
26
+ config: {
27
+ lists: {
28
+ provider: 'directus',
29
+ directus: {
30
+ baseUrl: 'https://cms.example.com',
31
+ apiKey: process.env.DIRECTUS_KEY
32
+ }
33
+ }
34
+ },
35
+ modules: [lists]
36
+ })
37
+
38
+ await app.start()
39
+ ```
40
+
41
+ Using in a Nuxt (modules) app
42
+
43
+ 1. Install the packages into your Nuxt app project.
44
+ 2. Add the layer module to your Nuxt alternate app bootstrap (see above).
45
+ 3. In Vue components or composables, use the `useLists()` composable the same way as in the Alternate UI:
46
+
47
+ ```ts
48
+ const { listLists, createList, addItem, updateItem } = useLists()
49
+
50
+ const lists = await listLists()
51
+ await createList({ title: 'Groceries' })
52
+ await addItem('list-id', { title: 'Buy milk', metadata: { dueDate: '2026-02-01' } })
53
+ ```
54
+
55
+ Advanced features
56
+
57
+ Providers may implement optional advanced methods:
58
+ - `toggleComplete(listId, itemId, completed)`
59
+ - `setDueDate(listId, itemId, dueDate)`
60
+ - `setReminder(listId, itemId, reminder)`
61
+ - `setPriority(listId, itemId, priority)`
62
+ - `shareList(listId, userId, role)`
63
+ - `searchItems(listId, query)`
64
+ - `archiveList(listId)`
65
+
66
+ If a provider doesn't implement these methods, the `memory` provider includes sensible defaults and the `directus` provider attempts fallbacks to existing endpoints when possible.
67
+
68
+ Creating a provider
69
+
70
+ Implement a `ListsProvider` and register it with `registerListsProvider(name, provider)`.
71
+
72
+ Example skeleton (register at runtime):
73
+
74
+ ```ts
75
+ import { registerListsProvider } from '@meeovi/layer-lists/app/composables/registry'
76
+
77
+ const MyProvider = {
78
+ async getList(id) { /* ... */ },
79
+ async listLists() { /* ... */ },
80
+ async createList(data) { /* ... */ },
81
+ async addItem(listId, item) { /* ... */ },
82
+ // ... implement optional methods as desired
83
+ }
84
+
85
+ registerListsProvider('my-provider', MyProvider)
86
+ ```
87
+
88
+ Runtime registration
89
+
90
+ Other modules can register providers at runtime using `registerListsProviderRuntime(name, provider)`.
91
+
92
+ Health checks
93
+
94
+ Use the provided `checkListsProviderHealth()` helper to validate that the configured provider is reachable (it performs a lightweight `listLists()` call). Example:
95
+
96
+ ```ts
97
+ import { checkListsProviderHealth } from '@meeovi/layer-lists/app/composables/utils/health'
98
+
99
+ const healthy = await checkListsProviderHealth()
100
+ if (!healthy) {
101
+ // fallback or alert
102
+ }
103
+ ```
104
+
105
+ What's next
106
+
107
+ - Add server-side sync/notifications (webhooks) for reminders.
108
+ - Add tests and CI for providers and bridges.
109
+ - Provide an Express example wiring server-side endpoints to the Directus client.
110
+
111
+ License: MIT
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@meeovi/layer-lists",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Official Lists module for the M Framework.",
5
5
  "scripts": {
6
+ "build": "tsc -p tsconfig.json",
6
7
  "test": "echo \"Error: no test specified\" && exit 1"
7
8
  },
8
9
  "keywords": [
@@ -16,11 +17,22 @@
16
17
  "type": "module",
17
18
  "dependencies": {
18
19
  "@meeovi/api": "^1.0.1",
20
+ "@meeovi/core": "^1.0.3",
21
+ "@meeovi/directus-client": "^1.0.0",
19
22
  "@meeovi/layer-social": "^1.0.2",
20
23
  "list.js": "^2.3.1",
21
24
  "nanoid": "^5.1.6"
22
25
  },
23
26
  "devDependencies": {
24
27
  "nuxt": "^4.3.0"
25
- }
26
- }
28
+ },
29
+ "exports": {
30
+ "./nuxt.config": "./nuxt.config.ts"
31
+ },
32
+ "files": [
33
+ "nuxt.config.ts",
34
+ "layers",
35
+ "runtime",
36
+ "src"
37
+ ]
38
+ }
@@ -1,64 +0,0 @@
1
- <template>
2
- <div>
3
- <section data-bs-version="5.1" class="features4 start cid-v0Her0Ajsb" id="features04-1">
4
- <div class="container">
5
- <div class="row justify-content-center">
6
- <div class="col-12 content-head">
7
- <div class="mbr-section-head mb-5">
8
- <h4 class="mbr-section-title mbr-fonts-style align-center mb-0 display-2">
9
- <strong>My Archived Lists</strong>
10
- </h4>
11
-
12
- <div class="row">
13
- <div class="item features-image col-12 col-md-6 col-lg-4" v-for="list in archived" :key="list.id">
14
- <listCard :list="list" />
15
- </div>
16
- </div>
17
- </div>
18
- </div>
19
- </div>
20
- </div>
21
- </section>
22
- </div>
23
- </template>
24
-
25
- <script setup>
26
- import {
27
- ref
28
- } from 'vue'
29
- import listCard from '~/components/related/list.vue'
30
- import {
31
- useUserStore
32
- } from '#auth/app/stores/user'
33
-
34
- const userStore = useUserStore()
35
- const userDisplayName = computed(() => {
36
- return userStore.user?.name || userStore.user?.username || 'User'
37
- })
38
-
39
- const model = ref(null)
40
-
41
- const {
42
- $directus,
43
- $readItems
44
- } = useNuxtApp()
45
-
46
- const {
47
- data: archived
48
- } = await useAsyncData('archived', () => {
49
- return $directus.request($readItems('lists', {
50
- filter: {
51
- user: {
52
- _eq: userDisplayName.value,
53
- },
54
- status: {
55
- _eq: "Archived",
56
- }
57
- },
58
- }))
59
- })
60
-
61
- useHead({
62
- title: 'My Archived Lists - Meeovi Tasks'
63
- })
64
- </script>
@@ -1,64 +0,0 @@
1
- <template>
2
- <div>
3
- <section data-bs-version="5.1" class="features4 start cid-v0Her0Ajsb" id="features04-1">
4
- <div class="container">
5
- <div class="row justify-content-center">
6
- <div class="col-12 content-head">
7
- <div class="mbr-section-head mb-5">
8
- <h4 class="mbr-section-title mbr-fonts-style align-center mb-0 display-2">
9
- <strong>My Bookmarks</strong>
10
- </h4>
11
-
12
- <div class="row">
13
- <div class="item features-image col-12 col-md-6 col-lg-4" v-for="bookmark in bookmarks" :key="bookmark.id">
14
- <bookmarkCard :bookmark="bookmark" />
15
- </div>
16
- </div>
17
- </div>
18
- </div>
19
- </div>
20
- </div>
21
- </section>
22
- </div>
23
- </template>
24
-
25
- <script setup>
26
- import {
27
- ref
28
- } from 'vue'
29
- import bookmarkCard from '~/components/related/bookmark.vue'
30
- import {
31
- useUserStore
32
- } from '#auth/app/stores/user'
33
-
34
- const userStore = useUserStore()
35
- const userDisplayName = computed(() => {
36
- return userStore.user?.name || userStore.user?.username || 'User'
37
- })
38
-
39
- const model = ref(null)
40
-
41
- const {
42
- $directus,
43
- $readItems
44
- } = useNuxtApp()
45
-
46
- const {
47
- data: bookmarks
48
- } = await useAsyncData('bookmarks', () => {
49
- return $directus.request($readItems('lists', {
50
- filter: {
51
- creator: {
52
- _eq: userDisplayName.value,
53
- },
54
- type: {
55
- _eq: "bookmark",
56
- }
57
- },
58
- }))
59
- })
60
-
61
- useHead({
62
- title: 'My Bookmarks Lists - Meeovi Tasks'
63
- })
64
- </script>
@@ -1,61 +0,0 @@
1
- <template>
2
- <div>
3
- <section data-bs-version="5.1" class="features4 start cid-v0Her0Ajsb" id="features04-1">
4
- <div class="container">
5
- <div class="row justify-content-center">
6
- <div class="col-12 content-head">
7
- <div class="mbr-section-head mb-5">
8
- <h4 class="mbr-section-title mbr-fonts-style align-center mb-0 display-2">
9
- <strong>My Lists</strong>
10
- </h4>
11
-
12
- <div class="row">
13
- <div class="item features-image col-12 col-md-6 col-lg-4" v-for="list in myLists" :key="list.id">
14
- <listCard :list="list" />
15
- </div>
16
- </div>
17
- </div>
18
- </div>
19
- </div>
20
- </div>
21
- </section>
22
- </div>
23
- </template>
24
-
25
- <script setup>
26
- import {
27
- ref
28
- } from 'vue'
29
- import listCard from '~/components/related/list.vue'
30
- import {
31
- useUserStore
32
- } from '#auth/app/stores/user'
33
-
34
- const userStore = useUserStore()
35
- const userDisplayName = computed(() => {
36
- return userStore.user?.name || userStore.user?.username || 'User'
37
- })
38
-
39
- const model = ref(null)
40
-
41
- const {
42
- $directus,
43
- $readItems
44
- } = useNuxtApp()
45
-
46
- const {
47
- data: myLists
48
- } = await useAsyncData('myLists', () => {
49
- return $directus.request($readItems('lists', {
50
- filter: {
51
- user: {
52
- _eq: userDisplayName.value,
53
- }
54
- },
55
- }))
56
- })
57
-
58
- useHead({
59
- title: 'My Lists - Meeovi Tasks'
60
- })
61
- </script>
@@ -1,64 +0,0 @@
1
- <template>
2
- <div>
3
- <section data-bs-version="5.1" class="features4 start cid-v0Her0Ajsb" id="features04-1">
4
- <div class="container">
5
- <div class="row justify-content-center">
6
- <div class="col-12 content-head">
7
- <div class="mbr-section-head mb-5">
8
- <h4 class="mbr-section-title mbr-fonts-style align-center mb-0 display-2">
9
- <strong>My Starred Lists</strong>
10
- </h4>
11
-
12
- <div class="row">
13
- <div class="item features-image col-12 col-md-6 col-lg-4" v-for="list in starred" :key="list.id">
14
- <listCard :list="list" />
15
- </div>
16
- </div>
17
- </div>
18
- </div>
19
- </div>
20
- </div>
21
- </section>
22
- </div>
23
- </template>
24
-
25
- <script setup>
26
- import {
27
- ref
28
- } from 'vue'
29
- import listCard from '~/components/related/list.vue'
30
- import {
31
- useUserStore
32
- } from '#auth/app/stores/user'
33
-
34
- const userStore = useUserStore()
35
- const userDisplayName = computed(() => {
36
- return userStore.user?.name || userStore.user?.username || 'User'
37
- })
38
-
39
- const model = ref(null)
40
-
41
- const {
42
- $directus,
43
- $readItems
44
- } = useNuxtApp()
45
-
46
- const {
47
- data: starred
48
- } = await useAsyncData('starred', () => {
49
- return $directus.request($readItems('lists', {
50
- filter: {
51
- user: {
52
- _eq: userDisplayName.value,
53
- },
54
- favorite: {
55
- _eq: "yes",
56
- }
57
- },
58
- }))
59
- })
60
-
61
- useHead({
62
- title: 'My Starred Lists - Meeovi Tasks'
63
- })
64
- </script>
@@ -1,190 +0,0 @@
1
- <template>
2
- <v-card class="list-item-card" :class="`item-type-${item.content.type}`">
3
- <!-- Media Item -->
4
- <div v-if="item.content.type === 'media'">
5
- <v-img
6
- :src="item.content.thumbnail || '/default-media.png'"
7
- height="200"
8
- cover
9
- >
10
- <div class="d-flex align-end fill-height">
11
- <v-chip color="primary" size="small" class="ma-2">
12
- {{ item.content.media_type }}
13
- </v-chip>
14
- </div>
15
- </v-img>
16
-
17
- <v-card-title class="text-truncate">{{ item.content.title }}</v-card-title>
18
- <v-card-subtitle v-if="item.content.metadata?.artist">
19
- {{ item.content.metadata.artist }}
20
- </v-card-subtitle>
21
- </div>
22
-
23
- <!-- Bookmark Item -->
24
- <div v-else-if="item.content.type === 'bookmark'">
25
- <v-card-title class="d-flex align-center">
26
- <v-img
27
- :src="item.content.favicon || '/default-favicon.png'"
28
- width="16"
29
- height="16"
30
- class="me-2"
31
- />
32
- <span class="text-truncate">{{ item.content.title }}</span>
33
- </v-card-title>
34
-
35
- <v-card-text>
36
- <p class="text-body-2 text-truncate">{{ item.content.description }}</p>
37
- <div class="d-flex align-center mt-2">
38
- <v-chip
39
- v-for="tag in item.content.tags?.slice(0, 2)"
40
- :key="tag"
41
- size="x-small"
42
- class="me-1"
43
- >
44
- {{ tag }}
45
- </v-chip>
46
- <v-chip
47
- v-if="item.content.read"
48
- color="success"
49
- size="x-small"
50
- variant="outlined"
51
- >
52
- Read
53
- </v-chip>
54
- </div>
55
- </v-card-text>
56
- </div>
57
-
58
- <!-- Product/Wishlist Item -->
59
- <div v-else-if="item.content.type === 'product'">
60
- <v-img
61
- :src="item.content.image || '/default-product.png'"
62
- height="200"
63
- cover
64
- >
65
- <div class="d-flex align-end fill-height">
66
- <v-chip
67
- :color="getPriorityColor(item.content.priority)"
68
- size="small"
69
- class="ma-2"
70
- >
71
- {{ item.content.priority }}
72
- </v-chip>
73
- </div>
74
- </v-img>
75
-
76
- <v-card-title class="text-truncate">{{ item.content.title }}</v-card-title>
77
- <v-card-subtitle v-if="item.content.price">
78
- ${{ item.content.price }}
79
- </v-card-subtitle>
80
- </div>
81
-
82
- <!-- Default Item -->
83
- <div v-else>
84
- <v-img
85
- v-if="item.content.image"
86
- :src="item.content.image"
87
- height="200"
88
- cover
89
- />
90
-
91
- <v-card-title class="text-truncate">{{ item.content.title }}</v-card-title>
92
- <v-card-text v-if="item.content.description">
93
- <p class="text-body-2">{{ item.content.description }}</p>
94
- </v-card-text>
95
- </div>
96
-
97
- <!-- Actions -->
98
- <v-card-actions>
99
- <v-btn
100
- v-if="item.content.url"
101
- :href="item.content.url"
102
- target="_blank"
103
- variant="text"
104
- size="small"
105
- prepend-icon="mdi-open-in-new"
106
- >
107
- Open
108
- </v-btn>
109
-
110
- <v-spacer />
111
-
112
- <v-menu>
113
- <template v-slot:activator="{ props }">
114
- <v-btn
115
- icon="mdi-dots-vertical"
116
- variant="text"
117
- size="small"
118
- v-bind="props"
119
- />
120
- </template>
121
-
122
- <v-list>
123
- <v-list-item @click="$emit('edit', item)">
124
- <template v-slot:prepend>
125
- <v-icon icon="mdi-pencil" />
126
- </template>
127
- <v-list-item-title>Edit</v-list-item-title>
128
- </v-list-item>
129
-
130
- <v-divider />
131
-
132
- <v-list-item @click="$emit('delete', item.id)" class="text-error">
133
- <template v-slot:prepend>
134
- <v-icon icon="mdi-delete" color="error" />
135
- </template>
136
- <v-list-item-title>Delete</v-list-item-title>
137
- </v-list-item>
138
- </v-list>
139
- </v-menu>
140
- </v-card-actions>
141
- </v-card>
142
- </template>
143
-
144
- <script setup>
145
- const props = defineProps({
146
- item: {
147
- type: Object,
148
- required: true
149
- },
150
- listType: {
151
- type: String,
152
- required: true
153
- }
154
- })
155
-
156
- const emit = defineEmits(['edit', 'delete'])
157
-
158
- const getPriorityColor = (priority) => {
159
- const colors = {
160
- low: 'blue',
161
- medium: 'orange',
162
- high: 'red'
163
- }
164
- return colors[priority] || 'grey'
165
- }
166
- </script>
167
-
168
- <style scoped>
169
- .list-item-card {
170
- height: 100%;
171
- transition: all 0.2s ease;
172
- }
173
-
174
- .list-item-card:hover {
175
- transform: translateY(-2px);
176
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
177
- }
178
-
179
- .item-type-media {
180
- border-left: 4px solid rgb(var(--v-theme-purple));
181
- }
182
-
183
- .item-type-bookmark {
184
- border-left: 4px solid rgb(var(--v-theme-orange));
185
- }
186
-
187
- .item-type-product {
188
- border-left: 4px solid rgb(var(--v-theme-pink));
189
- }
190
- </style>
@@ -1,52 +0,0 @@
1
- <template>
2
- <v-row justify="center">
3
- <v-dialog v-model="dialog" :scrim="false" transition="dialog-bottom-transition">
4
- <template v-slot:activator="{ props }">
5
- <v-btn v-bind="props" class="rightAddBtn">
6
- <v-icon start icon="fas:fa fa-plus"></v-icon>Create a Bookmark
7
- </v-btn>
8
- </template>
9
- <v-card class="b-1">
10
- <v-card-title>
11
- <h3>Create New Bookmark</h3>
12
- </v-card-title>
13
-
14
- <v-card-text>
15
- <div v-if="formError" class="error">{{ formError }}</div>
16
- <div v-else-if="formSuccess" class="success">{{ formSuccess }}</div>
17
- <form @submit.prevent="submitForm">
18
- <DirectusFormElement v-for="field in websiteFields" :key="field.field" :field="field" v-model="form[field.field]" />
19
- <v-btn type="submit">Submit</v-btn>
20
- </form>
21
- </v-card-text>
22
- </v-card>
23
- </v-dialog>
24
- </v-row>
25
- </template>
26
-
27
- <script setup>
28
- import { ref } from 'vue'
29
- import DirectusFormElement from '#shared/app/components/ui/forms/DirectusFormElement.vue'
30
- import { useDirectusForm } from '../../composables/globals/useDirectusForm'
31
-
32
- const dialog = ref(false)
33
- const { $directus, $readFieldsByCollection } = useNuxtApp()
34
-
35
- const { data, error } = await useAsyncData('websites', async () => {
36
- return $directus.request($readFieldsByCollection('websites'))
37
- })
38
-
39
- // guard against undefined/null data.value and empty arrays
40
- if (error.value || data.value == null || (data.value?.length ?? 0) === 0) {
41
- console.error(error)
42
- throw createError({
43
- statusCode: 404,
44
- statusMessage: 'Bookmark not found'
45
- })
46
- }
47
-
48
- const websiteFields = data
49
-
50
- // use composable for form handling (validation, submit, provide context)
51
- const { form, formError, formSuccess, submitForm } = useDirectusForm('websites', websiteFields, { clearOnSuccess: true, closeDialogRef: dialog })
52
- </script>