Skip to content

nv-suneditor

Vue 3 기반의 nv-suneditorSunEditor를 커스터마이징한 텍스트 에디터입니다. 이미지 업로드와 삽입, 폰트 지정, 외부 모델 연동 등의 기능을 확장하여 제공합니다.

또한, SunEditor의 기본 속성과 메서드를 그대로 사용할 수 있습니다. 자세한 속성 목록은 공식 문서를 참고하세요.

props

이름타입기본값필수 여부설명
v-modelstringnull에디터의 내용과 바인딩되는 값입니다.
v-model:hostobjectnull업로드 대상이 되는 객체입니다. (예: 게시물, 템플릿 등)
ref-domainstringnull업로드 시 파일이 소속되는 도메인 값입니다.
ref-categorystringnull업로드 시 파일이 소속되는 카테고리 값입니다.
ref-keystring numbernull업로드 파일이 연결될 대상 객체의 고유 식별자입니다. (예: 게시글 ID 등)
font-liststring[][]사용자 정의 폰트 목록을 설정할 수 있습니다.
img-upload-headerbooleanfalse이미지 업로드 영역 상단에 헤더(접기/펼치기 버튼 포함)를 표시할지 여부를 설정합니다.
기본값은 false이며, 이 값을 true로 설정하면 이미지 리스트 영역을 토글할 수 있는 버튼이 리스트 헤더에 표시됩니다.

기본

기본

  • 이미지 업로드
    전체 : 0.0KB
html
<nv-suneditor
    v-model="model.config"
    v-model:host="model"
    ref-domain="board"
    ref-category="image"
    :ref-key="model.id"
    :created-dt="model.createdDt"
    :font-list="['Do Hyeon', 'Hi Melody']"
    :img-upload-header="false"
/>
typescript
<script setup lang="ts">
import {ref} from 'vue';

const model = ref<{
    id?: string;
    config?: string;
    [key: string]: any;
}>({ id: 1, config: '초기 내용입니다.', createdDt: new Date(), refCategory: 'image', refDomain: 'board' });
</script>

INFO

에디터에서 이미지를 삽입하는 방식은 다음의 세 가지로 나뉩니다:

  1. 드래그 앤 드롭
    이미지를 에디터로 직접 드래그 → 자동으로 업로드 및 삽입 → 즉시 이미지 편집 다이얼로그가 열림

  2. 툴바의 이미지 버튼 사용
    툴바에서 이미지 버튼 클릭 → 로컬 파일 선택 → 업로드 및 자동 삽입 → 삽입 직후 편집 다이얼로그가 자동 실행

  3. 하단 이미지 업로드 목록에서 삽입
    nv-editor-image-upload를 통해 이미지 업로드 → 목록에 이미지가 표시됨 → 사용자가 원하는 시점에 삽입 가능 → 삽입 시 자동으로 편집 다이얼로그 실행

img-upload-header

  • img-upload-header prop은 에디터 하단에 표시되는 이미지 업로드 영역의 상단 헤더를 표시할지 여부를 결정합니다.
  • 이 헤더에는 토글 버튼(펼치기/접기) 과 제목 표시 영역이 포함되어 있어, 이미지 리스트를 깔끔하게 숨기거나 펼칠 수 있습니다.
  • 기본값은 false이며, true로 설정할 경우 사용자는 이미지 업로드 목록을 접거나 펼칠 수 있는 UI를 사용할 수 있게 됩니다.

기본

이미지 리스트 (0)
  • 이미지 업로드
    전체 : 0.0KB
html
<nv-suneditor
    v-model="model.config"
    v-model:host="model"
    ref-domain="board"
    ref-category="image"
    :ref-key="model.id"
    :created-dt="model.createdDt"
    :font-list="['Do Hyeon', 'Hi Melody']"
    :img-upload-header="true"
/>
typescript
<script setup lang="ts">
import {ref} from 'vue';

const model = ref<{
    id?: string;
    config?: string;
    [key: string]: any;
}>({ id: 1, config: '초기 내용입니다.', createdDt: new Date(), refCategory: 'image', refDomain: 'board' });
</script>

이미지 업로드

  • onImageUploadBefore 훅을 활용하여 커스텀 이미지 업로드 처리를 수행합니다.
  • 업로드한 이미지는 서버에서 URL을 응답받아 에디터 내에 삽입됩니다.
  • 업로드 실패 시 URL.createObjectURL()를 이용해 에디터에 이미지는 미리보기 형태로 삽입되지만, 이 이미지는 서버(또는 DB)에 저장되지 않으므로 실제로는 임시 표시용일 뿐입니다. 따라서 페이지를 새로고침하거나 다시 불러오면 해당 이미지는 사라집니다.
typescript
editorInstance.value.onImageUploadBefore = (files, info, core, uploadHandler) => {
    handleImageUpload(files, uploadHandler);
    return false;
};

const handleImageUpload = async (files, uploadHandler) => {
    const formData = new FormData();
    for (let i = 0; i < files.length; i++) {
        formData.append('files', files[i]);
    }
    formData.append('refDomain', props.refDomain);
    formData.append('refCategory', props.refCategory);
    formData.append('useFileExtension', true);
    formData.append('resultKey', 'result');

    try {
        const { data } = await api.post('/api/firo/attach/direct', formData);
        if (data?.result?.length > 0) {
            uploadHandler(data);
            data.result.forEach((it) => {
                uploadComp.value.addFileToBag(it);
            });
        } else {
            alert('이미지 업로드에 실패했습니다. 서버 응답에 URL이 없습니다.');
        }
    } catch (e) {
        alert('이미지 업로드 중 오류가 발생했습니다: ' + e.message);
        
        const fallbackImages = files.map(file => ({
            url: URL.createObjectURL(file),
            name: file.name,
            size: file.size,
        }));
        
        uploadHandler({ result: fallbackImages });
    }
};

이미지 삽입 후 자동 편집 다이얼로그 열기

  • 이미지 업로드가 완료되고 에디터에 삽입할 경우, 자동으로 편집 다이얼로그가 열립니다.
  • 편집 다이얼로그를 통해 Alternative text와 이미지 크기 등의 속성을 수정할 수 있습니다.
typescript
editorInstance.value.onImageUpload = (targetElement, index, state, info, remainingFilesCount, core) => {
  if (state === 'create' && remainingFilesCount === 0 && targetElement) {
    nextTick(() => {
      core.plugins.image.select.call(core, targetElement);
      core.plugins.image.openModify.call(core, false);
    })
  }
}

WARNING

  • 삽입된 이미지의 편집 다이얼로그를 통해 파일을 변경할 경우, 속성 내용은 변경되지 않습니다.

Font 추가

nv-suneditor에서는 @import, @font-face를 이용해 웹 폰트 또는 로컬 폰트를 추가할 수 있으며, 에디터 단위 또는 전체 에디터 공통으로 폰트를 적용할 수 있습니다.

TIP

  • font-list prop을 사용하면 기본 폰트에 추가로 원하는 폰트를 개별 에디터에만 적용할 수 있습니다.
  • 중복된 폰트 이름이 있을 경우 자동으로 병합되므로 충돌은 발생하지 않습니다.
  • 폰트는 CSS로 반드시 불러와야 에디터 내에서 적용이 가능합니다.

개별 에디터에 폰트 추가

  • 에디터를 사용하는 페이지에서 <style scoped>를 활용하여 폰트를 불러오고, :font-list 속성으로 추가할 수 있습니다.
vue
<nv-suneditor
    v-model="model.content"
    v-model:host="model"
    ref="inputArea"
    ref-target="msg-template"
    ref-target-type="content"
    :ref-target-key="model.id"
    :created-dt="model.createdDt"
    :font-list="['Do Hyeon', 'Hi Melody']"
/>

<style scoped>
    @import url('https://fonts.googleapis.com/css2?family=Do+Hyeon&display=swap');
    
    @font-face {
        font-family: 'Hi Melody';
        src: url('../../../assets/font/HiMelody-Regular.ttf') format('truetype');
    }
</style>
  • Do Hyeon: 구글 웹폰트로 불러오기
  • Hi Melody: 로컬 폰트로 직접 추가

전체 에디터 공통 폰트 추가

  • nv-suneditor.vue 컴포넌트 내부에서 전체 공통으로 사용할 폰트를 정의할 수 있습니다.
  • defaultFonts 배열을 수정하여 전체 에디터에서 사용할 기본 폰트 목록을 지정할 수 있습니다.
typescript
const defaultFonts = ['Arial', 'Comic Sans MS', 'Courier New', 'Impact', 'Georagia', 'tahoma', 'Trebuchet MS', 'Verdana', 'Noto Sans KR', 'Nanum Pen Script']
css
<style scoped>
    @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR&family=Nanum+Gothic&display=swap');
    
    @font-face {
      font-family: 'Nanum Pen Script';
      src: url('../../../assets/font/Nanum-Pen-Script.ttf') format('truetype');
    }
</style>

INFO

로컬 폰트를 사용할 경우 @font-face를 통해 해당 폰트를 등록하고, font-family 이름을 defaultFonts에 추가해야 합니다.