jsharmony-cms
Version:
920 lines (835 loc) • 34.5 kB
JavaScript
/*
Copyright 2020 apHarmony
This file is part of jsHarmony.
jsHarmony is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
jsHarmony is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this package. If not, see <http://www.gnu.org/licenses/>.
*/
/* globals tinymce */
exports = module.exports = function(jsh, cms, editor){
var _ = jsh._;
var $ = jsh.$;
/**
* @typedef {Object} IconDefinition
* @property {string} name - the name the icon is registered as
* @property {string} html - the html for the icon
*/
/**
* @typedef {Object} ComponentEvent
* @property {string} id - the ID of the component for the event target.
* @property {string} type - the component type for the event target.
*/
/**
* @typedef {Object} EditorComponent
* @property {string} componentType
* @property {bool} hasData
* @property {bool} hasProperties
* @property {string} iconId
* @property {string} menuLabel
*
*/
/**
* Each icon definition will be registered with the editor
* and available for use within the editor by name property.
* @type {Object.<string, IconDefinition>}
**/
var ICONS = {
edit: {
name: 'material_edit',
html: '<span class="material-icons" style="font-family: \'Material Icons\' !important;font-size:18px;">edit</span>'
},
settings: {
name: 'material_setting',
html: '<span class="material-icons" style="font-family: \'Material Icons\' !important;font-size:18px;">settings</span>'
},
widgets: {
name: 'material_widgets',
html: '<span class="material-icons" style="font-family: \'Material Icons\' !important;font-size:18px;">widgets</span>'
},
search: {
name: 'search',
html: '<span class="material-icons" style="font-family: \'Material Icons\' !important;font-size:18px;">search</span>'
}
};
/**
* This defines commands that can be used for the plugin.
* @type {Object.<string, string>}
*/
var COMMAND_NAMES = {
editComponentProperties: 'jsharmonyEditComponentProperties',
editComponentData: 'jsharmonyEditComponentData'
};
/**
* Register the JSH CMS Component plugin.
* @public
*/
this.register = function() {
if (tinymce.PluginManager.get('jsHarmonyCms') != undefined) return;
tinymce.PluginManager.add('jsHarmonyCms', function(editor, url) {
return new JsHarmonyComponentPlugin(editor);
});
};
/**
* @class
* @private
* @param {Object} editor - the TinyMce editor instance
*/
function JsHarmonyComponentPlugin(editor) {
this._editor = editor;
this.toolbarOptions = _.extend({}, cms.editor.defaultToolbarOptions);
this.initialize(cms.componentManager.componentTemplates);
}
/**
* Create the menu items used by the toolbar buttons, nested menu, etc..
* @private
* @param {componentTemplates[]} componentTemplates
* @returns {object[]}
*/
JsHarmonyComponentPlugin.prototype.createComponentMenuItems = function(editorComponents) {
var _this = this;
editorComponents = editorComponents || [];
var menuHierarchy = {
root: { items: [], submenus: {} }
};
_.each(editorComponents, function(editorComponent){
var componentTemplate = cms.componentManager.componentTemplates[editorComponent.componentType];
var parentNode = menuHierarchy.root;
if(componentTemplate && componentTemplate.options){
if('menu_visibility' in componentTemplate.options){
if(componentTemplate.options.menu_visibility===false) return;
var menu_visibility = jsh.XExt.JSEval(componentTemplate.options.menu_visibility, componentTemplate, {
jsh: jsh,
cms: cms,
component: componentTemplate,
_: _,
});
if(menu_visibility === false) return;
}
if(_.isArray(componentTemplate.options.menu_parents)){
for(var j=0;j<componentTemplate.options.menu_parents.length;j++){
var menu_parent = componentTemplate.options.menu_parents[j];
if(!(menu_parent in parentNode.submenus)){
parentNode.submenus[menu_parent] = { items: [], submenus: {} };
}
parentNode = parentNode.submenus[menu_parent];
}
}
}
parentNode.items.push({
type: 'menuitem',
text: editorComponent.menuLabel,
icon: editorComponent.iconId,
onAction: function() { _this.insertComponent(editorComponent.componentType); },
});
});
//Apply hierarchy
var getMenuItems = function(parentNode){
var rslt = [];
for(var key in parentNode.submenus){
(function(){
var childKey = key;
var childNode = parentNode.submenus[key];
var childItems = getMenuItems(childNode);
rslt.push({
type: 'nestedmenuitem',
text: childKey,
icon: 'cms_component_folder',
getSubmenuItems: function () {
return childItems;
},
});
})();
}
rslt = rslt.concat(parentNode.items);
//Sort
rslt.sort(function(a,b){
var atext = ((a && a.text)||'').toLowerCase();
var btext = ((b && b.text)||'').toLowerCase();
if(atext > btext) return 1;
if(atext < btext) return -1;
return 0;
});
return rslt;
};
return getMenuItems(menuHierarchy.root);
};
/**
* Create the nested menu for picking components to insert.
* @private
* @param {EditorComponent[]} editorComponents
*/
JsHarmonyComponentPlugin.prototype.createComponentMenuButton = function(editorComponents) {
if(!editorComponents || !editorComponents.length) return;
var _this = this;
this._editor.ui.registry.addNestedMenuItem('jsHarmonyCmsComponent', {
text: 'Component',
icon: ICONS.widgets.name,
getSubmenuItems: function() {
return _this.createComponentMenuItems(editorComponents);
}
});
};
/**
* Create the toolbar menu button for picking components to insert.
* @private
* @param {EditorComponent[]} editorComponents
*/
JsHarmonyComponentPlugin.prototype.createComponentToolbarButton = function(editorComponents) {
if(!editorComponents || !editorComponents.length) return;
var _this = this;
_this._editor.ui.registry.addMenuButton('jsHarmonyCmsComponent', {
icon: ICONS.widgets.name,
text: 'Component',
fetch: function(cb) {
cb(_this.createComponentMenuItems(editorComponents));
}
});
};
JsHarmonyComponentPlugin.prototype.createComponentContextMenu = function(editorComponents) {
if(!editorComponents || !editorComponents.length) return;
var _this = this;
function getComponentType(element){
while(element){
if(element.hasAttribute && element.hasAttribute('data-component')) return element.getAttribute('data-component');
element = element.parentNode;
}
return false;
}
//Disable other context items on components
_.each(_this._editor.ui.registry.getAll().contextMenus, function(menu){
if(menu.update) menu.update = jsh.XExt.chain(menu.update, function(element){
if(getComponentType(element)) return '';
});
});
//Add context menu for components
_this._editor.ui.registry.addContextMenu('jsharmonycmscomponentcontextmenu', {
update: function (element) {
var componentType = getComponentType(element);
if(componentType){
var info = _.find(editorComponents, function(info) { return info.componentType === componentType; });
if (!info) return '';
var menuItems = [];
if(info.hasData) menuItems.push('jsHarmonyCmsComponent_Edit');
if(info.hasProperties) menuItems.push('jsHarmonyCmsComponent_Configure');
return menuItems.join(' | ');
}
return '';
}
});
_this._editor.ui.registry.addMenuItem('jsHarmonyCmsComponent_Edit', {
tooltip: 'Edit Content',
text: 'Edit Content',
icon: ICONS.edit.name,
onAction: function() { _this._editor.execCommand(COMMAND_NAMES.editComponentData); }
});
_this._editor.ui.registry.addMenuItem('jsHarmonyCmsComponent_Configure', {
tooltip: 'Configure',
text: 'Configure',
icon: ICONS.settings.name,
onAction: function() { _this._editor.execCommand(COMMAND_NAMES.editComponentProperties); }
});
};
JsHarmonyComponentPlugin.prototype.createElementPathMenuButton = function() {
var _this = this;
this._editor.ui.registry.addNestedMenuItem('jsHarmonyCmsElementPath', {
text: 'Element Path',
icon: 'visualchars',
getSubmenuItems: function() {
var selection = _this._editor.selection;
if(!selection) return [];
var endNode = selection.getEnd();
if(!endNode) return [];
//Get path from node to editor root
var container = _this._editor.getContentAreaContainer();
var nodes = [];
var currentNode = endNode;
while(currentNode){
if(currentNode == container) break;
nodes.push(currentNode);
currentNode = currentNode.parentNode;
}
return _.map(nodes, function(node) {
return {
type: 'nestedmenuitem',
text: node.tagName,
getSubmenuItems: function () {
return [
{
type: 'menuitem',
text: 'Select Element',
onAction: function () {
var selection = _this._editor.selection;
selection.select(node);
}
},
{
type: 'menuitem',
text: 'Add Line Break Before',
onAction: function () {
var lineBreak = _this._editor.dom.create('p',undefined,' ');
$(lineBreak).insertBefore(node);
var selection = _this._editor.selection;
var textNode = (lineBreak.childNodes && lineBreak.childNodes.length) ? lineBreak.childNodes[0] : lineBreak;
selection.select(textNode);
}
},
{
type: 'menuitem',
text: 'Add Line Break After',
onAction: function () {
var lineBreak = _this._editor.dom.create('p',undefined,' ');
$(lineBreak).insertAfter(node);
var selection = _this._editor.selection;
var textNode = (lineBreak.childNodes && lineBreak.childNodes.length) ? lineBreak.childNodes[0] : lineBreak;
selection.select(textNode);
}
},
{
type: 'menuitem',
text: 'Delete Element',
onAction: function () {
var nextSelection = node.nextSibling || node.previousSibling || node.parentNode;
node.remove();
var selection = _this._editor.selection;
if(nextSelection) selection.select(nextSelection);
_this.checkForUpdate();
}
}
];
}
};
});
}
});
};
JsHarmonyComponentPlugin.prototype.checkForUpdate = function(){
if(!cms.controller) return;
var checkForUpdate = function(){ if(!cms.controller.hasChanges) cms.controller.getValues(); };
checkForUpdate();
setTimeout(checkForUpdate, 100);
};
JsHarmonyComponentPlugin.prototype.setMenuVisibility = function(visible){
var _this = this;
_this.toolbarOptions.show_menu = !!visible;
cms.editor.renderContentEditorToolbar(_this._editor);
};
JsHarmonyComponentPlugin.prototype.setToolbarVisibility = function(visible){
var _this = this;
_this.toolbarOptions.show_toolbar = !!visible;
cms.editor.renderContentEditorToolbar(_this._editor);
};
JsHarmonyComponentPlugin.prototype.setToolbarDock = function(dockPosition){
var _this = this;
_this.toolbarOptions.dock = dockPosition || 'auto';
cms.editor.renderContentEditorToolbar(_this._editor);
};
/**
* Create the "View" toolbar menu button, for hiding the menu and viewing source code.
* @private
*/
JsHarmonyComponentPlugin.prototype.createToolbarViewOptions = function() {
var _this = this;
_this._editor.ui.registry.addMenuButton('jsHarmonyCmsView', {
text: 'View',
icon: ICONS.search.name,
fetch: function (cb) {
return cb([
{
type: 'menuitem',
text: 'Toggle Menu',
onAction: function () {
_this.setMenuVisibility(!_this.toolbarOptions.show_menu);
}
},
{
type: 'menuitem',
text: 'Toggle Toolbar',
onAction: function () {
_this.setToolbarVisibility(!_this.toolbarOptions.show_toolbar);
}
},
{
type: 'menuitem',
icon: 'visualblocks',
text: 'Toggle Outlines',
onAction: function () {
if(document && document.body){
var hasOutlines = $(document.body).hasClass('jsHarmonyCMS_showEditorOutlines');
$(document.body).toggleClass('jsHarmonyCMS_hideEditorOutlines', hasOutlines);
$(document.body).toggleClass('jsHarmonyCMS_showEditorOutlines', !hasOutlines);
if(!jsh.xDialog.length){
var editorManager = tinymce.util.Tools.resolve('tinymce.EditorManager');
editorManager.activeEditor.focus();
}
}
}
},
{
type: 'menuitem',
icon: 'sourcecode',
text: 'Source Code',
onAction: function () {
_this._editor.execCommand('mceCodeEditor');
}
},
]);
}
});
};
JsHarmonyComponentPlugin.prototype.createEndEditToolbarButton = function() {
this._editor.ui.registry.addButton('jsHarmonyCmsEndEdit', {
text: 'End Edit',
icon: 'checkmark',
onAction: function () { cms.editor.endEdit(); }
});
};
JsHarmonyComponentPlugin.prototype.createMenuViewOptions = function() {
//if(!this._editor.settings.isjsHarmonyCmsComponent)
var _this = this;
this._editor.ui.registry.addMenuItem('jsHarmonyCmsToggleMenu', {
text: 'Toggle Menu',
onAction: function () {
_this.setMenuVisibility(!_this.toolbarOptions.show_menu);
}
});
this._editor.ui.registry.addMenuItem('jsHarmonyCmsToggleToolbar', {
text: 'Toggle Toolbar',
onAction: function () {
_this.setToolbarVisibility(!_this.toolbarOptions.show_toolbar);
}
});
_this._editor.ui.registry.addNestedMenuItem('jsharmonyCmsDockToolbar', {
text: 'Dock Toolbar',
getSubmenuItems: function() {
return [
{
type: 'togglemenuitem',
text: 'Top',
onAction: function() {
if(_.includes(['top','top_offset','auto'],_this.toolbarOptions.orig_dock)) _this.setToolbarDock(_this.toolbarOptions.orig_dock);
else _this.setToolbarDock('auto');
},
onSetup: function(api) {
api.setActive((_this.toolbarOptions.dock === 'top')||(_this.toolbarOptions.dock === 'top_offset')||(_this.toolbarOptions.dock === 'auto'));
return function() {};
}
},
{
type: 'togglemenuitem',
text: 'Bottom',
onAction: function(a) {
_this.setToolbarDock('bottom');
},
onSetup: function(api) {
api.setActive(_this.toolbarOptions.dock === 'bottom');
return function() {};
}
}
];
}
});
};
/**
* Create menu button for Spell Check
* @private
* @param {EditorComponent[]} editorComponents
*/
JsHarmonyComponentPlugin.prototype.createSpellCheckMessageMenuButton = function() {
this._editor.ui.registry.addMenuItem('jsHarmonyCmsSpellCheckMessage', {
text: 'Spell Check',
icon: 'spell-check',
onAction: function () {
jsh.XExt.Alert('The editor uses your browser\'s spellcheck.\n\nPress and hold the CTRL key while right-clicking on the misspelled words to see suggestions.\n\n');
}
});
};
JsHarmonyComponentPlugin.prototype.createEndEditMenuButton = function() {
this._editor.ui.registry.addMenuItem('jsHarmonyCmsEndEdit', {
text: 'End Edit',
icon: 'checkmark',
onAction: function () { cms.editor.endEdit(); }
});
};
JsHarmonyComponentPlugin.prototype.createContextToolbar = function(editorComponents) {
var _this = this;
var propButtonId = 'jsharmonyComponentPropEditor';
var dataButtonId = 'jsharmonyComponentDataEditor';
_this._editor.ui.registry.addButton(dataButtonId, {
tooltip: 'Edit Content',
text: 'Edit Content',
icon: ICONS.edit.name,
onAction: function() { _this._editor.execCommand(COMMAND_NAMES.editComponentData); }
});
_this._editor.ui.registry.addButton(propButtonId, {
tooltip: 'Configure',
text: 'Configure',
icon: ICONS.settings.name,
onAction: function() { _this._editor.execCommand(COMMAND_NAMES.editComponentProperties); }
});
var dataAndPropsToolbar = dataButtonId + ' ' + propButtonId;
var dataToolBar = dataButtonId;
var propsToolBar = propButtonId;
var toolbarPredicate = function(enableData, enableProps) {
return function(node) {
var isComponent = _this._editor.dom.is(node, '[data-component]');
if (!isComponent) {
return false;
}
var componentType = _this._editor.dom.getAttrib(node, 'data-component');
var editorComponent = _.find(editorComponents, function(info) { return info.componentType === componentType; });
if (!editorComponent) {
return false;
}
return enableData === editorComponent.hasData && enableProps === editorComponent.hasProperties;
};
};
var addToolbar = function(toolBarConfig, predicate) {
var contextId = 'jsharmonyComponentContextToolbar_' + toolBarConfig;
_this._editor.ui.registry.addContextToolbar(contextId, {
predicate: predicate,
items: toolBarConfig,
scope: 'node',
position: 'node'
});
};
addToolbar(dataAndPropsToolbar, toolbarPredicate(true, true));
addToolbar(dataToolBar, toolbarPredicate(true, false));
addToolbar(propsToolBar, toolbarPredicate(false, true));
};
/**
* Find the component instance if it exits
* @private
* @param {(string | HTMLElement)} element - if type is string then find the component by the string ID,
* if type is an HTMLElement then find element and get ID from the ID attribute.
* @returns {(Object | undefined)}
*/
JsHarmonyComponentPlugin.prototype.getComponentInstance = function(element) {
if (!element) return;
var id = element;
if (!_.isString(element)) {
id = $(element).attr('data-component-id') || '';
}
return cms.componentManager.components[id];
};
/**
* When an undo or redo event occurs in the editor
* the component needs to be re-rendered.
* @private
* @param {object} e - the undo/redo event from the TinyMCE editor
*/
JsHarmonyComponentPlugin.prototype.onUndoRedo = function(e) {
var _this = this;
var content = e.level.content;
if (!content) return;
var parser = new tinymce.html.DomParser({validate: false});
parser.addAttributeFilter('data-component-id', function(nodes, name) {
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var id = node.attributes.map['data-component-id'];
var type = node.attributes.map['data-component'];
if (id && type) {
cms.componentManager.renderContentComponent($(_this._editor.targetElm).find('[data-component-id="' + id + '"]')[0]);
}
}
});
parser.parse(content);
};
JsHarmonyComponentPlugin.prototype.setToolbarOptions = function(toolbarOptions){
var _this = this;
_this.toolbarOptions = _.extend({}, cms.editor.defaultToolbarOptions, toolbarOptions);
if(!('orig_dock' in toolbarOptions)) _this.toolbarOptions.orig_dock = toolbarOptions.dock;
if(!('orig_toolbar_or_menu' in toolbarOptions)) _this.toolbarOptions.orig_toolbar_or_menu = !!toolbarOptions.show_menu || toolbarOptions.show_toolbar;
};
/**
* Initialize the plugin.
* Do not call this more than one time per editor instance.
* @private
* @param {Object[]} components - the component configurations
*/
JsHarmonyComponentPlugin.prototype.initialize = function(components) {
var _this = this;
/** @type {EditorComponent[]} */
var editorComponents = [];
_this._editor.ui.registry.addIcon('cms_component_folder', '<span class="material-icons" style="font-family: \'Material Icons\' !important;font-size:18px;">folder_open</span>');
// Register component icons and build
// component info.
_.forEach(components, function(component) {
if ((component.target == 'content') && component.icon) {
// Icon name MUST be lowercase for TinyMce to work correctly.
var iconRegistryName = ('cms_component_icon_' + component.id).toLowerCase();
var iconMatch = /^(\w+):/.exec(component.icon);
var icon = '';
if (iconMatch) {
var iconType = iconMatch[1];
var iconValue = component.icon.slice(iconMatch[0].length);
if (iconType === 'material') {
icon = '<span class="material-icons" style="font-family: \'Material Icons\' !important;font-size:18px;">' + iconValue + '</span>';
} else if (iconType === 'svg' || iconType === 'html') {
icon = iconValue;
} else {
console.error('Unknown icon family "' + iconType + '"'); // eslint-disable-line no-console
}
} else {
icon = component.icon;
}
_this._editor.ui.registry.addIcon(iconRegistryName, icon);
editorComponents.push({
componentType: component.id,
hasData: ((component.data || {}).fields || []).length > 0,
hasProperties: ((component.properties || {}).fields || []).length > 0,
iconId: iconRegistryName,
menuLabel: component.title || component.id
});
}
});
// Register icons
for (var key in ICONS) {
_this._editor.ui.registry.addIcon(ICONS[key].name, ICONS[key].html);
}
//Create menu buttons, toolbar buttons, and context menu buttons
this.createContextToolbar(editorComponents);
this.createComponentToolbarButton(editorComponents);
this.createToolbarViewOptions();
this.createMenuViewOptions();
this.createElementPathMenuButton();
this.createComponentMenuButton(editorComponents);
this.createSpellCheckMessageMenuButton();
this.createEndEditMenuButton();
this.createEndEditToolbarButton();
this.createComponentContextMenu(editorComponents);
this._editor.on('SetContent', function(info){ if(cms.editor.onSetContent) cms.editor.onSetContent(_this._editor, info); });
this._editor.on('undo', function(info) { _this.onUndoRedo(info); });
this._editor.on('redo', function(info) { _this.onUndoRedo(info); });
this._editor.on('setToolbarOptions', function(e){
if(e && e.toolbarOptions) _this.setToolbarOptions(e.toolbarOptions);
});
this._editor.addCommand(COMMAND_NAMES.editComponentData, function() {
var el = _this._editor.selection.getStart();
_this.openDataEditor(el);
});
this._editor.addCommand(COMMAND_NAMES.editComponentProperties, function() {
var el = _this._editor.selection.getStart();
_this.openPropertiesEditor(el);
});
this._editor.addCommand('jsHarmonyCmsSetToolbarOptions', function(toolbarOptions) {
_this.setToolbarOptions(toolbarOptions);
});
this._editor.addQueryValueHandler('jsHarmonyCmsGetToolbarOptions', function() { return _this.toolbarOptions; });
this._editor.on('init', function() {
_this._editor.serializer.addAttributeFilter('data-component', function(nodes) { _this.serializerFilter(nodes); });
_this._editor.serializer.addAttributeFilter('cms-component', function(nodes) { _this.serializerFilter(nodes); });
_this._editor.parser.addAttributeFilter('data-component', function(nodes) { _this.renderContentComponents(nodes); });
_this._editor.parser.addAttributeFilter('cms-component', function(nodes) { _this.replacePageComponentsWithContentComponents(nodes); });
});
};
/**
* Insert the component into the editor.
* @private
* @param {string} componentType - the type of the component to insert.
*/
JsHarmonyComponentPlugin.prototype.insertComponent = function(componentType) {
var _this = this;
var domUtil = _this._editor.dom;
var selection = _this._editor.selection;
var componentTemplate = cms.componentManager.componentTemplates[componentType];
var isInline = (componentTemplate && componentTemplate.options && componentTemplate.options.editor_container) == 'inline';
if(isInline){
jsh.XExt.execif(!selection.isCollapsed(),
function(next){
selection.collapse(false);
setTimeout(next, 0);
},
function(){
_this._editor.insertContent(_this.createComponentContainer(componentType));
}
);
}
else {
var currentNode = selection.getEnd();
var placeholderId = domUtil.uniqueId();
var placeholder = domUtil.create('div', { id: placeholderId }, '');
$(placeholder).insertBefore(currentNode);
selection.select(placeholder);
selection.collapse(false);
_this._editor.insertContent(_this.createComponentContainer(componentType));
_this._editor.insertContent('<p></p>');
domUtil.remove(domUtil.select('#' + placeholderId));
}
};
/**
* Create the component container HTML string for
* inserting into the editor.
* @private
* @param {string} componentType - the type of component to create
* @returns {string} - HTML string
*/
JsHarmonyComponentPlugin.prototype.createComponentContainer = function(componentType) {
var componentTemplate = cms.componentManager.componentTemplates[componentType];
var isInline = (componentTemplate && componentTemplate.options && componentTemplate.options.editor_container) == 'inline';
var componentCode =
'<'+(isInline?'span':'div')+
' class="mceNonEditable"'+
' data-component="' + jsh.XExt.escapeHTML(componentType) + '"'+
' data-component-properties=""'+
' data-component-content=""'+
' data-is-insert="true"'+
'>'+
(isInline?' ':'') +
'</'+(isInline?'span':'div')+'>';
return componentCode;
};
/**
* Open the data editor for the component.
* @private
* @param {(string | Element)} element - if type is string then find the component by the string ID,
* if type is an HTMLElement then find component from the ID attribute.
*/
JsHarmonyComponentPlugin.prototype.openDataEditor = function(element) {
var component = this.getComponentInstance(element);
if (component && _.isFunction(component.openDataEditor)) {
component.openDataEditor();
}
};
/**
* Open the property editor for the component.
* @private
* @param {(string | Element)} element - if type is string then find the component by the string ID,
* if type is an HTMLElement then find component from the ID attribute.
*/
JsHarmonyComponentPlugin.prototype.openPropertiesEditor = function(element) {
var component = this.getComponentInstance(element);
if (component && _.isFunction(component.openPropertiesEditor)) {
component.openPropertiesEditor();
}
};
/**
* Filter the TinyMce content parsed nodes.
* @private
* @param {Array.<object>} nodes - a list of TinyMce nodes
*/
JsHarmonyComponentPlugin.prototype.renderContentComponents = function(nodes) {
var _this = this;
_.each(nodes, function(node) {
var id = cms.componentManager.getNextComponentId();
node.attr('data-component-id', id);
// Check if editor_container is correct
var componentType = node.attr('data-component');
var componentTemplate = componentType && cms.componentManager.componentTemplates[componentType];
var isInline = (componentTemplate && componentTemplate.options && componentTemplate.options.editor_container) == 'inline';
var nodeIsInline = ((node.name||'').toString().toLowerCase()=='span');
if(nodeIsInline !== isInline){
if(isInline) node.name = 'span';
else node.name = 'div';
}
// Content is not actually in the DOM yet.
// Wait for next loop
var isInitialized = cms.isInitialized;
setTimeout(function() {
cms.componentManager.renderContentComponent($(_this._editor.targetElm).find('[data-component-id="' + id + '"]')[0], {
init: !isInitialized
});
});
});
};
/**
* Replace cms-component elements with data-component
* @private
* @param {Array.<object>} nodes - a list of TinyMce nodes
*/
JsHarmonyComponentPlugin.prototype.replacePageComponentsWithContentComponents = function(nodes) {
var _this = this;
_.each(nodes, function(node) {
var cmsComponent = node.attr('cms-component');
var cmsComponentData = node.attr('cms-component-data');
var cmsComponentProperties = node.attr('cms-component-properties');
if(cmsComponent){
node.attr('data-component', cmsComponent);
node.attr('cms-component', null);
var nodeClass = (node.attr('class')||'');
if(nodeClass) nodeClass += ' ';
nodeClass += 'mceNonEditable';
node.attr('class', nodeClass);
node.attr('contenteditable', 'false');
var defaultProperties = {};
var defaultData = {};
var component = cms.componentManager.componentTemplates[cmsComponent];
if(component){
defaultProperties = cms.componentManager.getDefaultValues(component.properties);
defaultData = cms.componentManager.getDefaultValues(component.data);
}
if(cmsComponentData){
try{
cmsComponentData = JSON.parse(cmsComponentData);
if(_.isArray(cmsComponentData)){
for(var i=0;i<cmsComponentData.length;i++){ cmsComponentData[i] = _.extend({}, defaultData, cmsComponentData[i]); }
cmsComponentData = {items: cmsComponentData};
}
else{
cmsComponentData = _.extend({}, defaultData, cmsComponentData);
cmsComponentData = {item: cmsComponentData};
}
cmsComponentData = JSON.stringify(cmsComponentData);
}
catch(ex){
/* Do nothing */
}
node.attr('data-component-data', btoa(cmsComponentData));
node.attr('cms-component-data', null);
}
if(cmsComponentProperties){
try{
cmsComponentProperties = JSON.parse(cmsComponentProperties);
cmsComponentProperties = _.extend({}, defaultProperties, cmsComponentProperties);
cmsComponentProperties = JSON.stringify(cmsComponentProperties);
}
catch(ex){
/* Do nothing */
}
node.attr('data-component-properties', btoa(cmsComponentProperties));
node.attr('cms-component-properties', null);
}
}
});
_this.renderContentComponents(nodes);
};
/**
* Filter the TinyMce content to find relevant components
* and serialize the components for save.
* @private
* @param {Array.<object>} nodes - a list of TinyMce nodes
*/
JsHarmonyComponentPlugin.prototype.serializerFilter = function(nodes) {
var _this = this;
for(var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var componentAttr = node.attr('data-component');
if (componentAttr == undefined || componentAttr.length < 1) {
continue;
}
node.attr('data-component-id', null);
if(node.attr('class')){
var cls = node.attr('class').toString().trim().split(' ');
var newcls = '';
if(cls.indexOf('initialized')>=0){
for(var j=0;j<cls.length;j++){
if(!cls[j] || (cls[j]=='initialized')) continue;
newcls += cls[j] + ' ';
}
node.attr('class', newcls.trim() || null);
}
}
node.empty();
var newNode = tinymce.html.Node.create('#text');
newNode.value = String.fromCharCode(0x00A0);
node.append(newNode);
_this._editor.dom.setHTML(node, ' ');
}
};
};