@woodsportal/hubspot-kit 1.0.31 → 1.0.34
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/CHANGELOG.md +45 -0
- package/dist/cli.js +798 -281
- package/dist/cli.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/examples/fixtures/adopted-module-hybrid/woodscli.json +1 -1
- package/examples/fixtures/adopted-module-monolith/woodscli.json +1 -1
- package/examples/greenfield/module-hybrid-cdn/public/fields.json +26 -20
- package/examples/greenfield/module-hybrid-cdn/public/meta.json +9 -0
- package/examples/greenfield/module-hybrid-cdn/public/module.html +3 -4
- package/examples/greenfield/module-hybrid-cdn/src/assets/css/main.prod.css +6 -0
- package/examples/greenfield/module-hybrid-cdn/src/cdn/app-entry.ts +5 -0
- package/examples/greenfield/module-hybrid-cdn/src/hubspot/bootstrap.ts +6 -0
- package/examples/greenfield/module-hybrid-cdn/src/portal/ensure-portal-cdn-stylesheet.ts +58 -0
- package/examples/greenfield/module-hybrid-cdn/woodscli.json +1 -1
- package/examples/greenfield/module-monolith/woodscli.json +1 -1
- package/package.json +2 -2
- package/scripts/build-cdn.mjs +6 -1
- package/scripts/bump-module-build-id.mjs +28 -0
- package/scripts/hubspot-module-postbuild.mjs +35 -1
- package/scripts/load-portal-cdn-config.mjs +21 -3
- package/scripts/portal-build-id.mjs +346 -0
- package/scripts/portal-build-id.test.mjs +132 -0
- package/scripts/publish-cdn-github.mjs +60 -15
- package/scripts/resolve-cdn-base-url.mjs +2 -1
- package/scripts/resolve-module-build-id-meta.mjs +7 -0
- package/scripts/resolve-portal-build-id.mjs +16 -56
- package/scripts/tailwind/prefix-plugin.js +46 -8
- package/scripts/tailwind/tailwind-content-plugin.js +0 -1
- package/scripts/vite/portal-build-shared.js +3 -1
- package/src/dev-module-config/__tests__/flatten-visible-fields.test.ts +107 -0
- package/src/dev-module-config/runtime/module-config-bridge.ts +10 -2
- package/src/dev-module-config/schema/types.ts +10 -0
- package/src/dev-module-config/schema/visibility.ts +42 -4
- package/src/dev-module-config/ui/ModuleConfigShell.tsx +10 -9
- package/src/dev-module-config/ui/hubspot/HsOccurrenceGroupEditor.tsx +3 -3
- package/templates/module-hybrid-cdn/public/fields.json +52 -41
- package/templates/module-hybrid-cdn/public/meta.json +9 -0
- package/templates/module-hybrid-cdn/public/module.html +18 -4
- package/templates/module-hybrid-cdn/src/assets/css/main.dev.css +8 -0
- package/templates/module-hybrid-cdn/src/assets/css/main.prod.css +12 -0
- package/templates/module-hybrid-cdn/src/assets/css/tailwind.css.config.css +7 -0
- package/templates/module-hybrid-cdn/src/cdn/app-entry.ts +14 -3
- package/templates/module-hybrid-cdn/src/features/starter/components/starter-home.tsx +9 -11
- package/templates/module-hybrid-cdn/src/features/starter/hooks/starter-preview-values.ts +23 -5
- package/templates/module-hybrid-cdn/src/features/starter/hooks/use-starter-preview.dev.ts +1 -0
- package/templates/module-hybrid-cdn/src/hubspot/bootstrap.ts +6 -0
- package/templates/module-hybrid-cdn/src/portal/ensure-portal-cdn-stylesheet.ts +115 -0
- package/templates/module-hybrid-cdn/src/portal/mount-portal.tsx +35 -5
- package/templates/module-hybrid-cdn/woodscli.json +1 -1
- package/templates/module-monolith/public/fields.json +23 -16
- package/templates/module-monolith/public/meta.json +9 -0
- package/templates/module-monolith/src/features/starter/components/starter-home.tsx +9 -11
- package/templates/module-monolith/src/features/starter/hooks/starter-preview-values.ts +23 -5
- package/templates/module-monolith/src/features/starter/hooks/use-starter-preview.dev.ts +1 -0
- package/templates/module-monolith/woodscli.json +1 -1
- package/vite/vite.config.dev.mjs +16 -10
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CDN application chunk — mounts after vendor.js (HubSpot require_js order).
|
|
3
|
+
*
|
|
4
|
+
* Build-time: main.prod.css → app.{buildId}.css (merged into module.css at post-build).
|
|
5
|
+
* Runtime: skip JS CSS imports — HubSpot serves module.css on published pages.
|
|
3
6
|
*/
|
|
4
|
-
import '@/assets/css/main.
|
|
7
|
+
import '@/assets/css/main.prod.css'
|
|
5
8
|
|
|
6
9
|
import { resolvePortalApiEndpoint } from '@/portal/api-endpoint'
|
|
10
|
+
import { ensurePortalCdnStylesheetIfNeeded } from '@/portal/ensure-portal-cdn-stylesheet'
|
|
7
11
|
import { mountPortal } from '@/portal/mount-portal'
|
|
8
12
|
|
|
9
13
|
declare const __PORTAL_BUILD_ID__: string | undefined
|
|
@@ -31,7 +35,14 @@ async function autoMount() {
|
|
|
31
35
|
if (!document.getElementById('app')) {
|
|
32
36
|
return
|
|
33
37
|
}
|
|
34
|
-
await
|
|
38
|
+
await ensurePortalCdnStylesheetIfNeeded()
|
|
39
|
+
await mountPortal({ skipRuntimeCss: true })
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
if (document.readyState === 'loading') {
|
|
43
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
44
|
+
void autoMount()
|
|
45
|
+
})
|
|
46
|
+
} else {
|
|
47
|
+
void autoMount()
|
|
48
|
+
}
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { useStarterPreview } from '../hooks/use-starter-preview'
|
|
2
2
|
import { STARTER_FEATURES, WOODSCLI_ABOUT } from '../content/woodscli-guide-content'
|
|
3
3
|
|
|
4
|
-
function heroClass(style: ReturnType<typeof useStarterPreview>['heroStyle']): string {
|
|
5
|
-
if (style === 'minimal') {
|
|
6
|
-
return 'bg-[var(--mc-bg-shell,#f5f8fa)] text-[var(--mc-text,#33475b)] border-b border-[var(--mc-border,#cbd6e2)]'
|
|
7
|
-
}
|
|
8
|
-
if (style === 'bold') {
|
|
9
|
-
return 'bg-[#1c1c1f] text-white'
|
|
10
|
-
}
|
|
11
|
-
return 'bg-gradient-to-br from-[var(--starter-accent,#0091ae)] to-[#ff7a59] text-white'
|
|
12
|
-
}
|
|
13
|
-
|
|
14
4
|
export function StarterHome() {
|
|
15
5
|
const { title, tagline, showFeatureCards, accentColor, heroStyle } = useStarterPreview()
|
|
16
6
|
|
|
17
7
|
return (
|
|
18
8
|
<>
|
|
19
9
|
<div style={{ ['--starter-accent' as string]: accentColor }}>
|
|
20
|
-
<section
|
|
10
|
+
<section
|
|
11
|
+
className={
|
|
12
|
+
heroStyle === 'minimal'
|
|
13
|
+
? 'px-6 py-14 sm:px-10 sm:py-16 bg-[var(--mc-bg-shell,#f5f8fa)] text-[var(--mc-text,#33475b)] border-b border-[var(--mc-border,#cbd6e2)]'
|
|
14
|
+
: heroStyle === 'bold'
|
|
15
|
+
? 'px-6 py-14 sm:px-10 sm:py-16 bg-[#1c1c1f] text-white'
|
|
16
|
+
: 'px-6 py-14 sm:px-10 sm:py-16 bg-gradient-to-br from-[var(--starter-accent,#0091ae)] to-[#ff7a59] text-white'
|
|
17
|
+
}
|
|
18
|
+
>
|
|
21
19
|
<div className="mx-auto max-w-4xl">
|
|
22
20
|
<p className="mb-2 text-xs font-semibold uppercase tracking-widest opacity-80">
|
|
23
21
|
{WOODSCLI_ABOUT.package} · local dev
|
|
@@ -12,18 +12,36 @@ function normalizeHeroStyle(raw: unknown): StarterPreviewValues['heroStyle'] {
|
|
|
12
12
|
return 'gradient'
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/** CONTENT-tab fields may live under a `content` group (fields.json) or flat legacy keys. */
|
|
16
|
+
function readContentField(module: Record<string, unknown>, key: string): unknown {
|
|
17
|
+
const content = module.content
|
|
18
|
+
if (content && typeof content === 'object' && key in (content as Record<string, unknown>)) {
|
|
19
|
+
return (content as Record<string, unknown>)[key]
|
|
20
|
+
}
|
|
21
|
+
return module[key]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** STYLE-tab fields may live under a `styles` group (fields.json) or flat legacy keys. */
|
|
25
|
+
function readStyleField(module: Record<string, unknown>, key: string): unknown {
|
|
26
|
+
const styles = module.styles
|
|
27
|
+
if (styles && typeof styles === 'object' && key in (styles as Record<string, unknown>)) {
|
|
28
|
+
return (styles as Record<string, unknown>)[key]
|
|
29
|
+
}
|
|
30
|
+
return module[key]
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
export function mapStarterPreviewValues(module: Record<string, unknown>): StarterPreviewValues {
|
|
16
|
-
const accent = module
|
|
34
|
+
const accent = readStyleField(module, 'accent_color') as { color?: string } | undefined
|
|
17
35
|
|
|
18
36
|
return {
|
|
19
|
-
title: String(module
|
|
37
|
+
title: String(readContentField(module, 'title') ?? 'My HubSpot Portal'),
|
|
20
38
|
tagline: String(
|
|
21
|
-
module
|
|
39
|
+
readContentField(module, 'tagline') ??
|
|
22
40
|
'Customize this preview with Ctrl+Shift+Z, then replace this page with your portal app.',
|
|
23
41
|
),
|
|
24
|
-
showFeatureCards: module
|
|
42
|
+
showFeatureCards: readContentField(module, 'show_feature_cards') !== false,
|
|
25
43
|
accentColor: accent?.color ?? '#0091ae',
|
|
26
|
-
heroStyle: normalizeHeroStyle(module
|
|
44
|
+
heroStyle: normalizeHeroStyle(readStyleField(module, 'hero_style')),
|
|
27
45
|
}
|
|
28
46
|
}
|
|
29
47
|
|
|
@@ -12,6 +12,7 @@ export function subscribeStarterPreview(
|
|
|
12
12
|
): () => void {
|
|
13
13
|
const refresh = () => onChange(mapStarterPreviewValues(getModuleFieldValues()))
|
|
14
14
|
|
|
15
|
+
refresh()
|
|
15
16
|
window.addEventListener(MODULE_CONFIG_CHANGED, refresh)
|
|
16
17
|
return () => window.removeEventListener(MODULE_CONFIG_CHANGED, refresh)
|
|
17
18
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Thin HubSpot module.js bootstrap — JS app loads from CDN via require_js in module.html.
|
|
3
|
+
*
|
|
4
|
+
* CSS imports are extracted to module.css by the HubSpot Vite build (same pattern as
|
|
5
|
+
* digitalwoods.io-woodsportal-client-frontend). HubSpot CMS editor does not reliably
|
|
6
|
+
* load external jsDelivr stylesheets from module.html.
|
|
3
7
|
*/
|
|
8
|
+
import '@/assets/css/main.prod.css'
|
|
9
|
+
|
|
4
10
|
declare global {
|
|
5
11
|
interface Window {
|
|
6
12
|
WoodsPortalHubSpotBootstrap?: { ready: true }
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface Window {
|
|
3
|
+
hubSpotData?: {
|
|
4
|
+
assetBaseUrl?: string
|
|
5
|
+
buildId?: string
|
|
6
|
+
isInEditor?: boolean
|
|
7
|
+
}
|
|
8
|
+
hsInEditor?: boolean
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Resolve app.{buildId}.css on the CDN mirror (jsDelivr / custom base). */
|
|
13
|
+
export function portalCdnStylesheetUrl(): string | null {
|
|
14
|
+
const base = window.hubSpotData?.assetBaseUrl?.replace(/\/$/, '')
|
|
15
|
+
const buildId = window.hubSpotData?.buildId?.trim()
|
|
16
|
+
if (!base || !buildId) {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
return `${base}/app.${buildId}.css`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isHubSpotCmsEditor(): boolean {
|
|
23
|
+
if (typeof window === 'undefined') {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
return (
|
|
27
|
+
window.hubSpotData?.isInEditor === true ||
|
|
28
|
+
window.hsInEditor === true
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** True when HubSpot already loaded merged module.css with dw-prefixed portal utilities. */
|
|
33
|
+
export function hasPrefixedPortalStylesLoaded(): boolean {
|
|
34
|
+
if (typeof document === 'undefined') {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const link of document.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]')) {
|
|
39
|
+
if (/module\.css/i.test(link.href)) {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
for (const sheet of document.styleSheets) {
|
|
46
|
+
try {
|
|
47
|
+
const rules = sheet.cssRules
|
|
48
|
+
for (let i = 0; i < Math.min(rules.length, 100); i++) {
|
|
49
|
+
const text = rules[i]?.cssText ?? ''
|
|
50
|
+
if (text.includes('dw\\:') || text.includes('.dw:')) {
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// Cross-origin stylesheets are not readable.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// ignore
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Editor-only fallback when module.css did not load (external CDN CSS blocked in iframe). */
|
|
66
|
+
export function needsPortalCdnStylesheetFallback(): boolean {
|
|
67
|
+
if (!isHubSpotCmsEditor()) {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
return !hasPrefixedPortalStylesLoaded()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function waitForStylesheet(link: HTMLLinkElement): Promise<void> {
|
|
74
|
+
if (link.sheet) {
|
|
75
|
+
return Promise.resolve()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
link.addEventListener('load', () => resolve(), { once: true })
|
|
80
|
+
link.addEventListener('error', () => resolve(), { once: true })
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Inject CDN app.{buildId}.css when HubSpot editor preview skipped module.css. */
|
|
85
|
+
export async function ensurePortalCdnStylesheet(): Promise<void> {
|
|
86
|
+
const href = portalCdnStylesheetUrl()
|
|
87
|
+
if (!href) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const marker = 'data-portal-cdn-css'
|
|
92
|
+
const existing = document.querySelector<HTMLLinkElement>(`link[${marker}]`)
|
|
93
|
+
if (existing) {
|
|
94
|
+
if (existing.href === href) {
|
|
95
|
+
await waitForStylesheet(existing)
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
existing.remove()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const link = document.createElement('link')
|
|
102
|
+
link.rel = 'stylesheet'
|
|
103
|
+
link.href = href
|
|
104
|
+
link.crossOrigin = 'anonymous'
|
|
105
|
+
link.setAttribute(marker, 'true')
|
|
106
|
+
document.head.appendChild(link)
|
|
107
|
+
await waitForStylesheet(link)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function ensurePortalCdnStylesheetIfNeeded(): Promise<void> {
|
|
111
|
+
if (!needsPortalCdnStylesheetFallback()) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
await ensurePortalCdnStylesheet()
|
|
115
|
+
}
|
|
@@ -1,12 +1,42 @@
|
|
|
1
1
|
import ReactDOM from 'react-dom/client'
|
|
2
2
|
|
|
3
|
+
import isPrefix from '@woodsportal/hubspot-kit/scripts/tailwind/prefix.env.js'
|
|
4
|
+
|
|
3
5
|
import { App } from '@/app'
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
async function loadCss() {
|
|
8
|
+
if (isPrefix(import.meta.env.MODE)) {
|
|
9
|
+
await import('@/assets/css/main.prod.css')
|
|
10
|
+
} else {
|
|
11
|
+
await import('@/assets/css/main.dev.css')
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type MountPortalOptions = {
|
|
16
|
+
/** HubSpot serves module.css — skip duplicate global styles on CDN app chunk */
|
|
17
|
+
includeGlobalStyles?: boolean
|
|
18
|
+
/** HubSpot loads app styles via module.css — skip runtime CSS imports in CDN app chunk */
|
|
19
|
+
skipRuntimeCss?: boolean
|
|
20
|
+
/** Kit dev entry renders App separately with module-config shell */
|
|
21
|
+
skipAppRender?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Mount the portal into `#app` (CDN app chunk, monolith, or production paths). */
|
|
25
|
+
export async function mountPortal(options: MountPortalOptions = {}) {
|
|
26
|
+
const { skipRuntimeCss = false, skipAppRender = false } = options
|
|
27
|
+
|
|
28
|
+
if (!skipRuntimeCss) {
|
|
29
|
+
await loadCss()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (skipAppRender) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const rootElement = document.getElementById('app')
|
|
37
|
+
if (!rootElement) {
|
|
9
38
|
return
|
|
10
39
|
}
|
|
11
|
-
|
|
40
|
+
|
|
41
|
+
ReactDOM.createRoot(rootElement).render(<App />)
|
|
12
42
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"configVersion": 1,
|
|
3
3
|
"projectType": "module-hybrid-cdn",
|
|
4
4
|
"module": {"name": "my-portal-module", "uploadPath": "dw-modules/my-portal-module.module", "outDir": "dist/MyPortal.Module"},
|
|
5
|
-
"cdn": {"mode": "jsdelivr", "buildIdFile": ".
|
|
5
|
+
"cdn": {"mode": "jsdelivr", "buildIdFile": ".module-build-id", "mirror": "portal.cdn.json"},
|
|
6
6
|
"entries": {"app": "src/cdn/app-entry.ts", "vendor": "src/cdn/vendor-entry.ts", "hubspotBootstrap": "src/hubspot/bootstrap.ts", "localDev": "src/main.tsx"},
|
|
7
7
|
"dev": {"moduleConfigDir": ".wphs", "adapter": "./wphs.adapter.ts", "fieldsJson": "public/fields.json"}
|
|
8
8
|
}
|
|
@@ -23,22 +23,29 @@
|
|
|
23
23
|
"default": true
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
26
|
+
"type": "group",
|
|
27
|
+
"name": "styles",
|
|
28
|
+
"label": "Styles",
|
|
29
29
|
"tab": "STYLE",
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
"children": [
|
|
31
|
+
{
|
|
32
|
+
"name": "accent_color",
|
|
33
|
+
"label": "Accent color",
|
|
34
|
+
"type": "color",
|
|
35
|
+
"default": { "color": "#0091ae", "opacity": 100 }
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "hero_style",
|
|
39
|
+
"label": "Hero style",
|
|
40
|
+
"type": "choice",
|
|
41
|
+
"display": "select",
|
|
42
|
+
"choices": [
|
|
43
|
+
["gradient", "Gradient"],
|
|
44
|
+
["minimal", "Minimal"],
|
|
45
|
+
["bold", "Bold"]
|
|
46
|
+
],
|
|
47
|
+
"default": "gradient"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
43
50
|
}
|
|
44
51
|
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"label": "WoodsPortal monolith module",
|
|
3
|
+
"css_assets": [],
|
|
4
|
+
"external_js": [],
|
|
5
|
+
"global": false,
|
|
6
|
+
"host_template_types": ["PAGE", "BLOG_POST", "BLOG_LISTING"],
|
|
7
|
+
"content_types": ["SITE_PAGE", "LANDING_PAGE", "BLOG_POST", "BLOG_LISTING"],
|
|
8
|
+
"is_available_for_new_content": true
|
|
9
|
+
}
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { useStarterPreview } from '../hooks/use-starter-preview'
|
|
2
2
|
import { STARTER_FEATURES, WOODSCLI_ABOUT } from '../content/woodscli-guide-content'
|
|
3
3
|
|
|
4
|
-
function heroClass(style: ReturnType<typeof useStarterPreview>['heroStyle']): string {
|
|
5
|
-
if (style === 'minimal') {
|
|
6
|
-
return 'bg-[var(--mc-bg-shell,#f5f8fa)] text-[var(--mc-text,#33475b)] border-b border-[var(--mc-border,#cbd6e2)]'
|
|
7
|
-
}
|
|
8
|
-
if (style === 'bold') {
|
|
9
|
-
return 'bg-[#1c1c1f] text-white'
|
|
10
|
-
}
|
|
11
|
-
return 'bg-gradient-to-br from-[var(--starter-accent,#0091ae)] to-[#ff7a59] text-white'
|
|
12
|
-
}
|
|
13
|
-
|
|
14
4
|
export function StarterHome() {
|
|
15
5
|
const { title, tagline, showFeatureCards, accentColor, heroStyle } = useStarterPreview()
|
|
16
6
|
|
|
17
7
|
return (
|
|
18
8
|
<>
|
|
19
9
|
<div style={{ ['--starter-accent' as string]: accentColor }}>
|
|
20
|
-
<section
|
|
10
|
+
<section
|
|
11
|
+
className={
|
|
12
|
+
heroStyle === 'minimal'
|
|
13
|
+
? 'px-6 py-14 sm:px-10 sm:py-16 bg-[var(--mc-bg-shell,#f5f8fa)] text-[var(--mc-text,#33475b)] border-b border-[var(--mc-border,#cbd6e2)]'
|
|
14
|
+
: heroStyle === 'bold'
|
|
15
|
+
? 'px-6 py-14 sm:px-10 sm:py-16 bg-[#1c1c1f] text-white'
|
|
16
|
+
: 'px-6 py-14 sm:px-10 sm:py-16 bg-gradient-to-br from-[var(--starter-accent,#0091ae)] to-[#ff7a59] text-white'
|
|
17
|
+
}
|
|
18
|
+
>
|
|
21
19
|
<div className="mx-auto max-w-4xl">
|
|
22
20
|
<p className="mb-2 text-xs font-semibold uppercase tracking-widest opacity-80">
|
|
23
21
|
{WOODSCLI_ABOUT.package} · local dev
|
|
@@ -12,18 +12,36 @@ function normalizeHeroStyle(raw: unknown): StarterPreviewValues['heroStyle'] {
|
|
|
12
12
|
return 'gradient'
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/** CONTENT-tab fields may live under a `content` group (fields.json) or flat legacy keys. */
|
|
16
|
+
function readContentField(module: Record<string, unknown>, key: string): unknown {
|
|
17
|
+
const content = module.content
|
|
18
|
+
if (content && typeof content === 'object' && key in (content as Record<string, unknown>)) {
|
|
19
|
+
return (content as Record<string, unknown>)[key]
|
|
20
|
+
}
|
|
21
|
+
return module[key]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** STYLE-tab fields may live under a `styles` group (fields.json) or flat legacy keys. */
|
|
25
|
+
function readStyleField(module: Record<string, unknown>, key: string): unknown {
|
|
26
|
+
const styles = module.styles
|
|
27
|
+
if (styles && typeof styles === 'object' && key in (styles as Record<string, unknown>)) {
|
|
28
|
+
return (styles as Record<string, unknown>)[key]
|
|
29
|
+
}
|
|
30
|
+
return module[key]
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
export function mapStarterPreviewValues(module: Record<string, unknown>): StarterPreviewValues {
|
|
16
|
-
const accent = module
|
|
34
|
+
const accent = readStyleField(module, 'accent_color') as { color?: string } | undefined
|
|
17
35
|
|
|
18
36
|
return {
|
|
19
|
-
title: String(module
|
|
37
|
+
title: String(readContentField(module, 'title') ?? 'My HubSpot Portal'),
|
|
20
38
|
tagline: String(
|
|
21
|
-
module
|
|
39
|
+
readContentField(module, 'tagline') ??
|
|
22
40
|
'Customize this preview with Ctrl+Shift+Z, then replace this page with your portal app.',
|
|
23
41
|
),
|
|
24
|
-
showFeatureCards: module
|
|
42
|
+
showFeatureCards: readContentField(module, 'show_feature_cards') !== false,
|
|
25
43
|
accentColor: accent?.color ?? '#0091ae',
|
|
26
|
-
heroStyle: normalizeHeroStyle(module
|
|
44
|
+
heroStyle: normalizeHeroStyle(readStyleField(module, 'hero_style')),
|
|
27
45
|
}
|
|
28
46
|
}
|
|
29
47
|
|
|
@@ -12,6 +12,7 @@ export function subscribeStarterPreview(
|
|
|
12
12
|
): () => void {
|
|
13
13
|
const refresh = () => onChange(mapStarterPreviewValues(getModuleFieldValues()))
|
|
14
14
|
|
|
15
|
+
refresh()
|
|
15
16
|
window.addEventListener(MODULE_CONFIG_CHANGED, refresh)
|
|
16
17
|
return () => window.removeEventListener(MODULE_CONFIG_CHANGED, refresh)
|
|
17
18
|
}
|
package/vite/vite.config.dev.mjs
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import { defineConfig } from 'vite'
|
|
2
|
-
import viteReact from '@vitejs/plugin-react'
|
|
3
|
-
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
|
|
4
1
|
import { existsSync } from 'node:fs'
|
|
5
|
-
import {
|
|
6
|
-
import { fileURLToPath } from 'node:url'
|
|
7
|
-
import tailwindcss from '@tailwindcss/vite'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
8
3
|
|
|
4
|
+
import { importConsumerDep } from '../scripts/resolve-consumer-dep.mjs'
|
|
9
5
|
import tailwindPrefixPlugin from '../scripts/tailwind/prefix-plugin.js'
|
|
10
6
|
import TailwindContentPlugin from '../scripts/tailwind/tailwind-content-plugin.js'
|
|
11
7
|
import sdkLogBridge from '../scripts/vite/sdk-log-bridge.js'
|
|
@@ -25,8 +21,18 @@ const { config } = readWoodsportalConfig(projectRoot)
|
|
|
25
21
|
const routesDir = resolve(projectRoot, 'src/routes')
|
|
26
22
|
const hasTanStackRoutes = existsSync(routesDir)
|
|
27
23
|
|
|
28
|
-
export default
|
|
29
|
-
|
|
24
|
+
export default loadDevViteConfig()
|
|
25
|
+
|
|
26
|
+
async function loadDevViteConfig() {
|
|
27
|
+
// Load vite before plugins: parallel import of vite + @vitejs/plugin-react breaks on Node 20+ (CJS/ESM interop).
|
|
28
|
+
const { defineConfig } = await importConsumerDep('vite')
|
|
29
|
+
const [{ default: viteReact }, { TanStackRouterVite }, { default: tailwindcss }] = await Promise.all([
|
|
30
|
+
importConsumerDep('@vitejs/plugin-react'),
|
|
31
|
+
importConsumerDep('@tanstack/router-plugin/vite'),
|
|
32
|
+
importConsumerDep('@tailwindcss/vite'),
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
return defineConfig(({ mode, command }) => ({
|
|
30
36
|
root: projectRoot,
|
|
31
37
|
plugins: [
|
|
32
38
|
bootstrapShimResolver({ bootstrapShim, bootstrapGreenfieldShim, kitRoot }),
|
|
@@ -72,5 +78,5 @@ export default defineConfig(({ mode, command }) => {
|
|
|
72
78
|
allow: [projectRoot, kitRoot],
|
|
73
79
|
},
|
|
74
80
|
},
|
|
75
|
-
}
|
|
76
|
-
}
|
|
81
|
+
}))
|
|
82
|
+
}
|