컨텍스트: UI 와 Shadow DOM 격리
context.ui
섹션 제목: “context.ui”showNotification(message: string, type?: "error" | "info" | "warning"): void;
showStatusBarItem(text: string, align?: "left" | "right"): StatusBarItem;// StatusBarItem = { setText(text: string): void; dispose(): void }
addStyle(css: string): Disposable;
addSidebarPanel(opts: PluginSidebarPanelOptions): Disposable;addSettingsTab(opts: PluginSettingsTabOptions): Disposable;// PluginSidebarPanelOptions = { id: string; title: string; icon?: string;// onMount(el: HTMLElement): void; onUnmount?(el: HTMLElement): void }// PluginSettingsTabOptions = { id: string; title: string;// onMount(el: HTMLElement): void; onUnmount?(el: HTMLElement): void }
registerFileViewer(opts: PluginFileViewerOptions): Disposable;// PluginFileViewerOptions = { id: string; extensions: string[];// onMount(el: HTMLElement, ctx: PluginFileViewerContext): void;// onUpdate?(el: HTMLElement, ctx: PluginFileViewerContext): void;// onUnmount?(el: HTMLElement): void;// editable?: boolean; getText?(el: HTMLElement): string }// PluginFileViewerContext = { assetUrl: string; filePath: string;// refreshKey: number; zoomLevel: number; edit?: PluginFileViewerEdit }// PluginFileViewerEdit = { tabId: string; text: string; markChanged(): void }context.ui 자체는 매니페스트가 sidebar·statusbar·settings·viewer 중 하나라도 선언하면
쓸 수 있지만(하나만 있어도 객체가 열립니다), 메서드마다 자기 관문이 있습니다.
| 메서드 | 필요한 권한 |
|---|---|
showStatusBarItem |
statusbar |
addSidebarPanel |
sidebar |
addSettingsTab |
settings |
registerFileViewer |
viewer (editable: true 면 files 도) |
showNotification |
sidebar / statusbar / settings / viewer 중 하나 |
addStyle |
sidebar / statusbar / settings / viewer 중 하나 |
답을 돌려받는 선택창 · 입력창은 컨텍스트: 선택창과 입력창을
보십시오 — ui 권한이 필요 없습니다.
sandboxed 플러그인에는 addSidebarPanel이 없습니다. 패널을 매니페스트에 선언하고 내용을 보냅니다 —
사이드바 패널을 보십시오.
참고:
showStatusBarItem은StatusBarItem객체를 돌려줍니다 —.setText(...)로 텍스트를 그 자리에서 갱신하고.dispose()로 제거합니다. 두 번째 매개변수는align: "left" | "right"이고 기본값은"right"입니다.addStyle(css)는document.head(light DOM)에<style>태그를 넣습니다. Shadow DOM 사이드바 패널이나 설정 탭 안의 내용에는 스타일을 걸 수 없습니다 — Shadow DOM UI 격리를 보십시오.addSidebarPanel/addSettingsTab은 둘 다onMount(el)을 통해 격리된 Shadow DOM 하위 트리에 붙습니다 — 다음 절을 보십시오.registerFileViewer는 나열한 확장자의 파일을 코드 에디터가 아니라 여러분의 뷰어로 열게 합니다. 호스트가onMount에 평범한(light DOM) 요소와 컨텍스트를 넘깁니다 —assetUrl은asset:프로토콜로 서빙되는 그 파일이고(refreshKey로 이미 캐시가 무효화돼 있습니다),zoomLevel은 공유되는 에디터 배율입니다(Cmd+= / Cmd+- / Cmd+0, Ctrl+휠) — 그것에 맞춰 내용을 확대·축소하는 것은 뷰어의 몫입니다.onUpdate는 붙어 있는 동안 컨텍스트가 바뀌면 발생합니다(배율, 저장, 외부 재적재). 편집 마운트(아래 편집 가능한 파일 뷰어 페이지)에는 자기 자신의 저장이 전달되지 않습니다. 텍스트 확장자에서는 앱이 미리보기 ↔ 소스 전환을 유지합니다 — 여러분의 뷰어가 미리보기 쪽을, CodeMirror가 소스 쪽을 렌더합니다. 바이너리 확장자는 뷰어 전용이고, 앱의 바이너리 가드(UTF-8 읽기 금지, 텍스트 저장 금지)는 그 플러그인이 켜져 있든 아니든 적용됩니다. 내장media-viewer플러그인(src/plugins/builtin/media-viewer.ts)이 참조 구현입니다. 뷰어가 보여 주는 파일을 고칠 수도 있습니다 — 편집 가능한 파일 뷰어를 보십시오.
Shadow DOM UI 격리
섹션 제목: “Shadow DOM UI 격리”addSidebarPanel과 addSettingsTab은 여러분의 마크업을 앱의 DOM 트리에 바로 렌더하지 않습니다.
대신 호스트가 마운트 지점에 open Shadow DOM을 붙이고, 그 shadow root 안에 사는 <div>로
onMount(el)을 호출합니다. 그래서 패널의 CSS가 앱의 나머지와 서로 격리됩니다 — 앱의 전역
스타일시트가 새어 들어오지 않고, 패널이 넣은 CSS도 새어 나가지 않습니다.
실무에서 따라오는 것들:
-
shadow 안의 내용에는
onMount안에서el에<style>요소를 붙여 스타일을 거십시오 —context.ui.addStyle()은document.head(light DOM)를 대상으로 하므로 shadow 내용에 절대 닿지 않습니다. 두 예제 플러그인이 모두 이렇게 합니다.function appendStyle(el: HTMLElement, css: string): void {const style = document.createElement("style");style.textContent = css;el.appendChild(style);}onMount(el) {appendStyle(el, PANEL_STYLE);// ...el 아래에 패널 DOM 을 만든다} -
CSS 커스텀 속성은 shadow 경계를 넘어 상속됩니다. 앱의 디자인 토큰 변수(
var(--color-text-default),var(--color-border-default),var(--color-bg-subtle)등)가 여러분의 shadow root 안에서도 보이므로, 토큰 값을 복제하지 않고 살아 있는 앱 테마에 맞춰 패널을 꾸밀 수 있습니다. 상속된 커스텀 속성만으로 전부 꾸미는 완전한 예는examples/plugins/ai-summary/src/index.ts를 보십시오. -
onMount(el)은ShadowRoot객체 자체가 아니라 shadow root의 내부 내용<div>를 받습니다 (ShadowRoot에는.style/.classList가 없습니다).onUnmount(el)이 있으면 호스트가 하위 트리를 제거하기 전에 호출됩니다 — 추적되는Disposable이 아닌 정리(타이머,el의 자손에 직접 붙인 수동 이벤트 리스너 등)에 쓰십시오.

