UNPKG

ff-editor

Version:

Extensible WYSIWYG HTML Editor

1,785 lines (1,591 loc) 170 kB
/*! * ff-editor * https://attrs.github.io/ff-editor/ * * Copyright attrs and others * Released under the MIT license * https://github.com/attrs/ff-editor/blob/master/LICENSE */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define("ff", [], factory); else if(typeof exports === 'object') exports["ff"] = factory(); else root["ff"] = factory(); })(this, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // identity function for calling harmony imports with the correct context /******/ __webpack_require__.i = function(value) { return value; }; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 26); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { var Context = __webpack_require__(60); __webpack_require__(61)(Context); var def = Context(document); var lib = module.exports = function(doc) { if( doc instanceof Document ) { if( doc === document ) return def(doc); return doc.__tinyselector__ = doc.__tinyselector__ || Context(doc); } return def.apply(def, arguments); }; lib.fn = Context.fn; lib.util = Context.util; lib.each = Context.each; lib.create = Context.create; /***/ }), /* 1 */ /***/ (function(module, exports) { /* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ // css base code, injected by the css-loader module.exports = function(useSourceMap) { var list = []; // return the list of modules as css string list.toString = function toString() { return this.map(function (item) { var content = cssWithMappingToString(item, useSourceMap); if(item[2]) { return "@media " + item[2] + "{" + content + "}"; } else { return content; } }).join(""); }; // import a list of modules into the list list.i = function(modules, mediaQuery) { if(typeof modules === "string") modules = [[null, modules, ""]]; var alreadyImportedModules = {}; for(var i = 0; i < this.length; i++) { var id = this[i][0]; if(typeof id === "number") alreadyImportedModules[id] = true; } for(i = 0; i < modules.length; i++) { var item = modules[i]; // skip already imported module // this implementation is not 100% perfect for weird media query combinations // when a module is imported multiple times with different media queries. // I hope this will never occur (Hey this way we have smaller bundles) if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) { if(mediaQuery && !item[2]) { item[2] = mediaQuery; } else if(mediaQuery) { item[2] = "(" + item[2] + ") and (" + mediaQuery + ")"; } list.push(item); } } }; return list; }; function cssWithMappingToString(item, useSourceMap) { var content = item[1] || ''; var cssMapping = item[3]; if (!cssMapping) { return content; } if (useSourceMap && typeof btoa === 'function') { var sourceMapping = toComment(cssMapping); var sourceURLs = cssMapping.sources.map(function (source) { return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */' }); return [content].concat(sourceURLs).concat([sourceMapping]).join('\n'); } return [content].join('\n'); } // Adapted from convert-source-map (MIT) function toComment(sourceMap) { // eslint-disable-next-line no-undef var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))); var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64; return '/*# ' + data + ' */'; } /***/ }), /* 2 */ /***/ (function(module, exports, __webpack_require__) { /* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ var stylesInDom = {}, memoize = function(fn) { var memo; return function () { if (typeof memo === "undefined") memo = fn.apply(this, arguments); return memo; }; }, isOldIE = memoize(function() { // Test for IE <= 9 as proposed by Browserhacks // @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805 // Tests for existence of standard globals is to allow style-loader // to operate correctly into non-standard environments // @see https://github.com/webpack-contrib/style-loader/issues/177 return window && document && document.all && !window.atob; }), getElement = (function(fn) { var memo = {}; return function(selector) { if (typeof memo[selector] === "undefined") { memo[selector] = fn.call(this, selector); } return memo[selector] }; })(function (styleTarget) { return document.querySelector(styleTarget) }), singletonElement = null, singletonCounter = 0, styleElementsInsertedAtTop = [], fixUrls = __webpack_require__(46); module.exports = function(list, options) { if(typeof DEBUG !== "undefined" && DEBUG) { if(typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment"); } options = options || {}; options.attrs = typeof options.attrs === "object" ? options.attrs : {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of <style> // tags it will allow on a page if (typeof options.singleton === "undefined") options.singleton = isOldIE(); // By default, add <style> tags to the <head> element if (typeof options.insertInto === "undefined") options.insertInto = "head"; // By default, add <style> tags to the bottom of the target if (typeof options.insertAt === "undefined") options.insertAt = "bottom"; var styles = listToStyles(list, options); addStylesToDom(styles, options); return function update(newList) { var mayRemove = []; for(var i = 0; i < styles.length; i++) { var item = styles[i]; var domStyle = stylesInDom[item.id]; domStyle.refs--; mayRemove.push(domStyle); } if(newList) { var newStyles = listToStyles(newList, options); addStylesToDom(newStyles, options); } for(var i = 0; i < mayRemove.length; i++) { var domStyle = mayRemove[i]; if(domStyle.refs === 0) { for(var j = 0; j < domStyle.parts.length; j++) domStyle.parts[j](); delete stylesInDom[domStyle.id]; } } }; }; function addStylesToDom(styles, options) { for(var i = 0; i < styles.length; i++) { var item = styles[i]; var domStyle = stylesInDom[item.id]; if(domStyle) { domStyle.refs++; for(var j = 0; j < domStyle.parts.length; j++) { domStyle.parts[j](item.parts[j]); } for(; j < item.parts.length; j++) { domStyle.parts.push(addStyle(item.parts[j], options)); } } else { var parts = []; for(var j = 0; j < item.parts.length; j++) { parts.push(addStyle(item.parts[j], options)); } stylesInDom[item.id] = {id: item.id, refs: 1, parts: parts}; } } } function listToStyles(list, options) { var styles = []; var newStyles = {}; for(var i = 0; i < list.length; i++) { var item = list[i]; var id = options.base ? item[0] + options.base : item[0]; var css = item[1]; var media = item[2]; var sourceMap = item[3]; var part = {css: css, media: media, sourceMap: sourceMap}; if(!newStyles[id]) styles.push(newStyles[id] = {id: id, parts: [part]}); else newStyles[id].parts.push(part); } return styles; } function insertStyleElement(options, styleElement) { var styleTarget = getElement(options.insertInto) if (!styleTarget) { throw new Error("Couldn't find a style target. This probably means that the value for the 'insertInto' parameter is invalid."); } var lastStyleElementInsertedAtTop = styleElementsInsertedAtTop[styleElementsInsertedAtTop.length - 1]; if (options.insertAt === "top") { if(!lastStyleElementInsertedAtTop) { styleTarget.insertBefore(styleElement, styleTarget.firstChild); } else if(lastStyleElementInsertedAtTop.nextSibling) { styleTarget.insertBefore(styleElement, lastStyleElementInsertedAtTop.nextSibling); } else { styleTarget.appendChild(styleElement); } styleElementsInsertedAtTop.push(styleElement); } else if (options.insertAt === "bottom") { styleTarget.appendChild(styleElement); } else { throw new Error("Invalid value for parameter 'insertAt'. Must be 'top' or 'bottom'."); } } function removeStyleElement(styleElement) { styleElement.parentNode.removeChild(styleElement); var idx = styleElementsInsertedAtTop.indexOf(styleElement); if(idx >= 0) { styleElementsInsertedAtTop.splice(idx, 1); } } function createStyleElement(options) { var styleElement = document.createElement("style"); options.attrs.type = "text/css"; attachTagAttrs(styleElement, options.attrs); insertStyleElement(options, styleElement); return styleElement; } function createLinkElement(options) { var linkElement = document.createElement("link"); options.attrs.type = "text/css"; options.attrs.rel = "stylesheet"; attachTagAttrs(linkElement, options.attrs); insertStyleElement(options, linkElement); return linkElement; } function attachTagAttrs(element, attrs) { Object.keys(attrs).forEach(function (key) { element.setAttribute(key, attrs[key]); }); } function addStyle(obj, options) { var styleElement, update, remove, transformResult; // If a transform function was defined, run it on the css if (options.transform && obj.css) { transformResult = options.transform(obj.css); if (transformResult) { // If transform returns a value, use that instead of the original css. // This allows running runtime transformations on the css. obj.css = transformResult; } else { // If the transform function returns a falsy value, don't add this css. // This allows conditional loading of css return function() { // noop }; } } if (options.singleton) { var styleIndex = singletonCounter++; styleElement = singletonElement || (singletonElement = createStyleElement(options)); update = applyToSingletonTag.bind(null, styleElement, styleIndex, false); remove = applyToSingletonTag.bind(null, styleElement, styleIndex, true); } else if(obj.sourceMap && typeof URL === "function" && typeof URL.createObjectURL === "function" && typeof URL.revokeObjectURL === "function" && typeof Blob === "function" && typeof btoa === "function") { styleElement = createLinkElement(options); update = updateLink.bind(null, styleElement, options); remove = function() { removeStyleElement(styleElement); if(styleElement.href) URL.revokeObjectURL(styleElement.href); }; } else { styleElement = createStyleElement(options); update = applyToTag.bind(null, styleElement); remove = function() { removeStyleElement(styleElement); }; } update(obj); return function updateStyle(newObj) { if(newObj) { if(newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) return; update(obj = newObj); } else { remove(); } }; } var replaceText = (function () { var textStore = []; return function (index, replacement) { textStore[index] = replacement; return textStore.filter(Boolean).join('\n'); }; })(); function applyToSingletonTag(styleElement, index, remove, obj) { var css = remove ? "" : obj.css; if (styleElement.styleSheet) { styleElement.styleSheet.cssText = replaceText(index, css); } else { var cssNode = document.createTextNode(css); var childNodes = styleElement.childNodes; if (childNodes[index]) styleElement.removeChild(childNodes[index]); if (childNodes.length) { styleElement.insertBefore(cssNode, childNodes[index]); } else { styleElement.appendChild(cssNode); } } } function applyToTag(styleElement, obj) { var css = obj.css; var media = obj.media; if(media) { styleElement.setAttribute("media", media) } if(styleElement.styleSheet) { styleElement.styleSheet.cssText = css; } else { while(styleElement.firstChild) { styleElement.removeChild(styleElement.firstChild); } styleElement.appendChild(document.createTextNode(css)); } } function updateLink(linkElement, options, obj) { var css = obj.css; var sourceMap = obj.sourceMap; /* If convertToAbsoluteUrls isn't defined, but sourcemaps are enabled and there is no publicPath defined then lets turn convertToAbsoluteUrls on by default. Otherwise default to the convertToAbsoluteUrls option directly */ var autoFixUrls = options.convertToAbsoluteUrls === undefined && sourceMap; if (options.convertToAbsoluteUrls || autoFixUrls){ css = fixUrls(css); } if(sourceMap) { // http://stackoverflow.com/a/26603875 css += "\n/*# sourceMappingURL=data:application/json;base64," + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + " */"; } var blob = new Blob([css], { type: "text/css" }); var oldSrc = linkElement.href; linkElement.href = URL.createObjectURL(blob); if(oldSrc) URL.revokeObjectURL(oldSrc); } /***/ }), /* 3 */ /***/ (function(module, exports, __webpack_require__) { var $ = __webpack_require__(0); function Items(arr) { this._ = {}; if( $.util.isArrayLike(arr) ) { var self = this; [].forEach.call(arr, function(item) { self.add(item); }); } } var proto = Items.prototype = []; proto.push = function() { var self = this; [].forEach.call(arguments, function(item) { if( item && item.id ) self._[item.id] = item; }); return [].push.apply(this, arguments); }; proto.add = function(item) { if( $.util.isArrayLike(item) ) { var self = this; [].forEach.call(item, function(item) { self.push(item); }); } else { this.push(item); } return this; }; proto.get = function(id) { return this._[id]; }; proto.remove = function(item) { if( ~['string', 'number'].indexOf(typeof item) ) item = this._[item]; if( !item ) return this; for(var pos;~(pos = this.indexOf(item));) this.splice(pos, 1); return this; }; proto.clear = function() { this.splice(0, this.length); return this; }; module.exports = Items; /***/ }), /* 4 */ /***/ (function(module, exports, __webpack_require__) { var $ = __webpack_require__(0); var context = __webpack_require__(6); function rangeitem(id, text, tooltip, selector, fn) { return { id: id, text: text, tooltip: tooltip, onupdate: function(btn) { var range = this.range(); if( !range ) return btn.enable(false); btn.enable(true); btn.active(range.iswrapped(selector)); }, fn: fn || function(btn) { var part = this; var range = part.range(); if( !range ) return; range.togglewrap(selector); part.history().save(); } }; } module.exports = { insert: { heading: { id: 'insert.heading', text: '<i class="fa fa-header"></i>', tooltip: 'Heading', fn: function(e) { var placeholder = $(this.dom()).attr('placeholder'); this.insert(new context.Heading().placeholder(placeholder)); } }, paragraph: { id: 'insert.paragraph', text: '<i class="fa fa-font"></i>', tooltip: '문단', fn: function(e) { var placeholder = $(this.dom()).attr('placeholder'); this.insert(new context.Paragraph().placeholder(placeholder)); } }, imagefile: { id: 'insert.imagefile', text: '<i class="fa fa-picture-o"></i>', tooltip: '이미지 파일', fn: function(e) { var part = this; part.context().selectFiles(function(err, files) { if( err ) return context.error(err); if( !files.length ) return; part.insert(files); }, { upload: false, type: 'image' }); } }, image: { id: 'insert.image', text: '<i class="fa fa-instagram"></i>', tooltip: '이미지', fn: function(e) { var part = this; context.prompt('Please enter the image URL.', function(src) { src && part.insert(new context.Image(src)); }); } }, video: { id: 'insert.video', text: '<i class="fa fa-youtube-square"></i>', tooltip: '동영상', fn: function(e) { var part = this; context.prompt('Please enter the video URL', function(src) { src && part.insert(new context.Video(src)); }); } }, separator: { id: 'insert.separator', text: '<i class="fa fa-minus"></i>', tooltip: '구분선', fn: function(e) { this.insert(new context.Separator()); } }, link: { id: 'insert.link', text: '<i class="fa fa-link"></i>', tooltip: '링크', fn: function(e) { var part = this; context.prompt('Please enter the anchor URL', function(src) { src && part.insert(new context.Link(src)); }); } }, attach: { id: 'insert.attach', text: '<i class="fa fa-paperclip"></i>', tooltip: '첨부파일', fn: function(e) { var part = this; part.context().selectFile(function(err, file) { if( err ) return context.error(err); part.insert(new context.Link(file)); }); } } }, clear: { id: 'clear', text: '<i class="fa fa-eraser"></i>', tooltip: '내용 삭제', fn: function(e) { this.clear(); } }, heading: { id: 'heading', type: 'list', text: '<i class="fa fa-header"></i>', tooltip: 'Select Heading', onselect: function(item, i, btn) { var part = this; var dom = part.dom(); if( dom.tagName.toLowerCase() !== item.tag ) { var el = $(part.dom()); var parentpart = part.parent(); var newpart = new context.Heading({ tag: item.tag, html: el.html() }); el.after(newpart.dom()).remove(); newpart.focus(); parentpart && parentpart.history().save(); } }, items: [ { text: '<h1>Title</h1>', tag: 'h1' }, { text: '<h2>Title</h2>', tag: 'h2' }, { text: '<h3>Title</h3>', tag: 'h3' }, { text: '<h4>Title</h4>', tag: 'h4' }, { text: '<h5>Title</h5>', tag: 'h5' }, { text: '<h6>Title</h6>', tag: 'h6' } ] }, align: { id: 'align', text: '<i class="fa fa-align-justify"></i>', tooltip: '정렬', onupdate: function(btn) { if( btn.align == 'center' ) btn.text('<i class="fa fa-align-center"></i>'); else if( btn.align == 'right' ) btn.text('<i class="fa fa-align-right"></i>'); else if( btn.align == 'justify' ) btn.text('<i class="fa fa-align-justify"></i>'); else btn.text('<i class="fa fa-align-left"></i>'); }, fn: function(btn) { var part = this; var el = $(part.dom()); if( btn.align == 'center' ) { el.css('text-align', 'right'); btn.align = 'right'; } else if( btn.align == 'right' ) { el.css('text-align', 'justify'); btn.align = 'justify'; } else if( btn.align == 'justify' ) { el.css('text-align', ''); btn.align = ''; } else { el.css('text-align', 'center'); btn.align = 'center'; } part.history().save(); } }, draggable: { id: 'draggable', text: '<i class="fa fa-hand-pointer-o"></i>', tooltip: '요소이동', onupdate: function(btn) { var part = this; var el = $(part.dom()); if( el.ha('draggable') ) btn.active(true); else btn.active(false); }, fn: function() { var part = this; part.dragmode(!part.dragmode()); } }, clearfix: { id: 'clearfix', text: '<i class="fa fa-asterisk"></i>', tooltip: '클리어픽스', onupdate: function(btn) { btn.active($(this.dom()).hc('f_clearfix')); }, fn: function() { $(this.dom()).tc('f_clearfix'); this.history().save(); } }, remove: { id: 'remove', text: '<i class="fa fa-remove"></i>', onupdate: function(btn) { if( this.removable() ) btn.show(); else btn.hide(); }, fn: function() { this.remove(); } }, target: { id: 'target', text: '<i class="fa fa-external-link"></i>', tooltip: '새창으로', onupdate: function(btn) { var el = $(this.dom()); if( el.find('a').attr('target') ) btn.active(true); else btn.active(false); }, fn: function() { this.target(!this.target() ? '_blank' : null); } }, separator: { shape: { id: 'separator.shape', text: '<i class="fa fa-chevron-up"></i>', tooltip: '모양', onupdate: function(btn) { var part = this; var shape = part.shape(); if( shape == 'dotted' ) btn.text('<i class="fa fa-ellipsis-h"></i>'); else if( shape == 'dashed' ) btn.text('<i class="fa fa-ellipsis-h"></i>'); else if( shape == 'zigzag' ) btn.text('<i class="fa fa-chevron-up"></i>'); else if( shape == 'line' ) btn.text('<i class="fa fa-minus"></i>'); else btn.text('<i class="fa fa-minus"></i>'); }, fn: function(btn) { var part = this; var shape = part.shape(); if( shape == 'dotted' ) part.shape('dashed'); else if( shape == 'dashed' ) part.shape('zigzag'); else if( shape == 'zigzag' ) part.shape('line'); else if( shape == 'line' ) part.shape(false); else part.shape('dotted'); part.history().save(); } }, width: { id: 'separator.width', text: '<i class="fa fa-arrows-h"></i>', tooltip: '너비', onupdate: function(btn) { var part = this; var el = $(part.dom()); if( part.shape() === false ) return btn.hide(); btn.show(); if( el.hc('f_sep_narrow') ) btn.text('<i class="fa fa-minus"></i>'); else btn.text('<i class="fa fa-arrows-h"></i>'); }, fn: function(e) { var part = this; var el = $(part.dom()); el.tc('f_sep_narrow'); this.history().save(); } } }, row: { valign: { id: 'row.valign', text: '<i class="fa fa-align-justify"></i>', tooltip: '정렬', onupdate: function(btn) { var part = this; var valign = part.valign(); if( valign == 'middle' ) btn.text('<i class="fa fa-align-center ff-vert"></i>'); else if( valign == 'bottom' ) btn.text('<i class="fa fa-align-left ff-vert"></i>'); else if( valign == 'justify' ) btn.text('<i class="fa fa-align-justify ff-vert"></i>'); else btn.text('<i class="fa fa-align-right ff-vert"></i>'); }, fn: function(btn) { var part = this; var valign = part.valign(); if( valign == 'middle' ) part.valign('bottom'); else if( valign == 'bottom' ) part.valign('justify'); else if( valign == 'justify' ) part.valign(false); else part.valign('middle'); part.history().save(); } } }, image: { floatleft: { id: 'image.floatleft', text: '<i class="fa fa-dedent"></i>', tooltip: '좌측플로팅', fn: function(btn) { this.floating('left').history().save(); } }, floatright: { id: 'image.floatright', text: '<i class="fa fa-dedent ff-flip"></i>', tooltip: '우측플로팅', fn: function(btn) { this.floating('right').history().save(); } }, size: { id: 'image.size', text: '<i class="fa fa-circle-o"></i>', tooltip: '크기변경', onupdate: function(btn) { var part = this; var blockmode = part.blockmode(); if( blockmode == 'natural' ) btn.text('<i class="fa fa-square-o"></i>'); else if( blockmode == 'medium' ) btn.text('<i class="fa fa-arrows-alt"></i>'); else if( blockmode == 'full' ) btn.text('<i class="fa fa-circle-o"></i>'); else btn.text('<i class="fa fa-align-center"></i>'); }, fn: function(btn) { var part = this; var blockmode = part.blockmode(); if( blockmode == 'natural' ) part.blockmode('medium'); else if( blockmode == 'medium' ) part.blockmode('full'); else if( blockmode == 'full' ) part.blockmode(false); else part.blockmode('natural'); part.history().save(); } }, upload: { id: 'image.upload', text: '<i class="fa fa-file-image-o"></i>', tooltip: '사진변경(업로드)', fn: function() { var part = this; context.selectFile(function(err, file) { if( err ) return context.error(err); if( !file ) return; part.src(file.src).title(file.name); part.history().save(); }); } }, change: { id: 'image.change', text: '<i class="fa fa-instagram"></i>', tooltip: '사진변경', fn: function() { var part = this; context.prompt('Please enter the image URL.', function(src) { src && part.src(src).title(null); part.history().save(); }); } }, align: { id: 'image.align', text: '<i class="fa fa-align-justify"></i>', tooltip: '정렬', onupdate: function(btn) { var part = this; var el = $(part.figcaption()); var align = el.css('text-align'); if( part.isFigure() ) btn.show(); else btn.hide(); if( align == 'right' ) btn.text('<i class="fa fa-align-right"></i>'); else if( align == 'left' ) btn.text('<i class="fa fa-align-left"></i>'); else btn.text('<i class="fa fa-align-center"></i>'); }, fn: function(btn) { var part = this; var el = $(part.figcaption()); var align = el.css('text-align'); if( align == 'right' ) { el.css('text-align', 'left'); } else if( align == 'left' ) { el.css('text-align', null); } else { el.css('text-align', 'right'); } part.history().save(); } }, caption: { id: 'image.caption', text: '<i class="fa fa-text-width"></i>', onupdate: function(btn) { if( this.isFigure() ) btn.active(true); else btn.active(false); }, tooltip: '캡션', fn: function() { this.caption(!this.isFigure()); } } }, video: { size: { id: 'video.size', text: '<i class="fa fa-circle-o"></i>', tooltip: '크기변경', onupdate: function(btn) { var el = $(this.dom()); if( el.hc('f_video_fit') ) btn.text('<i class="fa fa-circle-o"></i>'); else btn.text('<i class="fa fa-arrows-alt"></i>'); }, fn: function() { var el = $(this.dom()); if( el.hc('f_video_fit') ) el.rc('f_video_fit').ac('f_video_narrow'); else el.rc('f_video_narrow').ac('f_video_fit'); } } }, paragraph: { font: { id: 'paragraph.font', type: 'list', text: '<i class="fa fa-font"></i>', tooltip: 'Select Font', onupdate: function(btn) { var range = this.range(); range && btn.active(range.iswrapped('span.f_txt_font')); }, onselect: function(item, i, btn) { var part = this; var dom = part.dom(); var range = part.range(); if( !range ) { dom.style.fontFamily = item.font || ''; part.history().save(); return; } range.unwrap('span.f_txt_font'); var node = range.wrap('span.f_txt_font'); node.style.fontFamily = item.font || ''; part.history().save(); }, items: function() { return context.fonts(); } }, fontsize: { id: 'paragraph.fontsize', type: 'list', text: '<i class="fa fa-text-height"></i>', tooltip: 'Select Font Size', onupdate: function(btn) { var range = this.range(); range && btn.active(range.iswrapped('span.f_txt_fontsize')); }, onselect: function(item, i, btn) { var part = this; var dom = part.dom(); var range = part.range(); if( !range ) { dom.style.fontSize = item.size || ''; part.history().save(); return; } range.unwrap('span.f_txt_fontsize'); var node = range.wrap('span.f_txt_fontsize'); node.style.fontSize = item.size || ''; part.history().save(); }, items: [ { text: 'Default' }, { text: '<span style="font-size:11px;">11px</span>', size: '11px' }, { text: '<span style="font-size:12px;">12px</span>', size: '12px' }, { text: '<span style="font-size:14px;">14px</span>', size: '14px' }, { text: '<span style="font-size:16px;">16px</span>', size: '16px' }, { text: '<span style="font-size:18px;">18px</span>', size: '18px' }, { text: '<span style="font-size:20px;">20px</span>', size: '20px' } ] }, color: { id: 'paragraph.color', type: 'list', text: '<i class="fa fa-square"></i>', tooltip: 'Select Color', onupdate: function(btn) { var range = this.range(); range && btn.active(range.iswrapped('span.f_txt_color')); }, onselect: function(item, i, btn) { var part = this; var dom = part.dom(); var range = part.range(); var change = function(color) { if( !range ) { dom.style.color = color || ''; return; } range.unwrap('span.f_txt_color'); var node = range.wrap('span.f_txt_color'); node.style.color = color || ''; } if( item.id == 'picker' ) { $('<input type="color">').on('change', function() { if( this.value ) change(this.value); }).click(); } else { change(item.color); part.history().save(); } }, items: function() { var colors = context.colors().slice(); colors.push({ id: 'picker', text: 'Select Color' }); return colors; } }, bold: rangeitem('paragraph.bold', '<i class="fa fa-bold"></i>', '굵게', 'b'), underline: rangeitem('paragraph.underline', '<i class="fa fa-underline"></i>', '밑줄', 'u'), italic: rangeitem('paragraph.italic', '<i class="fa fa-italic"></i>', '이탤릭', 'i'), strike: rangeitem('paragraph.strike', '<i class="fa fa-strikethrough"></i>', '가로줄', 'strike'), anchor: rangeitem('paragraph.anchor', '<i class="fa fa-link"></i>', '링크', 'a', function(e) { var part = this; var range = part.range(); if( !range || range.iswrapped('a') ) return range.unwrap('a'); context.prompt('Please enter the anchor URL.', function(href) { if( !href ) return; var a = range.wrap('a'); a.href = href; a.target = '_blank'; part.history().save(); }); }) } }; /***/ }), /* 5 */ /***/ (function(module, exports, __webpack_require__) { var $ = __webpack_require__(0); var Toolbar = __webpack_require__(28); Toolbar.Button = __webpack_require__(8); Toolbar.Separator = __webpack_require__(13); Toolbar.ListButton = __webpack_require__(12); Toolbar.update = function() { $('.ff-toolbar').each(function(i, el) { var toolbar = el.toolbar; toolbar && toolbar.update(); }); }; module.exports = Toolbar; /***/ }), /* 6 */ /***/ (function(module, exports, __webpack_require__) { var $ = __webpack_require__(0); var types = __webpack_require__(14); var Items = __webpack_require__(3); var RangeEditor = __webpack_require__(11); var history = __webpack_require__(25); var RangeEditor = __webpack_require__(11); var win = window, doc = document, editmode = false, data = {}, uploader, fonts = new Items(), colors = new Items(), fileinput = $('<input type="file">'), filechangeevent; var context = { scan: function(fn, all) { context.parts.apply(context, arguments); return context; }, parts: function(fn, all) { if( fn === true ) all = fn; return $(all ? '.ff [ff-id], [ff], [ff-type]' : '[ff-id], [ff], [ff-type]').reverse().each(function(i, el) { var id = el.getAttribute('ff-id'); var type = el.getAttribute('ff-type') || el.getAttribute('ff') || 'default'; var part = el._ff; if( !part ) { var Type = types.get(type); if( !Type ) return console.warn('[ff] not found type: ' + type); part = el._ff = new Type(el); id && data[id] && part.data(data[id]); } part.id = id; typeof fn == 'function' && fn.call(context, part); }).slice(); }, data: function(newdata, all) { if( !arguments.length ) { newdata = {}; context.parts(function(part) { if( part.id ) newdata[part.id] = part.data(); }); return newdata; } data = newdata || {}; $('[ff-id]').reverse().each(function(i, el) { var id = el.getAttribute('ff-id'); var type = el.getAttribute('ff-type') || el.getAttribute('ff') || 'default'; var part = el._ff; if( !part ) { var Type = types.get(type); if( !Type ) return console.warn('[ff] not found type: ' + type); part = new Type(el); } if( data[id] ) part.data(data[id]); else if( all === true ) part.data(null); }); context.fire('ff-data', { data: newdata }); return context; }, clear: function() { context.data(null, true).fire('ff-clear'); return context; }, reset: function() { console.warn('[ff] ff.reset is deprecated, use ff.data instead'); return context.data.apply(context, arguments); }, get: function() { console.warn('[ff] ff.get is deprecated, use ff.partof instead'); return context.partof.apply(context, arguments); }, part: function(id) { var el = $('[ff-id="' + id + '"]'); return el[0] && el[0]._ff; }, partof: function(node) { var found = $(node).parent(function() { return this._ff; }, true)[0]; return found && found._ff; }, partsof: function(node) { var parents = []; $(node).parent(function() { var part = this._ff; if( part ) parents.push(part); }, true); return parents; }, editmode: function(b) { if( !arguments.length ) return editmode; if( editmode === !!b ) return context; editmode = !!b; context.parts(function(part) { part.editmode(editmode); }, true); context.fire('ff-modechange', { editmode: editmode }); return context; }, // event fire: function(type, detail, cancellable, bubble) { return !!$(doc).fire(type, detail, cancellable, bubble)[0]; }, on: function(type, fn) { fn._wrapper = function() { return fn.apply(context, arguments); }; $(doc).on(type, fn._wrapper); return this; }, once: function(type, fn) { fn._wrapper = function() { return fn.apply(context, arguments); }; $(doc).once(type, fn._wrapper); return this; }, off: function(type, fn) { $(doc).off(type, fn._wrapper || fn); return this; }, // part type types: function() { return types; }, type: function(name, cls) { if( arguments.length <= 1 ) return types.get(name); types.define(name, cls); return context; }, // uploader uploader: function(fn) { if( !fn || typeof fn !== 'function' ) throw new TypeError('uploader must be a function'); uploader = fn; return context; }, upload: function(file, done) { uploader.call(context, file, function(err, result) { if( err ) context.fire('ff-upload-error', {error: err}); if( err ) return done && done(err); context.fire('ff-upload', {result:result}); done && done(null, result); }); return context; }, selectFiles: function(done, options) { options = options || {}; if( typeof options == 'boolean' ) options = {upload:options}; if( typeof options == 'string' ) options = {type:options}; if( typeof options == 'number' ) options = {limit:options}; var type = options.type; var upload = options.upload === false ? false : true; var limit = options.limit; var prevfilechangeevent = filechangeevent filechangeevent = function() { var files = [].slice.call(this.files); if( limit && files.length ) files = files.slice(0, limit); if( type ) { var tmp = []; files.forEach(function(file) { if( file.type && !file.type.indexOf(type) ) tmp.push(file); }); //console.log('files', files, tmp); files = tmp; } if( !upload ) return done(null, files); var srcs = []; $(files).async(function(file, done) { context.upload(file, function(err, src) { if( err ) return done(err); srcs.push(src); done(); }); }, function(err) { if( err ) return done(err); done(null, srcs); }); }; fileinput.value('').attr('multiple', limit === 1 ? null : '') .off('change', prevfilechangeevent) .on('change', filechangeevent).click(); return context; }, selectFile: function(done, options) { options = options || {}; options.limit = 1; return context.selectFiles(function(err, arr) { if( err ) return done(err); done(null, arr && arr[0]); }, options); }, // alert prompt: function(message, callback, options) { if( context.fire('ff-prompt', { message: message, callback: callback, options: options }, true) ) { callback && callback.call(context, prompt(message)); } return context; }, confirm: function(message, callback, options) { if( context.fire('ff-prompt', { message: message, callback: callback, options: options }, true) ) { callback && callback.call(context, confirm(message)); } return context; }, alert: function(message, callback, options) { if( context.fire('ff-alert', { message: message, callback: callback, options: options }, true) ) { callback && callback.call(context); alert(message); } return context; }, error: function(error, callback, options) { if( typeof error == 'string' ) error = new Error(error); if( context.fire('ff-error', { error: error, message: error.message, callback: callback, options: options }, true) ) { callback && callback.call(context); console.error(error); alert(error.message); } return context; }, // undo/redo history: function(fn) { if( !arguments.length ) return history; history.add(fn); return this; }, // range ranges: function(node, collapsed) { var selection = win.getSelection(); var ranges = []; if( selection.rangeCount ) for(var i=0; i < selection.rangeCount; i++) ranges.push(selection.getRangeAt(i)); if( !arguments.length ) return ranges; return ranges.filter(function(range) { if( !collapsed && range.collapsed ) return; return range && node.contains(range.startContainer) && node.contains(range.endContainer) && RangeEditor(range); }); }, range: function(node, collapsed) { var ranges = context.ranges(node, collapsed); return ranges && ranges.length && RangeEditor(ranges[ranges.length - 1]); }, // defaults fonts: function(arr) { if( !arguments.length ) return fonts; fonts = new Items(arr); return context; }, colors: function(arr) { if( !arguments.length ) return colors; colors = new Items(arr); return context; } }; $(doc).on('mousedown', function(e) { if( !editmode ) return; var target = e.target || e.srcElement; var part = context.partof(target); var focused = context.focused; if( part ) part.focus(); else focused && focused.blur(); }); module.exports = context; /***/ }), /* 7 */ /***/ (function(module, exports, __webpack_require__) { var Types = __webpack_require__(14); var Toolbar = __webpack_require__(5); var $ = __webpack_require__(0); var context = __webpack_require__(6); var Items = __webpack_require__(3); var tools = __webpack_require__(4); function Part(arg) { var dom = arg; if( dom && dom._ff ) return dom._ff; if( !(this instanceof Part) ) return null; if( !dom || !$.util.isElement(dom) ) dom = this.create.apply(this, arguments); if( !$.util.isElement(dom) ) throw new TypeError('illegal arguments: dom'); var self = dom._ff = this; var el = $(self._n = dom).ac('ff') .on('ff-data ff-focus ff-blur ff-modechange mouseenter mouseleave mouseup mousedown dragstart dragend', self); // regist event in prototypes (function() { var o = self, prototypes = []; do { if( o === Part.prototype ) break; prototypes.push(o); } while(o = Object.getPrototypeOf(o)); prototypes.reverse().forEach(function(o) { Object.getOwnPropertyNames(o).forEach(function(name) { if( !~['on', 'once'].indexOf(name) && !name.indexOf('on') ) self.on('ff-' + name.substring(2), self[name]); }); }); })(); if( dom !== arg ) self.removable(true); var toolbar = self.toolbar(); Part.toolbar.forEach(function(item) { toolbar.last(item); }); var toolbarposition = el.attr('ff-toolbar'); if( toolbarposition === 'true' ) toolbarposition = null; if( toolbarposition === 'false' ) toolbar.enable(false); else if( toolbarposition ) toolbar.position(toolbarposition); self.fire('ff-init'); if( context.editmode() ) self.editmode(true); context.fire('ff-detect', {part:self}); self.history().init(); } Part.prototype = { handleEvent: function(e) { if( e.defaultPrevented ) return; var type = e.type; var self = this; var editmode = self.editmode(); var toolbar = self.toolbar(); var target = e.target || e.srcElement; var dom = self.dom(); var el = $(dom); if( type == 'ff-data' ) { self.fire('ff-render', { type: 'data', originalEvent: e }); } else if( type == 'ff-modechange' ) { if( editmode ) { if( toolbar.always() ) toolbar.show(); el.ac('ff-edit-state'); self.fire('ff-editmode'); } else { toolbar.hide(true); el.rc('ff-edit-state').rc('ff-focus-state').rc('ff-enter-state').rc('ff-dragging'); self.fire('ff-viewmode'); self.blur(); } self.fire('ff-render', { type: 'modechange', originalEvent: e }); } if( editmode ) { if( type == 'ff-focus' ) { el.attr('draggable', true); toolbar.show(); } else if( type == 'ff-blur' ) { el.attr('draggable', null); toolbar.hide(); } else if( type == 'mouseenter' ) { toolbar.update(); el.ac('ff-enter-state'); } else if( ~['mousedown', 'mouseup'].indexOf(type) ) { setTimeout(function() { toolbar.update(); }, 0); } else if( type == 'mouseleave' ) { el.rc('ff-enter-state'); } else if( type == 'dragstart' ) { if( target === dom ) { toolbar.hide(); context.dragging = dom; e.dataTransfer.setDragImage(dom, 0, 0); e.dataTransfer.setData('text', dom.outerHTML); el.ac('ff-dragging'); self.history().init(); } } else if( type == 'dragend' ) { if( target === dom ) { toolbar.show(); context.dragging = null; el.rc('ff-dragging'); } } } }, context: function() { return context; }, createToolbar: function() { return new Toolbar(this); }, toolbar: function() { return this._t || (this._t = this.createToolbar()); }, removable: function(removable) { if( !arguments.length ) return this._rm; this._rm = !!removable; return this; }, dom: function() { return this._n; }, create: function(arg) { return $('<div/>').html(arg)[0]; }, html: function(html) { var part = this; var dom = part.dom(); if( !arguments.length ) { var editmode = part.editmode(); part.editmode(false); var html = dom.innerHTML; part.editmode(editmode); var tmp = $('<div/>').html(html); tmp .find('.ff, .ff-edit-state, .ff-enter-state, .ff-focus-state, .ff-dragging, [draggable], [contenteditable]') .rc('ff ff-edit-state ff-enter-state ff-focus-state ff-dragging') .attr('draggable', null) .attr('contenteditable', null); tmp.find('.ff-acc').remove(); return tmp.html(); } dom.innerHTML = html || ''; part.history().init(); return part; }, parent: function() { var p = this.dom().parentNode; return p && context.partof(p); }, parents: function() { var p = this.dom().parentNode; return p && context.partsof(p); }, remove: function() { var part = this; part.blur(); part.toolbar().hide(); part.fire('ff-remove'); $(part.dom()).remove(); return part; }, editmode: function(b) { var part = this; if( !arguments.length ) return !!part._md; var prev = part._md; var editmode = part._md = !!b; if( editmode !== prev ) part.fire('ff-modechange', {editmode: editmode}); return part; }, data: function(data) { var part = this; if( !arguments.length ) { if( part.getData ) return part.getData(); return part._d || null; } if( part.setData ) part.setData(data); else part._d = data; part.fire('ff-data', {old: part._d, data: data}); part.history().init(); return part; }, fire: function(type, detail, cancellable, bubble) { return !!$(this.dom()).fire(type, detail, cancellable, bubble)[0]; }, on: function(type, fn) { var part = this; fn._wrapper = function() { return fn.apply(part, arguments); }; $(this.dom()).on(type, fn._wrapper); return this; }, once: function(type, fn) { var part = this; fn._wrapper = function() { return fn.apply(part, arguments); }; $(this.dom()).once(type, fn._wrapper); return this; }, off: function(type, fn) { $(this.dom()).off(type, fn._wrapper || fn); return this; }, clear: function() { this.data(null).fire('ff-clear'); return this; }, click: function() { this.dom().click(); return this; }, focus: function() { var part = this; var dom = part.dom(); if( part.editmode() && part !== context.focused && document.body.contains(dom) ) { if( context.focused && typeof context.focused.blur == 'function' ) context.focused.blur(); $(dom).ac('ff-focus-state'); part.fire('ff-focus'); context.focused = part; } return part; }, blur: function() { var part = this; if( part.editmode() && part === context.focused ) { $(part.dom()).rc('ff-focus-state'); part.fire('ff-blur'); context.focused = null; } return part; }, ranges: function(collapsed) { return context.ranges(this.dom(), collapsed); }, range: function(collapsed) { return context.range(this.dom(), collapsed); }, // history createHistory: function() { var part = this; var dom = part.dom(); return (function(cls, css) { return function() {