기술지원 & FAQ

언제든지 도와드립니다

도입 전 기술 검토부터 운영 중 발생하는 이슈까지,
DEXTSOLUTION 기술팀이 빠르게 응답합니다.

문의 채널

아래 채널로 문의해 주시면 영업일 기준 1일 이내에 답변드립니다.

📄

라이선스 문의

도입 견적·라이선스 유형·볼륨 할인 등 구매 관련 문의

대표문의 02-6719-6200 · 02-6719-6219 · 02-6719-6202

🛠️

기술 지원

설치·연동·버그 리포트·API 사용법 등 기술적 문의

대표문의 02-6719-6200 · 02-6719-6219 · 02-6719-6202

📋

릴리즈 노트

버전별 변경사항·신규 기능·버그 수정 내역 확인

릴리즈 노트 보기

개발자 FAQ

설치부터 고급 기능까지 — 자주 묻는 기술 질문을 카테고리별로 정리했습니다.

설치 & 기본 설정

최소 필수 파일 2개<head><body> 닫는 태그 앞에 각각 로드합니다.

<!-- ① CSS (head 안에) -->
<link rel="stylesheet" href="dist/webeditor.min.css">

<!-- ② JS (body 닫기 전) -->
<script src="dist/webeditor.min.js"></script>

<!-- Word 가져오기가 필요한 경우만 추가 -->
<script src="dist/plugins/wordimport-native.min.js"></script>
jQuery, React, Vue 등 외부 라이브러리는 불필요합니다. 순수 JavaScript로 동작합니다.

HTML에 컨테이너 요소를 추가하고, JS에서 new WebEditor()를 호출합니다.

<!-- HTML -->
<div id="editor"></div>

<!-- JavaScript -->
<script>
const editor = new WebEditor('#editor', {
  height: 400,
  placeholder: '내용을 입력하세요...'
});
</script>

컨테이너는 div 이외에도 CSS 선택자 형태 '.my-editor' 또는 DOM 요소를 직접 전달할 수 있습니다.

번들러 환경(Webpack, Vite 등)에서는 ESM 번들을 사용합니다.

// ESM (권장)
import WebEditor from './dist/webeditor.esm.js';

// CommonJS
const WebEditor = require('./dist/webeditor.cjs.js');
TypeScript 자동완성을 위해 dist/webeditor.d.ts를 함께 배치하세요. tsconfig의 typeRoots 또는 /// <reference>로 참조합니다.
  • webeditor.min.css가 정상 로드되었는지 개발자 도구 → Network 탭에서 확인
  • 컨테이너 요소(#editor)가 DOM에 존재하는 시점 이후에 초기화했는지 확인 (DOMContentLoaded 이후 권장)
  • height 옵션이 숫자(px) 또는 문자열('400px')로 전달되었는지 확인
  • 부모 요소에 display:none이 적용된 경우 초기화 후 표시해도 높이가 0이 될 수 있습니다 → 표시 후 editor.setHeight(400) 재호출

destroy()를 호출하면 DOM을 원래 상태로 복원하고 모든 이벤트 리스너를 해제합니다.

editor.destroy();
// 이후 editor 변수를 null로 해제하는 것을 권장
editor = null;

SPA(Single Page Application) 환경에서 컴포넌트 언마운트 시 반드시 호출해 메모리 누수를 방지하세요.

가능합니다. 각 컨테이너마다 독립된 인스턴스를 생성합니다.

const editor1 = new WebEditor('#editor-1', { height: 300 });
const editor2 = new WebEditor('#editor-2', { height: 200, readOnly: true });

// 정적 API로 인스턴스 접근
const model0 = WebEditor.getAPIModelByIndex(0); // 첫 번째 인스턴스
const html = model0.getHTML();
라이선스 키는 WebEditor.setLicense(key)로 전역 1회만 설정하면 모든 인스턴스에 자동 적용됩니다.
초기화 옵션

menubar: true로 기본 메뉴바를 활성화하거나, 배열로 표시할 메뉴 항목을 직접 지정합니다.

// 메뉴바 전체 표시
new WebEditor('#editor', { menubar: true });

// 특정 메뉴만 표시
new WebEditor('#editor', {
  menubar: ['file', 'edit', 'view', 'insert', 'format', 'table', 'tools']
});

툴바는 showToolbar(false) 메서드로 런타임에 숨기거나 표시할 수 있습니다.

plugins 옵션에 사용할 플러그인 이름 배열을 전달합니다.

// 내장 플러그인 선택 (9종)
new WebEditor('#editor', {
  plugins: ['format', 'image', 'table', 'link', 'emoji',
            'video', 'sourceview', 'insert', 'find']
});

// Word 가져오기 추가 (외부 번들 필요)
new WebEditor('#editor', {
  plugins: ['format', 'image', 'table', 'link',
            'emoji', 'video', 'sourceview', 'insert',
            'find', 'wordimport']
});

기본값은 <p> 단락 삽입입니다. <br>로 변경하려면:

new WebEditor('#editor', {
  enterKey: 'br'   // 기본값: 'p'
});
게시판·공지사항 환경에서는 'p' 모드를 권장합니다. 'br' 모드는 단순 메모 입력 등 단락 구조가 불필요한 경우에 적합합니다.
new WebEditor('#editor', {
  pastePlainText: true   // 모든 붙여넣기를 plain text로 처리
});

일회성 plain-paste가 필요한 경우 Ctrl+Shift+V 단축키를 사용할 수도 있습니다.

// 초기화 옵션으로 설정
new WebEditor('#editor', {
  defaultFont: 'Malgun Gothic',   // 기본: 'Malgun Gothic'
  defaultFontSize: 14             // 기본: 14 (pt 단위)
});

// 런타임에 변경
editor.setDefaultFont('나눔고딕');
editor.setDefaultFontSize(16);
new WebEditor('#editor', {
  hyperLinkDefaultTarget: '_blank'  // '_self' | '_blank' | '_top' | '_parent'
});

사용자가 링크 삽입 다이얼로그에서 target을 별도로 변경하지 않으면 이 값이 기본으로 적용됩니다.

콘텐츠 I/O & 메서드
document.getElementById('submit-btn').addEventListener('click', () => {
  const html = editor.getHTML();   // 본문 HTML 문자열

  // hidden input에 담아 전송
  document.getElementById('content-field').value = html;
  document.getElementById('my-form').submit();
});

// 순수 텍스트만 필요한 경우
const text = editor.getText();
// 초기화 후 setHTML 호출
const editor = new WebEditor('#editor', { height: 400 });

editor.on('ready', () => {
  editor.setHTML(savedHtmlFromServer);
  editor.resetModified();  // 수정 플래그 초기화 (변경 감지 오작동 방지)
});
resetModified()를 호출하지 않으면 setHTML 직후부터 isModified()가 true를 반환합니다.
if (editor.isEmpty()) {
  alert('내용을 입력해 주세요.');
  return;
}

// 또는 직접 체크
const html = editor.getHTML();
const text = editor.getText().trim();
if (!text) { /* 빈 상태 */ }

isEmpty()는 공백·줄바꿈·&nbsp; 등을 모두 무시하고 실질적인 콘텐츠가 있는지 판단합니다.

// 기본 사용 (script, on* 이벤트 제거)
const safeHtml = editor.getSafeHTML();

// 세부 옵션 지정
const safeHtml = editor.getSafeHTML({
  stripScript: true,
  allowedTags: ['p','br','strong','em','u','a','img','table','tr','td'],
  allowedAttrs: ['href','src','alt','width','height'],
  imageRewriter: (src) => {
    // base64 이미지 → CDN 경유 URL
    if (src.startsWith('data:')) return '/img/placeholder.png';
    return src;
  }
});
서버에서 DB 저장 전 또는 화면 렌더링 전에 반드시 서버 측 정제도 함께 적용하는 것을 권장합니다.
// 초기화 시 설정
const viewer = new WebEditor('#viewer', {
  height: 300,
  readOnly: true    // 키보드·마우스 입력 완전 차단
});
viewer.setHTML(savedContent);

// 런타임 전환
editor.setReadOnly(true);   // 잠금
editor.setReadOnly(false);  // 편집 허용
console.log(editor.isReadOnly()); // true/false

setter 계열 메서드는 모두 this를 반환하므로 체이닝이 가능합니다.

editor
  .setHTML('<p>안녕하세요.</p>')
  .setReadOnly(false)
  .setHeight(500)
  .setDefaultFont('나눔고딕')
  .focus();
// 탭 전환 (0=WYSIWYG, 1=HTML소스, 2=미리보기)
editor.setTab(1);   // HTML 소스 편집 탭으로 이동

// 현재 탭 확인
const tabIndex = editor.getActiveTab(); // 0 | 1 | 2

// 탭 변경 이벤트 수신
editor.on('tabChange', (index) => {
  console.log('탭 전환:', ['WYSIWYG', 'HTML', '미리보기'][index]);
});
이미지 & 파일 업로드

uploadHandler 옵션에 비동기 함수를 등록합니다. 함수가 URL을 반환하면 base64 이미지가 해당 URL로 자동 교체됩니다.

new WebEditor('#editor', {
  uploadHandler: async (file, headers) => {
    const formData = new FormData();
    formData.append('file', file);

    const resp = await fetch('/api/upload', {
      method: 'POST',
      body: formData,
      headers: headers || {}  // CSRF 헤더 자동 포함 (v2.4)
    });

    if (!resp.ok) throw new Error('업로드 실패');

    const json = await resp.json();
    return json.url;  // 반환값이 서버 URL로 교체됨
  }
});
new WebEditor('#editor', {
  // 방법 1: 쿠키에서 자동 추출
  csrfCookie: 'csrftoken',   // 쿠키명 지정

  // 방법 2: 헤더 직접 지정
  uploadHeaders: {
    'X-CSRF-Token': document.querySelector('meta[name=csrf-token]').content,
    'X-Custom-Header': 'value'
  },

  uploadHandler: async (file, headers) => {
    // headers 매개변수에 위 헤더가 자동 포함됨
    const resp = await fetch('/api/upload', {
      method: 'POST',
      body: new FormData()...,
      headers
    });
    return (await resp.json()).url;
  }
});
// 이미지가 에디터 DOM에 삽입된 직후 (base64 상태)
editor.on('imageInserted', ({ src, file, node }) => {
  console.log('삽입된 이미지:', src.substring(0, 30));
});

// uploadHandler 완료 후 URL 교체 직후
editor.on('imageUploaded', ({ src, file, node }) => {
  console.log('업로드 완료 URL:', src);  // 서버 URL
});
new WebEditor('#editor', {
  maxImageWidth: 800   // px 단위, 0이면 무제한 (기본값)
});

이미지 삽입 시 원본이 800px을 초과하면 자동으로 width="800"이 적용됩니다. height는 비율에 맞게 자동 계산됩니다.

이미지를 더블클릭하거나 선택 후 우클릭 메뉴에서 이미지 속성 다이얼로그를 열면 설정 가능합니다.

  • 캡션: <figure><img><figcaption> 구조로 생성
  • 하이퍼링크: <a href="..."><img></a> 래핑, target 속성 지원
// 코드로 이미지 삽입
editor.insertImage('https://example.com/image.jpg');
이벤트 시스템
// change 이벤트: 내용이 변경될 때마다 발생
editor.on('change', (html) => {
  // 디바운스 적용 권장 (예: 1초 지연)
  clearTimeout(window._saveTimer);
  window._saveTimer = setTimeout(() => {
    localStorage.setItem('draft', html);
  }, 1000);
});

// 또는 v2.4 내장 AutoSave 사용
new WebEditor('#editor', {
  autoSave: {
    interval: 30000,       // 30초마다
    storageKey: 'draft',
    unloadWarning: true,   // 페이지 이탈 시 경고
    onSave: (html) => console.log('자동 저장됨')
  }
});
// 리스너 등록
const handler = (html) => console.log(html);
editor.on('change', handler);

// 특정 리스너 해제
editor.off('change', handler);

// 해당 이벤트의 모든 리스너 해제
editor.off('change');
  • ready — 에디터 초기화 완료
  • change(html) — 콘텐츠 변경
  • focus / blur — 포커스 획득/해제
  • input(e) — 키 입력 직후
  • paste(e) — 붙여넣기
  • selectionchange — 선택 영역 변경
  • tabChange(index) — 탭 전환 (0/1/2)
  • resize({width, height}) — 에디터 크기 변경
  • undo / redo — 실행 취소/다시 실행
  • imageInserted({src, file, node}) — 이미지 DOM 삽입 완료
  • imageUploaded({src, file, node}) — 이미지 업로드 URL 교체 완료
  • zoom({ratio}) — 확대/축소 변경
  • error({context, error}) — 내부 오류 (업로드 실패 등)
editor.on('error', ({ context, error }) => {
  console.error(`[${context}] 오류 발생:`, error.message);

  if (context === 'upload') {
    alert('이미지 업로드에 실패했습니다. 네트워크를 확인해 주세요.');
  }
});

context 값: 'upload', 'plugin', 'license', 'autosave'

라이선스

방법 1 — 초기화 옵션 (인스턴스별 적용)

new WebEditor('#editor', {
  license: 'WED2-XXXX-XXXX-...',
  onLicenseValid:   (info)  => console.log('유효:', info.customer),
  onLicenseInvalid: (state) => console.warn('오류:', state.reason)
});

방법 2 — 전역 설정 (다중 인스턴스에 권장)

// 인스턴스 생성 전에 1회만 호출
await WebEditor.setLicense('WED2-XXXX-XXXX-...');

const editor1 = new WebEditor('#ed1');
const editor2 = new WebEditor('#ed2'); // 자동 적용
  • 'unlicensed' — 키가 없거나 미설정
  • 'invalid' — 키 형식 오류 또는 서명 불일치
  • 'expired' — 라이선스 만료일 초과
  • 'mismatch' — 현재 도메인이 라이선스 허용 도메인에 포함되지 않음
localhost, 127.0.0.1, .local 등 개발 호스트는 라이선스 체크를 건너뜁니다. 개발 환경에서는 별도 키 없이 무료로 사용할 수 있습니다.
const info = await WebEditor.getLicenseInfo();
/*
{
  valid: true,
  customer: 'ACME-001',
  domains: ['example.com', '*.example.com'],
  type: 'perm',       // 'perm'|'year'|'trial30'|'trial180'|'custom'
  expiry: null,       // 영구는 null, 기간제는 Unix timestamp
  mode: 'ecdsa'
}
*/

// 인스턴스별 상태
const state = editor._licenseState;
// { evaluation: false, devHost: true }

정식 라이선스 키를 발급받아 적용하면 자동으로 사라집니다. 개발/테스트 단계에서 임시로 비활성화하려면:

new WebEditor('#editor', {
  evaluationWatermark: false  // 워터마크 표시 비활성화 (기본값: true)
});
운영 환경에서 evaluationWatermark: false를 사용하는 것은 라이선스 계약 위반입니다. 반드시 정식 키를 구매 후 적용하세요.
고급 기능 (v2.4.0)
const editor = new WebEditor('#editor', {
  autoSave: {
    interval: 30000,             // 저장 주기 (ms), 기본 60000
    storageKey: 'draft-post-1',  // localStorage 키
    unloadWarning: true,         // 페이지 이탈 시 경고 다이얼로그
    unloadMessage: '저장하지 않은 내용이 있습니다.',
    onSave: (html) => {
      console.log('자동 저장:', new Date().toLocaleTimeString());
    }
  }
});

// 임시 저장본 복구
const draft = editor.getAutoSaved();
if (draft && confirm('임시 저장된 내용을 복구하시겠습니까?')) {
  editor.setHTML(draft);
  editor.clearAutoSaved(); // 복구 후 초기화
}

// 수동 즉시 저장
editor.saveNow();
const editor = new WebEditor('#editor', {
  // 금칙어 설정
  profanity: {
    words: ['욕설1', '욕설2'],
    maskChar: '*',              // 마스킹 문자 (기본 '*')
    onDetect: (matches) => {
      console.log('금칙어 발견:', matches);
    }
  },

  // 개인정보 검출 패턴
  privacy: {
    detect: ['ssn', 'phone', 'email', 'credit-card', 'bank-account'],
    onDetect: (matches) => {
      console.warn('개인정보 검출:', matches);
    }
  }
});

// 저장 전 일괄 검증
document.getElementById('submit-btn').onclick = async () => {
  const result = await editor.validate();

  if (result.profanity.length > 0) {
    alert(`금칙어 ${result.profanity.length}건이 발견되었습니다.`);
    return;
  }
  if (result.privacy.length > 0) {
    if (!confirm('개인정보가 포함되어 있습니다. 계속하시겠습니까?')) return;
  }

  // 마스킹 처리 후 저장
  const maskedHtml = editor.maskSensitiveData();
  submitContent(maskedHtml);
};
// 기본 미니툴바 (Bold, Italic, Underline, Link, 색상)
new WebEditor('#editor', {
  inlineToolbar: true
});

텍스트를 드래그로 선택하면 선택 영역 위에 미니툴바가 자동으로 표시됩니다. 모바일 터치 환경도 지원합니다.

// 확대/축소 설정 (0.25 ~ 4.0 범위)
editor.setZoom(1.5);   // 150%
editor.setZoom(0.75);  // 75%
editor.setZoom(1.0);   // 100% (원래 크기)

// 현재 배율 조회
const ratio = editor.getZoom(); // 예: 1.5

// 줌 변경 이벤트
editor.on('zoom', ({ ratio }) => {
  console.log('현재 배율:', Math.round(ratio * 100) + '%');
});
// 에디터 본문만 브라우저 인쇄 다이얼로그로 PDF 저장
editor.saveToPDF();

// 새 창에서 인쇄 (인쇄 후 창 자동 닫힘)
editor.saveToPDFInWindow();
브라우저의 인쇄 → PDF로 저장 기능을 이용합니다. 서버 사이드 PDF 생성이 필요한 경우 getHTML()로 콘텐츠를 추출해 Puppeteer, wkhtmltopdf 등을 활용하세요.
MS Word 가져오기

외부 플러그인 번들을 추가로 로드하고, plugins 옵션에 'wordimport'를 포함시킵니다.

<!-- 외부 번들 추가 로드 -->
<script src="dist/plugins/wordimport-native.min.js"></script>

<script>
new WebEditor('#editor', {
  plugins: ['format', 'image', 'table', 'link', 'emoji',
            'video', 'sourceview', 'insert', 'find', 'wordimport']
});
</script>

활성화 후 메뉴바의 파일 → Word 가져오기 또는 에디터 영역에 .docx 파일을 드래그하면 자동 변환됩니다.

// File 객체를 직접 전달
document.getElementById('file-input').addEventListener('change', async (e) => {
  const file = e.target.files[0];
  if (!file || !file.name.endsWith('.docx')) return;

  try {
    await editor.importWordFile(file);
    console.log('Word 가져오기 완료');
  } catch (err) {
    console.error('가져오기 실패:', err.message);
  }
});

변환 지원 항목:

  • 제목(H1~H6), 단락, 줄바꿈
  • 굵게·기울임·밑줄·취소선·글자색·형광펜
  • 표 (셀 배경색, 테두리색, 열 너비 포함)
  • 인라인 이미지 (EMU → px 변환, base64 삽입)
  • 하이퍼링크

미지원 항목:

  • SmartArt, 차트, OLE 개체
  • 복잡한 단 나누기(Multi-column) 레이아웃
  • 매크로, VBA 스크립트
.doc(구 포맷)은 지원하지 않습니다. 반드시 .docx 형식으로 저장 후 가져오세요.

해결되지 않는 문제는 기술 지원팀에 직접 문의해 주세요.

기술 지원 문의 개발 매뉴얼 보기

대표문의 02-6719-6200 · 02-6719-6219 · 02-6719-6202