콘텐츠로 이동

플레이스홀더 시스템

플레이스홀더 시스템은 독자가 문서 전체에서 IP 주소, ASN 및 기타 배포별 값을 사용자 정의할 수 있도록 합니다. 작성자는 Markdown에 토큰을 작성하고, 브라우저가 런타임에 이를 사용자가 제공한 값으로 대체합니다.

토큰은 다음 정규식 패턴을 따릅니다:

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

토큰은 소문자 x로 시작하고 끝나며 대문자 식별자를 포함합니다. 예를 들어, xCUSTOMER_ASNxCUSTOMER_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폼에 표시되는 레이블
optionsdropdown인 경우만허용되는 값의 배열

src/lib/placeholder-store.ts가 모든 플레이스홀더 상태를 처리합니다.

값은 f5xc-placeholders 키 아래 localStorage에 영속화됩니다. 스토어는 네 가지 함수를 노출합니다:

함수용도
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_V4cidrToMask 조회를 통한 PROTECTED_CIDR_V4255.255.255.0
PROTECTED_PREFIX_V4cidrToShort를 통한 PROTECTED_NET_V4 + PROTECTED_CIDR_V4192.0.2.0/24

getAllValues()는 사용자 입력 값과 계산된 값을 병합하여 대체를 위한 완전한 맵을 제공합니다.

emitChange()는 전체 값 맵을 detail로 포함하는 placeholder-change CustomEvent를 document에 디스패치합니다:

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

이 이벤트는 DOM span 업데이트와 Mermaid 재렌더링 모두를 구동합니다.

src/components/PlaceholderForm.tsx가 편집 UI를 제공합니다.

  • 상태: loadValues()로 초기화된 useState
  • 마운트 시: useEffectemitChange()를 호출하여 초기 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가 클라이언트 측 토큰 대체를 처리합니다.

페이지 로드 시 init()이 실행됩니다:

  1. .sl-markdown-content를 루트로 선택합니다 (document.body로 폴백)
  2. document.createTreeWalkerNodeFilter.SHOW_TEXT를 사용하는 walkTextNodes(root, values)를 호출합니다
  3. 토큰 정규식과 일치하는 각 텍스트 노드에 대해 일반 텍스트 노드와 <span data-ph="KEY" class="ph-value"> 요소의 문서 프래그먼트로 분할합니다
  4. 원래 텍스트 노드를 프래그먼트로 대체합니다

워크 후, DOM에는 원시 토큰 대신 data-ph 속성을 가진 span이 포함됩니다.

폼이 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-changehandleChangespan을 업데이트하고 Mermaid 다이어그램을 재렌더링합니다
astro:page-loadinitAstro 클라이언트 측 네비게이션 후 DOM을 다시 워크합니다
  1. JSON 항목 추가 src/data/placeholders.json에:

    "MY_NEW_VALUE": {
    "type": "text",
    "default": "example",
    "description": "Description shown in the form"
    }
  2. 필드 그룹에 키 추가 src/lib/placeholder-store.ts에서. 기존 그룹의 keys 배열에 추가하거나 FIELD_GROUPS에 새 그룹을 생성합니다.

  3. 콘텐츠에서 토큰 사용: 아무 .mdx 파일에 xMY_NEW_VALUEx를 작성합니다. DOM 워커가 런타임에 이를 대체합니다.

계산된 값은 다른 플레이스홀더에서 파생됩니다. 추가하려면:

  1. src/lib/placeholder-store.tscidrToMask의 패턴을 따라 조회 테이블을 추가합니다 (필요한 경우).

  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. 다른 토큰과 마찬가지로 콘텐츠에서 xMY_COMPUTEDx를 사용합니다. 계산된 값은 placeholders.json 항목이나 필드 그룹이 필요하지 않습니다 — 폼에서는 보이지 않습니다.