@autumnsgrove/groveengine 0.8.0 → 0.8.6
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/OnboardingChecklist.svelte +2 -2
- package/dist/components/WispButton.svelte +83 -0
- package/dist/components/WispButton.svelte.d.ts +49 -0
- package/dist/components/WispPanel.svelte +1092 -0
- package/dist/components/WispPanel.svelte.d.ts +49 -0
- package/dist/components/custom/ContentWithGutter.svelte +7 -13
- package/dist/components/custom/TableOfContents.svelte +12 -1
- package/dist/components/quota/UpgradePrompt.svelte +1 -0
- package/dist/config/wisp.d.ts +145 -0
- package/dist/config/wisp.js +175 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/server/inference-client.d.ts +139 -0
- package/dist/server/inference-client.js +294 -0
- package/dist/ui/components/forms/SearchInput.svelte +0 -1
- package/dist/ui/components/gallery/ImageGallery.svelte +14 -3
- package/dist/ui/components/gallery/Lightbox.svelte +8 -3
- package/dist/ui/components/gallery/ZoomableImage.svelte +12 -2
- package/dist/ui/components/nature/Logo.svelte +55 -19
- package/dist/ui/components/nature/botanical/LeafFalling.svelte +2 -2
- package/dist/ui/components/nature/botanical/PetalFalling.svelte +7 -7
- package/dist/ui/components/nature/ground/Crocus.svelte +3 -3
- package/dist/ui/components/nature/ground/Daffodil.svelte +3 -3
- package/dist/ui/components/nature/ground/Tulip.svelte +5 -5
- package/dist/ui/components/nature/palette.d.ts +187 -76
- package/dist/ui/components/nature/palette.js +169 -81
- package/dist/ui/components/nature/trees/TreeCherry.svelte +3 -3
- package/dist/ui/components/nature/trees/TreeCherry.svelte.d.ts +1 -1
- package/dist/ui/components/nature/trees/TreePine.svelte +2 -2
- package/dist/ui/components/nature/trees/TreePine.svelte.d.ts +1 -1
- package/dist/ui/components/primitives/textarea/textarea.svelte +1 -1
- package/dist/ui/components/typography/Alagard.svelte +17 -0
- package/dist/ui/components/typography/Alagard.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Atkinson.svelte +17 -0
- package/dist/ui/components/typography/Atkinson.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Calistoga.svelte +17 -0
- package/dist/ui/components/typography/Calistoga.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Caveat.svelte +17 -0
- package/dist/ui/components/typography/Caveat.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Cozette.svelte +17 -0
- package/dist/ui/components/typography/Cozette.svelte.d.ts +10 -0
- package/dist/ui/components/typography/FontProvider.svelte +98 -0
- package/dist/ui/components/typography/FontProvider.svelte.d.ts +17 -0
- package/dist/ui/components/typography/IBMPlexMono.svelte +17 -0
- package/dist/ui/components/typography/IBMPlexMono.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Lexend.svelte +17 -0
- package/dist/ui/components/typography/Lexend.svelte.d.ts +10 -0
- package/dist/ui/components/typography/OpenDyslexic.svelte +17 -0
- package/dist/ui/components/typography/OpenDyslexic.svelte.d.ts +10 -0
- package/dist/ui/components/typography/PlusJakartaSans.svelte +17 -0
- package/dist/ui/components/typography/PlusJakartaSans.svelte.d.ts +10 -0
- package/dist/ui/components/typography/Quicksand.svelte +17 -0
- package/dist/ui/components/typography/Quicksand.svelte.d.ts +10 -0
- package/dist/ui/components/typography/README.md +153 -0
- package/dist/ui/components/typography/index.d.ts +13 -0
- package/dist/ui/components/typography/index.js +31 -0
- package/dist/ui/components/ui/CollapsibleSection.svelte +10 -0
- package/dist/ui/components/ui/GlassCarousel.svelte +446 -0
- package/dist/ui/components/ui/GlassCarousel.svelte.d.ts +57 -0
- package/dist/ui/components/ui/GlassConfirmDialog.svelte +2 -1
- package/dist/ui/components/ui/GlassLogo.svelte +2 -1
- package/dist/ui/components/ui/GlassOverlay.svelte +1 -1
- package/dist/ui/components/ui/index.d.ts +1 -0
- package/dist/ui/components/ui/index.js +1 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui/tokens/fonts.d.ts +1 -1
- package/dist/ui/tokens/fonts.js +0 -126
- package/dist/ui/vineyard/index.d.ts +9 -0
- package/dist/ui/vineyard/index.js +8 -0
- package/dist/utils/csrf.js +5 -2
- package/dist/utils/readability.d.ts +89 -0
- package/dist/utils/readability.js +204 -0
- package/package.json +38 -21
- package/static/fonts/alagard.ttf +0 -0
- package/LICENSE +0 -378
package/dist/ui/tokens/fonts.js
CHANGED
|
@@ -61,39 +61,7 @@ export const fonts = [
|
|
|
61
61
|
"sans-serif",
|
|
62
62
|
],
|
|
63
63
|
},
|
|
64
|
-
{
|
|
65
|
-
id: "luciole",
|
|
66
|
-
name: "Luciole",
|
|
67
|
-
file: "Luciole-Regular.ttf",
|
|
68
|
-
format: "truetype",
|
|
69
|
-
fontFamily: "Luciole",
|
|
70
|
-
category: "accessibility",
|
|
71
|
-
description: "French accessibility font designed for visually impaired readers.",
|
|
72
|
-
fallback: [
|
|
73
|
-
"-apple-system",
|
|
74
|
-
"BlinkMacSystemFont",
|
|
75
|
-
"Segoe UI",
|
|
76
|
-
"Roboto",
|
|
77
|
-
"sans-serif",
|
|
78
|
-
],
|
|
79
|
-
},
|
|
80
64
|
// Modern sans-serif fonts
|
|
81
|
-
{
|
|
82
|
-
id: "nunito",
|
|
83
|
-
name: "Nunito",
|
|
84
|
-
file: "Nunito-Regular.ttf",
|
|
85
|
-
format: "truetype",
|
|
86
|
-
fontFamily: "Nunito",
|
|
87
|
-
category: "sans-serif",
|
|
88
|
-
description: "Friendly rounded sans-serif. Warm and approachable.",
|
|
89
|
-
fallback: [
|
|
90
|
-
"-apple-system",
|
|
91
|
-
"BlinkMacSystemFont",
|
|
92
|
-
"Segoe UI",
|
|
93
|
-
"Roboto",
|
|
94
|
-
"sans-serif",
|
|
95
|
-
],
|
|
96
|
-
},
|
|
97
65
|
{
|
|
98
66
|
id: "quicksand",
|
|
99
67
|
name: "Quicksand",
|
|
@@ -110,38 +78,6 @@ export const fonts = [
|
|
|
110
78
|
"sans-serif",
|
|
111
79
|
],
|
|
112
80
|
},
|
|
113
|
-
{
|
|
114
|
-
id: "manrope",
|
|
115
|
-
name: "Manrope",
|
|
116
|
-
file: "Manrope-Regular.ttf",
|
|
117
|
-
format: "truetype",
|
|
118
|
-
fontFamily: "Manrope",
|
|
119
|
-
category: "sans-serif",
|
|
120
|
-
description: "Professional geometric sans. Clean and contemporary.",
|
|
121
|
-
fallback: [
|
|
122
|
-
"-apple-system",
|
|
123
|
-
"BlinkMacSystemFont",
|
|
124
|
-
"Segoe UI",
|
|
125
|
-
"Roboto",
|
|
126
|
-
"sans-serif",
|
|
127
|
-
],
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
id: "instrument-sans",
|
|
131
|
-
name: "Instrument Sans",
|
|
132
|
-
file: "InstrumentSans-Regular.ttf",
|
|
133
|
-
format: "truetype",
|
|
134
|
-
fontFamily: "Instrument Sans",
|
|
135
|
-
category: "sans-serif",
|
|
136
|
-
description: "Low contrast sans with humanist touches. Elegant simplicity.",
|
|
137
|
-
fallback: [
|
|
138
|
-
"-apple-system",
|
|
139
|
-
"BlinkMacSystemFont",
|
|
140
|
-
"Segoe UI",
|
|
141
|
-
"Roboto",
|
|
142
|
-
"sans-serif",
|
|
143
|
-
],
|
|
144
|
-
},
|
|
145
81
|
{
|
|
146
82
|
id: "plus-jakarta-sans",
|
|
147
83
|
name: "Plus Jakarta Sans",
|
|
@@ -158,67 +94,6 @@ export const fonts = [
|
|
|
158
94
|
"sans-serif",
|
|
159
95
|
],
|
|
160
96
|
},
|
|
161
|
-
// Serif fonts
|
|
162
|
-
{
|
|
163
|
-
id: "cormorant",
|
|
164
|
-
name: "Cormorant",
|
|
165
|
-
file: "Cormorant-Regular.ttf",
|
|
166
|
-
format: "truetype",
|
|
167
|
-
fontFamily: "Cormorant",
|
|
168
|
-
category: "serif",
|
|
169
|
-
description: "Elegant display serif inspired by Garamond. Refined and classic.",
|
|
170
|
-
fallback: ["Georgia", "Times New Roman", "serif"],
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
id: "bodoni-moda",
|
|
174
|
-
name: "Bodoni Moda",
|
|
175
|
-
file: "BodoniModa-Regular.ttf",
|
|
176
|
-
format: "truetype",
|
|
177
|
-
fontFamily: "Bodoni Moda",
|
|
178
|
-
category: "serif",
|
|
179
|
-
description: "High contrast modern serif. Bold and sophisticated.",
|
|
180
|
-
fallback: ["Georgia", "Times New Roman", "serif"],
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
id: "lora",
|
|
184
|
-
name: "Lora",
|
|
185
|
-
file: "Lora-Regular.ttf",
|
|
186
|
-
format: "truetype",
|
|
187
|
-
fontFamily: "Lora",
|
|
188
|
-
category: "serif",
|
|
189
|
-
description: "Well-balanced contemporary serif. Excellent for body text.",
|
|
190
|
-
fallback: ["Georgia", "Times New Roman", "serif"],
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
id: "eb-garamond",
|
|
194
|
-
name: "EB Garamond",
|
|
195
|
-
file: "EBGaramond-Regular.ttf",
|
|
196
|
-
format: "truetype",
|
|
197
|
-
fontFamily: "EB Garamond",
|
|
198
|
-
category: "serif",
|
|
199
|
-
description: "Revival of classic Garamond. Timeless book typography.",
|
|
200
|
-
fallback: ["Georgia", "Times New Roman", "serif"],
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
id: "merriweather",
|
|
204
|
-
name: "Merriweather",
|
|
205
|
-
file: "Merriweather-Regular.ttf",
|
|
206
|
-
format: "truetype",
|
|
207
|
-
fontFamily: "Merriweather",
|
|
208
|
-
category: "serif",
|
|
209
|
-
description: "Designed for screen reading. Excellent legibility.",
|
|
210
|
-
fallback: ["Georgia", "Times New Roman", "serif"],
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
id: "fraunces",
|
|
214
|
-
name: "Fraunces",
|
|
215
|
-
file: "Fraunces-Regular.ttf",
|
|
216
|
-
format: "truetype",
|
|
217
|
-
fontFamily: "Fraunces",
|
|
218
|
-
category: "serif",
|
|
219
|
-
description: 'Soft serif with "wonky" optical axes. Warm personality.',
|
|
220
|
-
fallback: ["Georgia", "Times New Roman", "serif"],
|
|
221
|
-
},
|
|
222
97
|
// Monospace fonts
|
|
223
98
|
{
|
|
224
99
|
id: "ibm-plex-mono",
|
|
@@ -335,7 +210,6 @@ export const fontCategoryLabels = {
|
|
|
335
210
|
default: "Default",
|
|
336
211
|
accessibility: "Accessibility",
|
|
337
212
|
"sans-serif": "Sans-Serif",
|
|
338
|
-
serif: "Serif",
|
|
339
213
|
monospace: "Monospace",
|
|
340
214
|
display: "Display & Special",
|
|
341
215
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vineyard Components
|
|
3
|
+
* Re-exports from @autumnsgrove/vineyard package
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { VineyardLayout, FeatureCard, StatusBadge } from '@autumnsgrove/groveengine/vineyard';
|
|
7
|
+
*/
|
|
8
|
+
export { VineyardLayout, FeatureCard, StatusBadge, DemoContainer, CodeExample, TierGate, RoadmapSection, } from "@autumnsgrove/vineyard/vineyard";
|
|
9
|
+
export type { VineyardStatus, GroveTool, GroveTier, VineyardLayoutProps, FeatureCardProps, StatusBadgeProps, DemoContainerProps, CodeExampleProps, TierGateProps, RoadmapSectionProps, } from "@autumnsgrove/vineyard/vineyard";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vineyard Components
|
|
3
|
+
* Re-exports from @autumnsgrove/vineyard package
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { VineyardLayout, FeatureCard, StatusBadge } from '@autumnsgrove/groveengine/vineyard';
|
|
7
|
+
*/
|
|
8
|
+
export { VineyardLayout, FeatureCard, StatusBadge, DemoContainer, CodeExample, TierGate, RoadmapSection, } from "@autumnsgrove/vineyard/vineyard";
|
package/dist/utils/csrf.js
CHANGED
|
@@ -44,7 +44,8 @@ export function validateCSRF(request) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
const origin = request.headers.get("origin");
|
|
47
|
-
|
|
47
|
+
// Check X-Forwarded-Host first (set by grove-router proxy), then fall back to host
|
|
48
|
+
const host = request.headers.get("x-forwarded-host") || request.headers.get("host");
|
|
48
49
|
|
|
49
50
|
// Allow same-origin requests
|
|
50
51
|
if (origin) {
|
|
@@ -65,9 +66,11 @@ export function validateCSRF(request) {
|
|
|
65
66
|
return false;
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
// Check if origin matches host OR is a *.grove.place subdomain
|
|
68
70
|
const hostMatches = host && originUrl.host === host;
|
|
71
|
+
const isGroveDomain = originUrl.hostname.endsWith(".grove.place") || originUrl.hostname === "grove.place";
|
|
69
72
|
|
|
70
|
-
if (!isLocalhost && !hostMatches) {
|
|
73
|
+
if (!isLocalhost && !hostMatches && !isGroveDomain) {
|
|
71
74
|
return false;
|
|
72
75
|
}
|
|
73
76
|
} catch {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readability Analysis - Local Calculations
|
|
3
|
+
*
|
|
4
|
+
* Readability scoring using Flesch-Kincaid and other metrics.
|
|
5
|
+
* No AI needed - purely algorithmic analysis.
|
|
6
|
+
*
|
|
7
|
+
* @see docs/specs/writing-assistant-unified-spec.md
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} ReadabilityResult
|
|
11
|
+
* @property {number} fleschKincaid - Grade level (e.g., 8.5)
|
|
12
|
+
* @property {string} readingTime - Human-readable time (e.g., "5 min read")
|
|
13
|
+
* @property {number} wordCount - Total words
|
|
14
|
+
* @property {number} sentenceCount - Total sentences
|
|
15
|
+
* @property {Object} sentenceStats - Sentence statistics
|
|
16
|
+
* @property {number} sentenceStats.average - Average words per sentence
|
|
17
|
+
* @property {number} sentenceStats.longest - Longest sentence word count
|
|
18
|
+
* @property {number} sentenceStats.shortest - Shortest sentence word count
|
|
19
|
+
* @property {string[]} suggestions - Improvement suggestions
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Calculate readability metrics for content
|
|
23
|
+
*
|
|
24
|
+
* @param {string} content - The text to analyze (can include markdown)
|
|
25
|
+
* @returns {ReadabilityResult}
|
|
26
|
+
*/
|
|
27
|
+
export function calculateReadability(content: string): ReadabilityResult;
|
|
28
|
+
/**
|
|
29
|
+
* Strip markdown syntax for cleaner readability analysis
|
|
30
|
+
*
|
|
31
|
+
* @param {string} content - Markdown content
|
|
32
|
+
* @returns {string} Plain text
|
|
33
|
+
*/
|
|
34
|
+
export function stripMarkdownForAnalysis(content: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Count syllables in a word (approximate)
|
|
37
|
+
*
|
|
38
|
+
* NOTE: This is a regex-based approximation that works reasonably well for
|
|
39
|
+
* common English words but will be inaccurate for:
|
|
40
|
+
* - Words with silent vowels (e.g., "subtle", "queue")
|
|
41
|
+
* - Compound words and contractions
|
|
42
|
+
* - Words borrowed from other languages
|
|
43
|
+
* - Proper nouns and technical terms
|
|
44
|
+
*
|
|
45
|
+
* For readability scoring purposes, this approximation is sufficient since
|
|
46
|
+
* Flesch-Kincaid is already an estimate and small syllable miscounts don't
|
|
47
|
+
* significantly impact the final grade level calculation.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} word - The word to count syllables for
|
|
50
|
+
* @returns {number} Estimated syllable count
|
|
51
|
+
*/
|
|
52
|
+
export function countSyllables(word: string): number;
|
|
53
|
+
/**
|
|
54
|
+
* Get a human-readable description of the grade level
|
|
55
|
+
*
|
|
56
|
+
* @param {number} grade - Flesch-Kincaid grade level
|
|
57
|
+
* @returns {string} Description
|
|
58
|
+
*/
|
|
59
|
+
export function getGradeDescription(grade: number): string;
|
|
60
|
+
export type ReadabilityResult = {
|
|
61
|
+
/**
|
|
62
|
+
* - Grade level (e.g., 8.5)
|
|
63
|
+
*/
|
|
64
|
+
fleschKincaid: number;
|
|
65
|
+
/**
|
|
66
|
+
* - Human-readable time (e.g., "5 min read")
|
|
67
|
+
*/
|
|
68
|
+
readingTime: string;
|
|
69
|
+
/**
|
|
70
|
+
* - Total words
|
|
71
|
+
*/
|
|
72
|
+
wordCount: number;
|
|
73
|
+
/**
|
|
74
|
+
* - Total sentences
|
|
75
|
+
*/
|
|
76
|
+
sentenceCount: number;
|
|
77
|
+
/**
|
|
78
|
+
* - Sentence statistics
|
|
79
|
+
*/
|
|
80
|
+
sentenceStats: {
|
|
81
|
+
average: number;
|
|
82
|
+
longest: number;
|
|
83
|
+
shortest: number;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* - Improvement suggestions
|
|
87
|
+
*/
|
|
88
|
+
suggestions: string[];
|
|
89
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readability Analysis - Local Calculations
|
|
3
|
+
*
|
|
4
|
+
* Readability scoring using Flesch-Kincaid and other metrics.
|
|
5
|
+
* No AI needed - purely algorithmic analysis.
|
|
6
|
+
*
|
|
7
|
+
* @see docs/specs/writing-assistant-unified-spec.md
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} ReadabilityResult
|
|
12
|
+
* @property {number} fleschKincaid - Grade level (e.g., 8.5)
|
|
13
|
+
* @property {string} readingTime - Human-readable time (e.g., "5 min read")
|
|
14
|
+
* @property {number} wordCount - Total words
|
|
15
|
+
* @property {number} sentenceCount - Total sentences
|
|
16
|
+
* @property {Object} sentenceStats - Sentence statistics
|
|
17
|
+
* @property {number} sentenceStats.average - Average words per sentence
|
|
18
|
+
* @property {number} sentenceStats.longest - Longest sentence word count
|
|
19
|
+
* @property {number} sentenceStats.shortest - Shortest sentence word count
|
|
20
|
+
* @property {string[]} suggestions - Improvement suggestions
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Calculate readability metrics for content
|
|
25
|
+
*
|
|
26
|
+
* @param {string} content - The text to analyze (can include markdown)
|
|
27
|
+
* @returns {ReadabilityResult}
|
|
28
|
+
*/
|
|
29
|
+
export function calculateReadability(content) {
|
|
30
|
+
// Strip markdown syntax for clean text analysis
|
|
31
|
+
const text = stripMarkdownForAnalysis(content);
|
|
32
|
+
|
|
33
|
+
const sentences = text.split(/[.!?]+/).filter((s) => s.trim().length > 0);
|
|
34
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
35
|
+
const syllables = words.reduce((sum, word) => sum + countSyllables(word), 0);
|
|
36
|
+
|
|
37
|
+
const sentenceCount = Math.max(sentences.length, 1);
|
|
38
|
+
const wordCount = Math.max(words.length, 1);
|
|
39
|
+
|
|
40
|
+
const wordsPerSentence = wordCount / sentenceCount;
|
|
41
|
+
const syllablesPerWord = syllables / wordCount;
|
|
42
|
+
|
|
43
|
+
// Flesch-Kincaid Grade Level
|
|
44
|
+
const fleschKincaid = Math.max(
|
|
45
|
+
0,
|
|
46
|
+
0.39 * wordsPerSentence + 11.8 * syllablesPerWord - 15.59
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Reading time (~200 words per minute for focused reading)
|
|
50
|
+
const minutes = Math.ceil(wordCount / 200);
|
|
51
|
+
|
|
52
|
+
// Sentence length stats
|
|
53
|
+
const sentenceLengths = sentences.map(
|
|
54
|
+
(s) => s.split(/\s+/).filter((w) => w.length > 0).length
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
fleschKincaid: Math.round(fleschKincaid * 10) / 10,
|
|
59
|
+
readingTime: `${minutes} min read`,
|
|
60
|
+
wordCount,
|
|
61
|
+
sentenceCount,
|
|
62
|
+
sentenceStats: {
|
|
63
|
+
average: Math.round(wordsPerSentence),
|
|
64
|
+
longest: sentenceLengths.length > 0 ? Math.max(...sentenceLengths) : 0,
|
|
65
|
+
shortest: sentenceLengths.length > 0 ? Math.min(...sentenceLengths) : 0
|
|
66
|
+
},
|
|
67
|
+
suggestions: generateSuggestions(fleschKincaid, wordsPerSentence, sentenceLengths)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Strip markdown syntax for cleaner readability analysis
|
|
73
|
+
*
|
|
74
|
+
* @param {string} content - Markdown content
|
|
75
|
+
* @returns {string} Plain text
|
|
76
|
+
*/
|
|
77
|
+
export function stripMarkdownForAnalysis(content) {
|
|
78
|
+
return content
|
|
79
|
+
.replace(/```[\s\S]*?```/g, '') // Remove code blocks
|
|
80
|
+
.replace(/`[^`]+`/g, '') // Remove inline code
|
|
81
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Replace links with text
|
|
82
|
+
.replace(/[#*_~>]/g, '') // Remove markdown chars
|
|
83
|
+
.replace(/^\s*[-+*]\s+/gm, '') // Remove list markers
|
|
84
|
+
.replace(/^\s*\d+\.\s+/gm, '') // Remove numbered list markers
|
|
85
|
+
.trim();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Count syllables in a word (approximate)
|
|
90
|
+
*
|
|
91
|
+
* NOTE: This is a regex-based approximation that works reasonably well for
|
|
92
|
+
* common English words but will be inaccurate for:
|
|
93
|
+
* - Words with silent vowels (e.g., "subtle", "queue")
|
|
94
|
+
* - Compound words and contractions
|
|
95
|
+
* - Words borrowed from other languages
|
|
96
|
+
* - Proper nouns and technical terms
|
|
97
|
+
*
|
|
98
|
+
* For readability scoring purposes, this approximation is sufficient since
|
|
99
|
+
* Flesch-Kincaid is already an estimate and small syllable miscounts don't
|
|
100
|
+
* significantly impact the final grade level calculation.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} word - The word to count syllables for
|
|
103
|
+
* @returns {number} Estimated syllable count
|
|
104
|
+
*/
|
|
105
|
+
export function countSyllables(word) {
|
|
106
|
+
word = word.toLowerCase().replace(/[^a-z]/g, '');
|
|
107
|
+
if (word.length <= 3) return 1;
|
|
108
|
+
|
|
109
|
+
word = word.replace(/(?:[^laeiouy]es|ed|[^laeiouy]e)$/, '');
|
|
110
|
+
word = word.replace(/^y/, '');
|
|
111
|
+
|
|
112
|
+
const matches = word.match(/[aeiouy]{1,2}/g);
|
|
113
|
+
return matches ? Math.max(matches.length, 1) : 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Generate readability improvement suggestions
|
|
118
|
+
*
|
|
119
|
+
* @param {number} grade - Flesch-Kincaid grade level
|
|
120
|
+
* @param {number} avgSentence - Average words per sentence
|
|
121
|
+
* @param {number[]} sentenceLengths - Array of sentence lengths
|
|
122
|
+
* @returns {string[]} Suggestions (max 4)
|
|
123
|
+
*/
|
|
124
|
+
function generateSuggestions(grade, avgSentence, sentenceLengths) {
|
|
125
|
+
const suggestions = [];
|
|
126
|
+
|
|
127
|
+
// Grade level suggestions
|
|
128
|
+
if (grade > 14) {
|
|
129
|
+
suggestions.push(
|
|
130
|
+
'Your writing is quite complex. Consider simplifying for broader accessibility.'
|
|
131
|
+
);
|
|
132
|
+
} else if (grade > 12) {
|
|
133
|
+
suggestions.push(
|
|
134
|
+
'College-level reading. Consider if this matches your audience.'
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Sentence length suggestions
|
|
139
|
+
if (avgSentence > 30) {
|
|
140
|
+
suggestions.push(
|
|
141
|
+
'Many sentences are quite long. Breaking them up could improve clarity.'
|
|
142
|
+
);
|
|
143
|
+
} else if (avgSentence > 25) {
|
|
144
|
+
suggestions.push(
|
|
145
|
+
'Some sentences are on the longer side. Variety in length can improve flow.'
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Very long sentences
|
|
150
|
+
const veryLong = sentenceLengths.filter((l) => l > 40);
|
|
151
|
+
if (veryLong.length > 0) {
|
|
152
|
+
suggestions.push(
|
|
153
|
+
`Found ${veryLong.length} sentence${veryLong.length > 1 ? 's' : ''} over 40 words.`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Very simple
|
|
158
|
+
if (grade < 6 && avgSentence < 10) {
|
|
159
|
+
suggestions.push(
|
|
160
|
+
'Very simple sentences. This works well for accessibility or quick reads.'
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Sentence variety
|
|
165
|
+
if (sentenceLengths.length > 5) {
|
|
166
|
+
const variance = calculateVariance(sentenceLengths);
|
|
167
|
+
if (variance < 10) {
|
|
168
|
+
suggestions.push(
|
|
169
|
+
'Sentence lengths are very uniform. Varying rhythm can make writing more engaging.'
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return suggestions.slice(0, 4); // Max 4 suggestions
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Calculate variance for sentence length variety
|
|
179
|
+
*
|
|
180
|
+
* @param {number[]} numbers - Array of numbers
|
|
181
|
+
* @returns {number} Variance
|
|
182
|
+
*/
|
|
183
|
+
function calculateVariance(numbers) {
|
|
184
|
+
if (numbers.length === 0) return 0;
|
|
185
|
+
const mean = numbers.reduce((a, b) => a + b, 0) / numbers.length;
|
|
186
|
+
return (
|
|
187
|
+
numbers.reduce((sum, n) => sum + Math.pow(n - mean, 2), 0) / numbers.length
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get a human-readable description of the grade level
|
|
193
|
+
*
|
|
194
|
+
* @param {number} grade - Flesch-Kincaid grade level
|
|
195
|
+
* @returns {string} Description
|
|
196
|
+
*/
|
|
197
|
+
export function getGradeDescription(grade) {
|
|
198
|
+
if (grade <= 5) return 'Elementary school level - very easy to read';
|
|
199
|
+
if (grade <= 8) return 'Middle school level - easy to read';
|
|
200
|
+
if (grade <= 10) return 'High school level - fairly easy to read';
|
|
201
|
+
if (grade <= 12) return 'High school senior level - moderately difficult';
|
|
202
|
+
if (grade <= 14) return 'College level - difficult';
|
|
203
|
+
return 'Graduate level - very difficult';
|
|
204
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autumnsgrove/groveengine",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
4
4
|
"description": "Multi-tenant blog engine for Grove Platform. Features gutter annotations, markdown editing, magic code auth, and Cloudflare Workers deployment.",
|
|
5
5
|
"author": "AutumnsGrove",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -73,6 +73,11 @@
|
|
|
73
73
|
"svelte": "./dist/ui/components/states/index.js",
|
|
74
74
|
"default": "./dist/ui/components/states/index.js"
|
|
75
75
|
},
|
|
76
|
+
"./ui/typography": {
|
|
77
|
+
"types": "./dist/ui/components/typography/index.d.ts",
|
|
78
|
+
"svelte": "./dist/ui/components/typography/index.js",
|
|
79
|
+
"default": "./dist/ui/components/typography/index.js"
|
|
80
|
+
},
|
|
76
81
|
"./ui/nature": {
|
|
77
82
|
"types": "./dist/ui/components/nature/index.d.ts",
|
|
78
83
|
"svelte": "./dist/ui/components/nature/index.js",
|
|
@@ -141,6 +146,11 @@
|
|
|
141
146
|
"./services/*": {
|
|
142
147
|
"types": "./dist/server/services/*.d.ts",
|
|
143
148
|
"default": "./dist/server/services/*.js"
|
|
149
|
+
},
|
|
150
|
+
"./vineyard": {
|
|
151
|
+
"types": "./dist/ui/vineyard/index.d.ts",
|
|
152
|
+
"svelte": "./dist/ui/vineyard/index.js",
|
|
153
|
+
"default": "./dist/ui/vineyard/index.js"
|
|
144
154
|
}
|
|
145
155
|
},
|
|
146
156
|
"files": [
|
|
@@ -148,6 +158,26 @@
|
|
|
148
158
|
"static",
|
|
149
159
|
"!dist/**/*.test.*"
|
|
150
160
|
],
|
|
161
|
+
"scripts": {
|
|
162
|
+
"dev": "vite dev",
|
|
163
|
+
"dev:wrangler": "wrangler pages dev -- vite dev",
|
|
164
|
+
"build": "vite build",
|
|
165
|
+
"build:package": "svelte-kit sync && svelte-package -o dist",
|
|
166
|
+
"package": "svelte-kit sync && svelte-package -o dist",
|
|
167
|
+
"prepublishOnly": "pnpm run package",
|
|
168
|
+
"preview": "vite preview",
|
|
169
|
+
"deploy": "wrangler pages deploy .svelte-kit/cloudflare --project-name groveengine",
|
|
170
|
+
"audit": "pnpm audit --audit-level=moderate",
|
|
171
|
+
"audit:fix": "pnpm audit --fix",
|
|
172
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
173
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
174
|
+
"test": "vitest",
|
|
175
|
+
"test:ui": "vitest --ui",
|
|
176
|
+
"test:run": "vitest run",
|
|
177
|
+
"test:security": "vitest run tests/security",
|
|
178
|
+
"test:coverage": "vitest run --coverage",
|
|
179
|
+
"test:watch": "vitest watch"
|
|
180
|
+
},
|
|
151
181
|
"peerDependencies": {
|
|
152
182
|
"@sveltejs/kit": "^2.0.0",
|
|
153
183
|
"svelte": "^5.0.0",
|
|
@@ -163,6 +193,8 @@
|
|
|
163
193
|
"@tailwindcss/typography": "^0.5.19",
|
|
164
194
|
"@testing-library/jest-dom": "^6.9.1",
|
|
165
195
|
"@testing-library/svelte": "^5.2.9",
|
|
196
|
+
"@types/react": "^19.2.7",
|
|
197
|
+
"@types/react-dom": "^19.2.3",
|
|
166
198
|
"@vitest/coverage-v8": "^4.0.16",
|
|
167
199
|
"@vitest/ui": "^4.0.14",
|
|
168
200
|
"autoprefixer": "^10.4.22",
|
|
@@ -170,15 +202,19 @@
|
|
|
170
202
|
"happy-dom": "^20.0.11",
|
|
171
203
|
"jsdom": "^27.2.0",
|
|
172
204
|
"postcss": "^8.5.6",
|
|
205
|
+
"react": "^19.2.3",
|
|
206
|
+
"react-dom": "^19.2.3",
|
|
173
207
|
"svelte": "^5.1.9",
|
|
174
208
|
"svelte-check": "^4.0.0",
|
|
175
209
|
"tailwind-variants": "^0.3.0",
|
|
176
210
|
"tailwindcss": "^3.4.18",
|
|
177
211
|
"typescript": "^5.6.0",
|
|
212
|
+
"vibe-kanban-web-companion": "^0.0.5",
|
|
178
213
|
"vite": "^5.4.10",
|
|
179
214
|
"vitest": "^4.0.14"
|
|
180
215
|
},
|
|
181
216
|
"dependencies": {
|
|
217
|
+
"@autumnsgrove/vineyard": "link:../../../Vineyard",
|
|
182
218
|
"chart.js": "^4.5.1",
|
|
183
219
|
"clsx": "^2.1.1",
|
|
184
220
|
"dompurify": "^3.3.0",
|
|
@@ -188,24 +224,5 @@
|
|
|
188
224
|
"marked": "^17.0.1",
|
|
189
225
|
"svelte-sonner": "^1.0.7",
|
|
190
226
|
"tailwind-merge": "^3.4.0"
|
|
191
|
-
},
|
|
192
|
-
"scripts": {
|
|
193
|
-
"dev": "vite dev",
|
|
194
|
-
"dev:wrangler": "wrangler pages dev -- vite dev",
|
|
195
|
-
"build": "vite build",
|
|
196
|
-
"build:package": "svelte-kit sync && svelte-package -o dist",
|
|
197
|
-
"package": "svelte-kit sync && svelte-package -o dist",
|
|
198
|
-
"preview": "vite preview",
|
|
199
|
-
"deploy": "wrangler pages deploy .svelte-kit/cloudflare --project-name groveengine",
|
|
200
|
-
"audit": "pnpm audit --audit-level=moderate",
|
|
201
|
-
"audit:fix": "pnpm audit --fix",
|
|
202
|
-
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
203
|
-
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
204
|
-
"test": "vitest",
|
|
205
|
-
"test:ui": "vitest --ui",
|
|
206
|
-
"test:run": "vitest run",
|
|
207
|
-
"test:security": "vitest run tests/security",
|
|
208
|
-
"test:coverage": "vitest run --coverage",
|
|
209
|
-
"test:watch": "vitest watch"
|
|
210
227
|
}
|
|
211
|
-
}
|
|
228
|
+
}
|
package/static/fonts/alagard.ttf
CHANGED
|
File without changes
|