아키텍처#

소스 구조#

플러그인의 모든 로직은 src/ 아래에 있습니다:

파일

역할

main.ts

플러그인 진입점: 설정을 불러오고, 설정 탭·에디터 서제스트·라이브 프리뷰 익스텐션·포스트 프로세서를 연결하며, 주소록 새로고침 명령을 추가합니다.

types.ts

공용 타입: Contact, CardDavProfile, HarangContactsSettings.

settings.ts / settingsTab.ts

기본 설정값과 설정 UI입니다. settingsTab.ts는 1.13.0부터 제공되는 선언형 getSettingDefinitions() API만을 구현하며, display() 메서드로 UI를 명령형으로 구성하는 대신 설정 정의 트리를 반환합니다. 이것이 이 플러그인의 최소 지원 Obsidian 버전이 1.13.4인 이유입니다.

i18n.ts

Obsidian 공식 getLanguage() API로 현재 UI 언어를 조회하고, 작은 ko/en 사전에서 일치하는 문자열을 반환합니다(없으면 영어로 대체).

carddav/client.ts

최소한의 읽기 전용 CardDAV 클라이언트입니다: Obsidian의 requestUrl을 통해 주소록 탐색(PROPFIND/current-user-principal/addressbook-home-set)과 연락처 조회(REPORT addressbook-query)를 수행합니다.

carddav/vcard.ts

vCard 3/4를 파싱해 FN, UID, EMAIL, TEL, ORGContact로 추출하는 작은 파서입니다.

carddav/store.ts

ContactStore: 설정된 모든 프로필의 연락처를 병합·캐시하며, TTL 기반 신선도 판단과 검색/조회 헬퍼를 제공합니다.

editorSuggest.ts

HrcardEditorSuggest: {{hrcard:에서 트리거되는, 별도의 자유 입력 트리거 없이 하나로 이어지는 단계별 서제스터입니다 - 입력된 쿼리를 :로 나눠 현재 어느 단계인지(프로필 이름, 이어서 그 프로필의 연락처 이름) 판단하고, {{hrcard:<profileId>:<uid>}}를 삽입합니다.

render/livePreview.ts

라이브 프리뷰에서 {{hrcard:...}} 범위를 칩 위젯으로 치환하는 CodeMirror 6 ViewPlugin입니다. 커서나 선택 영역이 겹치는 범위는 건너뛰어 원문 문법을 계속 편집할 수 있게 합니다.

render/postProcessor.ts

렌더링된 텍스트 노드를 순회하며 읽기 모드에서 동일한 치환을 수행하는 마크다운 포스트 프로세서입니다.

render/chip.ts / render/card.ts

칩 DOM 엘리먼트와, 클릭 시 열리는 상세 카드(직접 위치를 계산하는 플로팅 패널이며, 바깥 클릭이나 Esc로 닫힘)입니다.

데이터 흐름#

settingsTab.ts        -->  CardDavProfile[] (server URL, credentials)
     |
     v
carddav/client.ts      -->  PROPFIND/REPORT over requestUrl
     |                      (discovery + address book fetch)
     v
carddav/vcard.ts        -->  parses each vCard into a Contact
     |
     v
carddav/store.ts          -->  merged, cached Contact[] across profiles
     |
     +--> editorSuggest.ts (HrcardEditorSuggest)  -->  autocomplete while typing
     |
     +--> render/livePreview.ts   -->  chip widgets (Live Preview)
     |
     +--> render/postProcessor.ts -->  chip elements (Reading view)
              |
              v
       render/chip.ts + render/card.ts  -->  click-to-open detail card

참조 문법#

확정된 참조는 {{hrcard:<profileId>:<uid>}} 형태로 저장됩니다. profileIduid는 연락처를 정확히 찾는 데 사용되므로, (같은 서버든 다른 서버든) 표시 이름이 우연히 같은 두 연락처가 서로 혼동되는 일이 없습니다 - 표시 이름 자체는 저장된 문법에 전혀 포함되지 않고, 렌더링 시점에 그 uid에 대해 스토어가 현재 가지고 있는 값에서 매번 가져올 뿐입니다. 프로필을 만들 때 한 번 생성되고 이후로 절대 바뀌지 않는 내부 id를 사용자가 지정한 name 대신 사용하므로, 이 변경 이후로 작성된 참조는 설정에서 프로필 이름을 바꿔도 더 이상 깨지지 않습니다 - editorSuggest.ts는 자동완성 1단계에서 여전히 프로필을 표시 이름으로 검색·목록화하지만, 삽입할 텍스트를 구성할 때는 그 자리를 id로 조용히 바꿔치기합니다. 이는 하위 호환되지 않습니다: 이 변경 전에 삽입된 참조는 프로필의 이름을 대신 저장하고 있었고, id와 이름을 함께 시도하는 대체 해석기가 없기 때문에, 그런 참조는 프로필 이름이 바뀌는 즉시(또는 애초에 그 자리 값이 현재 어떤 프로필의 id와도 일치하지 않는다면 즉시) 더 이상 해석되지 않으며, {{hrcard: 자동완성으로 삭제 후 다시 삽입해야 합니다. {{...}}는 Obsidian의 위키링크 문법이 아니므로, 가상의 [[...]] 형태와 달리 Obsidian 자체의 링크 파서에 가로채이는 일이 없습니다 - 읽기 모드와 라이브 프리뷰 모두 원문 텍스트를 직접 매칭합니다. 손으로 입력한 참조가 해석되려면 정확한 프로필 id와 CardDAV UID가 필요한데, 둘 다 UI 어디에도 표시되지 않으므로 기존 참조에서 복사해오는 경우가 아니라면 사실상 입력하기 어렵습니다 - 실제로는 항상 {{hrcard: 자동완성을 통해 참조를 삽입하세요.

런타임 의존성 없음#

Obsidian 자체가 제공하는 것(obsidian 패키지, CodeMirror 6) 외에는 런타임 의존성이 없습니다 — CardDAV 클라이언트와 vCard 파서 모두 npm에서 가져오지 않고 직접 작성해, 번들 크기를 작게 유지하고 서드파티 HTTP/XML 파싱 라이브러리 자체의 취약점에 노출될 일을 없앴습니다.