@threenine/nuxstr-comments 1.5.0 → 1.5.1
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 +9 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -2
- package/dist/runtime/components/CommentView.vue +3 -3
- package/dist/runtime/components/NuxstrComments.vue +2 -2
- package/dist/runtime/components/PostComment.vue +1 -1
- package/dist/runtime/components/PostReply.vue +4 -4
- package/dist/runtime/components/{CommentCommandBar.vue → ReplyButton.vue} +13 -11
- package/dist/runtime/components/ReplyView.vue +1 -1
- package/dist/runtime/composables/useComments.d.ts +6 -20
- package/dist/runtime/composables/useComments.js +18 -8
- package/dist/runtime/composables/useNuxstr.d.ts +5 -4
- package/dist/runtime/composables/useNuxstr.js +18 -12
- package/dist/runtime/composables/useReplies.d.ts +2 -2
- package/dist/runtime/composables/useReplies.js +6 -12
- package/package.json +1 -1
- /package/dist/runtime/components/{CommentCommandBar.d.vue.ts → ReplyButton.d.vue.ts} +0 -0
- /package/dist/runtime/components/{CommentCommandBar.vue.d.ts → ReplyButton.vue.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -64,12 +64,19 @@ export default defineNuxtConfig({
|
|
|
64
64
|
nuxstrComments: {
|
|
65
65
|
relays: ['wss://relay.damus.io', 'wss://relay.nostr.band'],
|
|
66
66
|
tagStrategy: 'path',
|
|
67
|
-
tagPrefix: '
|
|
67
|
+
tagPrefix: 'comment:',
|
|
68
68
|
},
|
|
69
69
|
})
|
|
70
70
|
```
|
|
71
|
+
Select your preferred relays from the list of [relays](https://nostrwat.ch/) and configure the tag strategy and tag prefix.
|
|
72
|
+
|
|
73
|
+
When a user attempts to post, they will be prompted to log in with their Nostr browser extension [NIP-07](https://github.com/nostr-protocol/nips/blob/master/07.md).
|
|
74
|
+
|
|
75
|
+
Comments are published as kind: 1111 as defined in [NIP-22](https://github.com/nostr-protocol/nips/blob/master/22.md)
|
|
76
|
+
notes tagged with a `t` tag containing the content tag (e.g., `comment:/blog/my-post`).
|
|
77
|
+
|
|
78
|
+
Replys to comments are enable, and are also published as kind: 1111, as defined in [NIP-22](https://github.com/nostr-protocol/nips/blob/master/22.md)
|
|
71
79
|
|
|
72
|
-
When a user attempts to post, they will be prompted to log in with their Nostr browser extension (NIP-07). Comments are published as kind:1 notes tagged with a `t` tag containing the content tag (e.g., `content:/blog/my-post`). Rendering of comment bodies uses @nuxt/content's ContentRendererMarkdown component so users can write markdown.
|
|
73
80
|
|
|
74
81
|
|
|
75
82
|
## Contribution
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -55,8 +55,8 @@ const module = defineNuxtModule({
|
|
|
55
55
|
filePath: resolver.resolve("./runtime/components/ScaffoldComment.vue")
|
|
56
56
|
});
|
|
57
57
|
addComponent({
|
|
58
|
-
name: "
|
|
59
|
-
filePath: resolver.resolve("./runtime/components/
|
|
58
|
+
name: "ReplyButton",
|
|
59
|
+
filePath: resolver.resolve("./runtime/components/ReplyButton.vue")
|
|
60
60
|
});
|
|
61
61
|
addComponent({
|
|
62
62
|
name: "CommentView",
|
|
@@ -7,13 +7,13 @@ const props = defineProps({
|
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
<template>
|
|
10
|
-
<div class="
|
|
10
|
+
<div class="mt-2 mb-2">
|
|
11
11
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
12
12
|
<div
|
|
13
|
-
class="mb-
|
|
13
|
+
class="mb-1"
|
|
14
14
|
v-html="marked.parse(props.content)"
|
|
15
15
|
/>
|
|
16
16
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
17
|
-
<
|
|
17
|
+
<ReplyButton :content-id="props.id" />
|
|
18
18
|
</div>
|
|
19
19
|
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { onMounted } from "vue";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import useNuxstr from "../composables/useNuxstr";
|
|
4
|
+
import useComments from "../composables/useComments";
|
|
5
5
|
const props = defineProps({
|
|
6
6
|
contentId: { type: String, required: false }
|
|
7
7
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import
|
|
2
|
+
import useReplies from "../composables/useReplies";
|
|
3
3
|
import { ref } from "vue";
|
|
4
4
|
const props = defineProps({
|
|
5
5
|
rootId: { type: String, required: true }
|
|
@@ -23,7 +23,7 @@ async function postReply(comment) {
|
|
|
23
23
|
</script>
|
|
24
24
|
|
|
25
25
|
<template>
|
|
26
|
-
<div class="text-sm text-muted-foreground mt-
|
|
26
|
+
<div class="text-sm text-muted-foreground mt-4 p-6">
|
|
27
27
|
<div class="flex gap-2">
|
|
28
28
|
<div class="flex-1">
|
|
29
29
|
<UTextarea
|
|
@@ -33,13 +33,13 @@ async function postReply(comment) {
|
|
|
33
33
|
:rows="4"
|
|
34
34
|
/>
|
|
35
35
|
</div>
|
|
36
|
-
<div class="flex flex-col justify-center items-
|
|
36
|
+
<div class="flex flex-col justify-center items-centerp-2">
|
|
37
37
|
<UButton
|
|
38
38
|
icon="mingcute:send-line"
|
|
39
39
|
size="xl"
|
|
40
40
|
color="primary"
|
|
41
41
|
variant="solid"
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
@click="postReply(content)"
|
|
44
44
|
/>
|
|
45
45
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import { onMounted, ref } from "vue";
|
|
2
|
+
import useNuxstr from "../composables/useNuxstr";
|
|
3
|
+
import useReplies from "../composables/useReplies";
|
|
4
|
+
import { computed, onMounted, ref } from "vue";
|
|
5
5
|
const props = defineProps({
|
|
6
6
|
contentId: { type: String, required: true }
|
|
7
7
|
});
|
|
@@ -14,42 +14,44 @@ function toggleReply() {
|
|
|
14
14
|
onMounted(() => {
|
|
15
15
|
subscribeReplies();
|
|
16
16
|
});
|
|
17
|
+
const showChip = computed(() => replies.value.length > 0);
|
|
17
18
|
</script>
|
|
18
19
|
|
|
19
20
|
<template>
|
|
20
21
|
<div>
|
|
21
|
-
<div class="flex items-center gap-4 mx-auto mt-
|
|
22
|
+
<div class="flex items-center gap-4 mx-auto mt-2 mb-1">
|
|
22
23
|
<u-chip
|
|
24
|
+
:show="showChip"
|
|
23
25
|
:text="replies.length"
|
|
24
26
|
size="3xl"
|
|
25
27
|
inset
|
|
26
28
|
>
|
|
27
29
|
<u-button
|
|
28
|
-
variant="
|
|
30
|
+
variant="subtle"
|
|
29
31
|
icon="mdi:message-reply-text-outline"
|
|
30
32
|
title="Reply"
|
|
31
|
-
|
|
33
|
+
color="primary"
|
|
32
34
|
class="rounded-full hover:bg-gray-900"
|
|
33
35
|
@click="toggleReply"
|
|
34
36
|
/>
|
|
35
37
|
</u-chip>
|
|
36
38
|
</div>
|
|
37
|
-
<
|
|
38
|
-
class="flex flex-col gap-2 w-48 p-
|
|
39
|
+
<u-collapsible
|
|
40
|
+
class="flex flex-col gap-2 w-48 p-4"
|
|
39
41
|
:open
|
|
40
42
|
>
|
|
41
43
|
<template #content>
|
|
42
|
-
<div>
|
|
44
|
+
<div class="space-y-4">
|
|
43
45
|
<reply-view :replies="replies" />
|
|
44
46
|
</div>
|
|
45
47
|
|
|
46
48
|
<div
|
|
47
49
|
v-if="isLoggedIn"
|
|
48
|
-
class="mt-
|
|
50
|
+
class="mt-2"
|
|
49
51
|
>
|
|
50
52
|
<post-reply :root-id="props.contentId" />
|
|
51
53
|
</div>
|
|
52
54
|
</template>
|
|
53
|
-
</
|
|
55
|
+
</u-collapsible>
|
|
54
56
|
</div>
|
|
55
57
|
</template>
|
|
@@ -1,23 +1,8 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
3
|
-
loading:
|
|
4
|
-
error:
|
|
5
|
-
comments: import("vue").
|
|
6
|
-
id: string;
|
|
7
|
-
pubkey: string;
|
|
8
|
-
created_at: number;
|
|
9
|
-
content: string;
|
|
10
|
-
profile?: {
|
|
11
|
-
pubkey: string;
|
|
12
|
-
display_name?: string | undefined;
|
|
13
|
-
about?: string | undefined;
|
|
14
|
-
image?: string | undefined;
|
|
15
|
-
nip05?: string | undefined;
|
|
16
|
-
lud06?: string | undefined;
|
|
17
|
-
lud16?: string | undefined;
|
|
18
|
-
website?: string | undefined;
|
|
19
|
-
} | undefined;
|
|
20
|
-
}[], Comment[] | {
|
|
1
|
+
import { type Ref, type UnwrapRef } from 'vue';
|
|
2
|
+
declare function useComments(customContentId?: string): {
|
|
3
|
+
loading: Ref<boolean, boolean>;
|
|
4
|
+
error: Ref<UnwrapRef<string | null>, UnwrapRef<string | null>>;
|
|
5
|
+
comments: import("vue").ComputedRef<{
|
|
21
6
|
id: string;
|
|
22
7
|
pubkey: string;
|
|
23
8
|
created_at: number;
|
|
@@ -37,3 +22,4 @@ export declare function useComments(customContentId?: string): {
|
|
|
37
22
|
subscribeComments: () => Promise<void>;
|
|
38
23
|
postComment: (comment: string) => Promise<boolean>;
|
|
39
24
|
};
|
|
25
|
+
export default useComments;
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { computed, ref } from "vue";
|
|
2
|
-
import { useRoute, useRuntimeConfig
|
|
3
|
-
import
|
|
2
|
+
import { useRequestURL, useRoute, useRuntimeConfig } from "#imports";
|
|
3
|
+
import useNuxstr from "./useNuxstr.js";
|
|
4
4
|
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
|
5
|
-
|
|
5
|
+
function useComments(customContentId) {
|
|
6
6
|
const { ndk, connect, isLoggedIn, mapComment, pubkey, fetchProfile } = useNuxstr();
|
|
7
7
|
const route = useRoute();
|
|
8
8
|
const config = useRuntimeConfig();
|
|
9
9
|
const opts = config.public?.nuxstrComments || {};
|
|
10
10
|
const loading = ref(false);
|
|
11
11
|
const error = ref(null);
|
|
12
|
-
const
|
|
12
|
+
const commentsData = ref([]);
|
|
13
|
+
const comments = computed(() => {
|
|
14
|
+
return commentsData.value.slice().sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
|
|
15
|
+
});
|
|
13
16
|
const contentId = computed(() => {
|
|
14
17
|
if (customContentId) return customContentId;
|
|
15
18
|
return route.path;
|
|
@@ -27,12 +30,18 @@ export function useComments(customContentId) {
|
|
|
27
30
|
}
|
|
28
31
|
async function subscribeComments() {
|
|
29
32
|
await connect();
|
|
30
|
-
const filter = {
|
|
31
|
-
|
|
33
|
+
const filter = {
|
|
34
|
+
kinds: [NDKKind.GenericReply],
|
|
35
|
+
["#t"]: [tagValue()],
|
|
36
|
+
limit: 100,
|
|
37
|
+
["#k"]: ["web"],
|
|
38
|
+
["#A"]: [fullUrl(contentId.value)]
|
|
39
|
+
};
|
|
40
|
+
const sub = ndk.subscribe(filter);
|
|
32
41
|
sub.on("event", async (event) => {
|
|
33
42
|
const comment = mapComment(event);
|
|
34
43
|
comment.profile = await fetchProfile(event.pubkey);
|
|
35
|
-
|
|
44
|
+
commentsData.value.push(comment);
|
|
36
45
|
});
|
|
37
46
|
}
|
|
38
47
|
async function postComment(comment) {
|
|
@@ -58,9 +67,10 @@ export function useComments(customContentId) {
|
|
|
58
67
|
// Defined NIP 73
|
|
59
68
|
["K", "web"],
|
|
60
69
|
// Defined NIP 73,
|
|
61
|
-
["p", pubkey
|
|
70
|
+
["p", pubkey ?? ""]
|
|
62
71
|
];
|
|
63
72
|
return event;
|
|
64
73
|
}
|
|
65
74
|
return { loading, error, comments, isLoggedIn, subscribeComments, postComment };
|
|
66
75
|
}
|
|
76
|
+
export default useComments;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import NDK, { type NDKEvent } from '@nostr-dev-kit/ndk';
|
|
1
|
+
import NDK, { type NDKEvent, type NDKUserProfile } from '@nostr-dev-kit/ndk';
|
|
2
2
|
import type { Comment, Profile } from '../types/index.js';
|
|
3
|
-
|
|
3
|
+
declare function useNuxstr(): {
|
|
4
4
|
readonly ndk: NDK;
|
|
5
5
|
connect: () => Promise<NDK>;
|
|
6
6
|
login: () => Promise<void>;
|
|
7
7
|
logout: () => void;
|
|
8
8
|
isLoggedIn: import("vue").ComputedRef<boolean>;
|
|
9
|
-
pubkey:
|
|
10
|
-
mapProfile: (profile: NDKUserProfile) => Profile;
|
|
9
|
+
pubkey: string | undefined;
|
|
10
|
+
mapProfile: (profile: NDKUserProfile | null) => Profile;
|
|
11
11
|
mapComment: (event: NDKEvent) => Comment;
|
|
12
12
|
fetchProfile: (pubkey: string) => Promise<Profile | undefined>;
|
|
13
13
|
};
|
|
14
|
+
export default useNuxstr;
|
|
@@ -2,19 +2,21 @@ import { computed, ref } from "vue";
|
|
|
2
2
|
import { useRuntimeConfig } from "#imports";
|
|
3
3
|
import NDK, { NDKNip07Signer } from "@nostr-dev-kit/ndk";
|
|
4
4
|
import { useToast } from "#ui/composables/useToast";
|
|
5
|
-
|
|
5
|
+
function useNuxstr() {
|
|
6
|
+
const DEFAULT_PUBKEY = "";
|
|
6
7
|
const w = globalThis;
|
|
7
8
|
if (!w.__nuxstr) {
|
|
8
9
|
w.__nuxstr = {
|
|
9
10
|
ndk: null,
|
|
10
11
|
signer: null,
|
|
11
|
-
pubkey: ref(
|
|
12
|
+
pubkey: ref(DEFAULT_PUBKEY),
|
|
12
13
|
isConnecting: ref(false),
|
|
13
14
|
isConnected: ref(false),
|
|
14
|
-
userProfile: ref(
|
|
15
|
+
userProfile: ref(void 0)
|
|
15
16
|
};
|
|
16
17
|
}
|
|
17
18
|
const state = w.__nuxstr;
|
|
19
|
+
const DEFAULT_TIMESTAMP = 0;
|
|
18
20
|
const config = useRuntimeConfig();
|
|
19
21
|
const opts = config.public?.nuxstrComments || {};
|
|
20
22
|
function initializeNDK() {
|
|
@@ -52,13 +54,14 @@ export function useNuxstr() {
|
|
|
52
54
|
async function login() {
|
|
53
55
|
if (await checkExtension()) {
|
|
54
56
|
const ndk = initializeNDK();
|
|
57
|
+
await connect();
|
|
55
58
|
const signer = new NDKNip07Signer();
|
|
56
59
|
ndk.signer = signer;
|
|
57
|
-
const
|
|
60
|
+
const account = await signer.user();
|
|
58
61
|
state.signer = signer;
|
|
59
|
-
state.pubkey.value =
|
|
60
|
-
|
|
61
|
-
const profile = await
|
|
62
|
+
state.pubkey.value = account.pubkey;
|
|
63
|
+
const user = ndk.getUser({ pubkey: account.pubkey });
|
|
64
|
+
const profile = await user.fetchProfile();
|
|
62
65
|
state.userProfile.value = mapProfile(profile);
|
|
63
66
|
}
|
|
64
67
|
}
|
|
@@ -66,14 +69,15 @@ export function useNuxstr() {
|
|
|
66
69
|
return {
|
|
67
70
|
id: event.id,
|
|
68
71
|
pubkey: event.pubkey,
|
|
69
|
-
created_at: event.created_at ||
|
|
72
|
+
created_at: event.created_at || DEFAULT_TIMESTAMP,
|
|
70
73
|
content: event.content,
|
|
71
|
-
profile:
|
|
74
|
+
profile: void 0
|
|
72
75
|
};
|
|
73
76
|
}
|
|
74
77
|
async function fetchProfile(pubkey) {
|
|
75
78
|
try {
|
|
76
|
-
const
|
|
79
|
+
const ndk = initializeNDK();
|
|
80
|
+
const user = ndk.getUser({ pubkey });
|
|
77
81
|
const profile = await user.fetchProfile();
|
|
78
82
|
return mapProfile(profile);
|
|
79
83
|
} catch (error) {
|
|
@@ -82,6 +86,7 @@ export function useNuxstr() {
|
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
88
|
function mapProfile(profile) {
|
|
89
|
+
if (profile === null) return {};
|
|
85
90
|
return {
|
|
86
91
|
display_name: profile.displayName,
|
|
87
92
|
about: profile.about,
|
|
@@ -94,7 +99,7 @@ export function useNuxstr() {
|
|
|
94
99
|
}
|
|
95
100
|
function logout() {
|
|
96
101
|
state.signer = null;
|
|
97
|
-
state.pubkey.value =
|
|
102
|
+
state.pubkey.value = DEFAULT_PUBKEY;
|
|
98
103
|
}
|
|
99
104
|
return {
|
|
100
105
|
get ndk() {
|
|
@@ -104,9 +109,10 @@ export function useNuxstr() {
|
|
|
104
109
|
login,
|
|
105
110
|
logout,
|
|
106
111
|
isLoggedIn,
|
|
107
|
-
pubkey: state.pubkey,
|
|
112
|
+
pubkey: state.pubkey.value,
|
|
108
113
|
mapProfile,
|
|
109
114
|
mapComment,
|
|
110
115
|
fetchProfile
|
|
111
116
|
};
|
|
112
117
|
}
|
|
118
|
+
export default useNuxstr;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
declare function useReplies(rootCommentId: string): {
|
|
2
2
|
subscribeReplies: () => Promise<void>;
|
|
3
3
|
replies: import("vue").ComputedRef<{
|
|
4
4
|
id: string;
|
|
@@ -17,5 +17,5 @@ export declare function useReplies(rootCommentId?: string): {
|
|
|
17
17
|
} | undefined;
|
|
18
18
|
}[]>;
|
|
19
19
|
reply: (comment: string) => Promise<boolean>;
|
|
20
|
-
count: import("vue").ComputedRef<Promise<string>>;
|
|
21
20
|
};
|
|
21
|
+
export default useReplies;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { computed, ref } from "vue";
|
|
2
|
-
import
|
|
2
|
+
import useNuxstr from "./useNuxstr.js";
|
|
3
3
|
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
|
4
|
-
|
|
4
|
+
function useReplies(rootCommentId) {
|
|
5
5
|
const { ndk, connect, mapComment, pubkey, fetchProfile } = useNuxstr();
|
|
6
6
|
const repliesData = ref([]);
|
|
7
7
|
const error = ref(null);
|
|
@@ -11,7 +11,7 @@ export function useReplies(rootCommentId) {
|
|
|
11
11
|
async function subscribeReplies() {
|
|
12
12
|
await connect();
|
|
13
13
|
const filter = { kinds: [NDKKind.GenericReply], limit: 100, ["#e"]: [rootCommentId] };
|
|
14
|
-
const sub =
|
|
14
|
+
const sub = ndk.subscribe(filter);
|
|
15
15
|
sub.on("event", async (event) => {
|
|
16
16
|
const reply2 = mapComment(event);
|
|
17
17
|
reply2.profile = await fetchProfile(event.pubkey);
|
|
@@ -33,16 +33,10 @@ export function useReplies(rootCommentId) {
|
|
|
33
33
|
["e", `${rootCommentId}`],
|
|
34
34
|
["k", `${NDKKind.GenericReply}`],
|
|
35
35
|
// The parent kind
|
|
36
|
-
["p", pubkey
|
|
36
|
+
["p", pubkey ?? ""]
|
|
37
37
|
];
|
|
38
38
|
return event;
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
await connect();
|
|
42
|
-
const filter = { kinds: [NDKKind.GenericReply], limit: 100, ["#e"]: [rootCommentId] };
|
|
43
|
-
const events = await ndk.fetchEvents(filter);
|
|
44
|
-
console.log(events);
|
|
45
|
-
return `${Array.from(events).length}`;
|
|
46
|
-
});
|
|
47
|
-
return { subscribeReplies, replies, reply, count };
|
|
40
|
+
return { subscribeReplies, replies, reply };
|
|
48
41
|
}
|
|
42
|
+
export default useReplies;
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|