Files
racket-wiki/static/js/wiki/reference.js
T
2026-09-02 08:38:03 +02:00

103 lines
3.4 KiB
JavaScript

/*
* Wiki page references.
*
* This module owns the syntax used to identify root and namespaced pages.
* It has no application or DOM state.
*/
/** Split a wiki reference into its namespace and page slug. */
function splitPageReference(reference) {
const value = String(reference || "");
const separator = value.indexOf(":");
if (separator < 0) return { namespace: "", slug: value };
return { namespace: value.slice(0, separator), slug: value.slice(separator + 1) };
}
/** Build the compact external reference for a root or namespaced page. */
function pageReference(namespace, slug) {
const cleanNamespace = String(namespace || "").trim();
return cleanNamespace ? `${cleanNamespace}:${slug}` : slug;
}
/** Convert one freely entered reference part to a bounded wiki identifier. */
function slugifyReferencePart(value, maximum) {
return String(value || "")
.normalize("NFKD")
.toLocaleLowerCase()
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^\p{L}\p{N}]+/gu, "-")
.replace(/^-+|-+$/g, "")
.slice(0, maximum)
.replace(/-+$/g, "");
}
/**
* goal : Convert a freely entered page title/address to a wiki reference.
* pre : input may contain a namespace separated by a colon.
* post : No page is created and no application state is changed.
* result : A normalized reference, or null when the input is empty or invalid.
*/
function newPageReference(input) {
const parts = splitPageReference(String(input || "").trim());
const namespace = parts.namespace ? slugifyReferencePart(parts.namespace, 80) : "";
const slug = slugifyReferencePart(parts.slug, 120);
if (!slug || (parts.namespace && !namespace)) return null;
return pageReference(namespace, slug);
}
/** Normalize human-readable text for ambiguity-aware WikiWord matching. */
function normalizeMentionText(text) {
return String(text || "")
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.toLocaleLowerCase()
.replace(/[^\p{L}\p{N}]+/gu, "");
}
/** Return the separate capitalized words when text is a classic WikiWord. */
function wikiWordParts(text) {
const value = String(text || "");
if (!/^(?:\p{Lu}\p{Ll}+){2,}$/u.test(value)) return [];
return value.match(/\p{Lu}\p{Ll}+/gu) || [];
}
/**
* goal : Resolve a classic WikiWord to an existing page or new page target.
* pre : aliases is an ambiguity-aware map built by wiki-markdown.js.
* post : No page is created and aliases is not changed.
* result : A slug/title pair, or null when text is not a WikiWord.
*/
function wikiWordTarget(text, aliases, namespace = "") {
const parts = wikiWordParts(text);
if (parts.length === 0) return null;
const aliasKey = `${String(namespace || "").toLocaleLowerCase()}:${normalizeMentionText(text)}`;
const existing = aliases.get(aliasKey);
if (existing) return { slug: existing.slug, title: existing.title };
const slug = parts.map((part) => part.toLocaleLowerCase()).join("-");
return {
slug: pageReference(namespace, slug),
title: parts.join(" ")
};
}
/** Derive a readable title from the page part of a wiki reference. */
function slugTitle(reference) {
const slug = splitPageReference(reference || "").slug;
return slug
.split("-")
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(" ");
}
export {
newPageReference,
normalizeMentionText,
pageReference,
slugTitle,
splitPageReference,
wikiWordTarget
};