@autumnsgrove/groveengine 0.1.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.
- package/README.md +163 -0
- package/dist/auth/jwt.d.ts +14 -0
- package/dist/auth/jwt.js +109 -0
- package/dist/auth/session.d.ts +42 -0
- package/dist/auth/session.js +105 -0
- package/dist/components/admin/GutterManager.svelte +910 -0
- package/dist/components/admin/GutterManager.svelte.d.ts +15 -0
- package/dist/components/admin/MarkdownEditor.svelte +3114 -0
- package/dist/components/admin/MarkdownEditor.svelte.d.ts +43 -0
- package/dist/components/custom/CollapsibleSection.svelte +74 -0
- package/dist/components/custom/CollapsibleSection.svelte.d.ts +15 -0
- package/dist/components/custom/ContentWithGutter.svelte +646 -0
- package/dist/components/custom/ContentWithGutter.svelte.d.ts +19 -0
- package/dist/components/custom/GutterItem.svelte +201 -0
- package/dist/components/custom/GutterItem.svelte.d.ts +11 -0
- package/dist/components/custom/LeftGutter.svelte +271 -0
- package/dist/components/custom/LeftGutter.svelte.d.ts +17 -0
- package/dist/components/custom/MobileTOC.svelte +273 -0
- package/dist/components/custom/MobileTOC.svelte.d.ts +11 -0
- package/dist/components/custom/TableOfContents.svelte +163 -0
- package/dist/components/custom/TableOfContents.svelte.d.ts +11 -0
- package/dist/components/gallery/ImageGallery.svelte +681 -0
- package/dist/components/gallery/ImageGallery.svelte.d.ts +11 -0
- package/dist/components/gallery/Lightbox.svelte +107 -0
- package/dist/components/gallery/Lightbox.svelte.d.ts +19 -0
- package/dist/components/gallery/LightboxCaption.svelte +25 -0
- package/dist/components/gallery/LightboxCaption.svelte.d.ts +11 -0
- package/dist/components/gallery/ZoomableImage.svelte +163 -0
- package/dist/components/gallery/ZoomableImage.svelte.d.ts +17 -0
- package/dist/components/ui/Accordion.svelte +74 -0
- package/dist/components/ui/Accordion.svelte.d.ts +42 -0
- package/dist/components/ui/Badge.svelte +48 -0
- package/dist/components/ui/Badge.svelte.d.ts +26 -0
- package/dist/components/ui/Button.svelte +74 -0
- package/dist/components/ui/Button.svelte.d.ts +34 -0
- package/dist/components/ui/Card.svelte +102 -0
- package/dist/components/ui/Card.svelte.d.ts +46 -0
- package/dist/components/ui/Dialog.svelte +91 -0
- package/dist/components/ui/Dialog.svelte.d.ts +43 -0
- package/dist/components/ui/Input.svelte +81 -0
- package/dist/components/ui/Input.svelte.d.ts +35 -0
- package/dist/components/ui/Select.svelte +69 -0
- package/dist/components/ui/Select.svelte.d.ts +36 -0
- package/dist/components/ui/Sheet.svelte +98 -0
- package/dist/components/ui/Sheet.svelte.d.ts +45 -0
- package/dist/components/ui/Skeleton.svelte +31 -0
- package/dist/components/ui/Skeleton.svelte.d.ts +26 -0
- package/dist/components/ui/Table.svelte +59 -0
- package/dist/components/ui/Table.svelte.d.ts +44 -0
- package/dist/components/ui/Tabs.svelte +76 -0
- package/dist/components/ui/Tabs.svelte.d.ts +41 -0
- package/dist/components/ui/Textarea.svelte +81 -0
- package/dist/components/ui/Textarea.svelte.d.ts +35 -0
- package/dist/components/ui/Toast.svelte +18 -0
- package/dist/components/ui/Toast.svelte.d.ts +7 -0
- package/dist/components/ui/accordion/accordion-content.svelte +24 -0
- package/dist/components/ui/accordion/accordion-content.svelte.d.ts +4 -0
- package/dist/components/ui/accordion/accordion-item.svelte +12 -0
- package/dist/components/ui/accordion/accordion-item.svelte.d.ts +4 -0
- package/dist/components/ui/accordion/accordion-trigger.svelte +29 -0
- package/dist/components/ui/accordion/accordion-trigger.svelte.d.ts +7 -0
- package/dist/components/ui/accordion/index.d.ts +6 -0
- package/dist/components/ui/accordion/index.js +8 -0
- package/dist/components/ui/badge/badge.svelte +50 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +60 -0
- package/dist/components/ui/badge/index.d.ts +2 -0
- package/dist/components/ui/badge/index.js +2 -0
- package/dist/components/ui/button/button.svelte +82 -0
- package/dist/components/ui/button/button.svelte.d.ts +132 -0
- package/dist/components/ui/button/index.d.ts +2 -0
- package/dist/components/ui/button/index.js +4 -0
- package/dist/components/ui/card/card-content.svelte +16 -0
- package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-description.svelte +16 -0
- package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-footer.svelte +16 -0
- package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-header.svelte +16 -0
- package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-title.svelte +25 -0
- package/dist/components/ui/card/card-title.svelte.d.ts +8 -0
- package/dist/components/ui/card/card.svelte +20 -0
- package/dist/components/ui/card/card.svelte.d.ts +5 -0
- package/dist/components/ui/card/index.d.ts +7 -0
- package/dist/components/ui/card/index.js +9 -0
- package/dist/components/ui/dialog/dialog-content.svelte +38 -0
- package/dist/components/ui/dialog/dialog-content.svelte.d.ts +9 -0
- package/dist/components/ui/dialog/dialog-description.svelte +16 -0
- package/dist/components/ui/dialog/dialog-description.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-footer.svelte +20 -0
- package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +5 -0
- package/dist/components/ui/dialog/dialog-header.svelte +20 -0
- package/dist/components/ui/dialog/dialog-header.svelte.d.ts +5 -0
- package/dist/components/ui/dialog/dialog-overlay.svelte +19 -0
- package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-title.svelte +16 -0
- package/dist/components/ui/dialog/dialog-title.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/index.d.ts +12 -0
- package/dist/components/ui/dialog/index.js +14 -0
- package/dist/components/ui/index.d.ts +26 -0
- package/dist/components/ui/index.js +29 -0
- package/dist/components/ui/input/index.d.ts +2 -0
- package/dist/components/ui/input/index.js +4 -0
- package/dist/components/ui/input/input.svelte +46 -0
- package/dist/components/ui/input/input.svelte.d.ts +13 -0
- package/dist/components/ui/select/index.d.ts +11 -0
- package/dist/components/ui/select/index.js +13 -0
- package/dist/components/ui/select/select-content.svelte +39 -0
- package/dist/components/ui/select/select-content.svelte.d.ts +7 -0
- package/dist/components/ui/select/select-group-heading.svelte +16 -0
- package/dist/components/ui/select/select-group-heading.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-item.svelte +37 -0
- package/dist/components/ui/select/select-item.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-scroll-down-button.svelte +19 -0
- package/dist/components/ui/select/select-scroll-down-button.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-scroll-up-button.svelte +19 -0
- package/dist/components/ui/select/select-scroll-up-button.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-separator.svelte +13 -0
- package/dist/components/ui/select/select-separator.svelte.d.ts +4 -0
- package/dist/components/ui/select/select-trigger.svelte +24 -0
- package/dist/components/ui/select/select-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/separator/index.d.ts +2 -0
- package/dist/components/ui/separator/index.js +4 -0
- package/dist/components/ui/separator/separator.svelte +22 -0
- package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/index.d.ts +12 -0
- package/dist/components/ui/sheet/index.js +14 -0
- package/dist/components/ui/sheet/sheet-content.svelte +53 -0
- package/dist/components/ui/sheet/sheet-content.svelte.d.ts +62 -0
- package/dist/components/ui/sheet/sheet-description.svelte +16 -0
- package/dist/components/ui/sheet/sheet-description.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-footer.svelte +20 -0
- package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +5 -0
- package/dist/components/ui/sheet/sheet-header.svelte +20 -0
- package/dist/components/ui/sheet/sheet-header.svelte.d.ts +5 -0
- package/dist/components/ui/sheet/sheet-overlay.svelte +21 -0
- package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +6 -0
- package/dist/components/ui/sheet/sheet-title.svelte +16 -0
- package/dist/components/ui/sheet/sheet-title.svelte.d.ts +4 -0
- package/dist/components/ui/skeleton/index.d.ts +2 -0
- package/dist/components/ui/skeleton/index.js +4 -0
- package/dist/components/ui/skeleton/skeleton.svelte +17 -0
- package/dist/components/ui/skeleton/skeleton.svelte.d.ts +5 -0
- package/dist/components/ui/table/index.d.ts +9 -0
- package/dist/components/ui/table/index.js +11 -0
- package/dist/components/ui/table/table-body.svelte +16 -0
- package/dist/components/ui/table/table-body.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-caption.svelte +16 -0
- package/dist/components/ui/table/table-caption.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-cell.svelte +20 -0
- package/dist/components/ui/table/table-cell.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-footer.svelte +16 -0
- package/dist/components/ui/table/table-footer.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-head.svelte +23 -0
- package/dist/components/ui/table/table-head.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-header.svelte +16 -0
- package/dist/components/ui/table/table-header.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-row.svelte +23 -0
- package/dist/components/ui/table/table-row.svelte.d.ts +5 -0
- package/dist/components/ui/table/table.svelte +18 -0
- package/dist/components/ui/table/table.svelte.d.ts +5 -0
- package/dist/components/ui/tabs/index.d.ts +6 -0
- package/dist/components/ui/tabs/index.js +8 -0
- package/dist/components/ui/tabs/tabs-content.svelte +19 -0
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-list.svelte +19 -0
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte +19 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/textarea/index.d.ts +2 -0
- package/dist/components/ui/textarea/index.js +4 -0
- package/dist/components/ui/textarea/textarea.svelte +24 -0
- package/dist/components/ui/textarea/textarea.svelte.d.ts +6 -0
- package/dist/components/ui/toast.d.ts +86 -0
- package/dist/components/ui/toast.js +99 -0
- package/dist/db/schema.sql +238 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +20 -0
- package/dist/payments/index.d.ts +33 -0
- package/dist/payments/index.js +47 -0
- package/dist/payments/shop.d.ts +165 -0
- package/dist/payments/shop.js +588 -0
- package/dist/payments/stripe/client.d.ts +231 -0
- package/dist/payments/stripe/client.js +198 -0
- package/dist/payments/stripe/index.d.ts +18 -0
- package/dist/payments/stripe/index.js +17 -0
- package/dist/payments/stripe/provider.d.ts +50 -0
- package/dist/payments/stripe/provider.js +530 -0
- package/dist/payments/types.d.ts +355 -0
- package/dist/payments/types.js +7 -0
- package/dist/server/logger.d.ts +53 -0
- package/dist/server/logger.js +252 -0
- package/dist/styles/content.css +514 -0
- package/dist/styles/tokens.css +175 -0
- package/dist/utils/api.d.ts +20 -0
- package/dist/utils/api.js +109 -0
- package/dist/utils/cn.d.ts +15 -0
- package/dist/utils/cn.js +18 -0
- package/dist/utils/csrf.d.ts +22 -0
- package/dist/utils/csrf.js +72 -0
- package/dist/utils/debounce.d.ts +7 -0
- package/dist/utils/debounce.js +14 -0
- package/dist/utils/gallery.d.ts +66 -0
- package/dist/utils/gallery.js +181 -0
- package/dist/utils/gutter.d.ts +54 -0
- package/dist/utils/gutter.js +169 -0
- package/dist/utils/imageProcessor.d.ts +58 -0
- package/dist/utils/imageProcessor.js +205 -0
- package/dist/utils/json.d.ts +17 -0
- package/dist/utils/json.js +26 -0
- package/dist/utils/markdown.d.ts +101 -0
- package/dist/utils/markdown.js +947 -0
- package/dist/utils/sanitize.d.ts +25 -0
- package/dist/utils/sanitize.js +127 -0
- package/dist/utils/validation.d.ts +46 -0
- package/dist/utils/validation.js +169 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +5 -0
- package/package.json +129 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Lightbox from '../gallery/Lightbox.svelte';
|
|
3
|
+
import ImageGallery from '../gallery/ImageGallery.svelte';
|
|
4
|
+
import { sanitizeHTML } from '../../utils/sanitize.js';
|
|
5
|
+
|
|
6
|
+
let { item = {} } = $props();
|
|
7
|
+
|
|
8
|
+
let lightboxOpen = $state(false);
|
|
9
|
+
let lightboxSrc = $state('');
|
|
10
|
+
let lightboxAlt = $state('');
|
|
11
|
+
let lightboxCaption = $state('');
|
|
12
|
+
|
|
13
|
+
function openLightbox(src, alt, caption = '') {
|
|
14
|
+
lightboxSrc = src;
|
|
15
|
+
lightboxAlt = alt;
|
|
16
|
+
lightboxCaption = caption;
|
|
17
|
+
lightboxOpen = true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function closeLightbox() {
|
|
21
|
+
lightboxOpen = false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Handle clicks on images within markdown content
|
|
25
|
+
function handleContentClick(event) {
|
|
26
|
+
if (event.target.tagName === 'IMG') {
|
|
27
|
+
openLightbox(event.target.src, event.target.alt);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<div class="gutter-item" data-anchor={item.anchor || ''}>
|
|
33
|
+
{#if item.type === 'comment' || item.type === 'markdown'}
|
|
34
|
+
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
|
35
|
+
<div class="gutter-comment" onclick={handleContentClick}>
|
|
36
|
+
{@html sanitizeHTML(item.content)}
|
|
37
|
+
</div>
|
|
38
|
+
{:else if item.type === 'photo' || item.type === 'image'}
|
|
39
|
+
{@const imageSrc = item.src || item.url || item.file}
|
|
40
|
+
<figure class="gutter-photo">
|
|
41
|
+
<button class="image-button" onclick={() => openLightbox(imageSrc, item.caption || 'Gutter image', item.caption || '')}>
|
|
42
|
+
<img src={imageSrc} alt={item.caption || 'Gutter image'} />
|
|
43
|
+
</button>
|
|
44
|
+
{#if item.caption}
|
|
45
|
+
<figcaption>{item.caption}</figcaption>
|
|
46
|
+
{/if}
|
|
47
|
+
</figure>
|
|
48
|
+
{:else if item.type === 'gallery'}
|
|
49
|
+
<div class="gutter-gallery">
|
|
50
|
+
<ImageGallery images={item.images} />
|
|
51
|
+
</div>
|
|
52
|
+
{:else if item.type === 'emoji'}
|
|
53
|
+
<div class="gutter-emoji">
|
|
54
|
+
<img src={item.src} alt={item.alt || 'Emoji'} title={item.alt || ''} />
|
|
55
|
+
</div>
|
|
56
|
+
{/if}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<Lightbox
|
|
60
|
+
src={lightboxSrc}
|
|
61
|
+
alt={lightboxAlt}
|
|
62
|
+
caption={lightboxCaption}
|
|
63
|
+
isOpen={lightboxOpen}
|
|
64
|
+
onClose={closeLightbox}
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
<style>
|
|
68
|
+
.gutter-item {
|
|
69
|
+
margin-bottom: 1.5rem;
|
|
70
|
+
font-size: 0.875rem;
|
|
71
|
+
line-height: 1.5;
|
|
72
|
+
}
|
|
73
|
+
.gutter-comment {
|
|
74
|
+
padding: 0.75rem;
|
|
75
|
+
background: #f8f8f8;
|
|
76
|
+
border-left: 3px solid #2c5f2d;
|
|
77
|
+
border-radius: 0 6px 6px 0;
|
|
78
|
+
color: var(--light-text-secondary);
|
|
79
|
+
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
|
|
80
|
+
}
|
|
81
|
+
:global(.dark) .gutter-comment {
|
|
82
|
+
background: var(--light-bg-primary);
|
|
83
|
+
border-left-color: var(--accent-success);
|
|
84
|
+
color: var(--light-text-tertiary);
|
|
85
|
+
}
|
|
86
|
+
.gutter-comment :global(p) {
|
|
87
|
+
margin: 0 0 0.5rem 0;
|
|
88
|
+
}
|
|
89
|
+
.gutter-comment :global(p:last-child) {
|
|
90
|
+
margin-bottom: 0;
|
|
91
|
+
}
|
|
92
|
+
.gutter-comment :global(a) {
|
|
93
|
+
color: #2c5f2d;
|
|
94
|
+
text-decoration: underline;
|
|
95
|
+
}
|
|
96
|
+
:global(.dark) .gutter-comment :global(a) {
|
|
97
|
+
color: var(--accent-success);
|
|
98
|
+
}
|
|
99
|
+
.gutter-photo {
|
|
100
|
+
margin: 0;
|
|
101
|
+
}
|
|
102
|
+
.image-button {
|
|
103
|
+
padding: 0;
|
|
104
|
+
border: none;
|
|
105
|
+
background: none;
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
display: block;
|
|
108
|
+
}
|
|
109
|
+
.image-button:hover img {
|
|
110
|
+
opacity: 0.9;
|
|
111
|
+
}
|
|
112
|
+
.gutter-photo img {
|
|
113
|
+
width: 100%;
|
|
114
|
+
max-width: 160px;
|
|
115
|
+
height: auto;
|
|
116
|
+
border-radius: 6px;
|
|
117
|
+
display: block;
|
|
118
|
+
transition: opacity 0.2s;
|
|
119
|
+
}
|
|
120
|
+
/* Also constrain images in markdown comments */
|
|
121
|
+
.gutter-comment :global(img) {
|
|
122
|
+
max-width: 160px;
|
|
123
|
+
height: auto;
|
|
124
|
+
border-radius: 6px;
|
|
125
|
+
display: block;
|
|
126
|
+
margin-bottom: 0.5rem;
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
transition: opacity 0.2s;
|
|
129
|
+
}
|
|
130
|
+
.gutter-comment :global(img:hover) {
|
|
131
|
+
opacity: 0.9;
|
|
132
|
+
}
|
|
133
|
+
.gutter-photo figcaption {
|
|
134
|
+
margin-top: 0.5rem;
|
|
135
|
+
font-size: 0.75rem;
|
|
136
|
+
color: #666;
|
|
137
|
+
font-style: italic;
|
|
138
|
+
text-align: center;
|
|
139
|
+
transition: color 0.3s ease;
|
|
140
|
+
}
|
|
141
|
+
/* Gallery styles for gutter - compact version */
|
|
142
|
+
.gutter-gallery {
|
|
143
|
+
width: 100%;
|
|
144
|
+
max-width: 160px;
|
|
145
|
+
}
|
|
146
|
+
.gutter-gallery :global(.gallery-container) {
|
|
147
|
+
margin: 0;
|
|
148
|
+
}
|
|
149
|
+
.gutter-gallery :global(.gallery-image) {
|
|
150
|
+
max-height: 120px;
|
|
151
|
+
}
|
|
152
|
+
.gutter-gallery :global(.nav-button) {
|
|
153
|
+
width: 24px;
|
|
154
|
+
height: 24px;
|
|
155
|
+
}
|
|
156
|
+
.gutter-gallery :global(.nav-button svg) {
|
|
157
|
+
width: 12px;
|
|
158
|
+
height: 12px;
|
|
159
|
+
}
|
|
160
|
+
.gutter-gallery :global(.nav-prev) {
|
|
161
|
+
left: 4px;
|
|
162
|
+
}
|
|
163
|
+
.gutter-gallery :global(.nav-next) {
|
|
164
|
+
right: 4px;
|
|
165
|
+
}
|
|
166
|
+
.gutter-gallery :global(.gallery-info) {
|
|
167
|
+
padding: 4px;
|
|
168
|
+
}
|
|
169
|
+
.gutter-gallery :global(.gallery-progress) {
|
|
170
|
+
padding: 6px 0 4px;
|
|
171
|
+
}
|
|
172
|
+
.gutter-gallery :global(.progress-dot) {
|
|
173
|
+
width: 8px;
|
|
174
|
+
height: 8px;
|
|
175
|
+
}
|
|
176
|
+
.gutter-gallery :global(.progress-dot.active) {
|
|
177
|
+
width: 16px;
|
|
178
|
+
}
|
|
179
|
+
.gutter-gallery :global(.gallery-counter) {
|
|
180
|
+
font-size: 0.7rem;
|
|
181
|
+
padding-bottom: 4px;
|
|
182
|
+
}
|
|
183
|
+
.gutter-gallery :global(.gallery-caption) {
|
|
184
|
+
font-size: 0.75rem;
|
|
185
|
+
padding: 6px 8px;
|
|
186
|
+
}
|
|
187
|
+
/* Emoji styles */
|
|
188
|
+
.gutter-emoji {
|
|
189
|
+
display: flex;
|
|
190
|
+
justify-content: center;
|
|
191
|
+
padding: 0.5rem 0;
|
|
192
|
+
}
|
|
193
|
+
.gutter-emoji img {
|
|
194
|
+
width: 48px;
|
|
195
|
+
height: 48px;
|
|
196
|
+
transition: transform 0.2s;
|
|
197
|
+
}
|
|
198
|
+
.gutter-emoji img:hover {
|
|
199
|
+
transform: scale(1.15);
|
|
200
|
+
}
|
|
201
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default GutterItem;
|
|
2
|
+
type GutterItem = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const GutterItem: import("svelte").Component<{
|
|
7
|
+
item?: Record<string, any>;
|
|
8
|
+
}, {}, "">;
|
|
9
|
+
type $$ComponentProps = {
|
|
10
|
+
item?: Record<string, any>;
|
|
11
|
+
};
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { tick } from 'svelte';
|
|
3
|
+
import GutterItem from './GutterItem.svelte';
|
|
4
|
+
|
|
5
|
+
let { items = [], headers = [], contentHeight = 0, onOverflowChange = () => {} } = $props();
|
|
6
|
+
|
|
7
|
+
let gutterElement = $state();
|
|
8
|
+
let itemPositions = $state({});
|
|
9
|
+
let anchorGroupElements = $state({});
|
|
10
|
+
let overflowingAnchors = $state([]);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parse anchor string to determine anchor type and value
|
|
14
|
+
*/
|
|
15
|
+
function parseAnchor(anchor) {
|
|
16
|
+
if (!anchor) {
|
|
17
|
+
return { type: 'none', value: null };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Check for paragraph anchor: "paragraph:N"
|
|
21
|
+
const paragraphMatch = anchor.match(/^paragraph:(\d+)$/);
|
|
22
|
+
if (paragraphMatch) {
|
|
23
|
+
return { type: 'paragraph', value: parseInt(paragraphMatch[1], 10) };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check for tag anchor: "anchor:tagname"
|
|
27
|
+
const tagMatch = anchor.match(/^anchor:(\w+)$/);
|
|
28
|
+
if (tagMatch) {
|
|
29
|
+
return { type: 'tag', value: tagMatch[1] };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check for header anchor: "## Header Text"
|
|
33
|
+
const headerMatch = anchor.match(/^(#{1,6})\s+(.+)$/);
|
|
34
|
+
if (headerMatch) {
|
|
35
|
+
return { type: 'header', value: anchor };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Unknown format - treat as header for backwards compatibility
|
|
39
|
+
return { type: 'header', value: anchor };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generate a unique key for an anchor (used for grouping and positioning)
|
|
44
|
+
*/
|
|
45
|
+
function getAnchorKey(anchor) {
|
|
46
|
+
const parsed = parseAnchor(anchor);
|
|
47
|
+
switch (parsed.type) {
|
|
48
|
+
case 'header':
|
|
49
|
+
// For headers, use the header ID
|
|
50
|
+
const headerText = anchor.replace(/^#+\s*/, '');
|
|
51
|
+
const header = headers.find(h => h.text === headerText);
|
|
52
|
+
return header ? `header:${header.id}` : `header:${anchor}`;
|
|
53
|
+
case 'paragraph':
|
|
54
|
+
return `paragraph:${parsed.value}`;
|
|
55
|
+
case 'tag':
|
|
56
|
+
return `tag:${parsed.value}`;
|
|
57
|
+
default:
|
|
58
|
+
return `unknown:${anchor}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get all unique anchors from items (preserving order)
|
|
64
|
+
*/
|
|
65
|
+
function getUniqueAnchors() {
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
const anchors = [];
|
|
68
|
+
for (const item of items) {
|
|
69
|
+
if (item.anchor && !seen.has(item.anchor)) {
|
|
70
|
+
seen.add(item.anchor);
|
|
71
|
+
anchors.push(item.anchor);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return anchors;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Group items by their anchor
|
|
78
|
+
function getItemsForAnchor(anchor) {
|
|
79
|
+
return items.filter(item => item.anchor === anchor);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Get items that don't have a valid anchor (show at top)
|
|
83
|
+
function getOrphanItems() {
|
|
84
|
+
return items.filter(item => {
|
|
85
|
+
if (!item.anchor) return true;
|
|
86
|
+
const parsed = parseAnchor(item.anchor);
|
|
87
|
+
if (parsed.type === 'header') {
|
|
88
|
+
const headerText = item.anchor.replace(/^#+\s*/, '');
|
|
89
|
+
return !headers.find(h => h.text === headerText);
|
|
90
|
+
}
|
|
91
|
+
// Paragraph and tag anchors are valid if they have values
|
|
92
|
+
return parsed.type === 'none';
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Find the DOM element for an anchor
|
|
98
|
+
*/
|
|
99
|
+
function findAnchorElement(anchor) {
|
|
100
|
+
const parsed = parseAnchor(anchor);
|
|
101
|
+
const contentEl = document.querySelector('.content-body');
|
|
102
|
+
if (!contentEl) return null;
|
|
103
|
+
|
|
104
|
+
switch (parsed.type) {
|
|
105
|
+
case 'header': {
|
|
106
|
+
const headerText = anchor.replace(/^#+\s*/, '');
|
|
107
|
+
const header = headers.find(h => h.text === headerText);
|
|
108
|
+
if (header) {
|
|
109
|
+
return document.getElementById(header.id);
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
case 'paragraph': {
|
|
114
|
+
const paragraphs = contentEl.querySelectorAll('p');
|
|
115
|
+
const index = parsed.value - 1; // Convert to 0-based index
|
|
116
|
+
if (index >= 0 && index < paragraphs.length) {
|
|
117
|
+
return paragraphs[index];
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
case 'tag': {
|
|
122
|
+
return contentEl.querySelector(`[data-anchor="${parsed.value}"]`);
|
|
123
|
+
}
|
|
124
|
+
default:
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Calculate positions based on anchor locations, with collision detection
|
|
130
|
+
async function updatePositions() {
|
|
131
|
+
if (!gutterElement) return;
|
|
132
|
+
|
|
133
|
+
await tick(); // Wait for DOM to update
|
|
134
|
+
|
|
135
|
+
const gutterTop = gutterElement.offsetTop;
|
|
136
|
+
const minGap = 16; // Minimum gap between items in pixels
|
|
137
|
+
const bottomPadding = 32; // Padding from bottom of content
|
|
138
|
+
|
|
139
|
+
let lastBottom = 0; // Track the bottom edge of the last positioned item
|
|
140
|
+
const newOverflowingAnchors = [];
|
|
141
|
+
|
|
142
|
+
// Get all unique anchors that have items
|
|
143
|
+
const anchors = getUniqueAnchors();
|
|
144
|
+
|
|
145
|
+
// Sort anchors by their position in the document
|
|
146
|
+
const anchorPositions = anchors.map(anchor => {
|
|
147
|
+
const el = findAnchorElement(anchor);
|
|
148
|
+
return {
|
|
149
|
+
anchor,
|
|
150
|
+
key: getAnchorKey(anchor),
|
|
151
|
+
element: el,
|
|
152
|
+
top: el ? el.offsetTop : Infinity
|
|
153
|
+
};
|
|
154
|
+
}).sort((a, b) => a.top - b.top);
|
|
155
|
+
|
|
156
|
+
anchorPositions.forEach(({ anchor, key, element }) => {
|
|
157
|
+
const groupEl = anchorGroupElements[key];
|
|
158
|
+
|
|
159
|
+
if (element && groupEl) {
|
|
160
|
+
// Desired position (aligned with anchor element)
|
|
161
|
+
let desiredTop = element.offsetTop - gutterTop;
|
|
162
|
+
|
|
163
|
+
// Get the height of this gutter group
|
|
164
|
+
const groupHeight = groupEl.offsetHeight;
|
|
165
|
+
|
|
166
|
+
// Check for collision with previous item
|
|
167
|
+
if (desiredTop < lastBottom + minGap) {
|
|
168
|
+
// Push down to avoid overlap
|
|
169
|
+
desiredTop = lastBottom + minGap;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check if this item would overflow past the content
|
|
173
|
+
const effectiveContentHeight = contentHeight > 0 ? contentHeight : Infinity;
|
|
174
|
+
if (desiredTop + groupHeight > effectiveContentHeight - bottomPadding) {
|
|
175
|
+
// This item overflows - mark it and hide it in the gutter
|
|
176
|
+
newOverflowingAnchors.push(key);
|
|
177
|
+
itemPositions[key] = -9999; // Hide off-screen
|
|
178
|
+
} else {
|
|
179
|
+
itemPositions[key] = desiredTop;
|
|
180
|
+
// Update lastBottom for next iteration
|
|
181
|
+
lastBottom = desiredTop + groupHeight;
|
|
182
|
+
}
|
|
183
|
+
} else if (groupEl) {
|
|
184
|
+
// Element not found - hide this group
|
|
185
|
+
itemPositions[key] = -9999;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Update overflowing anchors and notify parent
|
|
190
|
+
overflowingAnchors = newOverflowingAnchors;
|
|
191
|
+
onOverflowChange(newOverflowingAnchors);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
$effect(() => {
|
|
195
|
+
// Update on resize
|
|
196
|
+
window.addEventListener('resize', updatePositions);
|
|
197
|
+
return () => {
|
|
198
|
+
window.removeEventListener('resize', updatePositions);
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Handle initial positioning and re-calculate when items, headers, or contentHeight change
|
|
203
|
+
$effect(() => {
|
|
204
|
+
// Explicitly reference dependencies to track changes
|
|
205
|
+
items;
|
|
206
|
+
headers;
|
|
207
|
+
contentHeight;
|
|
208
|
+
// Delay slightly to allow DOM updates
|
|
209
|
+
const timeout = setTimeout(updatePositions, 150);
|
|
210
|
+
return () => clearTimeout(timeout);
|
|
211
|
+
});
|
|
212
|
+
</script>
|
|
213
|
+
|
|
214
|
+
<aside class="left-gutter" bind:this={gutterElement}>
|
|
215
|
+
{#if items.length > 0}
|
|
216
|
+
<!-- Show orphan items at the top -->
|
|
217
|
+
{#each getOrphanItems() as item, index (index)}
|
|
218
|
+
<div class="gutter-item-wrapper">
|
|
219
|
+
<GutterItem {item} />
|
|
220
|
+
</div>
|
|
221
|
+
{/each}
|
|
222
|
+
|
|
223
|
+
<!-- Show items positioned by anchor -->
|
|
224
|
+
{#each getUniqueAnchors() as anchor (anchor)}
|
|
225
|
+
{@const anchorKey = getAnchorKey(anchor)}
|
|
226
|
+
{@const anchorItems = getItemsForAnchor(anchor)}
|
|
227
|
+
{#if anchorItems.length > 0}
|
|
228
|
+
<div
|
|
229
|
+
class="anchor-group"
|
|
230
|
+
data-for-anchor={anchorKey}
|
|
231
|
+
style="top: {itemPositions[anchorKey] || 0}px"
|
|
232
|
+
bind:this={anchorGroupElements[anchorKey]}
|
|
233
|
+
>
|
|
234
|
+
{#each anchorItems as item, index (index)}
|
|
235
|
+
<GutterItem {item} />
|
|
236
|
+
{/each}
|
|
237
|
+
</div>
|
|
238
|
+
{/if}
|
|
239
|
+
{/each}
|
|
240
|
+
{/if}
|
|
241
|
+
</aside>
|
|
242
|
+
|
|
243
|
+
<style>
|
|
244
|
+
.left-gutter {
|
|
245
|
+
position: relative;
|
|
246
|
+
padding: 1rem;
|
|
247
|
+
min-height: 100%;
|
|
248
|
+
}
|
|
249
|
+
.gutter-item-wrapper {
|
|
250
|
+
margin-bottom: 1rem;
|
|
251
|
+
}
|
|
252
|
+
.anchor-group {
|
|
253
|
+
position: absolute;
|
|
254
|
+
left: 1rem;
|
|
255
|
+
right: 1rem;
|
|
256
|
+
}
|
|
257
|
+
/* Scrollbar styling */
|
|
258
|
+
.left-gutter::-webkit-scrollbar {
|
|
259
|
+
width: 4px;
|
|
260
|
+
}
|
|
261
|
+
.left-gutter::-webkit-scrollbar-track {
|
|
262
|
+
background: transparent;
|
|
263
|
+
}
|
|
264
|
+
.left-gutter::-webkit-scrollbar-thumb {
|
|
265
|
+
background: var(--light-text-secondary);
|
|
266
|
+
border-radius: 2px;
|
|
267
|
+
}
|
|
268
|
+
:global(.dark) .left-gutter::-webkit-scrollbar-thumb {
|
|
269
|
+
background: var(--light-border-light);
|
|
270
|
+
}
|
|
271
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default LeftGutter;
|
|
2
|
+
type LeftGutter = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const LeftGutter: import("svelte").Component<{
|
|
7
|
+
items?: any[];
|
|
8
|
+
headers?: any[];
|
|
9
|
+
contentHeight?: number;
|
|
10
|
+
onOverflowChange?: Function;
|
|
11
|
+
}, {}, "">;
|
|
12
|
+
type $$ComponentProps = {
|
|
13
|
+
items?: any[];
|
|
14
|
+
headers?: any[];
|
|
15
|
+
contentHeight?: number;
|
|
16
|
+
onOverflowChange?: Function;
|
|
17
|
+
};
|