@duffcloudservices/cms 0.1.7 → 0.3.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/editor/editorBridge.d.ts +8 -1
- package/dist/editor/editorBridge.js +118 -22
- package/dist/editor/editorBridge.js.map +1 -1
- package/dist/index.d.ts +65 -2
- package/dist/index.js +30 -3
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +156 -1
- package/dist/plugins/index.js +295 -8
- package/dist/plugins/index.js.map +1 -1
- package/package.json +82 -77
- package/src/components/PreviewRibbon.vue +11 -0
- package/src/components/ResponsiveImage.vue +55 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as vue from 'vue';
|
|
2
|
-
import { ComputedRef } from 'vue';
|
|
2
|
+
import { ComputedRef, MaybeRefOrGetter } from 'vue';
|
|
3
|
+
import { ImageContext, ResponsiveImageResult } from '@duffcloudservices/cms-core';
|
|
4
|
+
export { ImageContext, ResponsiveImageOptions, ResponsiveImageResult, ResponsiveSource, isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Types for .dcs/content.yaml structure
|
|
@@ -582,6 +584,12 @@ interface MediaCarouselItem {
|
|
|
582
584
|
type: 'image' | 'video' | 'youtube' | 'instagram';
|
|
583
585
|
/** Accessibility alt text */
|
|
584
586
|
alt?: string;
|
|
587
|
+
/**
|
|
588
|
+
* Whether this image has responsive CDN variants available.
|
|
589
|
+
* Automatically set to `true` when the URL matches the DCS CDN asset pattern.
|
|
590
|
+
* Components rendering the carousel should use `<ResponsiveImage>` when this is `true`.
|
|
591
|
+
*/
|
|
592
|
+
responsive?: boolean;
|
|
585
593
|
}
|
|
586
594
|
/**
|
|
587
595
|
* Configuration for useMediaCarousel composable
|
|
@@ -622,4 +630,59 @@ interface UseMediaCarouselReturn {
|
|
|
622
630
|
*/
|
|
623
631
|
declare function useMediaCarousel(config: UseMediaCarouselConfig): UseMediaCarouselReturn;
|
|
624
632
|
|
|
625
|
-
|
|
633
|
+
/**
|
|
634
|
+
* Vue 3 composable that resolves responsive image variants for DCS CDN-hosted assets.
|
|
635
|
+
*
|
|
636
|
+
* Wraps the framework-agnostic `resolveResponsiveImage` from `@duffcloudservices/cms-core`
|
|
637
|
+
* with reactive Vue refs so it can be used directly in `<script setup>` blocks.
|
|
638
|
+
*
|
|
639
|
+
* @example
|
|
640
|
+
* ```vue
|
|
641
|
+
* <script setup lang="ts">
|
|
642
|
+
* import { useResponsiveImage } from '@duffcloudservices/cms'
|
|
643
|
+
*
|
|
644
|
+
* const hero = useResponsiveImage({
|
|
645
|
+
* src: 'https://files.duffcloudservices.com/kept/assets/hero/abc-123.jpg',
|
|
646
|
+
* alt: 'Hero image',
|
|
647
|
+
* context: 'hero',
|
|
648
|
+
* })
|
|
649
|
+
* </script>
|
|
650
|
+
*
|
|
651
|
+
* <template>
|
|
652
|
+
* <picture v-if="hero.hasVariants">
|
|
653
|
+
* <source
|
|
654
|
+
* v-for="source in hero.sources"
|
|
655
|
+
* :key="source.type"
|
|
656
|
+
* :srcset="source.srcset"
|
|
657
|
+
* :type="source.type"
|
|
658
|
+
* :sizes="source.sizes"
|
|
659
|
+
* />
|
|
660
|
+
* <img v-bind="hero.imgProps" class="w-full h-full object-cover" />
|
|
661
|
+
* </picture>
|
|
662
|
+
* <img v-else v-bind="hero.imgProps" class="w-full h-full object-cover" />
|
|
663
|
+
* </template>
|
|
664
|
+
* ```
|
|
665
|
+
*/
|
|
666
|
+
|
|
667
|
+
interface UseResponsiveImageOptions {
|
|
668
|
+
/** Source URL — can be a reactive ref, getter, or plain string. */
|
|
669
|
+
src: MaybeRefOrGetter<string>;
|
|
670
|
+
/** Alt text — can be a reactive ref, getter, or plain string. */
|
|
671
|
+
alt: MaybeRefOrGetter<string>;
|
|
672
|
+
/** Sizing context — determines which variants to include. */
|
|
673
|
+
context?: MaybeRefOrGetter<ImageContext | undefined>;
|
|
674
|
+
/** Optional `sizes` attribute override. */
|
|
675
|
+
sizes?: MaybeRefOrGetter<string | undefined>;
|
|
676
|
+
/** Skip variant resolution and use the original URL only. */
|
|
677
|
+
original?: MaybeRefOrGetter<boolean | undefined>;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Reactively resolves responsive image metadata for a DCS CDN URL.
|
|
681
|
+
*
|
|
682
|
+
* The returned object is a computed ref that recomputes whenever any
|
|
683
|
+
* of the input refs change. Spread `imgProps` onto an `<img>` or
|
|
684
|
+
* combine with `sources` inside a `<picture>` element.
|
|
685
|
+
*/
|
|
686
|
+
declare function useResponsiveImage(options: UseResponsiveImageOptions): ResponsiveImageResult;
|
|
687
|
+
|
|
688
|
+
export { type DcsContentFile, type GlobalSeoConfig, type HeadOverrides, type MediaCarouselItem, type PageSeoConfig, type ReleaseNote, type ReleaseNotesReturn, type ResolvedPageSeo, type SeoAlternateConfig, type SeoAuthorConfig, type SeoConfiguration, type SeoImagesConfig, type SeoOpenGraphConfig, type SeoSchemaConfig, type SeoSocialConfig, type SeoTwitterConfig, type SeoVerificationConfig, type SiteVersionReturn, type TextContentConfig, type TextContentReturn, type UseMediaCarouselConfig, type UseMediaCarouselReturn, type UseSeoConfig, type UseSeoReturn, createSiteSEO, useMediaCarousel, useReleaseNotes, useResponsiveImage, useSEO, useSiteVersion, useTextContent };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { ref, computed, onMounted, readonly } from 'vue';
|
|
1
|
+
import { ref, computed, onMounted, readonly, toValue } from 'vue';
|
|
2
2
|
import { useHead } from '@unhead/vue';
|
|
3
|
+
import { isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
|
|
4
|
+
export { isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
|
|
3
5
|
|
|
4
6
|
// src/composables/useTextContent.ts
|
|
5
7
|
var fetchCache = /* @__PURE__ */ new Map();
|
|
@@ -529,7 +531,10 @@ function useMediaCarousel(config) {
|
|
|
529
531
|
result.push({
|
|
530
532
|
url,
|
|
531
533
|
type,
|
|
532
|
-
alt: alt && alt !== altKey ? alt : void 0
|
|
534
|
+
alt: alt && alt !== altKey ? alt : void 0,
|
|
535
|
+
// Flag CDN-hosted images as responsive so carousel components
|
|
536
|
+
// can render them with <ResponsiveImage> automatically
|
|
537
|
+
responsive: type === "image" && isCdnAssetUrl(url)
|
|
533
538
|
});
|
|
534
539
|
}
|
|
535
540
|
if (result.length === 0) {
|
|
@@ -545,7 +550,29 @@ function useMediaCarousel(config) {
|
|
|
545
550
|
count
|
|
546
551
|
};
|
|
547
552
|
}
|
|
553
|
+
function useResponsiveImage(options) {
|
|
554
|
+
const result = computed(
|
|
555
|
+
() => resolveResponsiveImage({
|
|
556
|
+
src: toValue(options.src),
|
|
557
|
+
alt: toValue(options.alt),
|
|
558
|
+
context: toValue(options.context),
|
|
559
|
+
sizes: toValue(options.sizes),
|
|
560
|
+
original: toValue(options.original) ?? void 0
|
|
561
|
+
})
|
|
562
|
+
);
|
|
563
|
+
return {
|
|
564
|
+
get imgProps() {
|
|
565
|
+
return result.value.imgProps;
|
|
566
|
+
},
|
|
567
|
+
get sources() {
|
|
568
|
+
return result.value.sources;
|
|
569
|
+
},
|
|
570
|
+
get hasVariants() {
|
|
571
|
+
return result.value.hasVariants;
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
}
|
|
548
575
|
|
|
549
|
-
export { createSiteSEO, useMediaCarousel, useReleaseNotes, useSEO, useSiteVersion, useTextContent };
|
|
576
|
+
export { createSiteSEO, useMediaCarousel, useReleaseNotes, useResponsiveImage, useSEO, useSiteVersion, useTextContent };
|
|
550
577
|
//# sourceMappingURL=index.js.map
|
|
551
578
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/composables/useTextContent.ts","../src/composables/useSEO.ts","../src/composables/useReleaseNotes.ts","../src/composables/useSiteVersion.ts","../src/composables/useMediaCarousel.ts"],"names":["computed","getEnvVar","ref","onMounted","CACHE_TTL"],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * useTextContent Composable\r\n *\r\n * Provides text content management with build-time injection support and optional\r\n * runtime API overrides for DCS-managed customer sites.\r\n *\r\n * Content resolution order:\r\n * 1. Runtime API overrides (premium tier only, if mode is 'runtime')\r\n * 2. Build-time content from .dcs/content.yaml (injected via dcsContentPlugin)\r\n * 3. Hardcoded defaults passed to the composable\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup lang=\"ts\">\r\n * import { useTextContent } from '@duffcloudservices/cms'\r\n *\r\n * const { t, isLoading, error } = useTextContent({\r\n * pageSlug: 'home',\r\n * defaults: {\r\n * 'hero.title': 'Welcome to Our Site',\r\n * 'hero.subtitle': 'Build something amazing',\r\n * 'cta.primary': 'Get Started'\r\n * }\r\n * })\r\n * </script>\r\n *\r\n * <template>\r\n * <h1>{{ t('hero.title') }}</h1>\r\n * <p>{{ t('hero.subtitle') }}</p>\r\n * <button>{{ t('cta.primary') }}</button>\r\n * </template>\r\n * ```\r\n */\r\n\r\nimport { ref, computed, readonly, onMounted, type Ref } from 'vue'\r\nimport type { DcsContentFile, TextContentConfig, TextContentReturn } from '../types/content'\r\n\r\n// Declare the global injected by dcsContentPlugin\r\ndeclare const __DCS_CONTENT__: DcsContentFile | undefined\r\n\r\n// Simple in-memory cache for runtime fetches\r\nconst fetchCache = new Map<string, { data: Record<string, string>; expiresAt: number }>()\r\n\r\n/**\r\n * Safely get build-time content configuration.\r\n * Returns undefined if not available (no content.yaml or plugin not configured).\r\n */\r\nfunction getBuildTimeContent(): DcsContentFile | undefined {\r\n try {\r\n if (typeof __DCS_CONTENT__ !== 'undefined' && __DCS_CONTENT__ !== null) {\r\n return __DCS_CONTENT__\r\n }\r\n } catch {\r\n // __DCS_CONTENT__ not defined - that's fine, use defaults\r\n }\r\n return undefined\r\n}\r\n\r\n/**\r\n * Get build-time content for a specific page, merging global and page-specific content.\r\n */\r\nfunction getBuildTimePageContent(pageSlug: string): Record<string, string> {\r\n const content = getBuildTimeContent()\r\n if (!content) return {}\r\n\r\n const global = content.global ?? {}\r\n const page = content.pages?.[pageSlug] ?? {}\r\n\r\n return { ...global, ...page }\r\n}\r\n\r\n/**\r\n * Get environment variable value, handling both Vite and process.env patterns.\r\n */\r\nfunction getEnvVar(key: string, defaultValue = ''): string {\r\n try {\r\n // Vite pattern\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n const value = (import.meta.env as Record<string, string | undefined>)[key]\r\n if (value !== undefined) return value\r\n }\r\n } catch {\r\n // import.meta not available\r\n }\r\n\r\n try {\r\n // Node.js pattern\r\n if (typeof process !== 'undefined' && process.env) {\r\n const value = process.env[key]\r\n if (value !== undefined) return value\r\n }\r\n } catch {\r\n // process not available\r\n }\r\n\r\n return defaultValue\r\n}\r\n\r\n/**\r\n * useTextContent composable for DCS-managed text content.\r\n *\r\n * @param config - Configuration object\r\n * @returns Text content helpers and state\r\n */\r\nexport function useTextContent(config: TextContentConfig): TextContentReturn {\r\n const {\r\n pageSlug,\r\n defaults,\r\n fetchOnMount = true,\r\n cacheKey,\r\n cacheTtl = 60000,\r\n } = config\r\n\r\n // Get configuration from environment\r\n const apiBaseUrl = getEnvVar('VITE_API_BASE_URL', '')\r\n const siteSlug = getEnvVar('VITE_SITE_SLUG', '')\r\n const textOverrideMode = getEnvVar('VITE_TEXT_OVERRIDE_MODE', 'commit')\r\n const mode: 'commit' | 'runtime' = textOverrideMode === 'runtime' ? 'runtime' : 'commit'\r\n\r\n // Get build-time content immediately (synchronous)\r\n const buildTimeContent = getBuildTimePageContent(pageSlug)\r\n const hasBuildTimeContent = Object.keys(buildTimeContent).length > 0\r\n\r\n // State - initialize with build-time content\r\n const overrides = ref<Record<string, string>>({ ...buildTimeContent })\r\n const isLoading = ref(false)\r\n const error = ref<string | null>(null)\r\n\r\n // Computed merged texts (defaults + overrides)\r\n const texts = computed(() => ({ ...defaults, ...overrides.value }))\r\n\r\n /**\r\n * Get text by key with optional fallback.\r\n * Resolution order: overrides → defaults → fallback → key\r\n */\r\n function t(key: string, fallback?: string): string {\r\n return overrides.value[key] ?? defaults[key] ?? fallback ?? key\r\n }\r\n\r\n /**\r\n * Check if a key has an override (from build-time or runtime).\r\n */\r\n function hasOverride(key: string): boolean {\r\n return key in overrides.value\r\n }\r\n\r\n /**\r\n * Get an array of objects from indexed content keys.\r\n * Useful for lists where keys follow the pattern: arrayKey.index.property\r\n * Example: features.1.title, features.1.description, features.2.title, etc.\r\n *\r\n * @param arrayKey - The base key prefix (e.g., 'features', 'items')\r\n * @returns Array of objects with properties extracted from matching keys, sorted by index\r\n *\r\n * @example\r\n * ```ts\r\n * // Given content keys:\r\n * // positions.1.title = \"Software Engineer\"\r\n * // positions.1.description = \"Build cool stuff\"\r\n * // positions.2.title = \"Designer\"\r\n * // positions.2.description = \"Design cool stuff\"\r\n *\r\n * const positions = getArray('positions')\r\n * // Returns:\r\n * // [\r\n * // { _index: 1, title: \"Software Engineer\", description: \"Build cool stuff\" },\r\n * // { _index: 2, title: \"Designer\", description: \"Design cool stuff\" }\r\n * // ]\r\n * ```\r\n */\r\n function getArray(arrayKey: string): Array<Record<string, unknown> & { _index: number }> {\r\n const items: Record<number, Record<string, unknown> & { _index: number }> = {}\r\n const source = { ...defaults, ...overrides.value }\r\n\r\n Object.keys(source).forEach((key) => {\r\n if (key.startsWith(`${arrayKey}.`)) {\r\n const parts = key.split('.')\r\n // Format: arrayKey.index.property (or arrayKey.index.nested.property)\r\n // Example: features.1.title -> index=1, prop=title\r\n if (parts.length >= 3) {\r\n const index = Number.parseInt(parts[1], 10)\r\n const prop = parts.slice(2).join('.')\r\n\r\n if (!Number.isNaN(index)) {\r\n if (!items[index]) items[index] = { _index: index }\r\n items[index][prop] = source[key]\r\n }\r\n }\r\n }\r\n })\r\n\r\n return Object.values(items).sort((a, b) => a._index - b._index)\r\n }\r\n\r\n /**\r\n * Fetch runtime overrides from the API.\r\n * Only runs if mode is 'runtime' and API is configured.\r\n */\r\n async function fetchOverrides(): Promise<void> {\r\n // Skip API fetch in commit mode - use build-time content only\r\n if (mode !== 'runtime') {\r\n return\r\n }\r\n\r\n // Skip if API not configured\r\n if (!apiBaseUrl || !siteSlug) {\r\n console.warn(\r\n '[@duffcloudservices/cms] Runtime mode enabled but VITE_API_BASE_URL or VITE_SITE_SLUG not set'\r\n )\r\n return\r\n }\r\n\r\n // Check cache first\r\n const effectiveCacheKey = cacheKey ?? `${siteSlug}:${pageSlug}`\r\n const cached = fetchCache.get(effectiveCacheKey)\r\n if (cached && cached.expiresAt > Date.now()) {\r\n overrides.value = { ...buildTimeContent, ...cached.data }\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n error.value = null\r\n\r\n try {\r\n const url = `${apiBaseUrl}/api/v1/sites/${siteSlug}/pages/${pageSlug}/text`\r\n const response = await fetch(url, {\r\n headers: {\r\n Accept: 'application/json',\r\n },\r\n })\r\n\r\n if (!response.ok) {\r\n if (response.status === 404) {\r\n // No overrides for this page - that's fine\r\n return\r\n }\r\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\r\n }\r\n\r\n const data = await response.json()\r\n const apiOverrides = data.overrides ?? data.texts ?? {}\r\n\r\n // Cache the result\r\n fetchCache.set(effectiveCacheKey, {\r\n data: apiOverrides,\r\n expiresAt: Date.now() + cacheTtl,\r\n })\r\n\r\n // Merge: build-time content is baseline, API overrides on top\r\n overrides.value = { ...buildTimeContent, ...apiOverrides }\r\n } catch (e) {\r\n error.value = e instanceof Error ? e.message : 'Failed to load text overrides'\r\n console.error('[@duffcloudservices/cms] Failed to fetch text overrides:', e)\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n /**\r\n * Manually refresh overrides.\r\n * In commit mode, resets to build-time content.\r\n * In runtime mode, fetches fresh data from API.\r\n */\r\n async function refresh(): Promise<void> {\r\n if (mode !== 'runtime') {\r\n // In commit mode, just reset to build-time content\r\n overrides.value = { ...buildTimeContent }\r\n return\r\n }\r\n\r\n // Clear cache for this key\r\n const effectiveCacheKey = cacheKey ?? `${siteSlug}:${pageSlug}`\r\n fetchCache.delete(effectiveCacheKey)\r\n\r\n await fetchOverrides()\r\n }\r\n\r\n // Fetch on mount if enabled and in runtime mode (browser only)\r\n if (fetchOnMount && mode === 'runtime' && globalThis.window !== undefined) {\r\n onMounted(() => {\r\n fetchOverrides()\r\n })\r\n }\r\n\r\n return {\r\n t,\r\n getArray,\r\n texts,\r\n overrides,\r\n isLoading: readonly(isLoading) as Ref<boolean>,\r\n error: readonly(error) as Ref<string | null>,\r\n refresh,\r\n hasOverride,\r\n hasBuildTimeContent,\r\n mode,\r\n }\r\n}\r\n","/**\r\n * useSEO Composable\r\n *\r\n * Provides SEO configuration with build-time injection support from .dcs/seo.yaml.\r\n * Generates meta tags, Open Graph, Twitter Cards, and JSON-LD structured data.\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup lang=\"ts\">\r\n * import { useSEO } from '@duffcloudservices/cms'\r\n *\r\n * const { applyHead, getSchema, config } = useSEO('home')\r\n *\r\n * // Apply all meta tags\r\n * applyHead()\r\n *\r\n * // Or customize before applying\r\n * applyHead({\r\n * title: 'Custom Override Title',\r\n * schemas: [...getSchema(), customSchema]\r\n * })\r\n * </script>\r\n * ```\r\n */\r\n\r\nimport { computed, type ComputedRef } from 'vue'\r\nimport { useHead } from '@unhead/vue'\r\nimport type {\r\n SeoConfiguration,\r\n GlobalSeoConfig,\r\n SeoOpenGraphConfig,\r\n SeoTwitterConfig,\r\n SeoSchemaConfig,\r\n ResolvedPageSeo,\r\n UseSeoReturn,\r\n HeadOverrides,\r\n} from '../types/seo'\r\n\r\n// Declare the global injected by dcsSeoPlugin\r\ndeclare const __DCS_SEO__: SeoConfiguration | undefined\r\n\r\n/**\r\n * Safely get build-time SEO configuration.\r\n * Returns undefined if not available (no seo.yaml or plugin not configured).\r\n */\r\nfunction getBuildTimeSeo(): SeoConfiguration | undefined {\r\n try {\r\n if (typeof __DCS_SEO__ !== 'undefined' && __DCS_SEO__ !== null) {\r\n return __DCS_SEO__\r\n }\r\n } catch {\r\n // __DCS_SEO__ not defined - that's fine, use defaults\r\n }\r\n return undefined\r\n}\r\n\r\n// =============================================================================\r\n// Meta Tag Generation Utilities\r\n// =============================================================================\r\n\r\ninterface HeadInput {\r\n title?: string\r\n titleTemplate?: string | ((title: string) => string)\r\n meta?: Array<{ name?: string; property?: string; content: string }>\r\n link?: Array<{ rel: string; href: string; hreflang?: string }>\r\n script?: Array<{ type: string; children: string }>\r\n}\r\n\r\n/**\r\n * Generate Open Graph meta tags from config\r\n */\r\nfunction generateOpenGraphMeta(\r\n og: SeoOpenGraphConfig,\r\n global: GlobalSeoConfig,\r\n pageTitle: string,\r\n pageDescription: string,\r\n canonical: string\r\n): Array<{ property: string; content: string }> {\r\n const tags: Array<{ property: string; content: string }> = []\r\n\r\n tags.push({ property: 'og:title', content: og.title || pageTitle })\r\n tags.push({ property: 'og:description', content: og.description || pageDescription })\r\n tags.push({ property: 'og:url', content: og.url || canonical })\r\n tags.push({ property: 'og:type', content: og.type || 'website' })\r\n\r\n const image = og.image || global.images?.ogDefault\r\n if (image) {\r\n tags.push({ property: 'og:image', content: image })\r\n if (og.imageAlt || pageTitle) {\r\n tags.push({ property: 'og:image:alt', content: og.imageAlt || pageTitle })\r\n }\r\n if (og.imageWidth) {\r\n tags.push({ property: 'og:image:width', content: String(og.imageWidth) })\r\n }\r\n if (og.imageHeight) {\r\n tags.push({ property: 'og:image:height', content: String(og.imageHeight) })\r\n }\r\n }\r\n\r\n if (global.siteName) {\r\n tags.push({ property: 'og:site_name', content: global.siteName })\r\n }\r\n\r\n if (global.locale) {\r\n tags.push({ property: 'og:locale', content: global.locale })\r\n }\r\n\r\n // Article-specific tags\r\n if (og.type === 'article') {\r\n if (og.publishedTime) {\r\n tags.push({ property: 'article:published_time', content: og.publishedTime })\r\n }\r\n if (og.modifiedTime) {\r\n tags.push({ property: 'article:modified_time', content: og.modifiedTime })\r\n }\r\n if (og.author) {\r\n tags.push({ property: 'article:author', content: og.author })\r\n }\r\n if (og.section) {\r\n tags.push({ property: 'article:section', content: og.section })\r\n }\r\n if (og.tags) {\r\n og.tags.forEach((tag) => {\r\n tags.push({ property: 'article:tag', content: tag })\r\n })\r\n }\r\n }\r\n\r\n return tags\r\n}\r\n\r\n/**\r\n * Generate Twitter Card meta tags from config\r\n */\r\nfunction generateTwitterMeta(\r\n twitter: SeoTwitterConfig,\r\n global: GlobalSeoConfig,\r\n pageTitle: string,\r\n pageDescription: string\r\n): Array<{ name: string; content: string }> {\r\n const tags: Array<{ name: string; content: string }> = []\r\n\r\n tags.push({ name: 'twitter:card', content: twitter.card || 'summary_large_image' })\r\n tags.push({ name: 'twitter:title', content: twitter.title || pageTitle })\r\n tags.push({ name: 'twitter:description', content: twitter.description || pageDescription })\r\n\r\n const image = twitter.image || global.images?.twitterDefault\r\n if (image) {\r\n tags.push({ name: 'twitter:image', content: image })\r\n if (twitter.imageAlt || pageTitle) {\r\n tags.push({ name: 'twitter:image:alt', content: twitter.imageAlt || pageTitle })\r\n }\r\n }\r\n\r\n const site = twitter.site || global.social?.twitter\r\n if (site) {\r\n tags.push({ name: 'twitter:site', content: site.startsWith('@') ? site : `@${site}` })\r\n }\r\n\r\n if (twitter.creator) {\r\n tags.push({\r\n name: 'twitter:creator',\r\n content: twitter.creator.startsWith('@') ? twitter.creator : `@${twitter.creator}`,\r\n })\r\n }\r\n\r\n return tags\r\n}\r\n\r\n/**\r\n * Generate JSON-LD script content from schemas\r\n */\r\nfunction generateJsonLd(schemas: SeoSchemaConfig[], global: GlobalSeoConfig): object[] {\r\n return schemas.map((schema) => {\r\n const base: Record<string, unknown> = {\r\n '@context': 'https://schema.org',\r\n '@type': schema.type,\r\n }\r\n\r\n // Merge properties\r\n if (schema.properties) {\r\n Object.assign(base, schema.properties)\r\n }\r\n\r\n // Auto-populate common properties from global config\r\n if (schema.type === 'WebSite' && global.siteUrl && !base.url) {\r\n base.url = global.siteUrl\r\n }\r\n if (schema.type === 'WebSite' && global.siteName && !base.name) {\r\n base.name = global.siteName\r\n }\r\n\r\n return base\r\n })\r\n}\r\n\r\n/**\r\n * Resolve page SEO by merging global defaults with page-specific config\r\n */\r\nfunction resolvePageSeo(\r\n pageSlug: string,\r\n pagePath: string | undefined,\r\n seoConfig: SeoConfiguration | undefined\r\n): ResolvedPageSeo {\r\n const global = seoConfig?.global ?? {}\r\n const page = seoConfig?.pages?.[pageSlug] ?? {}\r\n\r\n // Build canonical URL\r\n let canonical = page.canonical || ''\r\n if (!canonical && global.siteUrl) {\r\n const path = pagePath ?? (pageSlug === 'home' ? '/' : `/${pageSlug}`)\r\n canonical = `${global.siteUrl.replace(/\\/$/, '')}${path}`\r\n }\r\n\r\n // Build title\r\n let title = page.title || global.defaultTitle || pageSlug\r\n if (!page.noTitleTemplate && global.titleTemplate) {\r\n title = global.titleTemplate.replace('%s', title)\r\n }\r\n\r\n // Merge Open Graph\r\n const openGraph: ResolvedPageSeo['openGraph'] = {\r\n type: page.openGraph?.type || 'website',\r\n title: page.openGraph?.title || page.title || global.defaultTitle || '',\r\n description: page.openGraph?.description || page.description || global.defaultDescription || '',\r\n ...page.openGraph,\r\n }\r\n\r\n // Merge Twitter\r\n const twitter: ResolvedPageSeo['twitter'] = {\r\n card: page.twitter?.card || 'summary_large_image',\r\n ...page.twitter,\r\n }\r\n\r\n // Combine schemas (global + page)\r\n const schemas = [...(global.schemas ?? []), ...(page.schemas ?? [])]\r\n\r\n return {\r\n title,\r\n description: page.description || global.defaultDescription || '',\r\n canonical,\r\n robots: page.robots || global.robots || 'index, follow',\r\n openGraph,\r\n twitter,\r\n schemas,\r\n alternates: page.alternates ?? [],\r\n }\r\n}\r\n\r\n/**\r\n * useSEO composable for DCS-managed SEO configuration.\r\n *\r\n * @param pageSlug - Page slug matching entry in seo.yaml\r\n * @param pagePath - Optional page path for canonical URL generation\r\n * @returns SEO helpers and state\r\n */\r\nexport function useSEO(pageSlug: string, pagePath?: string): UseSeoReturn {\r\n const seoConfig = getBuildTimeSeo()\r\n const hasBuildTimeSeo = seoConfig !== undefined\r\n\r\n // Computed resolved config\r\n const config: ComputedRef<ResolvedPageSeo> = computed(() =>\r\n resolvePageSeo(pageSlug, pagePath, seoConfig)\r\n )\r\n\r\n /**\r\n * Get JSON-LD schema objects for the page\r\n */\r\n function getSchema(): object[] {\r\n return generateJsonLd(config.value.schemas, seoConfig?.global ?? {})\r\n }\r\n\r\n /**\r\n * Get canonical URL for the page\r\n */\r\n function getCanonical(): string {\r\n return config.value.canonical\r\n }\r\n\r\n /**\r\n * Apply all meta tags via useHead\r\n */\r\n function applyHead(overrides?: HeadOverrides): void {\r\n const resolved = config.value\r\n const global = seoConfig?.global ?? {}\r\n\r\n const title = overrides?.title ?? resolved.title\r\n const description = overrides?.description ?? resolved.description\r\n\r\n // Build meta tags\r\n const meta: HeadInput['meta'] = []\r\n\r\n // Basic meta\r\n meta.push({ name: 'description', content: description })\r\n if (resolved.robots) {\r\n meta.push({ name: 'robots', content: resolved.robots })\r\n }\r\n\r\n // Verification codes\r\n if (global.verification?.google) {\r\n meta.push({ name: 'google-site-verification', content: global.verification.google })\r\n }\r\n if (global.verification?.bing) {\r\n meta.push({ name: 'msvalidate.01', content: global.verification.bing })\r\n }\r\n\r\n // Open Graph\r\n const ogMeta = generateOpenGraphMeta(\r\n resolved.openGraph,\r\n global,\r\n title,\r\n description,\r\n resolved.canonical\r\n )\r\n meta.push(...ogMeta.map((t) => ({ property: t.property, content: t.content })))\r\n\r\n // Twitter\r\n const twitterMeta = generateTwitterMeta(resolved.twitter, global, title, description)\r\n meta.push(...twitterMeta.map((t) => ({ name: t.name, content: t.content })))\r\n\r\n // Additional overrides\r\n if (overrides?.meta) {\r\n meta.push(...overrides.meta)\r\n }\r\n\r\n // Build links\r\n const link: HeadInput['link'] = []\r\n\r\n // Canonical\r\n if (resolved.canonical) {\r\n link.push({ rel: 'canonical', href: resolved.canonical })\r\n }\r\n\r\n // Alternate languages\r\n resolved.alternates.forEach((alt) => {\r\n link.push({ rel: 'alternate', href: alt.href, hreflang: alt.hreflang })\r\n })\r\n\r\n // Build scripts (JSON-LD)\r\n const schemas = overrides?.schemas ?? getSchema()\r\n const script: HeadInput['script'] = schemas.map((schema) => ({\r\n type: 'application/ld+json',\r\n children: JSON.stringify(schema),\r\n }))\r\n\r\n // Apply via useHead\r\n useHead({\r\n title,\r\n meta,\r\n link,\r\n script,\r\n })\r\n }\r\n\r\n return {\r\n config,\r\n applyHead,\r\n getSchema,\r\n getCanonical,\r\n hasBuildTimeSeo,\r\n }\r\n}\r\n\r\n/**\r\n * Create a typed useSEO function with site-specific defaults.\r\n * Useful for creating a site-wide wrapper.\r\n *\r\n * @example\r\n * ```ts\r\n * // composables/useSiteSeo.ts\r\n * import { createSiteSEO } from '@duffcloudservices/cms'\r\n *\r\n * export const useSiteSeo = createSiteSEO({\r\n * siteName: 'My Site',\r\n * siteUrl: 'https://example.com'\r\n * })\r\n * ```\r\n */\r\nexport function createSiteSEO(\r\n _siteDefaults: Partial<GlobalSeoConfig>\r\n): (pageSlug: string, pagePath?: string) => UseSeoReturn {\r\n return function siteUseSEO(pageSlug: string, pagePath?: string): UseSeoReturn {\r\n // Note: siteDefaults would be used if we needed to override at runtime\r\n // but build-time injection handles this via dcsSeoPlugin\r\n return useSEO(pageSlug, pagePath)\r\n }\r\n}\r\n","/**\r\n * useReleaseNotes Composable\r\n *\r\n * Fetches and displays versioned release notes from the DCS Portal API.\r\n * Supports fetching specific versions or the latest release.\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup lang=\"ts\">\r\n * import { useReleaseNotes } from '@duffcloudservices/cms'\r\n * import { useRoute } from 'vue-router'\r\n *\r\n * const route = useRoute()\r\n * const version = route.params.version as string || 'latest'\r\n *\r\n * const { releaseNote, isLoading, error } = useReleaseNotes(version)\r\n * </script>\r\n *\r\n * <template>\r\n * <div v-if=\"isLoading\">Loading...</div>\r\n * <div v-else-if=\"error\">{{ error }}</div>\r\n * <article v-else-if=\"releaseNote\">\r\n * <h1>{{ releaseNote.title }}</h1>\r\n * <p>{{ releaseNote.summary }}</p>\r\n * <div v-html=\"renderedMarkdown\" />\r\n * </article>\r\n * </template>\r\n * ```\r\n */\r\n\r\nimport { ref, onMounted } from 'vue'\r\nimport type { ReleaseNote, ReleaseNotesReturn } from '../types/release-notes'\r\n\r\n/**\r\n * Get environment variable value.\r\n */\r\nfunction getEnvVar(key: string, defaultValue = ''): string {\r\n try {\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n const value = (import.meta.env as Record<string, string | undefined>)[key]\r\n if (value !== undefined) return value\r\n }\r\n } catch {\r\n // import.meta not available\r\n }\r\n return defaultValue\r\n}\r\n\r\n// Simple cache for release notes\r\nconst releaseNotesCache = new Map<string, { data: ReleaseNote; expiresAt: number }>()\r\nconst CACHE_TTL = 5 * 60 * 1000 // 5 minutes\r\n\r\n/**\r\n * useReleaseNotes composable for fetching release notes from the DCS API.\r\n *\r\n * @param version - Semantic version (e.g., \"1.2.0\") or \"latest\"\r\n * @param options - Optional configuration\r\n * @returns Release notes data and state\r\n */\r\nexport function useReleaseNotes(\r\n version: string,\r\n options: { fetchOnMount?: boolean } = {}\r\n): ReleaseNotesReturn {\r\n const { fetchOnMount = true } = options\r\n\r\n const apiBaseUrl = getEnvVar('VITE_API_BASE_URL', 'https://portal.duffcloudservices.com')\r\n const siteSlug = getEnvVar('VITE_SITE_SLUG', '')\r\n\r\n const releaseNote = ref<ReleaseNote | null>(null)\r\n const isLoading = ref(false)\r\n const error = ref<string | null>(null)\r\n\r\n async function fetchReleaseNotes(): Promise<void> {\r\n if (!siteSlug) {\r\n error.value = 'VITE_SITE_SLUG environment variable not set'\r\n return\r\n }\r\n\r\n // Check cache\r\n const cacheKey = `${siteSlug}:${version}`\r\n const cached = releaseNotesCache.get(cacheKey)\r\n if (cached && cached.expiresAt > Date.now()) {\r\n releaseNote.value = cached.data\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n error.value = null\r\n\r\n try {\r\n const url = `${apiBaseUrl}/api/v1/sites/${siteSlug}/release-notes/${version}`\r\n const response = await fetch(url, {\r\n headers: {\r\n Accept: 'application/json',\r\n },\r\n })\r\n\r\n if (!response.ok) {\r\n if (response.status === 404) {\r\n error.value = `Release notes for version ${version} not found`\r\n return\r\n }\r\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\r\n }\r\n\r\n const data = await response.json()\r\n\r\n // Normalize the response\r\n const note: ReleaseNote = {\r\n version: data.version,\r\n title: data.title,\r\n summary: data.summary || '',\r\n notesMarkdown: data.notesMarkdown || data.notes || '',\r\n changeCount: data.changeCount || 0,\r\n releaseDate: data.releaseDate || data.releasedAt || '',\r\n }\r\n\r\n // Cache the result\r\n releaseNotesCache.set(cacheKey, {\r\n data: note,\r\n expiresAt: Date.now() + CACHE_TTL,\r\n })\r\n\r\n releaseNote.value = note\r\n } catch (e) {\r\n error.value = e instanceof Error ? e.message : 'Failed to load release notes'\r\n console.error('[@duffcloudservices/cms] Failed to fetch release notes:', e)\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n async function refresh(): Promise<void> {\r\n // Clear cache\r\n const cacheKey = `${siteSlug}:${version}`\r\n releaseNotesCache.delete(cacheKey)\r\n await fetchReleaseNotes()\r\n }\r\n\r\n // Fetch on mount if enabled\r\n if (fetchOnMount && typeof window !== 'undefined') {\r\n onMounted(() => {\r\n fetchReleaseNotes()\r\n })\r\n }\r\n\r\n return {\r\n releaseNote,\r\n isLoading,\r\n error,\r\n refresh,\r\n }\r\n}\r\n","/**\r\n * useSiteVersion Composable\r\n *\r\n * Gets the current site version for footer badges and version displays.\r\n * Fetches the latest release version from the DCS Portal API.\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup lang=\"ts\">\r\n * import { useSiteVersion } from '@duffcloudservices/cms'\r\n *\r\n * const { version, releaseNotesUrl } = useSiteVersion()\r\n * </script>\r\n *\r\n * <template>\r\n * <footer>\r\n * <a v-if=\"version\" :href=\"releaseNotesUrl\" class=\"version-badge\">\r\n * v{{ version }}\r\n * </a>\r\n * </footer>\r\n * </template>\r\n * ```\r\n */\r\n\r\nimport { ref, computed, onMounted } from 'vue'\r\nimport type { SiteVersionReturn } from '../types/release-notes'\r\n\r\n/**\r\n * Get environment variable value.\r\n */\r\nfunction getEnvVar(key: string, defaultValue = ''): string {\r\n try {\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n const value = (import.meta.env as Record<string, string | undefined>)[key]\r\n if (value !== undefined) return value\r\n }\r\n } catch {\r\n // import.meta not available\r\n }\r\n return defaultValue\r\n}\r\n\r\n// Cache for site version\r\nlet versionCache: { version: string; expiresAt: number } | null = null\r\nconst CACHE_TTL = 10 * 60 * 1000 // 10 minutes\r\n\r\n/**\r\n * useSiteVersion composable for displaying the current site version.\r\n *\r\n * @param options - Optional configuration\r\n * @returns Site version data and computed URL\r\n */\r\nexport function useSiteVersion(options: { fetchOnMount?: boolean } = {}): SiteVersionReturn {\r\n const { fetchOnMount = true } = options\r\n\r\n const apiBaseUrl = getEnvVar('VITE_API_BASE_URL', 'https://portal.duffcloudservices.com')\r\n const siteSlug = getEnvVar('VITE_SITE_SLUG', '')\r\n\r\n const version = ref<string | null>(null)\r\n const isLoading = ref(false)\r\n\r\n const releaseNotesUrl = computed(() => {\r\n if (!version.value) return '/releaseNotes/latest'\r\n return `/releaseNotes/${version.value}`\r\n })\r\n\r\n async function fetchVersion(): Promise<void> {\r\n if (!siteSlug) {\r\n return\r\n }\r\n\r\n // Check cache\r\n if (versionCache && versionCache.expiresAt > Date.now()) {\r\n version.value = versionCache.version\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n\r\n try {\r\n // Fetch the latest release notes to get the version\r\n const url = `${apiBaseUrl}/api/v1/sites/${siteSlug}/release-notes/latest`\r\n const response = await fetch(url, {\r\n headers: {\r\n Accept: 'application/json',\r\n },\r\n })\r\n\r\n if (!response.ok) {\r\n // No release notes yet - that's fine\r\n return\r\n }\r\n\r\n const data = await response.json()\r\n const latestVersion = data.version\r\n\r\n // Cache the result\r\n versionCache = {\r\n version: latestVersion,\r\n expiresAt: Date.now() + CACHE_TTL,\r\n }\r\n\r\n version.value = latestVersion\r\n } catch (e) {\r\n console.error('[@duffcloudservices/cms] Failed to fetch site version:', e)\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n // Fetch on mount if enabled\r\n if (fetchOnMount && typeof window !== 'undefined') {\r\n onMounted(() => {\r\n fetchVersion()\r\n })\r\n }\r\n\r\n return {\r\n version,\r\n isLoading,\r\n releaseNotesUrl,\r\n }\r\n}\r\n","/**\r\n * useMediaCarousel Composable\r\n *\r\n * Extracts media carousel items from text content keys following the pattern:\r\n * `{prefix}.{N}.url`, `{prefix}.{N}.type`, `{prefix}.{N}.alt`\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup lang=\"ts\">\r\n * import { useTextContent, useMediaCarousel } from '@duffcloudservices/cms'\r\n *\r\n * const { t } = useTextContent({\r\n * pageSlug: 'bio-mackenzie',\r\n * defaults: {\r\n * 'hero.media-carousel.0.url': '/images/staff/mackenzie.webp',\r\n * 'hero.media-carousel.0.type': 'image',\r\n * 'hero.media-carousel.0.alt': 'Mackenzie Kowalick',\r\n * 'hero.media-carousel.1.url': '/videos/intro.mp4',\r\n * 'hero.media-carousel.1.type': 'video',\r\n * 'hero.media-carousel.1.alt': 'Introduction video',\r\n * }\r\n * })\r\n *\r\n * const { items } = useMediaCarousel({\r\n * prefix: 'hero.media-carousel',\r\n * t,\r\n * defaults: [\r\n * { url: '/images/staff/mackenzie.webp', type: 'image', alt: 'Mackenzie Kowalick' }\r\n * ]\r\n * })\r\n * </script>\r\n *\r\n * <template>\r\n * <MediaCarousel :items=\"items\" />\r\n * </template>\r\n * ```\r\n */\r\n\r\nimport { computed, type ComputedRef } from 'vue'\r\n\r\n/**\r\n * Media carousel item representing an image, video, or embed\r\n */\r\nexport interface MediaCarouselItem {\r\n /** URL to the image, video file, or embed URL */\r\n url: string\r\n /** Type of media: 'image', 'video' (direct file), 'youtube', or 'instagram' */\r\n type: 'image' | 'video' | 'youtube' | 'instagram'\r\n /** Accessibility alt text */\r\n alt?: string\r\n}\r\n\r\n/**\r\n * Configuration for useMediaCarousel composable\r\n */\r\nexport interface UseMediaCarouselConfig {\r\n /** Key prefix for carousel items (e.g., 'hero.media-carousel') */\r\n prefix: string\r\n /** The t() function from useTextContent */\r\n t: (key: string, fallback?: string) => string\r\n /** Default items to use if no content keys are found */\r\n defaults?: MediaCarouselItem[]\r\n /** Maximum number of items to look for (default: 10) */\r\n maxItems?: number\r\n}\r\n\r\n/**\r\n * Return type for useMediaCarousel composable\r\n */\r\nexport interface UseMediaCarouselReturn {\r\n /** Computed array of media carousel items */\r\n items: ComputedRef<MediaCarouselItem[]>\r\n /** Whether any items were found from content keys */\r\n hasItems: ComputedRef<boolean>\r\n /** Number of items in the carousel */\r\n count: ComputedRef<number>\r\n}\r\n\r\n/**\r\n * Extract media carousel items from text content keys.\r\n *\r\n * Looks for keys in the format:\r\n * - `{prefix}.{N}.url` - Required URL for the media\r\n * - `{prefix}.{N}.type` - Type: 'image' or 'video' (defaults to 'image')\r\n * - `{prefix}.{N}.alt` - Alt text for accessibility\r\n *\r\n * Items are sorted by index (0, 1, etc.) and only included if they have a valid URL.\r\n *\r\n * @param config - Configuration object\r\n * @returns Media carousel helpers and state\r\n */\r\nexport function useMediaCarousel(config: UseMediaCarouselConfig): UseMediaCarouselReturn {\r\n const {\r\n prefix,\r\n t,\r\n defaults = [],\r\n maxItems = 10,\r\n } = config\r\n\r\n const items = computed<MediaCarouselItem[]>(() => {\r\n const result: MediaCarouselItem[] = []\r\n\r\n // Look for items from 0 to maxItems\r\n for (let i = 0; i < maxItems; i++) {\r\n const urlKey = `${prefix}.${i}.url`\r\n const typeKey = `${prefix}.${i}.type`\r\n const altKey = `${prefix}.${i}.alt`\r\n\r\n // Use a sentinel value to detect if the key exists\r\n const url = t(urlKey, '')\r\n \r\n // Skip if no URL (key doesn't exist or is empty)\r\n if (!url || url === urlKey) {\r\n continue\r\n }\r\n\r\n const typeValue = t(typeKey, 'image')\r\n // Parse type value - support image, video, youtube, instagram\r\n let type: 'image' | 'video' | 'youtube' | 'instagram' = 'image'\r\n if (typeValue === 'video') type = 'video'\r\n else if (typeValue === 'youtube') type = 'youtube'\r\n else if (typeValue === 'instagram') type = 'instagram'\r\n const alt = t(altKey, '')\r\n\r\n result.push({\r\n url,\r\n type,\r\n alt: alt && alt !== altKey ? alt : undefined,\r\n })\r\n }\r\n\r\n // If no items found from content keys, use defaults\r\n if (result.length === 0) {\r\n return defaults\r\n }\r\n\r\n return result\r\n })\r\n\r\n const hasItems = computed(() => items.value.length > 0)\r\n const count = computed(() => items.value.length)\r\n\r\n return {\r\n items,\r\n hasItems,\r\n count,\r\n }\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/composables/useTextContent.ts","../src/composables/useSEO.ts","../src/composables/useReleaseNotes.ts","../src/composables/useSiteVersion.ts","../src/composables/useMediaCarousel.ts","../src/composables/useResponsiveImage.ts"],"names":["computed","getEnvVar","ref","onMounted","CACHE_TTL"],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * useTextContent Composable\r\n *\r\n * Provides text content management with build-time injection support and optional\r\n * runtime API overrides for DCS-managed customer sites.\r\n *\r\n * Content resolution order:\r\n * 1. Runtime API overrides (premium tier only, if mode is 'runtime')\r\n * 2. Build-time content from .dcs/content.yaml (injected via dcsContentPlugin)\r\n * 3. Hardcoded defaults passed to the composable\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup lang=\"ts\">\r\n * import { useTextContent } from '@duffcloudservices/cms'\r\n *\r\n * const { t, isLoading, error } = useTextContent({\r\n * pageSlug: 'home',\r\n * defaults: {\r\n * 'hero.title': 'Welcome to Our Site',\r\n * 'hero.subtitle': 'Build something amazing',\r\n * 'cta.primary': 'Get Started'\r\n * }\r\n * })\r\n * </script>\r\n *\r\n * <template>\r\n * <h1>{{ t('hero.title') }}</h1>\r\n * <p>{{ t('hero.subtitle') }}</p>\r\n * <button>{{ t('cta.primary') }}</button>\r\n * </template>\r\n * ```\r\n */\r\n\r\nimport { ref, computed, readonly, onMounted, type Ref } from 'vue'\r\nimport type { DcsContentFile, TextContentConfig, TextContentReturn } from '../types/content'\r\n\r\n// Declare the global injected by dcsContentPlugin\r\ndeclare const __DCS_CONTENT__: DcsContentFile | undefined\r\n\r\n// Simple in-memory cache for runtime fetches\r\nconst fetchCache = new Map<string, { data: Record<string, string>; expiresAt: number }>()\r\n\r\n/**\r\n * Safely get build-time content configuration.\r\n * Returns undefined if not available (no content.yaml or plugin not configured).\r\n */\r\nfunction getBuildTimeContent(): DcsContentFile | undefined {\r\n try {\r\n if (typeof __DCS_CONTENT__ !== 'undefined' && __DCS_CONTENT__ !== null) {\r\n return __DCS_CONTENT__\r\n }\r\n } catch {\r\n // __DCS_CONTENT__ not defined - that's fine, use defaults\r\n }\r\n return undefined\r\n}\r\n\r\n/**\r\n * Get build-time content for a specific page, merging global and page-specific content.\r\n */\r\nfunction getBuildTimePageContent(pageSlug: string): Record<string, string> {\r\n const content = getBuildTimeContent()\r\n if (!content) return {}\r\n\r\n const global = content.global ?? {}\r\n const page = content.pages?.[pageSlug] ?? {}\r\n\r\n return { ...global, ...page }\r\n}\r\n\r\n/**\r\n * Get environment variable value, handling both Vite and process.env patterns.\r\n */\r\nfunction getEnvVar(key: string, defaultValue = ''): string {\r\n try {\r\n // Vite pattern\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n const value = (import.meta.env as Record<string, string | undefined>)[key]\r\n if (value !== undefined) return value\r\n }\r\n } catch {\r\n // import.meta not available\r\n }\r\n\r\n try {\r\n // Node.js pattern\r\n if (typeof process !== 'undefined' && process.env) {\r\n const value = process.env[key]\r\n if (value !== undefined) return value\r\n }\r\n } catch {\r\n // process not available\r\n }\r\n\r\n return defaultValue\r\n}\r\n\r\n/**\r\n * useTextContent composable for DCS-managed text content.\r\n *\r\n * @param config - Configuration object\r\n * @returns Text content helpers and state\r\n */\r\nexport function useTextContent(config: TextContentConfig): TextContentReturn {\r\n const {\r\n pageSlug,\r\n defaults,\r\n fetchOnMount = true,\r\n cacheKey,\r\n cacheTtl = 60000,\r\n } = config\r\n\r\n // Get configuration from environment\r\n const apiBaseUrl = getEnvVar('VITE_API_BASE_URL', '')\r\n const siteSlug = getEnvVar('VITE_SITE_SLUG', '')\r\n const textOverrideMode = getEnvVar('VITE_TEXT_OVERRIDE_MODE', 'commit')\r\n const mode: 'commit' | 'runtime' = textOverrideMode === 'runtime' ? 'runtime' : 'commit'\r\n\r\n // Get build-time content immediately (synchronous)\r\n const buildTimeContent = getBuildTimePageContent(pageSlug)\r\n const hasBuildTimeContent = Object.keys(buildTimeContent).length > 0\r\n\r\n // State - initialize with build-time content\r\n const overrides = ref<Record<string, string>>({ ...buildTimeContent })\r\n const isLoading = ref(false)\r\n const error = ref<string | null>(null)\r\n\r\n // Computed merged texts (defaults + overrides)\r\n const texts = computed(() => ({ ...defaults, ...overrides.value }))\r\n\r\n /**\r\n * Get text by key with optional fallback.\r\n * Resolution order: overrides → defaults → fallback → key\r\n */\r\n function t(key: string, fallback?: string): string {\r\n return overrides.value[key] ?? defaults[key] ?? fallback ?? key\r\n }\r\n\r\n /**\r\n * Check if a key has an override (from build-time or runtime).\r\n */\r\n function hasOverride(key: string): boolean {\r\n return key in overrides.value\r\n }\r\n\r\n /**\r\n * Get an array of objects from indexed content keys.\r\n * Useful for lists where keys follow the pattern: arrayKey.index.property\r\n * Example: features.1.title, features.1.description, features.2.title, etc.\r\n *\r\n * @param arrayKey - The base key prefix (e.g., 'features', 'items')\r\n * @returns Array of objects with properties extracted from matching keys, sorted by index\r\n *\r\n * @example\r\n * ```ts\r\n * // Given content keys:\r\n * // positions.1.title = \"Software Engineer\"\r\n * // positions.1.description = \"Build cool stuff\"\r\n * // positions.2.title = \"Designer\"\r\n * // positions.2.description = \"Design cool stuff\"\r\n *\r\n * const positions = getArray('positions')\r\n * // Returns:\r\n * // [\r\n * // { _index: 1, title: \"Software Engineer\", description: \"Build cool stuff\" },\r\n * // { _index: 2, title: \"Designer\", description: \"Design cool stuff\" }\r\n * // ]\r\n * ```\r\n */\r\n function getArray(arrayKey: string): Array<Record<string, unknown> & { _index: number }> {\r\n const items: Record<number, Record<string, unknown> & { _index: number }> = {}\r\n const source = { ...defaults, ...overrides.value }\r\n\r\n Object.keys(source).forEach((key) => {\r\n if (key.startsWith(`${arrayKey}.`)) {\r\n const parts = key.split('.')\r\n // Format: arrayKey.index.property (or arrayKey.index.nested.property)\r\n // Example: features.1.title -> index=1, prop=title\r\n if (parts.length >= 3) {\r\n const index = Number.parseInt(parts[1], 10)\r\n const prop = parts.slice(2).join('.')\r\n\r\n if (!Number.isNaN(index)) {\r\n if (!items[index]) items[index] = { _index: index }\r\n items[index][prop] = source[key]\r\n }\r\n }\r\n }\r\n })\r\n\r\n return Object.values(items).sort((a, b) => a._index - b._index)\r\n }\r\n\r\n /**\r\n * Fetch runtime overrides from the API.\r\n * Only runs if mode is 'runtime' and API is configured.\r\n */\r\n async function fetchOverrides(): Promise<void> {\r\n // Skip API fetch in commit mode - use build-time content only\r\n if (mode !== 'runtime') {\r\n return\r\n }\r\n\r\n // Skip if API not configured\r\n if (!apiBaseUrl || !siteSlug) {\r\n console.warn(\r\n '[@duffcloudservices/cms] Runtime mode enabled but VITE_API_BASE_URL or VITE_SITE_SLUG not set'\r\n )\r\n return\r\n }\r\n\r\n // Check cache first\r\n const effectiveCacheKey = cacheKey ?? `${siteSlug}:${pageSlug}`\r\n const cached = fetchCache.get(effectiveCacheKey)\r\n if (cached && cached.expiresAt > Date.now()) {\r\n overrides.value = { ...buildTimeContent, ...cached.data }\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n error.value = null\r\n\r\n try {\r\n const url = `${apiBaseUrl}/api/v1/sites/${siteSlug}/pages/${pageSlug}/text`\r\n const response = await fetch(url, {\r\n headers: {\r\n Accept: 'application/json',\r\n },\r\n })\r\n\r\n if (!response.ok) {\r\n if (response.status === 404) {\r\n // No overrides for this page - that's fine\r\n return\r\n }\r\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\r\n }\r\n\r\n const data = await response.json()\r\n const apiOverrides = data.overrides ?? data.texts ?? {}\r\n\r\n // Cache the result\r\n fetchCache.set(effectiveCacheKey, {\r\n data: apiOverrides,\r\n expiresAt: Date.now() + cacheTtl,\r\n })\r\n\r\n // Merge: build-time content is baseline, API overrides on top\r\n overrides.value = { ...buildTimeContent, ...apiOverrides }\r\n } catch (e) {\r\n error.value = e instanceof Error ? e.message : 'Failed to load text overrides'\r\n console.error('[@duffcloudservices/cms] Failed to fetch text overrides:', e)\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n /**\r\n * Manually refresh overrides.\r\n * In commit mode, resets to build-time content.\r\n * In runtime mode, fetches fresh data from API.\r\n */\r\n async function refresh(): Promise<void> {\r\n if (mode !== 'runtime') {\r\n // In commit mode, just reset to build-time content\r\n overrides.value = { ...buildTimeContent }\r\n return\r\n }\r\n\r\n // Clear cache for this key\r\n const effectiveCacheKey = cacheKey ?? `${siteSlug}:${pageSlug}`\r\n fetchCache.delete(effectiveCacheKey)\r\n\r\n await fetchOverrides()\r\n }\r\n\r\n // Fetch on mount if enabled and in runtime mode (browser only)\r\n if (fetchOnMount && mode === 'runtime' && globalThis.window !== undefined) {\r\n onMounted(() => {\r\n fetchOverrides()\r\n })\r\n }\r\n\r\n return {\r\n t,\r\n getArray,\r\n texts,\r\n overrides,\r\n isLoading: readonly(isLoading) as Ref<boolean>,\r\n error: readonly(error) as Ref<string | null>,\r\n refresh,\r\n hasOverride,\r\n hasBuildTimeContent,\r\n mode,\r\n }\r\n}\r\n","/**\r\n * useSEO Composable\r\n *\r\n * Provides SEO configuration with build-time injection support from .dcs/seo.yaml.\r\n * Generates meta tags, Open Graph, Twitter Cards, and JSON-LD structured data.\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup lang=\"ts\">\r\n * import { useSEO } from '@duffcloudservices/cms'\r\n *\r\n * const { applyHead, getSchema, config } = useSEO('home')\r\n *\r\n * // Apply all meta tags\r\n * applyHead()\r\n *\r\n * // Or customize before applying\r\n * applyHead({\r\n * title: 'Custom Override Title',\r\n * schemas: [...getSchema(), customSchema]\r\n * })\r\n * </script>\r\n * ```\r\n */\r\n\r\nimport { computed, type ComputedRef } from 'vue'\r\nimport { useHead } from '@unhead/vue'\r\nimport type {\r\n SeoConfiguration,\r\n GlobalSeoConfig,\r\n SeoOpenGraphConfig,\r\n SeoTwitterConfig,\r\n SeoSchemaConfig,\r\n ResolvedPageSeo,\r\n UseSeoReturn,\r\n HeadOverrides,\r\n} from '../types/seo'\r\n\r\n// Declare the global injected by dcsSeoPlugin\r\ndeclare const __DCS_SEO__: SeoConfiguration | undefined\r\n\r\n/**\r\n * Safely get build-time SEO configuration.\r\n * Returns undefined if not available (no seo.yaml or plugin not configured).\r\n */\r\nfunction getBuildTimeSeo(): SeoConfiguration | undefined {\r\n try {\r\n if (typeof __DCS_SEO__ !== 'undefined' && __DCS_SEO__ !== null) {\r\n return __DCS_SEO__\r\n }\r\n } catch {\r\n // __DCS_SEO__ not defined - that's fine, use defaults\r\n }\r\n return undefined\r\n}\r\n\r\n// =============================================================================\r\n// Meta Tag Generation Utilities\r\n// =============================================================================\r\n\r\ninterface HeadInput {\r\n title?: string\r\n titleTemplate?: string | ((title: string) => string)\r\n meta?: Array<{ name?: string; property?: string; content: string }>\r\n link?: Array<{ rel: string; href: string; hreflang?: string }>\r\n script?: Array<{ type: string; children: string }>\r\n}\r\n\r\n/**\r\n * Generate Open Graph meta tags from config\r\n */\r\nfunction generateOpenGraphMeta(\r\n og: SeoOpenGraphConfig,\r\n global: GlobalSeoConfig,\r\n pageTitle: string,\r\n pageDescription: string,\r\n canonical: string\r\n): Array<{ property: string; content: string }> {\r\n const tags: Array<{ property: string; content: string }> = []\r\n\r\n tags.push({ property: 'og:title', content: og.title || pageTitle })\r\n tags.push({ property: 'og:description', content: og.description || pageDescription })\r\n tags.push({ property: 'og:url', content: og.url || canonical })\r\n tags.push({ property: 'og:type', content: og.type || 'website' })\r\n\r\n const image = og.image || global.images?.ogDefault\r\n if (image) {\r\n tags.push({ property: 'og:image', content: image })\r\n if (og.imageAlt || pageTitle) {\r\n tags.push({ property: 'og:image:alt', content: og.imageAlt || pageTitle })\r\n }\r\n if (og.imageWidth) {\r\n tags.push({ property: 'og:image:width', content: String(og.imageWidth) })\r\n }\r\n if (og.imageHeight) {\r\n tags.push({ property: 'og:image:height', content: String(og.imageHeight) })\r\n }\r\n }\r\n\r\n if (global.siteName) {\r\n tags.push({ property: 'og:site_name', content: global.siteName })\r\n }\r\n\r\n if (global.locale) {\r\n tags.push({ property: 'og:locale', content: global.locale })\r\n }\r\n\r\n // Article-specific tags\r\n if (og.type === 'article') {\r\n if (og.publishedTime) {\r\n tags.push({ property: 'article:published_time', content: og.publishedTime })\r\n }\r\n if (og.modifiedTime) {\r\n tags.push({ property: 'article:modified_time', content: og.modifiedTime })\r\n }\r\n if (og.author) {\r\n tags.push({ property: 'article:author', content: og.author })\r\n }\r\n if (og.section) {\r\n tags.push({ property: 'article:section', content: og.section })\r\n }\r\n if (og.tags) {\r\n og.tags.forEach((tag) => {\r\n tags.push({ property: 'article:tag', content: tag })\r\n })\r\n }\r\n }\r\n\r\n return tags\r\n}\r\n\r\n/**\r\n * Generate Twitter Card meta tags from config\r\n */\r\nfunction generateTwitterMeta(\r\n twitter: SeoTwitterConfig,\r\n global: GlobalSeoConfig,\r\n pageTitle: string,\r\n pageDescription: string\r\n): Array<{ name: string; content: string }> {\r\n const tags: Array<{ name: string; content: string }> = []\r\n\r\n tags.push({ name: 'twitter:card', content: twitter.card || 'summary_large_image' })\r\n tags.push({ name: 'twitter:title', content: twitter.title || pageTitle })\r\n tags.push({ name: 'twitter:description', content: twitter.description || pageDescription })\r\n\r\n const image = twitter.image || global.images?.twitterDefault\r\n if (image) {\r\n tags.push({ name: 'twitter:image', content: image })\r\n if (twitter.imageAlt || pageTitle) {\r\n tags.push({ name: 'twitter:image:alt', content: twitter.imageAlt || pageTitle })\r\n }\r\n }\r\n\r\n const site = twitter.site || global.social?.twitter\r\n if (site) {\r\n tags.push({ name: 'twitter:site', content: site.startsWith('@') ? site : `@${site}` })\r\n }\r\n\r\n if (twitter.creator) {\r\n tags.push({\r\n name: 'twitter:creator',\r\n content: twitter.creator.startsWith('@') ? twitter.creator : `@${twitter.creator}`,\r\n })\r\n }\r\n\r\n return tags\r\n}\r\n\r\n/**\r\n * Generate JSON-LD script content from schemas\r\n */\r\nfunction generateJsonLd(schemas: SeoSchemaConfig[], global: GlobalSeoConfig): object[] {\r\n return schemas.map((schema) => {\r\n const base: Record<string, unknown> = {\r\n '@context': 'https://schema.org',\r\n '@type': schema.type,\r\n }\r\n\r\n // Merge properties\r\n if (schema.properties) {\r\n Object.assign(base, schema.properties)\r\n }\r\n\r\n // Auto-populate common properties from global config\r\n if (schema.type === 'WebSite' && global.siteUrl && !base.url) {\r\n base.url = global.siteUrl\r\n }\r\n if (schema.type === 'WebSite' && global.siteName && !base.name) {\r\n base.name = global.siteName\r\n }\r\n\r\n return base\r\n })\r\n}\r\n\r\n/**\r\n * Resolve page SEO by merging global defaults with page-specific config\r\n */\r\nfunction resolvePageSeo(\r\n pageSlug: string,\r\n pagePath: string | undefined,\r\n seoConfig: SeoConfiguration | undefined\r\n): ResolvedPageSeo {\r\n const global = seoConfig?.global ?? {}\r\n const page = seoConfig?.pages?.[pageSlug] ?? {}\r\n\r\n // Build canonical URL\r\n let canonical = page.canonical || ''\r\n if (!canonical && global.siteUrl) {\r\n const path = pagePath ?? (pageSlug === 'home' ? '/' : `/${pageSlug}`)\r\n canonical = `${global.siteUrl.replace(/\\/$/, '')}${path}`\r\n }\r\n\r\n // Build title\r\n let title = page.title || global.defaultTitle || pageSlug\r\n if (!page.noTitleTemplate && global.titleTemplate) {\r\n title = global.titleTemplate.replace('%s', title)\r\n }\r\n\r\n // Merge Open Graph\r\n const openGraph: ResolvedPageSeo['openGraph'] = {\r\n type: page.openGraph?.type || 'website',\r\n title: page.openGraph?.title || page.title || global.defaultTitle || '',\r\n description: page.openGraph?.description || page.description || global.defaultDescription || '',\r\n ...page.openGraph,\r\n }\r\n\r\n // Merge Twitter\r\n const twitter: ResolvedPageSeo['twitter'] = {\r\n card: page.twitter?.card || 'summary_large_image',\r\n ...page.twitter,\r\n }\r\n\r\n // Combine schemas (global + page)\r\n const schemas = [...(global.schemas ?? []), ...(page.schemas ?? [])]\r\n\r\n return {\r\n title,\r\n description: page.description || global.defaultDescription || '',\r\n canonical,\r\n robots: page.robots || global.robots || 'index, follow',\r\n openGraph,\r\n twitter,\r\n schemas,\r\n alternates: page.alternates ?? [],\r\n }\r\n}\r\n\r\n/**\r\n * useSEO composable for DCS-managed SEO configuration.\r\n *\r\n * @param pageSlug - Page slug matching entry in seo.yaml\r\n * @param pagePath - Optional page path for canonical URL generation\r\n * @returns SEO helpers and state\r\n */\r\nexport function useSEO(pageSlug: string, pagePath?: string): UseSeoReturn {\r\n const seoConfig = getBuildTimeSeo()\r\n const hasBuildTimeSeo = seoConfig !== undefined\r\n\r\n // Computed resolved config\r\n const config: ComputedRef<ResolvedPageSeo> = computed(() =>\r\n resolvePageSeo(pageSlug, pagePath, seoConfig)\r\n )\r\n\r\n /**\r\n * Get JSON-LD schema objects for the page\r\n */\r\n function getSchema(): object[] {\r\n return generateJsonLd(config.value.schemas, seoConfig?.global ?? {})\r\n }\r\n\r\n /**\r\n * Get canonical URL for the page\r\n */\r\n function getCanonical(): string {\r\n return config.value.canonical\r\n }\r\n\r\n /**\r\n * Apply all meta tags via useHead\r\n */\r\n function applyHead(overrides?: HeadOverrides): void {\r\n const resolved = config.value\r\n const global = seoConfig?.global ?? {}\r\n\r\n const title = overrides?.title ?? resolved.title\r\n const description = overrides?.description ?? resolved.description\r\n\r\n // Build meta tags\r\n const meta: HeadInput['meta'] = []\r\n\r\n // Basic meta\r\n meta.push({ name: 'description', content: description })\r\n if (resolved.robots) {\r\n meta.push({ name: 'robots', content: resolved.robots })\r\n }\r\n\r\n // Verification codes\r\n if (global.verification?.google) {\r\n meta.push({ name: 'google-site-verification', content: global.verification.google })\r\n }\r\n if (global.verification?.bing) {\r\n meta.push({ name: 'msvalidate.01', content: global.verification.bing })\r\n }\r\n\r\n // Open Graph\r\n const ogMeta = generateOpenGraphMeta(\r\n resolved.openGraph,\r\n global,\r\n title,\r\n description,\r\n resolved.canonical\r\n )\r\n meta.push(...ogMeta.map((t) => ({ property: t.property, content: t.content })))\r\n\r\n // Twitter\r\n const twitterMeta = generateTwitterMeta(resolved.twitter, global, title, description)\r\n meta.push(...twitterMeta.map((t) => ({ name: t.name, content: t.content })))\r\n\r\n // Additional overrides\r\n if (overrides?.meta) {\r\n meta.push(...overrides.meta)\r\n }\r\n\r\n // Build links\r\n const link: HeadInput['link'] = []\r\n\r\n // Canonical\r\n if (resolved.canonical) {\r\n link.push({ rel: 'canonical', href: resolved.canonical })\r\n }\r\n\r\n // Alternate languages\r\n resolved.alternates.forEach((alt) => {\r\n link.push({ rel: 'alternate', href: alt.href, hreflang: alt.hreflang })\r\n })\r\n\r\n // Build scripts (JSON-LD)\r\n const schemas = overrides?.schemas ?? getSchema()\r\n const script: HeadInput['script'] = schemas.map((schema) => ({\r\n type: 'application/ld+json',\r\n children: JSON.stringify(schema),\r\n }))\r\n\r\n // Apply via useHead\r\n useHead({\r\n title,\r\n meta,\r\n link,\r\n script,\r\n })\r\n }\r\n\r\n return {\r\n config,\r\n applyHead,\r\n getSchema,\r\n getCanonical,\r\n hasBuildTimeSeo,\r\n }\r\n}\r\n\r\n/**\r\n * Create a typed useSEO function with site-specific defaults.\r\n * Useful for creating a site-wide wrapper.\r\n *\r\n * @example\r\n * ```ts\r\n * // composables/useSiteSeo.ts\r\n * import { createSiteSEO } from '@duffcloudservices/cms'\r\n *\r\n * export const useSiteSeo = createSiteSEO({\r\n * siteName: 'My Site',\r\n * siteUrl: 'https://example.com'\r\n * })\r\n * ```\r\n */\r\nexport function createSiteSEO(\r\n _siteDefaults: Partial<GlobalSeoConfig>\r\n): (pageSlug: string, pagePath?: string) => UseSeoReturn {\r\n return function siteUseSEO(pageSlug: string, pagePath?: string): UseSeoReturn {\r\n // Note: siteDefaults would be used if we needed to override at runtime\r\n // but build-time injection handles this via dcsSeoPlugin\r\n return useSEO(pageSlug, pagePath)\r\n }\r\n}\r\n","/**\r\n * useReleaseNotes Composable\r\n *\r\n * Fetches and displays versioned release notes from the DCS Portal API.\r\n * Supports fetching specific versions or the latest release.\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup lang=\"ts\">\r\n * import { useReleaseNotes } from '@duffcloudservices/cms'\r\n * import { useRoute } from 'vue-router'\r\n *\r\n * const route = useRoute()\r\n * const version = route.params.version as string || 'latest'\r\n *\r\n * const { releaseNote, isLoading, error } = useReleaseNotes(version)\r\n * </script>\r\n *\r\n * <template>\r\n * <div v-if=\"isLoading\">Loading...</div>\r\n * <div v-else-if=\"error\">{{ error }}</div>\r\n * <article v-else-if=\"releaseNote\">\r\n * <h1>{{ releaseNote.title }}</h1>\r\n * <p>{{ releaseNote.summary }}</p>\r\n * <div v-html=\"renderedMarkdown\" />\r\n * </article>\r\n * </template>\r\n * ```\r\n */\r\n\r\nimport { ref, onMounted } from 'vue'\r\nimport type { ReleaseNote, ReleaseNotesReturn } from '../types/release-notes'\r\n\r\n/**\r\n * Get environment variable value.\r\n */\r\nfunction getEnvVar(key: string, defaultValue = ''): string {\r\n try {\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n const value = (import.meta.env as Record<string, string | undefined>)[key]\r\n if (value !== undefined) return value\r\n }\r\n } catch {\r\n // import.meta not available\r\n }\r\n return defaultValue\r\n}\r\n\r\n// Simple cache for release notes\r\nconst releaseNotesCache = new Map<string, { data: ReleaseNote; expiresAt: number }>()\r\nconst CACHE_TTL = 5 * 60 * 1000 // 5 minutes\r\n\r\n/**\r\n * useReleaseNotes composable for fetching release notes from the DCS API.\r\n *\r\n * @param version - Semantic version (e.g., \"1.2.0\") or \"latest\"\r\n * @param options - Optional configuration\r\n * @returns Release notes data and state\r\n */\r\nexport function useReleaseNotes(\r\n version: string,\r\n options: { fetchOnMount?: boolean } = {}\r\n): ReleaseNotesReturn {\r\n const { fetchOnMount = true } = options\r\n\r\n const apiBaseUrl = getEnvVar('VITE_API_BASE_URL', 'https://portal.duffcloudservices.com')\r\n const siteSlug = getEnvVar('VITE_SITE_SLUG', '')\r\n\r\n const releaseNote = ref<ReleaseNote | null>(null)\r\n const isLoading = ref(false)\r\n const error = ref<string | null>(null)\r\n\r\n async function fetchReleaseNotes(): Promise<void> {\r\n if (!siteSlug) {\r\n error.value = 'VITE_SITE_SLUG environment variable not set'\r\n return\r\n }\r\n\r\n // Check cache\r\n const cacheKey = `${siteSlug}:${version}`\r\n const cached = releaseNotesCache.get(cacheKey)\r\n if (cached && cached.expiresAt > Date.now()) {\r\n releaseNote.value = cached.data\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n error.value = null\r\n\r\n try {\r\n const url = `${apiBaseUrl}/api/v1/sites/${siteSlug}/release-notes/${version}`\r\n const response = await fetch(url, {\r\n headers: {\r\n Accept: 'application/json',\r\n },\r\n })\r\n\r\n if (!response.ok) {\r\n if (response.status === 404) {\r\n error.value = `Release notes for version ${version} not found`\r\n return\r\n }\r\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\r\n }\r\n\r\n const data = await response.json()\r\n\r\n // Normalize the response\r\n const note: ReleaseNote = {\r\n version: data.version,\r\n title: data.title,\r\n summary: data.summary || '',\r\n notesMarkdown: data.notesMarkdown || data.notes || '',\r\n changeCount: data.changeCount || 0,\r\n releaseDate: data.releaseDate || data.releasedAt || '',\r\n }\r\n\r\n // Cache the result\r\n releaseNotesCache.set(cacheKey, {\r\n data: note,\r\n expiresAt: Date.now() + CACHE_TTL,\r\n })\r\n\r\n releaseNote.value = note\r\n } catch (e) {\r\n error.value = e instanceof Error ? e.message : 'Failed to load release notes'\r\n console.error('[@duffcloudservices/cms] Failed to fetch release notes:', e)\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n async function refresh(): Promise<void> {\r\n // Clear cache\r\n const cacheKey = `${siteSlug}:${version}`\r\n releaseNotesCache.delete(cacheKey)\r\n await fetchReleaseNotes()\r\n }\r\n\r\n // Fetch on mount if enabled\r\n if (fetchOnMount && typeof window !== 'undefined') {\r\n onMounted(() => {\r\n fetchReleaseNotes()\r\n })\r\n }\r\n\r\n return {\r\n releaseNote,\r\n isLoading,\r\n error,\r\n refresh,\r\n }\r\n}\r\n","/**\r\n * useSiteVersion Composable\r\n *\r\n * Gets the current site version for footer badges and version displays.\r\n * Fetches the latest release version from the DCS Portal API.\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup lang=\"ts\">\r\n * import { useSiteVersion } from '@duffcloudservices/cms'\r\n *\r\n * const { version, releaseNotesUrl } = useSiteVersion()\r\n * </script>\r\n *\r\n * <template>\r\n * <footer>\r\n * <a v-if=\"version\" :href=\"releaseNotesUrl\" class=\"version-badge\">\r\n * v{{ version }}\r\n * </a>\r\n * </footer>\r\n * </template>\r\n * ```\r\n */\r\n\r\nimport { ref, computed, onMounted } from 'vue'\r\nimport type { SiteVersionReturn } from '../types/release-notes'\r\n\r\n/**\r\n * Get environment variable value.\r\n */\r\nfunction getEnvVar(key: string, defaultValue = ''): string {\r\n try {\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n const value = (import.meta.env as Record<string, string | undefined>)[key]\r\n if (value !== undefined) return value\r\n }\r\n } catch {\r\n // import.meta not available\r\n }\r\n return defaultValue\r\n}\r\n\r\n// Cache for site version\r\nlet versionCache: { version: string; expiresAt: number } | null = null\r\nconst CACHE_TTL = 10 * 60 * 1000 // 10 minutes\r\n\r\n/**\r\n * useSiteVersion composable for displaying the current site version.\r\n *\r\n * @param options - Optional configuration\r\n * @returns Site version data and computed URL\r\n */\r\nexport function useSiteVersion(options: { fetchOnMount?: boolean } = {}): SiteVersionReturn {\r\n const { fetchOnMount = true } = options\r\n\r\n const apiBaseUrl = getEnvVar('VITE_API_BASE_URL', 'https://portal.duffcloudservices.com')\r\n const siteSlug = getEnvVar('VITE_SITE_SLUG', '')\r\n\r\n const version = ref<string | null>(null)\r\n const isLoading = ref(false)\r\n\r\n const releaseNotesUrl = computed(() => {\r\n if (!version.value) return '/releaseNotes/latest'\r\n return `/releaseNotes/${version.value}`\r\n })\r\n\r\n async function fetchVersion(): Promise<void> {\r\n if (!siteSlug) {\r\n return\r\n }\r\n\r\n // Check cache\r\n if (versionCache && versionCache.expiresAt > Date.now()) {\r\n version.value = versionCache.version\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n\r\n try {\r\n // Fetch the latest release notes to get the version\r\n const url = `${apiBaseUrl}/api/v1/sites/${siteSlug}/release-notes/latest`\r\n const response = await fetch(url, {\r\n headers: {\r\n Accept: 'application/json',\r\n },\r\n })\r\n\r\n if (!response.ok) {\r\n // No release notes yet - that's fine\r\n return\r\n }\r\n\r\n const data = await response.json()\r\n const latestVersion = data.version\r\n\r\n // Cache the result\r\n versionCache = {\r\n version: latestVersion,\r\n expiresAt: Date.now() + CACHE_TTL,\r\n }\r\n\r\n version.value = latestVersion\r\n } catch (e) {\r\n console.error('[@duffcloudservices/cms] Failed to fetch site version:', e)\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n // Fetch on mount if enabled\r\n if (fetchOnMount && typeof window !== 'undefined') {\r\n onMounted(() => {\r\n fetchVersion()\r\n })\r\n }\r\n\r\n return {\r\n version,\r\n isLoading,\r\n releaseNotesUrl,\r\n }\r\n}\r\n","/**\r\n * useMediaCarousel Composable\r\n *\r\n * Extracts media carousel items from text content keys following the pattern:\r\n * `{prefix}.{N}.url`, `{prefix}.{N}.type`, `{prefix}.{N}.alt`\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup lang=\"ts\">\r\n * import { useTextContent, useMediaCarousel } from '@duffcloudservices/cms'\r\n *\r\n * const { t } = useTextContent({\r\n * pageSlug: 'bio-mackenzie',\r\n * defaults: {\r\n * 'hero.media-carousel.0.url': '/images/staff/mackenzie.webp',\r\n * 'hero.media-carousel.0.type': 'image',\r\n * 'hero.media-carousel.0.alt': 'Mackenzie Kowalick',\r\n * 'hero.media-carousel.1.url': '/videos/intro.mp4',\r\n * 'hero.media-carousel.1.type': 'video',\r\n * 'hero.media-carousel.1.alt': 'Introduction video',\r\n * }\r\n * })\r\n *\r\n * const { items } = useMediaCarousel({\r\n * prefix: 'hero.media-carousel',\r\n * t,\r\n * defaults: [\r\n * { url: '/images/staff/mackenzie.webp', type: 'image', alt: 'Mackenzie Kowalick' }\r\n * ]\r\n * })\r\n * </script>\r\n *\r\n * <template>\r\n * <MediaCarousel :items=\"items\" />\r\n * </template>\r\n * ```\r\n */\r\n\r\nimport { computed, type ComputedRef } from 'vue'\r\nimport { isCdnAssetUrl } from '@duffcloudservices/cms-core'\r\n\r\n/**\r\n * Media carousel item representing an image, video, or embed\r\n */\r\nexport interface MediaCarouselItem {\r\n /** URL to the image, video file, or embed URL */\r\n url: string\r\n /** Type of media: 'image', 'video' (direct file), 'youtube', or 'instagram' */\r\n type: 'image' | 'video' | 'youtube' | 'instagram'\r\n /** Accessibility alt text */\r\n alt?: string\r\n /**\r\n * Whether this image has responsive CDN variants available.\r\n * Automatically set to `true` when the URL matches the DCS CDN asset pattern.\r\n * Components rendering the carousel should use `<ResponsiveImage>` when this is `true`.\r\n */\r\n responsive?: boolean\r\n}\r\n\r\n/**\r\n * Configuration for useMediaCarousel composable\r\n */\r\nexport interface UseMediaCarouselConfig {\r\n /** Key prefix for carousel items (e.g., 'hero.media-carousel') */\r\n prefix: string\r\n /** The t() function from useTextContent */\r\n t: (key: string, fallback?: string) => string\r\n /** Default items to use if no content keys are found */\r\n defaults?: MediaCarouselItem[]\r\n /** Maximum number of items to look for (default: 10) */\r\n maxItems?: number\r\n}\r\n\r\n/**\r\n * Return type for useMediaCarousel composable\r\n */\r\nexport interface UseMediaCarouselReturn {\r\n /** Computed array of media carousel items */\r\n items: ComputedRef<MediaCarouselItem[]>\r\n /** Whether any items were found from content keys */\r\n hasItems: ComputedRef<boolean>\r\n /** Number of items in the carousel */\r\n count: ComputedRef<number>\r\n}\r\n\r\n/**\r\n * Extract media carousel items from text content keys.\r\n *\r\n * Looks for keys in the format:\r\n * - `{prefix}.{N}.url` - Required URL for the media\r\n * - `{prefix}.{N}.type` - Type: 'image' or 'video' (defaults to 'image')\r\n * - `{prefix}.{N}.alt` - Alt text for accessibility\r\n *\r\n * Items are sorted by index (0, 1, etc.) and only included if they have a valid URL.\r\n *\r\n * @param config - Configuration object\r\n * @returns Media carousel helpers and state\r\n */\r\nexport function useMediaCarousel(config: UseMediaCarouselConfig): UseMediaCarouselReturn {\r\n const {\r\n prefix,\r\n t,\r\n defaults = [],\r\n maxItems = 10,\r\n } = config\r\n\r\n const items = computed<MediaCarouselItem[]>(() => {\r\n const result: MediaCarouselItem[] = []\r\n\r\n // Look for items from 0 to maxItems\r\n for (let i = 0; i < maxItems; i++) {\r\n const urlKey = `${prefix}.${i}.url`\r\n const typeKey = `${prefix}.${i}.type`\r\n const altKey = `${prefix}.${i}.alt`\r\n\r\n // Use a sentinel value to detect if the key exists\r\n const url = t(urlKey, '')\r\n \r\n // Skip if no URL (key doesn't exist or is empty)\r\n if (!url || url === urlKey) {\r\n continue\r\n }\r\n\r\n const typeValue = t(typeKey, 'image')\r\n // Parse type value - support image, video, youtube, instagram\r\n let type: 'image' | 'video' | 'youtube' | 'instagram' = 'image'\r\n if (typeValue === 'video') type = 'video'\r\n else if (typeValue === 'youtube') type = 'youtube'\r\n else if (typeValue === 'instagram') type = 'instagram'\r\n const alt = t(altKey, '')\r\n\r\n result.push({\r\n url,\r\n type,\r\n alt: alt && alt !== altKey ? alt : undefined,\r\n // Flag CDN-hosted images as responsive so carousel components\r\n // can render them with <ResponsiveImage> automatically\r\n responsive: type === 'image' && isCdnAssetUrl(url),\r\n })\r\n }\r\n\r\n // If no items found from content keys, use defaults\r\n if (result.length === 0) {\r\n return defaults\r\n }\r\n\r\n return result\r\n })\r\n\r\n const hasItems = computed(() => items.value.length > 0)\r\n const count = computed(() => items.value.length)\r\n\r\n return {\r\n items,\r\n hasItems,\r\n count,\r\n }\r\n}\r\n","/**\r\n * Vue 3 composable that resolves responsive image variants for DCS CDN-hosted assets.\r\n *\r\n * Wraps the framework-agnostic `resolveResponsiveImage` from `@duffcloudservices/cms-core`\r\n * with reactive Vue refs so it can be used directly in `<script setup>` blocks.\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup lang=\"ts\">\r\n * import { useResponsiveImage } from '@duffcloudservices/cms'\r\n *\r\n * const hero = useResponsiveImage({\r\n * src: 'https://files.duffcloudservices.com/kept/assets/hero/abc-123.jpg',\r\n * alt: 'Hero image',\r\n * context: 'hero',\r\n * })\r\n * </script>\r\n *\r\n * <template>\r\n * <picture v-if=\"hero.hasVariants\">\r\n * <source\r\n * v-for=\"source in hero.sources\"\r\n * :key=\"source.type\"\r\n * :srcset=\"source.srcset\"\r\n * :type=\"source.type\"\r\n * :sizes=\"source.sizes\"\r\n * />\r\n * <img v-bind=\"hero.imgProps\" class=\"w-full h-full object-cover\" />\r\n * </picture>\r\n * <img v-else v-bind=\"hero.imgProps\" class=\"w-full h-full object-cover\" />\r\n * </template>\r\n * ```\r\n */\r\n\r\nimport { computed, type MaybeRefOrGetter, toValue } from 'vue'\r\nimport {\r\n resolveResponsiveImage,\r\n type ImageContext,\r\n type ResponsiveImageResult,\r\n} from '@duffcloudservices/cms-core'\r\n\r\nexport interface UseResponsiveImageOptions {\r\n /** Source URL — can be a reactive ref, getter, or plain string. */\r\n src: MaybeRefOrGetter<string>\r\n /** Alt text — can be a reactive ref, getter, or plain string. */\r\n alt: MaybeRefOrGetter<string>\r\n /** Sizing context — determines which variants to include. */\r\n context?: MaybeRefOrGetter<ImageContext | undefined>\r\n /** Optional `sizes` attribute override. */\r\n sizes?: MaybeRefOrGetter<string | undefined>\r\n /** Skip variant resolution and use the original URL only. */\r\n original?: MaybeRefOrGetter<boolean | undefined>\r\n}\r\n\r\n/**\r\n * Reactively resolves responsive image metadata for a DCS CDN URL.\r\n *\r\n * The returned object is a computed ref that recomputes whenever any\r\n * of the input refs change. Spread `imgProps` onto an `<img>` or\r\n * combine with `sources` inside a `<picture>` element.\r\n */\r\nexport function useResponsiveImage(options: UseResponsiveImageOptions): ResponsiveImageResult {\r\n const result = computed(() =>\r\n resolveResponsiveImage({\r\n src: toValue(options.src),\r\n alt: toValue(options.alt),\r\n context: toValue(options.context),\r\n sizes: toValue(options.sizes),\r\n original: toValue(options.original) ?? undefined,\r\n }),\r\n )\r\n\r\n // Return a reactive proxy that delegates to the computed\r\n return {\r\n get imgProps() {\r\n return result.value.imgProps\r\n },\r\n get sources() {\r\n return result.value.sources\r\n },\r\n get hasVariants() {\r\n return result.value.hasVariants\r\n },\r\n }\r\n}\r\n"]}
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
import { Component, Plugin as Plugin$1 } from 'vue';
|
|
3
|
+
import MarkdownIt from 'markdown-it';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* DCS Content Plugin for Vite
|
|
@@ -190,4 +191,158 @@ interface DcsPreviewPluginOptions {
|
|
|
190
191
|
*/
|
|
191
192
|
declare function dcsPreviewPlugin(ribbonComponent: Component, options?: DcsPreviewPluginOptions): Plugin$1;
|
|
192
193
|
|
|
193
|
-
|
|
194
|
+
/**
|
|
195
|
+
* DCS CDN Image Plugin for Vite
|
|
196
|
+
*
|
|
197
|
+
* Rewrites local static image references (e.g. `/images/staff/photo.jpg`)
|
|
198
|
+
* to CDN URLs at build time using the `.dcs/cdn-image-map.json` mapping file
|
|
199
|
+
* generated by the `image-migrate adopt` CLI command.
|
|
200
|
+
*
|
|
201
|
+
* For raster images with WebP variants, `<img>` elements are transformed into
|
|
202
|
+
* responsive `<picture>` elements with `srcset` for optimised delivery.
|
|
203
|
+
* SVGs receive a simple URL swap with no variant handling.
|
|
204
|
+
*
|
|
205
|
+
* **In development mode this plugin is a no-op** — local `/images/` paths
|
|
206
|
+
* continue to work via Vite's static asset serving so hot-reload is unaffected.
|
|
207
|
+
*
|
|
208
|
+
* The plugin handles two in-pipeline replacement vectors:
|
|
209
|
+
*
|
|
210
|
+
* 1. **Module transform** (`transform` hook) — rewrites `<img>` tags and
|
|
211
|
+
* string literals in Vue SFCs, TS, JS, CSS, MD, and HTML modules.
|
|
212
|
+
* 2. **Chunk rendering** (`renderChunk` hook) — rewrites image paths in
|
|
213
|
+
* final rendered JS/CSS chunks AFTER Vite's `define` substitution.
|
|
214
|
+
* This catches data injected by `dcsContentPlugin` via `__DCS_CONTENT__`
|
|
215
|
+
* (from `.dcs/content.yaml`) which bypasses the `transform` hook.
|
|
216
|
+
*
|
|
217
|
+
* A third vector — **post-build file processing** — is handled by the
|
|
218
|
+
* companion `dcsCdnBuildEnd` hook, which must be registered separately in
|
|
219
|
+
* VitePress config. VitePress generates HTML *after* both Vite builds
|
|
220
|
+
* complete, so Vite plugin hooks (`closeBundle`, `transformIndexHtml`)
|
|
221
|
+
* cannot catch SSR-rendered `<head>` tags or static `public/` files like
|
|
222
|
+
* `service-worker.js`.
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* // .vitepress/config.ts
|
|
227
|
+
* import { dcsCdnImagePlugin, dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'
|
|
228
|
+
*
|
|
229
|
+
* export default defineConfig({
|
|
230
|
+
* vite: {
|
|
231
|
+
* plugins: [
|
|
232
|
+
* dcsCdnImagePlugin()
|
|
233
|
+
* ]
|
|
234
|
+
* },
|
|
235
|
+
* buildEnd: dcsCdnBuildEnd()
|
|
236
|
+
* })
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
interface DcsCdnImagePluginOptions {
|
|
241
|
+
/** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */
|
|
242
|
+
mapPath?: string;
|
|
243
|
+
/**
|
|
244
|
+
* Patterns to match for replacement. Each must be a **leading-slash path prefix**
|
|
245
|
+
* that appears in source code (e.g. `/images/`). The `localPath` field in the
|
|
246
|
+
* mapping file is compared *without* a leading slash.
|
|
247
|
+
*
|
|
248
|
+
* Default: `['/images/']`
|
|
249
|
+
*/
|
|
250
|
+
pathPrefixes?: string[];
|
|
251
|
+
/**
|
|
252
|
+
* Default `sizes` attribute for responsive `<picture>` elements.
|
|
253
|
+
* Override per-context via the `data-sizes` attribute on the original `<img>`.
|
|
254
|
+
*
|
|
255
|
+
* Default: `'(max-width: 1024px) 100vw, 1024px'`
|
|
256
|
+
*/
|
|
257
|
+
defaultSizes?: string;
|
|
258
|
+
/** Enable debug logging */
|
|
259
|
+
debug?: boolean;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Vite plugin that rewrites static `/images/` paths to CDN URLs at build time.
|
|
263
|
+
*/
|
|
264
|
+
declare function dcsCdnImagePlugin(options?: DcsCdnImagePluginOptions): Plugin;
|
|
265
|
+
interface DcsCdnBuildEndOptions {
|
|
266
|
+
/** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */
|
|
267
|
+
mapPath?: string;
|
|
268
|
+
/**
|
|
269
|
+
* Patterns to match for replacement.
|
|
270
|
+
* Default: `['/images/']`
|
|
271
|
+
*/
|
|
272
|
+
pathPrefixes?: string[];
|
|
273
|
+
/** Enable debug logging */
|
|
274
|
+
debug?: boolean;
|
|
275
|
+
/**
|
|
276
|
+
* File extensions to process in the output directory.
|
|
277
|
+
* Default: `['.html', '.js']`
|
|
278
|
+
*/
|
|
279
|
+
extensions?: string[];
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* VitePress `buildEnd` hook factory that post-processes generated HTML and
|
|
283
|
+
* static files in the output directory to replace remaining `/images/` paths
|
|
284
|
+
* with CDN URLs.
|
|
285
|
+
*
|
|
286
|
+
* VitePress generates HTML **after** both Vite builds complete, so Vite
|
|
287
|
+
* plugin hooks (`closeBundle`, `transformIndexHtml`) cannot catch
|
|
288
|
+
* SSR-rendered `<head>` tags (favicons, OG images). This hook runs after all
|
|
289
|
+
* HTML files are written to disk.
|
|
290
|
+
*
|
|
291
|
+
* Also rewrites static files (e.g. `service-worker.js`) copied from
|
|
292
|
+
* `public/` that are not part of Vite's module pipeline.
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```ts
|
|
296
|
+
* // .vitepress/config.ts
|
|
297
|
+
* import { dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'
|
|
298
|
+
*
|
|
299
|
+
* export default defineConfig({
|
|
300
|
+
* buildEnd: dcsCdnBuildEnd({ debug: true })
|
|
301
|
+
* })
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
declare function dcsCdnBuildEnd(options?: DcsCdnBuildEndOptions): (siteConfig: {
|
|
305
|
+
root: string;
|
|
306
|
+
outDir: string;
|
|
307
|
+
}) => Promise<void>;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* markdown-it plugin that transforms standard `` image syntax
|
|
311
|
+
* into responsive `<picture>` elements when the URL matches the DCS CDN
|
|
312
|
+
* asset pattern.
|
|
313
|
+
*
|
|
314
|
+
* Non-CDN images are rendered with the default image renderer (plain `<img>`).
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* // .vitepress/config.ts
|
|
319
|
+
* import { responsiveImagePlugin } from '@duffcloudservices/cms/plugins'
|
|
320
|
+
*
|
|
321
|
+
* export default defineConfig({
|
|
322
|
+
* markdown: {
|
|
323
|
+
* config: (md) => {
|
|
324
|
+
* md.use(responsiveImagePlugin)
|
|
325
|
+
* },
|
|
326
|
+
* },
|
|
327
|
+
* })
|
|
328
|
+
* ```
|
|
329
|
+
*
|
|
330
|
+
* Input markdown:
|
|
331
|
+
* ```md
|
|
332
|
+
* 
|
|
333
|
+
* ```
|
|
334
|
+
*
|
|
335
|
+
* Rendered HTML:
|
|
336
|
+
* ```html
|
|
337
|
+
* <picture>
|
|
338
|
+
* <source srcset="...abc-123-sm.webp 640w, ...abc-123-md.webp 1024w, ...abc-123-lg.webp 1920w"
|
|
339
|
+
* type="image/webp"
|
|
340
|
+
* sizes="(max-width: 1024px) 100vw, 1024px" />
|
|
341
|
+
* <img src="...abc-123-md.webp" alt="Physical therapy session" loading="lazy" decoding="async" />
|
|
342
|
+
* </picture>
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
|
|
346
|
+
declare function responsiveImagePlugin(md: MarkdownIt): void;
|
|
347
|
+
|
|
348
|
+
export { type DcsCdnBuildEndOptions, type DcsCdnImagePluginOptions, type DcsContentPluginOptions, type DcsEditorPluginOptions, type DcsPreviewPluginOptions, type DcsSeoPluginOptions, dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsPreviewPlugin, dcsSeoPlugin, responsiveImagePlugin };
|