From 257a414649cfe3e82cefe57cc9940dbff53dbd9f Mon Sep 17 00:00:00 2001
From: Lucas Orth
Date: Tue, 22 Sep 2026 13:54:28 +0200
Subject: [PATCH] Mu-Plugin 1.5.0: Reihenfolge-Feld und Loop-Grid-Sortierung
---
build/gen_wf2.py | 1 +
build/reihenfolge_setzen.py | 22 ++++
wordpress/skrift-ratgeber-felder.php | 51 +++++++-
workflows/pruefen-und-importieren.json | 164 ++++++++++++-------------
4 files changed, 154 insertions(+), 84 deletions(-)
create mode 100644 build/reihenfolge_setzen.py
diff --git a/build/gen_wf2.py b/build/gen_wf2.py
index 2f2c342..179c116 100644
--- a/build/gen_wf2.py
+++ b/build/gen_wf2.py
@@ -443,6 +443,7 @@ const meta = {
faq_ueberschrift: a.faq_ueberschrift || '',
cta_ziel: a.cta_ziel || '',
stand_datum: a.stand_datum || heute,
+ ratgeber_reihenfolge: Number.isFinite(Number(a.reihenfolge)) && a.reihenfolge !== null && a.reihenfolge !== '' ? Number(a.reihenfolge) : 999,
faq: Array.isArray(a.faq)
? a.faq.filter(f => f && f.frage).map(f => ({ frage: String(f.frage), antwort: String(f.antwort || '') }))
: [],
diff --git a/build/reihenfolge_setzen.py b/build/reihenfolge_setzen.py
new file mode 100644
index 0000000..496bd6a
--- /dev/null
+++ b/build/reihenfolge_setzen.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+"""Schreibt ratgeber_reihenfolge aus artikel/*.json in die bestehenden WP-Beitraege (braucht Mu-Plugin ab 1.5.0)."""
+import base64, glob, json, urllib.request
+E = {}
+for z in open('.env', encoding='utf-8'):
+ z = z.strip()
+ if z and not z.startswith('#') and '=' in z:
+ k, v = z.split('=', 1); E[k] = v.strip().strip('"')
+AUTH = 'Basic ' + base64.b64encode(f"{E['WP_USER']}:{E['WP_APP_PASSWORD']}".encode()).decode()
+WP = E['WP_BASE_URL'].rstrip('/') + '/wp-json/wp/v2/posts'
+def ruf(url, daten=None):
+ r = urllib.request.Request(url, data=json.dumps(daten).encode() if daten else None, method='POST' if daten else 'GET',
+ headers={'Authorization': AUTH, 'Content-Type': 'application/json'})
+ return json.load(urllib.request.urlopen(r, timeout=60))
+for f in sorted(glob.glob('artikel/artikel-*.json')):
+ a = json.load(open(f, encoding='utf-8'))
+ treffer = ruf(f"{WP}?slug={a['slug']}&status=draft,publish,pending,future,private&context=edit&_fields=id,meta")
+ if not treffer:
+ print('kein Beitrag', a['slug']); continue
+ pid = treffer[0]['id']
+ neu = ruf(f"{WP}/{pid}?_fields=id,meta", {'meta': {'ratgeber_reihenfolge': a.get('reihenfolge', 999)}})
+ print(pid, a['slug'], neu['meta'].get('ratgeber_reihenfolge', 'Feld unbekannt, Plugin 1.5.0 fehlt'))
diff --git a/wordpress/skrift-ratgeber-felder.php b/wordpress/skrift-ratgeber-felder.php
index eef289f..b8e1c36 100644
--- a/wordpress/skrift-ratgeber-felder.php
+++ b/wordpress/skrift-ratgeber-felder.php
@@ -1,8 +1,8 @@
'skrift_ratgeber_meta_auth',
) );
+ // Feste Reihenfolge im Loop Grid, klein = weiter vorn. Schritte von 10 lassen Platz zum Einschieben.
+ register_post_meta( 'post', 'ratgeber_reihenfolge', array(
+ 'type' => 'integer',
+ 'single' => true,
+ 'default' => 999,
+ 'show_in_rest' => true,
+ 'sanitize_callback' => 'absint',
+ 'auth_callback' => 'skrift_ratgeber_meta_auth',
+ ) );
+
register_post_meta( 'post', 'faq', array(
'type' => 'array',
'single' => true,
@@ -101,6 +111,8 @@ function skrift_ratgeber_metabox( $post ) {
$cta = (string) get_post_meta( $post->ID, 'cta_ziel', true );
$stand = (string) get_post_meta( $post->ID, 'stand_datum', true );
$faq_u = (string) get_post_meta( $post->ID, 'faq_ueberschrift', true );
+ $rf = get_post_meta( $post->ID, 'ratgeber_reihenfolge', true );
+ $rf = ( '' === $rf ) ? 999 : (int) $rf;
$faq = get_post_meta( $post->ID, 'faq', true );
if ( ! is_array( $faq ) ) {
$faq = array();
@@ -132,6 +144,11 @@ function skrift_ratgeber_metabox( $post ) {
+
+
+
+ Klein = weiter vorn im Loop Grid.
+
@@ -199,6 +216,7 @@ add_action( 'save_post_post', function ( $post_id ) {
update_post_meta( $post_id, 'kurzantwort', wp_kses_post( wp_unslash( $_POST['skrift_kurzantwort'] ?? '' ) ) );
update_post_meta( $post_id, 'faq_ueberschrift', sanitize_text_field( wp_unslash( $_POST['skrift_faq_ueberschrift'] ?? '' ) ) );
update_post_meta( $post_id, 'cta_ziel', esc_url_raw( wp_unslash( $_POST['skrift_cta_ziel'] ?? '' ) ) );
+ update_post_meta( $post_id, 'ratgeber_reihenfolge', isset( $_POST['skrift_reihenfolge'] ) && '' !== $_POST['skrift_reihenfolge'] ? absint( $_POST['skrift_reihenfolge'] ) : 999 );
update_post_meta( $post_id, 'stand_datum', skrift_ratgeber_datum( sanitize_text_field( wp_unslash( $_POST['skrift_stand_datum'] ?? '' ) ) ) );
$faq = array();
@@ -753,3 +771,32 @@ add_action( 'elementor/dynamic_tags/register', function ( $dynamic_tags ) {
$dynamic_tags->register( new Skrift_Tag_FAQ_Ueberschrift() );
$dynamic_tags->register( new Skrift_Tag_Stand() );
} );
+
+
+/* ------------------------------------------------------------------------
+ * Loop Grid: Sortierung ueber ratgeber_reihenfolge
+ * In Elementor im Loop Grid unter Abfrage > Abfrage-ID "skrift_ratgeber" eintragen.
+ * Kategorie-Filter aus dem Widget (Einschliessen nach Begriff) bleibt erhalten.
+ * Beitraege ohne Wert landen hinten, bei gleichem Wert entscheidet das Datum.
+ * --------------------------------------------------------------------- */
+
+add_action( 'elementor/query/skrift_ratgeber', function ( $query ) {
+ $meta_query = (array) $query->get( 'meta_query' );
+ $meta_query[] = array(
+ 'relation' => 'OR',
+ 'rf_vorhanden' => array( 'key' => 'ratgeber_reihenfolge', 'type' => 'NUMERIC', 'compare' => 'EXISTS' ),
+ 'rf_fehlt' => array( 'key' => 'ratgeber_reihenfolge', 'compare' => 'NOT EXISTS' ),
+ );
+ $query->set( 'meta_query', $meta_query );
+ $query->set( 'orderby', array( 'rf_vorhanden' => 'ASC', 'date' => 'DESC' ) );
+} );
+
+// Beitraege ohne Wert bekommen beim Speichern 999, damit sie in der Sortierung hinten stehen.
+add_action( 'save_post_post', function ( $post_id ) {
+ if ( wp_is_post_revision( $post_id ) ) {
+ return;
+ }
+ if ( ! metadata_exists( 'post', $post_id, 'ratgeber_reihenfolge' ) ) {
+ update_post_meta( $post_id, 'ratgeber_reihenfolge', 999 );
+ }
+}, 20 );
diff --git a/workflows/pruefen-und-importieren.json b/workflows/pruefen-und-importieren.json
index d89f370..f96cbe4 100644
--- a/workflows/pruefen-und-importieren.json
+++ b/workflows/pruefen-und-importieren.json
@@ -9,7 +9,7 @@
-460,
100
],
- "id": "77876018-7154-4c9d-b1dd-5dd786cf8aae",
+ "id": "e645bc35-12a4-4aed-9c7a-ecc74d00f453",
"name": "Manuell starten"
},
{
@@ -25,9 +25,9 @@
-460,
280
],
- "id": "57a30ac3-70e9-4cd3-b898-2f2b4ac669ac",
+ "id": "1d24eaed-3b4f-41e8-9016-4cc6923ab622",
"name": "Webhook Start",
- "webhookId": "7a157b66-1824-459c-9dfb-0b52145d33a5"
+ "webhookId": "a54ca6b3-e645-465e-8969-fd2e242055ae"
},
{
"parameters": {
@@ -35,115 +35,115 @@
"assignments": {
"assignments": [
{
- "id": "30e392d0-59d5-4ede-bd7a-2e007e477c0a",
+ "id": "81fec9aa-b2e1-42a2-8eb3-87106e596bde",
"name": "nw_base",
"value": "https://app.neuronwriter.com/neuron-api/0.5/writer",
"type": "string"
},
{
- "id": "68bbc028-b18a-49ad-9032-5c0b0b3de53a",
+ "id": "ee7027dd-2ebd-4570-aaf5-85057bf01135",
"name": "gitea_base",
"value": "http://gitea:3000",
"type": "string"
},
{
- "id": "61a631f8-1840-45ab-ad22-3090d5de8e84",
+ "id": "03aee034-b048-4065-97c7-cf79f3983faa",
"name": "gitea_owner",
"value": "lucas.orth",
"type": "string"
},
{
- "id": "0efb15dc-4dbf-4e51-b7d6-7e0fd1535ee6",
+ "id": "4f52fda8-7009-45c4-a639-47f2781745a9",
"name": "gitea_repo",
"value": "Skrift-Auto-Article",
"type": "string"
},
{
- "id": "6f442b85-68aa-4723-8f20-0c4804863d94",
+ "id": "c90e3261-c81c-4793-bc2f-dd8161939f08",
"name": "branch",
"value": "main",
"type": "string"
},
{
- "id": "f26f278d-f9ce-4b2e-a9b4-17a528823163",
+ "id": "6a4e762a-a262-4aae-b4a9-a746e1e163e6",
"name": "artikel_ordner",
"value": "artikel",
"type": "string"
},
{
- "id": "0a60485f-7117-4736-a8e9-a180d5f66ea3",
+ "id": "f724b9be-2c72-4e94-a9b9-62a9f22baf50",
"name": "brief_ordner",
"value": "briefe",
"type": "string"
},
{
- "id": "fb4029bc-aaa5-4a2b-ac39-74e0a6b18ef6",
+ "id": "742cbf71-76b2-4c86-ab81-8ddf644d4422",
"name": "nachbessern_ordner",
"value": "nachbessern",
"type": "string"
},
{
- "id": "e255b391-5f12-4bde-84b4-c508bab8dac5",
+ "id": "4c5b9314-bc21-4474-8e48-056deecd14d8",
"name": "status_pfad",
"value": "status-importe.json",
"type": "string"
},
{
- "id": "5785c32f-ef1c-45c3-bde1-61eccb439105",
+ "id": "95d6f83b-aa71-4ebf-861e-ad6d2e9fa79b",
"name": "wp_base",
"value": "https://skrift.de",
"type": "string"
},
{
- "id": "b973a4ea-3b85-4c2f-abbd-8cf6d26025bc",
+ "id": "105b0778-3d05-4b5f-9fed-99409c2aa44f",
"name": "score_ziel",
"value": "={{ $json.body && $json.body.score_ziel ? Number($json.body.score_ziel) : 65 }}",
"type": "number"
},
{
- "id": "ff314c6a-5748-4de2-8e2a-7c05ba864085",
+ "id": "7f9d5e34-8319-407f-8c06-4acd2aab10f2",
"name": "pause_zwischen_artikeln",
"value": 5,
"type": "number"
},
{
- "id": "66d24a08-85e1-4f85-b365-e34283f3b3a8",
+ "id": "0562defc-78e5-4bf5-a3be-caccff62640c",
"name": "elementor_ausgabe",
"value": false,
"type": "boolean"
},
{
- "id": "afc4788f-4558-489f-8b3c-7df902791200",
+ "id": "d16d9864-9d93-438c-a7cf-ae9c9cdcaff0",
"name": "bild_aktiv",
"value": true,
"type": "boolean"
},
{
- "id": "fde4d84a-6f31-40a4-88c3-863e796aa45d",
+ "id": "2c439140-a3ad-4f7d-8565-0810b3c27467",
"name": "bild_modell",
"value": "gpt-image-2",
"type": "string"
},
{
- "id": "ef031954-d296-4068-872a-6fbe54e1ccc3",
+ "id": "1ae43107-262a-4257-a89d-132f8dabfdd9",
"name": "bild_groesse",
"value": "1200x1200",
"type": "string"
},
{
- "id": "94990856-3ac7-47df-9636-b51dfe6d5e1e",
+ "id": "886ab8cf-1e76-4775-ba4f-727fe2389d81",
"name": "bild_qualitaet",
"value": "medium",
"type": "string"
},
{
- "id": "d51af1a1-fc5a-4a60-ac9a-9ce2df113dd1",
+ "id": "e255dc56-5585-4c5d-831f-41913892e9f9",
"name": "faq_ueberschrift",
"value": "Häufige Fragen",
"type": "string"
},
{
- "id": "b8366f1f-fe40-4ca7-b052-1bee24d543be",
+ "id": "482a9f25-1180-422a-a03e-04e8d555b672",
"name": "benachrichtigung_an",
"value": "privat@lucas-orth.de",
"type": "string"
@@ -158,7 +158,7 @@
-240,
190
],
- "id": "01d2991f-f38d-485d-8871-af8267952501",
+ "id": "6ad49d4e-b542-499f-907a-2eb4a82e0cad",
"name": "Konfiguration"
},
{
@@ -183,7 +183,7 @@
-20,
190
],
- "id": "b9f2b463-32ec-4250-bfbc-ad262f3901d3",
+ "id": "d049d98d-d7c6-4b65-a981-2b783050d08f",
"name": "Artikelordner lesen",
"credentials": {
"httpHeaderAuth": {
@@ -206,7 +206,7 @@
200,
190
],
- "id": "c89a4237-836d-44c3-8572-2bf939a873af",
+ "id": "fe3a6d0a-982d-41f1-86a1-74363a3fd16b",
"name": "Artikeldateien filtern"
},
{
@@ -220,7 +220,7 @@
},
"conditions": [
{
- "id": "c7bbd13f-b4c6-48f9-8b79-4efcb95ab7a2",
+ "id": "875e5b9f-9c14-432f-abc9-ac4ede4f53b2",
"leftValue": "={{ $json.leer === true }}",
"rightValue": "",
"operator": {
@@ -241,7 +241,7 @@
420,
190
],
- "id": "946944dc-a368-46f7-9af0-dd93cf2972a1",
+ "id": "c83e34b8-5538-4aa2-893a-ab75c67d7bbf",
"name": "Artikel vorhanden?"
},
{
@@ -252,7 +252,7 @@
640,
40
],
- "id": "6171f836-f34c-4528-b8df-2fe872055632",
+ "id": "b971a2aa-7412-44b1-9ea7-192727cd8552",
"name": "Nichts zu tun"
},
{
@@ -268,7 +268,7 @@
640,
320
],
- "id": "9c1acd2c-c3ec-4dc9-8359-011552d42f61",
+ "id": "17dad5b6-7d29-4200-9895-d9cfa89e3e65",
"name": "Artikel nacheinander"
},
{
@@ -292,7 +292,7 @@
900,
420
],
- "id": "baf3e0c2-38c8-45c4-834e-db4d09a570a9",
+ "id": "70cf0ad6-149c-4930-8e32-6b469ef96dc4",
"name": "Artikeldatei laden",
"credentials": {
"httpHeaderAuth": {
@@ -316,7 +316,7 @@
900,
640
],
- "id": "c0751d4b-193c-4942-86c3-f588b7cca5c6",
+ "id": "15e29181-177a-496e-aedc-1861f3ca021a",
"name": "Fehler Artikeldatei",
"onError": "continueRegularOutput"
},
@@ -342,7 +342,7 @@
1120,
420
],
- "id": "998ea097-3d46-4bd5-9afb-9727c58b119d",
+ "id": "e0493f24-d401-47fc-917b-d1091ab17f1e",
"name": "Brief laden",
"credentials": {
"httpHeaderAuth": {
@@ -362,7 +362,7 @@
1340,
420
],
- "id": "abfc66c7-c1ef-4640-9e5e-1f7268173605",
+ "id": "2a7b4374-a366-44f3-96b1-d33dc7378b2c",
"name": "Artikel pruefen"
},
{
@@ -392,7 +392,7 @@
1560,
420
],
- "id": "9158e17f-118f-4e28-b4bd-27c2f38ab958",
+ "id": "f163c0a3-181e-40c0-88a2-f2157042d415",
"name": "Score ermitteln",
"credentials": {
"httpHeaderAuth": {
@@ -416,7 +416,7 @@
1560,
640
],
- "id": "e5155865-6106-494e-b238-de63ec2fcf23",
+ "id": "4455414d-1ed4-48a3-921b-d10cafe23afd",
"name": "Fehler Score",
"onError": "continueRegularOutput"
},
@@ -431,7 +431,7 @@
1780,
420
],
- "id": "c670cfc5-5c64-46c4-b9d9-da2f9f7d998a",
+ "id": "0fe1ca56-4b69-4504-aa55-87766e82d1e6",
"name": "Score auswerten"
},
{
@@ -445,7 +445,7 @@
},
"conditions": [
{
- "id": "da564c6f-a42b-4cf5-82bf-da3d41877da7",
+ "id": "62fbfd27-c661-454c-b433-b5977f969e52",
"leftValue": "={{ $json.bestanden }}",
"rightValue": "",
"operator": {
@@ -466,7 +466,7 @@
2000,
420
],
- "id": "3dfb6f30-0274-49c2-84c0-a010d862298c",
+ "id": "ca4f34e1-f915-4fcf-91bd-6cbcc71abe77",
"name": "Score erreicht?"
},
{
@@ -480,7 +480,7 @@
2220,
640
],
- "id": "bac5edd8-d56a-4533-b56c-f3b24bab3263",
+ "id": "f598e1f9-a15a-4b34-a59d-5bdf15513402",
"name": "Fehlende Begriffe ermitteln"
},
{
@@ -505,7 +505,7 @@
2440,
640
],
- "id": "3d514218-62c0-439e-8594-7ccc5ebebdae",
+ "id": "2a20625a-a1b4-470a-a558-edf71e3985b8",
"name": "Nachbesserung vorhanden?",
"credentials": {
"httpHeaderAuth": {
@@ -525,7 +525,7 @@
2660,
640
],
- "id": "a6b0f5d9-361d-4b09-bba3-e169b33b85ca",
+ "id": "4413cca3-3996-40ff-88bc-90deceac60c9",
"name": "Nachbesserung vorbereiten"
},
{
@@ -555,7 +555,7 @@
2880,
640
],
- "id": "4a9a76dc-68fa-4c8e-93a6-6c01f1ede8ce",
+ "id": "7c6e53c5-fb96-4c18-811b-c6764804b3f9",
"name": "Nachbesserung committen",
"credentials": {
"httpHeaderAuth": {
@@ -579,7 +579,7 @@
2880,
860
],
- "id": "b675c23e-1826-4d4c-ace8-a339efef67bf",
+ "id": "24d265f2-7379-4dcc-af74-bd275c293ac1",
"name": "Fehler Nachbesserung",
"onError": "continueRegularOutput"
},
@@ -594,7 +594,7 @@
3100,
640
],
- "id": "48d2bbc1-5cc0-496e-80ba-d002ee7ea687",
+ "id": "1bb8c4d3-91f7-4aa1-b0f3-ae176e5e4e3a",
"name": "Nachbesserung gemerkt"
},
{
@@ -624,7 +624,7 @@
2220,
240
],
- "id": "b84af3b2-4638-4713-81c1-c8d8747c0363",
+ "id": "b1dd3437-1339-47a8-acf2-ac50a07e2fba",
"name": "Revision zu NeuronWriter",
"credentials": {
"httpHeaderAuth": {
@@ -648,7 +648,7 @@
2220,
60
],
- "id": "c881c38e-ecdc-4a9c-a25f-e05e40c3e5ef",
+ "id": "41308b76-0798-4d3b-892f-bfa9a0c2331b",
"name": "Fehler Revision",
"onError": "continueRegularOutput"
},
@@ -674,7 +674,7 @@
2440,
240
],
- "id": "3fd00ae7-8bf9-4027-9119-bb8ba00d6e11",
+ "id": "92090059-6fd5-492b-b3d4-0f381ff24aa6",
"name": "Beitrag vorhanden?",
"credentials": {
"httpBasicAuth": {
@@ -708,7 +708,7 @@
2660,
240
],
- "id": "78586dc9-923d-49be-b5df-11f27a0cfbea",
+ "id": "db39d451-3fae-476d-ac3f-e8a7a754c3af",
"name": "Kategorie suchen",
"credentials": {
"httpBasicAuth": {
@@ -731,7 +731,7 @@
2880,
240
],
- "id": "e198abd7-2f0b-49f5-adc5-3c4a9ee3ab3a",
+ "id": "4574946a-3f6c-4616-9b39-ecc412802a93",
"name": "Kategorie pruefen"
},
{
@@ -745,7 +745,7 @@
},
"conditions": [
{
- "id": "b0e30459-6b54-4b74-8470-9520c5f3a9bb",
+ "id": "2cc73f4f-f7a9-4639-8e76-8a36e28e55ec",
"leftValue": "={{ $json.kategorie_fehlt }}",
"rightValue": "",
"operator": {
@@ -766,7 +766,7 @@
3100,
240
],
- "id": "bcbe3d18-9ecc-497e-886b-32cdab8263e7",
+ "id": "54eda814-f0fa-4289-b85c-7d6bf1552894",
"name": "Kategorie anlegen?"
},
{
@@ -796,7 +796,7 @@
3320,
140
],
- "id": "5a6a0165-5d7b-42f6-9818-2b76ebc1376b",
+ "id": "97c45331-c51c-4500-9559-1531e5dd69fe",
"name": "Kategorie anlegen",
"credentials": {
"httpBasicAuth": {
@@ -819,13 +819,13 @@
3540,
240
],
- "id": "c0e0e90e-e0d2-4a9d-8f9e-74fabf3a0561",
+ "id": "8cca486f-9191-4afb-9022-69a95657cf25",
"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 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 `
`)]));\n }\n const html = String(a.content_html || '');\n const stellen = [];\n const re2 = /]/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(`
${f.antwort || ''}
`)], true));\n });\n wurzel.push(behaelter([\n 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 ratgeber_reihenfolge: Number.isFinite(Number(a.reihenfolge)) && a.reihenfolge !== null && a.reihenfolge !== '' ? Number(a.reihenfolge) : 999,\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 ``)]));\n }\n const html = String(a.content_html || '');\n const stellen = [];\n const re2 = /]/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(`
${f.antwort || ''}
`)], true));\n });\n wurzel.push(behaelter([\n 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": "5712410d-e1ce-4147-a575-df8147fcdf86",
+ "id": "64401c03-a0e6-493d-be07-7054964fa3d7",
"name": "WordPress-Body bauen"
},
{
@@ -863,7 +863,7 @@
3980,
240
],
- "id": "7d1c919c-bb1e-4aff-b10b-ba9703dd66d7",
+ "id": "652a752f-aaf2-4169-bf1f-5e5c246b0575",
"name": "Beitrag anlegen oder aktualisieren",
"credentials": {
"httpBasicAuth": {
@@ -886,7 +886,7 @@
4200,
240
],
- "id": "553a5836-dd1b-44c5-9146-453e2fbf6bb6",
+ "id": "5c89708b-21a3-466b-a142-90ca5776cabb",
"name": "Ergebnis pruefen"
},
{
@@ -917,7 +917,7 @@
4420,
240
],
- "id": "ec545d25-2c80-4b3b-9b7b-dd30b61990d8",
+ "id": "617d82a6-7c93-42df-8aa3-5345ea5e5a11",
"name": "Cache leeren",
"credentials": {
"httpBasicAuth": {
@@ -937,7 +937,7 @@
4640,
240
],
- "id": "db87877c-313a-434a-a1ee-fe0f0644ad86",
+ "id": "e930894f-0a23-4b6f-8672-539fddded17a",
"name": "Importergebnis merken"
},
{
@@ -951,9 +951,9 @@
4860,
420
],
- "id": "06918dbc-a5cc-4598-ab4f-12963601622a",
+ "id": "b52ef44c-0a99-428f-ac87-6f23ad851dcc",
"name": "Pause Rate Limit",
- "webhookId": "5be31916-7738-453d-b4a8-c3f58380d328"
+ "webhookId": "601a717a-a791-4885-9ae4-6211c4042840"
},
{
"parameters": {
@@ -966,7 +966,7 @@
},
"conditions": [
{
- "id": "38a37fb2-cd95-46ec-bdde-60a18b572c1c",
+ "id": "3479c179-c254-4e07-bbe3-139c2232cb41",
"leftValue": "={{ $('Konfiguration').first().json.bild_aktiv === true && !Number($json.featured_media) }}",
"rightValue": "",
"operator": {
@@ -987,7 +987,7 @@
4860,
240
],
- "id": "04b068e9-ed31-4dcc-8970-04daebd61b7f",
+ "id": "ac9b1b62-9967-4bbf-b437-5b6d9d9c4f03",
"name": "Beitragsbild fehlt?"
},
{
@@ -1012,7 +1012,7 @@
5080,
120
],
- "id": "e11b4f49-0c55-4392-861c-d66a9762a623",
+ "id": "fe7baabb-0017-4539-b367-78b55a7b59a4",
"name": "Bilddaten laden",
"credentials": {
"httpHeaderAuth": {
@@ -1035,7 +1035,7 @@
5300,
120
],
- "id": "ebe17d0b-3139-4aca-85cd-b5345c550c1b",
+ "id": "7b14c694-e412-4a82-ac54-3dc038492daa",
"name": "Bildprompt bauen"
},
{
@@ -1049,7 +1049,7 @@
},
"conditions": [
{
- "id": "6d7b129e-23c1-40b4-949d-0b8b628a4984",
+ "id": "9cc14322-cbf9-4d63-8032-75f3a326e208",
"leftValue": "={{ !!$json.bild_prompt }}",
"rightValue": "",
"operator": {
@@ -1070,7 +1070,7 @@
5520,
120
],
- "id": "583dad0b-2fec-4bfb-8c50-32edd965c782",
+ "id": "ac5412be-fd49-4ae6-b727-54bb8a5e7b5b",
"name": "Bildprompt vorhanden?"
},
{
@@ -1100,7 +1100,7 @@
5740,
40
],
- "id": "3d2143cc-e937-4dca-bff4-1705102f41a2",
+ "id": "f4a67217-eac5-4292-b48f-97dd852db890",
"name": "Bild erzeugen",
"credentials": {
"httpHeaderAuth": {
@@ -1124,7 +1124,7 @@
5740,
260
],
- "id": "bff3ef3d-9be6-4b71-ad55-7de2a9244440",
+ "id": "d0507f71-febb-4770-8879-289cd5db8bde",
"name": "Fehler Bild",
"onError": "continueRegularOutput"
},
@@ -1139,7 +1139,7 @@
5960,
40
],
- "id": "d44f9381-0b68-4872-bd92-cb14cab24dfb",
+ "id": "0a26c40e-f669-447c-9873-b4658a0caa75",
"name": "Bild als Datei"
},
{
@@ -1174,7 +1174,7 @@
6180,
40
],
- "id": "47c9c7e9-1f65-4434-b4b0-05ce2577a98b",
+ "id": "1f93892d-ca03-480f-80d4-d3e9136ba6be",
"name": "Bild in Mediathek",
"credentials": {
"httpBasicAuth": {
@@ -1208,7 +1208,7 @@
6400,
40
],
- "id": "85b0be75-4b66-4af7-83a0-a00d5b9fcbd9",
+ "id": "df178293-f772-47db-bd70-5ebbbfecc99b",
"name": "Beitragsbild setzen",
"credentials": {
"httpBasicAuth": {
@@ -1231,7 +1231,7 @@
6620,
40
],
- "id": "0de4b676-fd3c-4438-aeb5-d279effc2bc8",
+ "id": "7acb87e1-6d08-4068-86e1-148e66e30a15",
"name": "Bild gemerkt"
},
{
@@ -1245,7 +1245,7 @@
900,
80
],
- "id": "6e3219cb-c2be-44d0-b2d0-6b182329ccc9",
+ "id": "de688426-8e55-4f3e-8fa0-6b6c536a645e",
"name": "Ergebnisse sammeln"
},
{
@@ -1270,7 +1270,7 @@
1120,
80
],
- "id": "b368e64d-9aac-4cde-be95-4d061e76bd37",
+ "id": "c4d40ae3-0c2a-4c85-afb2-8e35e3b7a910",
"name": "Statustabelle lesen",
"credentials": {
"httpHeaderAuth": {
@@ -1290,7 +1290,7 @@
1340,
80
],
- "id": "6fb9c69b-f398-4cc3-a1e5-633f91c24923",
+ "id": "ef884157-2d8f-4108-8413-f8a44d30377c",
"name": "Statustabelle fortschreiben"
},
{
@@ -1304,7 +1304,7 @@
},
"conditions": [
{
- "id": "04e69285-96fe-4313-9a53-22c17b0a1923",
+ "id": "88098264-cf81-4b80-8dd6-24e2b69ac9f6",
"leftValue": "={{ $json.ueberspringen }}",
"rightValue": "",
"operator": {
@@ -1325,7 +1325,7 @@
1560,
80
],
- "id": "5049ff31-926b-4c6c-a728-ba463fdd565b",
+ "id": "680f0cda-2253-4542-a268-b5789d36e3ba",
"name": "Gab es Importe?"
},
{
@@ -1355,7 +1355,7 @@
1780,
160
],
- "id": "73f50d9b-9334-4f6c-a702-ced32babc406",
+ "id": "e3c52d54-c52c-4afb-ba79-4de6fcbe6668",
"name": "Statustabelle schreiben",
"credentials": {
"httpHeaderAuth": {
@@ -1379,7 +1379,7 @@
1780,
340
],
- "id": "652ccbd8-38df-40b6-8cf9-43bd9ff9ba7c",
+ "id": "fe8b8aca-18cb-4111-858f-61863c8b411f",
"name": "Fehler Statustabelle",
"onError": "continueRegularOutput"
},
@@ -1394,7 +1394,7 @@
2000,
80
],
- "id": "80192868-178f-4eca-a313-073f06bf3ce3",
+ "id": "557665ff-7190-485b-bbba-b001c50d04bf",
"name": "Meldung formulieren"
},
{
@@ -1412,7 +1412,7 @@
2220,
80
],
- "id": "c81c485d-8ac3-4c5c-bf3e-e2934a349f32",
+ "id": "0d7c548b-73ac-49a6-a2cd-0c4791507058",
"name": "Benachrichtigung",
"disabled": true,
"notes": "SMTP-Credential zuweisen, Absender eintragen, dann aktivieren."