diff --git a/docs/website-api/AI_PROMPT.md b/docs/website-api/AI_PROMPT.md index aced2b2..94febe0 100644 --- a/docs/website-api/AI_PROMPT.md +++ b/docs/website-api/AI_PROMPT.md @@ -336,13 +336,13 @@ These are **required on every crawlable page** (home, listing, product/blog/port Business admins configure this under **Website → Tags & Badges**. 1. `GET /tenants/{domain}/website/business-info` -2. For each item in `metaTags` (array of `{ name, content }`), emit once in the root layout `
`: +2. For each item in `metaTags` (array of `{ html }`), emit the full HTML once in the root layout ``. ```html - +{html} ``` -In Next.js App Router, map them into `generateMetadata()` → `other: { [name]: content }`. Verification metas (eNamad, Google, etc.) belong here as custom name/value rows. +Example `html` value: `` (also supports `property`, `http-equiv`, etc.). Parse attributes into a `` element, or inject the sanitized string into ``. Do **not** wrap it again as `name`/`content` — `html` is already the whole tag. The dashboard “Name” field is an admin label only and is **not** in this public payload. 3. For each item in `trustBadges` (array of `{ kind, embedHtml }`), render `embedHtml` in the site footer (e.g. `dangerouslySetInnerHTML`). These are HTML widgets only (not meta). Scripts are stripped by the API — still treat the HTML as CMS-controlled content. diff --git a/docs/website-api/openapi.json b/docs/website-api/openapi.json index 78687eb..218907f 100644 --- a/docs/website-api/openapi.json +++ b/docs/website-api/openapi.json @@ -570,14 +570,16 @@ }, "metaTags": { "type": "array", - "description": "Enabled custom meta tags for the site (name/content). Configured under Website → Tags & Badges.", + "description": "Custom HTML snippets for the site . Each item is { html } with a full tag. Configured under Website → Tags & Badges (name there is an admin label only).", "items": { "type": "object", "properties": { - "name": { "type": "string" }, - "content": { "type": "string" } + "html": { + "type": "string", + "description": "Full sanitized HTML tag to inject in ." + } }, - "required": ["name", "content"] + "required": ["html"] } }, "trustBadges": { diff --git a/src/business-settings/business-settings.types.ts b/src/business-settings/business-settings.types.ts index 5a677ec..816a48a 100644 --- a/src/business-settings/business-settings.types.ts +++ b/src/business-settings/business-settings.types.ts @@ -131,7 +131,10 @@ export type WebsiteSettings = { tagsAndBadges: WebsiteTagsAndBadgesSettings; }; -/** Custom head meta tag (name + content). Present = published. */ +/** + * Custom head meta tag. + * `name` = admin label only; `content` = full `` HTML to inject. + */ export type WebsiteMetaTag = { id: string; name: string; diff --git a/src/business-settings/dto/update-business-settings.dto.ts b/src/business-settings/dto/update-business-settings.dto.ts index 1b74506..16b578e 100644 --- a/src/business-settings/dto/update-business-settings.dto.ts +++ b/src/business-settings/dto/update-business-settings.dto.ts @@ -8,7 +8,6 @@ import { IsNumber, IsOptional, IsString, - Matches, MaxLength, Min, MinLength, @@ -270,13 +269,16 @@ class WebsiteMetaTagDto { @MaxLength(64) id?: string; - @IsString() - @Matches(/^[a-zA-Z0-9_.:-]{1,80}$/) - name!: string; - + /** Admin label only (not necessarily the HTML name= attribute). */ @IsString() @MinLength(1) - @MaxLength(2000) + @MaxLength(80) + name!: string; + + /** Full `` HTML snippet. */ + @IsString() + @MinLength(1) + @MaxLength(4000) content!: string; } diff --git a/src/business-settings/website-tags-badges.util.ts b/src/business-settings/website-tags-badges.util.ts index 9561768..c65dff4 100644 --- a/src/business-settings/website-tags-badges.util.ts +++ b/src/business-settings/website-tags-badges.util.ts @@ -7,10 +7,9 @@ import { } from './business-settings.types'; const MAX_META_TAGS = 40; -const MAX_NAME_LEN = 80; -const MAX_CONTENT_LEN = 2000; +const MAX_LABEL_LEN = 80; +const MAX_META_HTML_LEN = 4000; const MAX_EMBED_LEN = 10000; -const META_NAME_RE = /^[a-zA-Z0-9_.:-]{1,80}$/; function isRecord(value: unknown): value is Record