diff --git a/README.md b/README.md
index 5374973..07c3235 100644
--- a/README.md
+++ b/README.md
@@ -165,6 +165,7 @@ Cowork legt je Artikel eine Datei `artikel/artikel- ... ${artikel.kurzantwort}...
Häufige Fragen
\n${faqText}` : '',
+ faqText ? `${artikel.faq_ueberschrift || 'Häufige Fragen'}
\n${faqText}` : '',
].filter(Boolean).join('\n');
const praxis = Array.isArray(artikel.praxis) && artikel.praxis.length ? artikel.praxis
@@ -440,6 +440,7 @@ const heute = new Date().toISOString().slice(0, 10);
const meta = {
kurzantwort: a.kurzantwort || '',
+ faq_ueberschrift: a.faq_ueberschrift || '',
cta_ziel: a.cta_ziel || '',
stand_datum: a.stand_datum || heute,
faq: Array.isArray(a.faq)
@@ -659,7 +660,7 @@ const prompt = [
'Clean closed outlines, no stray marks, no hatching and no small dashes inside the shapes.',
'Object fills are flat off-white cream (#F9F7F5), no shading, no highlights.',
`Motif: ${zeile.bildmotiv}${leer}. Shown straight from the front, not tilted.`,
- 'Minimal and iconic, lots of empty space, the object takes about 35 percent of the image height.',
+ 'The whole motif sits in the exact center and stays inside the middle band of the image: it takes about 28 percent of the image height and at most 60 percent of the image width, with wide empty background above and below.',
'No text, no letters, no numbers, no currency symbols, no logo, no border, no frame, no rounded corners, no people.',
].join(' ');
diff --git a/build/stilpruefung.js b/build/stilpruefung.js
index 9bb30e4..c6c9dd6 100644
--- a/build/stilpruefung.js
+++ b/build/stilpruefung.js
@@ -99,17 +99,14 @@ function stilpruefung(artikel) {
const cta = slugVon(artikel.cta_ziel);
if (!BUSINESS.includes(cta)) add('Links: CTA-Ziel ist keine Business-Leistungsseite', `cta_ziel "${artikel.cta_ziel || 'leer'}", erlaubt: ${BUSINESS.join(', ')}`);
const ctaAnzahl = slugs.filter(s => s === cta).length;
- if (ctaAnzahl < 2) add('Links: CTA-Ziel zu selten verlinkt', `${ctaAnzahl}x im Text, mindestens 2x (im Text und im Abschluss-CTA)`);
+ if (ctaAnzahl < 1) add('Links: CTA-Ziel nicht verlinkt', 'cta_ziel mindestens einmal im Fliesstext verlinken');
const bus = slugs.filter(s => BUSINESS.includes(s));
const priv = slugs.filter(s => PRIVAT.includes(s));
if (bus.length < 2) add('Links: zu wenige Business-Links', `${bus.length} Link(s) auf Business-Seiten, mindestens 2`);
- if (priv.length > 1) add('Links: zu viele Privatkunden-Links', `${priv.length} Links, hoechstens einer im Block "Auch für Privatpersonen."`);
- if (priv.length) {
- const block = (html.split(/Auch für Privatpersonen\.<\/h3>/)[1] || '').split(/
block.includes(s));
- if (!imBlock) add('Links: Privatkunden-Link ausserhalb des Privat-Blocks', 'Privat-Links nur im Block "Auch für Privatpersonen."');
- }
- if (priv.length >= bus.length && priv.length > 0) add('Links: Privat-Links nicht in der Minderheit', `${bus.length} Business gegen ${priv.length} Privat`);
+ if (priv.length) add('Links: Privatkunden-Link im Text', 'Privatkunden-Seiten nicht aus dem Artikeltext verlinken, das uebernimmt das Seitenlayout');
+ // Bausteine, die das Seitenlayout bereits liefert
+ if (/Auch für Privatpersonen/i.test(html)) add('Baustein doppelt: "Auch für Privatpersonen."', 'Block aus dem Artikeltext entfernen, er steht im Seitenlayout');
+ if (/
+ + +
+([\\s\\S]*?)<\\/p>/g)].map(m => ohneTags(m[1])).filter(Boolean);\n const volltext = [ohneTags(artikel.kurzantwort), ohneTags(html),\n ...(Array.isArray(artikel.faq) ? artikel.faq.map(f => ohneTags(f.antwort)) : [])].join(' ');\n\n // 1) Floskeln und Formeln, die in generierten Texten gehaeuft auftreten\n const formeln = [\n ['genau das', 'Formel'], ['genau diese', 'Formel'], ['genau daran', 'Formel'], ['genau so', 'Formel'],\n ['der grund ist', 'Formel'], ['nicht nur', 'Formel \"nicht nur, sondern auch\"'],\n ['entscheidend ist', 'Formel'], ['entscheidend für', 'Formel'], ['größten hebel', 'Formel'],\n ['den unterschied', 'Formel'], ['am ende des tages', 'Floskel'], ['unterm strich', 'Floskel'],\n ['kurz gesagt', 'Floskel'], ['mit anderen worten', 'Floskel'], ['die gute nachricht', 'Floskel'],\n ['spielt eine rolle', 'Floskel'], ['es lohnt sich', 'Floskel'], ['egal ob', 'Formel'],\n ['in diesem artikel', 'Meta-Satz'], ['wir zeigen ihnen', 'Meta-Satz'], ['fazit', 'Zusammenfassung'],\n ['tatsächlich', 'Fuellwort'], ['letztlich', 'Fuellwort'], ['schlicht', 'Fuellwort'], ['simpel', 'Fuellwort'],\n ['erfahrungsgemäß', 'unbelegte Behauptung'], ['in aller regel', 'unbelegte Behauptung'],\n ['studien zeigen', 'unbelegte Behauptung'], ['nachweislich', 'unbelegte Behauptung'],\n ['bekanntlich', 'Floskel'],\n ];\n const klein = volltext.toLowerCase();\n formeln.forEach(([f, art]) => {\n let i = klein.indexOf(f);\n while (i >= 0) {\n add(art + ': \"' + f + '\"', '…' + volltext.slice(Math.max(0, i - 40), i + f.length + 50) + '…');\n i = klein.indexOf(f, i + f.length);\n }\n });\n\n // 2) Aufzaehlungen im Fliesstext\n if (/\\berstens\\b[\\s\\S]*\\bzweitens\\b/i.test(volltext)) add('Aufzaehlung \"Erstens, zweitens\"', 'Im Fliesstext lieber eine echte Liste oder zwei Saetze');\n const dreier = volltext.match(/\\b\\w+(?:ig|lich|ell|bar|iv|sam)\\b, \\w+(?:ig|lich|ell|bar|iv|sam)\\b und \\w+(?:ig|lich|ell|bar|iv|sam)\\b/gi) || [];\n dreier.forEach(d => add('Dreierreihe aus Adjektiven', d));\n\n // 3) Rhetorische Frage im Absatz, direkt beantwortet\n absaetze.forEach(a => {\n const m = a.match(/[^.!?]{8,}\\?\\s+[A-ZÄÖÜ][^.!?]{3,}[.!]/);\n if (m) add('Rhetorische Frage mit Sofortantwort', m[0]);\n });\n\n // 4) Satzlaengen: gleichfoermiger Rhythmus\n const saetze = ohneTags(html).split(/(?<=[.!?])\\s+/).map(s => s.trim())\n .filter(s => s.split(' ').length >= 3);\n const laengen = saetze.map(s => s.split(' ').length);\n if (laengen.length >= 8) {\n const mittel = laengen.reduce((x, y) => x + y, 0) / laengen.length;\n const sd = Math.sqrt(laengen.reduce((x, y) => x + (y - mittel) ** 2, 0) / laengen.length);\n if (sd < 4.5) add('Gleichfoermige Satzlaengen', `Durchschnitt ${mittel.toFixed(1)} Woerter, Streuung nur ${sd.toFixed(1)}. Kurze und lange Saetze mischen.`);\n for (let i = 0; i + 4 <= laengen.length; i++) {\n const fenster = laengen.slice(i, i + 4);\n if (Math.max(...fenster) - Math.min(...fenster) <= 3) {\n add('Vier Saetze gleicher Laenge hintereinander', saetze[i]);\n i += 3;\n }\n }\n }\n\n // 5) Gleiche Satzanfaenge\n for (let i = 0; i + 3 <= saetze.length; i++) {\n const w = saetze.slice(i, i + 3).map(s => s.split(' ')[0].toLowerCase());\n if (w[0] === w[1] && w[1] === w[2] && w[0].length > 2) { add('Drei Saetze mit gleichem Anfang', saetze[i]); i += 2; }\n }\n const anfaenge = {};\n absaetze.forEach(a => { const k = a.split(' ').slice(0, 2).join(' ').toLowerCase(); anfaenge[k] = (anfaenge[k] || 0) + 1; });\n Object.entries(anfaenge).filter(([, n]) => n >= 3).forEach(([k, n]) => add('Absatzanfaenge wiederholen sich', `${n} Absaetze beginnen mit \"${k}\"`));\n\n // 6) Gleich lange Absaetze\n const al = absaetze.map(a => a.split(' ').length).filter(n => n >= 12);\n if (al.length >= 5) {\n const m = al.reduce((x, y) => x + y, 0) / al.length;\n const sd = Math.sqrt(al.reduce((x, y) => x + (y - m) ** 2, 0) / al.length);\n if (sd / m < 0.25) add('Absaetze fast gleich lang', `Durchschnitt ${m.toFixed(0)} Woerter, kaum Abweichung. Einen Absatz bewusst kurz halten.`);\n }\n\n // 7) Praxiswissen: ohne eigene Stichpunkte bleibt der Text generisch\n if (!Array.isArray(artikel.praxis) || artikel.praxis.length === 0) {\n add('Kein Praxiswissen hinterlegt', 'Feld \"praxis\" in artikel-index.json fuellen, dann den Artikel darauf aufbauen');\n }\n\n // 8) Interne Links: primaer auf Business-Leistungsseiten\n const B = 'https://skrift.de/';\n const BUSINESS = ['handgeschriebene-businessbriefe', 'handgeschriebene-postkarten',\n 'unterschriftenservice', 'automatisierte-follow-ups'];\n const NEUTRAL = ['papier-schriftmuster', 'kontakt'];\n const PRIVAT = ['handschrift-fuer-privatpersonen', 'handgeschriebene-private-briefe',\n 'handgeschriebene-private-postkarten'];\n const slugVon = (u) => String(u || '').replace(/^https?:\\/\\/(www\\.)?skrift\\.de\\/?/, '').replace(/\\/.*$/, '');\n const links = [...html.matchAll(/]*href=\"([^\"]+)\"/g)].map(m => m[1]);\n const intern = links.filter(u => /^https?:\\/\\/(www\\.)?skrift\\.de/.test(u) || u.startsWith('/'));\n const slugs = intern.map(u => u.startsWith('/') ? u.replace(/^\\/|\\/.*$/g, '') : slugVon(u));\n\n const cta = slugVon(artikel.cta_ziel);\n if (!BUSINESS.includes(cta)) add('Links: CTA-Ziel ist keine Business-Leistungsseite', `cta_ziel \"${artikel.cta_ziel || 'leer'}\", erlaubt: ${BUSINESS.join(', ')}`);\n const ctaAnzahl = slugs.filter(s => s === cta).length;\n if (ctaAnzahl < 2) add('Links: CTA-Ziel zu selten verlinkt', `${ctaAnzahl}x im Text, mindestens 2x (im Text und im Abschluss-CTA)`);\n const bus = slugs.filter(s => BUSINESS.includes(s));\n const priv = slugs.filter(s => PRIVAT.includes(s));\n if (bus.length < 2) add('Links: zu wenige Business-Links', `${bus.length} Link(s) auf Business-Seiten, mindestens 2`);\n if (priv.length > 1) add('Links: zu viele Privatkunden-Links', `${priv.length} Links, hoechstens einer im Block \"Auch für Privatpersonen.\"`);\n if (priv.length) {\n const block = (html.split(/ ${f.antwort || ''} ${artikel.kurzantwort} ([\\s\\S]*?)<\\/p>/g)].map(m => ohneTags(m[1])).filter(Boolean);\n const volltext = [ohneTags(artikel.kurzantwort), ohneTags(html),\n ...(Array.isArray(artikel.faq) ? artikel.faq.map(f => ohneTags(f.antwort)) : [])].join(' ');\n\n // 1) Floskeln und Formeln, die in generierten Texten gehaeuft auftreten\n const formeln = [\n ['genau das', 'Formel'], ['genau diese', 'Formel'], ['genau daran', 'Formel'], ['genau so', 'Formel'],\n ['der grund ist', 'Formel'], ['nicht nur', 'Formel \"nicht nur, sondern auch\"'],\n ['entscheidend ist', 'Formel'], ['entscheidend für', 'Formel'], ['größten hebel', 'Formel'],\n ['den unterschied', 'Formel'], ['am ende des tages', 'Floskel'], ['unterm strich', 'Floskel'],\n ['kurz gesagt', 'Floskel'], ['mit anderen worten', 'Floskel'], ['die gute nachricht', 'Floskel'],\n ['spielt eine rolle', 'Floskel'], ['es lohnt sich', 'Floskel'], ['egal ob', 'Formel'],\n ['in diesem artikel', 'Meta-Satz'], ['wir zeigen ihnen', 'Meta-Satz'], ['fazit', 'Zusammenfassung'],\n ['tatsächlich', 'Fuellwort'], ['letztlich', 'Fuellwort'], ['schlicht', 'Fuellwort'], ['simpel', 'Fuellwort'],\n ['erfahrungsgemäß', 'unbelegte Behauptung'], ['in aller regel', 'unbelegte Behauptung'],\n ['studien zeigen', 'unbelegte Behauptung'], ['nachweislich', 'unbelegte Behauptung'],\n ['bekanntlich', 'Floskel'],\n ];\n const klein = volltext.toLowerCase();\n formeln.forEach(([f, art]) => {\n let i = klein.indexOf(f);\n while (i >= 0) {\n add(art + ': \"' + f + '\"', '…' + volltext.slice(Math.max(0, i - 40), i + f.length + 50) + '…');\n i = klein.indexOf(f, i + f.length);\n }\n });\n\n // 2) Aufzaehlungen im Fliesstext\n if (/\\berstens\\b[\\s\\S]*\\bzweitens\\b/i.test(volltext)) add('Aufzaehlung \"Erstens, zweitens\"', 'Im Fliesstext lieber eine echte Liste oder zwei Saetze');\n const dreier = volltext.match(/\\b\\w+(?:ig|lich|ell|bar|iv|sam)\\b, \\w+(?:ig|lich|ell|bar|iv|sam)\\b und \\w+(?:ig|lich|ell|bar|iv|sam)\\b/gi) || [];\n dreier.forEach(d => add('Dreierreihe aus Adjektiven', d));\n\n // 3) Rhetorische Frage im Absatz, direkt beantwortet\n absaetze.forEach(a => {\n const m = a.match(/[^.!?]{8,}\\?\\s+[A-ZÄÖÜ][^.!?]{3,}[.!]/);\n if (m) add('Rhetorische Frage mit Sofortantwort', m[0]);\n });\n\n // 4) Satzlaengen: gleichfoermiger Rhythmus\n const saetze = ohneTags(html).split(/(?<=[.!?])\\s+/).map(s => s.trim())\n .filter(s => s.split(' ').length >= 3);\n const laengen = saetze.map(s => s.split(' ').length);\n if (laengen.length >= 8) {\n const mittel = laengen.reduce((x, y) => x + y, 0) / laengen.length;\n const sd = Math.sqrt(laengen.reduce((x, y) => x + (y - mittel) ** 2, 0) / laengen.length);\n if (sd < 4.5) add('Gleichfoermige Satzlaengen', `Durchschnitt ${mittel.toFixed(1)} Woerter, Streuung nur ${sd.toFixed(1)}. Kurze und lange Saetze mischen.`);\n for (let i = 0; i + 4 <= laengen.length; i++) {\n const fenster = laengen.slice(i, i + 4);\n if (Math.max(...fenster) - Math.min(...fenster) <= 3) {\n add('Vier Saetze gleicher Laenge hintereinander', saetze[i]);\n i += 3;\n }\n }\n }\n\n // 5) Gleiche Satzanfaenge\n for (let i = 0; i + 3 <= saetze.length; i++) {\n const w = saetze.slice(i, i + 3).map(s => s.split(' ')[0].toLowerCase());\n if (w[0] === w[1] && w[1] === w[2] && w[0].length > 2) { add('Drei Saetze mit gleichem Anfang', saetze[i]); i += 2; }\n }\n const anfaenge = {};\n absaetze.forEach(a => { const k = a.split(' ').slice(0, 2).join(' ').toLowerCase(); anfaenge[k] = (anfaenge[k] || 0) + 1; });\n Object.entries(anfaenge).filter(([, n]) => n >= 3).forEach(([k, n]) => add('Absatzanfaenge wiederholen sich', `${n} Absaetze beginnen mit \"${k}\"`));\n\n // 6) Gleich lange Absaetze\n const al = absaetze.map(a => a.split(' ').length).filter(n => n >= 12);\n if (al.length >= 5) {\n const m = al.reduce((x, y) => x + y, 0) / al.length;\n const sd = Math.sqrt(al.reduce((x, y) => x + (y - m) ** 2, 0) / al.length);\n if (sd / m < 0.25) add('Absaetze fast gleich lang', `Durchschnitt ${m.toFixed(0)} Woerter, kaum Abweichung. Einen Absatz bewusst kurz halten.`);\n }\n\n // 7) Praxiswissen: ohne eigene Stichpunkte bleibt der Text generisch\n if (!Array.isArray(artikel.praxis) || artikel.praxis.length === 0) {\n add('Kein Praxiswissen hinterlegt', 'Feld \"praxis\" in artikel-index.json fuellen, dann den Artikel darauf aufbauen');\n }\n\n // 8) Interne Links: primaer auf Business-Leistungsseiten\n const B = 'https://skrift.de/';\n const BUSINESS = ['handgeschriebene-businessbriefe', 'handgeschriebene-postkarten',\n 'unterschriftenservice', 'automatisierte-follow-ups'];\n const NEUTRAL = ['papier-schriftmuster', 'kontakt'];\n const PRIVAT = ['handschrift-fuer-privatpersonen', 'handgeschriebene-private-briefe',\n 'handgeschriebene-private-postkarten'];\n const slugVon = (u) => String(u || '').replace(/^https?:\\/\\/(www\\.)?skrift\\.de\\/?/, '').replace(/\\/.*$/, '');\n const links = [...html.matchAll(/]*href=\"([^\"]+)\"/g)].map(m => m[1]);\n const intern = links.filter(u => /^https?:\\/\\/(www\\.)?skrift\\.de/.test(u) || u.startsWith('/'));\n const slugs = intern.map(u => u.startsWith('/') ? u.replace(/^\\/|\\/.*$/g, '') : slugVon(u));\n\n const cta = slugVon(artikel.cta_ziel);\n if (!BUSINESS.includes(cta)) add('Links: CTA-Ziel ist keine Business-Leistungsseite', `cta_ziel \"${artikel.cta_ziel || 'leer'}\", erlaubt: ${BUSINESS.join(', ')}`);\n const ctaAnzahl = slugs.filter(s => s === cta).length;\n if (ctaAnzahl < 1) add('Links: CTA-Ziel nicht verlinkt', 'cta_ziel mindestens einmal im Fliesstext verlinken');\n const bus = slugs.filter(s => BUSINESS.includes(s));\n const priv = slugs.filter(s => PRIVAT.includes(s));\n if (bus.length < 2) add('Links: zu wenige Business-Links', `${bus.length} Link(s) auf Business-Seiten, mindestens 2`);\n if (priv.length) add('Links: Privatkunden-Link im Text', 'Privatkunden-Seiten nicht aus dem Artikeltext verlinken, das uebernimmt das Seitenlayout');\n // Bausteine, die das Seitenlayout bereits liefert\n if (/Auch für Privatpersonen/i.test(html)) add('Baustein doppelt: \"Auch für Privatpersonen.\"', 'Block aus dem Artikeltext entfernen, er steht im Seitenlayout');\n if (/ ${f.antwort || ''} ${artikel.kurzantwort} ${a.kurzantwort} ${f.antwort || ''} ${a.kurzantwort} ${f.antwort || ''}Auch für Privatpersonen\\.<\\/h3>/)[1] || '').split(/
block.includes(s));\n if (!imBlock) add('Links: Privatkunden-Link ausserhalb des Privat-Blocks', 'Privat-Links nur im Block \"Auch für Privatpersonen.\"');\n }\n if (priv.length >= bus.length && priv.length > 0) add('Links: Privat-Links nicht in der Minderheit', `${bus.length} Business gegen ${priv.length} Privat`);\n const unbekannt = [...new Set(slugs.filter(s => s && !BUSINESS.includes(s) && !NEUTRAL.includes(s) && !PRIVAT.includes(s)))];\n unbekannt.forEach(s => add('Links: kein Linkziel fuer Ratgeber', `/${s}/ (z. B. Dankeseite, Rechtstexte)`));\n\n return hinweise;\n}\n\n\nconst meta = $('Artikel nacheinander').first().json;\nconst artRoh = $('Artikeldatei laden').first().json;\nconst briefAntwort = $input.first().json;\n// n8n liefert den Koerper bei fullResponse je nach Version als body oder data.\nconst K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);\nconst kb = K(briefAntwort);\n\nlet artikel;\ntry { artikel = JSON.parse(Buffer.from(artRoh.content, 'base64').toString('utf8')); }\ncatch (e) { throw new Error('artikel-' + meta.slug + '.json ist kein gueltiges JSON: ' + e.message); }\n\nconst pflicht = ['slug', 'titel', 'query_id', 'kategorie', 'content_html'];\nconst fehlt = pflicht.filter(f => !artikel[f]);\nif (fehlt.length) {\n throw new Error('artikel-' + meta.slug + '.json fehlen Pflichtfelder: ' + fehlt.join(', ')\n + '. Erwartetes Format siehe README Abschnitt \"Vertrag Cowork -> Workflow 2\".');\n}\nif (artikel.slug !== meta.slug) {\n throw new Error('Slug im Dateinamen (' + meta.slug + ') und im JSON (' + artikel.slug + ') stimmen nicht ueberein.');\n}\n\nlet brief = null;\nif (briefAntwort.statusCode === 200 && kb && kb.content) {\n try { brief = JSON.parse(Buffer.from(kb.content, 'base64').toString('utf8')); }\n catch (e) { brief = null; }\n}\n\n// Preise muessen als Shortcode stehen, nie ausgeschrieben.\nconst preisTreffer = [];\nconst re = /(\\d{1,3}(?:[.,]\\d{1,2})?)\\s*(?:€|EUR|Euro)/gi;\nlet m;\nwhile ((m = re.exec(String(artikel.content_html || ''))) !== null) {\n preisTreffer.push(m[0].trim());\n}\nconst shortcodes = (String(artikel.content_html || '').match(/\\[skrift_preis[^\\]]*\\]/g) || []);\n\n// NeuronWriter bewertet nur den uebergebenen Text. Kurzantwort und FAQ stehen\n// aber auf der fertigen Seite, also gehoeren sie in die Bewertung. Der gespeicherte\n// Artikel bleibt unveraendert, das hier ist nur der Pruefkoerper.\nconst faqText = (Array.isArray(artikel.faq) ? artikel.faq : [])\n .map(f => `
${f.frage || ''}
Häufige Fragen
\\n${faqText}` : '',\n].filter(Boolean).join('\\n');\n\nconst praxis = Array.isArray(artikel.praxis) && artikel.praxis.length ? artikel.praxis\n : ((brief && brief.artikel && brief.artikel.praxis) || []);\n\nreturn [{ json: { slug: meta.slug, artikel, brief, brief_vorhanden: !!brief,\n bewertungs_html,\n stil_hinweise: stilpruefung({ ...artikel, praxis }),\n preis_warnung: preisTreffer.length\n ? `${preisTreffer.length} ausgeschriebene(r) Preis(e) im Text: ${[...new Set(preisTreffer)].slice(0,5).join(', ')}. `\n + 'Bitte durch [skrift_preis key=\"...\"] ersetzen.'\n : null,\n preis_shortcodes: shortcodes.length } }];\n"
+ "jsCode": "// Stilpruefung: findet typische Muster maschinell wirkender Texte im Deutschen.\n// Wird von gen_wf2.py in den Code-Node \"Artikel pruefen\" eingebettet und ist\n// lokal mit node testbar. Liefert Hinweise, blockiert nichts.\nfunction stilpruefung(artikel) {\n const hinweise = [];\n const add = (art, stelle) => hinweise.push({ art, stelle: String(stelle).slice(0, 140) });\n const ohneTags = (h) => String(h || '').replace(/<[^>]+>/g, ' ').replace(/ /g, ' ').replace(/\\s+/g, ' ').trim();\n\n const html = String(artikel.content_html || '');\n const absaetze = [...html.matchAll(/${f.frage || ''}
${artikel.faq_ueberschrift || 'Häufige Fragen'}
\\n${faqText}` : '',\n].filter(Boolean).join('\\n');\n\nconst praxis = Array.isArray(artikel.praxis) && artikel.praxis.length ? artikel.praxis\n : ((brief && brief.artikel && brief.artikel.praxis) || []);\n\nreturn [{ json: { slug: meta.slug, artikel, brief, brief_vorhanden: !!brief,\n bewertungs_html,\n stil_hinweise: stilpruefung({ ...artikel, praxis }),\n preis_warnung: preisTreffer.length\n ? `${preisTreffer.length} ausgeschriebene(r) Preis(e) im Text: ${[...new Set(preisTreffer)].slice(0,5).join(', ')}. `\n + 'Bitte durch [skrift_preis key=\"...\"] ersetzen.'\n : null,\n preis_shortcodes: shortcodes.length } }];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
@@ -362,7 +362,7 @@
1340,
420
],
- "id": "c4db72b1-1e0c-4c63-8dd6-18ce5ed9288f",
+ "id": "2432e9d4-90fb-4953-8521-f192cf502473",
"name": "Artikel pruefen"
},
{
@@ -392,7 +392,7 @@
1560,
420
],
- "id": "22ffdabc-ac80-4570-bee2-337463b9e11f",
+ "id": "c775a775-a975-418f-81b2-c23d44ca310c",
"name": "Score ermitteln",
"credentials": {
"httpHeaderAuth": {
@@ -416,7 +416,7 @@
1560,
640
],
- "id": "06e19a47-f72e-49af-9275-09383308e218",
+ "id": "03075c45-9d02-48f5-b176-94cdb509d866",
"name": "Fehler Score",
"onError": "continueRegularOutput"
},
@@ -431,7 +431,7 @@
1780,
420
],
- "id": "29350571-0c58-4cbb-946c-d413c0d3cac3",
+ "id": "3e90f007-8d26-494c-ade0-ee58db39ad39",
"name": "Score auswerten"
},
{
@@ -445,7 +445,7 @@
},
"conditions": [
{
- "id": "f05d0df3-ca20-4543-968b-5c2cb789774f",
+ "id": "009ffbe7-01c0-461c-b28d-b745c4d60afa",
"leftValue": "={{ $json.bestanden }}",
"rightValue": "",
"operator": {
@@ -466,7 +466,7 @@
2000,
420
],
- "id": "8f793efc-bb8b-4994-a02a-9a4726fbcea8",
+ "id": "19ff7716-5710-41d6-bfd7-4fe08bd16cb7",
"name": "Score erreicht?"
},
{
@@ -480,7 +480,7 @@
2220,
640
],
- "id": "2ca96cd1-a927-4fb3-85ae-8012437609df",
+ "id": "702f39bc-8886-42e5-a8dd-affd032569dd",
"name": "Fehlende Begriffe ermitteln"
},
{
@@ -505,7 +505,7 @@
2440,
640
],
- "id": "4e8517b2-8f12-4bc0-b19b-386da85a6d3a",
+ "id": "174bc5e0-168e-40ce-9486-d91f4e5cf91b",
"name": "Nachbesserung vorhanden?",
"credentials": {
"httpHeaderAuth": {
@@ -525,7 +525,7 @@
2660,
640
],
- "id": "3d8631ae-5f48-4a1b-a4c1-ebb27856dbb4",
+ "id": "eaab366d-7814-47c6-9aa9-04f4637918a1",
"name": "Nachbesserung vorbereiten"
},
{
@@ -555,7 +555,7 @@
2880,
640
],
- "id": "36244e4c-bd55-4777-b0c0-93ab9c17d14c",
+ "id": "7d2e670b-6bcd-4240-90b2-27ef59f54dcc",
"name": "Nachbesserung committen",
"credentials": {
"httpHeaderAuth": {
@@ -579,7 +579,7 @@
2880,
860
],
- "id": "dc67d35e-4a83-428e-83be-7c4196161806",
+ "id": "c903692e-805d-4034-9555-651a6b0f2779",
"name": "Fehler Nachbesserung",
"onError": "continueRegularOutput"
},
@@ -594,7 +594,7 @@
3100,
640
],
- "id": "eaf7de10-8b7a-4020-a5e9-5b58af6b95e8",
+ "id": "971c9f49-930f-4876-913c-d5bfb060826e",
"name": "Nachbesserung gemerkt"
},
{
@@ -624,7 +624,7 @@
2220,
240
],
- "id": "ed8e731a-4142-46ac-830e-cddcd44a9987",
+ "id": "ad73443d-79ec-418f-b9cb-422d73d71b40",
"name": "Revision zu NeuronWriter",
"credentials": {
"httpHeaderAuth": {
@@ -648,7 +648,7 @@
2220,
60
],
- "id": "9ed00d79-a091-406f-afa9-e6aca9d773ac",
+ "id": "90c1cd8b-44f7-40e9-80cc-407e6d4ccb9e",
"name": "Fehler Revision",
"onError": "continueRegularOutput"
},
@@ -674,7 +674,7 @@
2440,
240
],
- "id": "641f8e00-0809-41bb-a43a-2ab3077236cc",
+ "id": "9a6eaa50-09b1-4e68-a06f-74471a6e075c",
"name": "Beitrag vorhanden?",
"credentials": {
"httpBasicAuth": {
@@ -708,7 +708,7 @@
2660,
240
],
- "id": "34ba9cb0-fab5-4344-bc9c-699239d01c99",
+ "id": "9d7d2fca-b517-43c5-a031-c123e52985da",
"name": "Kategorie suchen",
"credentials": {
"httpBasicAuth": {
@@ -731,7 +731,7 @@
2880,
240
],
- "id": "e900cca9-39fe-4806-9588-91d1de3ba9e5",
+ "id": "f175da94-e137-4fac-b2e6-f38ef4d3e9b8",
"name": "Kategorie pruefen"
},
{
@@ -745,7 +745,7 @@
},
"conditions": [
{
- "id": "59dc46d5-6d0d-4796-bf86-95d715ea05d8",
+ "id": "c553d779-7e48-4b1b-8808-1c2ff31737f0",
"leftValue": "={{ $json.kategorie_fehlt }}",
"rightValue": "",
"operator": {
@@ -766,7 +766,7 @@
3100,
240
],
- "id": "0c0a8bc7-cbac-4e70-a7f6-e94c38ffa837",
+ "id": "a9b527e8-a341-4a47-a6a3-261e54d3efac",
"name": "Kategorie anlegen?"
},
{
@@ -796,7 +796,7 @@
3320,
140
],
- "id": "b64baa11-054d-4830-a56c-20c931ce3655",
+ "id": "1f2a04e4-0625-4ce5-a6d9-49737ccb7c85",
"name": "Kategorie anlegen",
"credentials": {
"httpBasicAuth": {
@@ -819,13 +819,13 @@
3540,
240
],
- "id": "cfb9aeda-8ebe-4895-a463-6f621d22020e",
+ "id": "7eb5e79b-817e-4e7a-bc4b-6d8dfc32fc61",
"name": "Kategorie festlegen"
},
{
"parameters": {
"mode": "runOnceForAllItems",
- "jsCode": "\nconst s = $input.first().json;\nconst dup = $('Beitrag vorhanden?').first().json;\nconst a = s.artikel;\n\n// Duplikatspruefung: exakter Slug-Treffer entscheidet ueber anlegen oder aktualisieren.\n// n8n liefert den Koerper bei fullResponse je nach Version als body oder data.\nconst K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);\nconst kd = K(dup);\nconst treffer = (dup.statusCode === 200 && Array.isArray(kd))\n ? kd.find(p => p.slug === s.slug) : null;\n\nconst heute = new Date().toISOString().slice(0, 10);\n\nconst meta = {\n kurzantwort: a.kurzantwort || '',\n cta_ziel: a.cta_ziel || '',\n stand_datum: a.stand_datum || heute,\n faq: Array.isArray(a.faq)\n ? a.faq.filter(f => f && f.frage).map(f => ({ frage: String(f.frage), antwort: String(f.antwort || '') }))\n : [],\n _seopress_titles_title: a.seo_titel || a.titel,\n _seopress_titles_desc: a.seo_beschreibung || '',\n _seopress_analysis_target_kw: a.keyword || '',\n};\n\n// --- Elementor-Struktur aus Artikel und FAQ ---\n// Zerlegt den Text an den H2-Grenzen in Text-Editor-Widgets und haengt das FAQ\n// als nested-accordion an, dasselbe Widget wie auf den Leistungsseiten.\nconst konf = $('Konfiguration').first().json;\nconst eid = () => Math.random().toString(16).slice(2, 9);\nconst textWidget = (h) => ({ id: eid(), elType: 'widget', settings: { editor: h },\n elements: [], widgetType: 'text-editor' });\nconst behaelter = (kinder, innen) => {\n const s = { content_width: 'full' };\n if (innen) Object.assign(s, {\n _title: 'Element',\n padding: { unit: 'px', top: '8', right: '8', bottom: '8', left: '8', isLinked: true },\n flex_gap: { column: '20', row: '20', isLinked: true, unit: 'px', size: 20 },\n });\n const c = { id: eid(), elType: 'container', settings: s, elements: kinder };\n if (innen) { c.isInner = true; c.isLocked = true; }\n return c;\n};\n\nfunction elementorBauen() {\n const wurzel = [];\n if (a.kurzantwort) {\n wurzel.push(behaelter([textWidget(\n `]/g;\n let mm;\n while ((mm = re2.exec(html)) !== null) stellen.push(mm.index);\n const bloecke = [];\n if (!stellen.length) {\n if (html.trim()) bloecke.push(html.trim());\n } else {\n if (stellen[0] > 0 && html.slice(0, stellen[0]).trim()) bloecke.push(html.slice(0, stellen[0]).trim());\n stellen.forEach((s, i) => {\n const ende = i + 1 < stellen.length ? stellen[i + 1] : html.length;\n const b = html.slice(s, ende).trim();\n if (b) bloecke.push(b);\n });\n }\n bloecke.forEach(b => wurzel.push(behaelter([textWidget(b)])));\n\n if (meta.faq.length) {\n const items = [], kinder = [];\n meta.faq.forEach(f => {\n items.push({ item_title: f.frage, _id: eid() });\n kinder.push(behaelter([textWidget(`
${konf.faq_ueberschrift || 'Häufige Fragen'}
`),\n { id: eid(), elType: 'widget', widgetType: 'nested-accordion',\n settings: { items, faq_schema: 'yes', default_state: 'all_collapsed' },\n elements: kinder },\n ]));\n }\n return wurzel;\n}\n\nif (konf.elementor_ausgabe) {\n meta._elementor_data = JSON.stringify(elementorBauen());\n meta._elementor_edit_mode = 'builder';\n meta._elementor_template_type = 'wp-post';\n}\n\nconst body = {\n title: a.titel,\n slug: s.slug,\n content: a.content_html,\n status: 'draft', // harte Vorgabe: nie automatisch veroeffentlichen\n categories: [s.kategorie_id],\n meta,\n};\nif (a.auszug) body.excerpt = a.auszug;\n\nreturn [{ json: {\n ...s,\n vorhanden: !!treffer,\n post_id: treffer ? treffer.id : null,\n alter_status: treffer ? treffer.status : null,\n wp_url: treffer\n ? `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts/${treffer.id}`\n : `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts`,\n wp_body: JSON.stringify(body),\n}}];\n"
+ "jsCode": "\nconst s = $input.first().json;\nconst dup = $('Beitrag vorhanden?').first().json;\nconst a = s.artikel;\n\n// Duplikatspruefung: exakter Slug-Treffer entscheidet ueber anlegen oder aktualisieren.\n// n8n liefert den Koerper bei fullResponse je nach Version als body oder data.\nconst K = (r) => (r && r.body !== undefined) ? r.body : ((r || {}).data);\nconst kd = K(dup);\nconst treffer = (dup.statusCode === 200 && Array.isArray(kd))\n ? kd.find(p => p.slug === s.slug) : null;\n\nconst heute = new Date().toISOString().slice(0, 10);\n\nconst meta = {\n kurzantwort: a.kurzantwort || '',\n faq_ueberschrift: a.faq_ueberschrift || '',\n cta_ziel: a.cta_ziel || '',\n stand_datum: a.stand_datum || heute,\n faq: Array.isArray(a.faq)\n ? a.faq.filter(f => f && f.frage).map(f => ({ frage: String(f.frage), antwort: String(f.antwort || '') }))\n : [],\n _seopress_titles_title: a.seo_titel || a.titel,\n _seopress_titles_desc: a.seo_beschreibung || '',\n _seopress_analysis_target_kw: a.keyword || '',\n};\n\n// --- Elementor-Struktur aus Artikel und FAQ ---\n// Zerlegt den Text an den H2-Grenzen in Text-Editor-Widgets und haengt das FAQ\n// als nested-accordion an, dasselbe Widget wie auf den Leistungsseiten.\nconst konf = $('Konfiguration').first().json;\nconst eid = () => Math.random().toString(16).slice(2, 9);\nconst textWidget = (h) => ({ id: eid(), elType: 'widget', settings: { editor: h },\n elements: [], widgetType: 'text-editor' });\nconst behaelter = (kinder, innen) => {\n const s = { content_width: 'full' };\n if (innen) Object.assign(s, {\n _title: 'Element',\n padding: { unit: 'px', top: '8', right: '8', bottom: '8', left: '8', isLinked: true },\n flex_gap: { column: '20', row: '20', isLinked: true, unit: 'px', size: 20 },\n });\n const c = { id: eid(), elType: 'container', settings: s, elements: kinder };\n if (innen) { c.isInner = true; c.isLocked = true; }\n return c;\n};\n\nfunction elementorBauen() {\n const wurzel = [];\n if (a.kurzantwort) {\n wurzel.push(behaelter([textWidget(\n `]/g;\n let mm;\n while ((mm = re2.exec(html)) !== null) stellen.push(mm.index);\n const bloecke = [];\n if (!stellen.length) {\n if (html.trim()) bloecke.push(html.trim());\n } else {\n if (stellen[0] > 0 && html.slice(0, stellen[0]).trim()) bloecke.push(html.slice(0, stellen[0]).trim());\n stellen.forEach((s, i) => {\n const ende = i + 1 < stellen.length ? stellen[i + 1] : html.length;\n const b = html.slice(s, ende).trim();\n if (b) bloecke.push(b);\n });\n }\n bloecke.forEach(b => wurzel.push(behaelter([textWidget(b)])));\n\n if (meta.faq.length) {\n const items = [], kinder = [];\n meta.faq.forEach(f => {\n items.push({ item_title: f.frage, _id: eid() });\n kinder.push(behaelter([textWidget(`
${konf.faq_ueberschrift || 'Häufige Fragen'}
`),\n { id: eid(), elType: 'widget', widgetType: 'nested-accordion',\n settings: { items, faq_schema: 'yes', default_state: 'all_collapsed' },\n elements: kinder },\n ]));\n }\n return wurzel;\n}\n\nif (konf.elementor_ausgabe) {\n meta._elementor_data = JSON.stringify(elementorBauen());\n meta._elementor_edit_mode = 'builder';\n meta._elementor_template_type = 'wp-post';\n}\n\nconst body = {\n title: a.titel,\n slug: s.slug,\n content: a.content_html,\n status: 'draft', // harte Vorgabe: nie automatisch veroeffentlichen\n categories: [s.kategorie_id],\n meta,\n};\nif (a.auszug) body.excerpt = a.auszug;\n\nreturn [{ json: {\n ...s,\n vorhanden: !!treffer,\n post_id: treffer ? treffer.id : null,\n alter_status: treffer ? treffer.status : null,\n wp_url: treffer\n ? `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts/${treffer.id}`\n : `${$('Konfiguration').first().json.wp_base}/wp-json/wp/v2/posts`,\n wp_body: JSON.stringify(body),\n}}];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
@@ -833,7 +833,7 @@
3760,
240
],
- "id": "fa7f4b40-e2fd-4e02-832c-2ff0da8527b3",
+ "id": "3e24c6b8-d0b0-4983-a06c-1183918148bb",
"name": "WordPress-Body bauen"
},
{
@@ -863,7 +863,7 @@
3980,
240
],
- "id": "c22b7697-ac02-45eb-aa2c-7fe87aeb46ff",
+ "id": "e05040a1-66fe-467f-87d7-f8985acb2191",
"name": "Beitrag anlegen oder aktualisieren",
"credentials": {
"httpBasicAuth": {
@@ -886,7 +886,7 @@
4200,
240
],
- "id": "9d81b408-d848-480d-99d7-cf09bf728877",
+ "id": "e6ff477c-cd6e-4d7d-8737-5cc4f69f6fb8",
"name": "Ergebnis pruefen"
},
{
@@ -917,7 +917,7 @@
4420,
240
],
- "id": "f4ad279d-9ee5-4d0a-9e82-151450889d92",
+ "id": "293acc6c-2693-4cd7-a21a-3edc80a803f2",
"name": "Cache leeren",
"credentials": {
"httpBasicAuth": {
@@ -937,7 +937,7 @@
4640,
240
],
- "id": "18f0e4ac-8ea3-450f-8d49-df200d7ddaaf",
+ "id": "4d8c093e-e322-4a87-a79d-77db2724bc1a",
"name": "Importergebnis merken"
},
{
@@ -951,9 +951,9 @@
4860,
420
],
- "id": "9613876f-73d4-4baa-99ad-31d8a2714e4c",
+ "id": "56000936-7d58-4ab7-892e-60520c4bc20e",
"name": "Pause Rate Limit",
- "webhookId": "d8b56a3f-dffc-4f1e-94a6-a3864185fa09"
+ "webhookId": "4782cf6a-9326-4b0f-9b35-4754fa88908f"
},
{
"parameters": {
@@ -966,7 +966,7 @@
},
"conditions": [
{
- "id": "433d059a-e005-4644-9f14-64cfe735e4b6",
+ "id": "1ae82a45-edd8-44f4-8946-47adc6800d41",
"leftValue": "={{ $('Konfiguration').first().json.bild_aktiv === true && !Number($json.featured_media) }}",
"rightValue": "",
"operator": {
@@ -987,7 +987,7 @@
4860,
240
],
- "id": "61319cb6-8092-4daa-a837-fb7008994b87",
+ "id": "ff9b0ab2-1a97-44dc-a97d-d33688cb2501",
"name": "Beitragsbild fehlt?"
},
{
@@ -1012,7 +1012,7 @@
5080,
120
],
- "id": "6c2b9f0c-e425-4f79-a0c8-de5b56da7ebf",
+ "id": "d7a48424-743c-464d-8290-efdcb05d48d1",
"name": "Bilddaten laden",
"credentials": {
"httpHeaderAuth": {
@@ -1027,7 +1027,7 @@
{
"parameters": {
"mode": "runOnceForAllItems",
- "jsCode": "\n// Prompt nach ratgeber/bildstil.md, Farbe und Motiv aus artikel-index.json.\nconst r = $('Importergebnis merken').first().json;\nconst antwort = $input.first().json;\nconst K = (x) => (x && x.body !== undefined) ? x.body : ((x || {}).data);\nconst konf = $('Konfiguration').first().json;\n\nlet zeile = null;\nconst kb = K(antwort);\nif (antwort.statusCode === 200 && kb && kb.content) {\n try {\n const tab = JSON.parse(Buffer.from(kb.content, 'base64').toString('utf8'));\n zeile = (Array.isArray(tab) ? tab : []).find(z => z.slug === r.slug) || null;\n } catch (e) { zeile = null; }\n}\nif (!zeile || !zeile.bild_farbe || !zeile.bildmotiv) {\n return [{ json: { ...r, bild_prompt: null,\n bild_hinweis: 'Kein bild_farbe oder bildmotiv in artikel-index.json, Bild uebersprungen.' } }];\n}\n\nconst FARBNAME = { '#7B96C2': 'muted light ink blue', '#B6997F': 'muted sepia brown',\n '#8C9A82': 'muted sage green', '#C47B5E': 'muted terracotta red' };\nconst farbe = String(zeile.bild_farbe).toUpperCase();\nconst name = FARBNAME[farbe] ? ` (${FARBNAME[farbe]}, not more saturated)` : '';\nconst leer = /price tag/i.test(zeile.bildmotiv) ? ', the price tag is completely empty' : '';\n\nconst prompt = [\n 'Hand-drawn doodle spot illustration, flat editorial icon style.',\n `Solid flat background color ${farbe}${name}, completely uniform, no texture, no gradient, no shadow, no vignette.`,\n 'In the center one simple object, drawn with thick uneven black marker outlines (#1A1A1A), slightly wobbly hand-drawn lines like a felt-tip pen, rounded line ends.',\n 'Clean closed outlines, no stray marks, no hatching and no small dashes inside the shapes.',\n 'Object fills are flat off-white cream (#F9F7F5), no shading, no highlights.',\n `Motif: ${zeile.bildmotiv}${leer}. Shown straight from the front, not tilted.`,\n 'Minimal and iconic, lots of empty space, the object takes about 35 percent of the image height.',\n 'No text, no letters, no numbers, no currency symbols, no logo, no border, no frame, no rounded corners, no people.',\n].join(' ');\n\nreturn [{ json: { ...r, bild_prompt: prompt, bild_titel: zeile.titel || r.slug,\n openai_body: JSON.stringify({ model: konf.bild_modell, prompt, size: konf.bild_groesse,\n n: 1, output_format: 'webp', quality: konf.bild_qualitaet }) } }];\n"
+ "jsCode": "\n// Prompt nach ratgeber/bildstil.md, Farbe und Motiv aus artikel-index.json.\nconst r = $('Importergebnis merken').first().json;\nconst antwort = $input.first().json;\nconst K = (x) => (x && x.body !== undefined) ? x.body : ((x || {}).data);\nconst konf = $('Konfiguration').first().json;\n\nlet zeile = null;\nconst kb = K(antwort);\nif (antwort.statusCode === 200 && kb && kb.content) {\n try {\n const tab = JSON.parse(Buffer.from(kb.content, 'base64').toString('utf8'));\n zeile = (Array.isArray(tab) ? tab : []).find(z => z.slug === r.slug) || null;\n } catch (e) { zeile = null; }\n}\nif (!zeile || !zeile.bild_farbe || !zeile.bildmotiv) {\n return [{ json: { ...r, bild_prompt: null,\n bild_hinweis: 'Kein bild_farbe oder bildmotiv in artikel-index.json, Bild uebersprungen.' } }];\n}\n\nconst FARBNAME = { '#7B96C2': 'muted light ink blue', '#B6997F': 'muted sepia brown',\n '#8C9A82': 'muted sage green', '#C47B5E': 'muted terracotta red' };\nconst farbe = String(zeile.bild_farbe).toUpperCase();\nconst name = FARBNAME[farbe] ? ` (${FARBNAME[farbe]}, not more saturated)` : '';\nconst leer = /price tag/i.test(zeile.bildmotiv) ? ', the price tag is completely empty' : '';\n\nconst prompt = [\n 'Hand-drawn doodle spot illustration, flat editorial icon style.',\n `Solid flat background color ${farbe}${name}, completely uniform, no texture, no gradient, no shadow, no vignette.`,\n 'In the center one simple object, drawn with thick uneven black marker outlines (#1A1A1A), slightly wobbly hand-drawn lines like a felt-tip pen, rounded line ends.',\n 'Clean closed outlines, no stray marks, no hatching and no small dashes inside the shapes.',\n 'Object fills are flat off-white cream (#F9F7F5), no shading, no highlights.',\n `Motif: ${zeile.bildmotiv}${leer}. Shown straight from the front, not tilted.`,\n 'The whole motif sits in the exact center and stays inside the middle band of the image: it takes about 28 percent of the image height and at most 60 percent of the image width, with wide empty background above and below.',\n 'No text, no letters, no numbers, no currency symbols, no logo, no border, no frame, no rounded corners, no people.',\n].join(' ');\n\nreturn [{ json: { ...r, bild_prompt: prompt, bild_titel: zeile.titel || r.slug,\n openai_body: JSON.stringify({ model: konf.bild_modell, prompt, size: konf.bild_groesse,\n n: 1, output_format: 'webp', quality: konf.bild_qualitaet }) } }];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
@@ -1035,7 +1035,7 @@
5300,
120
],
- "id": "f602842b-c4ed-4d15-8e85-72ce9d329fa3",
+ "id": "dd13bf7d-798d-43a4-af99-fd0ee8bffcab",
"name": "Bildprompt bauen"
},
{
@@ -1049,7 +1049,7 @@
},
"conditions": [
{
- "id": "a9afefa8-8157-43f5-91b1-f7446c5364bd",
+ "id": "98fcfee0-224f-422a-b567-ec68f40b465f",
"leftValue": "={{ !!$json.bild_prompt }}",
"rightValue": "",
"operator": {
@@ -1070,7 +1070,7 @@
5520,
120
],
- "id": "14894cfa-8e68-4edb-9d17-f2671780aa74",
+ "id": "620a3df5-f36b-4192-b215-4c73b5e123d9",
"name": "Bildprompt vorhanden?"
},
{
@@ -1100,7 +1100,7 @@
5740,
40
],
- "id": "31ab60fc-6031-439a-8402-74dd6d0d9437",
+ "id": "6c1873c5-117c-4fb9-9109-e9b2cee026d2",
"name": "Bild erzeugen",
"credentials": {
"httpHeaderAuth": {
@@ -1124,7 +1124,7 @@
5740,
260
],
- "id": "6efa2c23-95e2-4a94-873b-b509c10ab395",
+ "id": "1f440c70-8596-4c40-963b-f8643698bbd4",
"name": "Fehler Bild",
"onError": "continueRegularOutput"
},
@@ -1139,7 +1139,7 @@
5960,
40
],
- "id": "491b23b7-4679-429d-9745-47292b0154ca",
+ "id": "d883b5e0-6185-42ff-a6e2-921bfb467ed5",
"name": "Bild als Datei"
},
{
@@ -1174,7 +1174,7 @@
6180,
40
],
- "id": "b5e1265a-6b87-4c3f-840c-6cca67628550",
+ "id": "f8891b55-37e7-459b-969a-6f9f33c51c33",
"name": "Bild in Mediathek",
"credentials": {
"httpBasicAuth": {
@@ -1208,7 +1208,7 @@
6400,
40
],
- "id": "21f00fcc-1449-4cb7-96b2-6cf35a4d8056",
+ "id": "3d497e77-7911-4ed0-9f22-f20504712ef9",
"name": "Beitragsbild setzen",
"credentials": {
"httpBasicAuth": {
@@ -1231,7 +1231,7 @@
6620,
40
],
- "id": "78f4059e-6003-43b0-80dc-fad15546eff3",
+ "id": "453a90a0-5243-4d1a-9224-3e3fc3541599",
"name": "Bild gemerkt"
},
{
@@ -1245,7 +1245,7 @@
900,
80
],
- "id": "7241567b-ebff-4ddf-a4e3-f40ef3787b07",
+ "id": "3e4abd4e-4a6f-4e54-9924-c2cfa154cf82",
"name": "Ergebnisse sammeln"
},
{
@@ -1270,7 +1270,7 @@
1120,
80
],
- "id": "1b6c1a04-caf9-4bb8-ace9-b050fe4710bb",
+ "id": "ca0cb16c-0af2-4b3c-aaa2-5725972703be",
"name": "Statustabelle lesen",
"credentials": {
"httpHeaderAuth": {
@@ -1290,7 +1290,7 @@
1340,
80
],
- "id": "5aecb5c3-e0d3-45df-b33b-37c1d13c8c08",
+ "id": "78f81aa7-0ca1-4f29-815c-a5328ea514b9",
"name": "Statustabelle fortschreiben"
},
{
@@ -1304,7 +1304,7 @@
},
"conditions": [
{
- "id": "82bf79b1-edca-437a-88cd-913d7f3fd9c4",
+ "id": "d2c17929-ba0a-4080-ae1a-e9a6aad3e118",
"leftValue": "={{ $json.ueberspringen }}",
"rightValue": "",
"operator": {
@@ -1325,7 +1325,7 @@
1560,
80
],
- "id": "049968c9-10ac-4616-b768-832074799f58",
+ "id": "7ce09846-988b-490b-a1d3-1b986eb17713",
"name": "Gab es Importe?"
},
{
@@ -1355,7 +1355,7 @@
1780,
160
],
- "id": "e1193d3f-fed0-4f31-a90c-c333a5a2bdd5",
+ "id": "0a900f70-93be-4997-b04a-c74061ea39e9",
"name": "Statustabelle schreiben",
"credentials": {
"httpHeaderAuth": {
@@ -1379,7 +1379,7 @@
1780,
340
],
- "id": "6df2408f-6d6c-4a04-8641-3a4a738268c2",
+ "id": "96e36221-b847-4b03-95cb-e1a103a27df7",
"name": "Fehler Statustabelle",
"onError": "continueRegularOutput"
},
@@ -1394,7 +1394,7 @@
2000,
80
],
- "id": "3384fccd-d896-4c44-9c89-f91946e5d986",
+ "id": "2899cba3-36f3-4fb1-b1cf-c99fe19b9020",
"name": "Meldung formulieren"
},
{
@@ -1412,7 +1412,7 @@
2220,
80
],
- "id": "d2da0c0a-1998-405c-b585-7c8d7bbc834c",
+ "id": "12bdb6c6-d9d0-4271-ace8-e3e07c863819",
"name": "Benachrichtigung",
"disabled": true,
"notes": "SMTP-Credential zuweisen, Absender eintragen, dann aktivieren."