From 150a654cb94993763b05159a3764e6c048b0c096 Mon Sep 17 00:00:00 2001 From: crille Date: Thu, 2 Apr 2026 11:59:34 +0200 Subject: [PATCH] =?UTF-8?q?Startpunkten=20f=C3=B6r=20version=201.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 0 block.json | 8 +++ crille-schema.php | 109 ++++++++++++++++++++++++++++++ css/style.css | 25 +++++++ src/block.js | 11 +++ src/frontend.js | 169 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 322 insertions(+) create mode 100644 README.md create mode 100644 block.json create mode 100644 crille-schema.php create mode 100644 css/style.css create mode 100644 src/block.js create mode 100644 src/frontend.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/block.json b/block.json new file mode 100644 index 0000000..abdaf70 --- /dev/null +++ b/block.json @@ -0,0 +1,8 @@ +{ + "apiVersion": 2, + "name": "hv/schema-block", + "title": "HV Schema", + "category": "widgets", + "icon": "calendar", + "editorScript": "hv-schema-editor" +} diff --git a/crille-schema.php b/crille-schema.php new file mode 100644 index 0000000..55b8eb2 --- /dev/null +++ b/crille-schema.php @@ -0,0 +1,109 @@ +'hv_schema_render']); +} +add_action('init','hv_schema_register_block'); +add_action('wp_enqueue_scripts',function(){ wp_enqueue_script('hv-schema-frontend'); wp_enqueue_style('hv-schema-style');}); + +function hv_schema_render(){ return '
'; } + +// server-side ICS fetch +add_action('rest_api_init', function(){ + register_rest_route('hv/v1','/schema',[ 'methods'=>'GET', 'callback'=>'hv_schema_fetch' ]); +}); + +function hv_schema_fetch(){ + + // Hämta settings + $signatur = get_option('hv_schema_signatur', 's.COH'); + $antal = get_option('hv_schema_antal', 7); + + // Bygg ICS-URL dynamiskt + $url = 'https://schema.hv.se/setup/jsp/SchemaICAL.ics' + . '?startDatum=idag' + . '&intervallTyp=m' + . '&intervallAntal=6' + . '&sprak=SV' + . '&sokMedAND=true' + . '&forklaringar=true' + . '&resurser=' . urlencode($signatur); + + $res = wp_remote_get($url); + if(is_wp_error($res)) return []; + + // Skicka både ICS OCH antal + return [ + 'ics' => wp_remote_retrieve_body($res), + 'antal' => intval($antal) + ]; +} + +function hv_schema_settings_page(){ ?> +
+

HV Schema – Inställningar

+
+ + + + + + + + + + + + + + +
Signatur / Resurskod + +

Exempel: s.COH (din signatur)

+
Antal poster + +
+ + + +
+
+ \ No newline at end of file diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..f53e35f --- /dev/null +++ b/css/style.css @@ -0,0 +1,25 @@ +#hv-schema { + padding: 10px; + font-family: Arial; +} +.hv-item { + border: 1px solid #ccc; + padding: 8px; + margin-bottom: 6px; + border-radius: 4px; +} +.hv-kurs { + font-weight: bold; + color: #003366; +} +.hv-moment { + font-style: italic; +} +.hv-datum, +.hv-tid { + color: #333; +} +.hv-lokal { + color: #660000; + font-weight: bold; +} diff --git a/src/block.js b/src/block.js new file mode 100644 index 0000000..ad72afc --- /dev/null +++ b/src/block.js @@ -0,0 +1,11 @@ +(function (blocks, element) { + var el = element.createElement; + blocks.registerBlockType("hv/schema-block", { + edit: function () { + return el("p", {}, "Crille Schema-block (frontend visar data)."); + }, + save: function () { + return null; + }, + }); +})(window.wp.blocks, window.wp.element); diff --git a/src/frontend.js b/src/frontend.js new file mode 100644 index 0000000..13b8734 --- /dev/null +++ b/src/frontend.js @@ -0,0 +1,169 @@ +document.addEventListener("DOMContentLoaded", () => { + const el = document.getElementById("hv-schema"); + if (!el) return; + wp.apiFetch({ path: "/hv/v1/schema" }).then((raw) => { + const events = parseICS(raw.ics).slice(0, raw.antal); + el.innerHTML = events + .map( + (ev) => ` +
+
${ev.kurs}
+
${ev.moment}
+
${ev.datum}
+
${ev.tid}
+
${ev.lokal}
+
`, + ) + .join(""); + }); +}); +function parseICS(data) { + const lines = data.split(/\r?\n/); + let ev = [], + cur = null; + + for (const l of lines) { + if (l.startsWith("BEGIN:VEVENT")) cur = {}; + + if (l.startsWith("SUMMARY:")) { + const raw = l.replace("SUMMARY:", "").trim(); + + // + // ======== KURS ========= + // + let kurs = ""; + let kursMatch = raw.match(/Kurs\.grp:\s*([^:]+?)(?:\s+Sign:|$)/); + + if (kursMatch) { + kurs = kursMatch[1].trim(); + } + + // ✅ Ta bort dubletter: "A A" → "A" + kurs = kurs.replace(/^(.*)\s+\1$/, "$1").trim(); + + cur.kurs = kurs; + + // + // ======== MOMENT ========= + // + let moment = ""; + let momentMatch = raw.match(/Moment:\s*(.+?)(?:\s+Aktivitetstyp:|$)/); + + if (momentMatch) { + moment = momentMatch[1].trim(); + } else { + // fallback + const parts = raw.split(" - "); + if (parts[1]) { + moment = parts[1].trim(); + } + } + + // Rensa "Moment:" om det ligger kvar + moment = moment.replace(/^Moment:\s*/i, "").trim(); + + cur.moment = moment; + } + + // + // ======== DATUM & TID ========= + // + if (l.startsWith("DTSTART:")) { + const dt = l.replace("DTSTART:", ""); + cur.datum = dt.substring(0, 4) + "-" + dt.substring(4, 6) + "-" + dt.substring(6, 8); + cur.tid = dt.substring(9, 11) + ":" + dt.substring(11, 13); + } + + // + // ======== LOKAL ========= + // + if (l.startsWith("LOCATION:")) cur.lokal = l.replace("LOCATION:", "").trim(); + + if (l.startsWith("END:VEVENT")) ev.push(cur); + } + + return ev; +} +function parseICS(data) { + const lines = data.split(/\r?\n/); + let ev = [], + cur = null; + + for (const l of lines) { + if (l.startsWith("BEGIN:VEVENT")) cur = {}; + + if (l.startsWith("SUMMARY:")) { + const raw = l.replace("SUMMARY:", "").trim(); + + // + // ======== KURS ========= + // + let kurs = ""; + let kursMatch = raw.match(/Kurs\.grp:\s*([^:]+?)(?:\s+Sign:|$)/); + + if (kursMatch) kurs = kursMatch[1].trim(); + kurs = kurs.replace(/^(.*)\s+\1$/, "$1").trim(); // ta bort dubletter + cur.kurs = kurs; + + // + // ======== MOMENT ========= + // + let moment = ""; + let momentMatch = raw.match(/Moment:\s*(.+?)(?:\s+Aktivitetstyp:|$)/); + + if (momentMatch) { + moment = momentMatch[1].trim(); + } else { + const parts = raw.split(" - "); + if (parts[1]) moment = parts[1].trim(); + } + + moment = moment.replace(/^Moment:\s*/i, "").trim(); + cur.moment = moment; + } + + // + // ======== DATUM & TID (MED SVERIGE-TID) ======== + // + if (l.startsWith("DTSTART:")) { + const dt = l.replace("DTSTART:", ""); + + // Plocka ut delar ur ICS-datumet + const year = Number(dt.substring(0, 4)); + const month = Number(dt.substring(4, 6)); + const day = Number(dt.substring(6, 8)); + const hour = Number(dt.substring(9, 11)); + const min = Number(dt.substring(11, 13)); + + // Skapa en UTC-tid från ICS + const dateUTC = new Date(Date.UTC(year, month - 1, day, hour, min)); + + // Konvertera till svensk tid + const dateLocal = new Date( + dateUTC.toLocaleString("sv-SE", { timeZone: "Europe/Stockholm" }), + ); + + // Formatera svensk datumtext, t.ex. "2 april 2026" + const formattedDate = new Intl.DateTimeFormat("sv-SE", { + day: "numeric", + month: "long", + year: "numeric", + }).format(dateLocal); + + // Formatera tiden, t.ex. "13:15" + const hh = String(dateLocal.getHours()).padStart(2, "0"); + const mi = String(dateLocal.getMinutes()).padStart(2, "0"); + + cur.datum = formattedDate; + cur.tid = `${hh}:${mi}`; + } + // + // ======== LOKAL ========= + // + if (l.startsWith("LOCATION:")) cur.lokal = l.replace("LOCATION:", "").trim(); + + if (l.startsWith("END:VEVENT")) ev.push(cur); + } + + return ev; +}