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 `

${a.kurzantwort}

`)]));\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 `

${a.kurzantwort}

`)]));\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."