@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.
Files changed (219) hide show
  1. package/README.md +163 -0
  2. package/dist/auth/jwt.d.ts +14 -0
  3. package/dist/auth/jwt.js +109 -0
  4. package/dist/auth/session.d.ts +42 -0
  5. package/dist/auth/session.js +105 -0
  6. package/dist/components/admin/GutterManager.svelte +910 -0
  7. package/dist/components/admin/GutterManager.svelte.d.ts +15 -0
  8. package/dist/components/admin/MarkdownEditor.svelte +3114 -0
  9. package/dist/components/admin/MarkdownEditor.svelte.d.ts +43 -0
  10. package/dist/components/custom/CollapsibleSection.svelte +74 -0
  11. package/dist/components/custom/CollapsibleSection.svelte.d.ts +15 -0
  12. package/dist/components/custom/ContentWithGutter.svelte +646 -0
  13. package/dist/components/custom/ContentWithGutter.svelte.d.ts +19 -0
  14. package/dist/components/custom/GutterItem.svelte +201 -0
  15. package/dist/components/custom/GutterItem.svelte.d.ts +11 -0
  16. package/dist/components/custom/LeftGutter.svelte +271 -0
  17. package/dist/components/custom/LeftGutter.svelte.d.ts +17 -0
  18. package/dist/components/custom/MobileTOC.svelte +273 -0
  19. package/dist/components/custom/MobileTOC.svelte.d.ts +11 -0
  20. package/dist/components/custom/TableOfContents.svelte +163 -0
  21. package/dist/components/custom/TableOfContents.svelte.d.ts +11 -0
  22. package/dist/components/gallery/ImageGallery.svelte +681 -0
  23. package/dist/components/gallery/ImageGallery.svelte.d.ts +11 -0
  24. package/dist/components/gallery/Lightbox.svelte +107 -0
  25. package/dist/components/gallery/Lightbox.svelte.d.ts +19 -0
  26. package/dist/components/gallery/LightboxCaption.svelte +25 -0
  27. package/dist/components/gallery/LightboxCaption.svelte.d.ts +11 -0
  28. package/dist/components/gallery/ZoomableImage.svelte +163 -0
  29. package/dist/components/gallery/ZoomableImage.svelte.d.ts +17 -0
  30. package/dist/components/ui/Accordion.svelte +74 -0
  31. package/dist/components/ui/Accordion.svelte.d.ts +42 -0
  32. package/dist/components/ui/Badge.svelte +48 -0
  33. package/dist/components/ui/Badge.svelte.d.ts +26 -0
  34. package/dist/components/ui/Button.svelte +74 -0
  35. package/dist/components/ui/Button.svelte.d.ts +34 -0
  36. package/dist/components/ui/Card.svelte +102 -0
  37. package/dist/components/ui/Card.svelte.d.ts +46 -0
  38. package/dist/components/ui/Dialog.svelte +91 -0
  39. package/dist/components/ui/Dialog.svelte.d.ts +43 -0
  40. package/dist/components/ui/Input.svelte +81 -0
  41. package/dist/components/ui/Input.svelte.d.ts +35 -0
  42. package/dist/components/ui/Select.svelte +69 -0
  43. package/dist/components/ui/Select.svelte.d.ts +36 -0
  44. package/dist/components/ui/Sheet.svelte +98 -0
  45. package/dist/components/ui/Sheet.svelte.d.ts +45 -0
  46. package/dist/components/ui/Skeleton.svelte +31 -0
  47. package/dist/components/ui/Skeleton.svelte.d.ts +26 -0
  48. package/dist/components/ui/Table.svelte +59 -0
  49. package/dist/components/ui/Table.svelte.d.ts +44 -0
  50. package/dist/components/ui/Tabs.svelte +76 -0
  51. package/dist/components/ui/Tabs.svelte.d.ts +41 -0
  52. package/dist/components/ui/Textarea.svelte +81 -0
  53. package/dist/components/ui/Textarea.svelte.d.ts +35 -0
  54. package/dist/components/ui/Toast.svelte +18 -0
  55. package/dist/components/ui/Toast.svelte.d.ts +7 -0
  56. package/dist/components/ui/accordion/accordion-content.svelte +24 -0
  57. package/dist/components/ui/accordion/accordion-content.svelte.d.ts +4 -0
  58. package/dist/components/ui/accordion/accordion-item.svelte +12 -0
  59. package/dist/components/ui/accordion/accordion-item.svelte.d.ts +4 -0
  60. package/dist/components/ui/accordion/accordion-trigger.svelte +29 -0
  61. package/dist/components/ui/accordion/accordion-trigger.svelte.d.ts +7 -0
  62. package/dist/components/ui/accordion/index.d.ts +6 -0
  63. package/dist/components/ui/accordion/index.js +8 -0
  64. package/dist/components/ui/badge/badge.svelte +50 -0
  65. package/dist/components/ui/badge/badge.svelte.d.ts +60 -0
  66. package/dist/components/ui/badge/index.d.ts +2 -0
  67. package/dist/components/ui/badge/index.js +2 -0
  68. package/dist/components/ui/button/button.svelte +82 -0
  69. package/dist/components/ui/button/button.svelte.d.ts +132 -0
  70. package/dist/components/ui/button/index.d.ts +2 -0
  71. package/dist/components/ui/button/index.js +4 -0
  72. package/dist/components/ui/card/card-content.svelte +16 -0
  73. package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
  74. package/dist/components/ui/card/card-description.svelte +16 -0
  75. package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
  76. package/dist/components/ui/card/card-footer.svelte +16 -0
  77. package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
  78. package/dist/components/ui/card/card-header.svelte +16 -0
  79. package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
  80. package/dist/components/ui/card/card-title.svelte +25 -0
  81. package/dist/components/ui/card/card-title.svelte.d.ts +8 -0
  82. package/dist/components/ui/card/card.svelte +20 -0
  83. package/dist/components/ui/card/card.svelte.d.ts +5 -0
  84. package/dist/components/ui/card/index.d.ts +7 -0
  85. package/dist/components/ui/card/index.js +9 -0
  86. package/dist/components/ui/dialog/dialog-content.svelte +38 -0
  87. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +9 -0
  88. package/dist/components/ui/dialog/dialog-description.svelte +16 -0
  89. package/dist/components/ui/dialog/dialog-description.svelte.d.ts +4 -0
  90. package/dist/components/ui/dialog/dialog-footer.svelte +20 -0
  91. package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +5 -0
  92. package/dist/components/ui/dialog/dialog-header.svelte +20 -0
  93. package/dist/components/ui/dialog/dialog-header.svelte.d.ts +5 -0
  94. package/dist/components/ui/dialog/dialog-overlay.svelte +19 -0
  95. package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
  96. package/dist/components/ui/dialog/dialog-title.svelte +16 -0
  97. package/dist/components/ui/dialog/dialog-title.svelte.d.ts +4 -0
  98. package/dist/components/ui/dialog/index.d.ts +12 -0
  99. package/dist/components/ui/dialog/index.js +14 -0
  100. package/dist/components/ui/index.d.ts +26 -0
  101. package/dist/components/ui/index.js +29 -0
  102. package/dist/components/ui/input/index.d.ts +2 -0
  103. package/dist/components/ui/input/index.js +4 -0
  104. package/dist/components/ui/input/input.svelte +46 -0
  105. package/dist/components/ui/input/input.svelte.d.ts +13 -0
  106. package/dist/components/ui/select/index.d.ts +11 -0
  107. package/dist/components/ui/select/index.js +13 -0
  108. package/dist/components/ui/select/select-content.svelte +39 -0
  109. package/dist/components/ui/select/select-content.svelte.d.ts +7 -0
  110. package/dist/components/ui/select/select-group-heading.svelte +16 -0
  111. package/dist/components/ui/select/select-group-heading.svelte.d.ts +4 -0
  112. package/dist/components/ui/select/select-item.svelte +37 -0
  113. package/dist/components/ui/select/select-item.svelte.d.ts +4 -0
  114. package/dist/components/ui/select/select-scroll-down-button.svelte +19 -0
  115. package/dist/components/ui/select/select-scroll-down-button.svelte.d.ts +4 -0
  116. package/dist/components/ui/select/select-scroll-up-button.svelte +19 -0
  117. package/dist/components/ui/select/select-scroll-up-button.svelte.d.ts +4 -0
  118. package/dist/components/ui/select/select-separator.svelte +13 -0
  119. package/dist/components/ui/select/select-separator.svelte.d.ts +4 -0
  120. package/dist/components/ui/select/select-trigger.svelte +24 -0
  121. package/dist/components/ui/select/select-trigger.svelte.d.ts +4 -0
  122. package/dist/components/ui/separator/index.d.ts +2 -0
  123. package/dist/components/ui/separator/index.js +4 -0
  124. package/dist/components/ui/separator/separator.svelte +22 -0
  125. package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
  126. package/dist/components/ui/sheet/index.d.ts +12 -0
  127. package/dist/components/ui/sheet/index.js +14 -0
  128. package/dist/components/ui/sheet/sheet-content.svelte +53 -0
  129. package/dist/components/ui/sheet/sheet-content.svelte.d.ts +62 -0
  130. package/dist/components/ui/sheet/sheet-description.svelte +16 -0
  131. package/dist/components/ui/sheet/sheet-description.svelte.d.ts +4 -0
  132. package/dist/components/ui/sheet/sheet-footer.svelte +20 -0
  133. package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +5 -0
  134. package/dist/components/ui/sheet/sheet-header.svelte +20 -0
  135. package/dist/components/ui/sheet/sheet-header.svelte.d.ts +5 -0
  136. package/dist/components/ui/sheet/sheet-overlay.svelte +21 -0
  137. package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +6 -0
  138. package/dist/components/ui/sheet/sheet-title.svelte +16 -0
  139. package/dist/components/ui/sheet/sheet-title.svelte.d.ts +4 -0
  140. package/dist/components/ui/skeleton/index.d.ts +2 -0
  141. package/dist/components/ui/skeleton/index.js +4 -0
  142. package/dist/components/ui/skeleton/skeleton.svelte +17 -0
  143. package/dist/components/ui/skeleton/skeleton.svelte.d.ts +5 -0
  144. package/dist/components/ui/table/index.d.ts +9 -0
  145. package/dist/components/ui/table/index.js +11 -0
  146. package/dist/components/ui/table/table-body.svelte +16 -0
  147. package/dist/components/ui/table/table-body.svelte.d.ts +5 -0
  148. package/dist/components/ui/table/table-caption.svelte +16 -0
  149. package/dist/components/ui/table/table-caption.svelte.d.ts +5 -0
  150. package/dist/components/ui/table/table-cell.svelte +20 -0
  151. package/dist/components/ui/table/table-cell.svelte.d.ts +5 -0
  152. package/dist/components/ui/table/table-footer.svelte +16 -0
  153. package/dist/components/ui/table/table-footer.svelte.d.ts +5 -0
  154. package/dist/components/ui/table/table-head.svelte +23 -0
  155. package/dist/components/ui/table/table-head.svelte.d.ts +5 -0
  156. package/dist/components/ui/table/table-header.svelte +16 -0
  157. package/dist/components/ui/table/table-header.svelte.d.ts +5 -0
  158. package/dist/components/ui/table/table-row.svelte +23 -0
  159. package/dist/components/ui/table/table-row.svelte.d.ts +5 -0
  160. package/dist/components/ui/table/table.svelte +18 -0
  161. package/dist/components/ui/table/table.svelte.d.ts +5 -0
  162. package/dist/components/ui/tabs/index.d.ts +6 -0
  163. package/dist/components/ui/tabs/index.js +8 -0
  164. package/dist/components/ui/tabs/tabs-content.svelte +19 -0
  165. package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
  166. package/dist/components/ui/tabs/tabs-list.svelte +19 -0
  167. package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
  168. package/dist/components/ui/tabs/tabs-trigger.svelte +19 -0
  169. package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
  170. package/dist/components/ui/textarea/index.d.ts +2 -0
  171. package/dist/components/ui/textarea/index.js +4 -0
  172. package/dist/components/ui/textarea/textarea.svelte +24 -0
  173. package/dist/components/ui/textarea/textarea.svelte.d.ts +6 -0
  174. package/dist/components/ui/toast.d.ts +86 -0
  175. package/dist/components/ui/toast.js +99 -0
  176. package/dist/db/schema.sql +238 -0
  177. package/dist/index.d.ts +14 -0
  178. package/dist/index.js +20 -0
  179. package/dist/payments/index.d.ts +33 -0
  180. package/dist/payments/index.js +47 -0
  181. package/dist/payments/shop.d.ts +165 -0
  182. package/dist/payments/shop.js +588 -0
  183. package/dist/payments/stripe/client.d.ts +231 -0
  184. package/dist/payments/stripe/client.js +198 -0
  185. package/dist/payments/stripe/index.d.ts +18 -0
  186. package/dist/payments/stripe/index.js +17 -0
  187. package/dist/payments/stripe/provider.d.ts +50 -0
  188. package/dist/payments/stripe/provider.js +530 -0
  189. package/dist/payments/types.d.ts +355 -0
  190. package/dist/payments/types.js +7 -0
  191. package/dist/server/logger.d.ts +53 -0
  192. package/dist/server/logger.js +252 -0
  193. package/dist/styles/content.css +514 -0
  194. package/dist/styles/tokens.css +175 -0
  195. package/dist/utils/api.d.ts +20 -0
  196. package/dist/utils/api.js +109 -0
  197. package/dist/utils/cn.d.ts +15 -0
  198. package/dist/utils/cn.js +18 -0
  199. package/dist/utils/csrf.d.ts +22 -0
  200. package/dist/utils/csrf.js +72 -0
  201. package/dist/utils/debounce.d.ts +7 -0
  202. package/dist/utils/debounce.js +14 -0
  203. package/dist/utils/gallery.d.ts +66 -0
  204. package/dist/utils/gallery.js +181 -0
  205. package/dist/utils/gutter.d.ts +54 -0
  206. package/dist/utils/gutter.js +169 -0
  207. package/dist/utils/imageProcessor.d.ts +58 -0
  208. package/dist/utils/imageProcessor.js +205 -0
  209. package/dist/utils/json.d.ts +17 -0
  210. package/dist/utils/json.js +26 -0
  211. package/dist/utils/markdown.d.ts +101 -0
  212. package/dist/utils/markdown.js +947 -0
  213. package/dist/utils/sanitize.d.ts +25 -0
  214. package/dist/utils/sanitize.js +127 -0
  215. package/dist/utils/validation.d.ts +46 -0
  216. package/dist/utils/validation.js +169 -0
  217. package/dist/utils.d.ts +5 -0
  218. package/dist/utils.js +5 -0
  219. 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
+ };