Files
Gnuboard7/docs/frontend/editors.md
T
HeuJung 8328b1db77 docs(core,extensions): 확장 개발자 문서 9세트 집필과 생성기 정합 보강
S1 파일럿 3종(sirsoft-board · sirsoft-gdpr · sirsoft-admin_basic)에 이어
결제·본인인증 동형군 6종의 AGENTS.md · README.md · docs/ 5문서를 집필했다.
확장을 고치려는 쪽이 매번 src/ 를 훑어 구조를 재발견하지 않도록, 설계 의도와
확장점(발행·구독 훅)·수정 시 동반 의무·금지 패턴을 코드 근거로 서술했다.

생성기(ExtensionDocScaffolder)에서 표를 무의미하게 만들던 세 결함을 함께 고쳤다.
getLayoutExtensions 기본 구현이 돌려주는 절대경로가 파일 목록과 중복돼 로컬
머신 경로가 커밋 문서에 실리던 문제, getNotificationDefinitions 를 'key'/'event'
로 읽어 모든 행이 '-' 로 찍히던 문제, getSettingsLayout 절대경로가 정규화 없이
노출되던 문제다. 셋 다 예외를 남기지 않고 표만 조용히 망가뜨리므로
ExtensionDocContractTest 에 각각의 되돌림 red 를 확인한 단언을 두었다.
템플릿의 extensions/{id}/ 는 모듈·플러그인의 발행과 반대 방향(오버라이드)이라
별도 블록(template-overrides)으로 분리했다.

sirsoft-admin_basic 의 컴포넌트·핸들러·레이아웃 문서를 코어 docs/ 에서 그 템플릿
소유로 이관하고, 남은 참조 6축을 재는 가드를 추가했다. 이관 후 남은 옛 경로는
오류가 아니라 헛걸음으로만 나타나 드러나지 않는다. 의 상대 링크가
한 단계 얕아 공유 docs/ 대신 를 가리키던 문제도 함께 고쳤다.

README 상단의 확장명 이미지 배지를 평문 H1 로 바꿨다( 지시 2026-08-31).
루트 README.md · README.ko.md 도 같은 기준을 적용했다. 정보 배지는 유지한다.

sirsoft-gdpr: 회원탈퇴로 자동 철회된 동의가 관리자 동의 이력 화면의 출처 필터로
걸러지지 않던 문제를 고쳤다. Repository 가 'withdraw' 리터럴을 직접 UPDATE 에
싣는데 그 값이 ConsentSource enum 에 없어, 화면 필터 옵션·라벨 어느 쪽에도
도달하지 못했다. 어휘를 enum 단일 출처로 모으고 ko·en·ja 라벨과 필터 옵션을
함께 채웠으며, 어휘 대조 테스트의 모집단에 Repository 를 편입했다.
2026-08-31 15:57:44 +09:00

9.1 KiB

에디터 컴포넌트 가이드

중요: 콘텐츠 편집이 필요한 경우 이 문서의 에디터 컴포넌트를 사용하세요.


TL;DR (5초 요약)

1. HtmlEditor: HTML/텍스트 편집, 게시판/상품 설명 등 사용
2. CodeEditor: JSON/코드 편집, Monaco Editor 기반
3. RichTextEditor: 미구현 상태 (사용 금지)
4. 보안: DOMPurify로 XSS 방지 필수 (HtmlEditor 내장)
5. 폼 바인딩: name, onChange props로 자동 연동

목차

  1. 에디터 선택 가이드
  2. HtmlEditor 컴포넌트
  3. CodeEditor 컴포넌트
  4. 보안 처리
  5. 관련 문서

에디터 선택 가이드

용도 추천 에디터 설명
게시판 글 작성 HtmlEditor HTML/텍스트 모드 전환 가능
상품 설명 HtmlEditor HTML 모드로 서식 지원
공지사항 HtmlEditor 미리보기 기능 활용
JSON 편집 CodeEditor 문법 강조, 스키마 검증
코드 스니펫 CodeEditor 다양한 언어 지원
레이아웃 JSON CodeEditor 자동 포맷, IntelliSense

사용 불가 에디터

에디터 상태 비고
RichTextEditor 미구현 스켈레톤만 존재, 사용 금지

HtmlEditor 컴포넌트

타입: composite

파일: templates/sirsoft-admin_basic/src/components/composite/HtmlEditor.tsx

용도: HTML과 일반 텍스트를 편집할 수 있는 범용 에디터

주요 기능

  • HTML 모드 / 일반 텍스트 모드 전환
  • HTML 모드에서 미리보기 기능
  • DOMPurify를 통한 XSS 방지
  • Form 자동 바인딩 지원

Props 레퍼런스

Props 타입 필수 기본값 설명
content string ❌ '' 콘텐츠 값
onChange function ❌ - 콘텐츠 변경 콜백 (이벤트 객체 형식)
isHtml boolean ❌ false HTML 모드 여부
onIsHtmlChange function ❌ - HTML 모드 변경 콜백
rows number ❌ 15 Textarea 행 수
placeholder string ❌ '' 플레이스홀더 텍스트
label string ❌ - 라벨 텍스트
showHtmlModeToggle boolean ❌ true HTML 모드 체크박스 표시 여부
contentClassName string ❌ '' HtmlContent 미리보기 영역 클래스
purifyConfig object ❌ - DOMPurify 커스텀 설정
className string ❌ '' 컨테이너 CSS 클래스
name string ❌ 'content' 콘텐츠 필드명 (폼 바인딩용)
htmlFieldName string ❌ 'content_mode' HTML 모드 체크박스 필드명
readOnly boolean ❌ false 읽기 전용 모드

레이아웃 JSON 사용 예시

기본 사용 (게시판 글 작성)

{
  "id": "content_section",
  "type": "composite",
  "name": "HtmlEditor",
  "props": {
    "content": "{{_local.form?.content ?? ''}}",
    "isHtml": "{{(_local.form?.content_mode ?? 'text') === 'html'}}",
    "rows": 15,
    "placeholder": "$t:board.form.content_placeholder",
    "label": "$t:board.form.content",
    "showHtmlModeToggle": true,
    "name": "content",
    "htmlFieldName": "content_mode"
  },
  "actions": [
    {
      "event": "onChange",
      "type": "change",
      "handler": "setState",
      "params": {
        "target": "local",
        "form": {
          "content": "{{$args[0].target.value}}"
        }
      }
    },
    {
      "event": "onIsHtmlChange",
      "type": "change",
      "handler": "setState",
      "params": {
        "target": "local",
        "form": {
          "content_mode": "{{$args[0].target.checked ? 'html' : 'text'}}"
        }
      }
    }
  ]
}

HTML 모드 고정 (상품 설명)

{
  "id": "description_editor",
  "type": "composite",
  "name": "HtmlEditor",
  "props": {
    "content": "{{_local.form?.description ?? ''}}",
    "isHtml": true,
    "rows": 20,
    "placeholder": "$t:product.form.description_placeholder",
    "label": "$t:product.form.description",
    "showHtmlModeToggle": false,
    "name": "description"
  },
  "actions": [
    {
      "event": "onChange",
      "type": "change",
      "handler": "setState",
      "params": {
        "target": "local",
        "form": {
          "description": "{{$args[0].target.value}}"
        }
      }
    }
  ]
}

읽기 전용 (상세 보기)

{
  "id": "content_view",
  "type": "composite",
  "name": "HtmlEditor",
  "props": {
    "content": "{{post.data?.content ?? ''}}",
    "isHtml": "{{(post.data?.content_mode ?? 'text') === 'html'}}",
    "readOnly": true,
    "showHtmlModeToggle": false
  }
}

폼 바인딩 패턴

HtmlEditor는 두 개의 필드를 폼에 바인딩합니다:

  1. content 필드 (name props): 콘텐츠 값
  2. content_mode 필드 (htmlFieldName props): 콘텐츠 모드 (text/html)
{
  "state": {
    "form": {
      "content": "",
      "content_mode": "text"
    }
  }
}

이벤트 콜백 구조

// onChange 콜백
onChange({ target: { name: 'content', value: '콘텐츠 값' } })

// onIsHtmlChange 콜백
onIsHtmlChange({ target: { name: 'content_mode', checked: true } })

CodeEditor 컴포넌트

타입: composite

파일: templates/sirsoft-admin_basic/src/components/composite/CodeEditor.tsx

용도: JSON, JavaScript, HTML, CSS 등 코드 편집

라이브러리: Monaco Editor (@monaco-editor/react)

주요 기능

  • 다양한 프로그래밍 언어 지원
  • JSON 스키마 검증
  • 문법 강조 (Syntax Highlighting)
  • 자동 포맷팅
  • IntelliSense 지원
  • 라이트/다크 테마

Props 레퍼런스

Props 타입 필수 기본값 설명
value string ✅ - 에디터 값
onChange function ❌ - 값 변경 콜백 (문자열 직접 전달)
language string ❌ 'json' 언어 (json, javascript, html, css, typescript 등)
height string ❌ '100%' 에디터 높이
readOnly boolean ❌ false 읽기 전용 모드
theme 'vs-dark' | 'vs-light' ❌ 'vs-dark' 에디터 테마

레이아웃 JSON 사용 예시

JSON 편집기

{
  "id": "json_editor",
  "type": "composite",
  "name": "CodeEditor",
  "props": {
    "value": "{{_local.jsonContent ?? '{}'}}",
    "language": "json",
    "height": "400px",
    "theme": "vs-dark"
  },
  "actions": [
    {
      "event": "onChange",
      "type": "change",
      "handler": "setState",
      "params": {
        "target": "local",
        "jsonContent": "{{$args[0]}}"
      }
    }
  ]
}

JavaScript 코드 편집기

{
  "id": "js_editor",
  "type": "composite",
  "name": "CodeEditor",
  "props": {
    "value": "{{_local.code ?? ''}}",
    "language": "javascript",
    "height": "300px",
    "theme": "vs-light"
  },
  "actions": [
    {
      "event": "onChange",
      "type": "change",
      "handler": "setState",
      "params": {
        "target": "local",
        "code": "{{$args[0]}}"
      }
    }
  ]
}

읽기 전용 코드 뷰어

{
  "id": "code_viewer",
  "type": "composite",
  "name": "CodeEditor",
  "props": {
    "value": "{{config.data?.settings ?? '{}'}}",
    "language": "json",
    "height": "200px",
    "readOnly": true
  }
}

지원 언어

언어 language 값
JSON json
JavaScript javascript
TypeScript typescript
HTML html
CSS css
PHP php
Markdown markdown

이벤트 콜백 구조

// onChange 콜백 - 문자열 직접 전달 (HtmlEditor와 다름)
onChange('에디터 값')

보안 처리

HtmlEditor의 XSS 방지

HtmlEditor는 내부적으로 DOMPurify를 사용하여 HTML을 정화합니다:

  • 미리보기 시 DOMPurify로 HTML 정화
  • HtmlContent 컴포넌트에서 렌더링 시 추가 정화
  • 외부 링크에 rel="noopener noreferrer" 자동 추가

커스텀 DOMPurify 설정

{
  "type": "composite",
  "name": "HtmlEditor",
  "props": {
    "content": "{{_local.content}}",
    "isHtml": true,
    "purifyConfig": {
      "ALLOWED_TAGS": ["p", "br", "strong", "em", "a", "ul", "ol", "li"],
      "ALLOWED_ATTR": ["href", "target", "rel"]
    }
  }
}

백엔드 검증 필수

주의: 프론트엔드 정화만으로는 불충분
필수: 백엔드에서도 HTML Purifier 또는 유사 도구로 검증
✅ 필수: API 저장 시 XSS 필터링 적용

관련 문서