/* * 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 };