@codemeall/create-word-pages 0.1.2 → 0.1.3
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/package.json
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const baseUrlPattern = /^[A-Za-z0-9.-]+(\/[A-Za-z0-9._~-]+)*$/
|
|
2
|
+
|
|
3
|
+
export const requiredSiteFields = ["title", "githubUsername", "repositoryName", "baseUrl"]
|
|
4
|
+
|
|
5
|
+
export function validateWordPagesConfig(config) {
|
|
6
|
+
const fieldErrors = {}
|
|
7
|
+
const site = config?.site ?? {}
|
|
8
|
+
|
|
9
|
+
for (const field of requiredSiteFields) {
|
|
10
|
+
if (!String(site[field] ?? "").trim()) {
|
|
11
|
+
fieldErrors[field] = "Required"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const baseUrl = String(site.baseUrl ?? "").trim()
|
|
16
|
+
if (baseUrl) {
|
|
17
|
+
if (/^[a-z][a-z\d+.-]*:\/\//i.test(baseUrl)) {
|
|
18
|
+
fieldErrors.baseUrl = "Use the Pages URL without https://"
|
|
19
|
+
} else if (/\s/.test(baseUrl)) {
|
|
20
|
+
fieldErrors.baseUrl = "Remove spaces from the Pages URL"
|
|
21
|
+
} else if (baseUrl.startsWith("/") || baseUrl.endsWith("/")) {
|
|
22
|
+
fieldErrors.baseUrl = "Do not use leading or trailing slashes"
|
|
23
|
+
} else if (!baseUrlPattern.test(baseUrl)) {
|
|
24
|
+
fieldErrors.baseUrl = "Use a value like octocat.github.io/my-site"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
ok: Object.keys(fieldErrors).length === 0,
|
|
30
|
+
fieldErrors
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState } from "react"
|
|
2
2
|
import { createRoot } from "react-dom/client"
|
|
3
|
+
import { validateWordPagesConfig } from "./configValidation.js"
|
|
3
4
|
import "./styles.css"
|
|
4
5
|
|
|
5
6
|
type SaveState = "idle" | "dirty" | "saving" | "saved" | "failed"
|
|
@@ -23,6 +24,8 @@ type WordPagesConfig = {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
type SiteFieldErrors = Partial<Record<keyof WordPagesConfig["site"], string>>
|
|
28
|
+
|
|
26
29
|
function deriveBaseUrl(username: string, repositoryName: string) {
|
|
27
30
|
if (!username || !repositoryName) return ""
|
|
28
31
|
if (repositoryName.toLowerCase() === `${username.toLowerCase()}.github.io`) return `${username}.github.io`
|
|
@@ -63,6 +66,12 @@ function App() {
|
|
|
63
66
|
return displayPagesUrl(deriveBaseUrl(config.site.githubUsername, config.site.repositoryName))
|
|
64
67
|
}, [config])
|
|
65
68
|
|
|
69
|
+
const fieldErrors = useMemo<SiteFieldErrors>(() => {
|
|
70
|
+
if (!config) return {}
|
|
71
|
+
return validateWordPagesConfig(config).fieldErrors
|
|
72
|
+
}, [config])
|
|
73
|
+
const canSave = Object.keys(fieldErrors).length === 0 && saveState !== "saving"
|
|
74
|
+
|
|
66
75
|
if (!config) {
|
|
67
76
|
return (
|
|
68
77
|
<main className="shell">
|
|
@@ -95,6 +104,14 @@ function App() {
|
|
|
95
104
|
}
|
|
96
105
|
|
|
97
106
|
async function save() {
|
|
107
|
+
const validation = validateWordPagesConfig(config)
|
|
108
|
+
if (!validation.ok) {
|
|
109
|
+
setStatus("Required fields missing")
|
|
110
|
+
setSaveState("failed")
|
|
111
|
+
setMessage("Complete the required fields before saving word-pages.config.json.")
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
98
115
|
try {
|
|
99
116
|
setStatus("Saving")
|
|
100
117
|
setSaveState("saving")
|
|
@@ -107,6 +124,9 @@ function App() {
|
|
|
107
124
|
})
|
|
108
125
|
const result = await response.json().catch(() => ({ message: "No response body" }))
|
|
109
126
|
if (!response.ok || result.ok === false) {
|
|
127
|
+
if (result.fieldErrors) {
|
|
128
|
+
throw new Error(result.message ?? "Complete the required fields before saving.")
|
|
129
|
+
}
|
|
110
130
|
throw new Error(result.message ?? "Save failed")
|
|
111
131
|
}
|
|
112
132
|
|
|
@@ -130,7 +150,7 @@ function App() {
|
|
|
130
150
|
<h1>Configure your Obsidian-powered site.</h1>
|
|
131
151
|
<p className="lede">This wizard writes local configuration only. It does not ask for GitHub credentials and does not upload your vault.</p>
|
|
132
152
|
</div>
|
|
133
|
-
<button type="button" onClick={save} disabled={
|
|
153
|
+
<button type="button" onClick={save} disabled={!canSave}>
|
|
134
154
|
{saveState === "saving" ? "Saving..." : "Save changes"}
|
|
135
155
|
</button>
|
|
136
156
|
</section>
|
|
@@ -146,7 +166,8 @@ function App() {
|
|
|
146
166
|
<section className="grid">
|
|
147
167
|
<label>
|
|
148
168
|
Site title
|
|
149
|
-
<input value={config.site.title} onChange={(event) => updateSite("title", event.target.value)} />
|
|
169
|
+
<input value={config.site.title} onChange={(event) => updateSite("title", event.target.value)} aria-invalid={Boolean(fieldErrors.title)} />
|
|
170
|
+
{fieldErrors.title && <span className="field-error">{fieldErrors.title}</span>}
|
|
150
171
|
</label>
|
|
151
172
|
<label>
|
|
152
173
|
Author or organization
|
|
@@ -158,11 +179,13 @@ function App() {
|
|
|
158
179
|
</label>
|
|
159
180
|
<label>
|
|
160
181
|
GitHub username
|
|
161
|
-
<input value={config.site.githubUsername} onChange={(event) => updateSite("githubUsername", event.target.value)} />
|
|
182
|
+
<input value={config.site.githubUsername} onChange={(event) => updateSite("githubUsername", event.target.value)} aria-invalid={Boolean(fieldErrors.githubUsername)} />
|
|
183
|
+
{fieldErrors.githubUsername && <span className="field-error">{fieldErrors.githubUsername}</span>}
|
|
162
184
|
</label>
|
|
163
185
|
<label>
|
|
164
186
|
Repository name
|
|
165
|
-
<input value={config.site.repositoryName} onChange={(event) => updateSite("repositoryName", event.target.value)} />
|
|
187
|
+
<input value={config.site.repositoryName} onChange={(event) => updateSite("repositoryName", event.target.value)} aria-invalid={Boolean(fieldErrors.repositoryName)} />
|
|
188
|
+
{fieldErrors.repositoryName && <span className="field-error">{fieldErrors.repositoryName}</span>}
|
|
166
189
|
</label>
|
|
167
190
|
<label>
|
|
168
191
|
Repository visibility
|
|
@@ -174,7 +197,8 @@ function App() {
|
|
|
174
197
|
</label>
|
|
175
198
|
<label>
|
|
176
199
|
GitHub Pages base URL
|
|
177
|
-
<input value={config.site.baseUrl} onChange={(event) => updateSite("baseUrl", event.target.value)} />
|
|
200
|
+
<input value={config.site.baseUrl} onChange={(event) => updateSite("baseUrl", event.target.value)} aria-invalid={Boolean(fieldErrors.baseUrl)} />
|
|
201
|
+
{fieldErrors.baseUrl && <span className="field-error">{fieldErrors.baseUrl}</span>}
|
|
178
202
|
</label>
|
|
179
203
|
</section>
|
|
180
204
|
|
|
@@ -60,7 +60,7 @@ button {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
button:disabled {
|
|
63
|
-
cursor:
|
|
63
|
+
cursor: not-allowed;
|
|
64
64
|
opacity: 0.72;
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -131,6 +131,17 @@ textarea {
|
|
|
131
131
|
font: inherit;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
input[aria-invalid="true"] {
|
|
135
|
+
border-color: #b83232;
|
|
136
|
+
outline-color: #b83232;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.field-error {
|
|
140
|
+
color: #9f2525;
|
|
141
|
+
font-size: 0.88rem;
|
|
142
|
+
font-weight: 700;
|
|
143
|
+
}
|
|
144
|
+
|
|
134
145
|
textarea {
|
|
135
146
|
min-height: 104px;
|
|
136
147
|
resize: vertical;
|
|
@@ -2,8 +2,23 @@ import { defineConfig } from "vite"
|
|
|
2
2
|
import react from "@vitejs/plugin-react"
|
|
3
3
|
import { readFile, writeFile } from "node:fs/promises"
|
|
4
4
|
import path from "node:path"
|
|
5
|
+
import { validateWordPagesConfig } from "./src/configValidation.js"
|
|
5
6
|
|
|
6
7
|
const configPath = path.resolve(process.cwd(), "word-pages.config.json")
|
|
8
|
+
let printedCompletionHandoff = false
|
|
9
|
+
|
|
10
|
+
function printCompletionHandoff() {
|
|
11
|
+
if (printedCompletionHandoff) return
|
|
12
|
+
printedCompletionHandoff = true
|
|
13
|
+
console.log(`
|
|
14
|
+
Word Pages setup saved.
|
|
15
|
+
|
|
16
|
+
Next steps:
|
|
17
|
+
1. Open content/ in Obsidian and edit your pages, posts, and notes.
|
|
18
|
+
2. In another terminal, run: npm run preview
|
|
19
|
+
3. When ready, commit, push to GitHub, and enable Pages with GitHub Actions.
|
|
20
|
+
`)
|
|
21
|
+
}
|
|
7
22
|
|
|
8
23
|
export default defineConfig({
|
|
9
24
|
root: "wizard",
|
|
@@ -29,7 +44,20 @@ export default defineConfig({
|
|
|
29
44
|
req.on("end", async () => {
|
|
30
45
|
try {
|
|
31
46
|
const parsed = JSON.parse(raw)
|
|
47
|
+
const validation = validateWordPagesConfig(parsed)
|
|
48
|
+
if (!validation.ok) {
|
|
49
|
+
res.statusCode = 400
|
|
50
|
+
res.setHeader("Content-Type", "application/json")
|
|
51
|
+
res.end(JSON.stringify({
|
|
52
|
+
ok: false,
|
|
53
|
+
message: "Complete the required fields before saving.",
|
|
54
|
+
fieldErrors: validation.fieldErrors
|
|
55
|
+
}))
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
32
59
|
await writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`)
|
|
60
|
+
printCompletionHandoff()
|
|
33
61
|
res.setHeader("Content-Type", "application/json")
|
|
34
62
|
res.end(JSON.stringify({ ok: true, path: configPath }))
|
|
35
63
|
} catch (error) {
|