@commonpub/layer 0.3.14 → 0.3.15
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/components/MessageThread.vue +2 -2
- package/components/NotificationItem.vue +8 -3
- package/composables/useMirrorContent.ts +3 -1
- package/package.json +7 -7
- package/pages/federated-hubs/[id]/posts/[postId].vue +7 -8
- package/pages/federation/users/[handle].vue +7 -3
- package/pages/hubs/[slug]/posts/[postId].vue +3 -3
- package/server/api/federation/dm.post.ts +2 -1
- package/server/api/hubs/[slug]/share.post.ts +10 -5
|
@@ -31,7 +31,7 @@ function handleSend(): void {
|
|
|
31
31
|
v-for="msg in messages"
|
|
32
32
|
:key="msg.id"
|
|
33
33
|
class="cpub-msg"
|
|
34
|
-
:class="{ own: msg.senderId === currentUserId }"
|
|
34
|
+
:class="{ 'cpub-msg-own': msg.senderId === currentUserId }"
|
|
35
35
|
>
|
|
36
36
|
<div v-if="msg.senderId !== currentUserId" class="cpub-msg-sender">
|
|
37
37
|
<img v-if="msg.senderAvatarUrl" :src="msg.senderAvatarUrl" :alt="msg.senderName ?? ''" class="cpub-msg-avatar" />
|
|
@@ -81,7 +81,7 @@ function handleSend(): void {
|
|
|
81
81
|
max-width: 70%;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
.cpub-msg.own {
|
|
84
|
+
.cpub-msg.cpub-msg-own {
|
|
85
85
|
align-self: flex-end;
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -19,11 +19,16 @@ const iconMap: Record<string, string> = {
|
|
|
19
19
|
follow: 'fa-solid fa-user-plus',
|
|
20
20
|
mention: 'fa-solid fa-at',
|
|
21
21
|
system: 'fa-solid fa-bell',
|
|
22
|
+
hub: 'fa-solid fa-users',
|
|
23
|
+
fork: 'fa-solid fa-code-fork',
|
|
24
|
+
build: 'fa-solid fa-hammer',
|
|
25
|
+
contest: 'fa-solid fa-trophy',
|
|
26
|
+
certificate: 'fa-solid fa-certificate',
|
|
22
27
|
};
|
|
23
28
|
</script>
|
|
24
29
|
|
|
25
30
|
<template>
|
|
26
|
-
<div class="cpub-notif" :class="{ unread: !notification.read }">
|
|
31
|
+
<div class="cpub-notif" :class="{ 'cpub-notif-unread': !notification.read }">
|
|
27
32
|
<div class="cpub-notif-avatar-wrap">
|
|
28
33
|
<img v-if="notification.actorAvatarUrl" :src="notification.actorAvatarUrl" :alt="notification.actorName ?? ''" class="cpub-notif-avatar" />
|
|
29
34
|
<div v-else class="cpub-notif-avatar cpub-notif-avatar-fallback">
|
|
@@ -42,7 +47,7 @@ const iconMap: Record<string, string> = {
|
|
|
42
47
|
{{ new Date(notification.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) }}
|
|
43
48
|
</time>
|
|
44
49
|
</div>
|
|
45
|
-
<NuxtLink v-if="notification.link || notification.targetUrl" :to="notification.link || notification.targetUrl || '#'" class="cpub-notif-link" aria-label="View">
|
|
50
|
+
<NuxtLink v-if="notification.link || notification.targetUrl" :to="notification.link || notification.targetUrl || '#'" class="cpub-notif-link" :aria-label="`View ${notification.type} notification`">
|
|
46
51
|
<i class="fa-solid fa-arrow-right"></i>
|
|
47
52
|
</NuxtLink>
|
|
48
53
|
</div>
|
|
@@ -58,7 +63,7 @@ const iconMap: Record<string, string> = {
|
|
|
58
63
|
border-bottom: var(--border-width-default) solid var(--border2);
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
.cpub-notif.unread {
|
|
66
|
+
.cpub-notif.cpub-notif-unread {
|
|
62
67
|
background: var(--accent-bg);
|
|
63
68
|
border-color: var(--accent-border);
|
|
64
69
|
}
|
|
@@ -83,7 +83,9 @@ export function useMirrorContent(fedContent: Ref<Record<string, unknown> | null>
|
|
|
83
83
|
},
|
|
84
84
|
buildCount: 0,
|
|
85
85
|
bookmarkCount: 0,
|
|
86
|
-
attachments: Array.isArray(fc.attachments)
|
|
86
|
+
attachments: Array.isArray(fc.attachments)
|
|
87
|
+
? (fc.attachments as Array<Record<string, unknown>>).filter((a) => typeof a.type === 'string' && typeof a.url === 'string').map((a) => ({ type: a.type as string, url: a.url as string, name: typeof a.name === 'string' ? a.name : undefined }))
|
|
88
|
+
: [],
|
|
87
89
|
} satisfies ContentViewData;
|
|
88
90
|
});
|
|
89
91
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"files": [
|
|
@@ -44,14 +44,14 @@
|
|
|
44
44
|
"vue": "^3.4.0",
|
|
45
45
|
"vue-router": "^4.3.0",
|
|
46
46
|
"zod": "^4.3.6",
|
|
47
|
-
"@commonpub/docs": "0.5.2",
|
|
48
|
-
"@commonpub/editor": "0.5.0",
|
|
49
|
-
"@commonpub/learning": "0.5.0",
|
|
50
|
-
"@commonpub/protocol": "0.9.4",
|
|
51
|
-
"@commonpub/server": "2.11.1",
|
|
52
|
-
"@commonpub/schema": "0.8.11",
|
|
53
47
|
"@commonpub/auth": "0.5.0",
|
|
54
48
|
"@commonpub/config": "0.7.0",
|
|
49
|
+
"@commonpub/editor": "0.5.0",
|
|
50
|
+
"@commonpub/protocol": "0.9.4",
|
|
51
|
+
"@commonpub/docs": "0.5.2",
|
|
52
|
+
"@commonpub/schema": "0.8.12",
|
|
53
|
+
"@commonpub/learning": "0.5.0",
|
|
54
|
+
"@commonpub/server": "2.11.2",
|
|
55
55
|
"@commonpub/ui": "0.7.1"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {}
|
|
@@ -92,12 +92,10 @@ useSeoMeta({
|
|
|
92
92
|
description: () => post.value?.content?.slice(0, 160) ?? '',
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
100
|
-
}
|
|
95
|
+
useHead({
|
|
96
|
+
link: computed(() => hub.value?.url ? [{ rel: 'canonical', href: hub.value.url }] : []),
|
|
97
|
+
meta: computed(() => hub.value?.url ? [{ name: 'robots', content: 'noindex, follow' }] : []),
|
|
98
|
+
});
|
|
101
99
|
</script>
|
|
102
100
|
|
|
103
101
|
<template>
|
|
@@ -145,7 +143,7 @@ if (hub.value?.url) {
|
|
|
145
143
|
</div>
|
|
146
144
|
|
|
147
145
|
<div class="cpub-post-actions">
|
|
148
|
-
<button class="cpub-post-action-btn" :class="{ active: liked }" :disabled="liking" @click="handleLike" :
|
|
146
|
+
<button class="cpub-post-action-btn" :class="{ active: liked }" :disabled="liking" @click="handleLike" :aria-label="liked ? 'Unlike post' : 'Like post'" :aria-pressed="liked">
|
|
149
147
|
<i :class="liked ? 'fa-solid fa-heart' : 'fa-regular fa-heart'"></i>
|
|
150
148
|
{{ likeCount }}
|
|
151
149
|
</button>
|
|
@@ -164,6 +162,7 @@ if (hub.value?.url) {
|
|
|
164
162
|
class="cpub-reply-input"
|
|
165
163
|
type="text"
|
|
166
164
|
placeholder="Write a reply (sent via federation)..."
|
|
165
|
+
aria-label="Write a reply"
|
|
167
166
|
@keydown.enter="handleReply"
|
|
168
167
|
/>
|
|
169
168
|
<button class="cpub-btn cpub-btn-sm cpub-btn-primary" :disabled="replying || !replyContent.trim()" @click="handleReply">
|
|
@@ -251,7 +250,7 @@ if (hub.value?.url) {
|
|
|
251
250
|
width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;
|
|
252
251
|
background: var(--surface2); border: 1px solid var(--border);
|
|
253
252
|
font-family: var(--font-mono); font-size: 10px; font-weight: 700; color: var(--text-dim);
|
|
254
|
-
|
|
253
|
+
overflow: hidden;
|
|
255
254
|
}
|
|
256
255
|
.cpub-post-avatar-img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
|
|
257
256
|
|
|
@@ -8,6 +8,7 @@ const handle = computed(() => decodeURIComponent(route.params.handle as string))
|
|
|
8
8
|
|
|
9
9
|
const { searchResult, searchLoading, searchError, searchRemoteUser, followRemoteUser, unfollowRemoteUser } = useFederation();
|
|
10
10
|
const { user } = useAuth();
|
|
11
|
+
const toast = useToast();
|
|
11
12
|
|
|
12
13
|
const content = ref<FederatedContentItem[]>([]);
|
|
13
14
|
const contentLoading = ref(false);
|
|
@@ -29,7 +30,7 @@ async function sendDm(): Promise<void> {
|
|
|
29
30
|
showDmForm.value = false;
|
|
30
31
|
setTimeout(() => { dmSent.value = false; }, 5000);
|
|
31
32
|
} catch {
|
|
32
|
-
|
|
33
|
+
toast.error('Failed to send message');
|
|
33
34
|
} finally {
|
|
34
35
|
dmSending.value = false;
|
|
35
36
|
}
|
|
@@ -107,6 +108,8 @@ function stripHtml(html: string): string {
|
|
|
107
108
|
'cpub-remote-profile__follow-btn--pending': searchResult.isFollowPending,
|
|
108
109
|
}"
|
|
109
110
|
:disabled="searchResult.isFollowPending"
|
|
111
|
+
:aria-pressed="searchResult.isFollowing"
|
|
112
|
+
:aria-label="searchResult.isFollowing ? 'Unfollow user' : searchResult.isFollowPending ? 'Follow request pending' : 'Follow user'"
|
|
110
113
|
@click="searchResult.isFollowing ? onUnfollow() : onFollow()"
|
|
111
114
|
>
|
|
112
115
|
{{ searchResult.isFollowing ? 'Following' : searchResult.isFollowPending ? 'Pending' : 'Follow' }}
|
|
@@ -128,6 +131,7 @@ function stripHtml(html: string): string {
|
|
|
128
131
|
class="cpub-remote-profile__dm-textarea"
|
|
129
132
|
placeholder="Write a message..."
|
|
130
133
|
rows="3"
|
|
134
|
+
aria-label="Direct message"
|
|
131
135
|
></textarea>
|
|
132
136
|
<div class="cpub-remote-profile__dm-actions">
|
|
133
137
|
<button class="cpub-remote-profile__dm-send" :disabled="dmSending || !dmBody.trim()" @click="sendDm">
|
|
@@ -182,7 +186,7 @@ function stripHtml(html: string): string {
|
|
|
182
186
|
color: var(--text-2);
|
|
183
187
|
padding: var(--space-8) 0;
|
|
184
188
|
}
|
|
185
|
-
.cpub-remote-profile__error { color: var(--error
|
|
189
|
+
.cpub-remote-profile__error { color: var(--error); }
|
|
186
190
|
.cpub-remote-profile__header {
|
|
187
191
|
display: flex;
|
|
188
192
|
align-items: center;
|
|
@@ -317,7 +321,7 @@ function stripHtml(html: string): string {
|
|
|
317
321
|
}
|
|
318
322
|
.cpub-remote-profile__dm-sent {
|
|
319
323
|
font-size: var(--font-size-sm);
|
|
320
|
-
color: var(--green
|
|
324
|
+
color: var(--green);
|
|
321
325
|
font-weight: 600;
|
|
322
326
|
margin-bottom: var(--space-4);
|
|
323
327
|
}
|
|
@@ -152,7 +152,7 @@ useSeoMeta({
|
|
|
152
152
|
</div>
|
|
153
153
|
|
|
154
154
|
<div v-if="editing" class="cpub-post-edit-form">
|
|
155
|
-
<textarea v-model="editContent" class="cpub-post-edit-textarea" rows="4"></textarea>
|
|
155
|
+
<textarea v-model="editContent" class="cpub-post-edit-textarea" rows="4" aria-label="Edit post content"></textarea>
|
|
156
156
|
<div class="cpub-post-edit-actions">
|
|
157
157
|
<button class="cpub-btn cpub-btn-sm cpub-btn-primary" :disabled="saving || !editContent.trim()" @click="saveEdit">
|
|
158
158
|
{{ saving ? 'Saving...' : 'Save' }}
|
|
@@ -179,7 +179,7 @@ useSeoMeta({
|
|
|
179
179
|
</div>
|
|
180
180
|
|
|
181
181
|
<div class="cpub-post-actions">
|
|
182
|
-
<button class="cpub-post-action-btn" :class="{ active: post.isLiked }" :disabled="liking || !isAuthenticated" @click="toggleLike" :
|
|
182
|
+
<button class="cpub-post-action-btn" :class="{ active: post.isLiked }" :disabled="liking || !isAuthenticated" @click="toggleLike" :aria-label="post.isLiked ? 'Unlike post' : 'Like post'" :aria-pressed="post.isLiked ?? false">
|
|
183
183
|
<i :class="post.isLiked ? 'fa-solid fa-heart' : 'fa-regular fa-heart'"></i>
|
|
184
184
|
{{ post.likeCount ?? 0 }}
|
|
185
185
|
</button>
|
|
@@ -212,7 +212,7 @@ useSeoMeta({
|
|
|
212
212
|
Replying to a comment <button class="cpub-cancel-reply" @click="replyingTo = null"><i class="fa-solid fa-xmark"></i></button>
|
|
213
213
|
</div>
|
|
214
214
|
<div class="cpub-reply-row">
|
|
215
|
-
<input v-model="replyContent" class="cpub-reply-input" type="text" placeholder="Write a reply..." @keydown.enter="handleReply" />
|
|
215
|
+
<input v-model="replyContent" class="cpub-reply-input" type="text" placeholder="Write a reply..." aria-label="Write a reply" @keydown.enter="handleReply" />
|
|
216
216
|
<button class="cpub-btn cpub-btn-sm cpub-btn-primary" :disabled="replying || !replyContent.trim()" @click="handleReply">
|
|
217
217
|
<i class="fa-solid fa-paper-plane"></i> Reply
|
|
218
218
|
</button>
|
|
@@ -20,7 +20,8 @@ export default defineEventHandler(async (event) => {
|
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
22
|
await federateDirectMessage(db, user.id, resolved.actorUri, input.body, config.instance.domain);
|
|
23
|
-
} catch {
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error('[federation-dm] Failed to deliver:', err);
|
|
24
25
|
throw createError({ statusCode: 502, statusMessage: 'Failed to deliver message to remote server' });
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -57,11 +57,16 @@ export default defineEventHandler(async (event): Promise<HubPostItem> => {
|
|
|
57
57
|
originDomain: fedContent.originDomain,
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
let post;
|
|
61
|
+
try {
|
|
62
|
+
post = await createPost(db, user.id, {
|
|
63
|
+
hubId: hub.id,
|
|
64
|
+
type: 'share',
|
|
65
|
+
content: sharePayload,
|
|
66
|
+
});
|
|
67
|
+
} catch {
|
|
68
|
+
throw createError({ statusCode: 403, statusMessage: 'You must be a hub member to share content' });
|
|
69
|
+
}
|
|
65
70
|
|
|
66
71
|
// Federate the share using the object URI of the federated content
|
|
67
72
|
if (config.features.federation && config.features.federateHubs) {
|