jodit
Version:
Jodit is awesome and usefully wysiwyg editor with filebrowser
531 lines (468 loc) • 13.8 kB
text/typescript
/*!
* Jodit Editor (https://xdsoft.net/jodit/)
* Licensed under GNU General Public License version 2 or later or a commercial license or MIT;
* For GPL see LICENSE-GPL.txt in the project root for license information.
* For MIT see LICENSE-MIT.txt in the project root for license information.
* For commercial licenses see https://xdsoft.net/jodit/commercial/
* Copyright (c) 2013-2019 Valeriy Chupurnov. All rights reserved. https://xdsoft.net
*/
import { IJodit } from '../types/jodit';
import {
IDictionary,
IFileBrowser,
IFileBrowserCallBackData,
IRGB,
IUploader,
IUploaderData
} from '../types/';
import { Dom } from './Dom';
import {
$$,
each,
hexToRgb,
isPlainObject,
normalizeColor,
val,
hasBrowserColorPicker
} from './helpers/';
import { ToolbarIcon } from './toolbar/icon';
export namespace Widget {
/**
* Build color picker
*
* @param {Jodit} editor
* @param {function} callback Callback 'function (color) {}'
* @param {string} [coldColor] Color value ex. #fff or rgb(123, 123, 123) or rgba(123, 123, 123, 1)
* @example
* ```javascript
* $tabs = TabsWidget(editor, {
* 'Text' : ColorPickerWidget(editor, function (color) {
* box.style.color = color;
* }, box.style.color),
* 'Background' : ColorPickerWidget(editor, function (color) {
* box.style.backgroundColor = color;
* }, box.style.backgroundColor),
* });
* ```
*/
export const ColorPickerWidget = (
editor: IJodit,
callback: (newColor: string) => void,
coldColor: string
): HTMLDivElement => {
const valueHex = normalizeColor(coldColor),
form: HTMLDivElement = editor.create.div('jodit_colorpicker'),
iconEye: string = editor.options.textIcons
? ''
: ToolbarIcon.getIcon('eye'),
iconEraser: string = editor.options.textIcons
? `<span>${editor.i18n('eraser')}</span>`
: ToolbarIcon.getIcon('eraser'),
iconPalette: string = editor.options.textIcons
? `<span>${editor.i18n('palette')}</span>`
: ToolbarIcon.getIcon('palette'),
setColor = (target: HTMLElement, color: string) => {
target.innerHTML = ToolbarIcon.getIcon('eye');
target.classList.add('active');
const colorRGB: IRGB | null = hexToRgb(color);
if (colorRGB) {
(target.firstChild as HTMLElement).style.fill =
'rgb(' +
(255 - colorRGB.r) +
',' +
(255 - colorRGB.g) +
',' +
(255 - colorRGB.b) +
')';
}
},
eachColor = (colors: string[] | IDictionary<string[]>) => {
const stack: string[] = [];
if (isPlainObject(colors)) {
Object.keys(colors).forEach(key => {
stack.push(
'<div class="jodit_colorpicker_group jodit_colorpicker_group-' +
key +
'">'
);
stack.push(eachColor((colors as any)[key]));
stack.push('</div>');
});
} else if (Array.isArray(colors)) {
colors.forEach(color => {
stack.push(
'<a ' +
(valueHex === color ? ' class="active" ' : '') +
' title="' +
color +
'" style="background-color:' +
color +
'" data-color="' +
color +
'" href="javascript:void(0)">' +
(valueHex === color ? iconEye : '') +
'</a>'
);
});
}
return stack.join('');
};
form.appendChild(
editor.create.fromHTML(
'<div>' + eachColor(editor.options.colors) + '</div>'
)
);
form.appendChild(
editor.create.fromHTML(
'<a ' +
(editor.options.textIcons
? 'class="jodit_text_icon"'
: '') +
' data-color="" href="javascript:void(0)">' +
iconEraser +
'</a>'
)
);
if (editor.options.showBrowserColorPicker && hasBrowserColorPicker()) {
form.appendChild(
editor.create.fromHTML(
'<span>' +
'<em ' +
(editor.options.textIcons
? 'class="jodit_text_icon"'
: '') +
'>' +
iconPalette +
'</em>' +
'<input type="color" value=""/>' +
'</span>'
)
);
editor.events.on(form, 'change', (e: MouseEvent) => {
e.stopPropagation();
let target: HTMLInputElement = e.target as HTMLInputElement;
if (
!target ||
!target.tagName ||
target.tagName.toUpperCase() !== 'INPUT'
) {
return;
}
const color: string = target.value || '';
if (color) {
setColor(target, color);
}
if (callback && typeof callback === 'function') {
callback(color);
}
e.preventDefault();
});
}
editor.events.on(form, 'mousedown touchend', (e: MouseEvent) => {
e.stopPropagation();
let target: HTMLElement = e.target as HTMLElement;
if (
(!target ||
!target.tagName ||
target.tagName.toUpperCase() === 'SVG' ||
target.tagName.toUpperCase() === 'PATH') &&
target.parentNode
) {
target = Dom.closest(
target.parentNode,
'A',
editor.editor
) as HTMLElement;
}
if (target.tagName.toUpperCase() !== 'A') {
return;
}
const active: HTMLElement | null = form.querySelector('a.active');
if (active) {
active.classList.remove('active');
active.innerHTML = '';
}
const color: string = target.getAttribute('data-color') || '';
if (color) {
setColor(target, color);
}
if (callback && typeof callback === 'function') {
callback(color);
}
e.preventDefault();
});
return form;
};
/**
* Build tabs system
*
* @param {Jodit} editor
* @param {object} tabs PlainObject where 'key' will be tab's Title and `value` is tab's content
* @param {object} state You can use for this param any HTML element for remembering active tab
* @param {string} state.activeTab
*
* @example
* ```javascript
* let tabs = widget.create('Tabs', {
* 'Images': '<div>Images</div>',
* 'Title 2': Jodit.modules.Helpers.dom('<div>Some content</div>'),
* 'Color Picker': ColorPickerWidget(editor, function (color) {
* box.style.color = color;
* }, box.style.color),
* });
* ```
*/
export const TabsWidget = (
editor: IJodit,
tabs: IDictionary<(() => void) | HTMLElement>,
state?: { __activeTab: string }
): HTMLDivElement => {
const box: HTMLDivElement = editor.create.div('jodit_tabs'),
tabBox: HTMLDivElement = editor.create.div('jodit_tabs_wrapper'),
buttons: HTMLDivElement = editor.create.div('jodit_tabs_buttons'),
nameToTab: IDictionary<{
button: HTMLElement;
tab: HTMLElement;
}> = {};
let
firstTab: string = '',
tabcount: number = 0;
box.appendChild(buttons);
box.appendChild(tabBox);
each<(() => void) | HTMLElement>(tabs, (name: string, tabOptions) => {
const
tab = editor.create.div('jodit_tab'),
button = editor.create.element('a', {
href: 'javascript:void(0);'
});
if (!firstTab) {
firstTab = name.toString();
}
button.innerHTML = /<svg/.test(name.toString()) ? name : editor.i18n(name.toString());
buttons.appendChild(button);
if (typeof tabOptions !== 'function') {
tab.appendChild(tabOptions);
} else {
tab.appendChild(editor.create.div('jodit_tab_empty'));
}
tabBox.appendChild(tab);
editor.events.on(button, 'mousedown touchend', (e: MouseEvent) => {
$$('a', buttons).forEach(a => {
a.classList.remove('active');
});
$$('.jodit_tab', tabBox).forEach(a => {
a.classList.remove('active');
});
button.classList.add('active');
tab.classList.add('active');
if (typeof tabOptions === 'function') {
tabOptions.call(editor);
}
e.stopPropagation();
if (state) {
state.__activeTab = name.toString();
}
return false;
});
nameToTab[name] = {
button,
tab
};
tabcount += 1;
});
if (!tabcount) {
return box;
}
$$('a', buttons).forEach(a => {
a.style.width = (100 / tabcount).toFixed(10) + '%';
});
if (!state || !state.__activeTab || !nameToTab[state.__activeTab]) {
nameToTab[firstTab].button.classList.add('active');
nameToTab[firstTab].tab.classList.add('active');
} else {
nameToTab[state.__activeTab].button.classList.add('active');
nameToTab[state.__activeTab].tab.classList.add('active');
}
return box;
};
/**
* Generate 3 tabs
* upload - Use Drag and Drop
* url - By specifying the image url
* filebrowser - After opening the file browser . In the absence of one of the parameters will be less tabs
*
* @params {Object} callbacks Object with keys `url`, `upload` and `filebrowser`, values which are callback
* functions with different parameters
* @param {Function} callbacks.upload - function that will be called when the user selects a file or using drag
* and drop files to the `Upload` tab
* @param {Function} callbacks.url - function that will be called when the user enters the URL of the tab image
* and alternative text for images
* @param {Function} callbacks.filebrowser - function that will be called when the user clicks on the file browser
* tab, and then choose any image in the window that opens, faylbrauzera
* @params {HTMLNode} image image object
* @example
* ```javascript
* let widget = new Jodit.modules.Widget(editor);
*
* return widget.create('ImageSelector', {
* url: function (url, alt) {
* editor.selections.insertImage(url);
* },
* upload: function (images) {
* editor.selections.insertImage(images[0]);
* },
* filebrowser: function (images) {
* editor.selections.insertImage(images[0]);
* }
* }, image);
* ```
*/
interface ImageSelectorCallbacks {
url?: (this: IJodit, url: string, alt: string) => void;
filebrowser?: (data: IFileBrowserCallBackData) => void;
upload?: (this: IJodit, data: IFileBrowserCallBackData) => void;
}
/**
*
* @param {Jodit} editor
* @param {Widget.ImageSelectorCallbacks} callbacks
* @param {HTMLElement} elm
* @param {Function} close Close popup
* @param {boolean} isImage
* @return {HTMLDivElement}
* @constructor
*/
export const FileSelectorWidget = (
editor: IJodit,
callbacks: ImageSelectorCallbacks,
elm: HTMLElement | null,
close: () => void,
isImage: boolean = true
): HTMLDivElement => {
let currentImage: any;
const tabs: IDictionary<HTMLElement> | IDictionary<() => void> = {};
if (
callbacks.upload &&
editor.options.uploader &&
(editor.options.uploader.url ||
editor.options.uploader.insertImageAsBase64URI)
) {
const dragbox: HTMLElement = editor.create.fromHTML(
'<div class="jodit_draganddrop_file_box">' +
'<strong>' +
editor.i18n(isImage ? 'Drop image' : 'Drop file') +
'</strong>' +
'<span><br> ' +
editor.i18n('or click') +
'</span>' +
'<input type="file" accept="' +
(isImage ? 'image/*' : '*') +
'image/*" tabindex="-1" dir="auto" multiple=""/>' +
'</div>'
);
editor.getInstance<IUploader>('Uploader').bind(
dragbox,
(resp: IUploaderData) => {
if (typeof callbacks.upload === 'function') {
callbacks.upload.call(editor, {
baseurl: resp.baseurl,
files: resp.files
} as IFileBrowserCallBackData);
}
},
(error: Error) => {
editor.events.fire('errorMessage', error.message);
}
);
const icon = editor.options.textIcons
? ''
: ToolbarIcon.getIcon('upload');
tabs[icon + editor.i18n('Upload')] = dragbox;
}
if (callbacks.filebrowser) {
if (
editor.options.filebrowser.ajax.url ||
editor.options.filebrowser.items.url
) {
const icon = editor.options.textIcons
? ''
: ToolbarIcon.getIcon('folder');
tabs[icon + editor.i18n('Browse')] = () => {
close && close();
if (callbacks.filebrowser) {
(editor.getInstance(
'FileBrowser'
) as IFileBrowser).open(callbacks.filebrowser, isImage);
}
};
}
}
if (callbacks.url) {
const form: HTMLFormElement = editor.create.fromHTML(
'<form onsubmit="return false;" class="jodit_form">' +
'<input type="text" required name="url" placeholder="http://"/>' +
'<input type="text" name="text" placeholder="' +
editor.i18n('Alternative text') +
'"/>' +
'<div style="text-align: right">' +
'<button>' +
editor.i18n('Insert') +
'</button>' +
'</div>' +
'</form>'
) as HTMLFormElement,
button: HTMLButtonElement = form.querySelector(
'button'
) as HTMLButtonElement,
url: HTMLInputElement = form.querySelector(
'input[name=url]'
) as HTMLInputElement;
currentImage = null;
if (
elm &&
elm.nodeType !== Node.TEXT_NODE &&
(elm.tagName === 'IMG' || $$('img', elm).length)
) {
currentImage = elm.tagName === 'IMG' ? elm : $$('img', elm)[0];
val(form, 'input[name=url]', currentImage.getAttribute('src'));
val(form, 'input[name=text]', currentImage.getAttribute('alt'));
button.innerText = editor.i18n('Update');
}
if (
elm &&
elm.nodeType !== Node.TEXT_NODE &&
elm.nodeName === 'A'
) {
val(form, 'input[name=url]', elm.getAttribute('href') || '');
val(form, 'input[name=text]', elm.getAttribute('title') || '');
button.innerText = editor.i18n('Update');
}
form.addEventListener(
'submit',
(event: Event) => {
event.preventDefault();
event.stopPropagation();
if (!val(form, 'input[name=url]')) {
url.focus();
url.classList.add('jodit_error');
return false;
}
if (typeof callbacks.url === 'function') {
callbacks.url.call(
editor,
val(form, 'input[name=url]'),
val(form, 'input[name=text]')
);
}
return false;
},
false
);
const icon = editor.options.textIcons
? ''
: ToolbarIcon.getIcon('link');
tabs[icon + ' URL'] = form;
}
return TabsWidget(editor, tabs);
};
}