diff --git a/ansico-diagnosekoder/README.txt b/ansico-diagnosekoder/README.txt
new file mode 100644
index 0000000..e3e1fdb
--- /dev/null
+++ b/ansico-diagnosekoder/README.txt
@@ -0,0 +1,37 @@
+=== Ansico Diagnosekoder ===
+Contributors: aphandersen
+Tags: icd-10, diagnosis, gutenberg, search
+Requires at least: 6.2
+Tested up to: 6.5
+Stable tag: 1.0.0
+License: GPLv3 or later
+License URI: https://www.gnu.org/licenses/gpl-3.0.html
+
+Et WordPress-plugin der tilføjer Gutenberg-blokke til søgning i ICD-10 diagnosekoder baseret på en uploadet tekstfil.
+
+== Beskrivelse ==
+- Tilføjer blokken "Ansico Diagnosekoder" med live resultater
+- Tilføjer blokken "Ansico Diagnosekoder Søgefelt" til sidebar eller andre kompakte områder
+- Tilføjer blokken "Ansico Diagnosekoder Kodekort" til visning af side-tilknyttede koder
+- Backend-side under Indstillinger
+- Upload af ny tekstfil erstatter eksisterende datagrundlag
+- Søger kun i felterne kode og tekst
+- Søgeord highlightes i resultaterne
+- Klik på et resultat kopierer diagnosekoden
+
+== Installation ==
+1. Upload plugin-mappen til /wp-content/plugins/ eller installer ZIP-filen via WordPress.
+2. Aktivér pluginet.
+3. Gå til Indstillinger → Ansico Diagnosekoder.
+4. Upload din tekstfil.
+5. Indsæt blokken "Ansico Diagnosekoder" på en side.
+
+
+== Changelog ==
+
+= 1.0.0 =
+* Første stabile release.
+
+
+== Support ==
+Support: https://ansico.dk/Ansico/Ansico-diagnosekoder
diff --git a/ansico-diagnosekoder/ansico-diagnosekoder.php b/ansico-diagnosekoder/ansico-diagnosekoder.php
new file mode 100644
index 0000000..1558ef5
--- /dev/null
+++ b/ansico-diagnosekoder/ansico-diagnosekoder.php
@@ -0,0 +1,55 @@
+ 0 ? pageId : 0;
+ }
+
+ function preloadPageState(root) {
+ if (!root || !root.getAttribute) {
+ return;
+ }
+
+ var stateNode = root.querySelector('.ansico-diagnosekoder__page-state');
+ if (stateNode) {
+ try {
+ var state = JSON.parse(stateNode.textContent || '{}');
+ if (Array.isArray(state.favorites)) {
+ favoriteCodes = state.favorites.map(normalizeCode);
+ }
+ if (state.links && typeof state.links === 'object') {
+ codeLinks = {};
+ Object.keys(state.links).forEach(function (code) {
+ codeLinks[normalizeCode(code)] = state.links[code];
+ });
+ }
+ if (state.pageId && root.getAttribute('data-page-id') !== String(state.pageId)) {
+ root.setAttribute('data-page-id', String(state.pageId));
+ }
+ return;
+ } catch (error) {}
+ }
+
+ var favoritesRaw = root.getAttribute('data-page-favorites') || '[]';
+ var linksRaw = root.getAttribute('data-page-links') || '{}';
+
+ try {
+ var parsedFavorites = JSON.parse(favoritesRaw);
+ if (Array.isArray(parsedFavorites)) {
+ favoriteCodes = parsedFavorites.map(normalizeCode);
+ }
+ } catch (error) {}
+
+ try {
+ var parsedLinks = JSON.parse(linksRaw);
+ if (parsedLinks && typeof parsedLinks === 'object') {
+ codeLinks = {};
+ Object.keys(parsedLinks).forEach(function (code) {
+ codeLinks[normalizeCode(code)] = parsedLinks[code];
+ });
+ }
+ } catch (error) {}
+ }
+
+ function debounce(fn, delay) {
+ var timer;
+ return function () {
+ var context = this;
+ var args = arguments;
+ clearTimeout(timer);
+ timer = setTimeout(function () {
+ fn.apply(context, args);
+ }, delay);
+ };
+ }
+
+ function escapeHtml(str) {
+ return String(str)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ }
+
+ function escapeRegExp(str) {
+ return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ }
+
+ function normalizeCode(code) {
+ return String(code || '').replace(/\s+/g, '').toUpperCase();
+ }
+
+ function highlightText(text, query) {
+ var safeText = escapeHtml(text);
+ var trimmedQuery = String(query || '').trim();
+
+ if (!trimmedQuery) {
+ return safeText;
+ }
+
+ var parts = trimmedQuery.split(/\s+/).filter(Boolean);
+ if (!parts.length) {
+ return safeText;
+ }
+
+ var pattern = parts.map(escapeRegExp).join('|');
+ return safeText.replace(new RegExp('(' + pattern + ')', 'gi'), '$1');
+ }
+
+ function getCopyIcon() {
+ return '';
+ }
+
+ function getStarIcon(isActive) {
+ if (isActive) {
+ return '';
+ }
+
+ return '';
+ }
+
+ function getPlusIcon() {
+ return '';
+ }
+
+ function getMinusIcon() {
+ return '';
+ }
+
+ function isFavorite(code) {
+ return favoriteCodes.indexOf(normalizeCode(code)) !== -1;
+ }
+
+ function getLinkedUrl(code, fallback) {
+ var normalized = normalizeCode(code);
+ if (normalized && typeof codeLinks[normalized] === 'string' && codeLinks[normalized]) {
+ return codeLinks[normalized];
+ }
+ return fallback || '';
+ }
+
+ function setTemporaryLabel(button, label, duration) {
+ var originalLabel = button.getAttribute('data-label-default') || button.getAttribute('aria-label') || '';
+ button.setAttribute('aria-label', label);
+ button.classList.add('is-feedback-visible');
+ window.setTimeout(function () {
+ button.setAttribute('aria-label', originalLabel);
+ button.classList.remove('is-feedback-visible');
+ }, duration || 1400);
+ }
+
+ function pulseButton(button) {
+ button.classList.remove('is-pressed');
+ void button.offsetWidth;
+ button.classList.add('is-pressed');
+ window.setTimeout(function () {
+ button.classList.remove('is-pressed');
+ }, 220);
+ }
+
+ function copyText(value, button) {
+ var successLabel = button.getAttribute('data-label-success') || button.getAttribute('aria-label') || '';
+ var errorLabel = button.getAttribute('data-label-error') || button.getAttribute('aria-label') || '';
+ pulseButton(button);
+
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(value).then(function () {
+ setTemporaryLabel(button, successLabel, 1400);
+ }).catch(function () {
+ setTemporaryLabel(button, errorLabel, 1400);
+ });
+ return;
+ }
+
+ try {
+ var temp = document.createElement('textarea');
+ temp.value = value;
+ temp.setAttribute('readonly', 'readonly');
+ temp.style.position = 'absolute';
+ temp.style.left = '-9999px';
+ document.body.appendChild(temp);
+ temp.select();
+ document.execCommand('copy');
+ document.body.removeChild(temp);
+ setTemporaryLabel(button, successLabel, 1400);
+ } catch (error) {
+ setTemporaryLabel(button, errorLabel, 1400);
+ }
+ }
+
+
+
+
+
+ function searchInternalContent(query) {
+ if (!AnsicoDiagnosekoderConfig.contentSearchUrl) {
+ return Promise.resolve([]);
+ }
+
+ var url = AnsicoDiagnosekoderConfig.contentSearchUrl + '?q=' + encodeURIComponent(query) + '&limit=8';
+ return fetch(url, {
+ credentials: 'same-origin',
+ headers: {
+ 'X-WP-Nonce': AnsicoDiagnosekoderConfig.nonce
+ },
+ cache: 'no-store'
+ }).then(function (response) {
+ if (!response.ok) {
+ throw new Error('Internal content search failed');
+ }
+ return response.json();
+ }).then(function (data) {
+ return Array.isArray(data.results) ? data.results : [];
+ });
+ }
+
+ function ensureLinkModal() {
+ var existing = document.getElementById('ansico-diagnosekoder-link-modal');
+ if (existing) {
+ return existing;
+ }
+
+ var wrapper = document.createElement('div');
+ wrapper.id = 'ansico-diagnosekoder-link-modal';
+ wrapper.className = 'ansico-diagnosekoder__modal';
+ wrapper.setAttribute('hidden', 'hidden');
+ wrapper.innerHTML = '' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ '' +
+ '
';
+ document.body.appendChild(wrapper);
+ return wrapper;
+ }
+
+ function openLinkModal(code, currentUrl) {
+ var modal = ensureLinkModal();
+ var titleNode = modal.querySelector('.ansico-diagnosekoder__modal-title');
+ var labels = modal.querySelectorAll('.ansico-diagnosekoder__modal-label');
+ var hintNode = modal.querySelector('.ansico-diagnosekoder__modal-hint');
+ var codeInput = modal.querySelector('#ansico-diagnosekoder-link-modal-code');
+ var urlInput = modal.querySelector('#ansico-diagnosekoder-link-modal-url');
+ var searchInput = modal.querySelector('#ansico-diagnosekoder-link-modal-search');
+ var resultsNode = modal.querySelector('.ansico-diagnosekoder__modal-search-results');
+ var cancelButton = modal.querySelector('[data-modal-cancel="1"]');
+ var saveButton = modal.querySelector('[data-modal-save="1"]');
+ var closeButtons = modal.querySelectorAll('[data-modal-close="1"]');
+ var activeSearchToken = 0;
+
+ titleNode.textContent = currentUrl ? AnsicoDiagnosekoderConfig.strings.linkModalTitleEdit : AnsicoDiagnosekoderConfig.strings.linkModalTitleAdd;
+ labels[0].textContent = AnsicoDiagnosekoderConfig.strings.linkModalCodeLabel;
+ labels[1].textContent = AnsicoDiagnosekoderConfig.strings.linkModalUrlLabel;
+ labels[2].textContent = AnsicoDiagnosekoderConfig.strings.linkModalSearchLabel;
+ hintNode.textContent = AnsicoDiagnosekoderConfig.strings.linkModalHint;
+ cancelButton.textContent = AnsicoDiagnosekoderConfig.strings.linkModalCancel;
+ saveButton.textContent = AnsicoDiagnosekoderConfig.strings.linkModalSave;
+ codeInput.value = code || '';
+ urlInput.value = currentUrl || '';
+ urlInput.placeholder = AnsicoDiagnosekoderConfig.strings.linkPromptDefault || 'https://';
+ searchInput.value = '';
+ searchInput.placeholder = AnsicoDiagnosekoderConfig.strings.linkModalSearchPlaceholder;
+ resultsNode.innerHTML = '' + escapeHtml(AnsicoDiagnosekoderConfig.strings.linkModalSearchEmpty) + '
';
+ modal.removeAttribute('hidden');
+ document.body.classList.add('ansico-diagnosekoder-modal-open');
+
+ function closeModal() {
+ modal.setAttribute('hidden', 'hidden');
+ document.body.classList.remove('ansico-diagnosekoder-modal-open');
+ saveButton.onclick = null;
+ cancelButton.onclick = null;
+ closeButtons.forEach(function (button) { button.onclick = null; });
+ searchInput.oninput = null;
+ document.removeEventListener('keydown', onKeyDown);
+ }
+
+ function onKeyDown(event) {
+ if (event.key === 'Escape') {
+ closeModal();
+ }
+ }
+
+ function renderSearchResults(items) {
+ if (!items.length) {
+ resultsNode.innerHTML = '' + escapeHtml(AnsicoDiagnosekoderConfig.strings.linkModalSearchNone) + '
';
+ return;
+ }
+
+ var html = '';
+ items.forEach(function (item) {
+ html += '- ';
+ html += '
';
+ });
+ html += '
';
+ resultsNode.innerHTML = html;
+ resultsNode.querySelectorAll('.ansico-diagnosekoder__modal-result-button').forEach(function (button) {
+ button.addEventListener('click', function () {
+ urlInput.value = button.getAttribute('data-url') || '';
+ });
+ });
+ }
+
+ searchInput.oninput = debounce(function () {
+ var query = searchInput.value.trim();
+ activeSearchToken += 1;
+ var token = activeSearchToken;
+
+ if (!query) {
+ resultsNode.innerHTML = '' + escapeHtml(AnsicoDiagnosekoderConfig.strings.linkModalSearchEmpty) + '
';
+ return;
+ }
+
+ resultsNode.innerHTML = '' + escapeHtml(AnsicoDiagnosekoderConfig.strings.loading) + '
';
+ searchInternalContent(query).then(function (items) {
+ if (token !== activeSearchToken) {
+ return;
+ }
+ renderSearchResults(items);
+ }).catch(function () {
+ resultsNode.innerHTML = '' + escapeHtml(AnsicoDiagnosekoderConfig.strings.linkSearchError) + '
';
+ });
+ }, 180);
+
+ var resolver;
+ var promise = new Promise(function (resolve) {
+ resolver = resolve;
+ });
+
+ saveButton.onclick = function () {
+ resolver(String(urlInput.value || '').trim());
+ closeModal();
+ };
+ cancelButton.onclick = function () {
+ resolver(null);
+ closeModal();
+ };
+ closeButtons.forEach(function (button) {
+ button.onclick = function () {
+ resolver(null);
+ closeModal();
+ };
+ });
+
+ document.addEventListener('keydown', onKeyDown);
+ window.setTimeout(function () { urlInput.focus(); urlInput.select(); }, 0);
+ return promise;
+ }
+
+ function fetchPageState(pageId) {
+ if (!pageId || !AnsicoDiagnosekoderConfig.pageStateUrl) {
+ return Promise.resolve({ favorites: favoriteCodes, links: codeLinks, page_id: pageId || 0 });
+ }
+
+ var url = AnsicoDiagnosekoderConfig.pageStateUrl + '?page_id=' + encodeURIComponent(pageId) + '&_ansico_ts=' + Date.now();
+ return fetch(url, {
+ credentials: 'same-origin',
+ cache: 'no-store'
+ }).then(function (response) {
+ if (!response.ok) {
+ throw new Error('Page state failed');
+ }
+ return response.json();
+ }).then(function (data) {
+ syncClientCollections(data || {});
+ return data || {};
+ }).catch(function () {
+ return { favorites: favoriteCodes, links: codeLinks, page_id: pageId || 0 };
+ });
+ }
+
+ function mergeAndSortResults(results) {
+ var merged = (Array.isArray(results) ? results : []).map(function (item, index) {
+ var normalizedCode = normalizeCode(item.code);
+ var favorite = isFavorite(normalizedCode) || !!item.is_favorite;
+ var linkedUrl = getLinkedUrl(normalizedCode, item.linked_url || '');
+ return Object.assign({}, item, {
+ is_favorite: favorite,
+ linked_url: linkedUrl,
+ _normalized_code: normalizedCode,
+ _render_index: index
+ });
+ });
+
+ merged.sort(function (a, b) {
+ if (!!a.is_favorite !== !!b.is_favorite) {
+ return a.is_favorite ? -1 : 1;
+ }
+ return a._render_index - b._render_index;
+ });
+
+ return merged;
+ }
+
+ function renderResults(container, results, query) {
+ if (!results.length) {
+ container.innerHTML = '' + escapeHtml(AnsicoDiagnosekoderConfig.strings.noResults) + '
';
+ return;
+ }
+
+ var sortedResults = mergeAndSortResults(results);
+ var html = '';
+ sortedResults.forEach(function (item) {
+ var normalizedCode = item._normalized_code || normalizeCode(item.code);
+ var favorite = !!item.is_favorite;
+ var linkedUrl = item.linked_url || '';
+ var hasLink = !!linkedUrl;
+ var copyValue = item.code + ' - ' + item.text;
+ var copyAria = AnsicoDiagnosekoderConfig.strings.copyCodeAria.replace('%s', copyValue);
+ var favoriteAria = favorite ? AnsicoDiagnosekoderConfig.strings.removeFavorite : AnsicoDiagnosekoderConfig.strings.addFavorite;
+ var linkAria = hasLink ? AnsicoDiagnosekoderConfig.strings.removeLink : AnsicoDiagnosekoderConfig.strings.addLink;
+ var entryTagStart = hasLink ? '' : '';
+ var entryTagEnd = hasLink ? '' : '';
+
+ html += '- ';
+ html += '
';
+ html += entryTagStart + '
';
+ html += '' + highlightText(item.code, query) + '';
+ html += ' – ';
+ html += '' + highlightText(item.text, query) + '';
+ if (hasLink) {
+ html += '' + escapeHtml(AnsicoDiagnosekoderConfig.strings.openLink) + '';
+ }
+ html += '
' + entryTagEnd;
+ html += '
';
+ html += '';
+
+ if (AnsicoDiagnosekoderConfig.isLoggedIn) {
+ html += '';
+ html += '';
+ }
+
+ html += '
';
+ });
+ html += '
';
+ container.innerHTML = html;
+ }
+
+ function getQueryParam(name) {
+ var params = new URLSearchParams(window.location.search);
+ return params.get(name) || '';
+ }
+
+ function getSelectorEscaped(value) {
+ if (window.CSS && typeof window.CSS.escape === 'function') {
+ return window.CSS.escape(value);
+ }
+ return String(value).replace(/"/g, '\\"');
+ }
+
+ function updateFavoriteButtons(code, isFav, pageId) {
+ var normalizedCode = normalizeCode(code);
+ var selector = '.ansico-diagnosekoder__favorite-button[data-code="' + getSelectorEscaped(normalizedCode) + '"]';
+ if (pageId) {
+ selector = '.ansico-diagnosekoder[data-page-id="' + getSelectorEscaped(String(pageId)) + '"] ' + selector;
+ }
+ var buttons = document.querySelectorAll(selector);
+ buttons.forEach(function (button) {
+ var label = isFav ? AnsicoDiagnosekoderConfig.strings.removeFavorite : AnsicoDiagnosekoderConfig.strings.addFavorite;
+ button.classList.toggle('is-active', isFav);
+ button.setAttribute('aria-pressed', isFav ? 'true' : 'false');
+ button.setAttribute('aria-label', label);
+ button.setAttribute('data-label-default', label);
+ button.innerHTML = getStarIcon(isFav);
+ var item = button.closest('.ansico-diagnosekoder__item');
+ if (item) {
+ item.classList.toggle('is-favorite', isFav);
+ item.setAttribute('data-is-favorite', isFav ? '1' : '0');
+ }
+ });
+ }
+
+ function updateLinkButtons(code, url) {
+ var normalizedCode = normalizeCode(code);
+ var buttons = document.querySelectorAll('.ansico-diagnosekoder__link-button[data-code="' + getSelectorEscaped(normalizedCode) + '"]');
+ var hasLink = !!url;
+ buttons.forEach(function (button) {
+ var label = hasLink ? AnsicoDiagnosekoderConfig.strings.removeLink : AnsicoDiagnosekoderConfig.strings.addLink;
+ button.classList.toggle('is-active', hasLink);
+ button.setAttribute('aria-label', label);
+ button.setAttribute('data-label-default', label);
+ button.setAttribute('data-current-url', url || '');
+ button.setAttribute('title', hasLink ? AnsicoDiagnosekoderConfig.strings.removeLinkIcon : AnsicoDiagnosekoderConfig.strings.addLinkIcon);
+ button.innerHTML = hasLink ? getMinusIcon() : getPlusIcon();
+ var item = button.closest('.ansico-diagnosekoder__item');
+ if (item) {
+ item.classList.toggle('has-link', hasLink);
+ item.setAttribute('data-has-link', hasLink ? '1' : '0');
+ }
+ });
+ }
+
+ function syncClientCollections(data) {
+ if (Array.isArray(data.favorites)) {
+ favoriteCodes = data.favorites.map(normalizeCode);
+ }
+ if (data.links && typeof data.links === 'object') {
+ codeLinks = {};
+ Object.keys(data.links).forEach(function (code) {
+ codeLinks[normalizeCode(code)] = data.links[code];
+ });
+ }
+ }
+
+ function toggleFavorite(code, button, refreshCallback, pageId) {
+ fetch(AnsicoDiagnosekoderConfig.favoritesUrl, {
+ method: 'POST',
+ credentials: 'same-origin',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-WP-Nonce': AnsicoDiagnosekoderConfig.nonce
+ },
+ body: JSON.stringify({ code: normalizeCode(code), page_id: pageId || 0 })
+ }).then(function (response) {
+ if (!response.ok) {
+ throw new Error('Favorite toggle failed');
+ }
+ return response.json();
+ }).then(function (data) {
+ syncClientCollections(data);
+ pulseButton(button);
+ updateFavoriteButtons(code, !!data.is_favorite, pageId || 0);
+ if (typeof refreshCallback === 'function') {
+ refreshCallback();
+ }
+ }).catch(function () {
+ setTemporaryLabel(button, AnsicoDiagnosekoderConfig.strings.favoriteError, 1400);
+ });
+ }
+
+ function saveCodeLink(code, url, button, refreshCallback, pageId) {
+ fetch(AnsicoDiagnosekoderConfig.linksUrl, {
+ method: 'POST',
+ credentials: 'same-origin',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-WP-Nonce': AnsicoDiagnosekoderConfig.nonce
+ },
+ body: JSON.stringify({ code: normalizeCode(code), url: url, page_id: pageId || 0 })
+ }).then(function (response) {
+ if (!response.ok) {
+ throw new Error('Link save failed');
+ }
+ return response.json();
+ }).then(function (data) {
+ syncClientCollections(data);
+ pulseButton(button);
+ updateLinkButtons(code, data.url || '');
+ setTemporaryLabel(button, AnsicoDiagnosekoderConfig.strings.linkSaved, 1200);
+ if (typeof refreshCallback === 'function') {
+ refreshCallback();
+ }
+ }).catch(function () {
+ setTemporaryLabel(button, AnsicoDiagnosekoderConfig.strings.linkError, 1400);
+ });
+ }
+
+ function removeCodeLink(code, button, refreshCallback, pageId) {
+ var url = AnsicoDiagnosekoderConfig.linksUrl + '?code=' + encodeURIComponent(normalizeCode(code)) + '&page_id=' + encodeURIComponent(pageId || 0);
+ fetch(url, {
+ method: 'DELETE',
+ credentials: 'same-origin',
+ headers: {
+ 'X-WP-Nonce': AnsicoDiagnosekoderConfig.nonce
+ }
+ }).then(function (response) {
+ if (!response.ok) {
+ throw new Error('Link removal failed');
+ }
+ return response.json();
+ }).then(function (data) {
+ syncClientCollections(data);
+ pulseButton(button);
+ updateLinkButtons(code, '');
+ setTemporaryLabel(button, AnsicoDiagnosekoderConfig.strings.linkRemoved, 1200);
+ if (typeof refreshCallback === 'function') {
+ refreshCallback();
+ }
+ }).catch(function () {
+ setTemporaryLabel(button, AnsicoDiagnosekoderConfig.strings.linkError, 1400);
+ });
+ }
+
+
+ function initResultsBlock(root) {
+ preloadPageState(root);
+ var input = root.querySelector('.ansico-diagnosekoder__input');
+ var results = root.querySelector('.ansico-diagnosekoder__results');
+ var hasData = root.getAttribute('data-has-data') === '1';
+
+ if (!input || !results || !hasData) {
+ return;
+ }
+
+ var runSearch = function () {
+ var query = input.value.trim();
+
+ if (!query) {
+ results.innerHTML = '' + escapeHtml(AnsicoDiagnosekoderConfig.strings.empty) + '
';
+ return;
+ }
+
+ results.innerHTML = '' + escapeHtml(AnsicoDiagnosekoderConfig.strings.loading) + '
';
+ var pageId = getPageId(root);
+ var url = AnsicoDiagnosekoderConfig.restUrl + '?q=' + encodeURIComponent(query) + '&limit=50&page_id=' + encodeURIComponent(pageId) + '&_ansico_ts=' + Date.now();
+
+ fetchPageState(pageId).then(function () {
+ return fetch(url, {
+ credentials: 'same-origin',
+ cache: 'no-store'
+ });
+ })
+ .then(function (response) {
+ if (!response.ok) {
+ throw new Error('Request failed');
+ }
+ return response.json();
+ })
+ .then(function (data) {
+ syncClientCollections(data);
+ if (
+ (!Array.isArray(data.favorites) || !data.favorites.length) &&
+ (!data.links || !Object.keys(data.links).length) &&
+ (!favoriteCodes.length && !Object.keys(codeLinks).length)
+ ) {
+ preloadPageState(root);
+ }
+ renderResults(results, data.results || [], query);
+ })
+ .catch(function () {
+ results.innerHTML = '' + escapeHtml(AnsicoDiagnosekoderConfig.strings.error) + '
';
+ });
+ };
+
+ var debouncedSearch = debounce(runSearch, 200);
+ input.addEventListener('input', debouncedSearch);
+
+ results.addEventListener('click', function (event) {
+ var copyButton = event.target.closest('.ansico-diagnosekoder__copy-button');
+ if (copyButton) {
+ copyText(copyButton.getAttribute('data-copy-value') || '', copyButton);
+ return;
+ }
+
+ var favoriteButton = event.target.closest('.ansico-diagnosekoder__favorite-button');
+ if (favoriteButton) {
+ toggleFavorite(favoriteButton.getAttribute('data-code') || '', favoriteButton, runSearch, getPageId(root));
+ return;
+ }
+
+ var linkButton = event.target.closest('.ansico-diagnosekoder__link-button');
+ if (linkButton) {
+ var code = linkButton.getAttribute('data-code') || '';
+ var currentUrl = linkButton.getAttribute('data-current-url') || '';
+ if (currentUrl) {
+ removeCodeLink(code, linkButton, runSearch, getPageId(root));
+ return;
+ }
+ openLinkModal(code, currentUrl).then(function (enteredUrl) {
+ if (enteredUrl === null) {
+ return;
+ }
+ enteredUrl = String(enteredUrl).trim();
+ if (!enteredUrl) {
+ return;
+ }
+ saveCodeLink(code, enteredUrl, linkButton, runSearch, getPageId(root));
+ });
+ }
+ });
+
+ var stateNode = root.querySelector('.ansico-diagnosekoder__page-state');
+ var hasServerRenderedResults = false;
+
+ if (stateNode) {
+ try {
+ var initialState = JSON.parse(stateNode.textContent || '{}');
+ hasServerRenderedResults = !!(initialState && initialState.hasInitialResults);
+ } catch (error) {}
+ }
+
+ fetchPageState(getPageId(root)).then(function () {
+ var presetQuery = getQueryParam('q');
+ if (presetQuery) {
+ input.value = presetQuery;
+ if (!hasServerRenderedResults) {
+ runSearch();
+ }
+ }
+ });
+ }
+
+ function initSearchOnlyBlock(form) {
+ form.addEventListener('submit', function (event) {
+ var input = form.querySelector('.ansico-diagnosekoder__input');
+ var query = input ? input.value.trim() : '';
+ var resultsUrl = form.getAttribute('data-results-url') || AnsicoDiagnosekoderConfig.resultsUrl || '';
+
+ if (!resultsUrl) {
+ event.preventDefault();
+ var message = form.querySelector('.ansico-diagnosekoder__message');
+ if (message) {
+ message.textContent = AnsicoDiagnosekoderConfig.strings.missingResultsUrl;
+ }
+ return;
+ }
+
+ if (!query) {
+ event.preventDefault();
+ return;
+ }
+
+ event.preventDefault();
+ var url = new URL(resultsUrl, window.location.origin);
+ url.searchParams.set('q', query);
+ window.location.href = url.toString();
+ });
+ }
+
+ document.addEventListener('DOMContentLoaded', function () {
+ var resultsBlocks = document.querySelectorAll('.wp-block-ansico-diagnosekoder.ansico-diagnosekoder');
+ resultsBlocks.forEach(initResultsBlock);
+
+ var searchOnlyBlocks = document.querySelectorAll('.wp-block-ansico-diagnosekoder-soegefelt.ansico-diagnosekoder--search-only');
+ searchOnlyBlocks.forEach(initSearchOnlyBlock);
+ });
+})();
diff --git a/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-admin.php b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-admin.php
new file mode 100644
index 0000000..f40593a
--- /dev/null
+++ b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-admin.php
@@ -0,0 +1,388 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+
+
+
+
+
+ |
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+ |
+ |
+
+
+
+
+
+ |
+ |
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+
+
+ |
+ |
+
+
+ |
+ |
+
+
+ |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'ansico-diagnosekoder',
+ 'status' => 'settings_saved',
+ ], admin_url('options-general.php')));
+ exit;
+ }
+
+ public static function handle_import_data() {
+ if (!current_user_can('manage_options')) {
+ wp_die(esc_html__('Du har ikke rettigheder til dette.', 'ansico-diagnosekoder'));
+ }
+
+ check_admin_referer('ansico_diagnosekoder_import_data');
+
+ if (empty($_FILES['ansico_diagnosekoder_file']) || empty($_FILES['ansico_diagnosekoder_file']['tmp_name'])) {
+ wp_safe_redirect(add_query_arg([
+ 'page' => 'ansico-diagnosekoder',
+ 'status' => 'no_file',
+ ], admin_url('options-general.php')));
+ exit;
+ }
+
+ $file = $_FILES['ansico_diagnosekoder_file'];
+ $overrides = [
+ 'test_form' => false,
+ 'mimes' => [
+ 'txt' => 'text/plain',
+ 'csv' => 'text/plain',
+ ],
+ ];
+
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ $uploaded = wp_handle_upload($file, $overrides);
+
+ if (!empty($uploaded['error'])) {
+ self::redirect_with_error($uploaded['error']);
+ }
+
+ $parsed = Ansico_Diagnosekoder_Parser::parse_file($uploaded['file']);
+ if (!empty($parsed['meta']['error'])) {
+ self::redirect_with_error($parsed['meta']['error']);
+ }
+
+ if (empty($parsed['rows'])) {
+ self::redirect_with_error(__('Filen indeholdt ingen importerbare diagnosekoder.', 'ansico-diagnosekoder'));
+ }
+
+ update_option(ANSICO_DIAGNOSEKODER_OPTION_DATA, $parsed['rows'], false);
+ update_option(ANSICO_DIAGNOSEKODER_OPTION_META, $parsed['meta'], false);
+
+ wp_safe_redirect(add_query_arg([
+ 'page' => 'ansico-diagnosekoder',
+ 'status' => 'success',
+ ], admin_url('options-general.php')));
+ exit;
+ }
+
+ public static function handle_remove_data() {
+ if (!current_user_can('manage_options')) {
+ wp_die(esc_html__('Du har ikke rettigheder til dette.', 'ansico-diagnosekoder'));
+ }
+
+ check_admin_referer('ansico_diagnosekoder_remove_data');
+
+ delete_option(ANSICO_DIAGNOSEKODER_OPTION_DATA);
+ delete_option(ANSICO_DIAGNOSEKODER_OPTION_META);
+
+ wp_safe_redirect(add_query_arg([
+ 'page' => 'ansico-diagnosekoder',
+ 'status' => 'data_removed',
+ ], admin_url('options-general.php')));
+ exit;
+ }
+
+ public static function handle_remove_favorite() {
+ if (!current_user_can('manage_options')) {
+ wp_die(esc_html__('Du har ikke rettigheder til dette.', 'ansico-diagnosekoder'));
+ }
+
+ $code = isset($_POST['code']) ? sanitize_text_field(wp_unslash($_POST['code'])) : '';
+ if ($code === '') {
+ self::redirect_with_error(__('Der mangler en kode.', 'ansico-diagnosekoder'));
+ }
+
+ check_admin_referer('ansico_diagnosekoder_remove_favorite_' . $code);
+
+ $results_page_id = self::get_results_page_id();
+ if (!$results_page_id) {
+ self::redirect_with_error(__('Resultatsiden kunne ikke findes.', 'ansico-diagnosekoder'));
+ }
+
+ $favorites = Ansico_Diagnosekoder_REST::get_page_favorites($results_page_id);
+ $normalized_code = strtoupper(preg_replace('/\s+/', '', $code));
+ $favorites = array_values(array_filter($favorites, static function ($item) use ($normalized_code) {
+ return $item !== $normalized_code;
+ }));
+ update_post_meta($results_page_id, ANSICO_DIAGNOSEKODER_PAGE_FAVORITES_META, $favorites);
+
+ wp_safe_redirect(add_query_arg([
+ 'page' => 'ansico-diagnosekoder',
+ 'status' => 'favorite_removed',
+ ], admin_url('options-general.php')));
+ exit;
+ }
+
+ public static function handle_remove_link() {
+ if (!current_user_can('manage_options')) {
+ wp_die(esc_html__('Du har ikke rettigheder til dette.', 'ansico-diagnosekoder'));
+ }
+
+ $code = isset($_POST['code']) ? sanitize_text_field(wp_unslash($_POST['code'])) : '';
+ if ($code === '') {
+ self::redirect_with_error(__('Der mangler en kode.', 'ansico-diagnosekoder'));
+ }
+
+ check_admin_referer('ansico_diagnosekoder_remove_link_' . $code);
+
+ $results_page_id = self::get_results_page_id();
+ if (!$results_page_id) {
+ self::redirect_with_error(__('Resultatsiden kunne ikke findes.', 'ansico-diagnosekoder'));
+ }
+
+ $normalized_code = strtoupper(preg_replace('/\s+/', '', $code));
+ $links = Ansico_Diagnosekoder_REST::get_page_links($results_page_id);
+ unset($links[$normalized_code]);
+ update_post_meta($results_page_id, ANSICO_DIAGNOSEKODER_PAGE_LINKS_META, $links);
+
+ wp_safe_redirect(add_query_arg([
+ 'page' => 'ansico-diagnosekoder',
+ 'status' => 'link_removed',
+ ], admin_url('options-general.php')));
+ exit;
+ }
+
+ protected static function get_results_page_favorite_rows() {
+ $results_page_id = self::get_results_page_id();
+ if (!$results_page_id) {
+ return [];
+ }
+
+ return Ansico_Diagnosekoder_Parser::get_rows_by_codes(Ansico_Diagnosekoder_REST::get_page_favorites($results_page_id));
+ }
+
+ protected static function get_results_page_id() {
+ $results_url = (string) get_option(ANSICO_DIAGNOSEKODER_OPTION_RESULTS_URL, '');
+ if ($results_url === '') {
+ return 0;
+ }
+
+ $page_id = url_to_postid($results_url);
+ return absint($page_id);
+ }
+
+ protected static function get_results_page_link_rows() {
+ $results_page_id = self::get_results_page_id();
+ if (!$results_page_id) {
+ return [];
+ }
+
+ $links = Ansico_Diagnosekoder_REST::get_page_links($results_page_id);
+ if (empty($links)) {
+ return [];
+ }
+
+ $rows = Ansico_Diagnosekoder_Parser::get_rows_by_codes(array_keys($links));
+ foreach ($rows as &$row) {
+ $row['url'] = isset($links[$row['code']]) ? $links[$row['code']] : '';
+ }
+ unset($row);
+
+ return $rows;
+ }
+
+ protected static function redirect_with_error($message) {
+ set_transient('ansico_diagnosekoder_admin_error', wp_strip_all_tags((string) $message), 60);
+ wp_safe_redirect(add_query_arg([
+ 'page' => 'ansico-diagnosekoder',
+ 'status' => 'error',
+ ], admin_url('options-general.php')));
+ exit;
+ }
+}
diff --git a/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-block.php b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-block.php
new file mode 100644
index 0000000..f074294
--- /dev/null
+++ b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-block.php
@@ -0,0 +1,350 @@
+ esc_url_raw(rest_url('ansico-diagnosekoder/v1/search')),
+ 'favoritesUrl' => esc_url_raw(rest_url('ansico-diagnosekoder/v1/favorites')),
+ 'pageStateUrl' => esc_url_raw(rest_url('ansico-diagnosekoder/v1/page-state')),
+ 'contentSearchUrl' => esc_url_raw(rest_url('ansico-diagnosekoder/v1/content-search')),
+ 'linksUrl' => esc_url_raw(rest_url('ansico-diagnosekoder/v1/links')),
+ 'resultsUrl' => esc_url_raw((string) get_option(ANSICO_DIAGNOSEKODER_OPTION_RESULTS_URL, '')),
+ 'nonce' => wp_create_nonce('wp_rest'),
+ 'isLoggedIn' => is_user_logged_in(),
+ 'resultsPageId' => self::get_results_page_id(),
+ 'strings' => [
+ 'placeholder' => __('Søg efter diagnosekode eller beskrivelse', 'ansico-diagnosekoder'),
+ 'empty' => __('Skriv for at søge i diagnosekoder.', 'ansico-diagnosekoder'),
+ 'noResults' => __('Ingen resultater fundet.', 'ansico-diagnosekoder'),
+ 'loading' => __('Søger...', 'ansico-diagnosekoder'),
+ 'error' => __('Der opstod en fejl under søgningen.', 'ansico-diagnosekoder'),
+ 'goToResults' => __('Gå til søgeresultater', 'ansico-diagnosekoder'),
+ 'missingResultsUrl' => __('Resultatsidens URL er ikke angivet i pluginets indstillinger.', 'ansico-diagnosekoder'),
+ 'copyCode' => __('Kopiér kode og beskrivelse', 'ansico-diagnosekoder'),
+ 'copied' => __('Kopieret', 'ansico-diagnosekoder'),
+ 'copyError' => __('Kunne ikke kopiere', 'ansico-diagnosekoder'),
+ 'copyCodeAria' => __('Kopiér %s', 'ansico-diagnosekoder'),
+ 'addFavorite' => __('Tilføj til favoritter', 'ansico-diagnosekoder'),
+ 'removeFavorite' => __('Fjern fra favoritter', 'ansico-diagnosekoder'),
+ 'favoriteAdded' => __('Favorit gemt', 'ansico-diagnosekoder'),
+ 'favoriteRemoved' => __('Favorit fjernet', 'ansico-diagnosekoder'),
+ 'favoriteError' => __('Kunne ikke gemme favorit', 'ansico-diagnosekoder'),
+ 'copyIcon' => __('Kopiér', 'ansico-diagnosekoder'),
+ 'favoriteIcon' => __('Favorit', 'ansico-diagnosekoder'),
+ 'addLink' => __('Tilknyt URL', 'ansico-diagnosekoder'),
+ 'removeLink' => __('Fjern URL-tilknytning', 'ansico-diagnosekoder'),
+ 'linkSaved' => __('URL gemt', 'ansico-diagnosekoder'),
+ 'linkRemoved' => __('URL fjernet', 'ansico-diagnosekoder'),
+ 'linkError' => __('Kunne ikke gemme URL', 'ansico-diagnosekoder'),
+ 'linkPrompt' => __('Indtast URL for %s', 'ansico-diagnosekoder'),
+ 'linkPromptDefault' => __('https://', 'ansico-diagnosekoder'),
+ 'openLink' => __('Åbn link', 'ansico-diagnosekoder'),
+ 'linkIcon' => __('Link', 'ansico-diagnosekoder'),
+ 'addLinkIcon' => __('Tilknyt URL', 'ansico-diagnosekoder'),
+ 'removeLinkIcon' => __('Fjern URL', 'ansico-diagnosekoder'),
+ 'linkModalTitleAdd' => __('Tilknyt link til kode', 'ansico-diagnosekoder'),
+ 'linkModalTitleEdit' => __('Rediger link til kode', 'ansico-diagnosekoder'),
+ 'linkModalCodeLabel' => __('Diagnosekode', 'ansico-diagnosekoder'),
+ 'linkModalUrlLabel' => __('URL-adresse', 'ansico-diagnosekoder'),
+ 'linkModalSearchLabel' => __('Søg efter interne sider', 'ansico-diagnosekoder'),
+ 'linkModalSearchPlaceholder' => __('Søg efter side eller indlæg', 'ansico-diagnosekoder'),
+ 'linkModalSearchEmpty' => __('Skriv for at søge efter interne sider.', 'ansico-diagnosekoder'),
+ 'linkModalSearchNone' => __('Ingen interne sider fundet.', 'ansico-diagnosekoder'),
+ 'linkModalChoose' => __('Vælg', 'ansico-diagnosekoder'),
+ 'linkModalCancel' => __('Annuller', 'ansico-diagnosekoder'),
+ 'linkModalSave' => __('Gem link', 'ansico-diagnosekoder'),
+ 'linkModalOpen' => __('Åbn link-dialog', 'ansico-diagnosekoder'),
+ 'linkModalHint' => __('Du kan enten indsætte en URL eller vælge en intern side.', 'ansico-diagnosekoder'),
+ 'linkSearchError' => __('Kunne ikke søge efter interne sider.', 'ansico-diagnosekoder'),
+ ],
+ ]);
+
+ wp_register_style(
+ 'ansico-diagnosekoder-style',
+ ANSICO_DIAGNOSEKODER_PLUGIN_URL . 'assets/css/style.css',
+ [],
+ ANSICO_DIAGNOSEKODER_VERSION
+ );
+ }
+
+ public static function register_block() {
+ register_block_type('ansico/diagnosekoder', [
+ 'api_version' => 2,
+ 'editor_script' => 'ansico-diagnosekoder-block-editor',
+ 'style' => 'ansico-diagnosekoder-style',
+ 'script' => 'ansico-diagnosekoder-frontend',
+ 'render_callback' => [__CLASS__, 'render_block'],
+ ]);
+
+ register_block_type('ansico/diagnosekoder-soegefelt', [
+ 'api_version' => 2,
+ 'editor_script' => 'ansico-diagnosekoder-block-editor',
+ 'style' => 'ansico-diagnosekoder-style',
+ 'script' => 'ansico-diagnosekoder-frontend',
+ 'render_callback' => [__CLASS__, 'render_search_form_block'],
+ ]);
+
+ register_block_type('ansico/diagnosekoder-kodekort', [
+ 'api_version' => 2,
+ 'editor_script' => 'ansico-diagnosekoder-block-editor',
+ 'style' => 'ansico-diagnosekoder-style',
+ 'render_callback' => [__CLASS__, 'render_linked_code_block'],
+ ]);
+ }
+
+ public static function render_block($attributes = [], $content = '', $block = null) {
+ $meta = get_option(ANSICO_DIAGNOSEKODER_OPTION_META, []);
+ $input_id = wp_unique_id('ansico-diagnosekoder-input-');
+ $page_id = self::get_current_page_id();
+ if (!$page_id) {
+ $page_id = self::get_results_page_id();
+ }
+ $favorites = Ansico_Diagnosekoder_REST::get_page_favorites($page_id);
+ $links = Ansico_Diagnosekoder_REST::get_page_links($page_id);
+ $page_state = wp_json_encode([
+ 'pageId' => absint($page_id),
+ 'favorites' => array_values($favorites),
+ 'links' => $links,
+ ]);
+ $preset_query = isset($_GET['q']) ? sanitize_text_field(wp_unslash($_GET['q'])) : '';
+ $initial_results = $preset_query !== '' ? Ansico_Diagnosekoder_Parser::search($preset_query, 50, $favorites, $links) : [];
+ ob_start();
+ ?>
+
+
+
+ $url) {
+ if (self::normalize_url($url) === $current_url) {
+ $matched_codes[] = $code;
+ }
+ }
+
+ if (empty($matched_codes)) {
+ return '';
+ }
+
+ $rows = Ansico_Diagnosekoder_Parser::get_rows_by_codes($matched_codes);
+ if (empty($rows)) {
+ return '';
+ }
+
+ ob_start();
+ ?>
+
+ ID)) {
+ $page_id = absint($post->ID);
+ }
+
+ return $page_id;
+ }
+
+}
diff --git a/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-parser.php b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-parser.php
new file mode 100644
index 0000000..e7535e6
--- /dev/null
+++ b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-parser.php
@@ -0,0 +1,198 @@
+ [],
+ 'meta' => [
+ 'error' => __('Filen kunne ikke læses.', 'ansico-diagnosekoder'),
+ ],
+ ];
+ }
+
+ $handle = fopen($file_path, 'r');
+ if (!$handle) {
+ return [
+ 'rows' => [],
+ 'meta' => [
+ 'error' => __('Filen kunne ikke åbnes.', 'ansico-diagnosekoder'),
+ ],
+ ];
+ }
+
+ $rows = [];
+ $header_found = false;
+ $line_number = 0;
+
+ while (($line = fgets($handle)) !== false) {
+ $line_number++;
+ $line = trim($line);
+
+ if ($line === '') {
+ continue;
+ }
+
+ $columns = str_getcsv($line, ';', '"', '\\');
+ if (!is_array($columns) || count($columns) < 2) {
+ continue;
+ }
+
+ $col1 = isset($columns[0]) ? trim((string) $columns[0]) : '';
+ $col2 = isset($columns[1]) ? trim((string) $columns[1]) : '';
+
+ if (!$header_found) {
+ if (strcasecmp($col1, 'Kode') === 0 && strcasecmp($col2, 'Tekst') === 0) {
+ $header_found = true;
+ }
+ continue;
+ }
+
+ if ($col1 === '' || $col2 === '') {
+ continue;
+ }
+
+ $rows[] = [
+ 'code' => sanitize_text_field($col1),
+ 'text' => sanitize_text_field($col2),
+ ];
+ }
+
+ fclose($handle);
+
+ return [
+ 'rows' => $rows,
+ 'meta' => [
+ 'imported_at' => current_time('mysql'),
+ 'row_count' => count($rows),
+ 'source_name' => basename($file_path),
+ 'header_found' => $header_found,
+ 'line_count' => $line_number,
+ ],
+ ];
+ }
+
+ public static function search($query, $limit = 50, $favorite_codes = [], $links = []) {
+ $query = trim((string) $query);
+ if ($query === '') {
+ return [];
+ }
+
+ $data = get_option(ANSICO_DIAGNOSEKODER_OPTION_DATA, []);
+ if (!is_array($data) || empty($data)) {
+ return [];
+ }
+
+ $limit = $limit > 0 ? min($limit, 100) : 50;
+ $query_lower = function_exists('mb_strtolower') ? mb_strtolower($query, 'UTF-8') : strtolower($query);
+ $favorite_lookup = [];
+ foreach ((array) $favorite_codes as $favorite_code) {
+ $favorite_lookup[self::normalize_code($favorite_code)] = true;
+ }
+ $link_lookup = is_array($links) ? $links : [];
+
+ $results = [];
+ foreach ($data as $index => $row) {
+ if (!is_array($row) || empty($row['code']) || empty($row['text'])) {
+ continue;
+ }
+
+ $code = (string) $row['code'];
+ $text = (string) $row['text'];
+ $normalized_code = self::normalize_code($code);
+ $haystack_code = function_exists('mb_strtolower') ? mb_strtolower($code, 'UTF-8') : strtolower($code);
+ $haystack_text = function_exists('mb_strtolower') ? mb_strtolower($text, 'UTF-8') : strtolower($text);
+
+ if (strpos($haystack_code, $query_lower) === false && strpos($haystack_text, $query_lower) === false) {
+ continue;
+ }
+
+ $results[] = [
+ 'code' => $code,
+ 'text' => $text,
+ 'is_favorite' => isset($favorite_lookup[$normalized_code]),
+ 'linked_url' => isset($link_lookup[$normalized_code]) ? $link_lookup[$normalized_code] : '',
+ '_original_index' => $index,
+ ];
+ }
+
+ if (empty($results)) {
+ return [];
+ }
+
+ usort($results, static function ($a, $b) {
+ $a_favorite = !empty($a['is_favorite']);
+ $b_favorite = !empty($b['is_favorite']);
+
+ if ($a_favorite !== $b_favorite) {
+ return $a_favorite ? -1 : 1;
+ }
+
+ return ((int) ($a['_original_index'] ?? 0)) <=> ((int) ($b['_original_index'] ?? 0));
+ });
+
+ foreach ($results as &$result) {
+ unset($result['_original_index']);
+ }
+ unset($result);
+
+ return array_slice($results, 0, $limit);
+ }
+
+ public static function get_rows_by_codes($codes = []) {
+ $normalized_codes = [];
+ foreach ((array) $codes as $code) {
+ $normalized = self::normalize_code($code);
+ if ($normalized !== '') {
+ $normalized_codes[] = $normalized;
+ }
+ }
+ $normalized_codes = array_values(array_unique($normalized_codes));
+ if (empty($normalized_codes)) {
+ return [];
+ }
+
+ $lookup = array_fill_keys($normalized_codes, true);
+ $data = get_option(ANSICO_DIAGNOSEKODER_OPTION_DATA, []);
+ if (!is_array($data) || empty($data)) {
+ return [];
+ }
+
+ $rows = [];
+ foreach ($data as $row) {
+ if (!is_array($row) || empty($row['code']) || empty($row['text'])) {
+ continue;
+ }
+
+ $code = (string) $row['code'];
+ $normalized_code = self::normalize_code($code);
+ if (!isset($lookup[$normalized_code])) {
+ continue;
+ }
+
+ $rows[$normalized_code] = [
+ 'code' => $code,
+ 'text' => (string) $row['text'],
+ ];
+ }
+
+ $ordered = [];
+ foreach ($normalized_codes as $normalized_code) {
+ if (isset($rows[$normalized_code])) {
+ $ordered[] = $rows[$normalized_code];
+ }
+ }
+
+ return $ordered;
+ }
+
+ protected static function normalize_code($code) {
+ $code = sanitize_text_field((string) $code);
+ $code = preg_replace('/\s+/', '', $code);
+ return strtoupper((string) $code);
+ }
+}
diff --git a/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-rest.php b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-rest.php
new file mode 100644
index 0000000..8b2675a
--- /dev/null
+++ b/ansico-diagnosekoder/includes/class-ansico-diagnosekoder-rest.php
@@ -0,0 +1,387 @@
+ WP_REST_Server::READABLE,
+ 'callback' => [__CLASS__, 'search'],
+ 'permission_callback' => '__return_true',
+ 'args' => [
+ 'q' => [
+ 'type' => 'string',
+ 'required' => false,
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'limit' => [
+ 'type' => 'integer',
+ 'required' => false,
+ 'default' => 50,
+ 'sanitize_callback' => 'absint',
+ ],
+ 'page_id' => [
+ 'type' => 'integer',
+ 'required' => false,
+ 'default' => 0,
+ 'sanitize_callback' => 'absint',
+ ],
+ ],
+ ]);
+
+ register_rest_route('ansico-diagnosekoder/v1', '/page-state', [
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => [__CLASS__, 'page_state'],
+ 'permission_callback' => '__return_true',
+ 'args' => [
+ 'page_id' => [
+ 'type' => 'integer',
+ 'required' => false,
+ 'default' => 0,
+ 'sanitize_callback' => 'absint',
+ ],
+ ],
+ ]);
+
+ register_rest_route('ansico-diagnosekoder/v1', '/content-search', [
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => [__CLASS__, 'content_search'],
+ 'permission_callback' => static function () {
+ return is_user_logged_in();
+ },
+ 'args' => [
+ 'q' => [
+ 'type' => 'string',
+ 'required' => false,
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'limit' => [
+ 'type' => 'integer',
+ 'required' => false,
+ 'default' => 8,
+ 'sanitize_callback' => 'absint',
+ ],
+ ],
+ ]);
+
+ register_rest_route('ansico-diagnosekoder/v1', '/favorites', [
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => [__CLASS__, 'toggle_favorite'],
+ 'permission_callback' => static function () {
+ return is_user_logged_in();
+ },
+ 'args' => [
+ 'code' => [
+ 'type' => 'string',
+ 'required' => true,
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'page_id' => [
+ 'type' => 'integer',
+ 'required' => true,
+ 'sanitize_callback' => 'absint',
+ ],
+ ],
+ ]);
+
+ register_rest_route('ansico-diagnosekoder/v1', '/links', [
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => [__CLASS__, 'save_link'],
+ 'permission_callback' => static function () {
+ return is_user_logged_in();
+ },
+ 'args' => [
+ 'code' => [
+ 'type' => 'string',
+ 'required' => true,
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'url' => [
+ 'type' => 'string',
+ 'required' => true,
+ ],
+ 'page_id' => [
+ 'type' => 'integer',
+ 'required' => true,
+ 'sanitize_callback' => 'absint',
+ ],
+ ],
+ ]);
+
+ register_rest_route('ansico-diagnosekoder/v1', '/links', [
+ 'methods' => WP_REST_Server::DELETABLE,
+ 'callback' => [__CLASS__, 'remove_link'],
+ 'permission_callback' => static function () {
+ return is_user_logged_in();
+ },
+ 'args' => [
+ 'code' => [
+ 'type' => 'string',
+ 'required' => true,
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'page_id' => [
+ 'type' => 'integer',
+ 'required' => true,
+ 'sanitize_callback' => 'absint',
+ ],
+ ],
+ ]);
+ }
+
+ public static function search(WP_REST_Request $request) {
+ $query = (string) $request->get_param('q');
+ $limit = (int) $request->get_param('limit');
+ $page_id = absint($request->get_param('page_id'));
+ if (!$page_id) {
+ $page_id = self::get_results_page_id();
+ }
+ $limit = $limit > 0 ? min($limit, 100) : 50;
+
+ $favorite_codes = self::get_page_favorites($page_id);
+ $links = self::get_page_links($page_id);
+
+ $response = rest_ensure_response([
+ 'query' => $query,
+ 'results' => Ansico_Diagnosekoder_Parser::search($query, $limit, $favorite_codes, $links),
+ 'meta' => get_option(ANSICO_DIAGNOSEKODER_OPTION_META, []),
+ 'favorites' => $favorite_codes,
+ 'links' => $links,
+ 'is_logged_in' => is_user_logged_in(),
+ 'page_id' => $page_id,
+ ]);
+
+ $response->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
+ $response->header('Pragma', 'no-cache');
+ return $response;
+ }
+
+ public static function page_state(WP_REST_Request $request) {
+ $page_id = absint($request->get_param('page_id'));
+ if (!$page_id) {
+ $page_id = self::get_results_page_id();
+ }
+
+ $response = rest_ensure_response([
+ 'page_id' => $page_id,
+ 'favorites' => self::get_page_favorites($page_id),
+ 'links' => self::get_page_links($page_id),
+ ]);
+ $response->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
+ $response->header('Pragma', 'no-cache');
+ return $response;
+ }
+
+ public static function content_search(WP_REST_Request $request) {
+ $query = trim((string) $request->get_param('q'));
+ $limit = (int) $request->get_param('limit');
+ $limit = $limit > 0 ? min($limit, 20) : 8;
+
+ if ($query === '') {
+ return rest_ensure_response(['results' => []]);
+ }
+
+ $all_post_types = get_post_types([], 'objects');
+ $excluded_types = ['attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'user_request', 'wp_block', 'wp_navigation', 'wp_template', 'wp_template_part', 'wp_global_styles', 'wp_font_family', 'wp_font_face'];
+
+ $post_types = [];
+ foreach ($all_post_types as $post_type => $object) {
+ if (in_array($post_type, $excluded_types, true)) {
+ continue;
+ }
+
+ if (strpos($post_type, 'wp_') === 0) {
+ continue;
+ }
+
+ // Include posts, pages, and all custom post types that can reasonably have frontend URLs,
+ // even when the CPT is excluded from normal WordPress search.
+ if (!empty($object->public) || !empty($object->publicly_queryable) || !empty($object->show_ui)) {
+ $post_types[] = $post_type;
+ }
+ }
+
+ $post_types = array_values(array_unique($post_types));
+
+ if (empty($post_types)) {
+ $post_types = ['page', 'post'];
+ }
+
+ $posts = get_posts([
+ 'post_type' => $post_types,
+ 'post_status' => 'publish',
+ 's' => $query,
+ 'posts_per_page' => $limit,
+ 'orderby' => 'relevance',
+ 'order' => 'DESC',
+ 'suppress_filters' => false,
+ ]);
+
+ $results = [];
+ foreach ($posts as $post) {
+ $results[] = [
+ 'id' => (int) $post->ID,
+ 'title' => get_the_title($post),
+ 'url' => get_permalink($post),
+ 'type' => get_post_type($post),
+ ];
+ }
+
+ return rest_ensure_response(['results' => $results]);
+ }
+
+ public static function toggle_favorite(WP_REST_Request $request) {
+ $code = self::normalize_code((string) $request->get_param('code'));
+ $page_id = absint($request->get_param('page_id'));
+ if (!$page_id) {
+ $page_id = self::get_results_page_id();
+ }
+
+ if ($code === '') {
+ return new WP_Error('ansico_empty_code', __('Der mangler en kode.', 'ansico-diagnosekoder'), ['status' => 400]);
+ }
+
+ if (!$page_id || get_post_status($page_id) === false) {
+ return new WP_Error('ansico_invalid_page', __('Der mangler en gyldig side.', 'ansico-diagnosekoder'), ['status' => 400]);
+ }
+
+ $favorites = self::get_page_favorites($page_id);
+ $index = array_search($code, $favorites, true);
+ $is_favorite = false;
+
+ if ($index === false) {
+ $favorites[] = $code;
+ $is_favorite = true;
+ } else {
+ unset($favorites[$index]);
+ $favorites = array_values($favorites);
+ }
+
+ update_post_meta($page_id, ANSICO_DIAGNOSEKODER_PAGE_FAVORITES_META, $favorites);
+
+ return rest_ensure_response([
+ 'code' => $code,
+ 'is_favorite' => $is_favorite,
+ 'favorites' => $favorites,
+ 'page_id' => $page_id,
+ ]);
+ }
+
+ public static function get_page_favorites($page_id) {
+ $page_id = absint($page_id);
+ if (!$page_id) {
+ return [];
+ }
+
+ $favorites = get_post_meta($page_id, ANSICO_DIAGNOSEKODER_PAGE_FAVORITES_META, true);
+ if (!is_array($favorites)) {
+ return [];
+ }
+
+ $favorites = array_map([__CLASS__, 'normalize_code'], $favorites);
+ return array_values(array_unique(array_filter($favorites)));
+ }
+
+ public static function save_link(WP_REST_Request $request) {
+ $code = self::normalize_code((string) $request->get_param('code'));
+ $url = esc_url_raw((string) $request->get_param('url'));
+ $page_id = absint($request->get_param('page_id'));
+ if (!$page_id) {
+ $page_id = self::get_results_page_id();
+ }
+
+ if ($code === '') {
+ return new WP_Error('ansico_empty_code', __('Der mangler en kode.', 'ansico-diagnosekoder'), ['status' => 400]);
+ }
+
+ if (!$url || !wp_http_validate_url($url)) {
+ return new WP_Error('ansico_invalid_url', __('URL-adressen er ikke gyldig.', 'ansico-diagnosekoder'), ['status' => 400]);
+ }
+
+ if (!$page_id || get_post_status($page_id) === false) {
+ return new WP_Error('ansico_invalid_page', __('Der mangler en gyldig side.', 'ansico-diagnosekoder'), ['status' => 400]);
+ }
+
+ $links = self::get_page_links($page_id);
+ $links[$code] = $url;
+ update_post_meta($page_id, ANSICO_DIAGNOSEKODER_PAGE_LINKS_META, $links);
+
+ return rest_ensure_response([
+ 'code' => $code,
+ 'url' => $url,
+ 'links' => $links,
+ 'page_id' => $page_id,
+ ]);
+ }
+
+ public static function remove_link(WP_REST_Request $request) {
+ $code = self::normalize_code((string) $request->get_param('code'));
+ $page_id = absint($request->get_param('page_id'));
+ if (!$page_id) {
+ $page_id = self::get_results_page_id();
+ }
+
+ if (!$page_id || get_post_status($page_id) === false) {
+ return new WP_Error('ansico_invalid_page', __('Der mangler en gyldig side.', 'ansico-diagnosekoder'), ['status' => 400]);
+ }
+
+ $links = self::get_page_links($page_id);
+ if ($code === '') {
+ return new WP_Error('ansico_empty_code', __('Der mangler en kode.', 'ansico-diagnosekoder'), ['status' => 400]);
+ }
+
+ unset($links[$code]);
+ update_post_meta($page_id, ANSICO_DIAGNOSEKODER_PAGE_LINKS_META, $links);
+
+ return rest_ensure_response([
+ 'code' => $code,
+ 'links' => $links,
+ 'page_id' => $page_id,
+ ]);
+ }
+
+ public static function get_page_links($page_id) {
+ $page_id = absint($page_id);
+ if (!$page_id) {
+ return [];
+ }
+
+ $links = get_post_meta($page_id, ANSICO_DIAGNOSEKODER_PAGE_LINKS_META, true);
+ if (!is_array($links)) {
+ return [];
+ }
+
+ $normalized = [];
+ foreach ($links as $code => $url) {
+ $normalized_code = self::normalize_code((string) $code);
+ $normalized_url = esc_url_raw((string) $url);
+ if ($normalized_code === '' || $normalized_url === '' || !wp_http_validate_url($normalized_url)) {
+ continue;
+ }
+ $normalized[$normalized_code] = $normalized_url;
+ }
+
+ return $normalized;
+ }
+
+ protected static function get_results_page_id() {
+ $results_url = (string) get_option(ANSICO_DIAGNOSEKODER_OPTION_RESULTS_URL, '');
+ if ($results_url === '') {
+ return 0;
+ }
+
+ return absint(url_to_postid($results_url));
+ }
+
+ protected static function normalize_code($code) {
+ $code = sanitize_text_field((string) $code);
+ $code = preg_replace('/\s+/', '', $code);
+ return strtoupper((string) $code);
+ }
+}