- Home
- Docs Builder
- Placeholder System
Placeholder System
The placeholder system lets readers customize IP addresses, ASNs, and other deployment-specific values throughout the documentation. Authors write tokens in their Markdown; the browser replaces them with user-supplied values at runtime.
Token Format
Section titled “Token Format”Tokens follow the regex pattern:
x([A-Z][A-Z0-9_]+)xA token starts and ends with a lowercase x and contains an uppercase identifier. For example, xCUSTOMER_ASNx references the CUSTOMER_ASN placeholder.
The regex is defined in src/scripts/placeholder-dom.ts:
const PH_REGEX = /x([A-Z][A-Z0-9_]+)x/g;Placeholder Definitions
Section titled “Placeholder Definitions”All placeholders are declared in src/data/placeholders.json. Each entry has this shape:
{ "CUSTOMER_ASN": { "type": "text", "default": "64496", "description": "Your public ASN (registered with ARIN/RIR)" }}| Field | Required | Description |
|---|---|---|
type | yes | "text" for free-form input, "dropdown" for select menus |
default | yes | Initial value shown before the reader changes anything |
description | yes | Label displayed in the form |
options | only for dropdown | Array of allowed values |
State Management
Section titled “State Management”src/lib/placeholder-store.ts handles all placeholder state.
Storage
Section titled “Storage”Values are persisted in localStorage under the key f5xc-placeholders. The store exposes four functions:
| Function | Purpose |
|---|---|
getDefaults() | Returns a map of every placeholder key to its default value from JSON |
loadValues() | Reads from localStorage, falls back to getDefaults() |
saveValues(values) | Writes the current map to localStorage |
clearValues() | Removes the localStorage entry |
Field Groups
Section titled “Field Groups”FIELD_GROUPS organizes placeholder keys into labeled sections for the form UI:
export const FIELD_GROUPS: FieldGroup[] = [ { label: 'Data Center & Scrubbing Centers', keys: ['DC_NAME', 'CENTER_1', 'CENTER_2'] }, { label: 'Protected Prefixes', keys: ['PROTECTED_CIDR_V4', 'PROTECTED_NET_V4', ...] }, { label: 'BGP', keys: ['CUSTOMER_ASN', 'F5_XC_ASN', 'BGP_PASSWORD'] }, // ... more groups];Computed Values
Section titled “Computed Values”Some values are derived from user input rather than entered directly. getComputedValues() calculates these from lookup tables:
const cidrToMask: Record<string, string> = { '/24 (256 IPs)': '255.255.255.0', '/23 (512 IPs)': '255.255.254.0', // ...};Two computed placeholders are produced:
| Computed Key | Derived From | Example |
|---|---|---|
PROTECTED_MASK_V4 | PROTECTED_CIDR_V4 via cidrToMask lookup | 255.255.255.0 |
PROTECTED_PREFIX_V4 | PROTECTED_NET_V4 + PROTECTED_CIDR_V4 via cidrToShort | 192.0.2.0/24 |
getAllValues() merges user-entered values with computed values, giving a complete map for substitution.
Event Emission
Section titled “Event Emission”emitChange() dispatches a placeholder-change CustomEvent on document with the full value map as detail:
export function emitChange(values: Record<string, string>) { document.dispatchEvent( new CustomEvent('placeholder-change', { detail: getAllValues(values) }), );}This event drives both the DOM span updates and Mermaid re-rendering.
React Form Component
Section titled “React Form Component”src/components/PlaceholderForm.tsx provides the editing UI.
- State:
useStateinitialized fromloadValues() - On mount:
useEffectcallsemitChange()to trigger the initial DOM substitution - handleChange: Updates React state, calls
saveValues()andemitChange() - handleReset: Calls
clearValues(), resets state togetDefaults(), emits change - Rendering: Iterates
FIELD_GROUPS, rendering a<fieldset>per group. Each key gets either an<input>(text type) or<select>(dropdown type) - Layout: The form is wrapped in a
<details>element, collapsed by default
Astro Wrapper
Section titled “Astro Wrapper”src/components/PlaceholderFormWrapper.astro connects the React component to the Astro page:
<PlaceholderForm client:only="react" />
<script> import '../scripts/placeholder-dom.ts';</script>client:only="react" tells Astro to hydrate the component purely on the client (no SSR). The <script> tag imports the DOM walker so it runs on every page that includes this wrapper.
The wrapper also injects global CSS for form styling (.ph-form-wrapper, .ph-grid, .ph-value, etc.).
DOM Walker
Section titled “DOM Walker”src/scripts/placeholder-dom.ts handles the client-side token replacement.
Initial Walk
Section titled “Initial Walk”On page load, init() runs:
- Selects
.sl-markdown-contentas the root (falls back todocument.body) - Calls
walkTextNodes(root, values)which usesdocument.createTreeWalkerwithNodeFilter.SHOW_TEXT - For each text node matching the token regex, splits it into a document fragment of plain text nodes and
<span data-ph="KEY" class="ph-value">elements - Replaces the original text node with the fragment
After the walk, the DOM contains spans with data-ph attributes instead of raw tokens.
Subsequent Updates
Section titled “Subsequent Updates”When the form emits a placeholder-change event, updateSpans() runs:
document.querySelectorAll<HTMLSpanElement>('span[data-ph]').forEach((span) => { const name = span.getAttribute('data-ph')!; if (values[name] !== undefined) { span.textContent = values[name]; }});This avoids re-walking the tree — it directly updates the span text content.
Event Listeners
Section titled “Event Listeners”The script registers two listeners:
| Event | Handler | Purpose |
|---|---|---|
placeholder-change | handleChange | Updates spans and re-renders Mermaid diagrams |
astro:page-load | init | Re-walks the DOM after Astro client-side navigation |
How To: Add a New Placeholder
Section titled “How To: Add a New Placeholder”-
Add the JSON entry in
src/data/placeholders.json:"MY_NEW_VALUE": {"type": "text","default": "example","description": "Description shown in the form"} -
Add the key to a field group in
src/lib/placeholder-store.ts. Either add it to an existing group’skeysarray or create a new group inFIELD_GROUPS. -
Use the token in content: Write
xMY_NEW_VALUExin any.mdxfile. The DOM walker will replace it at runtime.
How To: Add a Computed Value
Section titled “How To: Add a Computed Value”Computed values are derived from other placeholders. To add one:
-
Add a lookup table (if needed) in
src/lib/placeholder-store.ts, following the pattern ofcidrToMask. -
Extend
getComputedValues()to include the new derived key:export function getComputedValues(values: Record<string, string>): Record<string, string> {// ... existing logicreturn {PROTECTED_MASK_V4: mask,PROTECTED_PREFIX_V4: `${net}${short}`,MY_COMPUTED: derivedValue, // add here};} -
Use
xMY_COMPUTEDxin content like any other token. Computed values do not need aplaceholders.jsonentry or a field group — they are invisible to the form.