@autumnsgrove/groveengine 0.6.4 → 0.7.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 (58) hide show
  1. package/dist/auth/index.d.ts +1 -2
  2. package/dist/auth/index.js +8 -4
  3. package/dist/auth/session.d.ts +14 -33
  4. package/dist/auth/session.js +5 -103
  5. package/dist/components/admin/FloatingToolbar.svelte +373 -0
  6. package/dist/components/admin/FloatingToolbar.svelte.d.ts +17 -0
  7. package/dist/components/admin/MarkdownEditor.svelte +26 -347
  8. package/dist/components/admin/MarkdownEditor.svelte.d.ts +1 -1
  9. package/dist/components/admin/composables/index.d.ts +0 -2
  10. package/dist/components/admin/composables/index.js +0 -2
  11. package/dist/components/custom/ContentWithGutter.svelte +22 -25
  12. package/dist/components/custom/MobileTOC.svelte +20 -13
  13. package/dist/components/quota/UpgradePrompt.svelte +1 -1
  14. package/dist/server/services/database.d.ts +138 -0
  15. package/dist/server/services/database.js +234 -0
  16. package/dist/server/services/index.d.ts +5 -1
  17. package/dist/server/services/index.js +24 -2
  18. package/dist/server/services/turnstile.d.ts +66 -0
  19. package/dist/server/services/turnstile.js +131 -0
  20. package/dist/server/services/users.d.ts +104 -0
  21. package/dist/server/services/users.js +158 -0
  22. package/dist/styles/README.md +50 -0
  23. package/dist/styles/vine-pattern.css +24 -0
  24. package/dist/types/turnstile.d.ts +42 -0
  25. package/dist/ui/components/forms/TurnstileWidget.svelte +111 -0
  26. package/dist/ui/components/forms/TurnstileWidget.svelte.d.ts +14 -0
  27. package/dist/ui/components/primitives/dialog/dialog-overlay.svelte +1 -1
  28. package/dist/ui/components/primitives/sheet/sheet-overlay.svelte +1 -1
  29. package/dist/ui/components/ui/Glass.svelte +158 -0
  30. package/dist/ui/components/ui/Glass.svelte.d.ts +52 -0
  31. package/dist/ui/components/ui/GlassButton.svelte +157 -0
  32. package/dist/ui/components/ui/GlassButton.svelte.d.ts +39 -0
  33. package/dist/ui/components/ui/GlassCard.svelte +160 -0
  34. package/dist/ui/components/ui/GlassCard.svelte.d.ts +39 -0
  35. package/dist/ui/components/ui/GlassConfirmDialog.svelte +208 -0
  36. package/dist/ui/components/ui/GlassConfirmDialog.svelte.d.ts +52 -0
  37. package/dist/ui/components/ui/GlassOverlay.svelte +93 -0
  38. package/dist/ui/components/ui/GlassOverlay.svelte.d.ts +33 -0
  39. package/dist/ui/components/ui/Logo.svelte +161 -23
  40. package/dist/ui/components/ui/Logo.svelte.d.ts +4 -10
  41. package/dist/ui/components/ui/index.d.ts +5 -0
  42. package/dist/ui/components/ui/index.js +6 -0
  43. package/dist/ui/styles/grove.css +136 -0
  44. package/dist/ui/tokens/fonts.d.ts +69 -0
  45. package/dist/ui/tokens/fonts.js +341 -0
  46. package/dist/ui/tokens/index.d.ts +6 -5
  47. package/dist/ui/tokens/index.js +7 -6
  48. package/dist/utils/gutter.d.ts +2 -8
  49. package/dist/utils/markdown.d.ts +1 -0
  50. package/dist/utils/markdown.js +32 -11
  51. package/package.json +1 -1
  52. package/static/robots.txt +520 -0
  53. package/dist/auth/jwt.d.ts +0 -20
  54. package/dist/auth/jwt.js +0 -123
  55. package/dist/components/admin/composables/useCommandPalette.svelte.d.ts +0 -87
  56. package/dist/components/admin/composables/useCommandPalette.svelte.js +0 -158
  57. package/dist/components/admin/composables/useSlashCommands.svelte.d.ts +0 -104
  58. package/dist/components/admin/composables/useSlashCommands.svelte.js +0 -215
@@ -1,2 +1 @@
1
- export * from "./jwt.js";
2
- export * from "./session.js";
1
+ export { verifyTenantOwnership, getVerifiedTenantId } from "./session.js";
@@ -1,5 +1,9 @@
1
- // Auth barrel export
2
- // Re-exports all authentication functions
1
+ /**
2
+ * Auth barrel export
3
+ *
4
+ * Note: Legacy JWT utilities have been removed.
5
+ * Session management is now handled by Heartwood SessionDO.
6
+ * Only tenant verification functions remain.
7
+ */
3
8
 
4
- export * from './jwt.js';
5
- export * from './session.js';
9
+ export { verifyTenantOwnership, getVerifiedTenantId } from './session.js';
@@ -1,42 +1,23 @@
1
1
  /**
2
- * Create a session token for a user
3
- * @param {User} user - User data
4
- * @param {string} secret - Session secret
5
- * @returns {Promise<string>} - Signed JWT token
2
+ * Tenant Access Control Utilities
3
+ *
4
+ * Note: Legacy JWT session functions have been removed.
5
+ * Session management is now handled by Heartwood SessionDO.
6
+ * This file only contains tenant verification functions.
6
7
  */
7
- export function createSession(user: User, secret: string): Promise<string>;
8
8
  /**
9
- * Verify a session token and return user data
10
- * @param {string} token - Session token
11
- * @param {string} secret - Session secret
12
- * @returns {Promise<User|null>} - User data or null if invalid
9
+ * @typedef {Object} User
10
+ * @property {string} email
13
11
  */
14
- export function verifySession(token: string, secret: string): Promise<User | null>;
15
12
  /**
16
- * Create Set-Cookie header value for session
17
- * @param {string} token - Session token
18
- * @param {boolean} isProduction - Whether in production (for secure flag)
19
- * @returns {string} - Cookie header value
13
+ * @typedef {Object} SessionError
14
+ * @property {string} message
15
+ * @property {number} status
20
16
  */
21
- export function createSessionCookie(token: string, isProduction?: boolean): string;
22
17
  /**
23
- * Create Set-Cookie header value to clear session
24
- * @returns {string} - Cookie header value
18
+ * @typedef {Object} TenantRow
19
+ * @property {string} email
25
20
  */
26
- export function clearSessionCookie(): string;
27
- /**
28
- * Parse session token from cookie header
29
- * @param {string} cookieHeader - Cookie header value
30
- * @returns {string|null} - Session token or null
31
- */
32
- export function parseSessionCookie(cookieHeader: string): string | null;
33
- /**
34
- * Check if an email is in the allowed admin list
35
- * @param {string} email - Email address to check
36
- * @param {string} allowedList - Comma-separated list of allowed emails
37
- * @returns {boolean} - Whether the user is allowed
38
- */
39
- export function isAllowedAdmin(email: string, allowedList: string): boolean;
40
21
  /**
41
22
  * Verify that a user owns/has access to a tenant
42
23
  * @param {import('@cloudflare/workers-types').D1Database} db - D1 database instance
@@ -44,7 +25,7 @@ export function isAllowedAdmin(email: string, allowedList: string): boolean;
44
25
  * @param {string} userEmail - User's email address
45
26
  * @returns {Promise<boolean>} - Whether the user owns the tenant
46
27
  */
47
- export function verifyTenantOwnership(db: any, tenantId: string | undefined | null, userEmail: string): Promise<boolean>;
28
+ export function verifyTenantOwnership(db: import("@cloudflare/workers-types").D1Database, tenantId: string | undefined | null, userEmail: string): Promise<boolean>;
48
29
  /**
49
30
  * Get tenant ID with ownership verification
50
31
  * Throws 403 if user doesn't own the tenant
@@ -54,7 +35,7 @@ export function verifyTenantOwnership(db: any, tenantId: string | undefined | nu
54
35
  * @returns {Promise<string>} - Verified tenant ID
55
36
  * @throws {SessionError} - If unauthorized
56
37
  */
57
- export function getVerifiedTenantId(db: any, tenantId: string | undefined | null, user: User | null | undefined): Promise<string>;
38
+ export function getVerifiedTenantId(db: import("@cloudflare/workers-types").D1Database, tenantId: string | undefined | null, user: User | null | undefined): Promise<string>;
58
39
  export type User = {
59
40
  email: string;
60
41
  };
@@ -1,9 +1,11 @@
1
1
  /**
2
- * Session management utilities
2
+ * Tenant Access Control Utilities
3
+ *
4
+ * Note: Legacy JWT session functions have been removed.
5
+ * Session management is now handled by Heartwood SessionDO.
6
+ * This file only contains tenant verification functions.
3
7
  */
4
8
 
5
- import { signJwt, verifyJwt } from "./jwt.js";
6
-
7
9
  /**
8
10
  * @typedef {Object} User
9
11
  * @property {string} email
@@ -20,106 +22,6 @@ import { signJwt, verifyJwt } from "./jwt.js";
20
22
  * @property {string} email
21
23
  */
22
24
 
23
- const SESSION_COOKIE_NAME = "session";
24
- const SESSION_DURATION_SECONDS = 60 * 60 * 24 * 7; // 7 days
25
-
26
- /**
27
- * Create a session token for a user
28
- * @param {User} user - User data
29
- * @param {string} secret - Session secret
30
- * @returns {Promise<string>} - Signed JWT token
31
- */
32
- export async function createSession(user, secret) {
33
- const payload = {
34
- sub: user.email,
35
- email: user.email,
36
- exp: Math.floor(Date.now() / 1000) + SESSION_DURATION_SECONDS,
37
- };
38
-
39
- return await signJwt(payload, secret);
40
- }
41
-
42
- /**
43
- * Verify a session token and return user data
44
- * @param {string} token - Session token
45
- * @param {string} secret - Session secret
46
- * @returns {Promise<User|null>} - User data or null if invalid
47
- */
48
- export async function verifySession(token, secret) {
49
- const payload = await verifyJwt(token, secret);
50
-
51
- if (!payload || !payload.email) {
52
- return null;
53
- }
54
-
55
- return {
56
- email: payload.email,
57
- };
58
- }
59
-
60
- /**
61
- * Create Set-Cookie header value for session
62
- * @param {string} token - Session token
63
- * @param {boolean} isProduction - Whether in production (for secure flag)
64
- * @returns {string} - Cookie header value
65
- */
66
- export function createSessionCookie(token, isProduction = true) {
67
- const parts = [
68
- `${SESSION_COOKIE_NAME}=${token}`,
69
- "Path=/",
70
- `Max-Age=${SESSION_DURATION_SECONDS}`,
71
- "HttpOnly",
72
- "SameSite=Strict",
73
- ];
74
-
75
- if (isProduction) {
76
- parts.push("Secure");
77
- }
78
-
79
- return parts.join("; ");
80
- }
81
-
82
- /**
83
- * Create Set-Cookie header value to clear session
84
- * @returns {string} - Cookie header value
85
- */
86
- export function clearSessionCookie() {
87
- return `${SESSION_COOKIE_NAME}=; Path=/; Max-Age=0; HttpOnly; SameSite=Strict`;
88
- }
89
-
90
- /**
91
- * Parse session token from cookie header
92
- * @param {string} cookieHeader - Cookie header value
93
- * @returns {string|null} - Session token or null
94
- */
95
- export function parseSessionCookie(cookieHeader) {
96
- if (!cookieHeader) {
97
- return null;
98
- }
99
-
100
- /** @type {Record<string, string>} */
101
- const cookies = cookieHeader.split(";").reduce((/** @type {Record<string, string>} */ acc, /** @type {string} */ cookie) => {
102
- const [key, value] = cookie.trim().split("=");
103
- if (key && value) {
104
- acc[key] = value;
105
- }
106
- return acc;
107
- }, {});
108
-
109
- return cookies[SESSION_COOKIE_NAME] || null;
110
- }
111
-
112
- /**
113
- * Check if an email is in the allowed admin list
114
- * @param {string} email - Email address to check
115
- * @param {string} allowedList - Comma-separated list of allowed emails
116
- * @returns {boolean} - Whether the user is allowed
117
- */
118
- export function isAllowedAdmin(email, allowedList) {
119
- const allowed = allowedList.split(",").map((e) => e.trim().toLowerCase());
120
- return allowed.includes(email.toLowerCase());
121
- }
122
-
123
25
  /**
124
26
  * Verify that a user owns/has access to a tenant
125
27
  * @param {import('@cloudflare/workers-types').D1Database} db - D1 database instance
@@ -0,0 +1,373 @@
1
+ <script>
2
+ import { Bold, Italic, Link, Heading1, Heading2, Heading3, Code } from "lucide-svelte";
3
+ import { tick, onMount } from "svelte";
4
+
5
+ /**
6
+ * FloatingToolbar - Medium-style floating toolbar for text formatting
7
+ * Appears above selected text with formatting options
8
+ *
9
+ * @security This component modifies raw markdown content but does NOT sanitize input.
10
+ * The parent component MUST sanitize all content before persisting to the database
11
+ * to prevent XSS attacks. Use sanitizeMarkdown() when converting to HTML for display.
12
+ * See: $lib/utils/sanitize.js
13
+ */
14
+
15
+ // Props
16
+ /** @type {{ textareaRef?: HTMLTextAreaElement | null, content?: string, readonly?: boolean, onContentChange?: (content: string) => void }} */
17
+ let {
18
+ textareaRef = /** @type {HTMLTextAreaElement | null} */ (null),
19
+ content = $bindable(""),
20
+ readonly = false,
21
+ onContentChange = () => {},
22
+ } = $props();
23
+
24
+ // Toolbar state
25
+ let isVisible = $state(false);
26
+ let toolbarPosition = $state({ top: 0, left: 0 });
27
+ let selectionStart = $state(0);
28
+ let selectionEnd = $state(0);
29
+
30
+ /** @type {HTMLDivElement | null} */
31
+ let toolbarRef = $state(null);
32
+
33
+ // Track selection changes
34
+ function handleSelectionChange() {
35
+ if (!textareaRef || readonly) return;
36
+
37
+ const start = textareaRef.selectionStart;
38
+ const end = textareaRef.selectionEnd;
39
+
40
+ // Only show toolbar when there's actual selected text
41
+ if (start !== end && document.activeElement === textareaRef) {
42
+ selectionStart = start;
43
+ selectionEnd = end;
44
+ positionToolbar();
45
+ isVisible = true;
46
+ } else {
47
+ isVisible = false;
48
+ }
49
+ }
50
+
51
+ function positionToolbar() {
52
+ if (!textareaRef || !toolbarRef) return;
53
+
54
+ // Get textarea bounding rect
55
+ const textareaRect = textareaRef.getBoundingClientRect();
56
+
57
+ // Calculate approximate position based on selection
58
+ // For textarea, we need to estimate position based on text metrics
59
+ const textBeforeSelection = content.substring(0, selectionStart);
60
+ const lines = textBeforeSelection.split('\n');
61
+ const currentLineIndex = lines.length - 1;
62
+ const lineHeight = parseFloat(getComputedStyle(textareaRef).lineHeight) || 24;
63
+
64
+ // Calculate vertical position (above the selection)
65
+ const scrollTop = textareaRef.scrollTop;
66
+ const lineTop = currentLineIndex * lineHeight - scrollTop;
67
+ const toolbarTop = textareaRect.top + lineTop - 48; // 48px gap above selection
68
+
69
+ // Calculate horizontal center
70
+ const toolbarWidth = toolbarRef?.offsetWidth || 200;
71
+ let toolbarLeft = textareaRect.left + (textareaRect.width / 2) - (toolbarWidth / 2);
72
+
73
+ // Viewport constraints
74
+ const viewportWidth = window.innerWidth;
75
+ const viewportHeight = window.innerHeight;
76
+ const padding = 12;
77
+
78
+ // Constrain to viewport
79
+ toolbarLeft = Math.max(padding, Math.min(toolbarLeft, viewportWidth - toolbarWidth - padding));
80
+ const finalTop = Math.max(padding, Math.min(toolbarTop, viewportHeight - 60));
81
+
82
+ toolbarPosition = {
83
+ top: finalTop,
84
+ left: toolbarLeft,
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Wrap selected text with formatting markers
90
+ * @param {string} before
91
+ * @param {string} after
92
+ */
93
+ async function wrapSelection(before, after) {
94
+ if (!textareaRef) return;
95
+
96
+ const selectedText = content.substring(selectionStart, selectionEnd);
97
+ const newContent =
98
+ content.substring(0, selectionStart) +
99
+ before + selectedText + after +
100
+ content.substring(selectionEnd);
101
+
102
+ content = newContent;
103
+ onContentChange(newContent);
104
+
105
+ await tick();
106
+
107
+ // Restore selection inside the wrapped text
108
+ textareaRef.selectionStart = selectionStart + before.length;
109
+ textareaRef.selectionEnd = selectionEnd + before.length;
110
+ textareaRef.focus();
111
+
112
+ isVisible = false;
113
+ }
114
+
115
+ /**
116
+ * Insert text at beginning of selected line(s)
117
+ * @param {string} prefix
118
+ */
119
+ async function insertLinePrefix(prefix) {
120
+ if (!textareaRef) return;
121
+
122
+ // Find the start of the current line
123
+ const beforeSelection = content.substring(0, selectionStart);
124
+ const lineStart = beforeSelection.lastIndexOf('\n') + 1;
125
+
126
+ const newContent =
127
+ content.substring(0, lineStart) +
128
+ prefix +
129
+ content.substring(lineStart);
130
+
131
+ content = newContent;
132
+ onContentChange(newContent);
133
+
134
+ await tick();
135
+
136
+ // Position cursor after the prefix
137
+ const newPos = selectionStart + prefix.length;
138
+ textareaRef.selectionStart = newPos;
139
+ textareaRef.selectionEnd = selectionEnd + prefix.length;
140
+ textareaRef.focus();
141
+
142
+ isVisible = false;
143
+ }
144
+
145
+ function handleBold() {
146
+ wrapSelection("**", "**");
147
+ }
148
+
149
+ function handleItalic() {
150
+ wrapSelection("_", "_");
151
+ }
152
+
153
+ function handleCode() {
154
+ wrapSelection("`", "`");
155
+ }
156
+
157
+ function handleLink() {
158
+ wrapSelection("[", "](url)");
159
+ }
160
+
161
+ function handleH1() {
162
+ insertLinePrefix("# ");
163
+ }
164
+
165
+ function handleH2() {
166
+ insertLinePrefix("## ");
167
+ }
168
+
169
+ function handleH3() {
170
+ insertLinePrefix("### ");
171
+ }
172
+
173
+ function handleClickOutside(e) {
174
+ if (toolbarRef && !toolbarRef.contains(e.target) && e.target !== textareaRef) {
175
+ isVisible = false;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Handle keyboard shortcuts for formatting
181
+ * @param {KeyboardEvent} e
182
+ */
183
+ function handleKeyboardShortcuts(e) {
184
+ if (!textareaRef || readonly) return;
185
+ if (document.activeElement !== textareaRef) return;
186
+
187
+ const isMod = e.metaKey || e.ctrlKey;
188
+ if (!isMod) return;
189
+
190
+ // Update selection state before applying formatting
191
+ selectionStart = textareaRef.selectionStart;
192
+ selectionEnd = textareaRef.selectionEnd;
193
+
194
+ // Only apply if there's a selection
195
+ if (selectionStart === selectionEnd) return;
196
+
197
+ switch (e.key.toLowerCase()) {
198
+ case 'b':
199
+ e.preventDefault();
200
+ handleBold();
201
+ break;
202
+ case 'i':
203
+ e.preventDefault();
204
+ handleItalic();
205
+ break;
206
+ }
207
+ }
208
+
209
+ // Set up selection monitoring and keyboard shortcuts
210
+ onMount(() => {
211
+ document.addEventListener("mouseup", handleSelectionChange);
212
+ document.addEventListener("keyup", handleSelectionChange);
213
+ document.addEventListener("mousedown", handleClickOutside);
214
+ document.addEventListener("keydown", handleKeyboardShortcuts);
215
+
216
+ return () => {
217
+ document.removeEventListener("mouseup", handleSelectionChange);
218
+ document.removeEventListener("keyup", handleSelectionChange);
219
+ document.removeEventListener("mousedown", handleClickOutside);
220
+ document.removeEventListener("keydown", handleKeyboardShortcuts);
221
+ };
222
+ });
223
+
224
+ // Re-position when selection changes
225
+ $effect(() => {
226
+ if (isVisible && toolbarRef) {
227
+ positionToolbar();
228
+ }
229
+ });
230
+ </script>
231
+
232
+ {#if isVisible}
233
+ <div
234
+ bind:this={toolbarRef}
235
+ class="floating-toolbar"
236
+ style="top: {toolbarPosition.top}px; left: {toolbarPosition.left}px;"
237
+ role="toolbar"
238
+ aria-label="Text formatting toolbar"
239
+ >
240
+ <button
241
+ type="button"
242
+ class="toolbar-btn"
243
+ onclick={handleBold}
244
+ title="Bold (Cmd+B)"
245
+ aria-label="Bold"
246
+ >
247
+ <Bold size={16} />
248
+ </button>
249
+
250
+ <button
251
+ type="button"
252
+ class="toolbar-btn"
253
+ onclick={handleItalic}
254
+ title="Italic (Cmd+I)"
255
+ aria-label="Italic"
256
+ >
257
+ <Italic size={16} />
258
+ </button>
259
+
260
+ <button
261
+ type="button"
262
+ class="toolbar-btn"
263
+ onclick={handleCode}
264
+ title="Inline code"
265
+ aria-label="Code"
266
+ >
267
+ <Code size={16} />
268
+ </button>
269
+
270
+ <div class="toolbar-divider"></div>
271
+
272
+ <button
273
+ type="button"
274
+ class="toolbar-btn"
275
+ onclick={handleLink}
276
+ title="Insert link"
277
+ aria-label="Link"
278
+ >
279
+ <Link size={16} />
280
+ </button>
281
+
282
+ <div class="toolbar-divider"></div>
283
+
284
+ <button
285
+ type="button"
286
+ class="toolbar-btn"
287
+ onclick={handleH1}
288
+ title="Heading 1"
289
+ aria-label="Heading 1"
290
+ >
291
+ <Heading1 size={16} />
292
+ </button>
293
+
294
+ <button
295
+ type="button"
296
+ class="toolbar-btn"
297
+ onclick={handleH2}
298
+ title="Heading 2"
299
+ aria-label="Heading 2"
300
+ >
301
+ <Heading2 size={16} />
302
+ </button>
303
+
304
+ <button
305
+ type="button"
306
+ class="toolbar-btn"
307
+ onclick={handleH3}
308
+ title="Heading 3"
309
+ aria-label="Heading 3"
310
+ >
311
+ <Heading3 size={16} />
312
+ </button>
313
+ </div>
314
+ {/if}
315
+
316
+ <style>
317
+ .floating-toolbar {
318
+ position: fixed;
319
+ display: flex;
320
+ align-items: center;
321
+ gap: 0.25rem;
322
+ padding: 0.5rem 0.75rem;
323
+ background: rgba(30, 30, 30, 0.95);
324
+ border: 1px solid rgba(255, 255, 255, 0.1);
325
+ border-radius: 9999px;
326
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.05);
327
+ backdrop-filter: blur(8px);
328
+ z-index: 1000;
329
+ animation: toolbar-appear 0.15s ease-out;
330
+ }
331
+
332
+ @keyframes toolbar-appear {
333
+ from {
334
+ opacity: 0;
335
+ transform: translateY(4px);
336
+ }
337
+ to {
338
+ opacity: 1;
339
+ transform: translateY(0);
340
+ }
341
+ }
342
+
343
+ .toolbar-btn {
344
+ display: flex;
345
+ align-items: center;
346
+ justify-content: center;
347
+ width: 32px;
348
+ height: 32px;
349
+ padding: 0;
350
+ background: transparent;
351
+ border: none;
352
+ border-radius: 6px;
353
+ color: rgba(255, 255, 255, 0.7);
354
+ cursor: pointer;
355
+ transition: all 0.15s ease;
356
+ }
357
+
358
+ .toolbar-btn:hover {
359
+ background: rgba(255, 255, 255, 0.1);
360
+ color: rgba(255, 255, 255, 0.95);
361
+ }
362
+
363
+ .toolbar-btn:active {
364
+ transform: scale(0.95);
365
+ }
366
+
367
+ .toolbar-divider {
368
+ width: 1px;
369
+ height: 20px;
370
+ background: rgba(255, 255, 255, 0.15);
371
+ margin: 0 0.25rem;
372
+ }
373
+ </style>
@@ -0,0 +1,17 @@
1
+ export default FloatingToolbar;
2
+ type FloatingToolbar = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ declare const FloatingToolbar: import("svelte").Component<{
7
+ textareaRef?: HTMLTextAreaElement | null;
8
+ content?: string;
9
+ readonly?: boolean;
10
+ onContentChange?: (content: string) => void;
11
+ }, {}, "content">;
12
+ type $$ComponentProps = {
13
+ textareaRef?: HTMLTextAreaElement | null;
14
+ content?: string;
15
+ readonly?: boolean;
16
+ onContentChange?: (content: string) => void;
17
+ };