Accept full meta HTML in Tags & Badges (name is label only).

Admins paste complete <meta> tags as the value; the name field is only a list label.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-10-03 18:39:20 +03:30
co-authored by Cursor
parent 68b985dee3
commit eb9b8b9fa3
5 changed files with 41 additions and 26 deletions
@@ -6,7 +6,7 @@ import { useT } from '../i18n/useT'
import styles from './WebsiteMetaTagModal.module.css'
const ANIMATION_MS = 220
const META_NAME_RE = /^[a-zA-Z0-9_.:-]{1,80}$/
const META_HTML_RE = /^<meta\b[^>]*>/i
interface WebsiteMetaTagModalProps {
open: boolean
@@ -65,11 +65,11 @@ export function WebsiteMetaTagModal({
e.preventDefault()
const trimmedName = name.trim()
const trimmedContent = content.trim()
if (!trimmedName || !META_NAME_RE.test(trimmedName)) {
if (!trimmedName) {
setFormError(t('website.badges.meta.nameInvalid'))
return
}
if (!trimmedContent) {
if (!trimmedContent || !META_HTML_RE.test(trimmedContent)) {
setFormError(t('website.badges.meta.contentRequired'))
return
}
+16 -14
View File
@@ -2435,7 +2435,7 @@ const en = {
'website.badges.errorLoad': 'Unable to load tags & badges.',
'website.badges.errorSave': 'Unable to save tags & badges.',
'website.badges.meta.title': 'Custom meta tags',
'website.badges.meta.desc': 'Each row is added to the website head as',
'website.badges.meta.desc': 'Name is only a label. Value is the full tag, e.g.',
'website.badges.meta.sample': '<meta name="…" content="…">',
'website.badges.meta.add': 'Add meta tag',
'website.badges.meta.modalTitle': 'Add meta tag',
@@ -2445,12 +2445,13 @@ const en = {
'website.badges.meta.nameLabel': 'Name',
'website.badges.meta.valueLabel': 'Value',
'website.badges.meta.remove': 'Remove meta tag',
'website.badges.meta.empty': 'No custom tags yet. Use + to add a name and value.',
'website.badges.meta.namePlaceholder': 'name (e.g. google-site-verification)',
'website.badges.meta.contentPlaceholder': 'value / content',
'website.badges.meta.nameInvalid':
'Meta tag names may only use letters, numbers, and _ . : -',
'website.badges.meta.contentRequired': 'Each meta tag needs both a name and a value.',
'website.badges.meta.empty': 'No custom tags yet. Use + to paste a full meta tag.',
'website.badges.meta.namePlaceholder': 'Label (e.g. Google verification)',
'website.badges.meta.contentPlaceholder':
'<meta name="google-site-verification" content="…">',
'website.badges.meta.nameInvalid': 'Enter a short label for this meta tag.',
'website.badges.meta.contentRequired':
'Value must be a full meta tag starting with <meta …>.',
'website.badges.enamad.title': 'eNamad',
'website.badges.enamad.desc':
'If you have received an eNamad badge for your website, enter its HTML code below to show it on the site.',
@@ -5353,7 +5354,7 @@ const fa: Record<MessageKey, string> = {
'website.badges.errorLoad': 'بارگذاری تگ و نشان‌ها ممکن نشد.',
'website.badges.errorSave': 'ذخیره تگ و نشان‌ها ممکن نشد.',
'website.badges.meta.title': 'تگ‌های متا سفارشی',
'website.badges.meta.desc': 'هر ردیف در هد وب‌سایت قرار می‌گیرد به‌صورت',
'website.badges.meta.desc': 'نام فقط برچسب است. مقدار کل تگ است، مثلاً',
'website.badges.meta.sample': '<meta name="…" content="…">',
'website.badges.meta.add': 'افزودن تگ متا',
'website.badges.meta.modalTitle': 'افزودن تگ متا',
@@ -5363,12 +5364,13 @@ const fa: Record<MessageKey, string> = {
'website.badges.meta.nameLabel': 'نام',
'website.badges.meta.valueLabel': 'مقدار',
'website.badges.meta.remove': 'حذف تگ متا',
'website.badges.meta.empty': 'هنوز تگ سفارشی ندارید. با + نام و مقدار اضافه کنید.',
'website.badges.meta.namePlaceholder': 'نام (مثلاً google-site-verification)',
'website.badges.meta.contentPlaceholder': 'مقدار / محتوا',
'website.badges.meta.nameInvalid':
'نام تگ فقط می‌تواند شامل حروف، اعداد و _ . : - باشد.',
'website.badges.meta.contentRequired': 'هر تگ متا باید هم نام و هم مقدار داشته باشد.',
'website.badges.meta.empty': 'هنوز تگ سفارشی ندارید. با + کل تگ متا را بچسبانید.',
'website.badges.meta.namePlaceholder': 'برچسب (مثلاً تأیید گوگل)',
'website.badges.meta.contentPlaceholder':
'<meta name="google-site-verification" content="…">',
'website.badges.meta.nameInvalid': 'یک برچسب کوتاه برای این تگ متا وارد کنید.',
'website.badges.meta.contentRequired':
'مقدار باید یک تگ متای کامل باشد که با <meta …> شروع شود.',
'website.badges.enamad.title': 'اینماد',
'website.badges.enamad.desc':
'در صورتی که نشان اینماد برای وبسایت خود دریافت کرده اید، کد HTML آن را در زیر وارد کنید تا در وبسایت نمایش داده شود.',
@@ -27,14 +27,20 @@
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 12px;
line-height: 1.4;
color: var(--text-primary);
background: rgba(15, 23, 42, 0.55);
border: 1px solid rgba(148, 163, 184, 0.28);
color: #0f172a;
background: #f8fafc;
border: 1px solid rgba(148, 163, 184, 0.5);
border-radius: 6px;
unicode-bidi: isolate;
white-space: nowrap;
}
:global(html[data-theme='dark']) .metaSample {
color: var(--text-primary);
background: rgba(15, 23, 42, 0.55);
border-color: rgba(148, 163, 184, 0.28);
}
.metaList {
list-style: none;
margin: 0;
@@ -66,15 +72,21 @@
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 12px;
font-weight: 600;
color: var(--text-primary);
background: rgba(15, 23, 42, 0.45);
border: 1px solid rgba(148, 163, 184, 0.25);
color: #0f172a;
background: #ffffff;
border: 1px solid rgba(148, 163, 184, 0.5);
border-radius: 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:global(html[data-theme='dark']) .metaName {
color: var(--text-primary);
background: rgba(15, 23, 42, 0.45);
border-color: rgba(148, 163, 184, 0.25);
}
.metaValue {
min-width: 0;
font-size: 12px;
@@ -23,7 +23,7 @@ import sharedStyles from './ProductSettingsPage.module.css'
import settingsStyles from './WebsiteSettingsPage.module.css'
import styles from './WebsiteBadgesPage.module.css'
const META_NAME_RE = /^[a-zA-Z0-9_.:-]{1,80}$/
const META_HTML_RE = /^<meta\b[^>]*>/i
const MAX_META_TAGS = 40
type TrustKind = 'enamad' | 'samandehi'
@@ -152,11 +152,11 @@ export function WebsiteBadgesPage() {
const name = tag.name.trim()
const content = tag.content.trim()
if (!name && !content) continue
if (!name || !META_NAME_RE.test(name)) {
if (!name) {
setError(t('website.badges.meta.nameInvalid'))
return
}
if (!content) {
if (!content || !META_HTML_RE.test(content)) {
setError(t('website.badges.meta.contentRequired'))
return
}
@@ -111,6 +111,7 @@ export interface WebsiteSchemaSettings {
sameAs: string[]
}
/** name = admin label; content = full `<meta …>` HTML */
export interface WebsiteMetaTag {
id: string
name: string