UNPKG

suneditor

Version:

Vanilla JavaScript WYSIWYG web editor (2.x legacy version, actively maintained)

378 lines (323 loc) 15.9 kB
/* * wysiwyg web editor * * suneditor.js * Copyright 2017 JiHong Lee. * MIT license. */ 'use strict'; (function (global, factory) { if (typeof module === 'object' && typeof module.exports === 'object') { module.exports = global.document ? factory(global, true) : function (w) { if (!w.document) { throw new Error('SUNEDITOR_MODULES a window with a document'); } return factory(w); }; } else { factory(global); } }(typeof window !== 'undefined' ? window : this, function (window, noGlobal) { const fileBrowser = { name: 'fileBrowser', _xmlHttp: null, _loading: null, /** * @description Constructor * @param {Object} core Core object */ add: function (core) { const context = core.context; context.fileBrowser = { _closeSignal: false, area: null, header: null, tagArea: null, body: null, list: null, tagElements: null, items: [], selectedTags: [], selectorHandler: null, contextPlugin: '', columnSize: 4 }; /** fileBrowser */ let browser_div = core.util.createElement('DIV'); browser_div.className = 'se-file-browser sun-editor-common'; let back = core.util.createElement('DIV'); back.className = 'se-file-browser-back'; let content = core.util.createElement('DIV'); content.className = 'se-file-browser-inner'; content.innerHTML = this.set_browser(core); browser_div.appendChild(back); browser_div.appendChild(content); this._loading = browser_div.querySelector('.se-loading-box'); context.fileBrowser.area = browser_div; context.fileBrowser.header = content.querySelector('.se-file-browser-header'); context.fileBrowser.titleArea = content.querySelector('.se-file-browser-title'); context.fileBrowser.tagArea = content.querySelector('.se-file-browser-tags'); context.fileBrowser.body = content.querySelector('.se-file-browser-body'); context.fileBrowser.list = content.querySelector('.se-file-browser-list'); /** add event listeners */ context.fileBrowser.tagArea.addEventListener('click', this.onClickTag.bind(core)); context.fileBrowser.list.addEventListener('click', this.onClickFile.bind(core)); content.addEventListener('mousedown', this._onMouseDown_browser.bind(core)); content.addEventListener('click', this._onClick_browser.bind(core)); /** append html */ context.element.relative.appendChild(browser_div); /** empty memory */ browser_div = null, back = null, content = null; }, set_browser: function (core) { const lang = core.lang; return '<div class="se-file-browser-content">' + '<div class="se-file-browser-header">' + '<button type="button" data-command="close" class="se-btn se-file-browser-close" class="close" title="' + lang.dialogBox.close + '" aria-label="' + lang.dialogBox.close + '">' + core.icons.cancel + '</button>' + '<span class="se-file-browser-title"></span>' + '<div class="se-file-browser-tags"></div>' + '</div>' + '<div class="se-file-browser-body">' + '<div class="se-loading-box sun-editor-common"><div class="se-loading-effect"></div></div>' + '<div class="se-file-browser-list"></div>' + '</div>' + '</div>'; }, /** * @description Event to control the behavior of closing the browser * @param {MouseEvent} e Event object * @private */ _onMouseDown_browser: function (e) { if (/se-file-browser-inner/.test(e.target.className)) { this.context.fileBrowser._closeSignal = true; } else { this.context.fileBrowser._closeSignal = false; } }, /** * @description Event to close the window when the outside area of the browser or close button is click * @param {MouseEvent} e Event object * @private */ _onClick_browser: function (e) { e.stopPropagation(); if (/close/.test(e.target.getAttribute('data-command')) || this.context.fileBrowser._closeSignal) { this.plugins.fileBrowser.close.call(this); } }, /** * @description Open a file browser plugin * @param {String} pluginName Plugin name using the file browser * @param {Function|null} selectorHandler When the function comes as an argument value, it substitutes "context.selectorHandler". */ open: function (pluginName, selectorHandler) { if (this.plugins.fileBrowser._bindClose) { this._d.removeEventListener('keydown', this.plugins.fileBrowser._bindClose); this.plugins.fileBrowser._bindClose = null; } this.plugins.fileBrowser._bindClose = function (e) { if (!/27/.test(e.keyCode)) return; this.plugins.fileBrowser.close.call(this); }.bind(this); this._d.addEventListener('keydown', this.plugins.fileBrowser._bindClose); const fileBrowserContext = this.context.fileBrowser; fileBrowserContext.contextPlugin = pluginName; fileBrowserContext.selectorHandler = selectorHandler; const pluginContext = this.context[pluginName]; const listClassName = pluginContext.listClass; if (!this.util.hasClass(fileBrowserContext.list, listClassName)) { fileBrowserContext.list.className = 'se-file-browser-list ' + listClassName; } if (this.options.popupDisplay === 'full') { fileBrowserContext.area.style.position = 'fixed'; } else { fileBrowserContext.area.style.position = 'absolute'; } fileBrowserContext.titleArea.textContent = pluginContext.title; fileBrowserContext.area.style.display = 'block'; if (this.context[pluginName].directData) { this.plugins.fileBrowser._drawListItem.call(this, this.context[pluginName].directData, true); } else { this.plugins.fileBrowser._drawFileList.call(this, this.context[pluginName].url, this.context[pluginName].header); } }, _bindClose: null, /** * @description Close a fileBrowser plugin * The plugin's "init" method is called. */ close: function () { const fileBrowserPlugin = this.plugins.fileBrowser; if (fileBrowserPlugin._xmlHttp) { fileBrowserPlugin._xmlHttp.abort(); } if (fileBrowserPlugin._bindClose) { this._d.removeEventListener('keydown', fileBrowserPlugin._bindClose); fileBrowserPlugin._bindClose = null; } const fileBrowserContext = this.context.fileBrowser; fileBrowserContext.area.style.display = 'none'; fileBrowserContext.selectorHandler = null; fileBrowserContext.selectedTags = []; fileBrowserContext.items = []; fileBrowserContext.list.innerHTML = fileBrowserContext.tagArea.innerHTML = fileBrowserContext.titleArea.textContent = ''; if (typeof this.plugins[fileBrowserContext.contextPlugin].init === 'function') this.plugins[fileBrowserContext.contextPlugin].init.call(this); fileBrowserContext.contextPlugin = ''; }, /** * @description Show file browser loading box */ showBrowserLoading: function () { this._loading.style.display = 'block'; }, /** * @description Close file browser loading box */ closeBrowserLoading: function () { this._loading.style.display = 'none'; }, _drawFileList: function (url, browserHeader) { const fileBrowserPlugin = this.plugins.fileBrowser; const xmlHttp = fileBrowserPlugin._xmlHttp = this.util.getXMLHttpRequest(); xmlHttp.onreadystatechange = fileBrowserPlugin._callBackGet.bind(this, xmlHttp); xmlHttp.open('get', url, true); if(browserHeader !== null && typeof browserHeader === 'object' && this._w.Object.keys(browserHeader).length > 0){ for(let key in browserHeader){ xmlHttp.setRequestHeader(key, browserHeader[key]); } } xmlHttp.send(null); this.plugins.fileBrowser.showBrowserLoading(); }, _callBackGet: function (xmlHttp) { if (xmlHttp.readyState === 4) { this.plugins.fileBrowser._xmlHttp = null; if (xmlHttp.status === 200) { try { const res = JSON.parse(xmlHttp.responseText); if (res.result.length > 0) { this.plugins.fileBrowser._drawListItem.call(this, res.result, true); } else if (res.nullMessage) { this.context.fileBrowser.list.innerHTML = res.nullMessage; } } catch (e) { throw Error('[SUNEDITOR.fileBrowser.drawList.fail] cause : "' + e.message + '"'); } finally { this.plugins.fileBrowser.closeBrowserLoading(); this.context.fileBrowser.body.style.maxHeight = (this._w.innerHeight - this.context.fileBrowser.header.offsetHeight - 50) + 'px'; } } else { // exception this.plugins.fileBrowser.closeBrowserLoading(); if (xmlHttp.status !== 0) { const res = !xmlHttp.responseText ? xmlHttp : JSON.parse(xmlHttp.responseText); const err = '[SUNEDITOR.fileBrowser.get.serverException] status: ' + xmlHttp.status + ', response: ' + (res.errorMessage || xmlHttp.responseText); throw Error(err); } } } }, _drawListItem: function (items, update) { const fileBrowserContext = this.context.fileBrowser; const pluginContext = this.context[fileBrowserContext.contextPlugin]; const _tags = []; const len = items.length; const columnSize = pluginContext.columnSize || fileBrowserContext.columnSize; const splitSize = columnSize <= 1 ? 1 : (Math.round(len/columnSize) || 1); const drawItemHandler = pluginContext.itemTemplateHandler; let tagsHTML = ''; let listHTML = '<div class="se-file-item-column">'; let columns = 1; for (let i = 0, item, tags; i < len; i++) { item = items[i]; tags = !item.tag ? [] : typeof item.tag === 'string' ? item.tag.split(',') : item.tag; tags = item.tag = tags.map(function (v) { return v.trim(); }); listHTML += drawItemHandler(item); if ((i + 1) % splitSize === 0 && columns < columnSize && (i + 1) < len) { columns++; listHTML += '</div><div class="se-file-item-column">'; } if (update && tags.length > 0) { for (let t = 0, tLen = tags.length, tag; t < tLen; t++) { tag = tags[t]; if (tag && _tags.indexOf(tag) === -1) { _tags.push(tag); tagsHTML += '<a title="' + tag + '" aria-label="' + tag + '">' + tag + '</a>'; } } } } listHTML += '</div>'; fileBrowserContext.list.innerHTML = listHTML; if (update) { fileBrowserContext.items = items; fileBrowserContext.tagArea.innerHTML = tagsHTML; fileBrowserContext.tagElements = fileBrowserContext.tagArea.querySelectorAll('A'); } }, onClickTag: function (e) { const target = e.target; if (!this.util.isAnchor(target)) return; const tagName = target.textContent; const fileBrowserPlugin = this.plugins.fileBrowser; const fileBrowserContext = this.context.fileBrowser; const selectTag = fileBrowserContext.tagArea.querySelector('a[title="' + tagName + '"]'); const selectedTags = fileBrowserContext.selectedTags; const sTagIndex = selectedTags.indexOf(tagName); if (sTagIndex > -1){ selectedTags.splice(sTagIndex, 1); this.util.removeClass(selectTag, 'on'); } else { selectedTags.push(tagName); this.util.addClass(selectTag, 'on'); } fileBrowserPlugin._drawListItem.call(this, selectedTags.length === 0 ? fileBrowserContext.items : fileBrowserContext.items.filter(function (item) { return item.tag.some(function (tag) { return selectedTags.indexOf(tag) > -1; }); }), false); }, onClickFile: function (e) { e.preventDefault(); e.stopPropagation(); const fileBrowserContext = this.context.fileBrowser; const listEl = fileBrowserContext.list; let target = e.target; let command = null; if (target === listEl) return; while (listEl !== target.parentNode) { command = target.getAttribute('data-command'); if (command) break; target = target.parentNode; } if (!command) return; const handler = (fileBrowserContext.selectorHandler || this.context[fileBrowserContext.contextPlugin].selectorHandler); handler(target, target.parentNode.querySelector('.__se__img_name').textContent); this.plugins.fileBrowser.close.call(this); } }; if (typeof noGlobal === typeof undefined) { if (!window.SUNEDITOR_MODULES) { Object.defineProperty(window, 'SUNEDITOR_MODULES', { enumerable: true, writable: false, configurable: false, value: {} }); } Object.defineProperty(window.SUNEDITOR_MODULES, 'fileBrowser', { enumerable: true, writable: false, configurable: false, value: fileBrowser }); } return fileBrowser; }));