@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.
- package/README.md +111 -0
- package/package.json +15 -3
- package/app/components/features/archived.vue +0 -64
- package/app/components/features/bookmarks.vue +0 -64
- package/app/components/features/lists.vue +0 -61
- package/app/components/features/starred.vue +0 -64
- package/app/components/lists/ListItemCard.vue +0 -190
- package/app/components/lists/add-bookmark.vue +0 -52
- package/app/components/lists/add-list-item.vue +0 -88
- package/app/components/lists/add-list.vue +0 -57
- package/app/components/lists/lists.vue +0 -6
- package/app/components/lists/listsettings.vue +0 -145
- package/app/components/lists/update-bookmark.vue +0 -267
- package/app/components/lists/update-list.vue +0 -192
- package/app/components/media/MediaPlayer.vue +0 -302
- package/app/components/partials/addtolist.vue +0 -233
- package/app/components/partials/createListBtn.vue +0 -95
- package/app/components/partials/listBtn.vue +0 -35
- package/app/components/related/list.vue +0 -33
- package/app/components/related/relatedlists.vue +0 -43
- package/app/components/tasks/TaskItem.vue +0 -204
- package/app/composables/bookmarks/createBookmark.js +0 -30
- package/app/composables/bookmarks/deleteBookmark.js +0 -15
- package/app/composables/bookmarks/updateBookmark.js +0 -15
- package/app/composables/config.ts +0 -17
- package/app/composables/content/uploadFiles.js +0 -41
- package/app/composables/globals/useDirectusForm.ts +0 -1
- package/app/composables/lists/createList.js +0 -25
- package/app/composables/lists/deleteList.js +0 -14
- package/app/composables/lists/updateList.js +0 -20
- package/app/composables/lists/useBookmarks.js +0 -69
- package/app/composables/lists/useLists.js +0 -120
- package/app/composables/lists/usePlaylist.js +0 -64
- package/app/composables/lists/useSaved.js +0 -29
- package/app/composables/lists/useTasks.js +0 -86
- package/app/composables/lists/useWishlist.js +0 -51
- package/app/composables/providers/atproto.ts +0 -156
- package/app/composables/providers/directus.ts +0 -49
- package/app/composables/providers/memory.ts +0 -88
- package/app/composables/registry.ts +0 -13
- package/app/composables/types.ts +0 -35
- package/app/composables/useLists.ts +0 -20
- package/app/composables/utils/transforms.ts +0 -42
- package/app/composables/utils/validation.ts +0 -21
- package/app/pages/lists/bookmark/[id].vue +0 -76
- package/app/pages/lists/index.vue +0 -152
- 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.
|
|
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>
|