ff-editor
Version:
Extensible WYSIWYG HTML Editor
1,785 lines (1,591 loc) • 170 kB
JavaScript
/*!
* 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() {