@threenine/nuxstr-comments 1.2.2 → 1.3.0

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 (31) hide show
  1. package/README.md +14 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +23 -2
  4. package/dist/runtime/components/CommentAuthor.d.vue.ts +6 -0
  5. package/dist/runtime/components/CommentAuthor.vue +27 -0
  6. package/dist/runtime/components/CommentAuthor.vue.d.ts +6 -0
  7. package/dist/runtime/components/CommentCommandBar.d.vue.ts +5 -0
  8. package/dist/runtime/components/CommentCommandBar.vue +55 -0
  9. package/dist/runtime/components/CommentCommandBar.vue.d.ts +5 -0
  10. package/dist/runtime/components/CommentView.d.vue.ts +6 -0
  11. package/dist/runtime/components/CommentView.vue +19 -0
  12. package/dist/runtime/components/CommentView.vue.d.ts +6 -0
  13. package/dist/runtime/components/NuxstrComments.d.vue.ts +5 -0
  14. package/dist/runtime/components/NuxstrComments.vue +20 -35
  15. package/dist/runtime/components/PostComment.d.vue.ts +5 -0
  16. package/dist/runtime/components/PostComment.vue +23 -18
  17. package/dist/runtime/components/PostReply.d.vue.ts +5 -0
  18. package/dist/runtime/components/PostReply.vue +47 -0
  19. package/dist/runtime/components/PostReply.vue.d.ts +5 -0
  20. package/dist/runtime/components/ReplyView.d.vue.ts +5 -0
  21. package/dist/runtime/components/ReplyView.vue +23 -0
  22. package/dist/runtime/components/ReplyView.vue.d.ts +5 -0
  23. package/dist/runtime/components/ScaffoldComment.d.vue.ts +2 -0
  24. package/dist/runtime/components/ScaffoldComment.vue +11 -6
  25. package/dist/runtime/composables/{useNuxstrComments.d.ts → useComments.d.ts} +1 -1
  26. package/dist/runtime/composables/{useNuxstrComments.js → useComments.js} +26 -20
  27. package/dist/runtime/composables/useNuxstr.d.ts +2 -1
  28. package/dist/runtime/composables/useNuxstr.js +17 -8
  29. package/dist/runtime/composables/useReplies.d.ts +21 -0
  30. package/dist/runtime/composables/useReplies.js +48 -0
  31. package/package.json +5 -5
package/README.md CHANGED
@@ -14,7 +14,7 @@ Find and replace all on all files (CMD+SHIFT+F):
14
14
  [![License][license-src]][license-href]
15
15
  [![Nuxt][nuxt-src]][nuxt-href]
16
16
 
17
- Nuxstr Comments for doing amazing things.
17
+ Enable [nostr protocol](https://nostr.com/) based comment system on your Nuxt 4 based applications.
18
18
 
19
19
  - [✨  Release Notes](/CHANGELOG.md)
20
20
  <!-- - [🏀 Online playground](https://stackblitz.com/github/your-org/@threenine/nuxstr-comments?file=playground%2Fapp.vue) -->
@@ -103,6 +103,19 @@ When a user attempts to post, they will be prompted to log in with their Nostr b
103
103
 
104
104
  </details>
105
105
 
106
+ ## Support
107
+ ⚡️ lightning address:
108
+
109
+ ```
110
+ threenine@getalby.com
111
+ ```
112
+
113
+ <br/>
114
+ <div align="center">
115
+ <a href="https://www.buymeacoffee.com/xbhtjcric" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
116
+
117
+ </div>
118
+
106
119
 
107
120
  <!-- Badges -->
108
121
  [npm-version-src]: https://img.shields.io/npm/v/@threenine/nuxstr-comments/latest.svg?style=flat&colorA=020420&colorB=00DC82
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@threenine/nuxstr-comments",
3
3
  "configKey": "nuxstrComments",
4
- "version": "1.2.2",
4
+ "version": "1.3.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -8,7 +8,7 @@ const module = defineNuxtModule({
8
8
  },
9
9
  // Default configuration options of the Nuxt module
10
10
  defaults: {
11
- relays: ["wss://relay.damus.io", "wss://relay.primal.net", "wss://a.nos.lol", "wss://freelay.sovbit.host", "wss://nos.lol", "wss://nostr.dodge.me.uk"],
11
+ relays: ["wss://relay.damus.io", "wss://purplepag.es/"],
12
12
  tagStrategy: "path",
13
13
  tagPrefix: "comment:"
14
14
  },
@@ -39,7 +39,8 @@ const module = defineNuxtModule({
39
39
  addPlugin(resolver.resolve("./runtime/plugin"));
40
40
  addImports([
41
41
  { name: "useNuxstr", as: "useNuxstr", from: resolver.resolve("./runtime/composables/useNuxstr") },
42
- { name: "useNuxstrComments", as: "useNuxstrComments", from: resolver.resolve("./runtime/composables/useNuxstrComments") }
42
+ { name: "useComments", as: "useComments", from: resolver.resolve("./runtime/composables/useComments") },
43
+ { name: "useReplies", as: "useReplies", from: resolver.resolve("./runtime/composables/useReplies") }
43
44
  ]);
44
45
  addComponent({
45
46
  name: "NuxstrComments",
@@ -53,6 +54,26 @@ const module = defineNuxtModule({
53
54
  name: "ScaffoldComment",
54
55
  filePath: resolver.resolve("./runtime/components/ScaffoldComment.vue")
55
56
  });
57
+ addComponent({
58
+ name: "CommentCommandBar",
59
+ filePath: resolver.resolve("./runtime/components/CommentCommandBar.vue")
60
+ });
61
+ addComponent({
62
+ name: "CommentView",
63
+ filePath: resolver.resolve("./runtime/components/CommentView.vue")
64
+ });
65
+ addComponent({
66
+ name: "CommentAuthor",
67
+ filePath: resolver.resolve("./runtime/components/CommentAuthor.vue")
68
+ });
69
+ addComponent({
70
+ name: "PostReply",
71
+ filePath: resolver.resolve("./runtime/components/PostReply.vue")
72
+ });
73
+ addComponent({
74
+ name: "ReplyView",
75
+ filePath: resolver.resolve("./runtime/components/ReplyView.vue")
76
+ });
56
77
  }
57
78
  });
58
79
 
@@ -0,0 +1,6 @@
1
+ type __VLS_Props = {
2
+ profile: Profile;
3
+ createdAt: number;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ export default _default;
@@ -0,0 +1,27 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ profile: { type: null, required: true },
4
+ createdAt: { type: Number, required: true }
5
+ });
6
+ </script>
7
+
8
+ <template>
9
+ <div class="flex items-center gap-3">
10
+ <div
11
+ v-if="props.profile.image"
12
+ class="flex-shrink-0"
13
+ >
14
+ <img
15
+ :src="props.profile.image"
16
+ :alt="props.profile.name || props.profile.display_name || 'User avatar'"
17
+ class="w-6 h-6 rounded-full object-cover"
18
+ >
19
+ </div>
20
+ <div class="flex-1 min-w-0 mb-3">
21
+ <div class="truncate">
22
+ {{ props.profile.display_name || props.profile.name || `${props.profile.pubkey.slice(0, 8)}\u2026` }}
23
+ <span class="text-xs">{{ new Date(props.createdAt * 1e3).toLocaleString() }}</span>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </template>
@@ -0,0 +1,6 @@
1
+ type __VLS_Props = {
2
+ profile: Profile;
3
+ createdAt: number;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ export default _default;
@@ -0,0 +1,5 @@
1
+ type __VLS_Props = {
2
+ contentId: string;
3
+ };
4
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ export default _default;
@@ -0,0 +1,55 @@
1
+ <script setup>
2
+ import { useNuxstr } from "../composables/useNuxstr";
3
+ import { useReplies } from "../composables/useReplies";
4
+ import { onMounted } from "vue";
5
+ const props = defineProps({
6
+ contentId: { type: String, required: true }
7
+ });
8
+ const { replies, subscribeReplies } = useReplies(props.contentId);
9
+ const { isLoggedIn } = useNuxstr();
10
+ const open = ref(false);
11
+ function toggleReply() {
12
+ open.value = !open.value;
13
+ }
14
+ onMounted(() => {
15
+ subscribeReplies();
16
+ });
17
+ </script>
18
+
19
+ <template>
20
+ <div>
21
+ <div class="flex items-center gap-4 mx-auto mt-4 mb-4">
22
+ <u-chip
23
+ :text="replies.length"
24
+ size="3xl"
25
+ inset
26
+ >
27
+ <u-button
28
+ variant="ghost"
29
+ icon="mdi:message-reply-text-outline"
30
+ title="Reply"
31
+ square
32
+ class="rounded-full hover:bg-gray-900"
33
+ @click="toggleReply"
34
+ />
35
+ </u-chip>
36
+ </div>
37
+ <UCollapsible
38
+ class="flex flex-col gap-2 w-48 p-16"
39
+ :open
40
+ >
41
+ <template #content>
42
+ <div>
43
+ <reply-view :replies="replies" />
44
+ </div>
45
+
46
+ <div
47
+ v-if="isLoggedIn"
48
+ class="mt-4"
49
+ >
50
+ <post-reply :root-id="props.contentId" />
51
+ </div>
52
+ </template>
53
+ </UCollapsible>
54
+ </div>
55
+ </template>
@@ -0,0 +1,5 @@
1
+ type __VLS_Props = {
2
+ contentId: string;
3
+ };
4
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ export default _default;
@@ -0,0 +1,6 @@
1
+ type __VLS_Props = {
2
+ content: string;
3
+ id?: string;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ export default _default;
@@ -0,0 +1,19 @@
1
+ <script setup>
2
+ import { marked } from "marked";
3
+ const props = defineProps({
4
+ content: { type: String, required: true },
5
+ id: { type: String, required: false }
6
+ });
7
+ </script>
8
+
9
+ <template>
10
+ <div class="prose prose-sm prose-invert mt-2 mb-2">
11
+ <!-- eslint-disable-next-line vue/no-v-html -->
12
+ <div
13
+ class="mb-4"
14
+ v-html="marked.parse(props.content)"
15
+ />
16
+ <!-- eslint-disable-next-line vue/no-v-html -->
17
+ <comment-command-bar :content-id="props.id" />
18
+ </div>
19
+ </template>
@@ -0,0 +1,6 @@
1
+ type __VLS_Props = {
2
+ content: string;
3
+ id?: string;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ export default _default;
@@ -0,0 +1,5 @@
1
+ type __VLS_Props = {
2
+ contentId?: string;
3
+ };
4
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ export default _default;
@@ -1,13 +1,12 @@
1
1
  <script setup>
2
2
  import { onMounted } from "vue";
3
3
  import { useNuxstr } from "../composables/useNuxstr";
4
- import { useNuxstrComments } from "../composables/useNuxstrComments";
5
- import { marked } from "marked";
4
+ import { useComments } from "../composables/useComments";
6
5
  const props = defineProps({
7
6
  contentId: { type: String, required: false }
8
7
  });
9
8
  const { login, isLoggedIn } = useNuxstr();
10
- const { comments, subscribeComments, loading } = useNuxstrComments(props.contentId);
9
+ const { comments, subscribeComments, loading } = useComments(props.contentId);
11
10
  onMounted(() => {
12
11
  subscribeComments();
13
12
  });
@@ -16,9 +15,9 @@ onMounted(() => {
16
15
  <template>
17
16
  <div class="nuxstr-comments space-y-4">
18
17
  <div class="flex items-center justify-between">
19
- <h3 class="text-lg font-semibold">
18
+ <span class="text-lg font-semibold text-primary">
20
19
  Comments
21
- </h3>
20
+ </span>
22
21
 
23
22
  <div
24
23
  v-if="!isLoggedIn"
@@ -30,7 +29,7 @@ onMounted(() => {
30
29
  leading-icon="game-icons:ostrich"
31
30
  @click="login"
32
31
  >
33
- Login
32
+ Sign in
34
33
  </UButton>
35
34
  </div>
36
35
  </div>
@@ -47,41 +46,30 @@ onMounted(() => {
47
46
  <div
48
47
  v-if="loading"
49
48
  >
50
- <ScaffoldComment />
49
+ <scaffold-comment />
51
50
  </div>
52
51
 
53
52
  <div
54
53
  v-else
55
54
  class="space-y-6"
56
55
  >
56
+ <div v-if="comments.length === 0">
57
+ <scaffold-comment />
58
+ </div>
57
59
  <div
58
60
  v-for="c in comments"
61
+ v-else
59
62
  :key="c.id"
60
- class="rounded border p-3 mt-2 mb-2"
63
+ class="rounded border border-gray-900 p-3 mt-2 mb-2"
61
64
  >
62
- <div class="flex items-center gap-3 mb-3 mt-2">
63
- <div
64
- v-if="c.profile?.image"
65
- class="flex-shrink-0"
66
- >
67
- <UAvatar
68
- :src="c.profile.image"
69
- :alt="c.profile.name || c.profile.display_name || 'User avatar'"
70
- class="w-8 h-8 rounded-full object-cover"
71
- />
72
- </div>
73
- <div class="flex-1 min-w-0">
74
- <div class="truncate">
75
- {{ c.profile?.display_name || c.profile?.name || `${c.pubkey.slice(0, 8)}\u2026` }}
76
- <span class="text-xs">{{ new Date(c.created_at * 1e3).toLocaleString() }}</span>
77
- </div>
78
- </div>
79
- </div>
80
- <div class="prose prose-sm prose-invert mt-2 mb-2">
81
- <!-- eslint-disable-next-line vue/no-v-html -->
82
- <div v-html="marked.parse(c.content)" />
83
- <!-- eslint-disable-next-line vue/no-v-html -->
84
- </div>
65
+ <comment-author
66
+ :profile="c.profile"
67
+ :created-at="c.created_at"
68
+ />
69
+ <comment-view
70
+ :id="c.id"
71
+ :content="c.content"
72
+ />
85
73
  </div>
86
74
 
87
75
  <div
@@ -91,9 +79,6 @@ onMounted(() => {
91
79
  </div>
92
80
  </div>
93
81
  </ClientOnly>
82
+ <client-only />
94
83
  </div>
95
84
  </template>
96
-
97
- <style scoped>
98
- .nuxstr-comments :deep(pre){white-space:pre-wrap}
99
- </style>
@@ -0,0 +1,5 @@
1
+ type __VLS_Props = {
2
+ contentId?: string;
3
+ };
4
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ export default _default;
@@ -1,11 +1,11 @@
1
1
  <script setup>
2
2
  import { ref } from "vue";
3
- import { useNuxstrComments } from "../composables/useNuxstrComments";
3
+ import { useComments } from "../composables/useComments";
4
4
  const props = defineProps({
5
5
  contentId: { type: String, required: false }
6
6
  });
7
7
  const EMPTY_COMMENT = "";
8
- const { postComment } = useNuxstrComments(props.contentId);
8
+ const { postComment } = useComments(props.contentId);
9
9
  const comment = ref(EMPTY_COMMENT);
10
10
  function isValidComment(commentText) {
11
11
  return commentText.trim().length > 0;
@@ -23,22 +23,27 @@ async function handlePost() {
23
23
  </script>
24
24
 
25
25
  <template>
26
- <div class="text-sm text-muted-foreground border border-green mt-16">
27
- <UTextarea
28
- v-model="comment"
29
- class="w-full"
30
- placeholder="Write a comment ...."
31
- :rows="4"
32
- />
33
- <div class="flex justify-end">
34
- <UButton
35
- color="primary"
36
- variant="solid"
37
- :disabled="!comment.trim()"
38
- @click="handlePost"
39
- >
40
- Post Comment
41
- </UButton>
26
+ <div class="text-sm text-muted-foreground border border-green mt-4 p-6">
27
+ <div class="flex gap-2">
28
+ <div class="flex-1">
29
+ <UTextarea
30
+ v-model="comment"
31
+ class="w-full mb-4"
32
+ placeholder="Write a comment ...."
33
+ :rows="4"
34
+ />
35
+ </div>
36
+ <div class="flex flex-col justify-center items-center p-2">
37
+ <UButton
38
+ icon="mingcute:send-line"
39
+ color="primary"
40
+ variant="solid"
41
+ :disabled="!comment.trim()"
42
+ class=""
43
+ size="xl"
44
+ @click="handlePost"
45
+ />
46
+ </div>
42
47
  </div>
43
48
  </div>
44
49
  </template>
@@ -0,0 +1,5 @@
1
+ type __VLS_Props = {
2
+ rootId: string;
3
+ };
4
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ export default _default;
@@ -0,0 +1,47 @@
1
+ <script setup>
2
+ import { useReplies } from "../composables/useReplies";
3
+ const props = defineProps({
4
+ rootId: { type: String, required: true }
5
+ });
6
+ const { reply } = useReplies(props.rootId);
7
+ const EMPTY_COMMENT = "";
8
+ const content = ref(EMPTY_COMMENT);
9
+ function isValidComment(commentText) {
10
+ return commentText.trim().length > 0;
11
+ }
12
+ function clearComment() {
13
+ content.value = EMPTY_COMMENT;
14
+ }
15
+ async function postReply(comment) {
16
+ if (!isValidComment(comment)) return;
17
+ const wasPosted = await reply(comment);
18
+ if (wasPosted) {
19
+ clearComment();
20
+ }
21
+ }
22
+ </script>
23
+
24
+ <template>
25
+ <div class="text-sm text-muted-foreground mt-16 p-6">
26
+ <div class="flex gap-2">
27
+ <div class="flex-1">
28
+ <UTextarea
29
+ v-model="content"
30
+ class="w-full mb-4 rounded-xl"
31
+ placeholder="Write a reply to this comment ...."
32
+ :rows="4"
33
+ />
34
+ </div>
35
+ <div class="flex flex-col justify-center items-center p-2">
36
+ <UButton
37
+ icon="mingcute:send-line"
38
+ size="xl"
39
+ color="primary"
40
+ variant="solid"
41
+ class="mb-4 mr-2"
42
+ @click="postReply(content)"
43
+ />
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </template>
@@ -0,0 +1,5 @@
1
+ type __VLS_Props = {
2
+ rootId: string;
3
+ };
4
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ export default _default;
@@ -0,0 +1,5 @@
1
+ type __VLS_Props = {
2
+ replies: Comment[];
3
+ };
4
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ export default _default;
@@ -0,0 +1,23 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ replies: { type: Array, required: true }
4
+ });
5
+ </script>
6
+
7
+ <template>
8
+ <div class="px-10 py-4">
9
+ <div
10
+ v-for="reply in props.replies"
11
+ :key="reply.id"
12
+ class="rounded border border-gray-900 p-3 mt-2 mb-2"
13
+ >
14
+ <div>
15
+ <comment-author
16
+ :profile="reply.profile"
17
+ :created-at="reply.created_at"
18
+ />
19
+ <p>{{ reply.content }}</p>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </template>
@@ -0,0 +1,5 @@
1
+ type __VLS_Props = {
2
+ replies: Comment[];
3
+ };
4
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -3,12 +3,17 @@
3
3
  </script>
4
4
 
5
5
  <template>
6
- <div class="flex items-center gap-4">
7
- <USkeleton class="h-12 w-12 rounded-full animate-pulse rounded-md bg-elevated" />
8
-
9
- <div class="grid gap-2">
10
- <USkeleton class="h-4 w-[500px]" />
11
- <USkeleton class="h-4 w-[500px]" />
6
+ <div>
7
+ <p class="text-xs">
8
+ No comments available
9
+ </p>
10
+ <div class="rounded border p-3 mt-2 mb-2">
11
+ <div class="flex gap-2 mb-3 items-center">
12
+ <span><USkeleton class="h-4 w-5 rounded-full" /></span><USkeleton class="h-4" />
13
+ </div>
14
+ <div class="mt-3">
15
+ <USkeleton class="h-4" />
16
+ </div>
12
17
  </div>
13
18
  </div>
14
19
  </template>
@@ -1,5 +1,5 @@
1
1
  import type { Comment } from '~/src/runtime/types';
2
- export declare function useNuxstrComments(customContentId?: string): {
2
+ export declare function useComments(customContentId?: string): {
3
3
  loading: import("vue").Ref<boolean, boolean>;
4
4
  error: import("vue").Ref<string | null, string | null>;
5
5
  comments: import("vue").Ref<{
@@ -2,8 +2,8 @@ import { computed, ref } from "vue";
2
2
  import { useRoute, useRuntimeConfig, useRequestURL } from "#imports";
3
3
  import { useNuxstr } from "./useNuxstr.js";
4
4
  import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
5
- export function useNuxstrComments(customContentId) {
6
- const { ndk, connect, isLoggedIn, mapProfile, mapComment } = useNuxstr();
5
+ export function useComments(customContentId) {
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 || {};
@@ -22,19 +22,12 @@ export function useNuxstrComments(customContentId) {
22
22
  const url = useRequestURL();
23
23
  return `${url.protocol}//${url.host}`;
24
24
  }
25
- async function fetchProfile(pubkey) {
26
- try {
27
- const user = ndk.getUser({ pubkey });
28
- const profile = await user.fetchProfile();
29
- return mapProfile(profile);
30
- } catch (error2) {
31
- console.error("Failed to fetch profile for", pubkey, error2);
32
- return void 0;
33
- }
25
+ function fullUrl(path) {
26
+ return `${siteUrl()}${path}`;
34
27
  }
35
28
  async function subscribeComments() {
36
29
  await connect();
37
- const filter = { kinds: [NDKKind.GenericReply], ["#t"]: [tagValue()], limit: 100, ["#k"]: [siteUrl()] };
30
+ const filter = { kinds: [NDKKind.GenericReply], ["#t"]: [tagValue()], limit: 100, ["#k"]: ["web"], ["#A"]: [fullUrl(contentId.value)] };
38
31
  const sub = await ndk.subscribe(filter);
39
32
  sub.on("event", async (event) => {
40
33
  const comment = mapComment(event);
@@ -44,17 +37,30 @@ export function useNuxstrComments(customContentId) {
44
37
  }
45
38
  async function postComment(comment) {
46
39
  await connect();
47
- const e = new NDKEvent(ndk);
48
- e.kind = NDKKind.GenericReply;
49
- e.content = comment;
50
- e.tags = [
51
- ["t", tagValue()],
52
- ["k", siteUrl()]
53
- ];
54
- return await e.publish().then(() => true).catch((err) => {
40
+ const ndkEvent = await createCommentEvent(comment);
41
+ return await ndkEvent.publish().then(() => true).catch((err) => {
55
42
  error.value = err?.message || String(err);
56
43
  return false;
57
44
  });
58
45
  }
46
+ async function createCommentEvent(comment) {
47
+ const event = new NDKEvent(ndk);
48
+ event.kind = NDKKind.GenericReply;
49
+ event.content = comment;
50
+ event.tags = [
51
+ ["A", fullUrl(contentId.value)],
52
+ ["a", fullUrl(contentId.value)],
53
+ ["I", fullUrl(contentId.value)],
54
+ //
55
+ ["i", fullUrl(contentId.value)],
56
+ ["t", tagValue()],
57
+ ["k", "web"],
58
+ // Defined NIP 73
59
+ ["K", "web"],
60
+ // Defined NIP 73,
61
+ ["p", pubkey.value]
62
+ ];
63
+ return event;
64
+ }
59
65
  return { loading, error, comments, isLoggedIn, subscribeComments, postComment };
60
66
  }
@@ -1,5 +1,5 @@
1
1
  import NDK, { type NDKEvent } from '@nostr-dev-kit/ndk';
2
- import type { Profile, Comment } from '../types/index.js';
2
+ import type { Comment, Profile } from '../types/index.js';
3
3
  export declare function useNuxstr(): {
4
4
  readonly ndk: NDK;
5
5
  connect: () => Promise<NDK>;
@@ -9,4 +9,5 @@ export declare function useNuxstr(): {
9
9
  pubkey: import("vue").Ref<string | null | undefined, string | null | undefined>;
10
10
  mapProfile: (profile: NDKUserProfile) => Profile;
11
11
  mapComment: (event: NDKEvent) => Comment;
12
+ fetchProfile: (pubkey: string) => Promise<Profile | undefined>;
12
13
  };
@@ -19,7 +19,9 @@ export function useNuxstr() {
19
19
  const opts = config.public?.nuxstrComments || {};
20
20
  function initializeNDK() {
21
21
  if (!state.ndk) {
22
- state.ndk = new NDK({ explicitRelayUrls: opts.relays || [] });
22
+ state.ndk = new NDK({
23
+ explicitRelayUrls: opts.relays || []
24
+ });
23
25
  }
24
26
  return state.ndk;
25
27
  }
@@ -56,12 +58,8 @@ export function useNuxstr() {
56
58
  state.signer = signer;
57
59
  state.pubkey.value = user.pubkey;
58
60
  await connect();
59
- const profile = ndk.getUser({ pubkey: user.pubkey });
60
- profile.fetchProfile().then((profile2) => {
61
- state.userProile = mapProfile(profile2);
62
- }).catch((err) => {
63
- console.error("Failed to fetch profile", err);
64
- });
61
+ const profile = await ndk.getUser({ pubkey: user.pubkey });
62
+ state.userProfile.value = mapProfile(profile);
65
63
  }
66
64
  }
67
65
  function mapComment(event) {
@@ -73,6 +71,16 @@ export function useNuxstr() {
73
71
  profile: null
74
72
  };
75
73
  }
74
+ async function fetchProfile(pubkey) {
75
+ try {
76
+ const user = state.ndk.getUser({ pubkey });
77
+ const profile = await user.fetchProfile();
78
+ return mapProfile(profile);
79
+ } catch (error) {
80
+ console.error("Failed to fetch profile for", pubkey, error);
81
+ return void 0;
82
+ }
83
+ }
76
84
  function mapProfile(profile) {
77
85
  return {
78
86
  display_name: profile.displayName,
@@ -98,6 +106,7 @@ export function useNuxstr() {
98
106
  isLoggedIn,
99
107
  pubkey: state.pubkey,
100
108
  mapProfile,
101
- mapComment
109
+ mapComment,
110
+ fetchProfile
102
111
  };
103
112
  }
@@ -0,0 +1,21 @@
1
+ export declare function useReplies(rootCommentId?: string): {
2
+ subscribeReplies: () => Promise<void>;
3
+ replies: import("vue").ComputedRef<{
4
+ id: string;
5
+ pubkey: string;
6
+ created_at: number;
7
+ content: string;
8
+ profile?: {
9
+ pubkey: string;
10
+ display_name?: string | undefined;
11
+ about?: string | undefined;
12
+ image?: string | undefined;
13
+ nip05?: string | undefined;
14
+ lud06?: string | undefined;
15
+ lud16?: string | undefined;
16
+ website?: string | undefined;
17
+ } | undefined;
18
+ }[]>;
19
+ reply: (comment: string) => Promise<boolean>;
20
+ count: import("vue").ComputedRef<Promise<string>>;
21
+ };
@@ -0,0 +1,48 @@
1
+ import { computed, ref } from "vue";
2
+ import { useNuxstr } from "./useNuxstr.js";
3
+ import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
4
+ export function useReplies(rootCommentId) {
5
+ const { ndk, connect, mapComment, pubkey, fetchProfile } = useNuxstr();
6
+ const repliesData = ref([]);
7
+ const error = ref(null);
8
+ const replies = computed(() => {
9
+ return repliesData.value.slice().sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
10
+ });
11
+ async function subscribeReplies() {
12
+ await connect();
13
+ const filter = { kinds: [NDKKind.GenericReply], limit: 100, ["#e"]: [rootCommentId] };
14
+ const sub = await ndk.subscribe(filter);
15
+ sub.on("event", async (event) => {
16
+ const reply2 = mapComment(event);
17
+ reply2.profile = await fetchProfile(event.pubkey);
18
+ repliesData.value.push(reply2);
19
+ });
20
+ }
21
+ async function reply(comment) {
22
+ const ndkEvent = await createReplyEvent(comment);
23
+ return await ndkEvent.publish().then(() => true).catch((err) => {
24
+ error.value = err?.message || String(err);
25
+ return false;
26
+ });
27
+ }
28
+ async function createReplyEvent(comment) {
29
+ const event = new NDKEvent(ndk);
30
+ event.kind = NDKKind.GenericReply;
31
+ event.content = comment;
32
+ event.tags = [
33
+ ["e", `${rootCommentId}`],
34
+ ["k", `${NDKKind.GenericReply}`],
35
+ // The parent kind
36
+ ["p", pubkey.value]
37
+ ];
38
+ return event;
39
+ }
40
+ const count = computed(async () => {
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 };
48
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@threenine/nuxstr-comments",
3
- "version": "1.2.2",
4
- "description": "Nuxstr Comments",
3
+ "version": "1.3.0",
4
+ "description": "Nuxt module to enable Nostr Comments on Nuxt 4 based websites",
5
5
  "repository": "threenine/nuxstr-comments",
6
6
  "license": "MIT",
7
7
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  "@nuxt/eslint": "1.9.0",
34
34
  "@nuxt/eslint-config": "^1.9.0",
35
35
  "@nuxt/module-builder": "^1.0.2",
36
- "@nuxt/schema": "^4.0.3",
36
+ "@nuxt/schema": "^4.1.1",
37
37
  "@nuxt/scripts": "0.11.10",
38
38
  "@nuxt/test-utils": "^3.19.2",
39
39
  "@nuxt/ui": "^3.3.3",
@@ -41,9 +41,9 @@
41
41
  "@testing-library/vue": "^8.1.0",
42
42
  "@unhead/vue": "^2.0.14",
43
43
  "changelogen": "^0.6.2",
44
- "eslint": "^9.33.0",
44
+ "eslint": "^9.35.0",
45
45
  "jsdom": "^26.1.0",
46
- "nuxt": "^4.0.3",
46
+ "nuxt": "^4.1.2",
47
47
  "typescript": "~5.9.2",
48
48
  "vitest": "^3.2.4",
49
49
  "vue-tsc": "^3.0.6",