@drawnagency/primitives 0.1.0 → 0.2.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/dist/auth/index.js +26 -3
- package/dist/chunk-2VTPWODA.js +60 -0
- package/dist/chunk-CS7F6IOY.js +39 -0
- package/dist/chunk-HOJAF4VD.js +264 -0
- package/dist/chunk-IP6ODLXX.js +341 -0
- package/dist/chunk-T4BJ6RSB.js +58 -0
- package/dist/chunk-UKEVUCIZ.js +200 -0
- package/dist/chunk-UMSFICAC.js +36 -0
- package/dist/index.js +156 -4
- package/dist/lib/index.js +62 -12
- package/dist/lib/sanitize.d.ts.map +1 -1
- package/dist/media/index.js +36 -9
- package/dist/schemas/index.js +52 -7
- package/package.json +5 -4
- package/src/lib/sanitize.ts +6 -2
- package/dist/auth/cookies.js +0 -44
- package/dist/auth/errors.js +0 -10
- package/dist/auth/security.js +0 -48
- package/dist/auth/types.js +0 -1
- package/dist/components/brandguide/ColorSwatchSettings.js +0 -10
- package/dist/components/brandguide/Colors.js +0 -79
- package/dist/components/brandguide/DoDontList.js +0 -22
- package/dist/components/brandguide/DoDontMediaGrid.js +0 -5
- package/dist/components/editor/AudiencePicker.js +0 -24
- package/dist/components/editor/DeleteButton.js +0 -6
- package/dist/components/editor/DragHandle.js +0 -8
- package/dist/components/editor/InsertButton.js +0 -7
- package/dist/components/editor/SectionWrapper.js +0 -135
- package/dist/components/editor/SettingsButton.js +0 -6
- package/dist/components/editor/SettingsForm.js +0 -64
- package/dist/components/editor/StatusBadge.js +0 -10
- package/dist/components/editor/StatusPicker.js +0 -30
- package/dist/components/editor/index.js +0 -7
- package/dist/components/primitives/CustomParagraph.js +0 -24
- package/dist/components/primitives/EditableGrid.js +0 -90
- package/dist/components/primitives/EditableList.js +0 -54
- package/dist/components/primitives/EditablePlainText.js +0 -52
- package/dist/components/primitives/EditableRichText.js +0 -86
- package/dist/components/primitives/HeadingSection.js +0 -7
- package/dist/components/primitives/IconPicker.js +0 -21
- package/dist/components/primitives/LinkPopover.js +0 -48
- package/dist/components/primitives/MediaSettingsForms.js +0 -42
- package/dist/components/primitives/ResolvedMedia.js +0 -9
- package/dist/components/primitives/RichTextToolbar.js +0 -26
- package/dist/components/primitives/tiptap-presets.js +0 -44
- package/dist/components/primitives/useEditableCollection.js +0 -61
- package/dist/components/primitives/useEditablePlainText.js +0 -27
- package/dist/components/primitives/useEditableRichText.js +0 -52
- package/dist/components/sections/Button/CTAButton.js +0 -18
- package/dist/components/sections/Button/index.js +0 -28
- package/dist/components/sections/Colors/index.js +0 -34
- package/dist/components/sections/DoDontList/index.js +0 -33
- package/dist/components/sections/DoDontMediaGrid/index.js +0 -41
- package/dist/components/sections/IconList/IconList.js +0 -131
- package/dist/components/sections/IconList/IconListSettings.js +0 -22
- package/dist/components/sections/IconList/index.js +0 -27
- package/dist/components/sections/LinkHeading/index.js +0 -15
- package/dist/components/sections/MediaGrid/MediaGrid.js +0 -62
- package/dist/components/sections/MediaGrid/index.js +0 -35
- package/dist/components/sections/Prose/Prose.js +0 -11
- package/dist/components/sections/Prose/index.js +0 -15
- package/dist/components/sections/SectionLayout.js +0 -15
- package/dist/components/sections/SplitContent/SplitContent.js +0 -31
- package/dist/components/sections/SplitContent/SplitContentSettings.js +0 -17
- package/dist/components/sections/SplitContent/index.js +0 -27
- package/dist/components/sections/SubHeading/index.js +0 -18
- package/dist/components/sections/SubSubHeading/index.js +0 -18
- package/dist/components/sections/ViewRenderer.js +0 -13
- package/dist/components/sections/register-schemas.js +0 -15
- package/dist/components/sections/register.js +0 -15
- package/dist/components/shared/Button.js +0 -27
- package/dist/components/shared/Checkbox.js +0 -10
- package/dist/components/shared/ColorPicker.js +0 -5
- package/dist/components/shared/ErrorBoundary.js +0 -30
- package/dist/components/shared/FontPicker.js +0 -190
- package/dist/components/shared/FormLabel.js +0 -5
- package/dist/components/shared/IconButton.js +0 -16
- package/dist/components/shared/Input.js +0 -8
- package/dist/components/shared/Navigation.js +0 -71
- package/dist/components/shared/PasswordInput.js +0 -11
- package/dist/components/shared/Popover.js +0 -33
- package/dist/components/shared/PopoverItem.js +0 -6
- package/dist/components/shared/Select.js +0 -9
- package/dist/components/shared/Textarea.js +0 -8
- package/dist/components/shared/Toggle.js +0 -5
- package/dist/components/shared/Tooltip.js +0 -8
- package/dist/components/shared/icons.js +0 -23
- package/dist/components/shell/AudienceAddForm.js +0 -43
- package/dist/components/shell/AudienceRow.js +0 -74
- package/dist/components/shell/EditorContext.js +0 -24
- package/dist/components/shell/EditorLoginForm.js +0 -46
- package/dist/components/shell/EditorModal.js +0 -43
- package/dist/components/shell/EditorModalContext.js +0 -20
- package/dist/components/shell/EditorShell.js +0 -483
- package/dist/components/shell/MediaLibraryContext.js +0 -5
- package/dist/components/shell/MediaLibraryModal.js +0 -145
- package/dist/components/shell/ProcessingIndicator.js +0 -15
- package/dist/components/shell/SectionSkeleton.js +0 -22
- package/dist/components/shell/SectionTypePicker.js +0 -15
- package/dist/components/shell/SiteSettingsDisplay.js +0 -28
- package/dist/components/shell/SiteSettingsModal.js +0 -40
- package/dist/components/shell/SiteSettingsUsers.js +0 -87
- package/dist/components/shell/SiteSettingsViewerAccess.js +0 -94
- package/dist/components/shell/ViewerLoginForm.js +0 -40
- package/dist/data/google-fonts.json +0 -7718
- package/dist/hooks/index.js +0 -6
- package/dist/hooks/useActiveHeadings.js +0 -99
- package/dist/hooks/useEditorPersistence.js +0 -73
- package/dist/hooks/useEditorPublish.js +0 -145
- package/dist/hooks/useFocusTrap.js +0 -51
- package/dist/hooks/useMediaPipeline.js +0 -253
- package/dist/hooks/useResolvedMedia.js +0 -39
- package/dist/lib/cn.js +0 -5
- package/dist/lib/contrast.js +0 -11
- package/dist/lib/dexie.js +0 -236
- package/dist/lib/events.js +0 -15
- package/dist/lib/google-fonts.js +0 -11
- package/dist/lib/grid.js +0 -7
- package/dist/lib/icons.js +0 -27
- package/dist/lib/loader.js +0 -57
- package/dist/lib/nav.js +0 -58
- package/dist/lib/registry.js +0 -64
- package/dist/lib/safeRedirect.js +0 -11
- package/dist/lib/sanitize.js +0 -6
- package/dist/lib/timestamp.js +0 -28
- package/dist/media/github.js +0 -60
- package/dist/media/queue.js +0 -116
- package/dist/media/resolve.js +0 -50
- package/dist/media/types.js +0 -1
- package/dist/media/utils.js +0 -41
- package/dist/media/videoPoster.js +0 -44
- package/dist/media/worker.js +0 -73
- package/dist/schemas/audience.js +0 -19
- package/dist/schemas/auth.js +0 -22
- package/dist/schemas/media-grid-options.js +0 -7
- package/dist/schemas/media.js +0 -28
- package/dist/schemas/sections.js +0 -12
- package/dist/schemas/shared.js +0 -71
- package/dist/schemas/site-config.js +0 -26
package/dist/auth/index.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
AUDIENCE_COOKIE,
|
|
3
|
+
LastOwnerError,
|
|
4
|
+
SESSION_COOKIE,
|
|
5
|
+
SESSION_MAX_AGE_SECONDS,
|
|
6
|
+
setSessionCookie,
|
|
7
|
+
signSessionToken,
|
|
8
|
+
verifySessionToken
|
|
9
|
+
} from "../chunk-2VTPWODA.js";
|
|
10
|
+
import {
|
|
11
|
+
isSameOriginRequest,
|
|
12
|
+
requireSessionSecret,
|
|
13
|
+
safeNextPath
|
|
14
|
+
} from "../chunk-CS7F6IOY.js";
|
|
15
|
+
export {
|
|
16
|
+
AUDIENCE_COOKIE,
|
|
17
|
+
LastOwnerError,
|
|
18
|
+
SESSION_COOKIE,
|
|
19
|
+
SESSION_MAX_AGE_SECONDS,
|
|
20
|
+
isSameOriginRequest,
|
|
21
|
+
requireSessionSecret,
|
|
22
|
+
safeNextPath,
|
|
23
|
+
setSessionCookie,
|
|
24
|
+
signSessionToken,
|
|
25
|
+
verifySessionToken
|
|
26
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
requireSessionSecret
|
|
3
|
+
} from "./chunk-CS7F6IOY.js";
|
|
4
|
+
|
|
5
|
+
// src/auth/cookies.ts
|
|
6
|
+
import * as jose from "jose";
|
|
7
|
+
var SESSION_COOKIE = "bp-session";
|
|
8
|
+
var AUDIENCE_COOKIE = "bp-audience";
|
|
9
|
+
var SESSION_MAX_AGE_SECONDS = 60 * 60 * 24;
|
|
10
|
+
async function signSessionToken(session) {
|
|
11
|
+
return new jose.SignJWT({
|
|
12
|
+
userId: session.userId,
|
|
13
|
+
email: session.email,
|
|
14
|
+
role: session.role,
|
|
15
|
+
siteId: session.siteId
|
|
16
|
+
}).setProtectedHeader({ alg: "HS256" }).setExpirationTime(`${SESSION_MAX_AGE_SECONDS}s`).setIssuedAt().sign(requireSessionSecret());
|
|
17
|
+
}
|
|
18
|
+
async function verifySessionToken(token) {
|
|
19
|
+
const secret = requireSessionSecret();
|
|
20
|
+
try {
|
|
21
|
+
const { payload } = await jose.jwtVerify(token, secret);
|
|
22
|
+
const role = payload.role;
|
|
23
|
+
if (role !== "owner" && role !== "editor") return null;
|
|
24
|
+
return {
|
|
25
|
+
userId: payload.userId ?? null,
|
|
26
|
+
email: payload.email ?? null,
|
|
27
|
+
role,
|
|
28
|
+
siteId: payload.siteId
|
|
29
|
+
};
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function setSessionCookie(cookies, token, isProduction) {
|
|
35
|
+
cookies.set(SESSION_COOKIE, token, {
|
|
36
|
+
httpOnly: true,
|
|
37
|
+
secure: isProduction,
|
|
38
|
+
sameSite: "lax",
|
|
39
|
+
path: "/",
|
|
40
|
+
maxAge: SESSION_MAX_AGE_SECONDS
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/auth/errors.ts
|
|
45
|
+
var LastOwnerError = class extends Error {
|
|
46
|
+
constructor(message = "Cannot remove the last owner") {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = "LastOwnerError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
SESSION_COOKIE,
|
|
54
|
+
AUDIENCE_COOKIE,
|
|
55
|
+
SESSION_MAX_AGE_SECONDS,
|
|
56
|
+
signSessionToken,
|
|
57
|
+
verifySessionToken,
|
|
58
|
+
setSessionCookie,
|
|
59
|
+
LastOwnerError
|
|
60
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/auth/security.ts
|
|
2
|
+
function requireSessionSecret() {
|
|
3
|
+
const secret = import.meta.env.SESSION_SECRET;
|
|
4
|
+
if (!secret) {
|
|
5
|
+
throw new Error("SESSION_SECRET is required but not set");
|
|
6
|
+
}
|
|
7
|
+
const bytes = new TextEncoder().encode(secret);
|
|
8
|
+
if (bytes.byteLength < 32) {
|
|
9
|
+
throw new Error("SESSION_SECRET must be at least 32 bytes");
|
|
10
|
+
}
|
|
11
|
+
return bytes;
|
|
12
|
+
}
|
|
13
|
+
function isSameOriginRequest(request) {
|
|
14
|
+
const origin = request.headers.get("Origin");
|
|
15
|
+
const referer = request.headers.get("Referer");
|
|
16
|
+
const source = origin ?? referer;
|
|
17
|
+
if (!source) return false;
|
|
18
|
+
try {
|
|
19
|
+
const sourceOrigin = new URL(source).origin;
|
|
20
|
+
const requestOrigin = new URL(request.url).origin;
|
|
21
|
+
return sourceOrigin === requestOrigin;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function safeNextPath(next) {
|
|
27
|
+
if (!next) return null;
|
|
28
|
+
if (!next.startsWith("/")) return null;
|
|
29
|
+
if (next.startsWith("//")) return null;
|
|
30
|
+
if (next.includes("\\")) return null;
|
|
31
|
+
if (/[\x00-\x1f]/.test(next)) return null;
|
|
32
|
+
return next;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
requireSessionSecret,
|
|
37
|
+
isSameOriginRequest,
|
|
38
|
+
safeNextPath
|
|
39
|
+
};
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IndexSchema,
|
|
3
|
+
getSectionSchema
|
|
4
|
+
} from "./chunk-UKEVUCIZ.js";
|
|
5
|
+
import {
|
|
6
|
+
safeNextPath
|
|
7
|
+
} from "./chunk-CS7F6IOY.js";
|
|
8
|
+
|
|
9
|
+
// src/lib/cn.ts
|
|
10
|
+
import { clsx } from "clsx";
|
|
11
|
+
import { twMerge } from "tailwind-merge";
|
|
12
|
+
function cn(...inputs) {
|
|
13
|
+
return twMerge(clsx(inputs));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/lib/nav.ts
|
|
17
|
+
function toSectionId(text) {
|
|
18
|
+
return text.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
|
|
19
|
+
}
|
|
20
|
+
function generateNavLinks(sections) {
|
|
21
|
+
const nav = [];
|
|
22
|
+
let currentParent = null;
|
|
23
|
+
let currentChild = null;
|
|
24
|
+
for (const { section, meta } of sections) {
|
|
25
|
+
const content = section.content;
|
|
26
|
+
if (!content.heading) continue;
|
|
27
|
+
if (section.type === "link_heading") {
|
|
28
|
+
currentParent = {
|
|
29
|
+
href: `#${toSectionId(content.heading)}`,
|
|
30
|
+
label: content.heading,
|
|
31
|
+
status: meta.status,
|
|
32
|
+
children: []
|
|
33
|
+
};
|
|
34
|
+
currentChild = null;
|
|
35
|
+
nav.push(currentParent);
|
|
36
|
+
} else if (section.type === "sub_heading") {
|
|
37
|
+
if (content.excludeFromNav) continue;
|
|
38
|
+
if (!currentParent) continue;
|
|
39
|
+
currentChild = {
|
|
40
|
+
href: `#${toSectionId(content.heading)}`,
|
|
41
|
+
label: content.heading,
|
|
42
|
+
status: meta.status,
|
|
43
|
+
children: []
|
|
44
|
+
};
|
|
45
|
+
currentParent.children.push(currentChild);
|
|
46
|
+
} else if (section.type === "sub_sub_heading") {
|
|
47
|
+
if (content.excludeFromNav) continue;
|
|
48
|
+
if (!currentChild) continue;
|
|
49
|
+
currentChild.children.push({
|
|
50
|
+
href: `#${toSectionId(content.heading)}`,
|
|
51
|
+
label: content.heading,
|
|
52
|
+
status: meta.status,
|
|
53
|
+
children: []
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return nav;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/lib/contrast.ts
|
|
61
|
+
function deriveContrast(hex) {
|
|
62
|
+
const h = hex.replace("#", "");
|
|
63
|
+
const expanded = h.length === 3 ? h[0] + h[0] + h[1] + h[1] + h[2] + h[2] : h;
|
|
64
|
+
const r = parseInt(expanded.substring(0, 2), 16);
|
|
65
|
+
const g = parseInt(expanded.substring(2, 4), 16);
|
|
66
|
+
const b = parseInt(expanded.substring(4, 6), 16);
|
|
67
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
68
|
+
return luminance >= 0.5 ? "#1a1a1a" : "#f0f0f0";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/lib/sanitize.ts
|
|
72
|
+
var purifier;
|
|
73
|
+
if (typeof window !== "undefined") {
|
|
74
|
+
import("dompurify").then((m) => {
|
|
75
|
+
purifier = m.default;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function sanitizeHtml(html) {
|
|
79
|
+
if (!html) return "";
|
|
80
|
+
return purifier ? purifier.sanitize(html) : html;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/lib/grid.ts
|
|
84
|
+
var gridColsClass = {
|
|
85
|
+
1: "grid-cols-1",
|
|
86
|
+
2: "grid-cols-1 min-sm:grid-cols-2",
|
|
87
|
+
3: "grid-cols-1 min-sm:grid-cols-2 lg:grid-cols-3",
|
|
88
|
+
4: "grid-cols-2 lg:grid-cols-4",
|
|
89
|
+
5: "grid-cols-2 min-sm:grid-cols-3 lg:grid-cols-5"
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/lib/icons.ts
|
|
93
|
+
import {
|
|
94
|
+
Check,
|
|
95
|
+
X,
|
|
96
|
+
ArrowRight,
|
|
97
|
+
Star,
|
|
98
|
+
Info,
|
|
99
|
+
AlertTriangle,
|
|
100
|
+
Heart,
|
|
101
|
+
Lightbulb,
|
|
102
|
+
Flag,
|
|
103
|
+
Shield,
|
|
104
|
+
Zap,
|
|
105
|
+
Eye,
|
|
106
|
+
Lock,
|
|
107
|
+
Globe,
|
|
108
|
+
Users,
|
|
109
|
+
Target,
|
|
110
|
+
Bookmark,
|
|
111
|
+
Clock,
|
|
112
|
+
ThumbsUp,
|
|
113
|
+
ThumbsDown
|
|
114
|
+
} from "lucide-react";
|
|
115
|
+
var curatedIcons = [
|
|
116
|
+
{ id: "check", label: "Check", icon: Check },
|
|
117
|
+
{ id: "x", label: "X", icon: X },
|
|
118
|
+
{ id: "arrow-right", label: "Arrow Right", icon: ArrowRight },
|
|
119
|
+
{ id: "star", label: "Star", icon: Star },
|
|
120
|
+
{ id: "info", label: "Info", icon: Info },
|
|
121
|
+
{ id: "alert-triangle", label: "Warning", icon: AlertTriangle },
|
|
122
|
+
{ id: "heart", label: "Heart", icon: Heart },
|
|
123
|
+
{ id: "lightbulb", label: "Lightbulb", icon: Lightbulb },
|
|
124
|
+
{ id: "flag", label: "Flag", icon: Flag },
|
|
125
|
+
{ id: "shield", label: "Shield", icon: Shield },
|
|
126
|
+
{ id: "zap", label: "Zap", icon: Zap },
|
|
127
|
+
{ id: "eye", label: "Eye", icon: Eye },
|
|
128
|
+
{ id: "lock", label: "Lock", icon: Lock },
|
|
129
|
+
{ id: "globe", label: "Globe", icon: Globe },
|
|
130
|
+
{ id: "users", label: "Users", icon: Users },
|
|
131
|
+
{ id: "target", label: "Target", icon: Target },
|
|
132
|
+
{ id: "bookmark", label: "Bookmark", icon: Bookmark },
|
|
133
|
+
{ id: "clock", label: "Clock", icon: Clock },
|
|
134
|
+
{ id: "thumbs-up", label: "Thumbs Up", icon: ThumbsUp },
|
|
135
|
+
{ id: "thumbs-down", label: "Thumbs Down", icon: ThumbsDown }
|
|
136
|
+
];
|
|
137
|
+
var iconMap = new Map(curatedIcons.map((e) => [e.id, e]));
|
|
138
|
+
function getIcon(id) {
|
|
139
|
+
return iconMap.get(id);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/lib/google-fonts.ts
|
|
143
|
+
function buildGoogleFontsUrl(headingFont, bodyFont) {
|
|
144
|
+
const fonts = /* @__PURE__ */ new Set();
|
|
145
|
+
if (headingFont !== "system-ui") fonts.add(headingFont);
|
|
146
|
+
if (bodyFont !== "system-ui") fonts.add(bodyFont);
|
|
147
|
+
if (fonts.size === 0) return null;
|
|
148
|
+
const params = [...fonts].map((f) => `family=${f.replace(/ /g, "+")}:wght@400;700`).join("&");
|
|
149
|
+
return `https://fonts.googleapis.com/css2?${params}&display=swap`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/lib/timestamp.ts
|
|
153
|
+
function formatTimestamp(iso) {
|
|
154
|
+
const date = new Date(iso);
|
|
155
|
+
const now = /* @__PURE__ */ new Date();
|
|
156
|
+
const diffMs = now.getTime() - date.getTime();
|
|
157
|
+
const diffMinutes = Math.floor(diffMs / 6e4);
|
|
158
|
+
const diffHours = Math.floor(diffMs / 36e5);
|
|
159
|
+
const sameDay = date.getUTCFullYear() === now.getUTCFullYear() && date.getUTCMonth() === now.getUTCMonth() && date.getUTCDate() === now.getUTCDate();
|
|
160
|
+
if (diffMinutes < 1) return "just now";
|
|
161
|
+
if (diffMinutes < 60) {
|
|
162
|
+
return `${diffMinutes} ${diffMinutes === 1 ? "minute" : "minutes"} ago`;
|
|
163
|
+
}
|
|
164
|
+
if (sameDay) {
|
|
165
|
+
return `${diffHours} ${diffHours === 1 ? "hour" : "hours"} ago`;
|
|
166
|
+
}
|
|
167
|
+
return date.toLocaleDateString("en-US", {
|
|
168
|
+
month: "short",
|
|
169
|
+
day: "numeric",
|
|
170
|
+
timeZone: "UTC"
|
|
171
|
+
}) + " at " + date.toLocaleTimeString("en-US", {
|
|
172
|
+
hour: "numeric",
|
|
173
|
+
minute: "2-digit",
|
|
174
|
+
hour12: true,
|
|
175
|
+
timeZone: "UTC"
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/lib/safeRedirect.ts
|
|
180
|
+
function safeRedirect(url, fallback) {
|
|
181
|
+
if (!url) return fallback;
|
|
182
|
+
return safeNextPath(url) ?? fallback;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/lib/events.ts
|
|
186
|
+
function createEvent(name) {
|
|
187
|
+
return {
|
|
188
|
+
dispatch(detail) {
|
|
189
|
+
window.dispatchEvent(new CustomEvent(name, { detail }));
|
|
190
|
+
},
|
|
191
|
+
listen(callback) {
|
|
192
|
+
const handler = (e) => callback(e.detail);
|
|
193
|
+
window.addEventListener(name, handler);
|
|
194
|
+
return () => window.removeEventListener(name, handler);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
var editModeEvent = createEvent("editmodechange");
|
|
199
|
+
var navChangeEvent = createEvent("sitenavchange");
|
|
200
|
+
var darkModeEvent = createEvent("sitedarkmode");
|
|
201
|
+
|
|
202
|
+
// src/lib/loader.ts
|
|
203
|
+
function mergeSiteContent(index, sectionFiles) {
|
|
204
|
+
const sections = [];
|
|
205
|
+
for (const id of index.order) {
|
|
206
|
+
const raw = sectionFiles[id];
|
|
207
|
+
if (!raw) {
|
|
208
|
+
console.warn(`Section file missing for id: ${id}, skipping`);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const result = getSectionSchema().safeParse(raw);
|
|
212
|
+
if (!result.success) {
|
|
213
|
+
const type = raw.type ?? "unknown";
|
|
214
|
+
console.warn(`Skipping section "${id}" (type: ${type}): invalid schema`);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const meta = index.sections[id];
|
|
218
|
+
sections.push({ section: result.data, meta });
|
|
219
|
+
}
|
|
220
|
+
return { sections, index };
|
|
221
|
+
}
|
|
222
|
+
function loadStaticSiteContent(index, sectionGlob) {
|
|
223
|
+
const sectionFiles = {};
|
|
224
|
+
for (const [path, data] of Object.entries(sectionGlob)) {
|
|
225
|
+
const filename = path.split("/").pop();
|
|
226
|
+
const id = filename.replace(".json", "");
|
|
227
|
+
sectionFiles[id] = data;
|
|
228
|
+
}
|
|
229
|
+
return mergeSiteContent(index, sectionFiles);
|
|
230
|
+
}
|
|
231
|
+
async function loadSiteContent(contentDir) {
|
|
232
|
+
const fs = await import("fs/promises");
|
|
233
|
+
const path = await import("path");
|
|
234
|
+
const indexPath = path.join(contentDir, "index.json");
|
|
235
|
+
const indexRaw = JSON.parse(await fs.readFile(indexPath, "utf-8"));
|
|
236
|
+
const index = IndexSchema.parse(indexRaw);
|
|
237
|
+
const sectionFiles = {};
|
|
238
|
+
for (const id of index.order) {
|
|
239
|
+
const sectionPath = path.join(contentDir, "sections", `${id}.json`);
|
|
240
|
+
sectionFiles[id] = JSON.parse(await fs.readFile(sectionPath, "utf-8"));
|
|
241
|
+
}
|
|
242
|
+
return mergeSiteContent(index, sectionFiles);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export {
|
|
246
|
+
cn,
|
|
247
|
+
toSectionId,
|
|
248
|
+
generateNavLinks,
|
|
249
|
+
deriveContrast,
|
|
250
|
+
sanitizeHtml,
|
|
251
|
+
gridColsClass,
|
|
252
|
+
curatedIcons,
|
|
253
|
+
getIcon,
|
|
254
|
+
buildGoogleFontsUrl,
|
|
255
|
+
formatTimestamp,
|
|
256
|
+
safeRedirect,
|
|
257
|
+
createEvent,
|
|
258
|
+
editModeEvent,
|
|
259
|
+
navChangeEvent,
|
|
260
|
+
darkModeEvent,
|
|
261
|
+
mergeSiteContent,
|
|
262
|
+
loadStaticSiteContent,
|
|
263
|
+
loadSiteContent
|
|
264
|
+
};
|