@ewanc26/svelte-standard-site 0.2.2 → 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.
- package/dist/components/ActionBar.svelte +85 -0
- package/dist/components/ActionBar.svelte.d.ts +13 -0
- package/dist/components/Avatar.svelte +104 -0
- package/dist/components/Avatar.svelte.d.ts +19 -0
- package/dist/components/Comment.svelte +172 -0
- package/dist/components/Comment.svelte.d.ts +22 -0
- package/dist/components/CommentsSection.svelte +89 -0
- package/dist/components/DocumentCard.svelte +126 -56
- package/dist/components/DocumentCard.svelte.d.ts +51 -0
- package/dist/components/Footnotes.svelte +72 -0
- package/dist/components/Footnotes.svelte.d.ts +13 -0
- package/dist/components/RecommendButton.svelte +153 -0
- package/dist/components/RecommendButton.svelte.d.ts +17 -0
- package/dist/components/ThemeProvider.svelte +92 -0
- package/dist/components/ThemeProvider.svelte.d.ts +13 -0
- package/dist/components/Toast.svelte +177 -0
- package/dist/components/Toast.svelte.d.ts +32 -0
- package/dist/components/Watermark.svelte +100 -0
- package/dist/components/Watermark.svelte.d.ts +17 -0
- package/dist/components/common/ThemedCard.svelte +15 -15
- package/dist/components/common/ThemedCard.svelte.d.ts +5 -0
- package/dist/components/document/BlockRenderer.svelte +3 -0
- package/dist/components/document/DocumentRenderer.svelte +41 -1
- package/dist/components/document/RichText.svelte +87 -2
- package/dist/components/document/RichText.svelte.d.ts +2 -0
- package/dist/components/document/blocks/OrderedListBlock.svelte +152 -0
- package/dist/components/document/blocks/UnorderedListBlock.svelte +1 -1
- package/dist/components/index.d.ts +28 -0
- package/dist/components/index.js +30 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +6 -4
- package/dist/publisher.d.ts +73 -0
- package/dist/publisher.js +185 -0
- package/dist/schemas.d.ts +1162 -2
- package/dist/schemas.js +316 -0
- package/dist/types.d.ts +393 -2
- package/dist/types.js +1 -1
- package/dist/utils/native-comments.d.ts +68 -0
- package/dist/utils/native-comments.js +149 -0
- package/dist/utils/theme-helpers.d.ts +41 -1
- package/dist/utils/theme-helpers.js +98 -1
- package/dist/utils/theme.d.ts +48 -1
- package/dist/utils/theme.js +158 -0
- package/package.json +62 -65
- package/src/lib/components/ActionBar.svelte +85 -0
- package/src/lib/components/Avatar.svelte +104 -0
- package/src/lib/components/Comment.svelte +172 -0
- package/src/lib/components/CommentsSection.svelte +89 -0
- package/src/lib/components/DocumentCard.svelte +126 -56
- package/src/lib/components/Footnotes.svelte +72 -0
- package/src/lib/components/RecommendButton.svelte +153 -0
- package/src/lib/components/ThemeProvider.svelte +92 -0
- package/src/lib/components/Toast.svelte +177 -0
- package/src/lib/components/Watermark.svelte +100 -0
- package/src/lib/components/common/ThemedCard.svelte +15 -15
- package/src/lib/components/document/BlockRenderer.svelte +3 -0
- package/src/lib/components/document/DocumentRenderer.svelte +41 -1
- package/src/lib/components/document/RichText.svelte +87 -2
- package/src/lib/components/document/blocks/OrderedListBlock.svelte +152 -0
- package/src/lib/components/document/blocks/UnorderedListBlock.svelte +1 -1
- package/src/lib/components/index.ts +32 -0
- package/src/lib/index.ts +119 -5
- package/src/lib/publisher.ts +251 -0
- package/src/lib/schemas.ts +411 -0
- package/src/lib/types.ts +506 -2
- package/src/lib/utils/native-comments.ts +197 -0
- package/src/lib/utils/theme-helpers.ts +136 -3
- package/src/lib/utils/theme.ts +189 -1
- package/dist/components/document/blocks/UnorderedListBlock.svelte.d.ts +0 -9
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Comments utilities for pub.leaflet.comment records
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { CommentRecord, Facet, LinearDocumentQuote } from '../types.js';
|
|
6
|
+
|
|
7
|
+
/** Collection name for comments */
|
|
8
|
+
export const COMMENTS_COLLECTION = 'pub.leaflet.comment';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a comment record
|
|
12
|
+
*/
|
|
13
|
+
export function createCommentRecord(params: {
|
|
14
|
+
subject: string; // AT-URI of the document being commented on
|
|
15
|
+
plaintext: string;
|
|
16
|
+
reply?: { parent: string }; // AT-URI of the parent comment if replying
|
|
17
|
+
facets?: Facet[];
|
|
18
|
+
onPage?: string; // Page ID if commenting on a specific page
|
|
19
|
+
attachment?: LinearDocumentQuote;
|
|
20
|
+
}): CommentRecord {
|
|
21
|
+
const record: CommentRecord = {
|
|
22
|
+
$type: 'pub.leaflet.comment',
|
|
23
|
+
subject: params.subject,
|
|
24
|
+
plaintext: params.plaintext,
|
|
25
|
+
createdAt: new Date().toISOString()
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (params.reply) {
|
|
29
|
+
record.reply = {
|
|
30
|
+
$type: 'pub.leaflet.comment#replyRef',
|
|
31
|
+
parent: params.reply.parent
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (params.facets) {
|
|
36
|
+
record.facets = params.facets;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (params.onPage) {
|
|
40
|
+
record.onPage = params.onPage;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (params.attachment) {
|
|
44
|
+
record.attachment = {
|
|
45
|
+
$type: 'pub.leaflet.comment#linearDocumentQuote',
|
|
46
|
+
...params.attachment
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return record;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Parse a comment AT-URI to extract components
|
|
55
|
+
*/
|
|
56
|
+
export function parseCommentUri(uri: string): {
|
|
57
|
+
did: string;
|
|
58
|
+
collection: string;
|
|
59
|
+
rkey: string;
|
|
60
|
+
} | null {
|
|
61
|
+
const match = uri.match(/^at:\/\/([^/]+)\/([^/]+)\/([^/]+)$/);
|
|
62
|
+
if (!match) return null;
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
did: match[1],
|
|
66
|
+
collection: match[2],
|
|
67
|
+
rkey: match[3]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Build a comment AT-URI
|
|
73
|
+
*/
|
|
74
|
+
export function buildCommentUri(did: string, rkey: string): string {
|
|
75
|
+
return `at://${did}/${COMMENTS_COLLECTION}/${rkey}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Fetch comments for a document
|
|
80
|
+
*/
|
|
81
|
+
export async function fetchComments(
|
|
82
|
+
subject: string,
|
|
83
|
+
service = 'https://public.api.bsky.app'
|
|
84
|
+
): Promise<Array<CommentRecord & { uri: string; cid: string; author: { did: string } }>> {
|
|
85
|
+
try {
|
|
86
|
+
const response = await fetch(
|
|
87
|
+
`${service}/xrpc/com.atproto.repo.listRecords?collection=${COMMENTS_COLLECTION}&limit=100`
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error(`Failed to fetch comments: ${response.status}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const data = await response.json();
|
|
95
|
+
|
|
96
|
+
// Filter comments for this subject
|
|
97
|
+
const comments = data.records
|
|
98
|
+
?.filter((record: any) => record.value?.subject === subject)
|
|
99
|
+
?.map((record: any) => ({
|
|
100
|
+
...record.value,
|
|
101
|
+
uri: record.uri,
|
|
102
|
+
cid: record.cid,
|
|
103
|
+
author: { did: record.uri.split('/')[2] }
|
|
104
|
+
})) ?? [];
|
|
105
|
+
|
|
106
|
+
return comments;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('Error fetching comments:', error);
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Organize comments into a threaded structure
|
|
115
|
+
*/
|
|
116
|
+
export function organizeCommentsIntoThreads(
|
|
117
|
+
comments: Array<CommentRecord & { uri: string; cid: string; author: { did: string } }>
|
|
118
|
+
): Array<CommentRecord & { uri: string; cid: string; author: { did: string }; replies: any[] }> {
|
|
119
|
+
const byId = new Map<string, any>();
|
|
120
|
+
const rootComments: any[] = [];
|
|
121
|
+
|
|
122
|
+
// First pass: create all comment objects
|
|
123
|
+
for (const comment of comments) {
|
|
124
|
+
byId.set(comment.uri, { ...comment, replies: [] });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Second pass: organize into threads
|
|
128
|
+
for (const comment of comments) {
|
|
129
|
+
const node = byId.get(comment.uri);
|
|
130
|
+
if (!node) continue;
|
|
131
|
+
|
|
132
|
+
if (comment.reply?.parent) {
|
|
133
|
+
const parent = byId.get(comment.reply.parent);
|
|
134
|
+
if (parent) {
|
|
135
|
+
parent.replies.push(node);
|
|
136
|
+
} else {
|
|
137
|
+
// Parent not found, treat as root
|
|
138
|
+
rootComments.push(node);
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
rootComments.push(node);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Sort root comments by date (newest first)
|
|
146
|
+
rootComments.sort(
|
|
147
|
+
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return rootComments;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Count total comments in a thread
|
|
155
|
+
*/
|
|
156
|
+
export function countThreadComments(thread: { replies: any[] }): number {
|
|
157
|
+
let count = 1;
|
|
158
|
+
for (const reply of thread.replies) {
|
|
159
|
+
count += countThreadComments(reply);
|
|
160
|
+
}
|
|
161
|
+
return count;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get quoted text from a document
|
|
166
|
+
*/
|
|
167
|
+
export function extractQuotedText(
|
|
168
|
+
document: any,
|
|
169
|
+
quote: LinearDocumentQuote['quote']
|
|
170
|
+
): string | null {
|
|
171
|
+
if (!quote || !document?.content?.pages) return null;
|
|
172
|
+
|
|
173
|
+
// Find the linear document page
|
|
174
|
+
const page = document.content.pages.find((p: any) =>
|
|
175
|
+
quote.start.block.every((b, i) => b === p.id || i === 0)
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (!page) return null;
|
|
179
|
+
|
|
180
|
+
// Extract text based on block indices
|
|
181
|
+
const blocks = page.blocks || [];
|
|
182
|
+
const startBlockIndex = quote.start.block[quote.start.block.length - 1];
|
|
183
|
+
const endBlockIndex = quote.end.block[quote.end.block.length - 1];
|
|
184
|
+
|
|
185
|
+
if (startBlockIndex === undefined || endBlockIndex === undefined) return null;
|
|
186
|
+
|
|
187
|
+
// For simplicity, return the text from the first block
|
|
188
|
+
// In a real implementation, you'd handle multi-block quotes
|
|
189
|
+
const startBlock = blocks[startBlockIndex]?.block;
|
|
190
|
+
if (!startBlock) return null;
|
|
191
|
+
|
|
192
|
+
if (startBlock.plaintext) {
|
|
193
|
+
return startBlock.plaintext.slice(quote.start.offset, quote.end.offset);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import type { BasicTheme } from '../types.js';
|
|
2
|
-
import { rgbToCSS } from './theme.js';
|
|
1
|
+
import type { BasicTheme, ExtendedTheme, BackgroundImage } from '../types.js';
|
|
2
|
+
import { rgbToCSS, colorToCSS, isRGBA } from './theme.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Generate color-mix CSS for theme colors with transparency
|
|
6
6
|
*/
|
|
7
|
-
export function mixThemeColor(
|
|
7
|
+
export function mixThemeColor(
|
|
8
|
+
variable: string,
|
|
9
|
+
opacity: number,
|
|
10
|
+
fallback = 'transparent'
|
|
11
|
+
): string {
|
|
8
12
|
return `color-mix(in srgb, var(${variable}) ${opacity}%, ${fallback})`;
|
|
9
13
|
}
|
|
10
14
|
|
|
@@ -72,6 +76,75 @@ export function getThemedAccent(
|
|
|
72
76
|
};
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Get theme-aware page background
|
|
81
|
+
*/
|
|
82
|
+
export function getThemedPageBackground(
|
|
83
|
+
hasTheme: boolean,
|
|
84
|
+
showPageBackground?: boolean
|
|
85
|
+
): {
|
|
86
|
+
backgroundColor?: string;
|
|
87
|
+
} {
|
|
88
|
+
if (!hasTheme) return {};
|
|
89
|
+
if (showPageBackground === false) return {};
|
|
90
|
+
return { backgroundColor: 'var(--theme-page-background)' };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get background image styles
|
|
95
|
+
*/
|
|
96
|
+
export function getBackgroundImageStyles(bgImage: BackgroundImage | undefined): {
|
|
97
|
+
backgroundImage?: string;
|
|
98
|
+
backgroundSize?: string;
|
|
99
|
+
backgroundPosition?: string;
|
|
100
|
+
} {
|
|
101
|
+
if (!bgImage?.url) return {};
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
backgroundImage: `url(${bgImage.url})`,
|
|
105
|
+
backgroundSize: 'cover',
|
|
106
|
+
backgroundPosition: 'center'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get font styles from theme
|
|
112
|
+
*/
|
|
113
|
+
export function getFontStyles(theme: ExtendedTheme | undefined): {
|
|
114
|
+
fontFamily?: string;
|
|
115
|
+
} {
|
|
116
|
+
if (!theme) return {};
|
|
117
|
+
|
|
118
|
+
const headingFont = theme.headingFont;
|
|
119
|
+
const bodyFont = theme.bodyFont;
|
|
120
|
+
|
|
121
|
+
if (bodyFont) {
|
|
122
|
+
return { fontFamily: `"${bodyFont}", system-ui, sans-serif` };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get heading font styles from theme
|
|
130
|
+
*/
|
|
131
|
+
export function getHeadingFontStyles(theme: ExtendedTheme | undefined): {
|
|
132
|
+
fontFamily?: string;
|
|
133
|
+
} {
|
|
134
|
+
if (!theme?.headingFont) return {};
|
|
135
|
+
return { fontFamily: `"${theme.headingFont}", system-ui, sans-serif` };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get page width styles from theme
|
|
140
|
+
*/
|
|
141
|
+
export function getPageWidthStyles(theme: ExtendedTheme | undefined): {
|
|
142
|
+
maxWidth?: string;
|
|
143
|
+
} {
|
|
144
|
+
if (!theme?.pageWidth) return {};
|
|
145
|
+
return { maxWidth: `${theme.pageWidth}px` };
|
|
146
|
+
}
|
|
147
|
+
|
|
75
148
|
/**
|
|
76
149
|
* Convert BasicTheme to CSS custom properties
|
|
77
150
|
*/
|
|
@@ -85,3 +158,63 @@ export function themeToCssVars(theme?: BasicTheme): Record<string, string> {
|
|
|
85
158
|
'--theme-accent-foreground': rgbToCSS(theme.accentForeground)
|
|
86
159
|
};
|
|
87
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Convert ExtendedTheme to CSS custom properties
|
|
164
|
+
*/
|
|
165
|
+
export function extendedThemeToCssVars(theme?: ExtendedTheme): Record<string, string> {
|
|
166
|
+
if (!theme) return {};
|
|
167
|
+
|
|
168
|
+
const vars: Record<string, string> = {};
|
|
169
|
+
|
|
170
|
+
if (theme.backgroundColor) {
|
|
171
|
+
vars['--theme-background'] = colorToCSS(theme.backgroundColor);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Extended theme uses different property names
|
|
175
|
+
if (theme.pageBackground) {
|
|
176
|
+
vars['--theme-page-background'] = colorToCSS(theme.pageBackground);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (theme.primary) {
|
|
180
|
+
vars['--theme-accent'] = colorToCSS(theme.primary);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (theme.accentBackground) {
|
|
184
|
+
vars['--theme-accent-background'] = colorToCSS(theme.accentBackground);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (theme.accentText) {
|
|
188
|
+
vars['--theme-accent-foreground'] = colorToCSS(theme.accentText);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (theme.headingFont) {
|
|
192
|
+
vars['--theme-heading-font'] = `"${theme.headingFont}", system-ui, sans-serif`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (theme.bodyFont) {
|
|
196
|
+
vars['--theme-body-font'] = `"${theme.bodyFont}", system-ui, sans-serif`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (theme.pageWidth) {
|
|
200
|
+
vars['--theme-page-width'] = `${theme.pageWidth}px`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return vars;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Convert any theme to CSS custom properties
|
|
208
|
+
*/
|
|
209
|
+
export function anyThemeToCssVars(
|
|
210
|
+
theme?: BasicTheme | ExtendedTheme
|
|
211
|
+
): Record<string, string> {
|
|
212
|
+
if (!theme) return {};
|
|
213
|
+
|
|
214
|
+
// Check for basic theme properties
|
|
215
|
+
if ('background' in theme && 'foreground' in theme) {
|
|
216
|
+
return themeToCssVars(theme as BasicTheme);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return extendedThemeToCssVars(theme as ExtendedTheme);
|
|
220
|
+
}
|
package/src/lib/utils/theme.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type { RGBColor } from '../types.js';
|
|
1
|
+
import type { RGBColor, RGBAColor, Color, BasicTheme, ExtendedTheme, BackgroundImage } from '../types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Type guard for RGBA color
|
|
5
|
+
*/
|
|
6
|
+
export function isRGBA(color: Color): color is RGBAColor {
|
|
7
|
+
return 'a' in color;
|
|
8
|
+
}
|
|
2
9
|
|
|
3
10
|
/**
|
|
4
11
|
* Convert RGB color object to CSS rgb() string
|
|
@@ -7,6 +14,25 @@ export function rgbToCSS(color: RGBColor): string {
|
|
|
7
14
|
return `rgb(${color.r}, ${color.g}, ${color.b})`;
|
|
8
15
|
}
|
|
9
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Convert RGBA color object to CSS rgba() string
|
|
19
|
+
*/
|
|
20
|
+
export function rgbaToCSS(color: RGBAColor): string {
|
|
21
|
+
// Alpha is 0-100, convert to 0-1
|
|
22
|
+
const alpha = color.a / 100;
|
|
23
|
+
return `rgba(${color.r}, ${color.g}, ${color.b}, ${alpha})`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convert Color (RGB or RGBA) to CSS string
|
|
28
|
+
*/
|
|
29
|
+
export function colorToCSS(color: Color): string {
|
|
30
|
+
if (isRGBA(color)) {
|
|
31
|
+
return rgbaToCSS(color);
|
|
32
|
+
}
|
|
33
|
+
return rgbToCSS(color);
|
|
34
|
+
}
|
|
35
|
+
|
|
10
36
|
/**
|
|
11
37
|
* Convert RGB color object to hex string
|
|
12
38
|
*/
|
|
@@ -15,6 +41,15 @@ export function rgbToHex(color: RGBColor): string {
|
|
|
15
41
|
return `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}`;
|
|
16
42
|
}
|
|
17
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Convert RGBA color object to hex string with alpha
|
|
46
|
+
*/
|
|
47
|
+
export function rgbaToHex(color: RGBAColor): string {
|
|
48
|
+
const toHex = (n: number) => n.toString(16).padStart(2, '0');
|
|
49
|
+
const alpha = Math.round((color.a / 100) * 255);
|
|
50
|
+
return `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}${toHex(alpha)}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
18
53
|
/**
|
|
19
54
|
* Get theme CSS variables from BasicTheme
|
|
20
55
|
*/
|
|
@@ -31,3 +66,156 @@ export function getThemeVars(theme: {
|
|
|
31
66
|
'--theme-accent-foreground': rgbToCSS(theme.accentForeground)
|
|
32
67
|
};
|
|
33
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get CSS for background image
|
|
72
|
+
*/
|
|
73
|
+
export function getBackgroundImageCSS(bgImage: BackgroundImage | undefined): Record<string, string> {
|
|
74
|
+
if (!bgImage?.url) return {};
|
|
75
|
+
|
|
76
|
+
const styles: Record<string, string> = {
|
|
77
|
+
'background-image': `url(${bgImage.url})`,
|
|
78
|
+
'background-size': 'cover',
|
|
79
|
+
'background-position': 'center',
|
|
80
|
+
'background-repeat': 'no-repeat'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (bgImage.opacity !== undefined) {
|
|
84
|
+
// For opacity, we need to use a pseudo-element or overlay
|
|
85
|
+
// This is handled separately in the component
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (bgImage.blur !== undefined) {
|
|
89
|
+
styles['background-blur'] = `${bgImage.blur}px`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return styles;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Convert BasicTheme to CSS custom properties
|
|
97
|
+
*/
|
|
98
|
+
export function basicThemeToCssVars(theme: BasicTheme): Record<string, string> {
|
|
99
|
+
return {
|
|
100
|
+
'--theme-background': rgbToCSS(theme.background),
|
|
101
|
+
'--theme-foreground': rgbToCSS(theme.foreground),
|
|
102
|
+
'--theme-accent': rgbToCSS(theme.accent),
|
|
103
|
+
'--theme-accent-foreground': rgbToCSS(theme.accentForeground)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Convert ExtendedTheme to CSS custom properties
|
|
109
|
+
*/
|
|
110
|
+
export function extendedThemeToCssVars(theme: ExtendedTheme): Record<string, string> {
|
|
111
|
+
const vars: Record<string, string> = {};
|
|
112
|
+
|
|
113
|
+
if (theme.backgroundColor) {
|
|
114
|
+
vars['--theme-background'] = colorToCSS(theme.backgroundColor);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (theme.pageBackground) {
|
|
118
|
+
vars['--theme-page-background'] = colorToCSS(theme.pageBackground);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (theme.primary) {
|
|
122
|
+
vars['--theme-primary'] = colorToCSS(theme.primary);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (theme.accentBackground) {
|
|
126
|
+
vars['--theme-accent-background'] = colorToCSS(theme.accentBackground);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (theme.accentText) {
|
|
130
|
+
vars['--theme-accent-text'] = colorToCSS(theme.accentText);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (theme.headingFont) {
|
|
134
|
+
vars['--theme-heading-font'] = theme.headingFont;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (theme.bodyFont) {
|
|
138
|
+
vars['--theme-body-font'] = theme.bodyFont;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (theme.pageWidth) {
|
|
142
|
+
vars['--theme-page-width'] = `${theme.pageWidth}px`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Map extended theme properties to standard theme variables for compatibility
|
|
146
|
+
if (theme.backgroundColor) {
|
|
147
|
+
vars['--theme-background'] = colorToCSS(theme.backgroundColor);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Use primary as accent if accent is not defined
|
|
151
|
+
if (theme.primary && !theme.accentBackground) {
|
|
152
|
+
vars['--theme-accent'] = colorToCSS(theme.primary);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return vars;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Convert any theme (Basic or Extended) to CSS custom properties
|
|
160
|
+
*/
|
|
161
|
+
export function themeToCssVars(theme: BasicTheme | ExtendedTheme | undefined): Record<string, string> {
|
|
162
|
+
if (!theme) return {};
|
|
163
|
+
|
|
164
|
+
// Check if it's a basic theme
|
|
165
|
+
if ('background' in theme && 'foreground' in theme && 'accent' in theme && 'accentForeground' in theme) {
|
|
166
|
+
return basicThemeToCssVars(theme as BasicTheme);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Otherwise treat as extended theme
|
|
170
|
+
return extendedThemeToCssVars(theme as ExtendedTheme);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get font family CSS with fallbacks
|
|
175
|
+
*/
|
|
176
|
+
export function getFontFamilyCSS(fontName: string | undefined, fallback: string): string {
|
|
177
|
+
if (!fontName) return fallback;
|
|
178
|
+
return `"${fontName}", ${fallback}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Generate Google Fonts URL for custom fonts
|
|
183
|
+
*/
|
|
184
|
+
export function getGoogleFontsUrl(fonts: { headingFont?: string; bodyFont?: string }): string | null {
|
|
185
|
+
const families: string[] = [];
|
|
186
|
+
|
|
187
|
+
if (fonts.headingFont) {
|
|
188
|
+
families.push(`${encodeURIComponent(fonts.headingFont)}:wght@400;600;700`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (fonts.bodyFont) {
|
|
192
|
+
families.push(`${encodeURIComponent(fonts.bodyFont)}:wght@400;500;600`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (families.length === 0) return null;
|
|
196
|
+
|
|
197
|
+
return `https://fonts.googleapis.com/css2?family=${families.join('&family=')}&display=swap`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get all theme CSS variables including defaults
|
|
202
|
+
*/
|
|
203
|
+
export function getAllThemeVars(theme?: BasicTheme | ExtendedTheme): Record<string, string> {
|
|
204
|
+
const defaults: Record<string, string> = {
|
|
205
|
+
'--theme-background': 'rgb(255, 255, 255)',
|
|
206
|
+
'--theme-foreground': 'rgb(0, 0, 0)',
|
|
207
|
+
'--theme-accent': 'rgb(0, 0, 225)',
|
|
208
|
+
'--theme-accent-foreground': 'rgb(255, 255, 255)',
|
|
209
|
+
'--theme-page-background': 'rgb(255, 255, 255)',
|
|
210
|
+
'--theme-primary': 'rgb(0, 0, 225)',
|
|
211
|
+
'--theme-accent-background': 'rgb(0, 0, 225)',
|
|
212
|
+
'--theme-accent-text': 'rgb(255, 255, 255)',
|
|
213
|
+
'--theme-heading-font': 'system-ui, -apple-system, sans-serif',
|
|
214
|
+
'--theme-body-font': 'system-ui, -apple-system, sans-serif',
|
|
215
|
+
'--theme-page-width': '800px'
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const themeVars = themeToCssVars(theme);
|
|
219
|
+
|
|
220
|
+
return { ...defaults, ...themeVars };
|
|
221
|
+
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import UnorderedListBlock from './UnorderedListBlock.svelte';
|
|
2
|
-
declare const UnorderedListBlock: import("svelte").Component<{
|
|
3
|
-
block: {
|
|
4
|
-
children: ListItem[];
|
|
5
|
-
};
|
|
6
|
-
hasTheme?: boolean;
|
|
7
|
-
}, {}, "">;
|
|
8
|
-
type UnorderedListBlock = ReturnType<typeof UnorderedListBlock>;
|
|
9
|
-
export default UnorderedListBlock;
|