@striven-erp/striven-editor
Version:
The editor used for the Striven ERP system.
1,370 lines (1,087 loc) • 142 kB
JavaScript
import './striveneditor.css';
import {
EXTENSIONS,
FONTPACK,
OPTIONGROUPS,
DEFAULTOPTIONS,
ACTIVEOPTIONCOLOR,
FORECOLORICON,
HILITECOLORICON,
FONTNAMES,
FONTSIZES,
COLLAPSEICON,
EXPANDICON,
DESIGNICON,
UPLOADICON
} from './defaults.js';
// Helpers
import { createSVG, denormalizeCamel, blowUpElement, getImageDimensions, getImageDataURL, computeImageDimensions, createImageElement } from './utils';
// Polyfills
import ResizeObserver from 'resize-observer-polyfill';
// Plugins
import linkify from 'linkifyjs/element';
// Pickr
//import './classic.min.css';
//import Pickr from './pickr.min.js';
import '@simonwep/pickr/dist/themes/classic.min.css'; // 'classic' theme
import Pickr from '@simonwep/pickr/dist/pickr.es5.min';
// Formatting
import js_beautify from 'js-beautify';
/* Represents an instance of the Striven Editor */
export default class StrivenEditor {
/**
* Bind onchange handler to contenteditable element
* @param {HTMLElement} element to bind onchange to
*/
_bindContenteditableOnChange(el) {
const se = this;
el.addEventListener('focus', function () {
if (el.data_orig === undefined) {
el.data_orig = el.innerHTML;
}
});
el.addEventListener('blur', function () {
const menus = se.editor.getElementsByClassName('se-popup-open:not(.se-toolbar-group)');
const inputs = se.editor.getElementsByTagName('input');
const colorPicker = se.editor.querySelectorAll('.pcr-app.visible');
const actives = [...menus, ...colorPicker, ...inputs, se.body, se.toolbar, se.editor];
if (
el.innerHTML != el.data_orig &&
!se.toolbarClick &&
!actives.includes(document.activeElement) &&
!menus.length &&
!colorPicker.length
) {
se.createLinks();
if (se.options.change) {
se.options.change(se.getContent());
}
delete el.data_orig;
}
});
}
/**
* Instantiate the StrivenEditor
* @param {HTMLElement} el The element to initialize StrivenEditor on
* @param {Object} options StrivenEditor options to initialize the editor with
*/
constructor(el, options) {
const se = this;
// Webpack inserts the package.json version
se['_version'] = __VERSION__;
// Establish the browser context
se.establishBrowser();
// Initialize a range instance for the editor
se.range = new Range();
// Initialize a file array for file upload
se.files = [];
// Default Option Groups with SVG Data
se.optionGroups = OPTIONGROUPS;
// Gets whether an image is being uploaded when pasting or inserting an image
se._isImageUploading = false;
// Initialize Options
if (options) {
// Set options property
se.options = options;
// Font Pack for font-awesome (not being used)
options.fontPack || (se.options.fontPack = FONTPACK);
// Allowed File Extensions
options.extensions || (se.options.extensions = EXTENSIONS);
// Enabled Toolbar options
options.toolbarOptions || (se.options.toolbarOptions = DEFAULTOPTIONS);
// Fill color for active options
options.activeOptionColor || (se.options.activeOptionColor = ACTIVEOPTIONCOLOR);
// Default fonts names
options.fontNames || (se.options.fontNames = FONTNAMES);
// Allow File Upload
options.fileUpload !== false && (se.options.fileUpload = true);
// Configure Options with Minimal Enabled
if (options.toolbarOptions && options.minimal) {
// Get custom options from toolbarOptions
const customs = options.toolbarOptions.filter((opt) => typeof opt === 'object');
// Set toolbar options for minimal configuration
se.options.toolbarOptions = ['bold', 'italic', 'underline', 'insertUnorderedList', 'attachment', 'link', 'image', ...customs];
//disallow tabbing
se.options.canTab = false;
}
} else {
// Set default options
se.options = {
fontPack: FONTPACK,
extensions: EXTENSIONS,
toolbarOptions: DEFAULTOPTIONS,
activeOptionColor: ACTIVEOPTIONCOLOR,
fontNames: FONTNAMES,
fileUpload: true,
canTab: false
};
}
// Initialize the editor
se.initEditor(el); // Core Editor Initialization
se.initResponsive(); // Editor Reponsive Logic
se.initOverflow(); // Overflow Content Logic
se.overflow(); // Trigger overflow login on init
se.getIsImageUploading = () => se._isImageUploading;
// DOM Access to the Editor Instance
el.StrivenEditor = () => se;
// Bind handler functions to scope
se.bound_popupEscapeHandler = se.popupEscapeHandler.bind(se);
}
/**
* Initiliaze the StrivenEditor on the passed element.
* @param {HTMLElement} el
*/
initEditor(el) {
const se = this;
se.editor = el;
se.toolbar = se.initToolbar();
se.body = se.initBody();
se.linkMenu = se.initLinkMenu();
se.imageMenu = se.initImageMenu();
se.tableMenu = se.initTableMenu();
se.metaDataSection = se.options.metaUrl ? se.initMetaDataSection() : null;
se.filesSection = se.options.fileUpload ? se.initFilesSection() : null;
// Add Striven Editor Class
se.editor.classList.add('striven-editor');
// Stop events from bubbling up the DOM
se.editor.onkeypress = (e) => e.stopPropagation();
// Initialze with the value property in the options
se.setContent(se.options.value || '');
window.addEventListener('click', () => {
if (!se.editor.contains(document.activeElement)) {
se.closeAllMenus();
}
});
// Remove all link option popups on escape
window.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
[...se.body.querySelectorAll('.se-link-options')].forEach((o) => o.remove());
[...se.body.querySelectorAll('.se-image-options')].forEach((o) => o.remove());
}
});
// Toolbar Hide
if (se.options.toolbarHide) {
// Hide the toolbar template if there is one
se.toolbarTemplate && (se.toolbarTemplate.style.display = 'none');
// Hide the toolbar options
se.toolbarOptionsGroup.style.display = 'none';
// Add the close class
se.toolbar.classList.add('se-toolbar-close');
// Bind the focus event to reopen the toolbar
const bodyFocus = se.body.onfocus;
se.body.onfocus = () => {
se.overflow();
se.openToolbar();
bodyFocus && bodyFocus();
};
// Bind the blur event close the toolbar
const bodyBlur = se.body.onblur;
se.body.onblur = () => {
bodyBlur && bodyBlur();
se.overflow();
// Do not close the toolbar is editor is active
setTimeout(() => {
if (
se.linkMenu.dataset.active !== 'true' &&
se.imageMenu.dataset.active !== 'true' &&
se.tableMenu.dataset.active !== 'true' &&
!se.isEditorInFocus()
) {
se.closeToolbar();
}
}, 200);
};
} else {
se.toolbar.style.boxShadow = '#ddd -1px 2px 3px 0px';
se.toolbar.style.height = 'fit-content';
}
// Toolbar Options
se.toolbarOptions.forEach((optionEl) => {
// Execute Toolbar Commands
const optionElClick = optionEl.onclick;
// Bind the toolbar commands click hander
optionEl.onclick = (e) => {
if (!se.browser.isSafari()) {
se.range = se.getRange();
}
// Get the document execute command
const command = optionEl.id.split('-').pop();
// Command Logic
switch (command) {
case 'bold':
case 'underline':
case 'italic':
case 'strikethrough':
// If active, deactive the command
if (optionEl.classList.contains('se-toolbar-option-active')) {
optionEl.classList.remove('se-toolbar-option-active');
// Save the range
se.setRange();
// Focus back into the body
se.body.focus();
// If the command is active, deactivate the command
document.queryCommandState(command) && se.executeCommand(command);
} else {
// Set the command active (body on focus will execute the command)
optionEl.classList.add('se-toolbar-option-active');
// Save the range
se.setRange();
// Focus back into the body
se.body.focus();
}
break;
case 'removeFormat':
se.executeCommand(command);
// Deactivate all toolbar options
se.toolbarOptions.forEach((o) => o.classList.remove('se-toolbar-option-active'));
break;
case 'indent':
setTimeout(() => se.setRange(se.range), 0);
default:
se.body.focus();
se.executeCommand(command);
break;
}
optionElClick && optionElClick();
};
});
// Add dividers
function constructDivider() {
const divider = document.createElement('div');
divider.classList.add('se-divider-section');
const bar = document.createElement('div');
bar.classList.add('se-divider-bar');
divider.append(bar);
return divider;
}
const areas = [...se.toolbarGroups, ...se.toolbar.querySelectorAll('.se-toolbar-selection')];
areas.forEach((a) => {
if (a.querySelector('.se-toolbar-option')) {
a.after(constructDivider());
}
});
// Construct editor elements
se.toolbar && se.editor.appendChild(se.toolbar);
se.body && se.editor.appendChild(se.body);
se.linkMenu && se.editor.appendChild(se.linkMenu);
se.imageMenu && se.editor.appendChild(se.imageMenu);
se.tableMenu && se.editor.appendChild(se.tableMenu);
se.metaDataSection && se.editor.appendChild(se.metaDataSection);
se.filesSection && se.editor.appendChild(se.filesSection);
// Reposition Toolbar
if (se.options.toolbarBottom) {
se.toolbar.classList.add('se-toolbar-bottom');
se.toolbar.classList.add('se-toolbar-top');
se.linkMenu.classList.remove('se-popup-top');
se.linkMenu.classList.add('se-popup-bottom');
se.imageMenu.classList.remove('se-popup-top');
se.imageMenu.classList.add('se-popup-bottom');
se.tableMenu.classList.remove('se-popup-top');
se.tableMenu.classList.add('se-popup-bottom');
se.editor.removeChild(se.toolbar);
se.editor.append(se.toolbar);
}
se.options.init && se.options.init(se);
}
/**
* Open the toolbar for when the toolbarHide option is set to true
*/
openToolbar() {
const se = this;
se.toolbar.classList.remove('se-toolbar-close');
setTimeout(() => {
se.toolbarOptionsGroup.style.display = 'flex';
se.toolbarTemplate && (se.toolbarTemplate.style.display = 'flex');
}, 200);
}
/**
* Close the toolbar for when the toolbarHide option is set to true
*/
closeToolbar() {
const se = this;
se.closeAllMenus();
se.toolbarOptionsGroup.style.display = 'none';
se.toolbarTemplate && (se.toolbarTemplate.style.display = 'none');
se.toolbar.classList.add('se-toolbar-close');
}
/**
* Initialized the toolbar for StrivenEditor
* @returns {HTMLElement} The StrivenEditor toolbar
*/
initToolbar() {
const se = this;
const toolbar = document.createElement('div');
se.toolbarOptionsGroup = document.createElement('div');
const groups = Object.keys(se.optionGroups);
toolbar.classList.add('se-toolbar');
toolbar.classList.add('toolbar-top');
se.toolbarOptionsGroup.classList.add('se-toolbar-options');
toolbar.onclick = (ev) => {
se.body.focus();
};
// Append Font Options
!se.options.minimal && se.initToolbarFontOptions();
//iterate groups
groups.forEach((group) => {
// add menu to toolbarOptions
const toolbarMenu = document.createElement('div');
// const toolbarMenuIcon = document.createElement("i");
toolbarMenu.classList.add('se-toolbar-menu');
toolbarMenu.id = `menu-${group}`;
toolbarMenu.setAttribute('data-name', group);
// toolbarMenuIcon.classList.add(se.options.fontPack);
// toolbarMenuIcon.classList.add(se.optionGroups[group].menu);
const arrow = {
viewBox: '0 0 1792 1792',
d: 'M1395 736q0 13-10 23l-466 466q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l393 393 393-393q10-10 23-10t23 10l50 50q10 10 10 23z'
};
const svgSpan = se.constructSVG(se.optionGroups[group].menu);
toolbarMenu.appendChild(svgSpan.getElementsByTagName('svg')[0]);
if (group !== 'options') {
const arrowSpan = se.constructSVG(arrow);
arrowSpan.classList.add('se-arrow-span');
toolbarMenu.appendChild(arrowSpan);
}
se.toolbarOptionsGroup.appendChild(toolbarMenu);
// add group to toolbarOptions
const toolbarGroup = document.createElement('div');
toolbarGroup.classList.add('se-toolbar-group');
toolbarGroup.id = `group-${group}`;
// iterate options within group
se.options.toolbarOptions.forEach((option) => {
const toolbarOption = se.optionGroups[group].group.filter((group) => group[option])[0];
if (toolbarOption) {
const svgData = toolbarOption[option];
const optionSpan = se.constructSVG(svgData);
optionSpan.classList.add('se-toolbar-option');
optionSpan.id = `toolbar-${option}`;
optionSpan.title = denormalizeCamel(option);
optionSpan.setAttribute('data-group-name', group);
switch (option) {
case 'removeFormat':
optionSpan.setAttribute('title', 'Clear Format');
break;
case 'hiliteColor':
optionSpan.setAttribute('title', 'Background Color');
break;
default:
optionSpan.setAttribute('title', denormalizeCamel(option));
break;
}
toolbarGroup.appendChild(optionSpan);
}
});
se.toolbarOptionsGroup.appendChild(toolbarGroup);
});
toolbar.appendChild(se.toolbarOptionsGroup);
// Custom toolbar template
if (se.options.toolbarTemplate) {
const toolbarTemplate = document.createElement('div');
toolbarTemplate.id = 'toolbar-template';
toolbarTemplate.setAttribute('style', 'display: flex');
toolbarTemplate.appendChild(se.options.toolbarTemplate);
toolbar.appendChild(toolbarTemplate);
se.toolbarTemplate = toolbarTemplate;
}
se.toolbarOptions = toolbar.querySelectorAll('span');
se.toolbarGroups = [...toolbar.getElementsByClassName('se-toolbar-group')];
se.toolbarMenus = [...toolbar.getElementsByClassName('se-toolbar-menu')];
// Remove menu that has no options enabled
se.toolbarGroups.forEach((group) => {
if (group && group.children.length < 1) {
const groupName = group.id.split('-')[1];
const menu = se.toolbarMenus.filter((menu) => menu && menu.id.split('-')[1] === groupName)[0];
menu.remove();
}
});
const miscOptions = toolbar.querySelector('#group-options');
// toolbar group for custom options
const customOptions = se.options.toolbarOptions.filter((option) => typeof option === 'object');
if (customOptions.length > 0) {
customOptions.forEach((opt) => {
const { icon, handler, title } = opt;
if (typeof icon === 'object') {
const option = se.constructSVG({ viewBox: icon.viewBox, d: icon.d });
option.classList.add('se-toolbar-option');
option.onclick = () => handler(option);
option.setAttribute('id', `toolbar-${title}`);
option.setAttribute('title', denormalizeCamel(title));
miscOptions.appendChild(option);
} else {
const option = document.createElement('span');
const image = document.createElement('img');
image.setAttribute('src', opt.icon);
image.setAttribute('alt', 'custom option');
option.style.paddingTop = '6px';
option.style.paddingBottom = '8px';
option.classList.add('se-toolbar-option');
option.onclick = () => handler();
option.append(image);
miscOptions.appendChild(option);
}
});
}
const removeFormatOption = toolbar.querySelector('#toolbar-removeFormat');
if (removeFormatOption) {
removeFormatOption.remove();
miscOptions.append(removeFormatOption);
}
se.toolbarClick = false;
toolbar.addEventListener('mousedown', () => {
se.toolbarClick = true;
});
toolbar.addEventListener('mouseup', () => {
se.toolbarClick = false;
});
return toolbar;
}
/*
* Initializes the toolbars font options
*/
initToolbarFontOptions() {
const se = this;
function initMenu(name, onOpen) {
const menu = document.createElement('div');
menu.setAttribute('id', `${name}-menu`);
menu.classList.add('se-popup', se.options.toolbarBottom ? 'se-popup-bottom' : 'se-popup-top');
menu.dataset.active = 'false';
menu.open = () => {
se.closeAllMenus();
se.setMenuOffset(se.toolbar.querySelector(`#toolbar-${name}`), menu);
menu.classList.add('se-popup-open');
menu.dataset.active = 'true';
se.addPopupEscapeHandler();
onOpen && onOpen(menu);
};
menu.close = () => {
menu.classList.remove('se-popup-open');
menu.dataset.active = 'false';
se.removePopupEscapeHandler();
};
se.editor.append(menu);
return menu;
}
function initFontNameMenu(select) {
const menu = initMenu('fontName');
menu.dataset.init = 'true';
['(inherited font)', ...se.options.fontNames].forEach((f) => {
const fontOption = document.createElement('div');
fontOption.classList.add('se-toolbar-popup-option');
fontOption.textContent = f;
fontOption.style.fontFamily = f;
const trigger = (e) => {
let fontselect = e.target.textContent;
select.textContent = fontselect;
menu.close();
function execute() {
if (fontselect === '(inherited font)') {
fontselect = getComputedStyle(se.body).fontFamily;
}
if (se.browser.isEdge() || se.browser.isFirefox()) {
document.execCommand('fontName', false, fontselect);
} else {
document.execCommand('fontName', true, fontselect);
}
}
execute();
const refocus = se.body.onfocus;
se.body.onfocus = () => {
se.setRange(se.range);
if (!se.getContent()) {
const enabler = () => {
setTimeout(() => {
execute();
se.body.removeEventListener('keydown', enabler);
});
};
se.body.addEventListener('keydown', enabler);
}
setTimeout(() => execute(), 0);
if (se.scrollPosition && !se.browser.isEdge()) {
se.body.scrollTo(se.scrollPosition.x, se.scrollPosition.y);
}
se.body.onfocus = refocus;
};
se.body.focus();
};
fontOption.onmousedown = trigger;
menu.append(fontOption);
});
return menu;
}
function initFontSizeMenu(select) {
const menu = initMenu('fontSize');
const sizes = FONTSIZES;
['(inherited size)', ...Object.keys(sizes)].forEach((size) => {
let s = sizes[size] || '(inherited size)';
const fontOption = document.createElement('div');
fontOption.classList.add('se-toolbar-popup-option');
fontOption.textContent = s;
function execute() {
let execSize = size;
if (size === '(inherited size)') {
execSize = 3;
}
if (se.browser.isEdge() || se.browser.isFirefox()) {
document.execCommand('fontSize', false, execSize);
} else {
document.execCommand('fontSize', true, execSize);
}
}
const trigger = (e) => {
const fontsize = e.target.textContent;
select.textContent = fontsize;
select.dataset.command = size;
menu.close();
execute();
const refocus = se.body.focus;
se.body.onfocus = () => {
se.setRange(se.range);
if (!se.getContent()) {
const enabler = () => {
setTimeout(() => {
execute();
se.body.removeEventListener('keydown', enabler);
}, 0);
};
se.body.addEventListener('keydown', enabler);
}
setTimeout(() => execute(), 0);
if (se.scrollPosition && !se.browser.isEdge()) {
se.body.scrollTo(se.scrollPosition.x, se.scrollPosition.y);
}
se.body.onfocus = refocus;
};
se.body.focus();
};
fontOption.onmousedown = trigger;
menu.append(fontOption);
});
return menu;
}
function initFontFormatMenu() {
const menu = initMenu('fontFormat');
const formats = [
{
command: 'H1',
option: '<h1 style="margin: 0; color: #000;">Heading 1</h1>'
},
{
command: 'H2',
option: '<h2 style="margin: 0; color: #000;">Heading 2</h2>'
},
{
command: 'H3',
option: '<h3 style="margin: 0; color: #000;">Heading 3</h4>'
},
{
command: 'H4',
option: '<h4 style="margin: 0; color: #000;">Heading 4</h4>'
},
{
command: 'H5',
option: '<h5 style="margin: 0; color: #000;">Heading 5</h5>'
},
{
command: 'H6',
option: '<h6 style="margin: 0; color: #000;">Heading 6</h6>'
},
{
command: 'P',
option: '<p style="margin: 0; color: #000;">Paragraph</p>'
}
];
formats.forEach((s) => {
const fontOption = document.createElement('div');
fontOption.classList.add('se-toolbar-popup-option');
fontOption.innerHTML = s.option;
fontOption.onclick = (e) => {
menu.close();
se.body.focus();
se.setRange();
if (se.browser.isFirefox() || se.browser.isEdge()) {
document.execCommand('removeFormat', false);
} else {
document.execCommand('removeFormat', true);
}
se.executeCommand('formatBlock', s.command);
};
menu.append(fontOption);
});
return menu;
}
// Get enabled toolbar options
const enabledOptions = se.options.toolbarOptions;
// Enable font name option
if (enabledOptions.includes('fontName')) {
// Initialize and append toolbar option
const fontSelect = document.createElement('div');
const selectedFont = document.createElement('p');
// Initialize the popup menu to be length of longest name
const menu = initFontNameMenu(selectedFont);
// Toggle the fontselect popup on the select click
fontSelect.onclick = () => {
if (!se.browser.isSafari()) {
se.range = se.getRange();
}
if (menu.dataset.active === 'true') {
menu.close();
} else {
menu.open();
}
};
// Set the select to initialize on the first font name
selectedFont.textContent = '(inherited font)';
fontSelect.setAttribute('id', 'toolbar-fontName');
fontSelect.classList.add('se-toolbar-selection');
selectedFont.classList.add('se-toolbar-option');
fontSelect.append(selectedFont);
se.toolbarOptionsGroup.append(fontSelect);
se.fontName = selectedFont;
}
// Enable font size option
if (enabledOptions.includes('fontSize')) {
const fontSizeSelect = document.createElement('div');
const selectedFontSize = document.createElement('p');
const menu = initFontSizeMenu(selectedFontSize);
fontSizeSelect.onclick = () => {
if (!se.browser.isSafari()) {
se.range = se.getRange();
}
if (menu.dataset.active === 'true') {
menu.close();
} else {
menu.open();
}
};
fontSizeSelect.setAttribute('id', 'toolbar-fontSize');
selectedFontSize.textContent = '(inherited size)';
fontSizeSelect.classList.add('se-toolbar-selection');
selectedFontSize.classList.add('se-toolbar-option');
fontSizeSelect.append(selectedFontSize);
se.toolbarOptionsGroup.append(fontSizeSelect);
se.fontSize = selectedFontSize;
}
if (enabledOptions.includes('fontFormat')) {
const fontFormatSelect = document.createElement('div');
const selectedFontFormat = document.createElement('p');
const menu = initFontFormatMenu();
fontFormatSelect.onclick = () => {
if (!se.browser.isSafari()) {
se.range = se.getRange();
}
if (menu.dataset.active === 'true') {
menu.close();
} else {
menu.open();
}
};
fontFormatSelect.setAttribute('id', 'toolbar-fontFormat');
selectedFontFormat.textContent = 'Format';
fontFormatSelect.classList.add('se-toolbar-selection');
selectedFontFormat.classList.add('se-toolbar-option');
fontFormatSelect.append(selectedFontFormat);
se.toolbarOptionsGroup.append(fontFormatSelect);
}
}
/**
* Initialized the StrivenEditor body
* @returns {HTMLElement} The StrivenEditor body
*/
initBody() {
const se = this;
const body = document.createElement('div');
body.classList.add('se-body');
body.contentEditable = 'true';
body.style.height = se.editor.style.height;
body.style.minHeight = se.editor.style.minHeight;
body.style.maxHeight = se.editor.style.maxHeight;
se.editor.setAttribute('height', se.editor.style.height);
se.editor.setAttribute('min-height', se.editor.style.minHeight);
se.editor.setAttribute('max-height', se.editor.style.maxHeight);
se.editor.style.height = 'auto';
se.editor.style.minHeight = 'auto';
se.editor.style.maxHeight = 'auto';
se.options.placeholder && (body.dataset.placeholder = se.options.placeholder);
se._bindContenteditableOnChange(body);
// Execute this function on mouseup and keyup
const execRange = () => {
const current = se.getRange();
if (current) {
se.range = current;
}
};
// Paste Handler
body.onpaste = (e) => {
// Editor Paste Handler
if (se.options.onPaste) {
const content = se.options.onPaste(e);
if (content) {
e.preventDefault();
se.executeCommand('insertHTML', content);
return true;
}
}
const afterPaste = () => {
// After the paste
setTimeout(() => {
// Editor After Paste Handler
se.options.afterPaste && se.options.afterPaste(e);
}, 10);
se.overflow();
};
// Paste image logic
if (e.clipboardData.files && e.clipboardData.files.length > 0 ) {
e.preventDefault();
se.insertImages(e.clipboardData.files).finally(() => {
afterPaste();
});
return true;
}
// pasting text content
if (e.clipboardData.items && e.clipboardData.items.length > 0 && e.clipboardData.items[0].type === 'text/plain') {
let plainText = e.clipboardData.getData('text/plain');
if (se.validURL(plainText.trim())) {
// get meta data
if (se.options.metaUrl) {
se.getMeta(plainText).then((res) => {
const { url, title, image, description } = res;
url && title && image && se.createMetaDataElement(url, image, title, description);
});
}
}
}
let pastedHTML = false;
if (e.clipboardData.types.includes('text/html')) {
//get the html
pastedHTML = e.clipboardData.getData('text/html');
}
// Wrap pasted link content for resetting the range
if (pastedHTML) {
e.preventDefault();
let pasteNode = document.createElement('span');
pasteNode.innerHTML = pastedHTML;
//cleanup styles
this.pruneInlineStyles(pasteNode);
//cleanup css classes
this.cleanCss(pasteNode);
//sanitize
if (se.options.sanitizePaste) {
pasteNode = this.scrubHTML(pasteNode);
}
pasteNode.setAttribute('class', 'se-pasted-content');
se.executeCommand('insertHTML', pasteNode.innerHTML);
}
afterPaste();
};
body.onkeydown = (e) => {
switch (e.key) {
case 'Tab':
if (se.options.canTab) {
e.shiftKey ? se.executeCommand('outdent') : se.executeCommand('indent');
e.preventDefault();
}
break;
case 'Shift':
break;
case 'Backspace':
se.textBuffer && (se.textBuffer = se.textBuffer.substring(0, se.textBuffer.length - 1));
break;
case 'Control':
break;
case 'Semicolon':
if (e.shiftKey) {
se.textBuffer ? (se.textBuffer += ':') : (se.textBuffer = ':');
}
break;
default:
se.textBuffer ? (se.textBuffer += e.key) : (se.textBuffer = e.key);
break;
}
};
// State of the editor
const bodyKeyup = body.onkeyup;
body.onkeyup = (e) => {
bodyKeyup && bodyKeyup();
execRange();
if (e.key) {
switch (e.key) {
case 'Enter':
se.options.onEnter && se.options.onEnter(e);
break;
default:
break;
}
}
se.setFontStates();
se.toolbarState();
};
// addEventListener('keyup', (e) => {
// const tab = (e.key === 'Tab' || e.keyCode === 9);
// if (tab && document.activeElement === se.body) {
// if (se.body.textContent.trim() === '') {
// const r = se.getRange();
// if (r) {
// const selNode = document.createElement('span');
// selNode.innerHTML = ' ';
// se.body.append(selNode);
// r.selectNode(selNode);
// r.collapse();
// }
// } else {
// const r = se.getRange();
// if (r) {
// r.selectNodeContents(se.body);
// r.collapse();
// }
// }
// }
// if (tab && document.activeElement !== se.body && se.body.textContent.trim() === '') {
// se.clearContent();
// }
// });
const bodyFocus = body.onfocus;
body.onfocus = (e) => {
!se.browser.isEdge() && se.setRange();
window.addEventListener('mouseup', execRange);
se.editor.classList.add('se-focus');
if (se.body.textContent.trim() === '') {
const r = se.getRange();
if (r) {
const selNode = document.createTextNode('');
se.body.append(selNode);
setTimeout(() => {
se.getRange().selectNode(selNode);
selNode.remove();
}, 0);
}
}
if (se.scrollPosition && !se.browser.isEdge()) {
body.scrollTo(se.scrollPosition);
}
bodyFocus && bodyFocus();
};
// Bind state management event
body.addEventListener('focus', () => {
// disables all states
se.options.toolbarOptions.forEach((opt) => {
if (typeof opt === 'string') {
if (document.queryCommandState('insertUnorderedList') && opt === 'insertUnorderedList') {
return false;
}
if (document.queryCommandState('insertOrderedList') && opt === 'insertOrderedList') {
return false;
}
document.queryCommandState(opt) && se.executeCommand(opt);
}
});
// enable only active states
se.getActiveOptions().forEach((opt) => {
!document.queryCommandState(opt) && se.executeCommand(opt);
});
});
const bodyBlur = body.onblur;
body.onblur = (e) => {
se.editor.classList.remove('se-focus');
window.removeEventListener('mouseup', execRange);
se.scrollPosition = {
y: body.scrollTop,
x: body.scrollWidth
};
se.textBuffer = null;
se.clearLinksToEdit();
se.clearImagesToEdit();
bodyBlur && bodyBlur();
};
const bodyClick = body.onclick;
body.onclick = (event) => {
se.closeAllMenus();
se.setFontStates();
body.textContent && se.toolbarState();
se.handleImageClick(event.target);
bodyClick && bodyClick();
};
return body;
}
/**
* Intializes the StrivenEditor link menu popup
* @returns {HTMLElement} The StrivenEditor link menu
*/
initLinkMenu() {
const se = this;
const linkMenu = document.createElement('div');
const linkMenuHeader = document.createElement('p');
const linkMenuForm = document.createElement('div');
const linkMenuButtons = document.createElement('div');
const linkMenuButton = document.createElement('button');
const linkMenuCloseButton = document.createElement('button');
const linkMenuFormLabel = document.createElement('p');
const linkMenuFormInput = document.createElement('input');
const linkMenuCheck = document.createElement('input');
function resetInput() {
linkMenuFormInput.value = 'http://';
}
linkMenu.id = 'link-menu';
linkMenu.classList.add('se-popup', 'se-popup-top');
linkMenu.dataset.active = 'false';
linkMenuForm.classList.add('se-popup-form');
linkMenuFormLabel.classList.add('se-form-label');
linkMenuFormLabel.textContent = 'URL';
linkMenuFormInput.classList.add('se-form-input');
se.options.useBootstrap && linkMenuFormInput.classList.add('form-control');
linkMenuFormInput.type = 'text';
linkMenuFormInput.placeholder = 'Insert a Link';
resetInput();
linkMenuButton.type = 'button';
linkMenuCloseButton.type = 'button';
linkMenuButtons.classList.add('se-popup-button-container');
linkMenuButton.classList.add('se-popup-button', 'se-button-primary');
linkMenuButton.textContent = 'Insert';
linkMenuCloseButton.classList.add('se-popup-button', 'se-button-secondary');
linkMenuCloseButton.textContent = 'Close';
linkMenuButton.onclick = (e) => {
const linkValue = linkMenuFormInput.value;
se.body.focus();
se.setRange();
if (linkValue) {
const linkToEdit = se.body.querySelector('.se-link-to-edit');
if (linkToEdit) {
linkToEdit.setAttribute('href', linkValue);
linkToEdit.innerText = textRowInput.value || linkValue;
linkToEdit.classList.remove('se-link-to-edit');
linkToEdit.setAttribute('target', windowRowInput.checked ? '_blank' : '');
linkToEdit.setAttribute('contenteditable', true);
se.makeLinksClickable([linkToEdit]);
} else if (!se.body.textContent.trim()) {
const linkToCreate = document.createElement('a');
linkToCreate.setAttribute('href', linkValue);
linkToCreate.innerText = textRowInput.value || linkValue;
linkToCreate.setAttribute('contenteditable', true);
linkToCreate.setAttribute('target', windowRowInput.checked ? '_blank' : '');
se.body.append(linkToCreate);
se.makeLinksClickable([linkToCreate]);
}
if (se.options.metaUrl && se.validURL(linkValue)) {
se.getMeta(linkValue).then((res) => {
const { url, image, title, description } = res;
url && image && title && se.createMetaDataElement(url, image, title, description);
});
}
// trigger input event
let inpEvent = new Event('input', { cancelable: true, bubbles: true });
se.body.dispatchEvent(inpEvent);
se.closeLinkMenu();
} else {
se.body.focus();
se.closeLinkMenu();
}
resetInput();
};
linkMenuCloseButton.onclick = (e) => {
se.body.focus();
se.closeLinkMenu();
resetInput();
};
linkMenuHeader.classList.add('se-popup-header');
linkMenuHeader.innerText = 'Insert Link';
linkMenu.appendChild(linkMenuHeader);
linkMenuForm.appendChild(linkMenuFormLabel);
linkMenuForm.appendChild(linkMenuFormInput);
const textRow = linkMenuForm.cloneNode(true);
const textRowLabel = textRow.querySelector('.se-form-label');
const textRowInput = textRow.querySelector('.se-form-input');
if (textRowLabel) {
textRowLabel.innerText = 'Text';
}
if (textRowInput) {
textRowInput.value = '';
textRowInput.placeholder = 'Text content';
}
const windowRow = linkMenuForm.cloneNode(true);
const windowRowLabel = windowRow.querySelector('.se-form-label');
const windowRowInput = windowRow.querySelector('.se-form-input');
if (windowRow) {
windowRow.setAttribute('style', 'justify-content: flex-end; align-items: center; flex-direction: row-reverse');
}
if (windowRowLabel) {
windowRowLabel.innerText = 'Open in new window';
windowRowLabel.style.marginLeft = '5px';
}
if (windowRowInput) {
windowRowInput.checked = true;
windowRowInput.setAttribute('type', 'checkbox');
windowRowInput.setAttribute('style', 'width: auto');
}
linkMenu.appendChild(linkMenuForm);
linkMenu.appendChild(textRow);
linkMenu.appendChild(windowRow);
linkMenuButtons.appendChild(linkMenuButton);
linkMenuButtons.appendChild(linkMenuCloseButton);
linkMenu.appendChild(linkMenuButtons);
[...linkMenu.getElementsByTagName('input')].forEach((inp) => {
inp.onkeydown = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
}
};
inp.onblur = () => {
setTimeout(() => se.clearLinksToEdit(), 200);
};
});
return linkMenu;
}
/**
* Initializes the StrivenEditor image menu popup
* @returns {HTMLElement} The StrivenEditor image menu
*/
initImageMenu() {
const se = this;
const imageMenu = document.createElement('div');
const imageMenuHeader = document.createElement('div');
// Upload tab button
const imageMenuUploadTabButton = document.createElement('button');
imageMenuUploadTabButton.classList.add('se-tab-button', 'se-tab-button-upload', 'tab-button-active');
imageMenuUploadTabButton.type = 'button';
imageMenuUploadTabButton.tabIndex = 1;
// Link tab button
const imageMenuLinkTabButton = document.createElement('button');
imageMenuLinkTabButton.classList.add('se-tab-button', 'se-tab-button-link');
imageMenuLinkTabButton.type = 'button';
imageMenuUploadTabButton.tabIndex = 2;
// Image Menu Tabs
const imageMenuUploadTab = document.createElement('div');
imageMenuUploadTab.classList.add('se-image-menu-tab', 'se-image-menu-upload-tab');
const imageMenuLinkTab = document.createElement('div');
imageMenuLinkTab.classList.add('se-image-menu-tab', 'se-image-menu-link-tab');
imageMenuLinkTab.style.display = 'none';
// Upload form inputs
const imageMenuUploadInput = document.createElement('input');
imageMenuUploadInput.type = 'file';
imageMenuUploadInput.accept = 'image/jpeg,image/webp,image/gif,image/png,image/svg+xml,image/bmp,image/x-icon';
imageMenuUploadInput.multiple = true;
imageMenuUploadInput.style.display = 'none';
// Drop zone
const imageMenuUploadDropZone = document.createElement('div');
imageMenuUploadDropZone.classList.add('se-file-drop-dropzone');
const imageMenuUploadDropZoneText = document.createElement('p');
imageMenuUploadDropZoneText.textContent = 'Click to upload OR drag and drop images here';
imageMenuUploadDropZone.appendChild(createSVG(UPLOADICON, undefined, '48px', '48px'));
imageMenuUploadDropZone.appendChild(imageMenuUploadDropZoneText);
// Link form inputs
const imageMenuForm = document.createElement('div');
const imageMenuButtons = document.createElement('div');
const imageInsertButton = document.createElement('button');
const imageMenuCloseButton = document.createElement('button');
const imageMenuFormLabel = document.createElement('p');
const imageMenuFormSourceInput = document.createElement('input');
imageMenu.id = 'image-menu';
imageMenu.classList.add('se-popup', 'se-popup-top');
imageMenu.dataset.active = 'false';
imageMenuForm.classList.add('se-popup-form');
imageMenuFormLabel.classList.add('se-form-label');
imageMenuFormLabel.textContent = 'Image URL';
// Set up image URL input field
imageMenuFormSourceInput.classList.add('se-form-input');
se.options.useBootstrap && imageMenuFormSourceInput.classList.add('form-control');
imageMenuFormSourceInput.type = 'text';
im