diff --git a/frontend/public/icons/close-soon.png b/frontend/public/icons/close-soon.png new file mode 100755 index 0000000..e8f32a7 Binary files /dev/null and b/frontend/public/icons/close-soon.png differ diff --git a/frontend/public/icons/close-soon24.png b/frontend/public/icons/close-soon24.png new file mode 100755 index 0000000..0dbea00 Binary files /dev/null and b/frontend/public/icons/close-soon24.png differ diff --git a/frontend/public/icons/closed-open.png b/frontend/public/icons/closed-open.png new file mode 100755 index 0000000..d0fce16 Binary files /dev/null and b/frontend/public/icons/closed-open.png differ diff --git a/frontend/public/icons/closed-open24.png b/frontend/public/icons/closed-open24.png new file mode 100755 index 0000000..1847505 Binary files /dev/null and b/frontend/public/icons/closed-open24.png differ diff --git a/frontend/public/icons/closed.png b/frontend/public/icons/closed.png new file mode 100755 index 0000000..9692f66 Binary files /dev/null and b/frontend/public/icons/closed.png differ diff --git a/frontend/public/icons/closed24.png b/frontend/public/icons/closed24.png new file mode 100755 index 0000000..11f0dbe Binary files /dev/null and b/frontend/public/icons/closed24.png differ diff --git a/frontend/public/icons/discontinued.png b/frontend/public/icons/discontinued.png new file mode 100755 index 0000000..06ceba5 Binary files /dev/null and b/frontend/public/icons/discontinued.png differ diff --git a/frontend/public/icons/discontinued24.png b/frontend/public/icons/discontinued24.png new file mode 100755 index 0000000..1bce198 Binary files /dev/null and b/frontend/public/icons/discontinued24.png differ diff --git a/frontend/public/icons/open-soon.png b/frontend/public/icons/open-soon.png new file mode 100755 index 0000000..d26a81d Binary files /dev/null and b/frontend/public/icons/open-soon.png differ diff --git a/frontend/public/icons/open-soon24.png b/frontend/public/icons/open-soon24.png new file mode 100755 index 0000000..7bb0c4b Binary files /dev/null and b/frontend/public/icons/open-soon24.png differ diff --git a/frontend/public/icons/open-winter.png b/frontend/public/icons/open-winter.png new file mode 100755 index 0000000..93151ae Binary files /dev/null and b/frontend/public/icons/open-winter.png differ diff --git a/frontend/public/icons/open-winter24.png b/frontend/public/icons/open-winter24.png new file mode 100755 index 0000000..9667ff0 Binary files /dev/null and b/frontend/public/icons/open-winter24.png differ diff --git a/frontend/public/icons/open.png b/frontend/public/icons/open.png new file mode 100755 index 0000000..6634316 Binary files /dev/null and b/frontend/public/icons/open.png differ diff --git a/frontend/public/icons/open24.png b/frontend/public/icons/open24.png new file mode 100755 index 0000000..827bb70 Binary files /dev/null and b/frontend/public/icons/open24.png differ diff --git a/frontend/public/icons/under-development.png b/frontend/public/icons/under-development.png new file mode 100755 index 0000000..f483edc Binary files /dev/null and b/frontend/public/icons/under-development.png differ diff --git a/frontend/public/icons/under-development24.png b/frontend/public/icons/under-development24.png new file mode 100755 index 0000000..b360ac8 Binary files /dev/null and b/frontend/public/icons/under-development24.png differ diff --git a/frontend/public/icons/unknown.png b/frontend/public/icons/unknown.png new file mode 100755 index 0000000..18297f5 Binary files /dev/null and b/frontend/public/icons/unknown.png differ diff --git a/frontend/public/icons/unknown24.png b/frontend/public/icons/unknown24.png new file mode 100755 index 0000000..edfcf33 Binary files /dev/null and b/frontend/public/icons/unknown24.png differ diff --git a/frontend/src/app/FacilitySearch.tsx b/frontend/src/app/FacilitySearch.tsx index 26e4401..de64ba8 100755 --- a/frontend/src/app/FacilitySearch.tsx +++ b/frontend/src/app/FacilitySearch.tsx @@ -220,6 +220,59 @@ const toPlainText = (value: string | null | undefined) => .replace(/\s+/g, " ") .trim(); +const escapeHtml = (value: string) => + value + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + +const sanitizeHref = (value: string) => { + const href = value.trim(); + return /^(https?:|mailto:|tel:|\/|#)/i.test(href) ? href : "#"; +}; + +const sanitizeRichText = (value: string | null | undefined) => { + const source = String(value || "").replace(/\r\n?/g, "\n"); + if (!source.trim()) return ""; + + const placeholders = new Map(); + let index = 0; + const keep = (html: string) => { + const key = `__HTML_TOKEN_${index++}__`; + placeholders.set(key, html); + return key; + }; + + let safe = source + .replace(/<\s*br\s*\/?\s*>/gi, () => keep("
")) + .replace(/<\s*(strong|b)\s*>/gi, () => keep("")) + .replace(/<\s*\/\s*(strong|b)\s*>/gi, () => keep("")) + .replace(/<\s*(em|i)\s*>/gi, () => keep("")) + .replace(/<\s*\/\s*(em|i)\s*>/gi, () => keep("")) + .replace(/<\s*p\s*>/gi, () => keep("

")) + .replace(/<\s*\/\s*p\s*>/gi, () => keep("

")) + .replace(/<\s*(ul|ol)\s*>/gi, (_, tag: string) => keep(`<${tag.toLowerCase()}>`)) + .replace(/<\s*\/\s*(ul|ol)\s*>/gi, (_, tag: string) => keep(``)) + .replace(/<\s*li\s*>/gi, () => keep("
  • ")) + .replace(/<\s*\/\s*li\s*>/gi, () => keep("
  • ")) + .replace(/<\s*a\b([^>]*)>/gi, (_, attrs: string) => { + const hrefMatch = attrs.match(/href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i); + const href = sanitizeHref(hrefMatch?.[1] || hrefMatch?.[2] || hrefMatch?.[3] || "#"); + return keep(``); + }) + .replace(/<\s*\/\s*a\s*>/gi, () => keep("")); + + safe = escapeHtml(safe).replace(/\n/g, "
    "); + + for (const [token, html] of placeholders) { + safe = safe.replaceAll(token, html); + } + + return safe; +}; + const getAreaLabel = (value: string, countyOptions: Array<{ slug: string; label: string }>) => { if (!value) return "Hele Norge"; const builtIn = HIERARCHICAL_AREA_OPTIONS.find((option) => option.value === value); @@ -575,9 +628,9 @@ export default function FacilitySearch({ {processedFacilities.map((facility) => (
    - +
    -
    -
    -
    - - {facility.holeValue || "--"} hull - - - {facility.banetype || "Banetype ukjent"} - +
    +
    +
    +
    + + {facility.holeValue || "--"} hull + + + {facility.banetype || "Banetype ukjent"} + +
    + {Number.isFinite(facility.distance) && ( + + {Math.round(facility.distance)} km unna + + )}
    - {Number.isFinite(facility.distance) && ( - - {Math.round(facility.distance)} km unna - + + {facility.footnote && ( +
    +

    + {formatUpdatedDate(facility.footnote_updated_at || facility.status_updated_at)} +

    +

    + {facility.footnote} +

    +
    + )} + + {String(facility.description || "").trim() && ( +
    )}
    - {facility.footnote && ( -
    -

    - {formatUpdatedDate(facility.footnote_updated_at || facility.status_updated_at)} -

    -

    - {facility.footnote} -

    -
    - )} - - {toPlainText(facility.description) && ( -

    - {toPlainText(facility.description)} -

    - )} - -
    +
    {facility.phone ? facility.phone : facility.city || "Se detaljer"}
    {facility.website_url && ( diff --git a/frontend/src/app/admin/rediger/[slug]/EditFacilityClient.tsx b/frontend/src/app/admin/rediger/[slug]/EditFacilityClient.tsx index 59f7d31..1850be2 100644 --- a/frontend/src/app/admin/rediger/[slug]/EditFacilityClient.tsx +++ b/frontend/src/app/admin/rediger/[slug]/EditFacilityClient.tsx @@ -482,7 +482,11 @@ export default function EditFacilityClient({ initialData, allFacilities }: { ini
    -