/* Kundenansicht neu (P7): gemeinsame Bausteine der vier Bestellseiten (Abo bestellen, Wechsel, Zusatzspeicher, Gutschrift-Antrag). Die Sätze kommen vom Server (GET /api/abo → bestelltexte); hier werden sie nur gezeichnet. Link-Texte stehen in der Vorlage in eckigen Klammern, ihre Adressen daneben in urls. Jede Adresse läuft durch sicherLink; Kundentext wird nie als HTML gelesen. Keine h1: den Titel meldet die Ansicht mit meldeTitel. */ import { h } from './dom.js' import { K } from './ansicht-hilfen.js' /** 'unternehmen' | 'privat': nur exakt 'unternehmen' (me.customer.kundenartWirksam, sonst daten.kundenart) ist Betrieb; alles andere privat (E4). */ export function kundenartVon(me, daten = null) { return me?.customer?.kundenartWirksam === 'unternehmen' || daten?.kundenart === 'unternehmen' ? 'unternehmen' : 'privat' } /** Adresse nur, wenn sie ein Pfad auf derselben Herkunft ('/…', nicht '//…'), ein Anker ('#name…') oder http(s) ohne Zugangsdaten ist; sonst ''. */ export function sicherLink(url) { if (typeof url !== 'string' || url === '') return '' if (/[\s\u0000-\u001f\u007f\\]/.test(url)) return '' if (/^\/(?![/\\])/.test(url)) return url if (/^#[a-z][\w-]*(\?[\w=&.%-]*)?$/i.test(url)) return url if (/^https?:\/\/[^/?#]+/i.test(url)) { try { const u = new URL(url) return u.hostname !== '' && u.username === '' && u.password === '' ? url : '' } catch { return '' } } return '' } /** * 'Es gilt [A] und [B].' mit ['/a', '/b'] → Teile [{ text }, { text, href? }]. Die Markierungen bekommen der Reihe nach ihre Adresse; * eine unsichere oder fehlende Adresse macht den Teil zu einem Teil ohne href. Eine offene Klammer bleibt Text. */ export function linkTeile(text, urls) { const basis = typeof text === 'string' ? text : '' const adressen = Array.isArray(urls) ? urls : [] const teile = [] let ab = 0 let nr = 0 const muster = /\[([^\]]*)\]/g for (let m = muster.exec(basis); m; m = muster.exec(basis)) { if (m.index > ab) teile.push({ text: basis.slice(ab, m.index) }) const href = sicherLink(adressen[nr]) nr += 1 teile.push(href ? { text: m[1], href } : { text: m[1] }) ab = m.index + m[0].length } if (ab < basis.length) teile.push({ text: basis.slice(ab) }) return teile } const ausserhalb = (href) => !href.startsWith('#') /** *
mit den Links der Vorlage; '/…' und http(s) öffnen in neuem Tab (target _blank, rel noopener), '#…' bleibt im Fenster. * klasse: Vorgabe 'kn-linkzeile'. neuerTab: auch '#…' öffnet in neuem Tab (Bedingungen der Gutschrift neben dem Formular). */ export function baueLinkzeile({ text, urls } = {}, { id, klasse = 'kn-linkzeile', neuerTab = false } = {}) { const kinder = linkTeile(text, urls).map((t) => t.href ? h('a', { href: t.href, ...(neuerTab || ausserhalb(t.href) ? { target: '_blank', rel: 'noopener' } : {}) }, t.text) : t.text) return h('p', { id, class: klasse }, kinder) } /**
: „Du bestellst als Betrieb“ | „Du bestellst privat“, ' · ', dann der Hinweis zur Korrektur mit seinem Link. */ export function baueKundenartZeile(kundenart, texte, { id } = {}) { const lage = kundenart === 'unternehmen' ? texte?.unternehmen : texte?.privat const korrektur = linkTeile(texte?.korrektur?.text, texte?.korrektur?.urls).map((t) => t.href ? h('a', { href: t.href, ...(ausserhalb(t.href) ? { target: '_blank', rel: 'noopener' } : {}) }, t.text) : t.text) return h('p', { id, class: 'kn-kundenart-zeile' }, lage, ' · ', korrektur) } /** * Block „Was du bestellst“: zeilen [{ id?, label, text, klasse?, link? }] als Beschriftungsliste. Der Text ist immer Text. * link { text, href }: hinter dem Text ' · ' und ein Link (nur mit sicherer Adresse; Adressen außerhalb der Seite öffnen in neuem Tab). */ export function baueBlock(zeilen, { id } = {}) { return h('dl', { id, class: 'kn-block' }, (zeilen || []).map((z) => { const href = z.link ? sicherLink(z.link.href) : '' const link = href ? [' · ', h('a', { href, ...(ausserhalb(href) ? { target: '_blank', rel: 'noopener' } : {}) }, z.link.text)] : null return h('div', { class: 'kn-block-zeile' }, h('dt', null, z.label), h('dd', { id: z.id, class: z.klasse }, z.text, link)) })) } /** * Die ausführlichen Sätze UNTER dem Knopf (und der Kundenart-Zeile): Überschrift „Im Einzelnen“ und dieselbe Beschriftungsliste wie der Block. * Der Block über dem Knopf trägt die Kerninfo knapp, damit sie mit dem Knopf auf einen Handy-Bildschirm passt; hier steht sie ausführlich. */ export function baueEinzelheiten(zeilen, { id } = {}) { return h('section', { id, class: 'kn-einzelheiten' }, h('h3', null, 'Im Einzelnen'), baueBlock(zeilen)) } /** Adresse der Leistungsbeschreibung: die Bedingungen (erste Adresse der Linkzeile) mit Anker ('wartung', 'hosting'); unsicher oder fehlend ''. */ export function leistungLink(linkzeile, anker) { const basis = sicherLink(Array.isArray(linkzeile?.urls) ? linkzeile.urls[0] : '') return basis && !basis.includes('#') ? `${basis}#${anker}` : '' } const nichtLeer = (x) => typeof x === 'string' && x !== '' /** * true, wenn die Antwort den Wortlaut trägt: status, knopf und die Sätze der Seite (art 'abo' | 'wechsel' | 'speicher' | 'gutschrift'). * Ohne ihn zeigt keine Seite einen Bestellknopf. */ export function hatWortlaut(daten, art = 'abo') { const t = daten?.bestelltexte if (!t || typeof t !== 'object' || !nichtLeer(t.status) || !nichtLeer(t.knopf)) return false if (art === 'gutschrift') return nichtLeer(t.gutschrift?.satz?.text) && nichtLeer(t.gutschrift?.knopf) if (!nichtLeer(t[art]?.linkzeile?.text)) return false if (art === 'wechsel') return nichtLeer(t.wechsel.haken) && nichtLeer(t.wechsel.gesperrt) if (art === 'abo') return nichtLeer(t.abo.haken) return true } export { K }