pp-seo
Version:
`pp-seo` is a standalone Angular library that provides a highly configurable, sectioned form for managing all SEO (Search Engine Optimization) properties in your Angular applications. It supports a wide range of SEO fields, grouped into logical sections,
928 lines • 101 kB
JavaScript
import * as i0 from '@angular/core';
import { EventEmitter, forwardRef, Output, Input, Component } from '@angular/core';
import * as i1 from '@angular/forms';
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import * as i2 from '@angular/common';
import { CommonModule } from '@angular/common';
var FieldTypes;
(function (FieldTypes) {
FieldTypes["TEXT"] = "text";
FieldTypes["TEXTAREA"] = "textarea";
FieldTypes["ARRAY"] = "array";
FieldTypes["DROPDOWN"] = "dropdown";
FieldTypes["CHECKBOX"] = "checkbox";
FieldTypes["MULTISELECT"] = "multiselect";
})(FieldTypes || (FieldTypes = {}));
var HINTS;
(function (HINTS) {
HINTS["TEXT"] = "Enter text";
HINTS["TEXTAREA"] = "Enter text";
HINTS["ARRAY"] = "Enter text and press Enter to add";
HINTS["DROPDOWN"] = "Select an option";
HINTS["CHECKBOX"] = "Toggle the checkbox";
HINTS["DEFAULT"] = "Enter value";
})(HINTS || (HINTS = {}));
class Seo {
properties = this.getNewSeoProperties();
propertiesChange = new EventEmitter();
FIELD_TYPES = FieldTypes;
HINTS = HINTS;
onChange = (_) => { };
onTouched = () => { };
// Update a field value in the sectioned structure
updateProperty(sectionKey, fieldKey, value) {
if (this.properties.sections[sectionKey]?.fields) {
this.properties.sections[sectionKey].fields[fieldKey].value =
value;
}
this.validateAll();
this.onChange(this.properties);
this.propertiesChange.emit(this.properties);
}
// Sectioned invalid fields
invalidFields = {};
validateAll() {
let valid = true;
this.invalidFields = {};
// Validate each section
for (const [sectionKey, section] of Object.entries(this.properties.sections)) {
this.invalidFields[sectionKey] = {};
for (const [fieldKey, field] of Object.entries(section.fields)) {
this.invalidFields[sectionKey][fieldKey] = false;
if (field.hidden)
continue;
if (field.validation?.required && !field.value) {
this.invalidFields[sectionKey][fieldKey] = true;
valid = false;
continue;
}
if (field.value &&
field.validation?.maxLength &&
typeof field.value === 'string' &&
field.value.length > field.validation.maxLength) {
this.invalidFields[sectionKey][fieldKey] = true;
valid = false;
}
if (field.value &&
field.validation?.pattern &&
typeof field.value === 'string' &&
!field.validation.pattern.test(field.value)) {
this.invalidFields[sectionKey][fieldKey] = true;
valid = false;
}
}
}
return valid;
}
// Add item to array field
addArrayItem(sectionKey, fieldKey, event) {
event.preventDefault();
const section = this.properties.sections[sectionKey];
if (!section)
return;
const field = section.fields[fieldKey];
if (!field || !field._input)
return;
if (!Array.isArray(field.value))
field.value = [];
if (field._input.trim() && !field.value.includes(field._input.trim())) {
field.value.push(field._input.trim());
}
field._input = '';
this.onChange(this.properties);
this.propertiesChange.emit(this.properties);
}
// Remove item from array field
removeArrayItem(sectionKey, fieldKey, idx) {
const section = this.properties.sections[sectionKey];
if (!section)
return;
const field = section.fields[fieldKey];
if (!field || !Array.isArray(field.value))
return;
field.value.splice(idx, 1);
this.onChange(this.properties);
this.propertiesChange.emit(this.properties);
}
// Helper for template type safety
asField(entry) {
return entry.value;
}
writeValue(obj) {
if (obj) {
this.properties = obj;
}
}
registerOnChange(fn) {
this.onChange = fn;
}
registerOnTouched(fn) {
this.onTouched = fn;
}
getNewSeoProperties() {
return {
sections: {
coreMeta: {
label: 'Core Meta Tags',
fields: {
title: {
value: '',
label: 'Title Tag',
description: 'The title of the page (50-60 characters recommended)',
type: FieldTypes.TEXT,
validation: { required: true, maxLength: 60 },
hidden: false,
disabled: false,
},
description: {
value: '',
label: 'Meta Description',
description: 'Page description (150-160 characters recommended)',
type: FieldTypes.TEXTAREA,
validation: { maxLength: 160, required: false },
hidden: false,
disabled: false,
},
keywords: {
value: [],
label: 'Meta Keywords',
description: 'Comma-separated keywords (less important today)',
type: FieldTypes.ARRAY,
validation: { required: false },
hidden: false,
disabled: false,
},
canonicalUrl: {
value: '',
label: 'Canonical URL',
description: 'Preferred URL for this page to avoid duplicate content',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
robotsMeta: {
value: 'index, follow',
label: 'Robots Meta',
description: 'Controls how search engines index the page',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
viewport: {
value: 'width=device-width, initial-scale=1',
label: 'Viewport',
description: 'Controls mobile viewport behavior',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
charset: {
value: 'utf-8',
label: 'Charset',
description: 'Character encoding for the page',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 20 },
hidden: false,
disabled: false,
},
language: {
value: 'en',
label: 'Language',
description: 'Primary language of the page',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 10 },
hidden: false,
disabled: false,
},
author: {
value: '',
label: 'Author',
description: 'Author of the page content',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
publisher: {
value: '',
label: 'Publisher',
description: 'Publisher of the page content',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
rating: {
value: 'general',
label: 'Rating',
description: 'Content rating (general, mature, restricted, etc.)',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 20 },
hidden: false,
disabled: false,
},
copyright: {
value: '',
label: 'Copyright',
description: 'Copyright information',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
generator: {
value: '',
label: 'Generator',
description: 'Software used to generate the page',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
replyTo: {
value: '',
label: 'Reply-To',
description: 'Contact for content questions',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
contentType: {
value: 'text/html',
label: 'Content-Type',
description: 'MIME type of the page content',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 50 },
hidden: false,
disabled: false,
},
},
hidden: false,
disabled: false,
},
crawlingAndIndex: {
label: 'Crawling & Indexing',
fields: {
noindex: {
value: false,
label: 'Noindex',
description: 'Prevent search engines from indexing this page',
type: FieldTypes.CHECKBOX,
validation: { required: false },
hidden: false,
disabled: false,
},
nofollow: {
value: false,
label: 'Nofollow',
description: 'Prevent search engines from following links on this page',
type: FieldTypes.CHECKBOX,
validation: { required: false },
hidden: false,
disabled: false,
},
noimageindex: {
value: false,
label: 'Noimageindex',
description: 'Prevent search engines from indexing images on this page',
type: FieldTypes.CHECKBOX,
validation: { required: false },
hidden: false,
disabled: false,
},
noarchive: {
value: false,
label: 'Noarchive',
description: 'Prevent search engines from showing cached version',
type: FieldTypes.CHECKBOX,
validation: { required: false },
hidden: false,
disabled: false,
},
nosnippet: {
value: false,
label: 'Nosnippet',
description: 'Prevent search engines from showing snippets',
type: FieldTypes.CHECKBOX,
validation: { required: false },
hidden: false,
disabled: false,
},
notranslate: {
value: false,
label: 'Notranslate',
description: 'Prevent search engines from offering translation',
type: FieldTypes.CHECKBOX,
validation: { required: false },
hidden: false,
disabled: false,
},
unavailableAfter: {
value: '',
label: 'Unavailable After',
description: 'Date when page should stop being indexed (YYYY-MM-DD)',
type: FieldTypes.TEXT,
validation: { required: false, pattern: /^\d{4}-\d{2}-\d{2}$/ },
hidden: false,
disabled: false,
},
paginatedPrev: {
value: '',
label: 'Previous Page URL',
description: 'URL of previous page in paginated series',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
paginatedNext: {
value: '',
label: 'Next Page URL',
description: 'URL of next page in paginated series',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
},
hidden: false,
disabled: false,
},
cacheControl: {
label: 'Cache & Refresh Control',
fields: {
cacheControl: {
value: '',
label: 'Cache-Control',
description: 'Directives for caching mechanisms',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
pragma: {
value: '',
label: 'Pragma',
description: 'Legacy HTTP 1.0 cache control',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 50 },
hidden: false,
disabled: false,
},
expires: {
value: '',
label: 'Expires',
description: 'Date/time when cache expires',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 50 },
hidden: false,
disabled: false,
},
refresh: {
value: '',
label: 'Refresh',
description: 'Page refresh interval in seconds',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 20 },
hidden: false,
disabled: false,
},
},
hidden: false,
disabled: false,
},
socialMedia: {
label: 'Social Media & Open Graph',
fields: {
ogTitle: {
value: '',
label: 'OG Title',
description: 'Title for social media sharing',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
ogDescription: {
value: '',
label: 'OG Description',
description: 'Description for social media sharing',
type: FieldTypes.TEXTAREA,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
ogType: {
value: 'website',
label: 'OG Type',
description: 'Type of content (article, website, etc.)',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 50 },
hidden: false,
disabled: false,
},
ogUrl: {
value: '',
label: 'OG URL',
description: 'Canonical URL for social sharing',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
ogImage: {
value: '',
label: 'OG Image',
description: 'Image URL for social sharing',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
ogImageAlt: {
value: '',
label: 'OG Image Alt',
description: 'Alt text for social sharing image',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
ogImageWidth: {
value: '',
label: 'OG Image Width',
description: 'Width of social sharing image in pixels',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 10 },
hidden: false,
disabled: false,
},
ogImageHeight: {
value: '',
label: 'OG Image Height',
description: 'Height of social sharing image in pixels',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 10 },
hidden: false,
disabled: false,
},
ogSiteName: {
value: '',
label: 'OG Site Name',
description: 'Name of the website',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
ogLocale: {
value: 'en_US',
label: 'OG Locale',
description: 'Locale of the content (en_US, etc.)',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 10 },
hidden: false,
disabled: false,
},
twitterCard: {
value: 'summary',
label: 'Twitter Card Type',
description: 'Type of Twitter card (summary, summary_large_image, etc.)',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 30 },
hidden: false,
disabled: false,
},
twitterTitle: {
value: '',
label: 'Twitter Title',
description: 'Title for Twitter sharing',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 70 },
hidden: false,
disabled: false,
},
twitterDescription: {
value: '',
label: 'Twitter Description',
description: 'Description for Twitter sharing',
type: FieldTypes.TEXTAREA,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
twitterImage: {
value: '',
label: 'Twitter Image',
description: 'Image URL for Twitter sharing',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
twitterImageAlt: {
value: '',
label: 'Twitter Image Alt',
description: 'Alt text for Twitter image',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
twitterSite: {
value: '',
label: 'Twitter Site',
description: '@username of website',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 50 },
hidden: false,
disabled: false,
},
twitterCreator: {
value: '',
label: 'Twitter Creator',
description: '@username of content creator',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 50 },
hidden: false,
disabled: false,
},
fbAppId: {
value: '',
label: 'Facebook App ID',
description: 'For Facebook Insights',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 50 },
hidden: false,
disabled: false,
},
pinterestDescription: {
value: '',
label: 'Pinterest Description',
description: 'Description for Pinterest sharing',
type: FieldTypes.TEXTAREA,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
pinterestMedia: {
value: '',
label: 'Pinterest Media',
description: 'Image URL for Pinterest sharing',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
pinterestNoPin: {
value: false,
label: 'Pinterest No Pin',
description: 'Disable pinning of this page',
type: FieldTypes.CHECKBOX,
validation: { required: false },
hidden: false,
disabled: false,
},
},
hidden: false,
disabled: false,
},
mobileApp: {
label: 'Mobile & App-Specific',
fields: {
appleTouchIcon: {
value: '',
label: 'Apple Touch Icon',
description: 'URL of icon for iOS home screen',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
appleMobileWebAppCapable: {
value: false,
label: 'Apple Mobile Web App Capable',
description: 'Enable full-screen mode on iOS',
type: FieldTypes.CHECKBOX,
validation: { required: false },
hidden: false,
disabled: false,
},
appleStatusBarStyle: {
value: 'default',
label: 'Apple Status Bar Style',
description: 'Style of iOS status bar (default, black, black-translucent)',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 20 },
hidden: false,
disabled: false,
},
mobileWebAppCapable: {
value: false,
label: 'Mobile Web App Capable',
description: 'Enable full-screen mode on Android',
type: FieldTypes.CHECKBOX,
validation: { required: false },
hidden: false,
disabled: false,
},
themeColor: {
value: '',
label: 'Theme Color',
description: 'Color of browser UI (hex code)',
type: FieldTypes.TEXT,
validation: {
required: false,
maxLength: 7,
pattern: /^#[0-9A-F]{6}$/i,
},
hidden: false,
disabled: false,
},
msTileColor: {
value: '',
label: 'Microsoft Tile Color',
description: 'Color of Windows tile (hex code)',
type: FieldTypes.TEXT,
validation: {
required: false,
maxLength: 7,
pattern: /^#[0-9A-F]{6}$/i,
},
hidden: false,
disabled: false,
},
msTileImage: {
value: '',
label: 'Microsoft Tile Image',
description: 'Image URL for Windows tile',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
},
hidden: false,
disabled: false,
},
structuredData: {
label: 'Structured Data',
fields: {
jsonLd: {
value: '',
label: 'JSON-LD',
description: 'Structured data in JSON-LD format',
type: FieldTypes.TEXTAREA,
validation: { required: false },
hidden: false,
disabled: false,
},
},
hidden: false,
disabled: false,
},
alternateTags: {
label: 'Alternate Tags',
fields: {
hreflang: {
value: [],
label: 'Hreflang',
description: 'Alternate language versions (format: language-code|url)',
type: FieldTypes.ARRAY,
validation: { required: false },
hidden: false,
disabled: false,
},
alternateMedia: {
value: '',
label: 'Alternate Media',
description: 'Media query for alternate version',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
alternateFormat: {
value: '',
label: 'Alternate Format',
description: 'Alternate format (e.g., PDF)',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 50 },
hidden: false,
disabled: false,
},
},
hidden: false,
disabled: false,
},
siteVerification: {
label: 'Site Verification',
fields: {
googleSiteVerification: {
value: '',
label: 'Google Verification',
description: 'Google Search Console verification code',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
bingSiteVerification: {
value: '',
label: 'Bing Verification',
description: 'Bing Webmaster Tools verification code',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
yandexVerification: {
value: '',
label: 'Yandex Verification',
description: 'Yandex Webmaster verification code',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
pinterestVerification: {
value: '',
label: 'Pinterest Verification',
description: 'Pinterest verification code',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
facebookDomainVerification: {
value: '',
label: 'Facebook Verification',
description: 'Facebook Domain verification code',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
nortonSafeWeb: {
value: '',
label: 'Norton Safe Web',
description: 'Norton Safe Web verification code',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 100 },
hidden: false,
disabled: false,
},
},
hidden: false,
disabled: false,
},
securityPrivacy: {
label: 'Security & Privacy',
fields: {
referrerPolicy: {
value: 'strict-origin-when-cross-origin',
label: 'Referrer Policy',
description: 'Controls referrer information sent with requests',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 50 },
hidden: false,
disabled: false,
},
contentSecurityPolicy: {
value: '',
label: 'Content Security Policy',
description: 'Security policy for resources',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 500 },
hidden: false,
disabled: false,
},
permissionsPolicy: {
value: '',
label: 'Permissions Policy',
description: 'Controls browser features and APIs',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 500 },
hidden: false,
disabled: false,
},
xuaCompatible: {
value: 'IE=edge',
label: 'X-UA-Compatible',
description: 'Forces IE to use latest rendering engine',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 50 },
hidden: false,
disabled: false,
},
xframeOptions: {
value: 'SAMEORIGIN',
label: 'X-Frame-Options',
description: 'Controls if page can be displayed in a frame',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 50 },
hidden: false,
disabled: false,
},
},
hidden: false,
disabled: false,
},
performance: {
label: 'Performance',
fields: {
preconnect: {
value: [],
label: 'Preconnect',
description: 'Domains to preconnect to',
type: FieldTypes.ARRAY,
validation: { required: false },
hidden: false,
disabled: false,
},
prefetch: {
value: [],
label: 'Prefetch',
description: 'Resources to prefetch',
type: FieldTypes.ARRAY,
validation: { required: false },
hidden: false,
disabled: false,
},
preload: {
value: [],
label: 'Preload',
description: 'Critical resources to preload',
type: FieldTypes.ARRAY,
validation: { required: false },
hidden: false,
disabled: false,
},
dnsPrefetch: {
value: [],
label: 'DNS-Prefetch',
description: 'Domains to DNS-prefetch',
type: FieldTypes.ARRAY,
validation: { required: false },
hidden: false,
disabled: false,
},
prerender: {
value: '',
label: 'Prerender',
description: 'URL to prerender',
type: FieldTypes.TEXT,
validation: { required: false, maxLength: 200 },
hidden: false,
disabled: false,
},
},
hidden: false,
disabled: false,
},
},
disabled: false,
};
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Seo, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: Seo, isStandalone: true, selector: "pp-seo", inputs: { properties: "properties" }, outputs: { propertiesChange: "propertiesChange" }, host: { attributes: { "ngSkipHydration": "true" } }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => Seo),
multi: true,
},
], ngImport: i0, template: "<form [class.disabled]=\"properties.disabled\">\n <!-- Core Meta Tags -->\n @if (!properties.sections.coreMeta.hidden) {\n <h3>{{ properties.sections.coreMeta.label }}</h3>\n @for (field of properties.sections.coreMeta.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-coreMeta-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) {\n <input\n [id]=\"'seo-coreMeta-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('coreMeta', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['coreMeta']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.coreMeta.disabled || properties.disabled)\"\n />\n } @case (FIELD_TYPES.TEXTAREA) {\n <textarea\n [id]=\"'seo-coreMeta-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('coreMeta', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['coreMeta']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.coreMeta.disabled || properties.disabled)\"\n ></textarea>\n } @case (FIELD_TYPES.ARRAY) {\n <div class=\"custom-badge-group\">\n <div class=\"badge-list\">\n @for (item of field.value.value; track item) {\n <span class=\"badge\">\n {{ item }}\n <button\n type=\"button\"\n (click)=\"removeArrayItem('coreMeta', field.key, field.value.value.indexOf(item))\"\n [disabled]=\"!!(field.value.disabled || properties.sections.coreMeta.disabled || properties.disabled)\"\n >\n ×\n </button>\n </span>\n }\n </div>\n <input\n type=\"text\"\n [placeholder]=\"field.value.description\"\n [(ngModel)]=\"field.value._input\"\n [ngModelOptions]=\"{standalone: true}\"\n (keydown.enter)=\"addArrayItem('coreMeta', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.coreMeta.disabled || properties.disabled)\"\n class=\"custom-input\"\n />\n </div>\n } @case (FIELD_TYPES.DROPDOWN) {\n <select\n [id]=\"'seo-coreMeta-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('coreMeta', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['coreMeta']?.[field.key] }\"\n [disabled]=\"!!(field.value.disabled || properties.sections.coreMeta.disabled || properties.disabled)\"\n >\n @for (opt of field.value.options; track opt.value) {\n <option [value]=\"opt.value\">{{ opt.title }}</option>\n }\n </select>\n } @case (FIELD_TYPES.CHECKBOX) {\n <input\n type=\"checkbox\"\n [id]=\"'seo-coreMeta-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('coreMeta', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.coreMeta.disabled || properties.disabled)\"\n />\n } } @if (invalidFields['coreMeta']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (field.value.validation.pattern) { Must match required pattern. } @if\n (!invalidFields['coreMeta']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Crawling & Indexing -->\n @if (!properties.sections.crawlingAndIndex.hidden) {\n <h3>{{ properties.sections.crawlingAndIndex.label }}</h3>\n @for (field of properties.sections.crawlingAndIndex.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-crawling-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) {\n <input\n [id]=\"'seo-crawling-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('crawlingAndIndex', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['crawlingAndIndex']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.crawlingAndIndex.disabled || properties.disabled)\"\n />\n } @case (FIELD_TYPES.CHECKBOX) {\n <input\n type=\"checkbox\"\n [id]=\"'seo-crawling-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('crawlingAndIndex', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.crawlingAndIndex.disabled || properties.disabled)\"\n />\n } } @if (invalidFields['crawlingAndIndex']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (field.value.validation.pattern) { Must match required pattern. } @if\n (!invalidFields['crawlingAndIndex']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Cache Control -->\n @if (!properties.sections.cacheControl.hidden) {\n <h3>{{ properties.sections.cacheControl.label }}</h3>\n @for (field of properties.sections.cacheControl.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-cache-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n <input\n [id]=\"'seo-cache-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('cacheControl', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['cacheControl']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.cacheControl.disabled || properties.disabled)\"\n />\n\n @if (invalidFields['cacheControl']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (!invalidFields['cacheControl']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Social Media -->\n @if (!properties.sections.socialMedia.hidden) {\n <h3>{{ properties.sections.socialMedia.label }}</h3>\n @for (field of properties.sections.socialMedia.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-social-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) {\n <input\n [id]=\"'seo-social-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('socialMedia', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['socialMedia']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.socialMedia.disabled || properties.disabled)\"\n />\n } @case (FIELD_TYPES.TEXTAREA) {\n <textarea\n [id]=\"'seo-social-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('socialMedia', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['socialMedia']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.socialMedia.disabled || properties.disabled)\"\n ></textarea>\n } @case (FIELD_TYPES.CHECKBOX) {\n <input\n type=\"checkbox\"\n [id]=\"'seo-social-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('socialMedia', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.socialMedia.disabled || properties.disabled)\"\n />\n } } @if (invalidFields['socialMedia']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (!invalidFields['socialMedia']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Mobile & App-Specific -->\n @if (!properties.sections.mobileApp.hidden) {\n <h3>{{ properties.sections.mobileApp.label }}</h3>\n @for (field of properties.sections.mobileApp.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-mobile-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) {\n <input\n [id]=\"'seo-mobile-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('mobileApp', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['mobileApp']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.mobileApp.disabled || properties.disabled)\"\n />\n } @case (FIELD_TYPES.CHECKBOX) {\n <input\n type=\"checkbox\"\n [id]=\"'seo-mobile-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('mobileApp', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.mobileApp.disabled || properties.disabled)\"\n />\n } } @if (invalidFields['mobileApp']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (field.value.validation.pattern) { Must match required pattern. } @if\n (!invalidFields['mobileApp']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Structured Data -->\n @if (!properties.sections.structuredData.hidden) {\n <h3>{{ properties.sections.structuredData.label }}</h3>\n @for (field of properties.sections.structuredData.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-structured-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n <textarea\n [id]=\"'seo-structured-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('structuredData', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['structuredData']?.[field.key] }\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.structuredData.disabled || properties.disabled)\"\n ></textarea>\n\n @if (invalidFields['structuredData']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. }\n </small>\n } @if (!invalidFields['structuredData']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Alternate Tags -->\n @if (!properties.sections.alternateTags.hidden) {\n <h3>{{ properties.sections.alternateTags.label }}</h3>\n @for (field of properties.sections.alternateTags.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-alternate-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n @switch (field.value.type) { @case (FIELD_TYPES.ARRAY) {\n <div class=\"custom-badge-group\">\n <div class=\"badge-list\">\n @for (item of field.value.value; track item) {\n <span class=\"badge\">\n {{ item }}\n <button\n type=\"button\"\n (click)=\"removeArrayItem('alternateTags', field.key, field.value.value.indexOf(item))\"\n [disabled]=\"!!(field.value.disabled || properties.sections.alternateTags.disabled || properties.disabled)\"\n >\n ×\n </button>\n </span>\n }\n </div>\n <input\n type=\"text\"\n [placeholder]=\"field.value.description\"\n [(ngModel)]=\"field.value._input\"\n [ngModelOptions]=\"{standalone: true}\"\n (keydown.enter)=\"addArrayItem('alternateTags', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.alternateTags.disabled || properties.disabled)\"\n class=\"custom-input\"\n />\n </div>\n } @case (FIELD_TYPES.TEXT) {\n <input\n [id]=\"'seo-alternate-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('alternateTags', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['alternateTags']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.alternateTags.disabled || properties.disabled)\"\n />\n } } @if (invalidFields['alternateTags']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (!invalidFields['alternateTags']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Site Verification -->\n @if (!properties.sections.siteVerification.hidden) {\n <h3>{{ properties.sections.siteVerification.label }}</h3>\n @for (field of properties.sections.siteVerification.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-verification-' + field.key\"\n >{{ field.value.label }}</label\n >\n\n <input\n [id]=\"'seo-verification-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('siteVerification', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['siteVerification']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.siteVerification.disabled || properties.disabled)\"\n />\n\n @if (invalidFields['siteVerification']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (!invalidFields['siteVerification']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Security & Privacy -->\n @if (!properties.sections.securityPrivacy.hidden) {\n <h3>{{ properties.sections.securityPrivacy.label }}</h3>\n @for (field of properties.sections.securityPrivacy.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-security-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n <input\n [id]=\"'seo-security-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('securityPrivacy', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['securityPrivacy']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.securityPrivacy.disabled || properties.disabled)\"\n />\n\n @if (invalidFields['securityPrivacy']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (!invalidFields['securityPrivacy']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Performance -->\n @if (!properties.sections.performance.hidden) {\n <h3>{{ properties.sections.performance.label }}</h3>\n @for (field of properties.sections.performance.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-performance-' + field.key\"\n >{{ field.value.label }}</label\n >\n\n @switch (field.value.type) { @case (FIELD_TYPES.ARRAY) {\n <div class=\"custom-badge-group\">\n <div class=\"badge-list\">\n @for (item of field.value.value; track item) {\n <span class=\"badge\">\n {{ item }}\n <button\n type=\"button\"\n (click)=\"removeArrayItem('performance', field.key, field.value.value.indexOf(item))\"\n [disabled]=\"!!(field.value.disabled || properties.sections.performance.disabled || properties.disabled)\"\n >\n ×\n </button>\n </span>\n }\n </div>\n <input\n type=\"text\"\n [placeholder]=\"field.value.description\"\n [(ngModel)]=\"field.value._input\"\n [ngModelOptions]=\"{standalone: true}\"\n (keydown.enter)=\"addArrayItem('performance', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.performance.disabled || properties.disabled)\"\n class=\"custom-input\"\n />\n </div>\n } @case (FIELD_TYPES.TEXT) {\n <input\n [id]=\"'seo-performance-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('performance', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['performance']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.performance.disabled || properties.disabled)\"\n />\n } } @if (invalidFields['performance']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (!invalidFields['performance']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n</form>\n", styles: [".custom-input-group,.custom-badge-group{display:flex;flex-direction:column;margin-bottom:1.5rem}.custom-input-group label,.custom-badge-group label{font-size:1rem;color:#495057;margin-bottom:.5rem;font-weight:500}.input-description{font-size:.85rem;color:#888;margin-top:.25rem}.custom-input{padding:.75rem 1rem;border:1px solid #ced4da;border-radius:4px;font-size:1rem;transition:border-color .2s,box-shadow .2s;outline:none;background:#fff;color:#495057;box-shadow:none}.custom-input:focus{border-color:#2196f3;box-shadow:0 0 0 .2rem #2196f326}.custom-input:disabled{background:#f5f5f5;color:#bdbdbd}.custom-input.invalid{border-color:#e53935;box-shadow:0 0 0 .2rem #e5393526}.badge-list{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:.5rem}.badge{display:inline-flex;align-items:center;background:#e3f2fd;color:#1976d2;border-radius:12px;padding:.25rem .75rem;font-size:.95rem;margin-right:.25rem;margin-bottom:.25rem;border:1px solid #90caf9}.badge button{background:none;border:none;color:#1976d2;font-size:1.1em;margin-left:.5em;cursor:pointer;padding:0;line-height:1}.badge button:disabled{color:#bdbdbd;cursor:not-allowed}form.disabled{pointer-events:none;opacity:.7}select.custom-input{min-width:120px}textarea.custom-input{min-height:80px;resize:vertical}.input-hint{display:inline-block;margin-left:.5rem;font-size:.92em;color:#7b8a99;background:#f4f7fa;border-radius:8px;padding:.1em .7em;font-style:italic;vertical-align:middle}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i2.KeyValuePipe, name: "keyvalue" }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Seo, decorators: [{
type: Component,
args: [{ selector: 'pp-seo', standalone: true, imports: [FormsModule, CommonModule], providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => Seo),
multi: true,
},
], host: {
ngSkipHydration: 'true',
}, template: "<form [class.disabled]=\"properties.disabled\">\n <!-- Core Meta Tags -->\n @if (!properties.sections.coreMeta.hidden) {\n <h3>{{ properties.sections.coreMeta.label }}</h3>\n @for (field of properties.sections.coreMeta.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-coreMeta-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) {\n <input\n [id]=\"'seo-coreMeta-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('coreMeta', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['coreMeta']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.coreMeta.disabled || properties.disabled)\"\n />\n } @case (FIELD_TYPES.TEXTAREA) {\n <textarea\n [id]=\"'seo-coreMeta-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('coreMeta', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['coreMeta']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.coreMeta.disabled || properties.disabled)\"\n ></textarea>\n } @case (FIELD_TYPES.ARRAY) {\n <div class=\"custom-badge-group\">\n <div class=\"badge-list\">\n @for (item of field.value.value; track item) {\n <span class=\"badge\">\n {{ item }}\n <button\n type=\"button\"\n (click)=\"removeArrayItem('coreMeta', field.key, field.value.value.indexOf(item))\"\n [disabled]=\"!!(field.value.disabled || properties.sections.coreMeta.disabled || properties.disabled)\"\n >\n ×\n </button>\n </span>\n }\n </div>\n <input\n type=\"text\"\n [placeholder]=\"field.value.description\"\n [(ngModel)]=\"field.value._input\"\n [ngModelOptions]=\"{standalone: true}\"\n (keydown.enter)=\"addArrayItem('coreMeta', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.coreMeta.disabled || properties.disabled)\"\n class=\"custom-input\"\n />\n </div>\n } @case (FIELD_TYPES.DROPDOWN) {\n <select\n [id]=\"'seo-coreMeta-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('coreMeta', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['coreMeta']?.[field.key] }\"\n [disabled]=\"!!(field.value.disabled || properties.sections.coreMeta.disabled || properties.disabled)\"\n >\n @for (opt of field.value.options; track opt.value) {\n <option [value]=\"opt.value\">{{ opt.title }}</option>\n }\n </select>\n } @case (FIELD_TYPES.CHECKBOX) {\n <input\n type=\"checkbox\"\n [id]=\"'seo-coreMeta-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('coreMeta', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.coreMeta.disabled || properties.disabled)\"\n />\n } } @if (invalidFields['coreMeta']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (field.value.validation.pattern) { Must match required pattern. } @if\n (!invalidFields['coreMeta']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Crawling & Indexing -->\n @if (!properties.sections.crawlingAndIndex.hidden) {\n <h3>{{ properties.sections.crawlingAndIndex.label }}</h3>\n @for (field of properties.sections.crawlingAndIndex.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-crawling-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) {\n <input\n [id]=\"'seo-crawling-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('crawlingAndIndex', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['crawlingAndIndex']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.crawlingAndIndex.disabled || properties.disabled)\"\n />\n } @case (FIELD_TYPES.CHECKBOX) {\n <input\n type=\"checkbox\"\n [id]=\"'seo-crawling-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('crawlingAndIndex', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.crawlingAndIndex.disabled || properties.disabled)\"\n />\n } } @if (invalidFields['crawlingAndIndex']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (field.value.validation.pattern) { Must match required pattern. } @if\n (!invalidFields['crawlingAndIndex']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Cache Control -->\n @if (!properties.sections.cacheControl.hidden) {\n <h3>{{ properties.sections.cacheControl.label }}</h3>\n @for (field of properties.sections.cacheControl.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-cache-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n <input\n [id]=\"'seo-cache-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('cacheControl', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['cacheControl']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.cacheControl.disabled || properties.disabled)\"\n />\n\n @if (invalidFields['cacheControl']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (!invalidFields['cacheControl']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Social Media -->\n @if (!properties.sections.socialMedia.hidden) {\n <h3>{{ properties.sections.socialMedia.label }}</h3>\n @for (field of properties.sections.socialMedia.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-social-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) {\n <input\n [id]=\"'seo-social-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('socialMedia', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['socialMedia']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.socialMedia.disabled || properties.disabled)\"\n />\n } @case (FIELD_TYPES.TEXTAREA) {\n <textarea\n [id]=\"'seo-social-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('socialMedia', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['socialMedia']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.socialMedia.disabled || properties.disabled)\"\n ></textarea>\n } @case (FIELD_TYPES.CHECKBOX) {\n <input\n type=\"checkbox\"\n [id]=\"'seo-social-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('socialMedia', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.socialMedia.disabled || properties.disabled)\"\n />\n } } @if (invalidFields['socialMedia']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (!invalidFields['socialMedia']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Mobile & App-Specific -->\n @if (!properties.sections.mobileApp.hidden) {\n <h3>{{ properties.sections.mobileApp.label }}</h3>\n @for (field of properties.sections.mobileApp.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-mobile-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) {\n <input\n [id]=\"'seo-mobile-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('mobileApp', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['mobileApp']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.mobileApp.disabled || properties.disabled)\"\n />\n } @case (FIELD_TYPES.CHECKBOX) {\n <input\n type=\"checkbox\"\n [id]=\"'seo-mobile-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('mobileApp', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.mobileApp.disabled || properties.disabled)\"\n />\n } } @if (invalidFields['mobileApp']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (field.value.validation.pattern) { Must match required pattern. } @if\n (!invalidFields['mobileApp']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Structured Data -->\n @if (!properties.sections.structuredData.hidden) {\n <h3>{{ properties.sections.structuredData.label }}</h3>\n @for (field of properties.sections.structuredData.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-structured-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n <textarea\n [id]=\"'seo-structured-' + field.key\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('structuredData', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['structuredData']?.[field.key] }\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.structuredData.disabled || properties.disabled)\"\n ></textarea>\n\n @if (invalidFields['structuredData']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. }\n </small>\n } @if (!invalidFields['structuredData']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Alternate Tags -->\n @if (!properties.sections.alternateTags.hidden) {\n <h3>{{ properties.sections.alternateTags.label }}</h3>\n @for (field of properties.sections.alternateTags.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-alternate-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n @switch (field.value.type) { @case (FIELD_TYPES.ARRAY) {\n <div class=\"custom-badge-group\">\n <div class=\"badge-list\">\n @for (item of field.value.value; track item) {\n <span class=\"badge\">\n {{ item }}\n <button\n type=\"button\"\n (click)=\"removeArrayItem('alternateTags', field.key, field.value.value.indexOf(item))\"\n [disabled]=\"!!(field.value.disabled || properties.sections.alternateTags.disabled || properties.disabled)\"\n >\n ×\n </button>\n </span>\n }\n </div>\n <input\n type=\"text\"\n [placeholder]=\"field.value.description\"\n [(ngModel)]=\"field.value._input\"\n [ngModelOptions]=\"{standalone: true}\"\n (keydown.enter)=\"addArrayItem('alternateTags', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.alternateTags.disabled || properties.disabled)\"\n class=\"custom-input\"\n />\n </div>\n } @case (FIELD_TYPES.TEXT) {\n <input\n [id]=\"'seo-alternate-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('alternateTags', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['alternateTags']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.alternateTags.disabled || properties.disabled)\"\n />\n } } @if (invalidFields['alternateTags']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (!invalidFields['alternateTags']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Site Verification -->\n @if (!properties.sections.siteVerification.hidden) {\n <h3>{{ properties.sections.siteVerification.label }}</h3>\n @for (field of properties.sections.siteVerification.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-verification-' + field.key\"\n >{{ field.value.label }}</label\n >\n\n <input\n [id]=\"'seo-verification-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('siteVerification', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['siteVerification']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.siteVerification.disabled || properties.disabled)\"\n />\n\n @if (invalidFields['siteVerification']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (!invalidFields['siteVerification']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Security & Privacy -->\n @if (!properties.sections.securityPrivacy.hidden) {\n <h3>{{ properties.sections.securityPrivacy.label }}</h3>\n @for (field of properties.sections.securityPrivacy.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-security-' + field.key\"\n >{{ field.value.label }} @if (properties.hints !== false) {\n <span class=\"input-hint\">\n @switch (field.value.type) { @case (FIELD_TYPES.TEXT) { {{HINTS.TEXT}} }\n @case (FIELD_TYPES.TEXTAREA) { {{HINTS.TEXTAREA}} } @case\n (FIELD_TYPES.ARRAY) { {{HINTS.ARRAY}} } @case (FIELD_TYPES.DROPDOWN) {\n {{HINTS.DROPDOWN}} } @case (FIELD_TYPES.CHECKBOX) { {{HINTS.CHECKBOX}} }\n @default { {{HINTS.DEFAULT}} } }\n </span>\n }</label\n >\n\n <input\n [id]=\"'seo-security-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('securityPrivacy', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['securityPrivacy']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.securityPrivacy.disabled || properties.disabled)\"\n />\n\n @if (invalidFields['securityPrivacy']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (!invalidFields['securityPrivacy']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n\n <!-- Performance -->\n @if (!properties.sections.performance.hidden) {\n <h3>{{ properties.sections.performance.label }}</h3>\n @for (field of properties.sections.performance.fields | keyvalue; track\n field.key) { @if (!field.value.hidden) {\n <div class=\"custom-input-group\">\n <label [for]=\"'seo-performance-' + field.key\"\n >{{ field.value.label }}</label\n >\n\n @switch (field.value.type) { @case (FIELD_TYPES.ARRAY) {\n <div class=\"custom-badge-group\">\n <div class=\"badge-list\">\n @for (item of field.value.value; track item) {\n <span class=\"badge\">\n {{ item }}\n <button\n type=\"button\"\n (click)=\"removeArrayItem('performance', field.key, field.value.value.indexOf(item))\"\n [disabled]=\"!!(field.value.disabled || properties.sections.performance.disabled || properties.disabled)\"\n >\n ×\n </button>\n </span>\n }\n </div>\n <input\n type=\"text\"\n [placeholder]=\"field.value.description\"\n [(ngModel)]=\"field.value._input\"\n [ngModelOptions]=\"{standalone: true}\"\n (keydown.enter)=\"addArrayItem('performance', field.key, $event)\"\n [disabled]=\"!!(field.value.disabled || properties.sections.performance.disabled || properties.disabled)\"\n class=\"custom-input\"\n />\n </div>\n } @case (FIELD_TYPES.TEXT) {\n <input\n [id]=\"'seo-performance-' + field.key\"\n type=\"text\"\n [ngModel]=\"field.value.value\"\n [ngModelOptions]=\"{standalone: true}\"\n (ngModelChange)=\"updateProperty('performance', field.key, $event)\"\n class=\"custom-input\"\n [ngClass]=\"{ 'invalid': invalidFields['performance']?.[field.key] }\"\n [attr.maxLength]=\"field.value.validation.maxLength\"\n [attr.required]=\"field.value.validation.required ? true : null\"\n [placeholder]=\"field.value.description\"\n [disabled]=\"!!(field.value.disabled || properties.sections.performance.disabled || properties.disabled)\"\n />\n } } @if (invalidFields['performance']?.[field.key]) {\n <small class=\"input-description\" style=\"color: #e53935\">\n {{ field.value.label }} is invalid. @if (field.value.validation.required)\n { It is required. } @if (field.value.validation.maxLength) { Must be at\n most {{ field.value.validation.maxLength }} characters. }\n </small>\n } @if (!invalidFields['performance']?.[field.key]) {\n <small class=\"input-description\"> {{ field.value.description }} </small>\n }\n </div>\n } } }\n</form>\n", styles: [".custom-input-group,.custom-badge-group{display:flex;flex-direction:column;margin-bottom:1.5rem}.custom-input-group label,.custom-badge-group label{font-size:1rem;color:#495057;margin-bottom:.5rem;font-weight:500}.input-description{font-size:.85rem;color:#888;margin-top:.25rem}.custom-input{padding:.75rem 1rem;border:1px solid #ced4da;border-radius:4px;font-size:1rem;transition:border-color .2s,box-shadow .2s;outline:none;background:#fff;color:#495057;box-shadow:none}.custom-input:focus{border-color:#2196f3;box-shadow:0 0 0 .2rem #2196f326}.custom-input:disabled{background:#f5f5f5;color:#bdbdbd}.custom-input.invalid{border-color:#e53935;box-shadow:0 0 0 .2rem #e5393526}.badge-list{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:.5rem}.badge{display:inline-flex;align-items:center;background:#e3f2fd;color:#1976d2;border-radius:12px;padding:.25rem .75rem;font-size:.95rem;margin-right:.25rem;margin-bottom:.25rem;border:1px solid #90caf9}.badge button{background:none;border:none;color:#1976d2;font-size:1.1em;margin-left:.5em;cursor:pointer;padding:0;line-height:1}.badge button:disabled{color:#bdbdbd;cursor:not-allowed}form.disabled{pointer-events:none;opacity:.7}select.custom-input{min-width:120px}textarea.custom-input{min-height:80px;resize:vertical}.input-hint{display:inline-block;margin-left:.5rem;font-size:.92em;color:#7b8a99;background:#f4f7fa;border-radius:8px;padding:.1em .7em;font-style:italic;vertical-align:middle}\n"] }]
}], propDecorators: { properties: [{
type: Input
}], propertiesChange: [{
type: Output
}] } });
/*
* Public API Surface of pp-seo
*/
/**
* Generated bundle index. Do not edit.
*/
export { FieldTypes, Seo };
//# sourceMappingURL=pp-seo.mjs.map