diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 8e6d761..160f911 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -27,6 +27,9 @@ jobs: run: | echo "${{ secrets.SSH_DEPLOY_KEY }}" > /tmp/deploy_key chmod 600 /tmp/deploy_key + rsync -avz --delete \ + -e "ssh -o StrictHostKeyChecking=no -i /tmp/deploy_key" \ + ./types/ root@167.86.74.105:/opt/rustycms/types/ ssh -o StrictHostKeyChecking=no -i /tmp/deploy_key root@167.86.74.105 \ "docker compose -f /opt/rustycms/docker-compose.yml up -d && docker image prune -f" rm /tmp/deploy_key diff --git a/.gitignore b/.gitignore index 189f6b4..1a69542 100644 --- a/.gitignore +++ b/.gitignore @@ -19,10 +19,7 @@ content/de/page/* content/assets/* !content/assets/mountains-w300.webp -# Types: ignore all except demo types (demo + page for demo references) -types/* -!types/demo.json5 -!types/page.json5 +# Types are tracked in git and deployed to server via CI/CD .history .cursor diff --git a/types/blog_post.json5 b/types/blog_post.json5 new file mode 100644 index 0000000..81693d0 --- /dev/null +++ b/types/blog_post.json5 @@ -0,0 +1,60 @@ +{ + "name": "blog_post", + "description": "Simple blog post with title, body, tags and publish status", + "tags": [ + "content", + "blog" + ], + "category": "content", + "fieldOrder": [ + "author", + "body", + "created_at", + "excerpt", + "published", + "tags", + "title" + ], + "fields": { + "author": { + "type": "string", + "description": "Author name" + }, + "body": { + "type": "markdown", + "required": true, + "description": "Main content (Markdown)", + "minLength": 1 + }, + "created_at": { + "type": "datetime", + "auto": true, + "readonly": true, + "description": "Creation timestamp (auto-generated)" + }, + "excerpt": { + "type": "string", + "description": "Short summary for previews", + "maxLength": 500 + }, + "published": { + "type": "boolean", + "default": false, + "description": "Whether the post is publicly visible" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Categorisation tags" + }, + "title": { + "type": "string", + "required": true, + "description": "Title of the blog post", + "minLength": 1, + "maxLength": 200 + } + } +} \ No newline at end of file diff --git a/types/calendar.json5 b/types/calendar.json5 new file mode 100644 index 0000000..e6379f2 --- /dev/null +++ b/types/calendar.json5 @@ -0,0 +1,23 @@ +{ + name: "calendar", + description: "Calendar (entries identified by slug).", + tags: [ + "content" + ], + category: "content", + fields: { + items: { + type: "array", + items: { + type: "reference", + collection: "calendar_item", + }, + description: "Calendar entries (references to calendar_item)", + }, + fromPost: { + type: "boolean", + default: false, + description: "Include entries from post", + }, + }, +} \ No newline at end of file diff --git a/types/calendar_item.json5 b/types/calendar_item.json5 new file mode 100644 index 0000000..569bacc --- /dev/null +++ b/types/calendar_item.json5 @@ -0,0 +1,33 @@ +{ + name: "calendar_item", + description: "Single calendar entry (event) with title, description and time.", + tags: [ + "content" + ], + category: "content", + fields: { + title: { + type: "string", + required: true, + description: "Event title", + section: "Event", + }, + description: { + type: "string", + description: "Event description", + section: "Event", + }, + terminZeit: { + type: "datetime", + required: true, + description: "Event date/time", + section: "Date & link", + }, + link: { + type: "reference", + collection: "link", + description: "Optional link (e.g. registration or details page)", + section: "Date & link", + }, + }, +} \ No newline at end of file diff --git a/types/campaign.json5 b/types/campaign.json5 new file mode 100644 index 0000000..90f868d --- /dev/null +++ b/types/campaign.json5 @@ -0,0 +1,62 @@ +{ + "name": "campaign", + "description": "Campaign with URL pattern and optional content", + "tags": [ + "marketing", + "config" + ], + "category": "config", + "fields": { + "css": { + "type": "string", + "description": "Optional CSS", + "widget": "code", + "codeLanguage": "css" + }, + "html": { + "type": "html", + "description": "HTML content" + }, + "insertHtml": { + "type": "string", + "required": true, + "default": "beforeend", + "enum": [ + "afterbegin", + "beforeend", + "afterend", + "beforebegin", + "replace" + ], + "description": "Position relative to selector" + }, + "javascript": { + "type": "string", + "description": "Optional JavaScript", + "widget": "code", + "codeLanguage": "javascript" + }, + "medias": { + "type": "array", + "items": { + "type": "reference", + "collection": "img" + }, + "description": "Media (images)" + }, + "selector": { + "type": "string", + "required": true, + "description": "CSS selector where content is inserted" + }, + "timeUntil": { + "type": "datetime", + "description": "Time limit (until when the campaign runs)" + }, + "urlPattern": { + "type": "string", + "required": true, + "description": "URL pattern (e.g. regex) for campaign usage" + } + } +} \ No newline at end of file diff --git a/types/campaigns.json5 b/types/campaigns.json5 new file mode 100644 index 0000000..c8d05a1 --- /dev/null +++ b/types/campaigns.json5 @@ -0,0 +1,27 @@ +{ + // Corresponds to CF_Campaigns / Contentful_Campaigns.ts + name: "campaigns", + description: "Global campaign list and enable flag", + tags: ["marketing", "config"], + category: "config", + fields: { + id: { + type: "string", + required: true, + description: "Unique ID (e.g. campaigns-global)", + }, + campaigns: { + type: "array", + items: { + type: "reference", + collection: "campaign", + }, + description: "Campaign list", + }, + enable: { + type: "boolean", + default: true, + description: "Campaigns enabled", + }, + }, +} diff --git a/types/component_layout.json5 b/types/component_layout.json5 new file mode 100644 index 0000000..e10194a --- /dev/null +++ b/types/component_layout.json5 @@ -0,0 +1,86 @@ +{ + // Reusable partial (CF_ComponentLayout) – no own collection. + name: "component_layout", + description: "Reusable grid layout (mobile/tablet/desktop columns)", + tags: [ + "layout", + "partial" + ], + category: "layout", + reusable: true, + fields: { + breakout: { + type: "boolean", + default: false, + description: "Breakout layout (full width)", + }, + mobile: { + type: "string", + required: true, + enum: [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ], + default: "12", + description: "Width on mobile (1–12)", + }, + tablet: { + type: "string", + enum: [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ], + description: "Width on tablet (optional)", + }, + desktop: { + type: "string", + enum: [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ], + description: "Width on desktop (optional)", + }, + spaceBottom: { + type: "number", + enum: [ + 0, + 0.5, + 1, + 1.5, + 2 + ], + default: 0, + description: "Space below (rem)", + }, + }, +} \ No newline at end of file diff --git a/types/content_layout.json5 b/types/content_layout.json5 new file mode 100644 index 0000000..a76543e --- /dev/null +++ b/types/content_layout.json5 @@ -0,0 +1,150 @@ +{ + // Equivalent to: CF_Content interface + // Base type for pages/posts with a 3-row content layout + name: "content_layout", + description: "Base layout with 3 content rows (inherited by page, post, etc.)", + tags: ["layout", "partial"], + category: "layout", + fields: { + row1JustifyContent: { + type: "string", + enum: [ + "start", + "end", + "center", + "between", + "around", + "evenly" + ], + default: "start", + description: "Justify content for row 1", + }, + row1AlignItems: { + type: "string", + enum: [ + "start", + "end", + "center", + "baseline", + "stretch" + ], + default: "stretch", + description: "Align items for row 1", + }, + row1Content: { + type: "array", + items: { + type: "reference", + collections: [ + "markdown", + "post_overview", + "html", + "headline", + "image", + "quote", + "youtube_video", + "image_gallery", + "iframe", + "searchable_text", + "fullwidth_banner", + "list", + "link_list", + "calendar" + ], + }, + description: "Content components for row 1 (Markdown, Post Overview, HTML, Headline, Image, …)", + }, + row2JustifyContent: { + type: "string", + enum: [ + "start", + "end", + "center", + "between", + "around", + "evenly" + ], + default: "start", + }, + row2AlignItems: { + type: "string", + enum: [ + "start", + "end", + "center", + "baseline", + "stretch" + ], + default: "stretch", + }, + row2Content: { + type: "array", + items: { + type: "reference", + collections: [ + "markdown", + "post_overview", + "html", + "headline", + "image", + "quote", + "youtube_video", + "image_gallery", + "iframe", + "searchable_text", + "fullwidth_banner", + "list", + "link_list", + "calendar" + ], + }, + description: "Content components for row 2", + }, + row3JustifyContent: { + type: "string", + enum: [ + "start", + "end", + "center", + "between", + "around", + "evenly" + ], + default: "start", + }, + row3AlignItems: { + type: "string", + enum: [ + "start", + "end", + "center", + "baseline", + "stretch" + ], + default: "stretch", + }, + row3Content: { + type: "array", + items: { + type: "reference", + collections: [ + "markdown", + "post_overview", + "html", + "headline", + "image", + "quote", + "youtube_video", + "image_gallery", + "iframe", + "searchable_text", + "fullwidth_banner", + "list", + "link_list", + "calendar" + ], + }, + description: "Content components for row 3", + }, + } +} \ No newline at end of file diff --git a/types/footer.json5 b/types/footer.json5 new file mode 100644 index 0000000..e7c41b1 --- /dev/null +++ b/types/footer.json5 @@ -0,0 +1,17 @@ +{ + // Corresponds to CF_Footer extends CF_Content / Contentful_Footer.ts + name: "footer", + description: "Footer with content layout", + tags: ["layout", "config"], + category: "config", + extends: [ + "content_layout" + ], + fields: { + id: { + type: "string", + required: true, + description: "Unique footer ID (e.g. for navigation)", + }, + }, +} diff --git a/types/fullwidth_banner.json5 b/types/fullwidth_banner.json5 new file mode 100644 index 0000000..c9b5710 --- /dev/null +++ b/types/fullwidth_banner.json5 @@ -0,0 +1,43 @@ +{ + // Equivalent to: CF_FullwidthBanner interface + name: "fullwidth_banner", + description: "Full-width banner with dark/light variant", + tags: [ + "component", + "layout" + ], + category: "components", + fields: { + name: { + type: "string", + required: true, + description: "Internal name" + }, + variant: { + type: "string", + required: true, + enum: [ + "dark", + "light" + ], + default: "light", + description: "Color variant" + }, + headline: { + type: "string", + required: true + }, + subheadline: { + type: "string" + }, + text: { + type: "richtext", + description: "Banner body text" + }, + image: { + type: "referenceOrInline", + collection: "img", + description: "Banner image" + }, + } +} \ No newline at end of file diff --git a/types/headline.json5 b/types/headline.json5 new file mode 100644 index 0000000..71999e9 --- /dev/null +++ b/types/headline.json5 @@ -0,0 +1,37 @@ +{ + // Corresponds to CF_ComponentHeadline / Contentful_Headline.ts + name: "headline", + description: "Headline component with optional layout", + tags: ["component", "layout"], + category: "components", + fields: { + internal: { + type: "string", + required: true, + description: "Internal key", + }, + text: { + type: "string", + required: true, + description: "Headline text", + }, + tag: { + type: "string", + required: true, + enum: ["h1", "h2", "h3", "h4", "h5", "h6"], + default: "h2", + description: "HTML tag for headline", + }, + layout: { + type: "object", + useFields: "component_layout", + description: "Column width (grid) like CF_ComponentLayout", + }, + align: { + type: "string", + enum: ["left", "center", "right"], + default: "left", + description: "Text alignment", + }, + }, +} diff --git a/types/html.json5 b/types/html.json5 new file mode 100644 index 0000000..4f3b718 --- /dev/null +++ b/types/html.json5 @@ -0,0 +1,24 @@ +{ + // Corresponds to CF_HTML / Contentful_Html.ts + name: "html", + description: "Raw HTML content block with optional layout", + tags: ["content", "component"], + category: "components", + fields: { + id: { + type: "string", + required: true, + description: "Unique component ID", + }, + html: { + type: "html", + required: true, + description: "HTML content (safely embedded)", + }, + layout: { + type: "object", + useFields: "component_layout", + description: "Column width (grid) like CF_ComponentLayout", + }, + }, +} diff --git a/types/iframe.json5 b/types/iframe.json5 new file mode 100644 index 0000000..f42be75 --- /dev/null +++ b/types/iframe.json5 @@ -0,0 +1,34 @@ +{ + // Corresponds to CF_ComponentIframe / Contentful_Iframe.ts + name: "iframe", + description: "Embedded iframe with URL and optional title", + tags: ["media", "component"], + category: "components", + fields: { + name: { + type: "string", + required: true, + description: "Internal component name", + }, + content: { + type: "string", + required: true, + description: "Description/content for iframe", + }, + iframe: { + type: "string", + required: true, + description: "Iframe URL or embed code", + }, + overlayImage: { + type: "reference", + collection: "img", + description: "Optional overlay image (reference to img)", + }, + layout: { + type: "object", + useFields: "component_layout", + description: "Column width (grid) like CF_ComponentLayout", + }, + }, +} diff --git a/types/image.json5 b/types/image.json5 new file mode 100644 index 0000000..4c804a4 --- /dev/null +++ b/types/image.json5 @@ -0,0 +1,38 @@ +{ + name: "image", + description: "Image component with inline img (src + description) and layout", + tags: ["component", "media"], + category: "components", + fields: { + name: { + type: "string", + required: true, + description: "Internal component name", + }, + img: { + type: "referenceOrInline", + collection: "img", + required: true, + description: "Image: Slug (Referenz) oder Inline-Objekt (gleiche Felder wie img)", + }, + caption: { + type: "string", + description: "Image caption", + }, + layout: { + type: "object", + useFields: "component_layout", + description: "Column width (grid)", + }, + maxWidth: { + type: "integer", + description: "Max width in px", + }, + aspectRatio: { + type: "string", + description: "Aspect ratio", + enum: ["1:1", "4:3", "3:2", "16:9", "21:9", "2:3", "3:4"], + default: "4:3", + }, + }, +} diff --git a/types/image_gallery.json5 b/types/image_gallery.json5 new file mode 100644 index 0000000..f48a30f --- /dev/null +++ b/types/image_gallery.json5 @@ -0,0 +1,35 @@ +{ + // Corresponds to CF_ImageGallery / Contentful_ImageGallery.ts + name: "image_gallery", + description: "Gallery of image references", + tags: [ + "media", + "component" + ], + category: "components", + fields: { + name: { + type: "string", + required: true, + description: "Internal gallery name", + }, + images: { + type: "array", + required: true, + items: { + type: "referenceOrInline", + collection: "img", + }, + description: "Images (references to img)", + }, + layout: { + type: "object", + useFields: "component_layout", + description: "Column width (grid) like CF_ComponentLayout", + }, + description: { + type: "string", + description: "Optional gallery description", + }, + }, +} \ No newline at end of file diff --git a/types/img.json5 b/types/img.json5 new file mode 100644 index 0000000..812abb2 --- /dev/null +++ b/types/img.json5 @@ -0,0 +1,21 @@ +{ + name: "img", + description: "Image asset with URL and optional description (alt text)", + tags: [ + "asset", + "media" + ], + category: "components", + fields: { + description: { + type: "string", + description: "Alt text / description", + }, + src: { + type: "string", + required: true, + description: "Image URL", + widget: "imageUrl", + }, + }, +} \ No newline at end of file diff --git a/types/link.json5 b/types/link.json5 new file mode 100644 index 0000000..d9dc1d2 --- /dev/null +++ b/types/link.json5 @@ -0,0 +1,75 @@ +{ + // Corresponds to CF_Link / Contentful_Link.ts + name: "link", + description: "Internal or external link with label and optional icon", + tags: ["navigation", "component"], + category: "components", + fields: { + name: { + type: "string", + required: true, + description: "Internal link name", + }, + internal: { + type: "string", + required: true, + description: "Internal key", + }, + linkName: { + type: "string", + required: true, + description: "Display name (e.g. in navigation)", + }, + url: { + type: "string", + required: true, + description: "Target URL", + }, + icon: { + type: "string", + description: "Icon identifier", + }, + color: { + type: "string", + description: "Color (e.g. for buttons)", + }, + newTab: { + type: "boolean", + default: false, + description: "Open in new tab", + }, + external: { + type: "boolean", + default: false, + description: "External link", + }, + description: { + type: "string", + description: "Description", + }, + alt: { + type: "string", + description: "Alt text (e.g. for icon)", + }, + showText: { + type: "boolean", + default: true, + description: "Show text", + }, + author: { + type: "string", + required: true, + description: "Author (e.g. for sources)", + }, + date: { + type: "string", + required: true, + description: "Date (e.g. publication)", + }, + source: { + type: "string", + required: true, + description: "Source", + }, + }, +} diff --git a/types/link_list.json5 b/types/link_list.json5 new file mode 100644 index 0000000..6048252 --- /dev/null +++ b/types/link_list.json5 @@ -0,0 +1,23 @@ +{ + // Corresponds to CF_Link_List / Contentful_Link_List.ts (componentLinkList) + name: "link_list", + description: "List of links with optional headline", + tags: ["navigation", "component"], + category: "components", + fields: { + headline: { + type: "string", + required: true, + description: "Link list headline", + }, + links: { + type: "array", + required: true, + items: { + type: "reference", + collection: "link", + }, + description: "Links (references to link)", + }, + }, +} diff --git a/types/list.json5 b/types/list.json5 new file mode 100644 index 0000000..5321f51 --- /dev/null +++ b/types/list.json5 @@ -0,0 +1,20 @@ +{ + // Corresponds to CF_ComponentList / Contentful_List.ts + name: "list", + description: "Simple list of string items", + tags: ["content", "component"], + category: "components", + fields: { + internal: { + type: "string", + required: true, + description: "Internal key", + }, + item: { + type: "array", + required: true, + items: { type: "string" }, + description: "List entries", + }, + }, +} diff --git a/types/markdown.json5 b/types/markdown.json5 new file mode 100644 index 0000000..cb4b637 --- /dev/null +++ b/types/markdown.json5 @@ -0,0 +1,29 @@ +{ + // Corresponds to CF_Markdown / Contentful_Markdown.ts + name: "markdown", + description: "Markdown content block with optional layout", + tags: ["content", "component"], + category: "components", + fields: { + name: { + type: "string", + required: true, + description: "Internal component name", + }, + content: { + type: "textOrRef", + description: "Markdown/body text: either inline or file reference (file:path, e.g. file:slug.content.md)", + }, + layout: { + type: "object", + useFields: "component_layout", + description: "Column width (grid) like CF_ComponentLayout", + }, + alignment: { + type: "string", + enum: ["left", "center", "right"], + default: "left", + description: "Text alignment", + }, + }, +} diff --git a/types/navigation.json5 b/types/navigation.json5 new file mode 100644 index 0000000..22267a8 --- /dev/null +++ b/types/navigation.json5 @@ -0,0 +1,28 @@ +{ + // Corresponds to CF_Navigation / Contentful_Navigation.ts + name: "navigation", + description: "Navigation block with links (header/footer)", + tags: ["navigation", "layout"], + category: "config", + fields: { + name: { + type: "string", + required: true, + description: "Display name of navigation", + }, + internal: { + type: "string", + required: true, + description: "Internal key (e.g. navigation-header, navigation-footer)", + }, + links: { + type: "array", + required: true, + items: { + type: "reference", + collections: ["link", "page", "post"], + }, + description: "Navigation entries (references to link, page or post)", + }, + }, +} diff --git a/types/page_config.json5 b/types/page_config.json5 new file mode 100644 index 0000000..ff989ba --- /dev/null +++ b/types/page_config.json5 @@ -0,0 +1,50 @@ +{ + // Corresponds to CF_PageConfig / Contentful_PageConfig.ts + name: "page_config", + description: "Global page config (logo, footer, etc.)", + tags: [ + "config", + "layout" + ], + category: "config", + fields: { + logo: { + type: "referenceOrInline", + collection: "img", + required: true, + description: "Logo image (reference to img)", + }, + footerText1: { + type: "string", + required: true, + description: "Footer text (e.g. copyright)", + }, + seoTitle: { + type: "string", + required: true, + description: "Default SEO title for website", + }, + seoDescription: { + type: "string", + required: true, + description: "Default meta description", + }, + blogTagPageHeadline: { + type: "string", + description: "Headline for tag page (blog)", + }, + blogPostsPageHeadline: { + type: "string", + description: "Headline for blog overview page", + }, + blogPostsPageSubHeadline: { + type: "string", + description: "Subheadline for blog overview page", + }, + website: { + type: "string", + required: true, + description: "Website-URL", + }, + }, +} \ No newline at end of file diff --git a/types/post.json5 b/types/post.json5 new file mode 100644 index 0000000..68b22b6 --- /dev/null +++ b/types/post.json5 @@ -0,0 +1,94 @@ +{ + "name": "post", + "description": "Blog post with layout, SEO and optional tags", + "tags": [ + "content", + "blog" + ], + "category": "content", + "extends": [ + "content_layout", + "seo" + ], + "defaultSort": "_updated", + "defaultOrder": "desc", + "fieldOrder": [ + "slug", + "headline", + "subheadline", + "excerpt", + "content", + "postImage", + "postTag", + "created", + "date", + "icon", + "important", + "linkName", + "showCommentSection" + ], + "fields": { + "slug": { + "type": "string", + "required": true, + "unique": true + }, + "headline": { + "type": "string", + "required": true + }, + "subheadline": { + "type": "string" + }, + "excerpt": { + "type": "string", + "description": "Short summary for previews", + "maxLength": 500 + }, + "content": { + "type": "markdown", + "required": true, + "description": "Post body (Markdown)" + }, + "postImage": { + "type": "referenceOrInline", + "collection": "img", + "description": "Featured image" + }, + "postTag": { + "type": "array", + "items": { + "type": "reference", + "collection": "tag" + }, + "description": "Associated tags" + }, + "created": { + "type": "datetime", + "auto": true, + "readonly": true + }, + "date": { + "type": "datetime", + "description": "Optional display date" + }, + "icon": { + "type": "string" + }, + "important": { + "type": "boolean", + "required": true, + "default": false, + "description": "Mark post as important" + }, + "linkName": { + "type": "string", + "required": true + }, + "showCommentSection": { + "type": "boolean", + "default": true, + "description": "Show comment section (default: true)" + } + } +} \ No newline at end of file diff --git a/types/post_overview.json5 b/types/post_overview.json5 new file mode 100644 index 0000000..03a065a --- /dev/null +++ b/types/post_overview.json5 @@ -0,0 +1,62 @@ +{ + // Corresponds to CF_Post_Overview / Contentful_Post_Overview.ts (extends CF_Content, CF_SEO) + name: "post_overview", + description: "Post listing page with layout and SEO", + tags: ["content", "blog"], + category: "content", + extends: [ + "content_layout", + "seo" + ], + fields: { + id: { + type: "string", + required: true, + description: "Unique post overview ID", + }, + headline: { + type: "string", + required: true, + description: "Headline", + }, + text: { + type: "richtext", + description: "Intro text", + }, + layout: { + type: "object", + useFields: "component_layout", + description: "Column width (grid)", + }, + allPosts: { + type: "boolean", + default: false, + description: "Show all posts (otherwise filter)", + }, + filterByTag: { + type: "array", + items: { + type: "reference", + collection: "tag", + }, + description: "Only posts with these tags", + }, + posts: { + type: "array", + items: { + type: "reference", + collection: "post", + }, + description: "Fixed list of posts (when not allPosts)", + }, + numberItems: { + type: "integer", + description: "Max. number of displayed entries", + }, + design: { + type: "string", + enum: ["cards", "list"], + description: "Display as cards or list", + }, + }, +} diff --git a/types/product.json5 b/types/product.json5 new file mode 100644 index 0000000..9e11ee9 --- /dev/null +++ b/types/product.json5 @@ -0,0 +1,75 @@ +{ + "name": "product", + "description": "Product with SKU, price and optional fields (strict schema)", + "tags": [ + "content", + "ecommerce" + ], + "category": "content", + "strict": true, + "fields": { + "category": { + "type": "string", + "required": true, + "enum": [ + "electronics", + "clothing", + "books", + "food", + "other" + ], + "description": "Product category" + }, + "created_at": { + "type": "datetime", + "auto": true, + "readonly": true + }, + "description": { + "type": "string", + "description": "Detailed product description", + "maxLength": 5000 + }, + "images": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Product image URLs (1–10)", + "minItems": 1, + "maxItems": 10 + }, + "in_stock": { + "type": "boolean", + "default": true, + "description": "Whether the product is in stock" + }, + "price": { + "type": "number", + "required": true, + "description": "Price in EUR", + "min": 0.0 + }, + "rating": { + "type": "number", + "nullable": true, + "description": "Average customer rating (null if unrated)", + "min": 0.0, + "max": 5.0 + }, + "sku": { + "type": "string", + "required": true, + "unique": true, + "description": "Stock Keeping Unit (e.g. EL-1234, BOOK-00042)", + "pattern": "^[A-Z]{2,4}-\\d{3,6}$" + }, + "title": { + "type": "string", + "required": true, + "description": "Product name", + "minLength": 1, + "maxLength": 200 + } + } +} \ No newline at end of file diff --git a/types/quote.json5 b/types/quote.json5 new file mode 100644 index 0000000..0bed618 --- /dev/null +++ b/types/quote.json5 @@ -0,0 +1,31 @@ +{ + // Corresponds to CF_Quote / Contentful_Quote.ts + name: "quote", + description: "Quote block with author and optional variant", + tags: ["content", "component"], + category: "components", + fields: { + quote: { + type: "string", + required: true, + description: "Quote text", + }, + author: { + type: "string", + required: true, + description: "Author or source of quote", + }, + variant: { + type: "string", + required: true, + enum: ["left", "right"], + default: "left", + description: "Quote alignment", + }, + layout: { + type: "object", + useFields: "component_layout", + description: "Column width (grid) like CF_ComponentLayout", + }, + }, +} diff --git a/types/searchable_text.json5 b/types/searchable_text.json5 new file mode 100644 index 0000000..c57c188 --- /dev/null +++ b/types/searchable_text.json5 @@ -0,0 +1,44 @@ +{ + // Corresponds to CF_ComponentSearchableText / Contentful_SearchableText.ts + name: "searchable_text", + description: "Searchable text with optional tag whitelist", + tags: ["content", "component", "search"], + category: "components", + fields: { + id: { + type: "string", + required: true, + description: "Unique component ID", + }, + tagWhitelist: { + type: "array", + items: { + type: "reference", + collection: "tag", + }, + description: "Optional: only search content with these tags", + }, + textFragments: { + type: "array", + required: true, + items: { + type: "reference", + collection: "text_fragment", + }, + description: "Searchable text fragments (references to text_fragment)", + }, + title: { + type: "string", + description: "Search component title", + }, + description: { + type: "string", + description: "Description", + }, + layout: { + type: "object", + useFields: "component_layout", + description: "Column width (grid) like CF_ComponentLayout", + }, + }, +} diff --git a/types/seo.json5 b/types/seo.json5 new file mode 100644 index 0000000..ab4ae59 --- /dev/null +++ b/types/seo.json5 @@ -0,0 +1,30 @@ +{ + // Equivalent to: CF_SEO interface + name: "seo", + description: "SEO meta (title, robots, description; inherited by page/post)", + tags: ["seo", "partial"], + category: "layout", + fields: { + seoTitle: { + type: "string", + required: true, + description: "SEO page title" + }, + seoMetaRobots: { + type: "string", + enum: [ + "index, follow", + "noindex, follow", + "index, nofollow", + "noindex, nofollow" + ], + default: "index, follow", + description: "Robots meta directive", + }, + seoDescription: { + type: "string", + maxLength: 160, + description: "Meta description for search engines" + }, + } +} \ No newline at end of file diff --git a/types/tag.json5 b/types/tag.json5 new file mode 100644 index 0000000..6851e43 --- /dev/null +++ b/types/tag.json5 @@ -0,0 +1,14 @@ +{ + // Equivalent to: CF_Tag interface + name: "tag", + description: "Tag for categorising content", + tags: ["taxonomy", "config"], + category: "config", + fields: { + name: { + type: "string", + required: true, + unique: true + }, + } +} \ No newline at end of file diff --git a/types/text_fragment.json5 b/types/text_fragment.json5 new file mode 100644 index 0000000..99d3e39 --- /dev/null +++ b/types/text_fragment.json5 @@ -0,0 +1,32 @@ +{ + // Corresponds to CF_TextFragment / Contentful_TextFragment.ts + name: "text_fragment", + description: "Reusable text fragment with optional tag references", + tags: ["content", "component"], + category: "components", + fields: { + id: { + type: "string", + required: true, + description: "Unique fragment ID", + }, + tags: { + type: "array", + items: { + type: "reference", + collection: "tag", + }, + description: "Optional tags for categorisation", + }, + title: { + type: "string", + required: true, + description: "Text fragment title", + }, + text: { + type: "richtext", + required: true, + description: "Searchable text content", + }, + }, +} diff --git a/types/top_banner.json5 b/types/top_banner.json5 new file mode 100644 index 0000000..76555a3 --- /dev/null +++ b/types/top_banner.json5 @@ -0,0 +1,19 @@ +{ + // Corresponds to CF_TopBanner / Contentful_TopBanner.ts + name: "top_banner", + description: "Top-of-page banner or notice text", + tags: ["component", "layout"], + category: "components", + fields: { + id: { + type: "string", + required: true, + description: "Unique ID", + }, + text: { + type: "string", + required: true, + description: "Banner text (e.g. notice at top of page)", + }, + }, +} diff --git a/types/translation_bundle.json5 b/types/translation_bundle.json5 new file mode 100644 index 0000000..beb2f9a --- /dev/null +++ b/types/translation_bundle.json5 @@ -0,0 +1,21 @@ +{ + name: "translation_bundle", + description: "Übersetzungs-Bundle: ein Objekt mit beliebigen Keys (String) und Werten (String). Für i18n / UI-Texte.", + tags: [ + "content", + "i18n" + ], + category: "content", + partial: false, + fields: { + // object mit additionalProperties = beliebige Keys, Werte müssen Strings sein + strings: { + type: "object", + required: true, + additionalProperties: { + type: "string" + }, + description: "Map: Übersetzungsschlüssel (key) → Text (value). Z. B. { \"searchable_text_help\": \"Hier könnt ihr...\", \"footer_copyright\": \"© 2024\" }", + }, + }, +} \ No newline at end of file diff --git a/types/youtube_video.json5 b/types/youtube_video.json5 new file mode 100644 index 0000000..4d4c48b --- /dev/null +++ b/types/youtube_video.json5 @@ -0,0 +1,38 @@ +{ + // Corresponds to CF_YoutubeVideo / Contentful_YoutubeVideo.ts + name: "youtube_video", + description: "Embedded YouTube video with optional params", + tags: [ + "media", + "component" + ], + category: "components", + fields: { + id: { + type: "string", + description: "Unique ID (optional, otherwise _slug)", + }, + youtubeId: { + type: "string", + required: true, + description: "YouTube video ID (e.g. from URL ?v=VIDEO_ID)", + }, + params: { + type: "string", + description: "Optional URL params (e.g. start=30, autoplay=1)", + }, + title: { + type: "string", + description: "Video title", + }, + description: { + type: "string", + description: "Short description", + }, + layout: { + type: "object", + useFields: "component_layout", + description: "Column width (grid) like CF_ComponentLayout", + }, + }, +} \ No newline at end of file