Froala 에디터에서 이미지 업로드 처리하기

Froala 에디터는 내가 본 위지위그 웹에디터 중에 최고라는 생각이 든다.
유료다.
그러나 로컬서버에서 개발시에는 도메인 인증이 없어도 동작한다.

부트스트랩용 웹에디터로 summernote를 사용해 왔었는데, 부트스트랩용이다 보니까 부트스트랩의 버전 업로드시마다 매번 손을 봐야 제대로 동작하는 번거로움이 있고, 아직 UX의 완성도도 떨어지는 문제점이 있다.

이런 문제로 식상해 있던 차에 페이스북 그룹에서 누군가가 Froala 에디터를 소개한 적이 있어 최근에 개발 중인 레일스 프로젝트에 적용해 보았다. 기대이상의 완성도에 만족했다. 한가지 이미지 업로드 부분은 직접 서버사이트 프로그램을 통해서 자신의 서버로 이미지를 업로드하도록 해 주어야 했다.

이글에서는 이 부분을 중점적으로 다룰 것이다.

Froala는 우선 부트스트랩과의 의존성을 가지지 않지만 부트스트랩의 스타일과 매우 잘 어울리는 UI를 가지고 있다.

image-1

레일스 프로젝트 개발시에 asset pipeline으로 사용할 수 있도록 wysiwyg-rails라는 젬도 있으니 Gemfile 에 추가하여 바로 사용할 수 있다.

gem 'wysiwyg-rails'
gem 'carrierwave', github: 'carrierwaveuploader/carrierwave'

주의 : carrierwave 젬은 이미 설치되어 있는 것으로 간주한다. 아직 설치가 되어 있지 않으면 github 문서 설명을 참고 하여 설치하면된다. 또한 carrierwave_backgrounder 젬을 사용하면 carrierwave 젬의 파일업로드 기능을 백그라운 프로세스로 처리할 수 있다.

번들 인스톨한 후, application.scss 파일에 아래의 내용을 추가한다.

@import 'froala_editor.min';
@import 'froala_style.min';
@import 'plugins/char_counter.min';
@import 'plugins/code_view.min';
@import 'plugins/colors.min';
@import 'plugins/file.min';
@import 'plugins/fullscreen.min';
@import 'plugins/image_manager.min';
@import 'plugins/image.min';
@import 'plugins/line_breaker.min';
@import 'plugins/quick_insert.min';
@import 'plugins/table.min';
@import 'plugins/video.min';
@import 'themes/gray.min';

application.js 파일에는 아래의 내용을 추가한다.

//= require froala_editor.min.js
//= require plugins/align.min.js
//= require plugins/char_counter.min.js
//= require plugins/code_beautifier.min.js
//= require plugins/code_view.min.js
//= require plugins/colors.min.js
//= require plugins/entities.min.js
//= require plugins/file.min.js
//= require plugins/font_family.min.js
//= require plugins/font_size.min.js
//= require plugins/fullscreen.min.js
//= require plugins/image.min.js
//= require plugins/image_manager.min.js
//= require plugins/inline_style.min.js
//= require plugins/line_breaker.min.js
//= require plugins/link.min.js
//= require plugins/lists.min.js
//= require plugins/paragraph_format.min.js
//= require plugins/quick_insert.min.js
//= require plugins/quote.min.js
//= require plugins/save.min.js
//= require plugins/table.min.js
//= require plugins/url.min.js
//= require plugins/video.min.js
//= require languages/ko.js

임의의 뷰 템플릿 파일을 열고 form_for 헬퍼 메소드에 아래와 같이 추가한다.

<%= f.input :content, input_html: { class: 'froala-editor' } %>

임의의 커피스크립트(.coffee.erb)파일을 열고 아래와 같이 작성한다.

$(document).on "turbolinks:load", ->
  $('.froala-editor').froalaEditor(
    language: 'ko'
    theme: 'gray'
    toolbarStickyOffset: 55
    toolbarButtons: [
      'fullscreen'
      'bold'
      'italic'
      'underline'
      'strikeThrough'
      'subscript'
      'superscript'
      'fontFamily'
      'fontSize'
      '|'
      'color'
      'inlineStyle'
      '|'
      'paragraphFormat'
      'align'
      'formatOL'
      'formatUL'
      'outdent'
      'indent'
      '-'
      'insertLink'
      'insertImage'
      'insertVideo'
      'insertFile'
      'insertTable'
      '|'
      'quote'
      'insertHR'
      'undo'
      'redo'
      'clearFormatting'
      'selectAll'
      'html'
    ]
    pluginsEnabled: null
    imageUploadParam: 'file'
    imageUploadURL: '/froala_files/upload'
    imageUploadMethod: 'POST'
    imageMaxSize: 5 * 1024 * 1024
    # imageUploadParams: id: 'my_editor'
    imageAllowedTypes: [
      'jpeg'
      'jpg'
      'png'
      'git'
    ]
    imageManagerPreloader: "<%= image_url('loader.gif') %>"
    imageManagerPageSize: 20
    imageManagerScrollOffset: 10
    imageManagerLoadURL: '/froala_files.json'
    imageManagerLoadMethod: 'GET'
    imageManagerDeleteURL: "/froala_files/destroy"
    imageManagerDeleteMethod: 'DELETE'
  ).on('froalaEditor.imageManager.error', (e, editor, error, response) ->
    console.log error
  ).on('froalaEditor.image.removed', (e, editor, $img) ->
    $.ajax(
      method: 'DELETE'
      url: "/froala_files/#{$img.attr('src').split('/').reverse()[1]}"
      data: src: $img.attr('src')).done((data) ->
        console.log "image was deleted #{$img.attr('src')}"
        return
    ).fail ->
      console.log "image delete problem #{$img.attr('src')}"
      return
    return
  ).on('froalaEditor.image.beforeUpload', (e, editor, images) ->
    # Return false if you want to stop the image upload.
    return
  ).on('froalaEditor.image.uploaded', (e, editor, response) ->
    console.log "image was uploaded #{response}"
    return
  ).on('froalaEditor.image.inserted', (e, editor, $img, response) ->
    # Image was inserted in the editor.
    return
  ).on('froalaEditor.image.replaced', (e, editor, $img, response) ->
    # Image was replaced in the editor.
    return
  ).on 'froalaEditor.image.error', (e, editor, error, response) ->    
    if error.code == 1
      console.log 'error_code_1 :  Bad link.'
    else if error.code == 2   
      console.log 'error_code_2 :  No link  in  upload response.'
    else if error.code == 3
      console.log 'error_code_3 :  Error during image upload.'
    else if error.code == 4
      console.log 'error_code_4 :  Parsing response failed .'
    else if error.code == 5
      console.log 'error_code_5 :  Image too text - large .'
    else if error.code == 6
      console.log 'error_code_6 :  Invalid image type .'
    else if error.code == 7
      console.log 'error_code_7 :  Image can be uploaded only to same domain  in  IE  8  and IE  9 .'
    # Response contains the original server response to the request if available.
    return
  return

다음은 에디터 내에서 이미지 파일을 자체 서버로 업로드하도록 파일 업로드용 전용 모델을 생성한 후 마이그레이션 한다.

$ rails generate model FroalaFile file:string

모델 클래스 파일(app/models/froala_file.rb)을 열고 아래와 같이 추가한다.

class FroalaFile < ApplicationRecord
  mount_uploader :file, FroalaUploader
end

그리고 carrierwave 젬을 사용하기 때문에 이 모델에서 사용할 업로더 클래스 파일(app/uploaders/froala_uploader.rb)를 생성한다.

# encoding: utf-8
class FroalaUploader < CarrierWave::Uploader::Base

  include CarrierWave::RMagick

  # Choose what kind of storage to use for this uploader:
  if Rails.env == 'production'
    storage :aws
  else
    storage :file
  end

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # 업로드 상단에 아래의 after 매크로를 추가한다.
  after :remove, :delete_empty_upstream_dirs

  def delete_empty_upstream_dirs
    path = ::File.expand_path(store_dir, root)
    Dir.delete(path) # fails if path not empty dir
  rescue SystemCallError
    true # nothing, the dir is not empty
  end

  # A4 size
  process :resize_to_fit => [595, 842]

  # Create different versions of your uploaded files:
  version :thumb do
    process :resize_to_fill => [120,100]
    # process :resize_to_fit => [50, 50]
  end

  # Add a white list of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  def extension_white_list
    %w(jpg jpeg gif png)
  endend

이제는 froala_files 컨트롤러를 생성하고 index, create, destroy 세 개의 액션을 생성한다.

class FroalaFilesController < ApplicationController

  def index
    @files = FroalaFile.order(created_at: :desc)
  end

  def upload
    file = FroalaFile.new(froala_file_params)
    file.save
    uploader = FroalaUploader.new
    uploader.store!(file)
    render json: { link: file.file_url, success: true }
  rescue CarrierWave::IntegrityError => e
    render json: { error: e.message }
  end

  def destroy
    file = FroalaFile.find(params[:id])
    file.destroy
    head :ok
  rescue ActiveRecord::RecordNotFound
    head :no_content
  end

  private

  def froala_file_params
    params.permit(:file)
  end

end

마지막으로 저장된 이미지 목록을 가져오기 위한 json 포맷을 생성하는 jbuilder 파일(app/views/froala_files/index.json.jbuilder)을 작성한다.

json.array!(@files) do |file|
  json.id file.id
  json.thumb file.file_url(:thumb)
  json.url file.file_url
end

config/routes.rb 파일을 열고 아래와 같이 라우트를 추가한다.

  delete 'froala_files/destroy' => 'froala_files#destroy'
  resources :froala_files, only: [:index, :create, :destroy] do
    collection do
      post :upload
    end
  end

참고로, 커맨드라인에서 위의 라우팅 결과를 확인해 볼 수 있다.

$ rake routes -c froala_files
Running via Spring preloader in process 1632
              Prefix Verb   URI Pattern                     Controller#Action
froala_files_destroy DELETE /froala_files/destroy(.:format) froala_files#destroy
 upload_froala_files POST   /froala_files/upload(.:format)  froala_files#upload
        froala_files GET    /froala_files(.:format)         froala_files#index
                     POST   /froala_files(.:format)         froala_files#create
         froala_file DELETE /froala_files/:id(.:format)     froala_files#destroy

여기서 주목할 것은 destroy 액션에 대해서 두개의 경로를 지정했다는 것이다. 에디터 내에서 이미지를 선택할 때 보이는 휴지통을 아이콘을 클릭할 때는 /froala_files/:id(.:format) URI 패턴이 적용되고, 이미지관리창에서 이미지를 삭제할 때는 /froala_files/destroy(.:format) URI 패턴이 적용되도록 하기 위함이다.

운영서버로 배포할 경우 정품 구매 후 환경변수에 FROALA_EDITOR_ACTIVATION_KEY를 등록해 주어야 제대로 에디터가 표시된다.

이상으로 최고의 위지위그 에디터 Froala의 사용법을 알아 봤다.

글쓴이: 최효성

외과전문의,웹프로그래밍,컴퓨터 일러스트레이션 / Surgeon, Medical Illustration, Web Programmer

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중