dist/ 산출물뿐이며, demo/ 예제로 통합 패턴을 참고할 수 있습니다.dist/ ├── webeditor.js // UMD 번들 (가독성 유지) ├── webeditor.min.js // UMD 번들 (terser minified) ├── webeditor.esm.js // ES Module 빌드 ├── webeditor.css // 전체 스타일 ├── webeditor.min.css // 압축 스타일 ├── webeditor.d.ts // TypeScript 타입 선언 └── plugins/ // 별도 로드(opt-in) 플러그인 번들 ├── wordimport-native(.min).js // MS Word(.docx) 가져오기 ├── hwpximport-native(.min).js // 한글 HWPX(.hwpx) 가져오기 ├── xlsximport-native(.min).js // 엑셀 XLSX(.xlsx) 가져오기 ├── pptimport-native(.min).js // 파워포인트 PPTX(.pptx) 가져오기 └── markdown-native(.min).js // 마크다운(.md) 뷰어/편집
demo/ ├── sdk-guide.html // 본 문서 — 통합 가이드 ├── board.html // 게시판 목록 ├── write.html // 게시글 작성 (Word 가져오기 통합) ├── view.html // 게시글 보기 (XSS 새니타이저 포함) ├── dom-api.html // DOM API 인터랙티브 데모 ├── menubar.html // 메뉴바·UI 컨트롤 데모 └── wordimport.html // MS Word 가져오기 데모 assets/ └── icons.svg // SVG 스프라이트 (60+ symbols) img/ ├── dee1.PNG // 밝은 배경용 로고 ├── dee2.PNG // 어두운 배경용 로고 └── dee_gray.png // 무채색 로고
문서 가져오기(Word·HWPX·XLSX·PPTX·마크다운)는 코어 번들과 분리된 외부 플러그인으로, 필요할 때만 해당 번들을 로드해 plugins에 추가하면 됩니다(미사용 시 코어에 영향 0). 모두 외부 라이브러리·서버 없이 브라우저에서 100% 변환합니다(폐쇄망 적합).
<!-- 1. 코어 로드 --> <script src="dist/webeditor.min.js"></script> <!-- 2. Word 가져오기 플러그인 로드 (별도 파일) --> <script src="dist/plugins/wordimport-native.min.js"></script> <!-- 3. 에디터 옵션에 'wordimport' 추가 --> <script> const editor = new WebEditor('#editor', { menubar: true, plugins: ['format', 'image', 'table', 'link', 'wordimport'], }); </script>
.docx 파일 드래그앤드롭
hwpximport-native.min.js / xlsximport-native.min.js / pptimport-native.min.js / markdown-native.min.js를 로드하고 plugins에 'hwpximport' / 'xlsximport' / 'pptimport' / 'markdown'을 추가하면 됩니다.
DecompressionStream API 필수)
<script> 하나와 new WebEditor()만으로 사용합니다.<!-- 1. CSS 로드 --> <link rel="stylesheet" href="dist/webeditor.min.css"> <!-- 2. 컨테이너 --> <div id="editor"></div> <!-- 3. JS 로드 + 초기화 --> <script src="dist/webeditor.min.js"></script> <script> const editor = new WebEditor('#editor'); </script>
const editor = new WebEditor('#editor', { height: '500px', placeholder: '공지사항 내용을 입력하세요.', onChange: (html) => { document.getElementById('char-count').textContent = editor.body.innerText.replace(/\s/g, '').length + '자'; }, onFocus: () => console.log('편집 시작'), onBlur: () => console.log('편집 완료'), });
const editor = new WebEditor('#editor', { plugins: ['format', 'link'], // 서식 + 링크만 });
const editor = new WebEditor('#editor', { plugins: ['format', 'image', 'link', 'emoji', 'insert'], });
const editor = new WebEditor('#editor'); // 기본 = 전체
toolbar 옵션으로 버튼 배치를 완전히 제어합니다. 각 하위 배열이 한 행이며 '|'는 구분선입니다. null(기본)이면 기본 배치가 적용됩니다.const editor = new WebEditor('#editor', { toolbar: [ // 1행: 서식 핵심만 ['bold', 'italic', 'underline', 'strikeThrough', '|', 'fontColor', 'highlightColor', '|', 'removeFormat'], // 2행: 레이아웃 + 삽입 ['justifyLeft', 'justifyCenter', 'justifyRight', '|', 'insertImage', 'insertLink', 'insertTable', '|', 'undo', 'redo'], ], });
| 서식 | paragraphStyle, fontFamily, fontSize, lineHeight, bold, italic, underline, strikeThrough, superscript, subscript, fontColor, highlightColor, fontSizeUp, fontSizeDown, removeFormat |
| 정렬·목록 | justifyLeft, justifyCenter, justifyRight, justifyFull, indent, outdent, insertUnorderedList, insertOrderedList |
| 편집 | undo, redo, caseUpper, caseLower, selectAll |
| 삽입 | insertImage, insertTable, insertLink, removeLink, insertVideo, insertShape (도형), emoji, specialChar, insertHR, insertBlockquote, insertCodeBlock, insertDateTime, insertPageBreak, documentBackground |
| 파일·가져오기 | newDocument, openHTML, save, saveAsPDF, printContent, wordImport, hwpxImport, xlsxImport, pptImport (가져오기 4종 — 해당 플러그인 로드 시) |
| 도구·구분선 | findReplace, guardScan (금칙어·개인정보 검사), 구분선 '|' |
paragraphStyle·fontFamily·fontSize·lineHeight는 드롭다운(셀렉트), fontColor·highlightColor는 색상 피커로 렌더됩니다.
리본 UI(uiTemplate:'ribbon')에서는 이 toolbar 배열이 하단 폰트 퀵바에 적용되고, 상단 탭/카드는 리본 기본 구성을 따릅니다.
uploadHandler를 지정하면 이미지를 서버에 업로드한 뒤 URL로 삽입합니다.
미지정 시 base64 Data URL로 삽입됩니다.
const editor = new WebEditor('#editor', { uploadHandler: async (file) => { const form = new FormData(); form.append('image', file); const res = await fetch('/api/upload', { method: 'POST', body: form }); const { url } = await res.json(); return url; // 최종 URL 반환 필수 }, });
init, execute, queryState)를 구현하면 됩니다.// 글자수 제한 플러그인 예제 class CharLimitPlugin { constructor(editor, limit = 1000) { this.editor = editor; this.limit = limit; } init() { this.editor.on('change', () => { const chars = this.editor.body.innerText.replace(/\s/g, '').length; if (chars > this.limit) { alert(`글자수 초과! 현재 ${chars}자 / 최대 ${this.limit}자`); } }); } execute(command) {} // 툴바 버튼 없으면 비워도 됨 queryState() { return {}; } } // 사용 const editor = new WebEditor('#editor', { plugins: ['format'] }); const limiter = new CharLimitPlugin(editor, 500); limiter.init(); editor.plugins.push(limiter);
// 폼 제출 시 hidden input에 HTML 복사 const editor = new WebEditor('#editor'); const form = document.querySelector('#post-form'); form.addEventListener('submit', (e) => { const html = editor.getHTML(); if (!html.trim() || html === '<br>') { e.preventDefault(); alert('내용을 입력해주세요.'); editor.focus(); return; } document.getElementById('content-input').value = html; });
// ID로 요소 모델 획득 const table = editor.getAPIModelById('mytable'); // CSS 선택자로 조회 const firstP = editor.querySelector('p'); const allImgs = editor.querySelectorAll('img'); // 본문 루트 const root = editor.getRoot(); // or editor.getBody() // 옵션 — 변경 이벤트 억제 (일괄 작업용) const p = editor.querySelector('p', { skipRendering: true });
const el = editor.getAPIModelById('title'); el.getText(); // "제목 텍스트" el.getHTML(); // "<strong>제목</strong>" el.getTagName(); // "h2" el.getId(); // "title" el.getAttribute('class'); el.getStyle(); // "color:red; font-size:14pt" el.getCSS('color'); // "red" el.getRect(); // DOMRect {x, y, width, height} el.getWidth(); // offsetWidth el.getNode(); // 원시 DOM Element (escape hatch)
el.setText('새 텍스트'); el.setHTML('<b>HTML</b>'); el.setStyle('color:red; font-weight:bold'); el.setCSS('color', 'blue'); el.setCSS({ color: 'red', fontSize: '14pt' }); el.setAttribute('data-id', '123'); el.removeAttribute('data-id'); el.addClass('highlight'); el.removeClass('highlight'); el.toggleClass('active'); el.hasClass('active'); // true / false // 모든 메서드는 자기 자신을 반환 → 체이닝 가능 el.setText('Hello').addClass('note').setStyle('color:blue');
// 탐색 el.parent(); // APIModel | null el.children(); // APIModel[] el.nextSibling(); el.prevSibling(); el.find('td'); // 하위 검색 (배열) el.findOne('td'); // 하위 첫 매치 el.closest('table'); // 조상 검색 (선택자 또는 함수) el.closest(m => m.hasClass('foo')); // 삽입 — 문자열 / Node / APIModel 모두 허용 el.append('<p>끝에</p>'); el.prepend('<p>앞에</p>'); el.insertBefore('<hr>'); el.insertAfter('<hr>'); // 제거 el.empty(); // 자식 노드 모두 제거 el.remove(); // 자기 자신 제거
const tbl = editor.getAPIModelById('mytable'); tbl.insertRow(1); // 인덱스 1번 위에 새 행 삽입 tbl.insertCol(2); // 인덱스 2번 왼쪽에 새 열 삽입 tbl.deleteRow(0); // 0번 행 삭제 tbl.deleteCol(); // 마지막 열 삭제 tbl.setWidth(400); tbl.setHeight('200px');
change)는 매 작업마다 발생합니다. 다수 변경 시 한 번만 통지하려면 skipRendering: true로 작업한 뒤 editor.render()를 호출합니다.// 100개 단락에 클래스 추가 — change는 1회만 발생 editor.querySelectorAll('p', { skipRendering: true }) .forEach(p => p.addClass('paragraph')); editor.render(); // → change 이벤트 1회 발생
// 입력: 폼 값을 에디터 표 셀에 채워넣기 function applyToEditor(form) { editor.getAPIModelById('cell-name').setText(form.name); editor.getAPIModelById('cell-phone').setText(form.phone); editor.getAPIModelById('cell-addr').setText(form.address); } // 추출: 에디터 표에서 값 읽어오기 function extractFromEditor() { return { name: editor.getAPIModelById('cell-name').getText(), phone: editor.getAPIModelById('cell-phone').getText(), address: editor.getAPIModelById('cell-addr').getText(), }; }
| 분류 | 메서드 |
|---|---|
| 식별·노드 | getNode, isValid, getId, setId, getTagName, matches |
| 속성 | getAttribute, setAttribute, removeAttribute, hasAttribute |
| 클래스 | addClass, removeClass, hasClass, toggleClass |
| 스타일 | getStyle, setStyle, setCSS(prop, val), setCSS({...}), getCSS |
| 내용 | getText, setText, getHTML, setHTML, empty, remove |
| 탐색 | parent, closest, children, childNodes, nextSibling, prevSibling, find, findOne |
| 삽입 | append, prepend, insertBefore, insertAfter |
| 측정 | getRect, getWidth, getHeight |
| 표 전용 | insertRow, insertCol, deleteRow, deleteCol, setWidth, setHeight |
// 기본 메뉴 활성화 (파일·편집·보기·삽입·서식·도구·도움말) const editor = new WebEditor('#editor', { menubar: true, }); // 커스텀 메뉴 구성 const editor2 = new WebEditor('#editor', { menubar: [ { label: '게시글', items: [ { label: '새 게시글', cmd: 'newDocument' }, { label: '임시저장', cmd: 'mySaveDraft' }, { sep: true }, { label: '발행', cmd: 'myPublish' }, ]}, { label: '서식', items: [ { label: '굵게', cmd: 'bold', shortcut: 'Ctrl+B' }, { label: '기울임', cmd: 'italic', shortcut: 'Ctrl+I' }, ]}, ], onMenuCommand: (cmd, editor, item) => { if (cmd === 'mySaveDraft') { fetch('/api/draft', { method: 'POST', body: editor.getHTML() }); return false; // 기본 처리 차단 } }, });
| 분류 | 명령(cmd) |
|---|---|
| 파일 | newDocument, openHTML, save, saveAs, print, printPreview |
| 편집 | undo, redo, cut, copy, paste, selectAll, find |
| 보기 | toggleToolbar, toggleMenuBar, toggleStatusBar, toggleFullscreen, modeEdit, modeHTML, modePreview |
| 삽입 | insertImage, insertTable, createLink, insertVideo, insertEmoji, insertHorizontalRule |
| 서식 | bold, italic, underline, strikeThrough, clearFormat, clearCSSFormat |
| 도구 | toggleReadOnly, showWordCount, about, shortcuts |
// 개별 토글 editor.showMenuBar(true); // 메뉴바 표시/숨김 editor.showToolbar(false); // 툴바 표시/숨김 editor.showStatusBar(true); // 상태바 표시/숨김 editor.setReadOnly(true); // 읽기 전용 전환 editor.setFullscreen(true); // 전체 화면 (F11, Esc 자동 지원) editor.setHeight('500px'); editor.setWidth('100%'); // 런타임에 메뉴바 동적 추가/제거 editor.enableMenuBar(); // 기본 메뉴 추가 editor.enableMenuBar(customConfig); // 커스텀 추가 editor.disableMenuBar(); // 일괄 적용 editor.setUI({ menubar: true, toolbar: true, statusbar: false, readOnly: false, fullscreen: false, height: '600px', }); // 현재 상태 조회 const state = editor.getUI(); // → { toolbar:true, menubar:true, statusbar:true, readOnly:false, fullscreen:false, width:'100%', height:'400px' }
F11 — 전체 화면 전환Esc — 전체 화면 종료Esc — 드롭다운 닫기.docx(MS Word)·.hwpx(한글)·.xlsx(엑셀)·.pptx(파워포인트) 문서를 에디터로 가져오는 opt-in 외부 플러그인입니다.
외부 라이브러리 0 — 브라우저 내장 DecompressionStream + DOMParser만 사용(폐쇄망 적합).
각각 별도 번들이라 plugins에 지정한 것만 활성화됩니다(미지정 시 영향 0). 여러 번들을 함께 로드하면 툴바·메뉴·리본에 가져오기 아이콘이 나란히 표시됩니다.
<!-- 코어 + Word 플러그인 함께 로드 (순서 중요) --> <script src="dist/webeditor.js"></script> <script src="dist/plugins/wordimport-native.js"></script> <script> const editor = new WebEditor('#editor', { menubar: true, plugins: ['format', 'image', 'table', 'wordimport'], // ← 추가 }); // 프로그래매틱 호출 editor.importWordFile(file, { mode: 'cursor', // 'replace' | 'cursor' | 'append' silent: false, // true면 다이얼로그 생략 }); </script>
.docx 파일 드래그앤드롭editor.importWordFile(file, options) API 호출| 지원 | 미지원 |
|---|---|
| 단락·정렬·들여쓰기 제목 스타일 (H1~H6) 굵게/기울임/밑줄/취소선 색상·폰트·크기·배경 표 + 셀 병합 (colspan/rowspan) 셀 음영·내부 격자선·테두리 글머리·번호 목록 하이퍼링크 이미지 (PNG/JPG, base64) 페이지 나누기 |
SmartArt · 도형 수식 (MathML) 머리글 / 꼬리글 각주 / 미주 VBA 매크로 변경 내용 추적 |
<!-- 코어 + HWPX 플러그인 함께 로드 --> <script src="dist/webeditor.js"></script> <script src="dist/plugins/hwpximport-native.js"></script> <script> const editor = new WebEditor('#editor', { menubar: true, plugins: ['format', 'image', 'table', 'hwpximport'], // ← 추가 }); // 프로그래매틱 호출 editor.importHwpxFile(file, { mode: 'replace', // 'cursor' | 'append' | 'replace' maxTableWidth: 740, // 최상위 표를 지정 px 너비로 비율 스케일(권장 740~780) silent: false, }); </script>
.hwpx 파일 드래그앤드롭editor.importHwpxFile(file, options) API 호출| 지원 | 미지원 |
|---|---|
| 단락·정렬·줄간격 굵게/기울임/밑줄·글자색·크기 표(셀 병합·테두리·단색/그라디언트 배경·너비/높이·세로정렬) 이미지(base64 인라인) 벡터 도형 → 인라인 SVG 심볼폰트(PUA) 특수문자 목차 점선 리더·페이지 나눔 도형 안 텍스트(hp:drawText) |
수식 머리글/꼬리글 각주·미주 본문 텍스트 감싸기·도형 회전 목록·하이퍼링크(텍스트만 보존) 구 바이너리 .hwp(미대상) |
.hwpx(OWPML)만 대상입니다. 미지원 요소는 텍스트 보존 + 경고로 처리되며, 도형은 내부 텍스트만 추출합니다.
<!-- 코어 + XLSX 플러그인 함께 로드 --> <script src="dist/webeditor.js"></script> <script src="dist/plugins/xlsximport-native.js"></script> <script> const editor = new WebEditor('#editor', { menubar: true, plugins: ['format', 'table', 'xlsximport'], // ← 추가 }); // 프로그래매틱 호출 — 시트를 HTML 표로 변환 editor.importXlsxFile(file, { mode: 'replace', // 'cursor' | 'append' | 'replace' maxTableWidth: 740, // 각 시트 표 최대 폭(px) allSheets: true, // 모든(비숨김) 시트 / false=활성 시트만 sheetSeparator: 'heading', // 'heading' | 'pagebreak' | 'hr' }); </script>
.xlsx 파일 드래그앤드롭editor.importXlsxFile(file, options) API 호출| 지원 | 미지원 |
|---|---|
| 시트 → HTML 표 변환 빈 셀·빈 행 격자 보정 셀 병합(colspan/rowspan) 굵게·글자색·솔리드 배경 변별 테두리(각 변 개별) 숫자서식(날짜·통화·백분율·천단위) 텍스트 넘침(빈 셀 흡수) 열 너비·행 높이 다중 시트(세로 나열/시트 탭) |
차트·피벗·스파크라인 조건부 서식·데이터 유효성 셀 내 이미지·도형 테마 색( @theme)수식 재계산(캐시 결과값 표시) |
maxCells 상한으로 보호됩니다.
<!-- 코어 + PPTX 플러그인 함께 로드 --> <script src="dist/webeditor.js"></script> <script src="dist/plugins/pptimport-native.js"></script> <script> const editor = new WebEditor('#editor', { menubar: true, plugins: ['format', 'image', 'table', 'pptimport'], // ← 추가 }); // 프로그래매틱 호출 — 슬라이드를 절대좌표 HTML로 렌더 editor.importPptxFile(file, { allSlides: true, // 전체 슬라이드 / slides:[1,3] 로 일부 지정 mode: 'replace', silent: false, }); </script>
.pptx 파일 드래그앤드롭editor.importPptxFile(file, options) API 호출| 지원 | 미지원 |
|---|---|
| 슬라이드 절대좌표 배치(레이아웃 보존) 벡터 도형(prstGeom·custGeom→SVG) 채움·외곽선(테마 참조 포함) 테마색(schemeClr+lumMod/shade/alpha) 표(병합·행높이·셀 테두리) 차트(원형/도넛/막대/선 SVG 직접) 이미지(srcRect 크롭·EMF 근사 렌더) 플레이스홀더 색·크기 상속 텍스트 인셋·정렬·spAutoFit |
WMF · EMF 그라디언트/텍스트/클리핑 그라디언트/패턴 브러시(단색 근사) 애니메이션 · 화면전환 · 노트 SmartArt(drawing 없으면 자리표시) PPTX 내보내기(export) |
doc-preview.html)에서는 슬라이드 탭으로 열람합니다. EMF 정확 렌더가 필요한 사내 덱은 내부 유틸 tools/pptx-emf-to-png.ps1로 PNG 변환 후 가져오기를 권장합니다.
Chrome 80+ · Edge 80+ · Firefox 113+ · Safari 16.4+ (이전 버전 미지원)
dist/webeditor.d.ts가 포함되어 있어 타입 추론이 자동으로 동작합니다.
전체 인터페이스 정의(WebEditorOptions·LicenseInfo·메서드 시그니처)는
📑 API 레퍼런스 §11 TypeScript 타입을 참조하세요.import WebEditor, { WebEditorOptions } from '@webeditor/core'; const options: WebEditorOptions = { height: '500px', onChange: (html: string) => saveContent(html), }; const editor = new WebEditor('#editor', options); // 타입 안전한 API 사용 const html: string = editor.getHTML(); editor.setHTML('<p>안녕하세요!</p>'); editor.on('change', (html: string) => console.log(html));
const editor = new WebEditor('#editor', { autoSave: { interval: 30000, // 저장 주기(ms) storageKey: 'my-doc', // 기본: we-autosave-{id} unloadWarning: true, // 변경 후 이탈 시 경고 onSave: (html) => {} // false 반환 시 저장 억제 } }); // 페이지 로드 시 복구 const saved = editor.getAutoSaved(); // string | null if (saved) { editor.setHTML(saved); editor.clearAutoSaved(); } editor.saveNow(); // 즉시 저장
editor.saveToPDF({ title: '문서', hideToolbar: true }); // 본문만 인쇄(PDF)
editor.saveToPDFInWindow({ title: '문서' }); // 새 창 출력 후 인쇄
editor.setZoom(1.5); // 0.25 ~ 4.0 (범위 밖 RangeError)
editor.getZoom(); // 현재 비율
editor.on('zoom', ({ ratio }) => console.log(ratio));
editor.getSafeHTML(); // script/on*/javascript: 제거 editor.getSafeHTML({ allowedTags: ['p','b','i','a'], // 외 태그는 언랩(텍스트 보존) allowedAttrs: ['href'], imageRewriter: (src) => 'https://cdn/' + src });
const editor = new WebEditor('#editor', { privacy: { detect: ['ssn','phone','email','credit-card','bank-account'] }, profanity: { words: ['금칙어1','금칙어2'], maskChar: '*' } // words: 배열 또는 URL }); const r = await editor.validate(); // { profanity:[...], privacy:[...] } await editor.maskSensitiveData(); // 감지 항목 마스킹(반환: 개수)
new WebEditor('#editor', { uploadHeaders: { 'X-App': 'dee' }, // 커스텀 헤더 csrfCookie: 'XSRF-TOKEN', // 쿠키값 → X-CSRF-Token 자동 주입 uploadHandler: (file, headers) => fetch('/upload', { method:'POST', headers, body: makeForm(file) }) .then(r => r.json()).then(j => j.url) });
new WebEditor('#editor', { inlineToolbar: true, // 텍스트 선택 시 부유 서식 툴바 hyperLinkDefaultTarget: '_blank' // 새 링크 기본 새 창 });
// 메뉴·리본 붙여넣기 버튼이 내부적으로 호출 (HTTPS/localhost + 권한 필요) const ok = await editor.pasteFromClipboard(); // 실패 시 false → Ctrl+V 안내 폴백 // 편집 영역 우클릭 → 잘라내기·복사·텍스트 복사·붙여넣기·텍스트로 붙여넣기·전체 선택 // (표 안에서는 표 전용 메뉴 + 'TSV로 텍스트 복사')
// 문서 배경(이미지/반복/배경색) — 소형 base64 임베드, 대형 업로드 URL editor.setDocumentBackground({ image: url, repeat: 'no-repeat', attach: 'fixed' }); editor.getDocumentBackground(); editor.removeDocumentBackground(); // 금칙어·개인정보 검사 결과 팝업(체크박스 선택 → 선택 항목만 마스킹) await editor.reviewSensitiveData(); // 한 글자 단위 실행취소(기본) — 한글은 완성 음절 단위 new WebEditor('#editor', { undoGranularity: 'char' }); // 'char' | 'group'
false) 시에는 종전처럼 height를 최소 높이로 삼아 내용에 따라 에디터가 늘어납니다.// ① 초기화 옵션 — 높이 고정 + 내부 세로 스크롤 const editor = new WebEditor('#editor', { height: '400px', // 고정할 편집창 높이 fixedHeight: true // 기본 false(내용에 따라 확장) · true(높이 고정 + 세로 스크롤) }); // ② 런타임 전환 editor.setFixedHeight(true); // 고정 켜기(내부 스크롤) editor.setFixedHeight(false); // 고정 끄기(내용에 따라 확장) // ③ 고정 높이 값 변경 — setHeight 는 자동으로 고정 모드로 동작 editor.setHeight(600); // 600px 고정 + 내부 스크롤
const editor = new WebEditor('#editor', { uiTemplate: 'ribbon', // 'classic' | 'ribbon' iconTheme: 'blue', // default·gray·blue·colorful·vivid·natural·modern·glass·solid·emoji 또는 폴더명 iconThemePath: 'assets/icon-themes' }); editor.setUITemplate('classic'); // 런타임 전환 editor.setIconTheme('vivid'); // 툴바 + 리본 탭 함께 갱신
// 이미지 URL 직접 삽입 (javascript:·vbscript:·비이미지 data: 스킴 자동 차단) editor.insertImage('https://example.com/photo.png', '대체 텍스트'); // 설정 허브에서 툴바 아이콘·메뉴바 구조·너비를 시각 구성 → config 저장 후 그대로 적용 new WebEditor('#editor', { menubar: cfg.menubar, toolbar: cfg.toolbar, statusbar: cfg.statusbar, width: cfg.width }); // 문서 템플릿(양식 11종)·표 레이아웃(16종) — 공용 모듈 한 줄 부착 DeeDocTools.attach(editor); // 파일 ▸ 템플릿 / 파일 ▸ 레이아웃
getHTML()·undo·재로드·외부 렌더에서 위치·형태가 그대로 보존됩니다.// 코어 포함 — plugins 에 'shape' 만 있으면 툴바 '도형 삽입' 버튼 노출 const editor = new WebEditor('#editor', { plugins: ['format', 'table', 'shape'] }); // 명령으로 도형 선택 팔레트 열기(✕·Esc·바깥클릭으로 취소) editor.execute('insertShape');
groups.media(또는 toolbar 배열)에 'insertShape'를 추가해야 버튼이 노출됩니다. 현재 회전·반전·연결선 자동앵커는 미지원(축정렬 단색 도형).
plugins: ['format', 'table', 'hwpximport'] // opt-in, 별도 번들 editor.importHwpxFile(file, { mode: 'replace', maxTableWidth: 740 });
// 파일 메뉴/툴바: Word · 한글(HWPX) · 엑셀(XLSX) · 파워포인트(PPTX) 가져오기 + 도형 삽입 const editor = new WebEditor('#editor', { plugins: ['format','image','table','wordimport','hwpximport','xlsximport','pptimport','shape'] }); await editor.importPptxFile(file, { allSlides: true });
.md 드래그앤드롭으로도 가져올 수 있습니다. <script>·javascript: 링크는 자동 차단됩니다.<!-- 코어 + 마크다운 확장팩 번들 (opt-in) --> <script src="dist/webeditor.min.js"></script> <script src="dist/plugins/markdown-native.min.js"></script> const editor = new WebEditor('#editor', { plugins: ['format', 'image', 'table', 'link', 'markdown'] }); // ① .md 파일 → 본문 await editor.importMarkdownFile(file, { mode: 'replace' }); // 'replace' | 'append' | 'cursor' // ② 마크다운 문자열 → 본문 editor.setMarkdown('# 제목\n\n**굵게**, *기울임*, `코드`, [링크](https://…)', { mode: 'replace' }); // ③ 본문(HTML) → 마크다운 문자열 const md = editor.getMarkdown(); // ④ 마크다운 → 안전 HTML (삽입 없이 렌더만) const { html, warnings } = editor.viewMarkdown(md);
getMarkdown() 출력·.md 다운로드가 있는 통합 에디터 데모를 제공합니다.// 마크다운 뷰어(뷰/편집 · 꽉채우기 모드) demo/markdown.html // 마크다운 에디터 통합(실시간 마크다운 · .md 다운로드) demo/markdown-editor.html // 지원 문법: 제목 / 강조(굵게·기울임·취소선) / 인라인 코드 / 링크 // 목록·중첩 / 체크박스([x],[ ]) / 표(GFM) / 인용 / 코드블록
// 첨부한 .docx·.hwpx·.md 를 메뉴바·아이콘바 없는 에디터 새창에서 미리보기 const ed = new WebEditor('#pv', { menubar: false, plugins: ['format','image','table','link','wordimport','hwpximport','markdown'] }); ed.showToolbar(false); // 아이콘(툴바) 숨김 ed.setHeight(window.innerHeight - 46); // 고정 높이 + 내부 스크롤 if (/\.hwpx$/i.test(name)) ed.importHwpxFile(file, { silent: true, mode: 'replace' }); else if (/\.md$/i.test(name)) ed.importMarkdownFile(file, { mode: 'replace' }); else ed.importWordFile(file, { silent: true, mode: 'replace' }); // 좁은 화면 대응: 툴바 너비 구간별 배열(반응형) + 상단 메뉴 햄버거 토글(데모) new WebEditor('#editor', { toolbarResponsive: true }); // demo/mobile.html 참고 // 툴바가 너비를 넘칠 때: 기본은 한 줄 유지 + 우측 » 팝업으로 접기(toolbarOverflow: true) // · 넘치는 아이콘을 우측 끝 » 버튼(더 보기) 팝업으로 접어 툴바 높이를 한 줄로 유지 // · 구분선(|)이 없는 긴 배열이어도, 들어가는 만큼은 항상 보이고 넘치는 만큼만 »로 접힘 // · 라이브 데모: demo/ui-config.html 하단 "좁은 폭·오버플로우(») 미리보기" 참고 new WebEditor('#editor', { toolbarOverflow: false }); // 종전처럼 줄바꿈시키려면 false
editor.on('imageInserted', ({ src, file, node }) => {}); // 이미지 DOM 부착 시 editor.on('imageUploaded', ({ src, file, node }) => {}); // 업로드 URL 확정 시 editor.on('error', ({ context, error }) => {}); // 내부 오류 수집 editor.isEmpty(); // 본문 비었는지 (공백·빈 단락은 빈 것으로, 이미지·표는 false)
uploadHandler에 모두 반영되며, 파일 선택·붙여넣기·드래그앤드롭 전 경로에 적용됩니다. 미지정 시 기존 동작과 동일합니다.// ① 600px 초과 이미지만 600px 너비로 축소 (높이는 비율대로) const editor = new WebEditor('#editor', { imageResize: { maxWidth: 600, // 이 값 이하는 원본 그대로 업로드 quality: 0.85, // JPEG·WebP 재인코딩 품질 formats: ['image/jpeg', 'image/png', 'image/webp'], onLargerResult: 'original' // 결과가 더 크면 원본 사용 }, uploadHandler: async (file) => { // 여기로 '축소된' 파일이 전달된다 const form = new FormData(); form.append('file', file, file.name); const res = await fetch('/api/upload', { method: 'POST', body: form }); return (await res.json()).url; } }); // ② 축소가 실제로 일어난 경우에만 발생하는 이벤트 editor.on('imageResized', (e) => { console.log(e.originalWidth + 'x' + e.originalHeight + ' → ' + e.width + 'x' + e.height); console.log(e.originalSize + 'B → ' + e.resizedSize + 'B'); });
formats에 GIF를 넣어도 애니메이션은 원본이 유지됩니다(정적 GIF만 축소).maxImageWidth는 보이는 크기(CSS)만 제한하는 별개 옵션입니다 — 용량을 줄이려면 imageResize를 쓰세요.
// 기본값 — 툴바: 통합 1개 / 메뉴바: 형식별 4개 new WebEditor('#editor', { importMode: 'unified', // 툴바 (기본) 'unified' | 'individual' menubarImportMode: 'individual' // 메뉴바 (기본) 'individual' | 'unified' }); // 이전처럼 툴바에도 형식별 버튼 4개를 쓰려면 new WebEditor('#editor', { importMode: 'individual' });
toolbar 배열에 wordImport·hwpxImport·xlsxImport·pptImport를
직접 나열했다면 그 버튼들이 사라집니다. importDocument 하나로 교체하거나 importMode:'individual'을 지정하세요.
uiTemplate:'ribbon') 탭에 외부 모듈이 카드를 추가할 수 있는 공개 API입니다. 중복은 자동으로 건너뛰고 해당 패널만 다시 그립니다.const editor = new WebEditor('#editor', { uiTemplate: 'ribbon' }); editor.ribbon.addCards('file', [ { icon: '#icon-doc-template', label: '템플릿', cmd: 'docTemplate' }, { icon: '#icon-toolbar', label: '레이아웃', cmd: 'docLayout' }, ], { after: ['importDocument', 'openHTML'] }); // 앵커 후보를 순서대로 탐색 // opts: { newGroup:true } 새 그룹(구분선 뒤) / { after:'cmd' | ['cmd1','cmd2'] } 지정 카드 뒤
» 로 접히는 단위가 그룹 → 아이콘 낱개로 바뀌어, 들어가는 만큼 최대한 노출됩니다. 아이콘 테마에는 solid(채움형)가 추가되어 총 8종입니다.new WebEditor('#editor', { toolbarOverflow: true, // (기본) 넘치면 » 팝업으로 접기 / false = 다음 줄로 줄바꿈 iconTheme: 'solid', // default·gray·blue·colorful·vivid·natural·modern·glass·solid iconThemePath: 'assets/icon-themes' }); editor.setIconTheme('solid'); // 런타임 전환
dist/locales/ja.js 를 함께 로드하면 됩니다(④).'ko' 고정 — 기존 코드의 렌더 결과는 이전과 완전히 동일합니다. 자동 감지는 'auto'를 명시해야 동작합니다.// 영어 UI 로 시작 const editor = new WebEditor('#editor', { language: 'en' }); // 환경 자동 감지 — <html lang> → navigator.language 순, 미등록이면 ko new WebEditor('#editor', { language: 'auto' });
'auto'는 opt-in입니다.languagechange 이벤트가 발화합니다.// 언어 선택 버튼 구현 function switchLang(lang) { editor.setLanguage(lang); // → this (체이닝 가능) } editor.on('languagechange', (lang) => { console.log('전환 완료:', lang); }); editor.getLanguage(); // 'en'
onError로 보고됩니다(화면이 깨지지 않음).
'네임스페이스.키' → 문자열 맵입니다. 에디터를 만들기 전에 호출하세요.// ① 새 언어 추가 — 코어 수정 불필요 (예: 프랑스어) WebEditor.registerLocale('fr', { 'menu.file': 'Fichier', 'menu.edit': 'Édition', 'core.placeholder': 'Saisissez votre contenu.', }); new WebEditor('#editor', { language: 'fr' }); // ※ 일본어는 직접 만들 필요 없습니다 — 완성된 팩이 동봉돼 있습니다(④) // ② 일부 문구만 우리 제품 용어로 교체 — merge 필수 WebEditor.registerLocale('ko', { 'menu.file': '문서', 'core.placeholder': '공지 내용을 입력하세요.', }, { merge: true });
merge: true를 빠뜨리면 언어팩이 통째로 교체되어 지정하지 않은 문구가 전부 폴백 처리됩니다.src/i18n.js(코어)·src/plugins/_shared/ImportLocale.js(가져오기 확장팩)에서 확인합니다 — 현재 ko·en·ja 각 636개.
<!-- 로드 순서는 상관없다 — webeditor.js 보다 먼저여도, 나중이어도 된다 --> <script src="dist/webeditor.min.js"></script> <script src="dist/locales/ja.min.js"></script> const editor = new WebEditor('#editor', { language: 'ja' }); // 문구를 우리 제품 용어로 바꿀 때 — merge 를 빠뜨리면 나머지가 전부 한글이 된다 WebEditor.registerLocale('ja', { 'menu.newDoc': '新規作成', 'guard.banned': 'NG ワード', }, { merge: true });
make-i18n-check.js 가 코어 사전과 대조해 누락을 잡습니다.licenseLang: 'ja'. 라이선스 모듈은 에디터 생성 전에도 뜰 수 있어 코어와 사전이 분리돼 있습니다.src/locales/ja.js, 전역 참조는 window.DeeEditorLocaleJA. 동작 예제는 demo/ja/index.html.dee-editor-v2.6.4.0-ja.zip — 언어팩 · 적용 예제 · 일본어 퀵스타트만 담았습니다.
// imp.* 를 덮어쓰려면 '확장팩 로드 이후'여야 병합이 유지된다 <script src="dist/webeditor.min.js"></script> <script src="dist/plugins/wordimport-native.min.js"></script> <script> WebEditor.registerLocale('ko', { 'imp.ok': '불러오기' }, { merge: true }); </script>
import WebEditor, { WebEditorLanguage, WebEditorLocalePack } from './dist/webeditor'; const ed = new WebEditor('#editor', { language: 'en' }); ed.setLanguage('ko'); ed.on('languagechange', (lang) => { /* lang: string */ }); const pack: WebEditorLocalePack = { 'menu.file': 'Fichier' }; WebEditor.registerLocale('fr', pack); // → boolean
demo/en/, 일본어 예제는 demo/ja/).