const TEMPLATES = { vinted: { name: 'Vinted Classic', wrap(listing) { const tags = Array.isArray(listing.tags) ? listing.tags.join(' ') : listing.tags || ''; return `

${richDescription(listing)}

${listing.brand || listing.size || listing.color ? ` ${listing.brand ? `` : ''} ${listing.category ? `` : ''} ${listing.size ? `` : ''} ${listing.color ? `` : ''} ${listing.condition ? `` : ''}
Marke${esc(listing.brand)}
Kategorie${esc(listing.category)}
Größe${esc(listing.size)}
Farbe${esc(listing.color)}
Zustand${esc(listing.condition)}
` : ''} ${tags ? `

${esc(tags)}

` : ''}
`; }, }, 'ebay-ka': { name: 'Kleinanzeigen Modern', wrap(listing) { return `

${richDescription(listing)}

${listing.brand || listing.size ? `

Artikeldetails

${listing.brand ? `
Marke: ${esc(listing.brand)}
` : ''} ${listing.category ? `
Kategorie: ${esc(listing.category)}
` : ''} ${listing.size ? `
Größe: ${esc(listing.size)}
` : ''} ${listing.color ? `
Farbe: ${esc(listing.color)}
` : ''} ${listing.condition ? `
Zustand: ${esc(listing.condition)}
` : ''}
` : ''}
`; }, }, 'ebay-de': { name: 'eBay Professional', wrap(listing) { return `

${esc(listing.title)}

${richDescription(listing)}

${listing.brand || listing.size ? `
Artikelmerkmale
${listing.brand ? `` : ''} ${listing.category ? `` : ''} ${listing.size ? `` : ''} ${listing.color ? `` : ''} ${listing.condition ? `` : ''}
Marke${esc(listing.brand)}
Typ${esc(listing.category)}
Größe${esc(listing.size)}
Farbe${esc(listing.color)}
Zustand${esc(listing.condition)}
` : ''}

Privatverkauf — keine Garantie, keine Rücknahme.

`; }, }, etsy: { name: 'Etsy Handmade', wrap(listing) { const tags = Array.isArray(listing.tags) ? listing.tags : []; return `

${richDescription(listing)}

${listing.brand || listing.size ? `

Details

${listing.brand ? `
Brand: ${esc(listing.brand)}
` : ''} ${listing.category ? `
Category: ${esc(listing.category)}
` : ''} ${listing.size ? `
Size: ${esc(listing.size)}
` : ''} ${listing.color ? `
Color: ${esc(listing.color)}
` : ''} ${listing.condition ? `
Condition: ${esc(listing.condition)}
` : ''}
` : ''} ${tags.length ? `

${tags.map(t => `${esc(t)}`).join(' ')}

` : ''}
`; }, }, }; function esc(s) { return String(s || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } function nl2br(s) { return esc(s || '').replace(/\n/g, '
'); } const ALLOWED_RICH_TAGS = new Set([ 'p', 'br', 'strong', 'b', 'em', 'i', 'u', 's', 'strike', 'ol', 'ul', 'li', 'blockquote', 'a', 'h1', 'h2', 'h3', 'span', 'div', 'sub', 'sup', 'code', 'pre', ]); const VOID_RICH_TAGS = new Set(['br']); function sanitizeStyle(style) { const safe = []; for (const declaration of String(style || '').split(';')) { const [rawName, ...rest] = declaration.split(':'); const name = String(rawName || '').trim().toLowerCase(); const value = rest.join(':').trim(); if (!value) continue; if (!['color', 'background-color', 'text-align'].includes(name)) continue; if (/url\s*\(|expression\s*\(|javascript:/i.test(value)) continue; if (name === 'text-align' && !/^(left|right|center|justify)$/i.test(value)) continue; if (['color', 'background-color'].includes(name) && !/^(#[0-9a-f]{3,8}|rgba?\([\d\s.,%]+\)|hsla?\([\d\s.,%]+\)|[a-z]{3,20})$/i.test(value)) continue; safe.push(`${name}:${value}`); } return safe.join(';'); } export function sanitizeRichHtml(input) { let html = String(input || ''); html = html .replace(//g, '') .replace(/<(script|style|iframe|object|embed|form|input|button|textarea|select|option|svg|math|video|audio)[^>]*>[\s\S]*?<\/\1\s*>/gi, '') .replace(/<(script|style|iframe|object|embed|form|input|button|textarea|select|option|svg|math|video|audio)\b[^>]*\/?\s*>/gi, ''); return html.replace(/<\/?([a-z0-9-]+)([^>]*)>/gi, (full, rawTag, rawAttrs) => { const tag = String(rawTag || '').toLowerCase(); const closing = /^<\//.test(full); if (!ALLOWED_RICH_TAGS.has(tag)) return ''; if (closing) return VOID_RICH_TAGS.has(tag) ? '' : ``; const attrs = []; const source = String(rawAttrs || ''); source.replace(/([a-z0-9:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/gi, (_m, rawName, dq, sq, bare) => { const name = String(rawName || '').toLowerCase(); const value = dq ?? sq ?? bare ?? ''; if (name.startsWith('on')) return ''; if (name === 'href' && tag === 'a') { const href = String(value).trim(); if (/^(https?:|mailto:|tel:|#|\/)/i.test(href)) attrs.push(`href="${esc(href)}"`); } else if (name === 'target' && tag === 'a' && ['_blank', '_self'].includes(value)) { attrs.push(`target="${value}"`); } else if (name === 'rel' && tag === 'a') { attrs.push('rel="noopener noreferrer"'); } else if (name === 'style') { const safeStyle = sanitizeStyle(value); if (safeStyle) attrs.push(`style="${esc(safeStyle)}"`); } else if (name === 'class' && /^ql-(align|indent|size|font|color|background)-[a-z0-9-]+$/i.test(value)) { attrs.push(`class="${esc(value)}"`); } return ''; }); if (tag === 'a' && attrs.some(attr => attr.startsWith('target="_blank"')) && !attrs.some(attr => attr.startsWith('rel='))) { attrs.push('rel="noopener noreferrer"'); } return `<${tag}${attrs.length ? ` ${attrs.join(' ')}` : ''}>`; }); } function richDescription(listing) { const html = sanitizeRichHtml(listing?.description_html || ''); return html || nl2br(listing?.description || ''); } export function wrapInTemplate(listing, platformId) { const tpl = TEMPLATES[platformId]; if (!tpl) return `

${richDescription(listing)}

`; return tpl.wrap(listing); } export function getHtmlTemplates() { return Object.entries(TEMPLATES).map(([id, t]) => ({ id, name: t.name })); }