@duffcloudservices/cms-react 0.1.2 → 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/README.md +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ Hook for text content with build-time and runtime support.
|
|
|
92
92
|
- `page` (string, required): Page slug matching entry in content.yaml
|
|
93
93
|
- `defaults` (Record<string, string>): Default text values
|
|
94
94
|
- `runtime` (boolean): Enable runtime fetching (premium tier)
|
|
95
|
-
- `siteSlug` (string):
|
|
95
|
+
- `siteSlug` (string, deprecated): Retained for source compatibility but ignored for routing. The site is resolved server-side from the request Host or the dedicated Container App's `DCS_SITE_SLUG`; the slug is no longer placed in the request URL.
|
|
96
96
|
- `apiBaseUrl` (string): API base URL
|
|
97
97
|
|
|
98
98
|
**Returns:**
|
package/dist/index.d.ts
CHANGED
|
@@ -17,7 +17,13 @@ interface UseTextContentOptions {
|
|
|
17
17
|
defaults?: Record<string, string>;
|
|
18
18
|
/** Enable runtime content fetching (premium tier only) */
|
|
19
19
|
runtime?: boolean;
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Site slug for runtime fetching.
|
|
22
|
+
* @deprecated The site is now resolved server-side from the request Host or
|
|
23
|
+
* the dedicated Container App's `DCS_SITE_SLUG`. This option is retained for
|
|
24
|
+
* source compatibility but is ignored for routing (no longer placed in the
|
|
25
|
+
* URL path).
|
|
26
|
+
*/
|
|
21
27
|
siteSlug?: string;
|
|
22
28
|
/** API base URL for runtime fetching */
|
|
23
29
|
apiBaseUrl?: string;
|
package/dist/index.js
CHANGED
|
@@ -61,7 +61,7 @@ function useTextContent(input) {
|
|
|
61
61
|
[content]
|
|
62
62
|
);
|
|
63
63
|
const refresh = useCallback(async () => {
|
|
64
|
-
if (!runtime
|
|
64
|
+
if (!runtime) return;
|
|
65
65
|
setIsLoading(true);
|
|
66
66
|
setError(null);
|
|
67
67
|
try {
|
|
@@ -76,7 +76,7 @@ function useTextContent(input) {
|
|
|
76
76
|
}
|
|
77
77
|
}, [runtime, siteSlug, apiBaseUrl]);
|
|
78
78
|
useEffect(() => {
|
|
79
|
-
if (runtime
|
|
79
|
+
if (runtime) {
|
|
80
80
|
refresh();
|
|
81
81
|
}
|
|
82
82
|
}, [runtime, siteSlug]);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useTextContent.ts","../src/hooks/useSEO.ts"],"names":["normalizeOptions","useMemo","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * useTextContent Hook for React\n *\n * Provides text content management with build-time injection support and optional\n * runtime API overrides for DCS-managed customer sites.\n */\n\nimport { useState, useEffect, useCallback, useMemo } from 'react'\nimport {\n getPageContent,\n fetchRuntimeContent,\n type ContentConfiguration,\n} from '@duffcloudservices/cms-core/browser'\n\n// Declare the global injected by dcsContentPlugin\ndeclare const __DCS_CONTENT__: ContentConfiguration | undefined\ndeclare const __DCS_RUNTIME_MODE__: boolean | undefined\n\n/**\n * Configuration for useTextContent hook\n */\nexport interface UseTextContentOptions {\n /** Page slug matching entry in content.yaml */\n page: string\n /** Default text values for all keys used by this page */\n defaults?: Record<string, string>\n /** Enable runtime content fetching (premium tier only) */\n runtime?: boolean\n /** Site slug for runtime fetching */\n siteSlug?: string\n /** API base URL for runtime fetching */\n apiBaseUrl?: string\n}\n\n/**\n * Text translation function signature\n */\nexport type TextFunction = (key: string, fallback?: string) => string\n\n/**\n * Return type of useTextContent hook\n */\nexport interface UseTextContentReturn {\n /** Get text by key with optional fallback */\n t: TextFunction\n /** All resolved content for the page */\n content: Record<string, string>\n /** Loading state for runtime fetch */\n isLoading: boolean\n /** Error message if fetch failed */\n error: string | null\n /** Manually refresh content (runtime mode only) */\n refresh: () => Promise<void>\n}\n\n/**\n * Safely get build-time content configuration.\n */\nfunction getBuildTimeContent(): ContentConfiguration | undefined {\n try {\n if (__DCS_CONTENT__ !== undefined && __DCS_CONTENT__ !== null) {\n return __DCS_CONTENT__\n }\n } catch {\n // __DCS_CONTENT__ not defined - that's fine, use defaults\n }\n return undefined\n}\n\n/**\n * Check if runtime mode is enabled via build-time flag\n */\nfunction isRuntimeModeEnabled(): boolean {\n try {\n return __DCS_RUNTIME_MODE__ === true\n } catch {\n return false\n }\n}\n\n/**\n * Get environment variable value\n */\nfunction getEnvVar(key: string, defaultValue = ''): string {\n try {\n const value = import.meta.env?.[key as keyof ImportMetaEnv]\n if (value !== undefined) return String(value)\n } catch {\n // import.meta not available\n }\n return defaultValue\n}\n\n/**\n * Normalize input to options object\n */\nfunction normalizeOptions(input: string | UseTextContentOptions): UseTextContentOptions {\n if (typeof input === 'string') {\n return { page: input }\n }\n return input\n}\n\n/**\n * useTextContent hook for React components.\n *\n * @example\n * // Simple usage with page slug - returns t function directly\n * const t = useTextContent('home')\n * return <h1>{t('hero.title', 'Welcome')}</h1>\n *\n * @example\n * // Advanced usage with options - returns full object\n * const { t, isLoading } = useTextContent({\n * page: 'home',\n * runtime: true,\n * siteSlug: 'my-site'\n * })\n *\n * @param input - Page slug string or configuration options\n * @returns Text function (simple usage) or full return object (advanced usage)\n */\nexport function useTextContent(input: string): TextFunction\nexport function useTextContent(input: UseTextContentOptions): UseTextContentReturn\nexport function useTextContent(input: string | UseTextContentOptions): TextFunction | UseTextContentReturn {\n const options = normalizeOptions(input)\n const isSimpleUsage = typeof input === 'string'\n const {\n page,\n defaults = {},\n runtime = isRuntimeModeEnabled(),\n siteSlug = getEnvVar('VITE_SITE_SLUG', ''),\n apiBaseUrl = getEnvVar('VITE_API_BASE_URL', ''),\n } = options\n\n // Get build-time content\n const buildContent = getBuildTimeContent()\n const buildPageContent = useMemo(\n () => (buildContent ? getPageContent(buildContent, page) : {}),\n [buildContent, page]\n )\n\n // Runtime state\n const [runtimeContent, setRuntimeContent] = useState<ContentConfiguration | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n // Merged content: runtime > build-time > defaults\n const content = useMemo(() => {\n const runtimePage = runtimeContent ? getPageContent(runtimeContent, page) : {}\n return { ...defaults, ...buildPageContent, ...runtimePage }\n }, [defaults, buildPageContent, runtimeContent, page])\n\n // Text translation function\n const t = useCallback(\n (key: string, fallback?: string): string => {\n return content[key] ?? fallback ?? key\n },\n [content]\n )\n\n // Fetch runtime content\n const refresh = useCallback(async () => {\n if (!runtime || !siteSlug) return\n\n setIsLoading(true)\n setError(null)\n\n try {\n const data = await fetchRuntimeContent(siteSlug, { apiBaseUrl })\n if (data) {\n setRuntimeContent(data)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to fetch content')\n } finally {\n setIsLoading(false)\n }\n }, [runtime, siteSlug, apiBaseUrl])\n\n // Auto-fetch on mount if runtime mode\n useEffect(() => {\n if (runtime && siteSlug) {\n refresh()\n }\n }, [runtime, siteSlug]) // eslint-disable-line react-hooks/exhaustive-deps\n\n // Simple usage: return just the t function\n if (isSimpleUsage) {\n return t\n }\n\n // Advanced usage: return full object\n return { t, content, isLoading, error, refresh }\n}\n","/**\n * useSEO Hook for React\n *\n * Applies SEO meta tags to the document head based on .dcs/seo.yaml configuration.\n */\n\nimport { useEffect, useMemo } from 'react'\nimport {\n resolveSeoForPage,\n buildMetaTags,\n type SeoConfiguration,\n type ResolvedSeo,\n} from '@duffcloudservices/cms-core/browser'\n\n// Declare the global injected by dcsSeoPlugin\ndeclare const __DCS_SEO__: SeoConfiguration | undefined\n\n/**\n * Configuration for useSEO hook\n */\nexport interface UseSEOOptions {\n /** Page slug matching entry in seo.yaml */\n page: string\n /** Override resolved SEO values */\n overrides?: Partial<{\n title: string\n description: string\n image: string\n }>\n}\n\n/**\n * Return type of useSEO hook\n */\nexport interface UseSEOReturn {\n /** Resolved SEO configuration */\n seo: ResolvedSeo | null\n}\n\n/**\n * Safely get build-time SEO configuration.\n */\nfunction getBuildTimeSeo(): SeoConfiguration | undefined {\n try {\n if (__DCS_SEO__ !== undefined && __DCS_SEO__ !== null) {\n return __DCS_SEO__\n }\n } catch {\n // __DCS_SEO__ not defined\n }\n return undefined\n}\n\n/**\n * Update or create a meta tag in the document head\n */\nfunction updateMetaTag(\n attr: 'name' | 'property',\n key: string,\n content: string | undefined\n): void {\n if (!content) return\n\n let element = document.querySelector(`meta[${attr}=\"${key}\"]`)\n if (!element) {\n element = document.createElement('meta')\n element.setAttribute(attr, key)\n document.head.appendChild(element)\n }\n ;(element as HTMLMetaElement).content = content\n}\n\n/**\n * Normalize input to options object\n */\nfunction normalizeOptions(input: string | UseSEOOptions): UseSEOOptions {\n if (typeof input === 'string') {\n return { page: input }\n }\n return input\n}\n\n/**\n * useSEO hook for React components.\n * Automatically applies SEO meta tags to the document head.\n *\n * @example\n * // Simple usage with page slug\n * useSEO('home')\n *\n * @example\n * // Advanced usage with overrides\n * const { seo } = useSEO({\n * page: 'home',\n * overrides: { title: 'Custom Title' }\n * })\n *\n * @param input - Page slug string or configuration options\n * @returns Resolved SEO object\n */\nexport function useSEO(input: string): UseSEOReturn\nexport function useSEO(input: UseSEOOptions): UseSEOReturn\nexport function useSEO(input: string | UseSEOOptions): UseSEOReturn {\n const options = normalizeOptions(input)\n const { page, overrides } = options\n\n const seoConfig = getBuildTimeSeo()\n\n const resolvedSeo = useMemo(() => {\n if (!seoConfig) return null\n const base = resolveSeoForPage(seoConfig, page)\n return {\n ...base,\n ...(overrides?.title && { title: overrides.title }),\n ...(overrides?.description && { description: overrides.description }),\n ...(overrides?.image && { image: overrides.image }),\n }\n }, [seoConfig, page, overrides])\n\n // Apply to document head\n useEffect(() => {\n if (!resolvedSeo) return\n\n // Update document title\n document.title = resolvedSeo.title\n\n // Build and apply meta tags\n const tags = buildMetaTags(resolvedSeo)\n for (const tag of tags) {\n if (tag.property) {\n updateMetaTag('property', tag.property, tag.content)\n } else if (tag.name) {\n updateMetaTag('name', tag.name, tag.content)\n }\n }\n\n // Handle canonical link\n if (resolvedSeo.canonical) {\n let link = document.querySelector('link[rel=\"canonical\"]')\n if (!link) {\n link = document.createElement('link')\n link.setAttribute('rel', 'canonical')\n document.head.appendChild(link)\n }\n link.setAttribute('href', resolvedSeo.canonical)\n }\n }, [resolvedSeo])\n\n return { seo: resolvedSeo }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useTextContent.ts","../src/hooks/useSEO.ts"],"names":["normalizeOptions","useMemo","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * useTextContent Hook for React\n *\n * Provides text content management with build-time injection support and optional\n * runtime API overrides for DCS-managed customer sites.\n */\n\nimport { useState, useEffect, useCallback, useMemo } from 'react'\nimport {\n getPageContent,\n fetchRuntimeContent,\n type ContentConfiguration,\n} from '@duffcloudservices/cms-core/browser'\n\n// Declare the global injected by dcsContentPlugin\ndeclare const __DCS_CONTENT__: ContentConfiguration | undefined\ndeclare const __DCS_RUNTIME_MODE__: boolean | undefined\n\n/**\n * Configuration for useTextContent hook\n */\nexport interface UseTextContentOptions {\n /** Page slug matching entry in content.yaml */\n page: string\n /** Default text values for all keys used by this page */\n defaults?: Record<string, string>\n /** Enable runtime content fetching (premium tier only) */\n runtime?: boolean\n /**\n * Site slug for runtime fetching.\n * @deprecated The site is now resolved server-side from the request Host or\n * the dedicated Container App's `DCS_SITE_SLUG`. This option is retained for\n * source compatibility but is ignored for routing (no longer placed in the\n * URL path).\n */\n siteSlug?: string\n /** API base URL for runtime fetching */\n apiBaseUrl?: string\n}\n\n/**\n * Text translation function signature\n */\nexport type TextFunction = (key: string, fallback?: string) => string\n\n/**\n * Return type of useTextContent hook\n */\nexport interface UseTextContentReturn {\n /** Get text by key with optional fallback */\n t: TextFunction\n /** All resolved content for the page */\n content: Record<string, string>\n /** Loading state for runtime fetch */\n isLoading: boolean\n /** Error message if fetch failed */\n error: string | null\n /** Manually refresh content (runtime mode only) */\n refresh: () => Promise<void>\n}\n\n/**\n * Safely get build-time content configuration.\n */\nfunction getBuildTimeContent(): ContentConfiguration | undefined {\n try {\n if (__DCS_CONTENT__ !== undefined && __DCS_CONTENT__ !== null) {\n return __DCS_CONTENT__\n }\n } catch {\n // __DCS_CONTENT__ not defined - that's fine, use defaults\n }\n return undefined\n}\n\n/**\n * Check if runtime mode is enabled via build-time flag\n */\nfunction isRuntimeModeEnabled(): boolean {\n try {\n return __DCS_RUNTIME_MODE__ === true\n } catch {\n return false\n }\n}\n\n/**\n * Get environment variable value\n */\nfunction getEnvVar(key: string, defaultValue = ''): string {\n try {\n const value = import.meta.env?.[key as keyof ImportMetaEnv]\n if (value !== undefined) return String(value)\n } catch {\n // import.meta not available\n }\n return defaultValue\n}\n\n/**\n * Normalize input to options object\n */\nfunction normalizeOptions(input: string | UseTextContentOptions): UseTextContentOptions {\n if (typeof input === 'string') {\n return { page: input }\n }\n return input\n}\n\n/**\n * useTextContent hook for React components.\n *\n * @example\n * // Simple usage with page slug - returns t function directly\n * const t = useTextContent('home')\n * return <h1>{t('hero.title', 'Welcome')}</h1>\n *\n * @example\n * // Advanced usage with options - returns full object\n * const { t, isLoading } = useTextContent({\n * page: 'home',\n * runtime: true,\n * siteSlug: 'my-site'\n * })\n *\n * @param input - Page slug string or configuration options\n * @returns Text function (simple usage) or full return object (advanced usage)\n */\nexport function useTextContent(input: string): TextFunction\nexport function useTextContent(input: UseTextContentOptions): UseTextContentReturn\nexport function useTextContent(input: string | UseTextContentOptions): TextFunction | UseTextContentReturn {\n const options = normalizeOptions(input)\n const isSimpleUsage = typeof input === 'string'\n const {\n page,\n defaults = {},\n runtime = isRuntimeModeEnabled(),\n siteSlug = getEnvVar('VITE_SITE_SLUG', ''),\n apiBaseUrl = getEnvVar('VITE_API_BASE_URL', ''),\n } = options\n\n // Get build-time content\n const buildContent = getBuildTimeContent()\n const buildPageContent = useMemo(\n () => (buildContent ? getPageContent(buildContent, page) : {}),\n [buildContent, page]\n )\n\n // Runtime state\n const [runtimeContent, setRuntimeContent] = useState<ContentConfiguration | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n // Merged content: runtime > build-time > defaults\n const content = useMemo(() => {\n const runtimePage = runtimeContent ? getPageContent(runtimeContent, page) : {}\n return { ...defaults, ...buildPageContent, ...runtimePage }\n }, [defaults, buildPageContent, runtimeContent, page])\n\n // Text translation function\n const t = useCallback(\n (key: string, fallback?: string): string => {\n return content[key] ?? fallback ?? key\n },\n [content]\n )\n\n // Fetch runtime content. The site is resolved server-side (request Host /\n // DCS_SITE_SLUG), so `siteSlug` is no longer required to perform the fetch.\n const refresh = useCallback(async () => {\n if (!runtime) return\n\n setIsLoading(true)\n setError(null)\n\n try {\n const data = await fetchRuntimeContent(siteSlug, { apiBaseUrl })\n if (data) {\n setRuntimeContent(data)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to fetch content')\n } finally {\n setIsLoading(false)\n }\n }, [runtime, siteSlug, apiBaseUrl])\n\n // Auto-fetch on mount if runtime mode\n useEffect(() => {\n if (runtime) {\n refresh()\n }\n }, [runtime, siteSlug]) // eslint-disable-line react-hooks/exhaustive-deps\n\n // Simple usage: return just the t function\n if (isSimpleUsage) {\n return t\n }\n\n // Advanced usage: return full object\n return { t, content, isLoading, error, refresh }\n}\n","/**\n * useSEO Hook for React\n *\n * Applies SEO meta tags to the document head based on .dcs/seo.yaml configuration.\n */\n\nimport { useEffect, useMemo } from 'react'\nimport {\n resolveSeoForPage,\n buildMetaTags,\n type SeoConfiguration,\n type ResolvedSeo,\n} from '@duffcloudservices/cms-core/browser'\n\n// Declare the global injected by dcsSeoPlugin\ndeclare const __DCS_SEO__: SeoConfiguration | undefined\n\n/**\n * Configuration for useSEO hook\n */\nexport interface UseSEOOptions {\n /** Page slug matching entry in seo.yaml */\n page: string\n /** Override resolved SEO values */\n overrides?: Partial<{\n title: string\n description: string\n image: string\n }>\n}\n\n/**\n * Return type of useSEO hook\n */\nexport interface UseSEOReturn {\n /** Resolved SEO configuration */\n seo: ResolvedSeo | null\n}\n\n/**\n * Safely get build-time SEO configuration.\n */\nfunction getBuildTimeSeo(): SeoConfiguration | undefined {\n try {\n if (__DCS_SEO__ !== undefined && __DCS_SEO__ !== null) {\n return __DCS_SEO__\n }\n } catch {\n // __DCS_SEO__ not defined\n }\n return undefined\n}\n\n/**\n * Update or create a meta tag in the document head\n */\nfunction updateMetaTag(\n attr: 'name' | 'property',\n key: string,\n content: string | undefined\n): void {\n if (!content) return\n\n let element = document.querySelector(`meta[${attr}=\"${key}\"]`)\n if (!element) {\n element = document.createElement('meta')\n element.setAttribute(attr, key)\n document.head.appendChild(element)\n }\n ;(element as HTMLMetaElement).content = content\n}\n\n/**\n * Normalize input to options object\n */\nfunction normalizeOptions(input: string | UseSEOOptions): UseSEOOptions {\n if (typeof input === 'string') {\n return { page: input }\n }\n return input\n}\n\n/**\n * useSEO hook for React components.\n * Automatically applies SEO meta tags to the document head.\n *\n * @example\n * // Simple usage with page slug\n * useSEO('home')\n *\n * @example\n * // Advanced usage with overrides\n * const { seo } = useSEO({\n * page: 'home',\n * overrides: { title: 'Custom Title' }\n * })\n *\n * @param input - Page slug string or configuration options\n * @returns Resolved SEO object\n */\nexport function useSEO(input: string): UseSEOReturn\nexport function useSEO(input: UseSEOOptions): UseSEOReturn\nexport function useSEO(input: string | UseSEOOptions): UseSEOReturn {\n const options = normalizeOptions(input)\n const { page, overrides } = options\n\n const seoConfig = getBuildTimeSeo()\n\n const resolvedSeo = useMemo(() => {\n if (!seoConfig) return null\n const base = resolveSeoForPage(seoConfig, page)\n return {\n ...base,\n ...(overrides?.title && { title: overrides.title }),\n ...(overrides?.description && { description: overrides.description }),\n ...(overrides?.image && { image: overrides.image }),\n }\n }, [seoConfig, page, overrides])\n\n // Apply to document head\n useEffect(() => {\n if (!resolvedSeo) return\n\n // Update document title\n document.title = resolvedSeo.title\n\n // Build and apply meta tags\n const tags = buildMetaTags(resolvedSeo)\n for (const tag of tags) {\n if (tag.property) {\n updateMetaTag('property', tag.property, tag.content)\n } else if (tag.name) {\n updateMetaTag('name', tag.name, tag.content)\n }\n }\n\n // Handle canonical link\n if (resolvedSeo.canonical) {\n let link = document.querySelector('link[rel=\"canonical\"]')\n if (!link) {\n link = document.createElement('link')\n link.setAttribute('rel', 'canonical')\n document.head.appendChild(link)\n }\n link.setAttribute('href', resolvedSeo.canonical)\n }\n }, [resolvedSeo])\n\n return { seo: resolvedSeo }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duffcloudservices/cms-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "React hooks and Vite plugins for DCS CMS integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"react": "^18.0.0 || ^19.0.0"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@duffcloudservices/cms-core": "0.
|
|
25
|
+
"@duffcloudservices/cms-core": "0.5.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^20.11.0",
|