@ewanc26/svelte-standard-site 0.2.3 → 0.2.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 (69) hide show
  1. package/dist/components/ActionBar.svelte +85 -0
  2. package/dist/components/ActionBar.svelte.d.ts +13 -0
  3. package/dist/components/Avatar.svelte +104 -0
  4. package/dist/components/Avatar.svelte.d.ts +19 -0
  5. package/dist/components/Comment.svelte +172 -0
  6. package/dist/components/Comment.svelte.d.ts +22 -0
  7. package/dist/components/CommentsSection.svelte +89 -0
  8. package/dist/components/DocumentCard.svelte +126 -56
  9. package/dist/components/DocumentCard.svelte.d.ts +51 -0
  10. package/dist/components/Footnotes.svelte +72 -0
  11. package/dist/components/Footnotes.svelte.d.ts +13 -0
  12. package/dist/components/RecommendButton.svelte +153 -0
  13. package/dist/components/RecommendButton.svelte.d.ts +17 -0
  14. package/dist/components/ThemeProvider.svelte +92 -0
  15. package/dist/components/ThemeProvider.svelte.d.ts +13 -0
  16. package/dist/components/Toast.svelte +177 -0
  17. package/dist/components/Toast.svelte.d.ts +32 -0
  18. package/dist/components/Watermark.svelte +100 -0
  19. package/dist/components/Watermark.svelte.d.ts +17 -0
  20. package/dist/components/common/ThemedCard.svelte +15 -15
  21. package/dist/components/common/ThemedCard.svelte.d.ts +5 -0
  22. package/dist/components/document/BlockRenderer.svelte +3 -0
  23. package/dist/components/document/DocumentRenderer.svelte +41 -1
  24. package/dist/components/document/RichText.svelte +87 -2
  25. package/dist/components/document/RichText.svelte.d.ts +2 -0
  26. package/dist/components/document/blocks/OrderedListBlock.svelte +152 -0
  27. package/dist/components/document/blocks/UnorderedListBlock.svelte +1 -1
  28. package/dist/components/index.d.ts +28 -0
  29. package/dist/components/index.js +30 -0
  30. package/dist/index.d.ts +5 -4
  31. package/dist/index.js +6 -4
  32. package/dist/publisher.d.ts +73 -0
  33. package/dist/publisher.js +185 -0
  34. package/dist/schemas.d.ts +1162 -2
  35. package/dist/schemas.js +316 -0
  36. package/dist/types.d.ts +393 -2
  37. package/dist/types.js +1 -1
  38. package/dist/utils/native-comments.d.ts +68 -0
  39. package/dist/utils/native-comments.js +149 -0
  40. package/dist/utils/theme-helpers.d.ts +41 -1
  41. package/dist/utils/theme-helpers.js +98 -1
  42. package/dist/utils/theme.d.ts +48 -1
  43. package/dist/utils/theme.js +158 -0
  44. package/package.json +20 -20
  45. package/src/lib/components/ActionBar.svelte +85 -0
  46. package/src/lib/components/Avatar.svelte +104 -0
  47. package/src/lib/components/Comment.svelte +172 -0
  48. package/src/lib/components/CommentsSection.svelte +89 -0
  49. package/src/lib/components/DocumentCard.svelte +126 -56
  50. package/src/lib/components/Footnotes.svelte +72 -0
  51. package/src/lib/components/RecommendButton.svelte +153 -0
  52. package/src/lib/components/ThemeProvider.svelte +92 -0
  53. package/src/lib/components/Toast.svelte +177 -0
  54. package/src/lib/components/Watermark.svelte +100 -0
  55. package/src/lib/components/common/ThemedCard.svelte +15 -15
  56. package/src/lib/components/document/BlockRenderer.svelte +3 -0
  57. package/src/lib/components/document/DocumentRenderer.svelte +41 -1
  58. package/src/lib/components/document/RichText.svelte +87 -2
  59. package/src/lib/components/document/blocks/OrderedListBlock.svelte +152 -0
  60. package/src/lib/components/document/blocks/UnorderedListBlock.svelte +1 -1
  61. package/src/lib/components/index.ts +32 -0
  62. package/src/lib/index.ts +119 -5
  63. package/src/lib/publisher.ts +251 -0
  64. package/src/lib/schemas.ts +411 -0
  65. package/src/lib/types.ts +506 -2
  66. package/src/lib/utils/native-comments.ts +197 -0
  67. package/src/lib/utils/theme-helpers.ts +136 -3
  68. package/src/lib/utils/theme.ts +189 -1
  69. package/dist/components/document/blocks/UnorderedListBlock.svelte.d.ts +0 -9
@@ -0,0 +1,85 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ /** Snippet for action items */
4
+ children: import('svelte').Snippet;
5
+ /** Position of the action bar */
6
+ position?: 'left' | 'bottom' | 'right';
7
+ /** Has theme applied */
8
+ hasTheme?: boolean;
9
+ /** Sticky positioning */
10
+ sticky?: boolean;
11
+ }
12
+
13
+ const { children, position = 'left', hasTheme = false, sticky = false }: Props = $props();
14
+ </script>
15
+
16
+ <div
17
+ class="action-bar {position}"
18
+ class:themed={hasTheme}
19
+ class:sticky
20
+ >
21
+ {@render children()}
22
+ </div>
23
+
24
+ <style>
25
+ .action-bar {
26
+ display: flex;
27
+ gap: 0.5rem;
28
+ padding: 0.5rem;
29
+ border-radius: 0.5rem;
30
+ background-color: rgb(255 255 255);
31
+ box-shadow: 0 1px 3px rgb(0 0 0 / 0.1);
32
+ }
33
+
34
+ .action-bar.themed {
35
+ background-color: var(--theme-page-background, var(--theme-background));
36
+ }
37
+
38
+ .action-bar.left {
39
+ flex-direction: column;
40
+ align-items: center;
41
+ }
42
+
43
+ .action-bar.bottom {
44
+ flex-direction: row;
45
+ align-items: center;
46
+ }
47
+
48
+ .action-bar.right {
49
+ flex-direction: column;
50
+ align-items: center;
51
+ }
52
+
53
+ .action-bar.sticky {
54
+ position: sticky;
55
+ }
56
+
57
+ .action-bar.left.sticky {
58
+ top: 1rem;
59
+ }
60
+
61
+ .action-bar.bottom.sticky {
62
+ bottom: 1rem;
63
+ }
64
+
65
+ .action-bar :global(button) {
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ width: 2.5rem;
70
+ height: 2.5rem;
71
+ border-radius: 9999px;
72
+ background: transparent;
73
+ border: none;
74
+ cursor: pointer;
75
+ transition: background-color 0.15s;
76
+ }
77
+
78
+ .action-bar :global(button:hover) {
79
+ background-color: rgb(243 244 246);
80
+ }
81
+
82
+ .action-bar.themed :global(button:hover) {
83
+ background-color: color-mix(in srgb, var(--theme-foreground) 10%, transparent);
84
+ }
85
+ </style>
@@ -0,0 +1,13 @@
1
+ interface Props {
2
+ /** Snippet for action items */
3
+ children: import('svelte').Snippet;
4
+ /** Position of the action bar */
5
+ position?: 'left' | 'bottom' | 'right';
6
+ /** Has theme applied */
7
+ hasTheme?: boolean;
8
+ /** Sticky positioning */
9
+ sticky?: boolean;
10
+ }
11
+ declare const ActionBar: import("svelte").Component<Props, {}, "">;
12
+ type ActionBar = ReturnType<typeof ActionBar>;
13
+ export default ActionBar;
@@ -0,0 +1,104 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ /** User's DID */
4
+ did: string;
5
+ /** User's handle (optional, for display) */
6
+ handle?: string;
7
+ /** User's display name */
8
+ displayName?: string;
9
+ /** Avatar image URL */
10
+ src?: string;
11
+ /** Size in pixels */
12
+ size?: number;
13
+ /** Whether to link to profile */
14
+ link?: boolean;
15
+ /** Has theme applied */
16
+ hasTheme?: boolean;
17
+ }
18
+
19
+ const {
20
+ did,
21
+ handle,
22
+ displayName,
23
+ src,
24
+ size = 40,
25
+ link = true,
26
+ hasTheme = false
27
+ }: Props = $props();
28
+
29
+ // Generate initials from display name or handle
30
+ const initials = $derived(
31
+ (displayName || handle || did)
32
+ .split(/[.\s_@]+/)
33
+ .slice(0, 2)
34
+ .map((s) => s.charAt(0).toUpperCase())
35
+ .join('')
36
+ );
37
+
38
+ // Profile URL
39
+ const profileUrl = $derived(`https://leaflet.pub/p/${did}`);
40
+
41
+ // Size classes
42
+ const sizeStyle = $derived(`width: ${size}px; height: ${size}px; font-size: ${size * 0.4}px;`);
43
+ </script>
44
+
45
+ {#if link}
46
+ <a
47
+ href={profileUrl}
48
+ target="_blank"
49
+ rel="noopener noreferrer"
50
+ class="avatar-link"
51
+ title={displayName || handle || did}
52
+ >
53
+ {#if src}
54
+ <img
55
+ src={src}
56
+ alt={displayName || handle || 'Avatar'}
57
+ class="avatar rounded-full object-cover"
58
+ style={sizeStyle}
59
+ loading="lazy"
60
+ />
61
+ {:else}
62
+ <div
63
+ class="avatar-placeholder rounded-full flex items-center justify-center font-medium"
64
+ class:themed={hasTheme}
65
+ style={sizeStyle}
66
+ >
67
+ {initials}
68
+ </div>
69
+ {/if}
70
+ </a>
71
+ {:else if src}
72
+ <img
73
+ src={src}
74
+ alt={displayName || handle || 'Avatar'}
75
+ class="avatar rounded-full object-cover"
76
+ style={sizeStyle}
77
+ loading="lazy"
78
+ />
79
+ {:else}
80
+ <div
81
+ class="avatar-placeholder rounded-full flex items-center justify-center font-medium"
82
+ class:themed={hasTheme}
83
+ style={sizeStyle}
84
+ >
85
+ {initials}
86
+ </div>
87
+ {/if}
88
+
89
+ <style>
90
+ .avatar-link {
91
+ display: inline-block;
92
+ text-decoration: none;
93
+ }
94
+
95
+ .avatar-placeholder {
96
+ background-color: rgb(229 231 235);
97
+ color: rgb(107 114 128);
98
+ }
99
+
100
+ .avatar-placeholder.themed {
101
+ background-color: color-mix(in srgb, var(--theme-accent) 20%, transparent);
102
+ color: var(--theme-accent);
103
+ }
104
+ </style>
@@ -0,0 +1,19 @@
1
+ interface Props {
2
+ /** User's DID */
3
+ did: string;
4
+ /** User's handle (optional, for display) */
5
+ handle?: string;
6
+ /** User's display name */
7
+ displayName?: string;
8
+ /** Avatar image URL */
9
+ src?: string;
10
+ /** Size in pixels */
11
+ size?: number;
12
+ /** Whether to link to profile */
13
+ link?: boolean;
14
+ /** Has theme applied */
15
+ hasTheme?: boolean;
16
+ }
17
+ declare const Avatar: import("svelte").Component<Props, {}, "">;
18
+ type Avatar = ReturnType<typeof Avatar>;
19
+ export default Avatar;
@@ -0,0 +1,172 @@
1
+ <script lang="ts">
2
+ import RichText from './document/RichText.svelte';
3
+ import Comment from './Comment.svelte';
4
+ import type { CommentRecord, LinearDocumentQuote } from '../types.js';
5
+
6
+ interface CommentProps extends CommentRecord {
7
+ uri: string;
8
+ cid: string;
9
+ author: {
10
+ did: string;
11
+ handle?: string;
12
+ displayName?: string;
13
+ avatar?: string;
14
+ };
15
+ replies?: CommentProps[];
16
+ }
17
+
18
+ interface Props {
19
+ comment: CommentProps;
20
+ hasTheme?: boolean;
21
+ /** Whether to show the attachment/quote */
22
+ showQuote?: boolean;
23
+ /** Depth in the thread (for styling) */
24
+ depth?: number;
25
+ }
26
+
27
+ const { comment, hasTheme = false, showQuote = true, depth = 0 }: Props = $props();
28
+
29
+ // Format date
30
+ const formattedDate = $derived(
31
+ new Date(comment.createdAt).toLocaleDateString(undefined, {
32
+ year: 'numeric',
33
+ month: 'short',
34
+ day: 'numeric'
35
+ })
36
+ );
37
+
38
+ // Author display name
39
+ const authorName = $derived(
40
+ comment.author.displayName || comment.author.handle || comment.author.did
41
+ );
42
+
43
+ // Author profile URL
44
+ const authorUrl = $derived(`https://leaflet.pub/p/${comment.author.did}`);
45
+
46
+ // Quote text extraction
47
+ const quoteText = $derived(
48
+ comment.attachment?.quote ? extractQuotePreview(comment.attachment) : null
49
+ );
50
+
51
+ function extractQuotePreview(quote: LinearDocumentQuote): string {
52
+ if (quote.quote) {
53
+ return `Quote from block ${quote.quote.start.block.join('.')}`;
54
+ }
55
+ return 'Document quote';
56
+ }
57
+ </script>
58
+
59
+ <article
60
+ class="comment"
61
+ class:themed={hasTheme}
62
+ class:reply={depth > 0}
63
+ style:padding-left={depth > 0 ? '1rem' : undefined}
64
+ >
65
+ <header class="comment-header flex items-center gap-3 mb-2">
66
+ {#if comment.author.avatar}
67
+ <img
68
+ src={comment.author.avatar}
69
+ alt={authorName}
70
+ class="w-8 h-8 rounded-full"
71
+ />
72
+ {:else}
73
+ <div
74
+ class="avatar-placeholder w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium"
75
+ class:themed={hasTheme}
76
+ >
77
+ {authorName.charAt(0).toUpperCase()}
78
+ </div>
79
+ {/if}
80
+
81
+ <div class="flex-1 min-w-0">
82
+ <a
83
+ href={authorUrl}
84
+ target="_blank"
85
+ rel="noopener noreferrer"
86
+ class="author-name font-medium hover:underline"
87
+ class:themed={hasTheme}
88
+ >
89
+ {authorName}
90
+ </a>
91
+ {#if comment.author.handle}
92
+ <span class="author-handle text-sm opacity-60">@{comment.author.handle}</span>
93
+ {/if}
94
+ </div>
95
+
96
+ <time datetime={comment.createdAt} class="text-sm opacity-60">
97
+ {formattedDate}
98
+ </time>
99
+ </header>
100
+
101
+ <div class="comment-body">
102
+ {#if showQuote && comment.attachment}
103
+ <blockquote class="quote-attachment mb-2 px-3 py-2 border-l-2 text-sm opacity-80">
104
+ {quoteText}
105
+ </blockquote>
106
+ {/if}
107
+
108
+ <div class="comment-text">
109
+ <RichText plaintext={comment.plaintext} facets={comment.facets} {hasTheme} />
110
+ </div>
111
+ </div>
112
+
113
+ {#if comment.replies && comment.replies.length > 0}
114
+ <div class="replies mt-4 space-y-4 border-l-2 pl-4">
115
+ {#each comment.replies as reply}
116
+ <Comment comment={reply} {hasTheme} {showQuote} depth={depth + 1} />
117
+ {/each}
118
+ </div>
119
+ {/if}
120
+ </article>
121
+
122
+ <style>
123
+ .comment {
124
+ padding: 1rem;
125
+ border-radius: 0.5rem;
126
+ background-color: rgb(249 250 251);
127
+ }
128
+
129
+ .comment.themed {
130
+ background-color: color-mix(in srgb, var(--theme-foreground) 5%, transparent);
131
+ }
132
+
133
+ .comment.reply {
134
+ background-color: transparent;
135
+ }
136
+
137
+ .avatar-placeholder {
138
+ background-color: rgb(229 231 235);
139
+ color: rgb(107 114 128);
140
+ }
141
+
142
+ .avatar-placeholder.themed {
143
+ background-color: color-mix(in srgb, var(--theme-accent) 20%, transparent);
144
+ color: var(--theme-accent);
145
+ }
146
+
147
+ .author-name {
148
+ color: rgb(0 0 0);
149
+ }
150
+
151
+ .author-name.themed {
152
+ color: var(--theme-foreground);
153
+ }
154
+
155
+ .quote-attachment {
156
+ border-color: rgb(209 213 219);
157
+ background-color: rgb(255 255 255);
158
+ }
159
+
160
+ .comment:global(.themed) .quote-attachment {
161
+ border-color: color-mix(in srgb, var(--theme-foreground) 20%, transparent);
162
+ background-color: color-mix(in srgb, var(--theme-background) 50%, transparent);
163
+ }
164
+
165
+ .replies {
166
+ border-color: rgb(229 231 235);
167
+ }
168
+
169
+ .replies:global(.themed) {
170
+ border-color: color-mix(in srgb, var(--theme-foreground) 10%, transparent);
171
+ }
172
+ </style>
@@ -0,0 +1,22 @@
1
+ import Comment from './Comment.svelte';
2
+ import type { CommentRecord } from '../types.js';
3
+ declare const Comment: import("svelte").Component<{
4
+ comment: CommentRecord & {
5
+ uri: string;
6
+ cid: string;
7
+ author: {
8
+ did: string;
9
+ handle?: string;
10
+ displayName?: string;
11
+ avatar?: string;
12
+ };
13
+ replies?: (CommentRecord & /*elided*/ any)[];
14
+ };
15
+ hasTheme?: boolean;
16
+ /** Whether to show the attachment/quote */
17
+ showQuote?: boolean;
18
+ /** Depth in the thread (for styling) */
19
+ depth?: number;
20
+ }, {}, "">;
21
+ type Comment = ReturnType<typeof Comment>;
22
+ export default Comment;
@@ -0,0 +1,89 @@
1
+ <script lang="ts">
2
+ import Comment from './Comment.svelte';
3
+ import { organizeCommentsIntoThreads } from '../utils/native-comments.js';
4
+
5
+ interface CommentProps {
6
+ uri: string;
7
+ cid: string;
8
+ author: {
9
+ did: string;
10
+ handle?: string;
11
+ displayName?: string;
12
+ avatar?: string;
13
+ };
14
+ replies?: CommentProps[];
15
+ $type: 'pub.leaflet.comment';
16
+ subject: string;
17
+ plaintext: string;
18
+ createdAt: string;
19
+ facets?: any[];
20
+ attachment?: any;
21
+ }
22
+
23
+ interface Props {
24
+ /** AT-URI of the document */
25
+ subject: string;
26
+ /** Raw comments from API */
27
+ comments?: Array<any>;
28
+ /** Pre-organized comment threads */
29
+ threads?: CommentProps[];
30
+ hasTheme?: boolean;
31
+ /** Show quote attachments */
32
+ showQuotes?: boolean;
33
+ }
34
+
35
+ const { subject, comments = [], threads: propThreads, hasTheme = false, showQuotes = true }: Props = $props();
36
+
37
+ // Organize comments into threads if not provided
38
+ const commentThreads = $derived(
39
+ propThreads ?? organizeCommentsIntoThreads(comments)
40
+ );
41
+
42
+ // Total comment count
43
+ const totalComments = $derived(
44
+ commentThreads.reduce((sum, thread) => sum + countThread(thread), 0)
45
+ );
46
+
47
+ function countThread(thread: any): number {
48
+ let count = 1;
49
+ if (thread.replies) {
50
+ for (const reply of thread.replies) {
51
+ count += countThread(reply);
52
+ }
53
+ }
54
+ return count;
55
+ }
56
+ </script>
57
+
58
+ <section class="comments-section" class:themed={hasTheme}>
59
+ <header class="comments-header mb-6">
60
+ <h2 class="text-xl font-semibold">
61
+ Comments
62
+ {#if totalComments > 0}
63
+ <span class="text-sm font-normal opacity-60">({totalComments})</span>
64
+ {/if}
65
+ </h2>
66
+ </header>
67
+
68
+ {#if commentThreads.length === 0}
69
+ <p class="text-sm opacity-60">No comments yet.</p>
70
+ {:else}
71
+ <div class="comments-list space-y-4">
72
+ {#each commentThreads as thread}
73
+ <Comment comment={thread} {hasTheme} showQuote={showQuotes} />
74
+ {/each}
75
+ </div>
76
+ {/if}
77
+ </section>
78
+
79
+ <style>
80
+ .comments-section {
81
+ margin-top: 2rem;
82
+ padding-top: 2rem;
83
+ border-top: 1px solid rgb(229 231 235);
84
+ }
85
+
86
+ .comments-section.themed {
87
+ border-top-color: color-mix(in srgb, var(--theme-foreground) 10%, transparent);
88
+ }
89
+ </style>