@typecms/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -0
- package/dist/client-BW6BWpfF.d.mts +92 -0
- package/dist/flags/index.d.mts +100 -0
- package/dist/flags/index.mjs +99 -0
- package/dist/flags/index.mjs.map +1 -0
- package/dist/index.d.mts +67 -0
- package/dist/index.mjs +174 -0
- package/dist/index.mjs.map +1 -0
- package/dist/overlay-CLYAeNwg.d.mts +43 -0
- package/dist/preview/dom.d.mts +76 -0
- package/dist/preview/dom.mjs +352 -0
- package/dist/preview/dom.mjs.map +1 -0
- package/dist/preview/index.d.mts +438 -0
- package/dist/preview/index.mjs +736 -0
- package/dist/preview/index.mjs.map +1 -0
- package/dist/preview/next.d.mts +99 -0
- package/dist/preview/next.mjs +31 -0
- package/dist/preview/next.mjs.map +1 -0
- package/dist/preview/svelte.d.mts +99 -0
- package/dist/preview/svelte.mjs +437 -0
- package/dist/preview/svelte.mjs.map +1 -0
- package/dist/preview/vue.d.mts +113 -0
- package/dist/preview/vue.mjs +587 -0
- package/dist/preview/vue.mjs.map +1 -0
- package/dist/types/index.d.mts +358 -0
- package/dist/types/index.mjs +8 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/types-BNHDdA2G.d.mts +117 -0
- package/package.json +90 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/preview/vue.ts","../../src/preview/client.ts","../../src/preview/overlay.ts","../../src/preview/richtext.ts"],"sourcesContent":["/**\n * Preview Vue Composables\n *\n * Vue 3 integration for Type CMS live preview.\n * Mirrors the React layer (react.tsx) 1:1 on top of the\n * framework-agnostic preview client.\n *\n * @example\n * ```typescript\n * // main.ts\n * import { createApp } from 'vue'\n * import { TypecmsPreview } from '@typecms/sdk/preview/vue'\n *\n * const app = createApp(App)\n * app.use(TypecmsPreview, { overlay: true })\n * ```\n *\n * ```vue\n * <script setup>\n * import { usePreviewContent, useIsPreview } from '@typecms/sdk/preview/vue'\n *\n * const props = defineProps(['post'])\n * const isPreview = useIsPreview()\n * const content = usePreviewContent(() => props.post)\n * </script>\n * ```\n */\n\nimport {\n shallowRef,\n computed,\n inject,\n toValue,\n type App,\n type ComputedRef,\n type InjectionKey,\n type MaybeRefOrGetter,\n type Plugin,\n type Ref,\n} from 'vue'\nimport { createPreviewClient, deepMerge, getNestedValue } from './client'\nimport { attachPreviewOverlay, type PreviewOverlayOptions } from './overlay'\nimport { createRichTextPreview } from './richtext'\nimport type { PreviewConfig, PreviewState } from './types'\n\n// =============================================================================\n// Context\n// =============================================================================\n\nexport interface VuePreviewContext {\n /** Reactive preview state */\n state: Ref<PreviewState>\n /** Check if running in preview mode */\n isPreview: ComputedRef<boolean>\n /** Get preview value for a field path */\n getValue: <T>(path: string) => T | undefined\n /** Merge published content with preview overrides */\n mergeContent: <T extends Record<string, unknown>>(content: T) => T\n /** Notify editor of navigation */\n notifyNavigation: (pathname: string) => void\n /** Notify editor that a field was clicked in the preview */\n notifyFieldFocus: (path: string) => void\n /** Start listening for preview messages */\n connect: () => void\n /** Stop listening and reset state */\n disconnect: () => void\n}\n\nexport const PREVIEW_INJECTION_KEY: InjectionKey<VuePreviewContext> =\n Symbol('typecms-preview')\n\nexport interface VuePreviewOptions extends PreviewConfig {\n /**\n * Draw an automatic highlight box over the `[data-preview-field]` element\n * matching the field focused in the editor. Pass options to customize.\n */\n overlay?: boolean | PreviewOverlayOptions\n}\n\n/**\n * Create a preview context. Used by the TypecmsPreview plugin;\n * exposed for advanced setups (e.g. Nuxt plugins, manual provide()).\n */\nexport function createPreviewContext(options: VuePreviewOptions = {}): VuePreviewContext {\n const { overlay, ...config } = options\n const client = createPreviewClient(config)\n // shallowRef, deliberately: the client replaces state wholesale on every\n // message (never mutates in place), so deep reactivity adds nothing — and\n // its proxies would break the object-identity guarantee that\n // usePreviewRichText's structural sharing relies on (a reactive proxy of a\n // node is never `===` the raw node the editor sent).\n const state = shallowRef<PreviewState>(client.getState())\n\n let unsubscribe: (() => void) | null = null\n let detachOverlay: (() => void) | null = null\n\n function connect() {\n if (unsubscribe) return\n unsubscribe = client.subscribe((next) => {\n state.value = next\n })\n client.connect()\n if (overlay) {\n detachOverlay = attachPreviewOverlay(client, overlay === true ? {} : overlay)\n }\n }\n\n function disconnect() {\n unsubscribe?.()\n unsubscribe = null\n detachOverlay?.()\n detachOverlay = null\n client.disconnect()\n state.value = client.getState()\n }\n\n const isPreview = computed(() => state.value.enabled)\n\n function mergeContent<T extends Record<string, unknown>>(content: T): T {\n if (!state.value.enabled || Object.keys(state.value.values).length === 0) {\n return content\n }\n return deepMerge(content, state.value.values as Record<string, unknown>) as T\n }\n\n return {\n state,\n isPreview,\n getValue: <T>(path: string) => client.getValue<T>(path),\n mergeContent,\n notifyNavigation: (pathname: string) => client.notifyNavigation(pathname),\n notifyFieldFocus: (path: string) => client.notifyFieldFocus(path),\n connect,\n disconnect,\n }\n}\n\n// =============================================================================\n// Plugin\n// =============================================================================\n\n/**\n * Vue plugin for Type CMS live preview.\n *\n * Installs a preview context app-wide and starts listening for\n * editor messages immediately.\n */\nexport const TypecmsPreview: Plugin<[VuePreviewOptions?]> = {\n install(app: App, options: VuePreviewOptions = {}) {\n const context = createPreviewContext(options)\n context.connect()\n app.provide(PREVIEW_INJECTION_KEY, context)\n\n const originalUnmount = app.unmount\n app.unmount = function unmount(...args: Parameters<App['unmount']>) {\n context.disconnect()\n return originalUnmount.apply(this, args)\n }\n },\n}\n\n// =============================================================================\n// Composables\n// =============================================================================\n\n/**\n * Access the preview context.\n *\n * @throws Error if the TypecmsPreview plugin is not installed\n */\nexport function usePreviewContext(): VuePreviewContext {\n const context = inject(PREVIEW_INJECTION_KEY, null)\n if (!context) {\n throw new Error('usePreviewContext requires the TypecmsPreview plugin to be installed')\n }\n return context\n}\n\n/** Check if running in preview mode. */\nexport function useIsPreview(): ComputedRef<boolean> {\n const context = inject(PREVIEW_INJECTION_KEY, null)\n return computed(() => context?.state.value.enabled ?? false)\n}\n\n/** Get current preview state (null when the plugin is not installed). */\nexport function usePreviewState(): ComputedRef<PreviewState | null> {\n const context = inject(PREVIEW_INJECTION_KEY, null)\n return computed(() => context?.state.value ?? null)\n}\n\n/**\n * Get a preview value by field path, with fallback to the published value.\n */\nexport function usePreviewValue<T>(\n path: MaybeRefOrGetter<string>,\n fallback: MaybeRefOrGetter<T>,\n): ComputedRef<T> {\n const context = inject(PREVIEW_INJECTION_KEY, null)\n\n return computed(() => {\n const fallbackValue = toValue(fallback)\n if (!context || !context.state.value.enabled) {\n return fallbackValue\n }\n const previewValue = getNestedValue(context.state.value.values, toValue(path)) as T | undefined\n return previewValue !== undefined ? previewValue : fallbackValue\n })\n}\n\n/**\n * Merge published content with preview overrides.\n *\n * The primary composable for integrating preview data: pass fetched\n * content and get back a reactive merged version.\n */\nexport function usePreviewContent<T extends Record<string, unknown>>(\n content: MaybeRefOrGetter<T>,\n): ComputedRef<T> {\n const context = inject(PREVIEW_INJECTION_KEY, null)\n\n return computed(() => {\n const value = toValue(content)\n if (!context || !context.state.value.enabled) {\n return value\n }\n return context.mergeContent(value)\n })\n}\n\n/** Get the currently focused field path (if any). */\nexport function useFocusedField(): ComputedRef<string | null> {\n const context = inject(PREVIEW_INJECTION_KEY, null)\n return computed(() => context?.state.value.focusedPath ?? null)\n}\n\n/** Function to notify the editor of navigation. */\nexport function usePreviewNavigation(): (pathname: string) => void {\n const context = inject(PREVIEW_INJECTION_KEY, null)\n return context ? context.notifyNavigation : () => {}\n}\n\n/** Function to notify the editor when a preview field is clicked. */\nexport function usePreviewFieldFocus(): (path: string) => void {\n const context = inject(PREVIEW_INJECTION_KEY, null)\n return context ? context.notifyFieldFocus : () => {}\n}\n\n/**\n * Get a rich-text preview value by field path with STRUCTURAL SHARING.\n *\n * Like `usePreviewValue`, but pipes the document through a persistent\n * structural-sharing instance: subtrees that are deep-equal to the previous\n * update keep their object references, so keyed/memoized node renderers only\n * re-render nodes that actually changed — instead of re-rendering the whole\n * rich-text tree on every keystroke.\n *\n * Works for both TipTap ('richtextv2') and Slate ('richtext') documents —\n * or any JSON value. Returns the fallback (unshared) when preview is\n * disabled. Sharing memory resets when the path value changes.\n */\nexport function usePreviewRichText<T>(\n path: MaybeRefOrGetter<string>,\n fallback: MaybeRefOrGetter<T>,\n): ComputedRef<T> {\n const context = inject(PREVIEW_INJECTION_KEY, null)\n const preview = createRichTextPreview()\n let lastPath: string | null = null\n\n return computed(() => {\n const pathValue = toValue(path)\n if (lastPath !== null && lastPath !== pathValue) {\n preview.reset()\n }\n lastPath = pathValue\n\n const fallbackValue = toValue(fallback)\n if (!context || !context.state.value.enabled) {\n return fallbackValue\n }\n const previewValue = getNestedValue(context.state.value.values, pathValue) as T | undefined\n return preview.apply(previewValue !== undefined ? previewValue : fallbackValue)\n })\n}\n\n","/**\n * Preview Client\n *\n * Core client for handling live preview PostMessage communication.\n * This runs on the preview site (your frontend) inside an iframe.\n */\n\nimport type {\n PreviewConfig,\n PreviewState,\n PreviewMessage,\n PreviewUpdateMessage,\n PreviewInitMessage,\n PreviewReadyMessage,\n PreviewFocusMessage,\n} from './types'\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULT_ALLOWED_ORIGINS = [\n 'https://app.typecms.com',\n 'https://staging.typecms.com',\n 'http://localhost:3000',\n 'http://localhost:3001',\n]\n\n// =============================================================================\n// Preview Client\n// =============================================================================\n\nexport type PreviewListener = (state: PreviewState) => void\n\n/**\n * Create a preview client instance.\n *\n * This handles PostMessage communication between the Type CMS editor\n * and your preview site. The client runs in the preview iframe.\n *\n * @example\n * ```typescript\n * import { createPreviewClient } from '@typecms/sdk/preview'\n *\n * const preview = createPreviewClient({\n * debug: true,\n * onUpdate: (data) => {\n * console.log('Value changed:', data.path, data.value)\n * },\n * })\n *\n * // Start listening for messages\n * preview.connect()\n *\n * // Get current preview state\n * const state = preview.getState()\n *\n * // Cleanup when done\n * preview.disconnect()\n * ```\n */\nexport function createPreviewClient(config: PreviewConfig = {}) {\n const {\n allowedOrigins = DEFAULT_ALLOWED_ORIGINS,\n onInit,\n onUpdate,\n onNavigate,\n onFocus,\n debug = false,\n } = config\n\n // Internal state\n let state: PreviewState = {\n enabled: false,\n entryId: null,\n projectId: null,\n locale: null,\n typeId: null,\n values: {},\n focusedPath: null,\n }\n\n const listeners = new Set<PreviewListener>()\n let isConnected = false\n let parentOrigin: string | null = null\n\n // ==========================================================================\n // Internal Helpers\n // ==========================================================================\n\n function log(...args: unknown[]) {\n if (debug) {\n console.log('[TypeCMS Preview]', ...args)\n }\n }\n\n function setState(updates: Partial<PreviewState>) {\n state = { ...state, ...updates }\n listeners.forEach((listener) => listener(state))\n }\n\n function isAllowedOrigin(origin: string): boolean {\n return allowedOrigins.some((allowed) => {\n if (allowed === '*') return true\n // A trailing \"/*\" historically read as \"this origin\". Origins have no\n // path, so a startsWith() prefix match is unsafe — \"https://app.x.com/*\"\n // would also match the hostile origin \"https://app.x.com.evil.com\".\n // Treat \"/*\" as the exact origin (strip it) and always compare for\n // equality.\n const expected = allowed.endsWith('/*') ? allowed.slice(0, -2) : allowed\n return origin === expected\n })\n }\n\n function sendMessage(message: PreviewMessage) {\n if (!parentOrigin) {\n log('Cannot send message: no parent origin')\n return\n }\n\n if (typeof window === 'undefined' || !window.parent) {\n log('Cannot send message: not in iframe')\n return\n }\n\n log('Sending message:', message.type)\n window.parent.postMessage(message, parentOrigin)\n }\n\n // ==========================================================================\n // Message Handler\n // ==========================================================================\n\n function handleMessage(event: MessageEvent) {\n // Validate origin\n if (!isAllowedOrigin(event.origin)) {\n log('Ignored message from untrusted origin:', event.origin)\n return\n }\n\n // Validate message structure\n const message = event.data as PreviewMessage\n if (!message?.type?.startsWith('typecms:preview:')) {\n return\n }\n\n log('Received message:', message.type, message)\n parentOrigin = event.origin\n\n switch (message.type) {\n case 'typecms:preview:init': {\n const initMsg = message as PreviewInitMessage\n setState({\n enabled: true,\n entryId: initMsg.payload.entryId,\n projectId: initMsg.payload.projectId,\n locale: initMsg.payload.locale,\n typeId: initMsg.payload.typeId,\n values: {},\n focusedPath: null,\n })\n onInit?.(initMsg.payload)\n\n // Send ready response\n const readyMessage: PreviewReadyMessage = {\n type: 'typecms:preview:ready',\n timestamp: Date.now(),\n payload: {\n pathname: typeof window !== 'undefined' ? window.location.pathname : '/',\n },\n }\n sendMessage(readyMessage)\n break\n }\n\n case 'typecms:preview:update': {\n const updateMsg = message as PreviewUpdateMessage\n\n // If full values provided, replace entirely\n if (updateMsg.payload.values) {\n setState({ values: updateMsg.payload.values })\n } else {\n // Otherwise, merge the single path update\n const newValues = { ...state.values }\n setNestedValue(newValues, updateMsg.payload.path, updateMsg.payload.value)\n setState({ values: newValues })\n }\n\n onUpdate?.(updateMsg.payload)\n break\n }\n\n case 'typecms:preview:navigate': {\n onNavigate?.(message.payload.url)\n break\n }\n\n case 'typecms:preview:focus': {\n setState({ focusedPath: message.payload.path })\n onFocus?.(message.payload.path)\n break\n }\n }\n }\n\n // ==========================================================================\n // Public API\n // ==========================================================================\n\n return {\n /**\n * Start listening for preview messages.\n * Call this on mount.\n */\n connect() {\n if (typeof window === 'undefined') {\n log('Cannot connect: not in browser')\n return\n }\n\n if (isConnected) {\n log('Already connected')\n return\n }\n\n log('Connecting...')\n window.addEventListener('message', handleMessage)\n isConnected = true\n\n // Announce readiness to the embedding editor. The editor waits for a\n // `ready` before sending `init`, and we don't yet know its origin\n // (parentOrigin is only learned from an incoming message) — so without\n // this hello the handshake would deadlock. The payload is just our\n // pathname (non-sensitive), so it is safe to offer it to every\n // configured candidate origin; the browser delivers it only to the one\n // that actually matches the parent.\n if (window.parent && window.parent !== (window as unknown as Window)) {\n const hello: PreviewReadyMessage = {\n type: 'typecms:preview:ready',\n timestamp: Date.now(),\n payload: { pathname: window.location?.pathname ?? '/' },\n }\n for (const allowed of allowedOrigins) {\n const target = allowed === '*'\n ? '*'\n : allowed.endsWith('/*') ? allowed.slice(0, -2) : allowed\n try {\n window.parent.postMessage(hello, target)\n } catch {\n // Invalid target origin string — skip this candidate.\n }\n }\n }\n },\n\n /**\n * Stop listening for preview messages.\n * Call this on unmount.\n */\n disconnect() {\n if (typeof window === 'undefined') return\n\n log('Disconnecting...')\n window.removeEventListener('message', handleMessage)\n isConnected = false\n parentOrigin = null\n setState({\n enabled: false,\n entryId: null,\n projectId: null,\n locale: null,\n typeId: null,\n values: {},\n focusedPath: null,\n })\n },\n\n /**\n * Get current preview state.\n */\n getState(): PreviewState {\n return state\n },\n\n /**\n * Check if preview mode is active.\n */\n isEnabled(): boolean {\n return state.enabled\n },\n\n /**\n * Get a preview value by path.\n * Returns undefined if no preview value exists.\n */\n getValue<T = unknown>(path: string): T | undefined {\n return getNestedValue(state.values, path) as T | undefined\n },\n\n /**\n * Subscribe to state changes.\n */\n subscribe(listener: PreviewListener): () => void {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n /**\n * Notify the editor that the preview has navigated.\n */\n notifyNavigation(pathname: string) {\n const message: PreviewReadyMessage = {\n type: 'typecms:preview:ready',\n timestamp: Date.now(),\n payload: { pathname },\n }\n sendMessage(message)\n },\n\n /**\n * Notify the editor that a field was clicked in the preview.\n * The editor will scroll to the corresponding field.\n *\n * @example\n * ```typescript\n * // On click of an element with data-preview-field attribute:\n * const path = el.closest('[data-preview-field]')?.dataset.previewField\n * if (path) preview.notifyFieldFocus(path)\n * ```\n */\n notifyFieldFocus(path: string) {\n const message: PreviewFocusMessage = {\n type: 'typecms:preview:focus',\n timestamp: Date.now(),\n payload: { path },\n }\n sendMessage(message)\n },\n }\n}\n\n// =============================================================================\n// Utility Functions\n// =============================================================================\n\n/**\n * Set a nested value by path (e.g., \"content.title\")\n */\nconst UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype'])\n\nfunction setNestedValue(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.')\n let current: Record<string, unknown> = obj\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]\n // Handle array notation like \"items[0]\"\n const arrayMatch = part.match(/^(\\w+)\\[(\\d+)\\]$/)\n\n if (arrayMatch) {\n const [, name, indexStr] = arrayMatch\n const index = parseInt(indexStr, 10)\n if (UNSAFE_KEYS.has(name)) continue\n if (!current[name]) {\n current[name] = []\n }\n const arr = current[name] as unknown[]\n if (!arr[index]) {\n arr[index] = {}\n }\n current = arr[index] as Record<string, unknown>\n } else {\n if (UNSAFE_KEYS.has(part)) continue\n if (!current[part] || typeof current[part] !== 'object') {\n current[part] = {}\n }\n current = current[part] as Record<string, unknown>\n }\n }\n\n const lastPart = parts[parts.length - 1]\n const arrayMatch = lastPart.match(/^(\\w+)\\[(\\d+)\\]$/)\n\n if (arrayMatch) {\n const [, name, indexStr] = arrayMatch\n const index = parseInt(indexStr, 10)\n if (UNSAFE_KEYS.has(name)) return\n if (!current[name]) {\n current[name] = []\n }\n (current[name] as unknown[])[index] = value\n } else {\n if (UNSAFE_KEYS.has(lastPart)) return\n current[lastPart] = value\n }\n}\n\n/**\n * Get a nested value by path (supports \"items[0].name\" array notation)\n */\nexport function getNestedValue(obj: Record<string, unknown>, path: string): unknown {\n const parts = path.split('.')\n let current: unknown = obj\n\n for (const part of parts) {\n if (current === null || current === undefined) {\n return undefined\n }\n\n // Handle array notation like \"items[0]\"\n const arrayMatch = part.match(/^(\\w+)\\[(\\d+)\\]$/)\n\n if (arrayMatch) {\n const [, name, indexStr] = arrayMatch\n const index = parseInt(indexStr, 10)\n const arr = (current as Record<string, unknown>)[name] as unknown[] | undefined\n if (!arr) return undefined\n current = arr[index]\n } else {\n current = (current as Record<string, unknown>)[part]\n }\n }\n\n return current\n}\n\n/**\n * Deep merge two objects\n */\nexport function deepMerge<T extends Record<string, unknown>>(\n target: T,\n source: Record<string, unknown>\n): T {\n const result = { ...target }\n\n for (const key of Object.keys(source)) {\n const sourceValue = source[key]\n const targetValue = result[key as keyof T]\n\n if (\n sourceValue &&\n typeof sourceValue === 'object' &&\n !Array.isArray(sourceValue) &&\n targetValue &&\n typeof targetValue === 'object' &&\n !Array.isArray(targetValue)\n ) {\n result[key as keyof T] = deepMerge(\n targetValue as Record<string, unknown>,\n sourceValue as Record<string, unknown>\n ) as T[keyof T]\n } else {\n result[key as keyof T] = sourceValue as T[keyof T]\n }\n }\n\n return result\n}\n","/**\n * Preview Overlay\n *\n * Draws a highlight box over the element whose `data-preview-field`\n * matches the field currently focused in the Type CMS editor.\n * Zero-markup alternative to hand-rolling highlights with useFocusedField().\n */\n\nimport type { PreviewState } from './types'\n\nexport interface PreviewOverlayOptions {\n /** Outline color of the highlight box */\n color?: string\n /** z-index of the highlight box */\n zIndex?: number\n /** Scroll the highlighted element into view when focus changes */\n scrollIntoView?: boolean\n /** Attribute used to locate field elements */\n attribute?: string\n}\n\ninterface OverlayClient {\n subscribe(listener: (state: PreviewState) => void): () => void\n getState(): PreviewState\n}\n\nconst DEFAULT_OPTIONS: Required<PreviewOverlayOptions> = {\n color: '#3b82f6',\n zIndex: 9999,\n scrollIntoView: true,\n attribute: 'data-preview-field',\n}\n\n/**\n * Attach a focus-highlight overlay to the document.\n *\n * Subscribes to the preview client's state; whenever `focusedPath` changes,\n * positions a non-interactive highlight box over the matching\n * `[data-preview-field]` element. Returns a detach function.\n *\n * @example\n * ```typescript\n * const preview = createPreviewClient()\n * preview.connect()\n * const detach = attachPreviewOverlay(preview)\n * // ...on teardown\n * detach()\n * ```\n */\nexport function attachPreviewOverlay(\n client: OverlayClient,\n options: PreviewOverlayOptions = {},\n): () => void {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return () => {}\n }\n\n const { color, zIndex, scrollIntoView, attribute } = { ...DEFAULT_OPTIONS, ...options }\n\n const box = document.createElement('div')\n box.setAttribute('data-typecms-preview-overlay', '')\n Object.assign(box.style, {\n position: 'fixed',\n display: 'none',\n pointerEvents: 'none',\n boxSizing: 'border-box',\n border: `2px solid ${color}`,\n borderRadius: '4px',\n zIndex: String(zIndex),\n transition: 'top 120ms ease, left 120ms ease, width 120ms ease, height 120ms ease',\n })\n document.body.appendChild(box)\n\n let target: Element | null = null\n let lastPath: string | null = null\n\n function position() {\n if (!target || !target.isConnected) {\n box.style.display = 'none'\n return\n }\n const rect = target.getBoundingClientRect()\n Object.assign(box.style, {\n display: 'block',\n top: `${rect.top - 2}px`,\n left: `${rect.left - 2}px`,\n width: `${rect.width + 4}px`,\n height: `${rect.height + 4}px`,\n })\n }\n\n function update(state: PreviewState) {\n const path = state.enabled ? state.focusedPath : null\n if (path === lastPath) return\n lastPath = path\n\n target = path\n ? document.querySelector(`[${attribute}=\"${CSS.escape(path)}\"]`)\n : null\n\n if (target && scrollIntoView) {\n target.scrollIntoView({ behavior: 'smooth', block: 'center' })\n }\n position()\n }\n\n // Capture-phase scroll listener catches scrolling in nested containers\n window.addEventListener('scroll', position, true)\n window.addEventListener('resize', position)\n\n const unsubscribe = client.subscribe(update)\n update(client.getState())\n\n return () => {\n unsubscribe()\n window.removeEventListener('scroll', position, true)\n window.removeEventListener('resize', position)\n box.remove()\n target = null\n }\n}\n","/**\n * Rich-Text Live Preview Helpers\n *\n * Rich-text fields ('richtextv2' → TipTap, 'richtext' → Slate) store large\n * JSON documents. During live preview every keystroke sends the WHOLE new\n * document, and naive consumers re-render the entire rich-text tree — losing\n * performance and, in editable embeds, cursor state.\n *\n * These helpers apply STRUCTURAL SHARING: when a new document arrives, any\n * subtree that is deep-equal to the previous document's subtree at the same\n * position is returned as the SAME reference from the previous document.\n * React/Vue memoization and keyed renderers then only re-render the nodes\n * that actually changed.\n *\n * This module is framework-agnostic and dependency-free. Documents are\n * assumed to be plain JSON data (no cycles, no functions, no Dates).\n *\n * @example\n * ```typescript\n * import { createRichTextPreview } from '@typecms/sdk/preview'\n *\n * const preview = createRichTextPreview()\n * const docA = preview.apply(incomingDocA)\n * const docB = preview.apply(incomingDocB)\n * // Unchanged paragraphs in docB are the SAME object references as in docA.\n * ```\n */\n\n// =============================================================================\n// Document Shapes\n// =============================================================================\n\n/**\n * A TipTap/ProseMirror node. Kept permissive on purpose — the editor may\n * attach arbitrary attrs/marks and custom node types.\n */\nexport interface TipTapNode {\n type: string\n content?: TipTapNode[]\n text?: string\n marks?: Array<{ type: string; attrs?: Record<string, unknown> }>\n attrs?: Record<string, unknown>\n [key: string]: unknown\n}\n\n/**\n * A TipTap document: `{ type: 'doc', content: [...] }`.\n * Produced by 'richtextv2' fields.\n */\nexport interface TipTapDoc extends TipTapNode {\n type: 'doc'\n content: TipTapNode[]\n}\n\n/**\n * A Slate node. Element nodes carry `children`; text leaves carry `text`\n * (plus arbitrary mark properties like `bold`). Kept permissive on purpose.\n */\nexport interface SlateNode {\n type?: string\n children?: SlateNode[]\n text?: string\n [key: string]: unknown\n}\n\n// =============================================================================\n// Type Guards\n// =============================================================================\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Check whether a value looks like a TipTap document\n * (`{ type: 'doc', content: [...] }`, produced by 'richtextv2' fields).\n *\n * Permissive: only the root shape is inspected, children are not validated.\n * Use it to decide which renderer to hand a preview value to.\n */\nexport function isTipTapDoc(value: unknown): value is TipTapDoc {\n return isPlainObject(value) && value.type === 'doc' && Array.isArray(value.content)\n}\n\n/**\n * Check whether a value looks like a Slate document — an array of nodes\n * where each node has `children` or is a text leaf with `text`\n * (produced by 'richtext' fields).\n *\n * Permissive: only one level is inspected, grandchildren are not validated.\n * An empty array is treated as a valid (empty) Slate fragment.\n */\nexport function isSlateDoc(value: unknown): value is SlateNode[] {\n return (\n Array.isArray(value) &&\n value.every((node) => isPlainObject(node) && ('children' in node || 'text' in node))\n )\n}\n\n// =============================================================================\n// Structural Sharing\n// =============================================================================\n\n/**\n * Structurally share `next` against `prev`.\n *\n * Returns `prev` itself when the two are deep-equal. Otherwise returns a copy\n * of `next` in which every child object/array is replaced by the structurally\n * shared version against the corresponding `prev` child.\n *\n * Guarantee: any subtree of the result that is deep-equal to the previous\n * document's subtree at the same position is the SAME reference from `prev`,\n * so memoized/keyed renderers skip it.\n *\n * Arrays are aligned from the tail first, then by index from the head, so a\n * mid-array insert or delete (the common rich-text edit) still preserves\n * reference equality for the deep-equal siblings around the edit point.\n *\n * Handles arrays, plain objects, primitives, `null`, and mismatched types\n * (mismatches simply return `next`). Input is assumed to be acyclic JSON.\n */\nexport function shareStructure<T>(prev: T | undefined, next: T): T {\n if (prev === next || prev === undefined) {\n return next\n }\n\n if (Array.isArray(prev) && Array.isArray(next)) {\n return shareArray(prev, next) as T\n }\n\n if (isPlainObject(prev) && isPlainObject(next)) {\n return shareObject(prev, next) as T\n }\n\n // Primitives, null, or mismatched types — nothing to share.\n return next\n}\n\nfunction shareArray(prev: unknown[], next: unknown[]): unknown[] {\n const prevLength = prev.length\n const nextLength = next.length\n const result: unknown[] = new Array(nextLength)\n\n // Match deep-equal elements from the tail first so mid-array inserts and\n // deletes don't break sharing for everything after the edit point.\n let tail = 0\n const maxTail = Math.min(prevLength, nextLength)\n while (tail < maxTail) {\n const prevItem = prev[prevLength - 1 - tail]\n const shared = shareStructure(prevItem, next[nextLength - 1 - tail])\n if (shared !== prevItem) break\n result[nextLength - 1 - tail] = shared\n tail++\n }\n\n // Align the remaining head region by index.\n const prevHeadLength = prevLength - tail\n for (let i = 0; i < nextLength - tail; i++) {\n result[i] = i < prevHeadLength ? shareStructure(prev[i], next[i]) : next[i]\n }\n\n // Fully deep-equal — hand back the previous array itself.\n if (prevLength === nextLength) {\n let allShared = true\n for (let i = 0; i < nextLength; i++) {\n if (result[i] !== prev[i]) {\n allShared = false\n break\n }\n }\n if (allShared) return prev\n }\n\n return result\n}\n\nfunction shareObject(\n prev: Record<string, unknown>,\n next: Record<string, unknown>\n): Record<string, unknown> {\n const nextKeys = Object.keys(next)\n const result: Record<string, unknown> = {}\n let allShared = nextKeys.length === Object.keys(prev).length\n\n for (const key of nextKeys) {\n const hasPrev = Object.prototype.hasOwnProperty.call(prev, key)\n const shared = hasPrev ? shareStructure(prev[key], next[key]) : next[key]\n result[key] = shared\n if (!hasPrev || shared !== prev[key]) {\n allShared = false\n }\n }\n\n // Fully deep-equal — hand back the previous object itself.\n return allShared ? prev : result\n}\n\n// =============================================================================\n// Stateful Wrapper\n// =============================================================================\n\nexport interface RichTextPreviewOptions {\n /**\n * Seed the \"previous document\" memory, e.g. with the published document\n * already rendered on the page, so the very first preview update can\n * share unchanged subtrees with it.\n */\n initial?: unknown\n}\n\nexport interface RichTextPreview {\n /**\n * Structurally share `nextDoc` against the last applied document and\n * remember the result for the next call.\n */\n apply<T>(nextDoc: T): T\n /** Forget the last document. The next apply() returns its input as-is. */\n reset(): void\n}\n\n/**\n * Create a small stateful structural-sharing instance for a single\n * rich-text field. Useful outside React/Vue (vanilla JS, Svelte stores,\n * custom render loops).\n *\n * @example\n * ```typescript\n * const preview = createRichTextPreview()\n *\n * client.subscribe((state) => {\n * const doc = preview.apply(state.values.body)\n * render(doc) // unchanged nodes keep their references\n * })\n * ```\n */\nexport function createRichTextPreview(options: RichTextPreviewOptions = {}): RichTextPreview {\n let lastDoc: unknown = options.initial\n\n return {\n apply<T>(nextDoc: T): T {\n const shared = shareStructure(lastDoc as T | undefined, nextDoc)\n lastDoc = shared\n return shared\n },\n\n reset() {\n lastDoc = undefined\n },\n }\n}\n"],"mappings":"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","names":["arrayMatch"]}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base types for Type CMS content.
|
|
3
|
+
* These types are used by the SDK and can be imported by generated type files.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Represents a file/media asset in Type CMS.
|
|
7
|
+
*/
|
|
8
|
+
interface Asset {
|
|
9
|
+
/** Unique identifier */
|
|
10
|
+
_id: string;
|
|
11
|
+
/** Public URL of the asset */
|
|
12
|
+
url: string;
|
|
13
|
+
/** Alt text for accessibility */
|
|
14
|
+
alt?: string;
|
|
15
|
+
/** Display title */
|
|
16
|
+
title?: string;
|
|
17
|
+
/** Original filename */
|
|
18
|
+
name?: string;
|
|
19
|
+
/** Width in pixels (for images) */
|
|
20
|
+
width?: number;
|
|
21
|
+
/** Height in pixels (for images) */
|
|
22
|
+
height?: number;
|
|
23
|
+
/** File size in bytes */
|
|
24
|
+
size?: number;
|
|
25
|
+
/** MIME type */
|
|
26
|
+
mimeType?: string;
|
|
27
|
+
/** File type category */
|
|
28
|
+
type?: AssetType;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Asset type categories
|
|
32
|
+
*/
|
|
33
|
+
interface AssetType {
|
|
34
|
+
category?: 'image' | 'video' | 'audio' | 'document' | 'archive' | 'other';
|
|
35
|
+
extension?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Represents a hyperlink.
|
|
39
|
+
*/
|
|
40
|
+
interface Link {
|
|
41
|
+
/** Link URL */
|
|
42
|
+
url: string;
|
|
43
|
+
/** Link display text */
|
|
44
|
+
label?: string;
|
|
45
|
+
/** Open in new tab */
|
|
46
|
+
newTab?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Represents a physical address.
|
|
50
|
+
*/
|
|
51
|
+
interface Address {
|
|
52
|
+
/** Street address line 1 */
|
|
53
|
+
street?: string;
|
|
54
|
+
/** Street address line 2 */
|
|
55
|
+
street2?: string;
|
|
56
|
+
/** City */
|
|
57
|
+
city?: string;
|
|
58
|
+
/** State/Province */
|
|
59
|
+
state?: string;
|
|
60
|
+
/** Postal/ZIP code */
|
|
61
|
+
zip?: string;
|
|
62
|
+
/** Country */
|
|
63
|
+
country?: string;
|
|
64
|
+
/** Latitude coordinate */
|
|
65
|
+
lat?: number;
|
|
66
|
+
/** Longitude coordinate */
|
|
67
|
+
lng?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Reference to another entry.
|
|
71
|
+
*/
|
|
72
|
+
interface EntryReference {
|
|
73
|
+
/** Entry ID */
|
|
74
|
+
_id: string;
|
|
75
|
+
/** Entry type slug */
|
|
76
|
+
_type: string;
|
|
77
|
+
/** Entry title */
|
|
78
|
+
title?: string;
|
|
79
|
+
/** Entry slug */
|
|
80
|
+
slug?: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Reference to a collection item.
|
|
84
|
+
*/
|
|
85
|
+
interface CollectionReference {
|
|
86
|
+
/** Item ID */
|
|
87
|
+
_id: string;
|
|
88
|
+
/** Collection ID */
|
|
89
|
+
collectionId: string;
|
|
90
|
+
/** Item title */
|
|
91
|
+
title?: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Slate.js rich text node (v1 rich text).
|
|
95
|
+
*/
|
|
96
|
+
interface RichTextNode {
|
|
97
|
+
type?: string;
|
|
98
|
+
children?: RichTextNode[];
|
|
99
|
+
text?: string;
|
|
100
|
+
bold?: boolean;
|
|
101
|
+
italic?: boolean;
|
|
102
|
+
underline?: boolean;
|
|
103
|
+
strikethrough?: boolean;
|
|
104
|
+
code?: boolean;
|
|
105
|
+
url?: string;
|
|
106
|
+
[key: string]: unknown;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* TipTap rich text node (v2 rich text).
|
|
110
|
+
*/
|
|
111
|
+
interface TipTapNode {
|
|
112
|
+
type: string;
|
|
113
|
+
content?: TipTapNode[];
|
|
114
|
+
text?: string;
|
|
115
|
+
marks?: TipTapMark[];
|
|
116
|
+
attrs?: Record<string, unknown>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* TipTap mark (formatting).
|
|
120
|
+
*/
|
|
121
|
+
interface TipTapMark {
|
|
122
|
+
type: string;
|
|
123
|
+
attrs?: Record<string, unknown>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Base fields present on all entries.
|
|
127
|
+
*/
|
|
128
|
+
interface BaseEntry {
|
|
129
|
+
/** Unique identifier */
|
|
130
|
+
_id: string;
|
|
131
|
+
/** Content type slug */
|
|
132
|
+
_type: string;
|
|
133
|
+
/** Creation timestamp (ISO string) */
|
|
134
|
+
_createdAt: string;
|
|
135
|
+
/** Last update timestamp (ISO string) */
|
|
136
|
+
_updatedAt: string;
|
|
137
|
+
/** Publication timestamp (ISO string, if published) */
|
|
138
|
+
_publishedAt?: string;
|
|
139
|
+
/** Locale code */
|
|
140
|
+
_locale?: string;
|
|
141
|
+
/** Version number */
|
|
142
|
+
_version?: number;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Base fields for canvas/component blocks.
|
|
146
|
+
*/
|
|
147
|
+
interface BaseComponent {
|
|
148
|
+
/** Component type identifier (discriminator) */
|
|
149
|
+
__typename: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Extract component type from a union by __typename.
|
|
153
|
+
*/
|
|
154
|
+
type ExtractComponent<TUnion extends BaseComponent, TTypename extends TUnion['__typename']> = Extract<TUnion, {
|
|
155
|
+
__typename: TTypename;
|
|
156
|
+
}>;
|
|
157
|
+
/**
|
|
158
|
+
* Type guard to narrow canvas component by __typename.
|
|
159
|
+
*/
|
|
160
|
+
declare function isComponentType<TComponent extends BaseComponent, TTypename extends TComponent['__typename']>(component: TComponent, typename: TTypename): component is Extract<TComponent, {
|
|
161
|
+
__typename: TTypename;
|
|
162
|
+
}>;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Client configuration, options, and response types for the Type CMS
|
|
166
|
+
* Content Delivery API (`/api/projects/:projectId/content`).
|
|
167
|
+
*/
|
|
168
|
+
/**
|
|
169
|
+
* Configuration options for the Type CMS client.
|
|
170
|
+
*/
|
|
171
|
+
interface TypeCMSClientConfig {
|
|
172
|
+
/** Project ID (visible in the platform URL) */
|
|
173
|
+
projectId: string;
|
|
174
|
+
/**
|
|
175
|
+
* API token (`token_...`) created under Project Settings → Tokens.
|
|
176
|
+
* The token is bound to an environment — content is always served from
|
|
177
|
+
* that environment. Use a preview-environment token to read drafts.
|
|
178
|
+
*/
|
|
179
|
+
token: string;
|
|
180
|
+
/** API origin. Defaults to the Type CMS cloud API. */
|
|
181
|
+
baseUrl?: string;
|
|
182
|
+
/** Default language for every call (falls back to the project default). */
|
|
183
|
+
language?: string;
|
|
184
|
+
/**
|
|
185
|
+
* Default audience variant slug for every call — the personalization
|
|
186
|
+
* "lens" this client reads content through. Per-call `variant` overrides.
|
|
187
|
+
*/
|
|
188
|
+
variant?: string;
|
|
189
|
+
/** Request timeout in milliseconds (default 30 000). */
|
|
190
|
+
timeout?: number;
|
|
191
|
+
/** Custom fetch implementation (testing, polyfills, per-request caching). */
|
|
192
|
+
fetch?: typeof fetch;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* A published (or draft) content row as served by the delivery API — one per
|
|
196
|
+
* (entry × language × variant × environment).
|
|
197
|
+
*
|
|
198
|
+
* `content` is the fully built, nested content object with entry and asset
|
|
199
|
+
* references hydrated in place. `typeMap` maps dot-notation field paths to
|
|
200
|
+
* Type CMS field types (`"hero.image"` → `"file"`).
|
|
201
|
+
*/
|
|
202
|
+
interface ContentEntry<T = Record<string, unknown>> {
|
|
203
|
+
entryId: string;
|
|
204
|
+
title: string;
|
|
205
|
+
slug: string;
|
|
206
|
+
path: string;
|
|
207
|
+
/** Localized path aliases keyed by language */
|
|
208
|
+
pathAliases?: Record<string, string>;
|
|
209
|
+
language: string;
|
|
210
|
+
/** Variant key — null when this row is the base (non-personalized) content */
|
|
211
|
+
variantId: string | null;
|
|
212
|
+
/** Audience variant slug — null for base content */
|
|
213
|
+
variantSlug: string | null;
|
|
214
|
+
content: T;
|
|
215
|
+
/** Dot-notation field path → Type CMS field type */
|
|
216
|
+
typeMap: Record<string, string>;
|
|
217
|
+
description?: string;
|
|
218
|
+
tags?: string[];
|
|
219
|
+
templateId?: string;
|
|
220
|
+
typeId?: string;
|
|
221
|
+
publishedAt?: string | number;
|
|
222
|
+
publishedBy?: string;
|
|
223
|
+
/** Present and true when served by the draft path (`draft: true`) */
|
|
224
|
+
draft?: boolean;
|
|
225
|
+
[key: string]: unknown;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Sort expression: a field name for ascending, `-` prefix for descending.
|
|
229
|
+
*
|
|
230
|
+
* @example 'publishedAt' // ascending
|
|
231
|
+
* @example '-publishedAt' // descending
|
|
232
|
+
*/
|
|
233
|
+
type SortExpression = string;
|
|
234
|
+
/** Options shared by every content read. */
|
|
235
|
+
interface SharedReadOptions {
|
|
236
|
+
/** Language to read (defaults to the client's `language`, then the project default) */
|
|
237
|
+
language?: string;
|
|
238
|
+
/**
|
|
239
|
+
* Audience variant slug for personalization. Overrides the client default;
|
|
240
|
+
* pass `null` to force base content even when a client default is set.
|
|
241
|
+
*/
|
|
242
|
+
variant?: string | null;
|
|
243
|
+
/**
|
|
244
|
+
* When the requested variant has no published row, silently serve the base
|
|
245
|
+
* content instead (default true). The served row's `variantSlug` tells you
|
|
246
|
+
* which one you got. Set false to get `null`/empty instead.
|
|
247
|
+
*/
|
|
248
|
+
variantFallback?: boolean;
|
|
249
|
+
/**
|
|
250
|
+
* Read draft (unpublished) content instead of published rows. Requires a
|
|
251
|
+
* token bound to a preview environment; responses are never CDN-cached.
|
|
252
|
+
*/
|
|
253
|
+
draft?: boolean;
|
|
254
|
+
/** Convert richtext fields to HTML strings server-side ('json' by default). */
|
|
255
|
+
format?: 'json' | 'html';
|
|
256
|
+
}
|
|
257
|
+
/** Options for fetching a single entry. Provide exactly one selector. */
|
|
258
|
+
interface GetEntryOptions extends SharedReadOptions {
|
|
259
|
+
/** Look up by URL path (matches localized `pathAliases` too), e.g. '/about' */
|
|
260
|
+
path?: string;
|
|
261
|
+
/** Look up by slug */
|
|
262
|
+
slug?: string;
|
|
263
|
+
/** Look up by entry id */
|
|
264
|
+
entryId?: string;
|
|
265
|
+
}
|
|
266
|
+
/** Options for fetching a list of entries. */
|
|
267
|
+
interface GetEntriesOptions extends SharedReadOptions {
|
|
268
|
+
/** Restrict to a template (content type) id */
|
|
269
|
+
templateId?: string;
|
|
270
|
+
/** Extra equality filters on row fields (e.g. `{ tags: 'featured' }`) */
|
|
271
|
+
filter?: Record<string, unknown>;
|
|
272
|
+
/** Max rows to return (API caps at 100) */
|
|
273
|
+
limit?: number;
|
|
274
|
+
/** Rows to skip (pagination) */
|
|
275
|
+
skip?: number;
|
|
276
|
+
/** Sort: `'publishedAt'` ascending, `'-publishedAt'` descending */
|
|
277
|
+
sort?: SortExpression;
|
|
278
|
+
}
|
|
279
|
+
/** A page of entries. */
|
|
280
|
+
interface EntryList<T = Record<string, unknown>> {
|
|
281
|
+
entries: ContentEntry<T>[];
|
|
282
|
+
count: number;
|
|
283
|
+
skip: number;
|
|
284
|
+
limit: number;
|
|
285
|
+
}
|
|
286
|
+
/** Options for full-text search over published content. */
|
|
287
|
+
interface SearchOptions {
|
|
288
|
+
/** Language to search (defaults like other reads) */
|
|
289
|
+
language?: string;
|
|
290
|
+
/** Row fields to match against (default `['title', 'search']`) */
|
|
291
|
+
fields?: string[];
|
|
292
|
+
/** Max rows (API caps at 100) */
|
|
293
|
+
limit?: number;
|
|
294
|
+
/** Rows to skip */
|
|
295
|
+
skip?: number;
|
|
296
|
+
/** Sort: `'publishedAt'` / `'-publishedAt'` style (default newest first) */
|
|
297
|
+
sort?: SortExpression;
|
|
298
|
+
/** Extra filters: `publishedAt: [from, to]`, `publishedBy`, equality/$in fields */
|
|
299
|
+
filters?: Record<string, unknown>;
|
|
300
|
+
}
|
|
301
|
+
/** A search hit — a projected content row (title, path info, publish meta). */
|
|
302
|
+
interface SearchHit {
|
|
303
|
+
title?: string;
|
|
304
|
+
directoryPath?: string;
|
|
305
|
+
publishedAt?: string | number;
|
|
306
|
+
publishedBy?: string;
|
|
307
|
+
language?: string;
|
|
308
|
+
/** 'promoted' when a search curation pinned this hit */
|
|
309
|
+
curated?: string;
|
|
310
|
+
[key: string]: unknown;
|
|
311
|
+
}
|
|
312
|
+
/** A page of search hits. */
|
|
313
|
+
interface SearchResult {
|
|
314
|
+
entries: SearchHit[];
|
|
315
|
+
count: number;
|
|
316
|
+
skip: number;
|
|
317
|
+
limit: number;
|
|
318
|
+
}
|
|
319
|
+
/** Error payload shape returned by the API. */
|
|
320
|
+
interface APIError {
|
|
321
|
+
message: string;
|
|
322
|
+
errors?: unknown[];
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Typed Type CMS content client.
|
|
326
|
+
*
|
|
327
|
+
* Pass a per-call generic to type the `content` payload:
|
|
328
|
+
* `client.getEntry<HomePage>({ path: '/' })`.
|
|
329
|
+
*/
|
|
330
|
+
interface TypeCMSClient {
|
|
331
|
+
/**
|
|
332
|
+
* Fetch a single entry by path, slug, or id. Returns `null` when nothing
|
|
333
|
+
* is published for the selector (no exception on 404).
|
|
334
|
+
*/
|
|
335
|
+
getEntry<T = Record<string, unknown>>(options: GetEntryOptions): Promise<ContentEntry<T> | null>;
|
|
336
|
+
/**
|
|
337
|
+
* Fetch a page of entries. Returns an empty list when nothing matches.
|
|
338
|
+
*/
|
|
339
|
+
getEntries<T = Record<string, unknown>>(options?: GetEntriesOptions): Promise<EntryList<T>>;
|
|
340
|
+
/**
|
|
341
|
+
* Full-text search over published content.
|
|
342
|
+
*/
|
|
343
|
+
search(query: string, options?: SearchOptions): Promise<SearchResult>;
|
|
344
|
+
/**
|
|
345
|
+
* Low-level escape hatch: POST an arbitrary query body to the content
|
|
346
|
+
* endpoint and get the raw API response back. For everything the typed
|
|
347
|
+
* helpers don't cover.
|
|
348
|
+
*/
|
|
349
|
+
query<T = Record<string, unknown>>(body: Record<string, unknown>, params?: {
|
|
350
|
+
skip?: number;
|
|
351
|
+
limit?: number;
|
|
352
|
+
sort?: SortExpression;
|
|
353
|
+
format?: 'json' | 'html';
|
|
354
|
+
draft?: boolean;
|
|
355
|
+
}): Promise<EntryList<T>>;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export { type APIError, type Address, type Asset, type AssetType, type BaseComponent, type BaseEntry, type CollectionReference, type ContentEntry, type EntryList, type EntryReference, type ExtractComponent, type GetEntriesOptions, type GetEntryOptions, type Link, type RichTextNode, type SearchHit, type SearchOptions, type SearchResult, type SortExpression, type TipTapMark, type TipTapNode, type TypeCMSClient, type TypeCMSClientConfig, isComponentType };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/types/base.ts"],"sourcesContent":["/**\n * Base types for Type CMS content.\n * These types are used by the SDK and can be imported by generated type files.\n */\n\n// =============================================================================\n// Asset Types\n// =============================================================================\n\n/**\n * Represents a file/media asset in Type CMS.\n */\nexport interface Asset {\n /** Unique identifier */\n _id: string\n /** Public URL of the asset */\n url: string\n /** Alt text for accessibility */\n alt?: string\n /** Display title */\n title?: string\n /** Original filename */\n name?: string\n /** Width in pixels (for images) */\n width?: number\n /** Height in pixels (for images) */\n height?: number\n /** File size in bytes */\n size?: number\n /** MIME type */\n mimeType?: string\n /** File type category */\n type?: AssetType\n}\n\n/**\n * Asset type categories\n */\nexport interface AssetType {\n category?: 'image' | 'video' | 'audio' | 'document' | 'archive' | 'other'\n extension?: string\n}\n\n// =============================================================================\n// Link Types\n// =============================================================================\n\n/**\n * Represents a hyperlink.\n */\nexport interface Link {\n /** Link URL */\n url: string\n /** Link display text */\n label?: string\n /** Open in new tab */\n newTab?: boolean\n}\n\n// =============================================================================\n// Address Types\n// =============================================================================\n\n/**\n * Represents a physical address.\n */\nexport interface Address {\n /** Street address line 1 */\n street?: string\n /** Street address line 2 */\n street2?: string\n /** City */\n city?: string\n /** State/Province */\n state?: string\n /** Postal/ZIP code */\n zip?: string\n /** Country */\n country?: string\n /** Latitude coordinate */\n lat?: number\n /** Longitude coordinate */\n lng?: number\n}\n\n// =============================================================================\n// Reference Types\n// =============================================================================\n\n/**\n * Reference to another entry.\n */\nexport interface EntryReference {\n /** Entry ID */\n _id: string\n /** Entry type slug */\n _type: string\n /** Entry title */\n title?: string\n /** Entry slug */\n slug?: string\n}\n\n/**\n * Reference to a collection item.\n */\nexport interface CollectionReference {\n /** Item ID */\n _id: string\n /** Collection ID */\n collectionId: string\n /** Item title */\n title?: string\n}\n\n// =============================================================================\n// Rich Text Types\n// =============================================================================\n\n/**\n * Slate.js rich text node (v1 rich text).\n */\nexport interface RichTextNode {\n type?: string\n children?: RichTextNode[]\n text?: string\n bold?: boolean\n italic?: boolean\n underline?: boolean\n strikethrough?: boolean\n code?: boolean\n url?: string\n [key: string]: unknown\n}\n\n/**\n * TipTap rich text node (v2 rich text).\n */\nexport interface TipTapNode {\n type: string\n content?: TipTapNode[]\n text?: string\n marks?: TipTapMark[]\n attrs?: Record<string, unknown>\n}\n\n/**\n * TipTap mark (formatting).\n */\nexport interface TipTapMark {\n type: string\n attrs?: Record<string, unknown>\n}\n\n// =============================================================================\n// Base Entry Types\n// =============================================================================\n\n/**\n * Base fields present on all entries.\n */\nexport interface BaseEntry {\n /** Unique identifier */\n _id: string\n /** Content type slug */\n _type: string\n /** Creation timestamp (ISO string) */\n _createdAt: string\n /** Last update timestamp (ISO string) */\n _updatedAt: string\n /** Publication timestamp (ISO string, if published) */\n _publishedAt?: string\n /** Locale code */\n _locale?: string\n /** Version number */\n _version?: number\n}\n\n/**\n * Base fields for canvas/component blocks.\n */\nexport interface BaseComponent {\n /** Component type identifier (discriminator) */\n __typename: string\n}\n\n// =============================================================================\n// Utility Types\n// =============================================================================\n\n/**\n * Extract component type from a union by __typename.\n */\nexport type ExtractComponent<\n TUnion extends BaseComponent,\n TTypename extends TUnion['__typename']\n> = Extract<TUnion, { __typename: TTypename }>\n\n/**\n * Type guard to narrow canvas component by __typename.\n */\nexport function isComponentType<\n TComponent extends BaseComponent,\n TTypename extends TComponent['__typename']\n>(\n component: TComponent,\n typename: TTypename\n): component is Extract<TComponent, { __typename: TTypename }> {\n return component.__typename === typename\n}\n"],"mappings":";AAyMO,SAAS,gBAId,WACA,UAC6D;AAC7D,SAAO,UAAU,eAAe;AAClC;","names":[]}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview Types
|
|
3
|
+
*
|
|
4
|
+
* Types and interfaces for the live preview system.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Message types for editor <-> preview communication
|
|
8
|
+
*/
|
|
9
|
+
type PreviewMessageType = 'typecms:preview:init' | 'typecms:preview:update' | 'typecms:preview:ready' | 'typecms:preview:navigate' | 'typecms:preview:focus';
|
|
10
|
+
/**
|
|
11
|
+
* Base message structure
|
|
12
|
+
*/
|
|
13
|
+
interface PreviewMessageBase {
|
|
14
|
+
type: PreviewMessageType;
|
|
15
|
+
timestamp: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Initialization message sent from editor to preview
|
|
19
|
+
*/
|
|
20
|
+
interface PreviewInitMessage extends PreviewMessageBase {
|
|
21
|
+
type: 'typecms:preview:init';
|
|
22
|
+
payload: {
|
|
23
|
+
entryId: string;
|
|
24
|
+
projectId: string;
|
|
25
|
+
locale: string;
|
|
26
|
+
typeId: string;
|
|
27
|
+
slug?: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Update message sent from editor when values change
|
|
32
|
+
*/
|
|
33
|
+
interface PreviewUpdateMessage extends PreviewMessageBase {
|
|
34
|
+
type: 'typecms:preview:update';
|
|
35
|
+
payload: {
|
|
36
|
+
entryId: string;
|
|
37
|
+
locale: string;
|
|
38
|
+
/** Path that changed (e.g., "title", "content.blocks[0].text") */
|
|
39
|
+
path: string;
|
|
40
|
+
/** New value */
|
|
41
|
+
value: unknown;
|
|
42
|
+
/** Full values object (for initial sync or complex updates) */
|
|
43
|
+
values?: Record<string, unknown>;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Ready message sent from preview to editor
|
|
48
|
+
*/
|
|
49
|
+
interface PreviewReadyMessage extends PreviewMessageBase {
|
|
50
|
+
type: 'typecms:preview:ready';
|
|
51
|
+
payload: {
|
|
52
|
+
/** Pathname the preview is currently on */
|
|
53
|
+
pathname: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Navigation message sent from editor to preview
|
|
58
|
+
*/
|
|
59
|
+
interface PreviewNavigateMessage extends PreviewMessageBase {
|
|
60
|
+
type: 'typecms:preview:navigate';
|
|
61
|
+
payload: {
|
|
62
|
+
/** URL to navigate to */
|
|
63
|
+
url: string;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Focus message sent from editor when a field is focused
|
|
68
|
+
*/
|
|
69
|
+
interface PreviewFocusMessage extends PreviewMessageBase {
|
|
70
|
+
type: 'typecms:preview:focus';
|
|
71
|
+
payload: {
|
|
72
|
+
/** Path of the focused field */
|
|
73
|
+
path: string;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Union of all message types
|
|
78
|
+
*/
|
|
79
|
+
type PreviewMessage = PreviewInitMessage | PreviewUpdateMessage | PreviewReadyMessage | PreviewNavigateMessage | PreviewFocusMessage;
|
|
80
|
+
/**
|
|
81
|
+
* Preview mode state
|
|
82
|
+
*/
|
|
83
|
+
interface PreviewState {
|
|
84
|
+
/** Whether preview mode is active */
|
|
85
|
+
enabled: boolean;
|
|
86
|
+
/** Current entry being previewed */
|
|
87
|
+
entryId: string | null;
|
|
88
|
+
/** Current project */
|
|
89
|
+
projectId: string | null;
|
|
90
|
+
/** Current locale */
|
|
91
|
+
locale: string | null;
|
|
92
|
+
/** Current type ID */
|
|
93
|
+
typeId: string | null;
|
|
94
|
+
/** Live preview values (overrides fetched data) */
|
|
95
|
+
values: Record<string, unknown>;
|
|
96
|
+
/** Currently focused field path */
|
|
97
|
+
focusedPath: string | null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Preview client configuration
|
|
101
|
+
*/
|
|
102
|
+
interface PreviewConfig {
|
|
103
|
+
/** Allowed origin for postMessage (defaults to Type CMS domains) */
|
|
104
|
+
allowedOrigins?: string[];
|
|
105
|
+
/** Callback when preview is initialized */
|
|
106
|
+
onInit?: (data: PreviewInitMessage['payload']) => void;
|
|
107
|
+
/** Callback when values change */
|
|
108
|
+
onUpdate?: (data: PreviewUpdateMessage['payload']) => void;
|
|
109
|
+
/** Callback when navigation is requested */
|
|
110
|
+
onNavigate?: (url: string) => void;
|
|
111
|
+
/** Callback when a field is focused */
|
|
112
|
+
onFocus?: (path: string) => void;
|
|
113
|
+
/** Enable debug logging */
|
|
114
|
+
debug?: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type { PreviewState as P, PreviewConfig as a, PreviewFocusMessage as b, PreviewInitMessage as c, PreviewMessage as d, PreviewMessageBase as e, PreviewMessageType as f, PreviewNavigateMessage as g, PreviewReadyMessage as h, PreviewUpdateMessage as i };
|