跳到內容

佔位符系統

佔位符系統讓讀者可以在整份文件中自訂 IP 位址、ASN 及其他部署特定的值。作者在 Markdown 中撰寫 token;瀏覽器會在執行時將其替換為使用者提供的值。

Token 遵循以下正規表達式模式:

x([A-Z][A-Z0-9_]+)x

Token 以小寫的 x 開頭和結尾,中間包含大寫識別符。例如,xCUSTOMER_ASNx 參照的是 CUSTOMER_ASN 佔位符。

此正規表達式定義在 src/scripts/placeholder-dom.ts 中:

const PH_REGEX = /x([A-Z][A-Z0-9_]+)x/g;

所有佔位符都宣告在 src/data/placeholders.json 中。每個項目具有以下結構:

{
"CUSTOMER_ASN": {
"type": "text",
"default": "64496",
"description": "Your public ASN (registered with ARIN/RIR)"
}
}
欄位必填說明
type"text" 表示自由輸入,"dropdown" 表示下拉選單
default讀者變更前顯示的初始值
description表單中顯示的標籤
options僅下拉選單需要允許值的陣列

src/lib/placeholder-store.ts 處理所有佔位符狀態。

值透過 localStorage 持久化,使用的鍵為 f5xc-placeholders。儲存區公開四個函式:

函式用途
getDefaults()從 JSON 回傳每個佔位符鍵對應其 default 值的映射
loadValues()從 localStorage 讀取,失敗時退回到 getDefaults()
saveValues(values)將目前的映射寫入 localStorage
clearValues()移除 localStorage 項目

FIELD_GROUPS 將佔位符鍵組織為帶有標籤的區段,用於表單 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'] },
// ... 更多群組
];

某些值是由使用者輸入推導而來,而非直接輸入。getComputedValues() 從查詢表計算這些值:

const cidrToMask: Record<string, string> = {
'/24 (256 IPs)': '255.255.255.0',
'/23 (512 IPs)': '255.255.254.0',
// ...
};

產生兩個計算佔位符:

計算鍵來源範例
PROTECTED_MASK_V4透過 cidrToMask 查詢從 PROTECTED_CIDR_V4 推導255.255.255.0
PROTECTED_PREFIX_V4透過 cidrToShortPROTECTED_NET_V4 + PROTECTED_CIDR_V4 推導192.0.2.0/24

getAllValues() 將使用者輸入的值與計算值合併,提供完整的替換映射。

emitChange()document 上發送一個 placeholder-change CustomEvent,以完整的值映射作為 detail

export function emitChange(values: Record<string, string>) {
document.dispatchEvent(
new CustomEvent('placeholder-change', { detail: getAllValues(values) }),
);
}

此事件驅動 DOM span 更新和 Mermaid 重新繪製。

src/components/PlaceholderForm.tsx 提供編輯 UI。

  • 狀態useStateloadValues() 初始化
  • 掛載時useEffect 呼叫 emitChange() 觸發初始 DOM 替換
  • handleChange:更新 React 狀態,呼叫 saveValues()emitChange()
  • handleReset:呼叫 clearValues(),將狀態重設為 getDefaults(),發送變更事件
  • 繪製:遍歷 FIELD_GROUPS,每個群組繪製一個 <fieldset>。每個鍵根據類型取得 <input>(text 類型)或 <select>(dropdown 類型)
  • 版面配置:表單包裹在 <details> 元素中,預設為摺疊狀態

src/components/PlaceholderFormWrapper.astro 將 React 元件連接到 Astro 頁面:

<PlaceholderForm client:only="react" />
<script>
import '../scripts/placeholder-dom.ts';
</script>

client:only="react" 告知 Astro 僅在客戶端進行元件水合(不進行 SSR)。<script> 標籤匯入 DOM 遍歷器,使其在每個包含此包裝器的頁面上執行。

此包裝器也注入表單樣式的全域 CSS(.ph-form-wrapper.ph-grid.ph-value 等)。

src/scripts/placeholder-dom.ts 處理客戶端的 token 替換。

頁面載入時,init() 執行:

  1. 選取 .sl-markdown-content 作為根節點(退回到 document.body
  2. 呼叫 walkTextNodes(root, values),使用 document.createTreeWalker 搭配 NodeFilter.SHOW_TEXT
  3. 對於每個匹配 token 正規表達式的文字節點,將其分割為由純文字節點和 <span data-ph="KEY" class="ph-value"> 元素組成的文件片段
  4. 以此片段替換原始文字節點

遍歷完成後,DOM 中包含帶有 data-ph 屬性的 span,取代了原始 token。

當表單發送 placeholder-change 事件時,updateSpans() 執行:

document.querySelectorAll<HTMLSpanElement>('span[data-ph]').forEach((span) => {
const name = span.getAttribute('data-ph')!;
if (values[name] !== undefined) {
span.textContent = values[name];
}
});

這避免了重新遍歷整棵樹——直接更新 span 的文字內容。

此腳本註冊兩個監聽器:

事件處理函式用途
placeholder-changehandleChange更新 span 並重新繪製 Mermaid 圖表
astro:page-loadinit在 Astro 客戶端導航後重新遍歷 DOM
  1. src/data/placeholders.json 中新增 JSON 項目

    "MY_NEW_VALUE": {
    "type": "text",
    "default": "example",
    "description": "Description shown in the form"
    }
  2. src/lib/placeholder-store.ts 中將鍵加入欄位群組。將其加入現有群組的 keys 陣列中,或在 FIELD_GROUPS 中建立新群組。

  3. 在內容中使用 token:在任何 .mdx 檔案中撰寫 xMY_NEW_VALUEx。DOM 遍歷器會在執行時替換它。

計算值是從其他佔位符推導而來的。新增步驟如下:

  1. src/lib/placeholder-store.ts 中新增查詢表(如需要),遵循 cidrToMask 的模式。

  2. 擴展 getComputedValues() 以包含新的推導鍵:

    export function getComputedValues(values: Record<string, string>): Record<string, string> {
    // ... 現有邏輯
    return {
    PROTECTED_MASK_V4: mask,
    PROTECTED_PREFIX_V4: `${net}${short}`,
    MY_COMPUTED: derivedValue, // 在此新增
    };
    }
  3. 在內容中像其他 token 一樣使用 xMY_COMPUTEDx。計算值不需要 placeholders.json 項目或欄位群組——它們對表單是不可見的。