@morphika/webframe 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/LICENSE +21 -0
- package/README.md +46 -0
- package/admin/assets.ts +4 -0
- package/admin/database.ts +4 -0
- package/admin/index.ts +6 -0
- package/admin/login.ts +4 -0
- package/admin/navigation.ts +4 -0
- package/admin/pages-editor.ts +4 -0
- package/admin/pages.ts +4 -0
- package/admin/projects-editor.ts +4 -0
- package/admin/projects.ts +4 -0
- package/admin/settings.ts +4 -0
- package/admin/setup.ts +4 -0
- package/admin/storage.ts +4 -0
- package/admin/styles.ts +4 -0
- package/app/(site)/[slug]/loading.tsx +20 -0
- package/app/(site)/[slug]/page.tsx +83 -0
- package/app/(site)/error.tsx +32 -0
- package/app/(site)/layout.tsx +53 -0
- package/app/(site)/loading.tsx +20 -0
- package/app/(site)/not-found.tsx +41 -0
- package/app/(site)/page.tsx +43 -0
- package/app/(site)/preview/page.tsx +99 -0
- package/app/(site)/work/[slug]/loading.tsx +23 -0
- package/app/(site)/work/[slug]/page.tsx +84 -0
- package/app/admin/assets/page.tsx +573 -0
- package/app/admin/database/page.tsx +302 -0
- package/app/admin/error.tsx +53 -0
- package/app/admin/layout.tsx +273 -0
- package/app/admin/login/page.tsx +88 -0
- package/app/admin/navigation/page.tsx +157 -0
- package/app/admin/page.tsx +17 -0
- package/app/admin/pages/[slug]/page.tsx +849 -0
- package/app/admin/pages/page.tsx +588 -0
- package/app/admin/projects/[slug]/page.tsx +3 -0
- package/app/admin/projects/page.tsx +669 -0
- package/app/admin/settings/page.tsx +132 -0
- package/app/admin/setup/page.tsx +64 -0
- package/app/admin/storage/page.tsx +518 -0
- package/app/admin/styles/page.tsx +243 -0
- package/app/api/admin/assets/file/route.ts +81 -0
- package/app/api/admin/assets/health/route.ts +170 -0
- package/app/api/admin/assets/register/route.ts +163 -0
- package/app/api/admin/assets/registry/route.ts +98 -0
- package/app/api/admin/assets/relink/confirm/route.ts +242 -0
- package/app/api/admin/assets/relink/route.ts +202 -0
- package/app/api/admin/assets/scan/route.ts +271 -0
- package/app/api/admin/auth/route.ts +160 -0
- package/app/api/admin/custom-sections/[slug]/route.ts +159 -0
- package/app/api/admin/custom-sections/route.ts +127 -0
- package/app/api/admin/database/route.ts +53 -0
- package/app/api/admin/pages/[slug]/duplicate/route.ts +91 -0
- package/app/api/admin/pages/[slug]/route.ts +617 -0
- package/app/api/admin/pages/[slug]/set-home/route.ts +76 -0
- package/app/api/admin/pages/route.ts +129 -0
- package/app/api/admin/preview/route.ts +53 -0
- package/app/api/admin/r2/connect/route.ts +181 -0
- package/app/api/admin/r2/delete/route.ts +198 -0
- package/app/api/admin/r2/disconnect/route.ts +42 -0
- package/app/api/admin/r2/rename/route.ts +265 -0
- package/app/api/admin/r2/status/route.ts +106 -0
- package/app/api/admin/r2/upload-url/route.ts +148 -0
- package/app/api/admin/revalidate/route.ts +55 -0
- package/app/api/admin/settings/route.ts +279 -0
- package/app/api/admin/setup/complete/route.ts +51 -0
- package/app/api/admin/setup/route.ts +118 -0
- package/app/api/admin/storage/switch/route.ts +117 -0
- package/app/api/admin/styles/fonts/route.ts +97 -0
- package/app/api/admin/styles/route.ts +304 -0
- package/app/api/assets/[...path]/route.ts +98 -0
- package/app/api/custom-sections/[id]/route.ts +43 -0
- package/app/api/draft-mode/disable/route.ts +10 -0
- package/app/api/draft-mode/enable/route.ts +26 -0
- package/app/api/projects/route.ts +42 -0
- package/app/api/styles/route.ts +88 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +7 -0
- package/app/layout.tsx +53 -0
- package/app/robots.ts +17 -0
- package/app/sitemap.ts +48 -0
- package/app/studio/[[...index]]/page.tsx +8 -0
- package/components/admin/MetadataEditor.tsx +173 -0
- package/components/admin/PublishToggle.tsx +130 -0
- package/components/admin/icons.tsx +40 -0
- package/components/admin/nav-builder/NavBuilder.tsx +182 -0
- package/components/admin/nav-builder/NavBuilderGrid.tsx +326 -0
- package/components/admin/nav-builder/NavGeneralSettings.tsx +275 -0
- package/components/admin/nav-builder/NavGridCell.tsx +48 -0
- package/components/admin/nav-builder/NavGridItem.tsx +189 -0
- package/components/admin/nav-builder/NavItemSettings.tsx +288 -0
- package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -0
- package/components/admin/nav-builder/NavLivePreview.tsx +125 -0
- package/components/admin/nav-builder/NavSettingsFields.tsx +248 -0
- package/components/admin/nav-builder/NavSettingsPanel.tsx +127 -0
- package/components/admin/nav-builder/index.ts +10 -0
- package/components/admin/nav-builder/nav-builder-utils.ts +238 -0
- package/components/admin/setup-wizard/BrandingStep.tsx +218 -0
- package/components/admin/setup-wizard/DatabaseStep.tsx +331 -0
- package/components/admin/setup-wizard/DoneStep.tsx +187 -0
- package/components/admin/setup-wizard/SetupWizard.tsx +166 -0
- package/components/admin/setup-wizard/StorageStep.tsx +308 -0
- package/components/admin/setup-wizard/WelcomeStep.tsx +96 -0
- package/components/admin/setup-wizard/index.ts +9 -0
- package/components/admin/styles/ColorsEditor.tsx +214 -0
- package/components/admin/styles/FontsEditor.tsx +258 -0
- package/components/admin/styles/GridLayoutEditor.tsx +292 -0
- package/components/admin/styles/LinksButtonsEditor.tsx +120 -0
- package/components/admin/styles/TypographyEditor.tsx +266 -0
- package/components/admin/styles/index.ts +9 -0
- package/components/admin/styles/shared.tsx +68 -0
- package/components/blocks/BlockRenderer.tsx +404 -0
- package/components/blocks/ButtonBlockRenderer.tsx +52 -0
- package/components/blocks/CoverBlockRenderer.tsx +239 -0
- package/components/blocks/CustomSectionInstanceRenderer.tsx +82 -0
- package/components/blocks/EnterAnimationWrapper.tsx +140 -0
- package/components/blocks/HoverAnimationWrapper.tsx +308 -0
- package/components/blocks/ImageBlockRenderer.tsx +61 -0
- package/components/blocks/ImageGridBlockRenderer.tsx +545 -0
- package/components/blocks/PageBackground.tsx +28 -0
- package/components/blocks/PageNavAnimation.tsx +35 -0
- package/components/blocks/PageNavColor.tsx +24 -0
- package/components/blocks/PageRenderer.tsx +142 -0
- package/components/blocks/ParallaxGroupRenderer.tsx +448 -0
- package/components/blocks/ParallaxSlideRenderer.tsx +175 -0
- package/components/blocks/ProjectGridBlockRenderer.tsx +556 -0
- package/components/blocks/SectionRenderer.tsx +170 -0
- package/components/blocks/SectionV2Renderer.tsx +330 -0
- package/components/blocks/ShaderCanvas.tsx +392 -0
- package/components/blocks/SpacerBlockRenderer.tsx +17 -0
- package/components/blocks/TextBlockRenderer.tsx +87 -0
- package/components/blocks/TypewriterRichText.tsx +464 -0
- package/components/blocks/TypewriterWrapper.tsx +149 -0
- package/components/blocks/VideoBlockRenderer.tsx +304 -0
- package/components/blocks/index.ts +2 -0
- package/components/builder/AssetBrowser.tsx +2 -0
- package/components/builder/BlockLivePreview.tsx +101 -0
- package/components/builder/BlockTypePicker.tsx +178 -0
- package/components/builder/BuilderCanvas.tsx +354 -0
- package/components/builder/CanvasMinimap.tsx +200 -0
- package/components/builder/CanvasToolbar.tsx +202 -0
- package/components/builder/ColorPicker.tsx +243 -0
- package/components/builder/ColorSwatchPicker.tsx +274 -0
- package/components/builder/ColumnDragContext.tsx +51 -0
- package/components/builder/ColumnDragOverlay.tsx +110 -0
- package/components/builder/CustomSectionInstanceCard.tsx +97 -0
- package/components/builder/DeviceFrame.tsx +123 -0
- package/components/builder/DndWrapper.tsx +337 -0
- package/components/builder/InsertionLines.tsx +186 -0
- package/components/builder/ParallaxGroupCanvas.tsx +228 -0
- package/components/builder/ParallaxSlideHeader.tsx +113 -0
- package/components/builder/ReadOnlyFrame.tsx +417 -0
- package/components/builder/SectionEditorBar.tsx +288 -0
- package/components/builder/SectionTypePicker.tsx +422 -0
- package/components/builder/SectionV2Canvas.tsx +297 -0
- package/components/builder/SectionV2Column.tsx +488 -0
- package/components/builder/SettingsPanel.tsx +911 -0
- package/components/builder/SortableBlock.tsx +230 -0
- package/components/builder/SortableRow.tsx +362 -0
- package/components/builder/VirtualAssetGrid.tsx +397 -0
- package/components/builder/asset-browser/AssetBrowser.tsx +178 -0
- package/components/builder/asset-browser/FileLightbox.tsx +116 -0
- package/components/builder/asset-browser/FolderTreeItem.tsx +55 -0
- package/components/builder/asset-browser/R2BrowserContent.tsx +436 -0
- package/components/builder/asset-browser/R2ContextMenu.tsx +98 -0
- package/components/builder/asset-browser/VideoThumbnail.tsx +63 -0
- package/components/builder/asset-browser/helpers.ts +88 -0
- package/components/builder/asset-browser/index.ts +1 -0
- package/components/builder/asset-browser/types.ts +49 -0
- package/components/builder/asset-browser/useAssetBrowser.ts +344 -0
- package/components/builder/asset-browser/useR2DragDrop.ts +116 -0
- package/components/builder/asset-browser/useR2Operations.ts +189 -0
- package/components/builder/blockStyles.tsx +295 -0
- package/components/builder/editors/ButtonBlockEditor.tsx +184 -0
- package/components/builder/editors/CoverBlockEditor.tsx +488 -0
- package/components/builder/editors/EnterAnimationPicker.tsx +297 -0
- package/components/builder/editors/HoverEffectPicker.tsx +209 -0
- package/components/builder/editors/ImageBlockEditor.tsx +206 -0
- package/components/builder/editors/ImageGridBlockEditor.tsx +386 -0
- package/components/builder/editors/ProjectGridEditor.tsx +648 -0
- package/components/builder/editors/SpacerBlockEditor.tsx +167 -0
- package/components/builder/editors/StaggerSettings.tsx +108 -0
- package/components/builder/editors/TextAlignmentIcons.tsx +39 -0
- package/components/builder/editors/TextBlockEditor.tsx +462 -0
- package/components/builder/editors/TextStylePicker.tsx +183 -0
- package/components/builder/editors/VideoBlockEditor.tsx +278 -0
- package/components/builder/editors/index.ts +10 -0
- package/components/builder/editors/shared.tsx +345 -0
- package/components/builder/hooks/useColumnDrag.ts +472 -0
- package/components/builder/hooks/useColumnResize.ts +221 -0
- package/components/builder/index.ts +12 -0
- package/components/builder/live-preview/LiveButtonPreview.tsx +38 -0
- package/components/builder/live-preview/LiveCoverPreview.tsx +146 -0
- package/components/builder/live-preview/LiveImageGridPreview.tsx +123 -0
- package/components/builder/live-preview/LiveImagePreview.tsx +107 -0
- package/components/builder/live-preview/LiveProjectGridPreview.tsx +1010 -0
- package/components/builder/live-preview/LiveSpacerPreview.tsx +9 -0
- package/components/builder/live-preview/LiveTextEditor.tsx +198 -0
- package/components/builder/live-preview/LiveVideoPreview.tsx +98 -0
- package/components/builder/live-preview/index.ts +10 -0
- package/components/builder/live-preview/shared.tsx +153 -0
- package/components/builder/settings-panel/BlockLayoutTab.tsx +532 -0
- package/components/builder/settings-panel/BlockSettings.tsx +94 -0
- package/components/builder/settings-panel/ColumnV2Settings.tsx +160 -0
- package/components/builder/settings-panel/LayoutTab.tsx +310 -0
- package/components/builder/settings-panel/PageSettings.tsx +200 -0
- package/components/builder/settings-panel/ParallaxGroupSettings.tsx +118 -0
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +178 -0
- package/components/builder/settings-panel/SectionV2AnimationTab.tsx +103 -0
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +312 -0
- package/components/builder/settings-panel/SectionV2Settings.tsx +323 -0
- package/components/builder/settings-panel/TRBLInputs.tsx +51 -0
- package/components/builder/settings-panel/index.ts +19 -0
- package/components/builder/settings-panel/responsive-helpers.ts +524 -0
- package/components/ui/CustomCursor.tsx +118 -0
- package/components/ui/NavContentLightbox.tsx +152 -0
- package/components/ui/Navbar.tsx +582 -0
- package/components/ui/PortfolioTracker.tsx +87 -0
- package/components/ui/ScrollToTop.tsx +47 -0
- package/lib/animation/enter-presets.ts +147 -0
- package/lib/animation/enter-resolve.ts +90 -0
- package/lib/animation/enter-types.ts +128 -0
- package/lib/animation/hover-effect-presets.ts +210 -0
- package/lib/animation/hover-effect-types.ts +126 -0
- package/lib/asset-retry.ts +111 -0
- package/lib/assets.ts +92 -0
- package/lib/audit.ts +35 -0
- package/lib/auth-token.ts +94 -0
- package/lib/auth.ts +13 -0
- package/lib/builder/cascade-helpers.ts +51 -0
- package/lib/builder/cascade.ts +533 -0
- package/lib/builder/constants.ts +103 -0
- package/lib/builder/defaults.ts +182 -0
- package/lib/builder/history.ts +48 -0
- package/lib/builder/index.ts +21 -0
- package/lib/builder/layout-styles.ts +344 -0
- package/lib/builder/masonry.ts +166 -0
- package/lib/builder/responsive.ts +156 -0
- package/lib/builder/serializer.ts +845 -0
- package/lib/builder/store-blocks.ts +193 -0
- package/lib/builder/store-canvas.ts +319 -0
- package/lib/builder/store-helpers.ts +490 -0
- package/lib/builder/store-sections.ts +709 -0
- package/lib/builder/store.ts +333 -0
- package/lib/builder/templates.ts +297 -0
- package/lib/builder/types.ts +374 -0
- package/lib/builder/utils.ts +37 -0
- package/lib/color-utils.ts +116 -0
- package/lib/config/index.ts +57 -0
- package/lib/config/types.ts +122 -0
- package/lib/contexts/AssetContext.tsx +79 -0
- package/lib/contexts/NavAnimationContext.tsx +44 -0
- package/lib/contexts/NavColorContext.tsx +38 -0
- package/lib/contexts/PageExitContext.tsx +194 -0
- package/lib/contexts/ThumbStatusContext.tsx +83 -0
- package/lib/csrf-client.ts +34 -0
- package/lib/csrf.ts +68 -0
- package/lib/format-utils.ts +24 -0
- package/lib/hooks/useViewport.ts +42 -0
- package/lib/logger.ts +81 -0
- package/lib/revalidate.ts +23 -0
- package/lib/sanitize.ts +91 -0
- package/lib/sanity/client.ts +8 -0
- package/lib/sanity/queries.ts +486 -0
- package/lib/sanity/types.ts +869 -0
- package/lib/sanity/writeClient.ts +24 -0
- package/lib/security.ts +402 -0
- package/lib/setup/detect.ts +156 -0
- package/lib/shader/glsl/index.ts +27 -0
- package/lib/shader/glsl/pixelate.ts +51 -0
- package/lib/shader/glsl/rgb-shift.ts +45 -0
- package/lib/shader/glsl/ripple.ts +46 -0
- package/lib/shader/glsl/vertex.ts +14 -0
- package/lib/storage/index.ts +211 -0
- package/lib/storage/r2-adapter.ts +286 -0
- package/lib/storage/types.ts +125 -0
- package/lib/styles/provider.tsx +267 -0
- package/lib/thumbnails/generate.ts +151 -0
- package/lib/utils.ts +6 -0
- package/package.json +212 -0
- package/sanity/compose.ts +65 -0
- package/sanity/sanity.config.ts +126 -0
- package/sanity/schemas/assetRegistry.ts +301 -0
- package/sanity/schemas/blocks/blockLayout.ts +90 -0
- package/sanity/schemas/blocks/buttonBlock.ts +82 -0
- package/sanity/schemas/blocks/coverBlock.ts +229 -0
- package/sanity/schemas/blocks/imageBlock.ts +58 -0
- package/sanity/schemas/blocks/imageGridBlock.ts +112 -0
- package/sanity/schemas/blocks/index.ts +9 -0
- package/sanity/schemas/blocks/projectGridBlock.ts +251 -0
- package/sanity/schemas/blocks/spacerBlock.ts +41 -0
- package/sanity/schemas/blocks/textBlock.ts +139 -0
- package/sanity/schemas/blocks/videoBlock.ts +80 -0
- package/sanity/schemas/customSection.ts +69 -0
- package/sanity/schemas/customSectionInstance.ts +163 -0
- package/sanity/schemas/index.ts +111 -0
- package/sanity/schemas/objects/enterAnimationConfig.ts +72 -0
- package/sanity/schemas/objects/hoverEffectConfig.ts +90 -0
- package/sanity/schemas/objects/parallaxGroup.ts +66 -0
- package/sanity/schemas/objects/parallaxSlide.ts +217 -0
- package/sanity/schemas/objects/typewriterConfig.ts +38 -0
- package/sanity/schemas/page.ts +162 -0
- package/sanity/schemas/pageSection.ts +157 -0
- package/sanity/schemas/pageSectionV2.ts +269 -0
- package/sanity/schemas/siteSettings.ts +256 -0
- package/sanity/schemas/siteStyles.ts +210 -0
- package/site/error.ts +4 -0
- package/site/index.ts +8 -0
- package/site/not-found.ts +4 -0
- package/site/page.ts +4 -0
- package/site/preview.ts +4 -0
- package/site/robots.ts +4 -0
- package/site/sitemap.ts +4 -0
- package/site/work.ts +4 -0
- package/studio/index.ts +4 -0
- package/styles/admin.css +85 -0
- package/styles/animations.css +237 -0
- package/styles/base.css +148 -0
- package/styles/globals.css +10 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Viewport breakpoints for the public site.
|
|
7
|
+
*
|
|
8
|
+
* The builder previews at fixed frame widths (desktop=1200, tablet=810,
|
|
9
|
+
* phone=390), but on the live site we use wider ranges so that real
|
|
10
|
+
* devices hit the correct layout:
|
|
11
|
+
*
|
|
12
|
+
* Desktop: > 810px
|
|
13
|
+
* Tablet: 481–810px
|
|
14
|
+
* Phone: ≤ 480px (covers iPhone SE 375, iPhone 14 390, Pixel 412, etc.)
|
|
15
|
+
*
|
|
16
|
+
* Used by public-site renderers to apply the same responsive overrides
|
|
17
|
+
* that the builder previews via resolveBlock().
|
|
18
|
+
*/
|
|
19
|
+
export type Viewport = "desktop" | "tablet" | "phone";
|
|
20
|
+
|
|
21
|
+
function getViewport(width: number): Viewport {
|
|
22
|
+
if (width <= 480) return "phone";
|
|
23
|
+
if (width <= 810) return "tablet";
|
|
24
|
+
return "desktop";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns the current viewport based on window width.
|
|
29
|
+
* Updates on resize. SSR-safe (defaults to "desktop").
|
|
30
|
+
*/
|
|
31
|
+
export function useViewport(): Viewport {
|
|
32
|
+
const [viewport, setViewport] = useState<Viewport>("desktop");
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const update = () => setViewport(getViewport(window.innerWidth));
|
|
36
|
+
update();
|
|
37
|
+
window.addEventListener("resize", update);
|
|
38
|
+
return () => window.removeEventListener("resize", update);
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
return viewport;
|
|
42
|
+
}
|
package/lib/logger.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured logger for production code.
|
|
3
|
+
*
|
|
4
|
+
* Replaces raw console.log/error/warn with consistent, structured output.
|
|
5
|
+
* Server-side logs emit JSON for easy parsing by log aggregation tools.
|
|
6
|
+
* Client-side logs use console methods with consistent formatting.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* logger.error("[Storage]", "Operation failed", { status: 401 });
|
|
10
|
+
* logger.warn("[R2]", "CORS auto-configure failed");
|
|
11
|
+
* logger.info("[Scan]", "Retrying with empty root");
|
|
12
|
+
* logger.debug("[Storage]", "Provider resolved", { provider: "r2" });
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
16
|
+
|
|
17
|
+
const IS_SERVER = typeof window === "undefined";
|
|
18
|
+
const IS_PROD = process.env.NODE_ENV === "production";
|
|
19
|
+
|
|
20
|
+
function formatLog(level: LogLevel, tag: string, message: string, details?: unknown): string {
|
|
21
|
+
const entry: Record<string, unknown> = {
|
|
22
|
+
level,
|
|
23
|
+
tag,
|
|
24
|
+
message,
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
};
|
|
27
|
+
if (details !== undefined) {
|
|
28
|
+
// For Error objects, extract message and stack
|
|
29
|
+
if (details instanceof Error) {
|
|
30
|
+
entry.error = { message: details.message, stack: details.stack };
|
|
31
|
+
} else {
|
|
32
|
+
entry.details = details;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return JSON.stringify(entry);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function log(level: LogLevel, tag: string, message: string, details?: unknown): void {
|
|
39
|
+
// Suppress debug in production
|
|
40
|
+
if (level === "debug" && IS_PROD) return;
|
|
41
|
+
|
|
42
|
+
if (IS_SERVER) {
|
|
43
|
+
// Server: structured JSON for log aggregation
|
|
44
|
+
const line = formatLog(level, tag, message, details);
|
|
45
|
+
switch (level) {
|
|
46
|
+
case "error":
|
|
47
|
+
console.error(line);
|
|
48
|
+
break;
|
|
49
|
+
case "warn":
|
|
50
|
+
console.warn(line);
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
console.log(line);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
// Client: human-readable console output
|
|
58
|
+
const prefix = `${tag} ${message}`;
|
|
59
|
+
switch (level) {
|
|
60
|
+
case "error":
|
|
61
|
+
details !== undefined ? console.error(prefix, details) : console.error(prefix);
|
|
62
|
+
break;
|
|
63
|
+
case "warn":
|
|
64
|
+
details !== undefined ? console.warn(prefix, details) : console.warn(prefix);
|
|
65
|
+
break;
|
|
66
|
+
case "debug":
|
|
67
|
+
details !== undefined ? console.debug(prefix, details) : console.debug(prefix);
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
details !== undefined ? console.log(prefix, details) : console.log(prefix);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const logger = {
|
|
77
|
+
debug: (tag: string, message: string, details?: unknown) => log("debug", tag, message, details),
|
|
78
|
+
info: (tag: string, message: string, details?: unknown) => log("info", tag, message, details),
|
|
79
|
+
warn: (tag: string, message: string, details?: unknown) => log("warn", tag, message, details),
|
|
80
|
+
error: (tag: string, message: string, details?: unknown) => log("error", tag, message, details),
|
|
81
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { csrfHeaders } from "../lib/csrf-client";
|
|
2
|
+
import { logger } from "../lib/logger";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Trigger on-demand ISR revalidation from the admin UI.
|
|
6
|
+
* Call after any save that should be reflected on the public site.
|
|
7
|
+
*
|
|
8
|
+
* @param paths - Specific paths to revalidate, e.g. ["/", "/about"].
|
|
9
|
+
* If omitted, revalidates the entire site layout.
|
|
10
|
+
*/
|
|
11
|
+
export async function revalidateSite(paths?: string[]): Promise<void> {
|
|
12
|
+
try {
|
|
13
|
+
await fetch("/api/admin/revalidate", {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: { "Content-Type": "application/json", ...csrfHeaders() },
|
|
16
|
+
body: JSON.stringify(paths ? { paths } : {}),
|
|
17
|
+
});
|
|
18
|
+
} catch {
|
|
19
|
+
// Silent fail — revalidation is best-effort.
|
|
20
|
+
// The ISR timer will still catch up within 1 hour.
|
|
21
|
+
logger.warn("[Revalidate]", "Revalidation request failed (non-critical)");
|
|
22
|
+
}
|
|
23
|
+
}
|
package/lib/sanitize.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML sanitization utilities.
|
|
3
|
+
*
|
|
4
|
+
* Defense-in-depth: currently the site doesn't render raw HTML from user input,
|
|
5
|
+
* but this module provides sanitization in case features evolve.
|
|
6
|
+
*
|
|
7
|
+
* For production use with user-generated HTML content, install DOMPurify:
|
|
8
|
+
* npm install dompurify @types/dompurify
|
|
9
|
+
* and replace the stripHtml/sanitizeHtml functions below with DOMPurify calls.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Characters that must be escaped in HTML contexts */
|
|
13
|
+
const HTML_ESCAPE_MAP: Record<string, string> = {
|
|
14
|
+
"&": "&",
|
|
15
|
+
"<": "<",
|
|
16
|
+
">": ">",
|
|
17
|
+
'"': """,
|
|
18
|
+
"'": "'",
|
|
19
|
+
"/": "/",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const HTML_ESCAPE_RE = /[&<>"'/]/g;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Escape a string for safe insertion into HTML.
|
|
26
|
+
* Prevents XSS by encoding characters that have special meaning in HTML.
|
|
27
|
+
*/
|
|
28
|
+
export function escapeHtml(str: string): string {
|
|
29
|
+
if (!str || typeof str !== "string") return "";
|
|
30
|
+
return str.replace(HTML_ESCAPE_RE, (char) => HTML_ESCAPE_MAP[char] || char);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Strip all HTML tags from a string, leaving only text content.
|
|
35
|
+
* Use this when you need plain text from potentially HTML-containing input.
|
|
36
|
+
*/
|
|
37
|
+
export function stripHtml(str: string): string {
|
|
38
|
+
if (!str || typeof str !== "string") return "";
|
|
39
|
+
// Remove all tags
|
|
40
|
+
return str.replace(/<[^>]*>/g, "");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Basic HTML sanitization — strips dangerous elements and attributes.
|
|
45
|
+
* Allows a limited set of safe tags for basic formatting.
|
|
46
|
+
*
|
|
47
|
+
* NOTE: For full HTML sanitization with user-generated content,
|
|
48
|
+
* replace this with DOMPurify. This is a minimal server-side fallback.
|
|
49
|
+
*/
|
|
50
|
+
const SAFE_TAGS = new Set([
|
|
51
|
+
"p", "br", "b", "i", "em", "strong", "u", "s",
|
|
52
|
+
"h1", "h2", "h3", "h4", "h5", "h6",
|
|
53
|
+
"ul", "ol", "li", "blockquote", "code", "pre",
|
|
54
|
+
"a", "span", "div",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const SAFE_ATTRS = new Set([
|
|
58
|
+
"href", "title", "class", "id",
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sanitize HTML by stripping dangerous tags and attributes.
|
|
63
|
+
* For defense-in-depth only — prefer escapeHtml() or DOMPurify for
|
|
64
|
+
* actual user-generated HTML rendering.
|
|
65
|
+
*/
|
|
66
|
+
export function sanitizeHtml(html: string): string {
|
|
67
|
+
if (!html || typeof html !== "string") return "";
|
|
68
|
+
|
|
69
|
+
return html
|
|
70
|
+
// Remove script/style/iframe/object/embed tags and their contents
|
|
71
|
+
.replace(/<(script|style|iframe|object|embed|form)[^>]*>[\s\S]*?<\/\1>/gi, "")
|
|
72
|
+
// Remove event handler attributes
|
|
73
|
+
.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "")
|
|
74
|
+
// Remove javascript: and data: URLs in attributes
|
|
75
|
+
.replace(/(?:href|src|action)\s*=\s*(?:"(?:javascript|data|vbscript):[^"]*"|'(?:javascript|data|vbscript):[^']*')/gi, "")
|
|
76
|
+
// Remove tags not in the safe list
|
|
77
|
+
.replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>/g, (match, tagName) => {
|
|
78
|
+
const tag = tagName.toLowerCase();
|
|
79
|
+
if (!SAFE_TAGS.has(tag)) return "";
|
|
80
|
+
|
|
81
|
+
// For opening tags, strip unsafe attributes
|
|
82
|
+
if (!match.startsWith("</")) {
|
|
83
|
+
return match.replace(/\s+([a-zA-Z-]+)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/g,
|
|
84
|
+
(attrMatch, attrName) => {
|
|
85
|
+
return SAFE_ATTRS.has(attrName.toLowerCase()) ? attrMatch : "";
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return match;
|
|
90
|
+
});
|
|
91
|
+
}
|