Merge pull request #4332 from umbraco/temp8-macros-dont-render-and-other-macro-bugs

v8 Macros don't render in the RTE and various
This commit is contained in:
Bjarke Berg
2019-02-04 13:23:46 +01:00
committed by GitHub
88 changed files with 801 additions and 1231 deletions

View File

@@ -15598,9 +15598,9 @@
}
},
"tinymce": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.0.tgz",
"integrity": "sha512-hrPeCLXY/sVCo3i64CTW8P5xbDiEI8Uii/vWpcmQWAMhex6GWWd2U+L8WIMj5tKKGdfcIQAJfpfQthc/92bcKw=="
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.2.tgz",
"integrity": "sha512-ZRoTGG4GAsOI73QPSNkabO7nkoYw9H6cglRB44W2mMkxSiqxYi8WJlgkUphk0fDqo6ZD6r3E+NSP4UHxF2lySg=="
},
"tmp": {
"version": "0.0.33",

View File

@@ -39,7 +39,7 @@
"npm": "^6.4.1",
"signalr": "2.4.0",
"spectrum-colorpicker": "1.8.0",
"tinymce": "4.9.0",
"tinymce": "4.9.2",
"typeahead.js": "0.11.1",
"underscore": "1.9.1"
},

View File

@@ -30,7 +30,8 @@ angular.module("umbraco.directives")
promises.push(tinyMceService.getTinyMceEditorConfig({
htmlId: scope.uniqueId,
stylesheets: scope.configuration ? scope.configuration.stylesheets : null,
toolbar: toolbar
toolbar: toolbar,
mode: scope.configuration.mode
}));
// pin toolbar to top of screen if we have focus and it scrolls off the screen

View File

@@ -232,7 +232,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
body_class: 'umb-rte',
//see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix
cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster
cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster,
//this is used to style the inline macro bits, sorry hard coding this form now since we don't have a standalone
//stylesheet to load in for this with only these styles (the color is @pinkLight)
content_style: ".mce-content-body .umb-macro-holder { border: 3px dotted #f5c1bc; padding: 7px; display: block; margin: 3px; } .umb-rte .mce-content-body .umb-macro-holder.loading {background: url(assets/img/loader.gif) right no-repeat; background-size: 18px; background-position-x: 99%;}"
};
if (tinyMceConfig.customConfig) {
@@ -458,7 +462,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
*/
createInsertMacro: function (editor, callback) {
var createInsertMacroScope = this;
let self = this;
let activeMacroElement = null; //track an active macro element
/** Adds custom rules for the macro plugin and custom serialization */
editor.on('preInit', function (args) {
@@ -474,6 +479,16 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
}
});
});
/** when the contents load we need to find any macros declared and load in their content */
editor.on("SetContent", function (o) {
//get all macro divs and load their content
$(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function () {
self.loadMacroContent($(this), null);
});
});
/**
@@ -501,203 +516,26 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
tooltip: 'Insert macro',
onPostRender: function () {
var ctrl = this;
var isOnMacroElement = false;
let ctrl = this;
/**
if the selection comes from a different element that is not the macro's
we need to check if the selection includes part of the macro, if so we'll force the selection
to clear to the next element since if people can select part of the macro markup they can then modify it.
*/
function handleSelectionChange() {
if (!editor.selection.isCollapsed()) {
var endSelection = tinymce.activeEditor.selection.getEnd();
var startSelection = tinymce.activeEditor.selection.getStart();
//don't proceed if it's an entire element selected
if (endSelection !== startSelection) {
//if the end selection is a macro then move the cursor
//NOTE: we don't have to handle when the selection comes from a previous parent because
// that is automatically taken care of with the normal onNodeChanged logic since the
// evt.element will be the macro once it becomes part of the selection.
var $testForMacro = $(endSelection).closest(".umb-macro-holder");
if ($testForMacro.length > 0) {
//it came from before so move after, if there is no after then select ourselves
var next = $testForMacro.next();
if (next.length > 0) {
editor.selection.setCursorLocation($testForMacro.next().get(0));
} else {
selectMacroElement($testForMacro.get(0));
}
}
}
}
}
/** helper method to select the macro element */
function selectMacroElement(macroElement) {
// move selection to top element to ensure we can't edit this
editor.selection.select(macroElement);
// check if the current selection *is* the element (ie bug)
var currentSelection = editor.selection.getStart();
if (tinymce.isIE) {
if (!editor.dom.hasClass(currentSelection, 'umb-macro-holder')) {
while (!editor.dom.hasClass(currentSelection, 'umb-macro-holder') && currentSelection.parentNode) {
currentSelection = currentSelection.parentNode;
}
editor.selection.select(currentSelection);
}
}
}
/**
* Add a node change handler, test if we're editing a macro and select the whole thing, then set our isOnMacroElement flag.
* If we change the selection inside this method, then we end up in an infinite loop, so we have to remove ourselves
* from the event listener before changing selection, however, it seems that putting a break point in this method
* will always cause an 'infinite' loop as the caret keeps changing.
*
* TODO: I don't think we need this anymore with recent tinymce fixes: https://www.tiny.cloud/docs/plugins/noneditable/
* Check if the macro is currently selected and toggle the menu button
*/
function onNodeChanged(evt) {
//set our macro button active when on a node of class umb-macro-holder
var $macroElement = $(evt.element).closest(".umb-macro-holder");
handleSelectionChange();
//set the button active
ctrl.active($macroElement.length !== 0);
if ($macroElement.length > 0) {
var macroElement = $macroElement.get(0);
//remove the event listener before re-selecting
editor.off('NodeChange', onNodeChanged);
selectMacroElement(macroElement);
//set the flag
isOnMacroElement = true;
//re-add the event listener
editor.on('NodeChange', onNodeChanged);
} else {
isOnMacroElement = false;
}
activeMacroElement = getRealMacroElem(evt.element);
//set the button active/inactive
ctrl.active(activeMacroElement !== null);
}
/** when the contents load we need to find any macros declared and load in their content */
editor.on("LoadContent", function (o) {
//get all macro divs and load their content
$(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function () {
createInsertMacroScope.loadMacroContent($(this), null);
});
});
/**
* This prevents any other commands from executing when the current element is the macro so the content cannot be edited
*
* TODO: I don't think we need this anymore with recent tinymce fixes: https://www.tiny.cloud/docs/plugins/noneditable/
*/
editor.on('BeforeExecCommand', function (o) {
if (isOnMacroElement) {
if (o.preventDefault) {
o.preventDefault();
}
if (o.stopImmediatePropagation) {
o.stopImmediatePropagation();
}
return;
}
});
/**
* This double checks and ensures you can't paste content into the rendered macro
*
* TODO: I don't think we need this anymore with recent tinymce fixes: https://www.tiny.cloud/docs/plugins/noneditable/
*/
editor.on("Paste", function (o) {
if (isOnMacroElement) {
if (o.preventDefault) {
o.preventDefault();
}
if (o.stopImmediatePropagation) {
o.stopImmediatePropagation();
}
return;
}
});
//NOTE: This could be another way to deal with the active/inactive state
//editor.on('ObjectSelected', function (e) {});
//set onNodeChanged event listener
editor.on('NodeChange', onNodeChanged);
/**
* Listen for the keydown in the editor, we'll check if we are currently on a macro element, if so
* we'll check if the key down is a supported key which requires an action, otherwise we ignore the request
* so the macro cannot be edited.
*
* TODO: I don't think we need this anymore with recent tinymce fixes: https://www.tiny.cloud/docs/plugins/noneditable/
*/
editor.on('KeyDown', function (e) {
if (isOnMacroElement) {
var macroElement = editor.selection.getNode();
//get the 'real' element (either p or the real one)
macroElement = getRealMacroElem(macroElement);
//prevent editing
e.preventDefault();
e.stopPropagation();
var moveSibling = function (element, isNext) {
var $e = $(element);
var $sibling = isNext ? $e.next() : $e.prev();
if ($sibling.length > 0) {
editor.selection.select($sibling.get(0));
editor.selection.collapse(true);
} else {
//if we're moving previous and there is no sibling, then lets recurse and just select the next one
if (!isNext) {
moveSibling(element, true);
return;
}
//if there is no sibling we'll generate a new p at the end and select it
editor.setContent(editor.getContent() + "<p>&nbsp;</p>");
editor.selection.select($(editor.dom.getRoot()).children().last().get(0));
editor.selection.collapse(true);
}
};
//supported keys to move to the next or prev element (13-enter, 27-esc, 38-up, 40-down, 39-right, 37-left)
//supported keys to remove the macro (8-backspace, 46-delete)
// TODO: Should we make the enter key insert a line break before or leave it as moving to the next element?
if ($.inArray(e.keyCode, [13, 40, 39]) !== -1) {
//move to next element
moveSibling(macroElement, true);
} else if ($.inArray(e.keyCode, [27, 38, 37]) !== -1) {
//move to prev element
moveSibling(macroElement, false);
} else if ($.inArray(e.keyCode, [8, 46]) !== -1) {
//delete macro element
//move first, then delete
moveSibling(macroElement, false);
editor.dom.remove(macroElement);
}
return;
}
});
},
/** The insert macro button click event handler */
@@ -710,11 +548,9 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
//when we click we could have a macro already selected and in that case we'll want to edit the current parameters
//so we'll need to extract them and submit them to the dialog.
var macroElement = editor.selection.getNode();
macroElement = getRealMacroElem(macroElement);
if (macroElement) {
if (activeMacroElement) {
//we have a macro selected so we'll need to parse it's alias and parameters
var contents = $(macroElement).contents();
var contents = $(activeMacroElement).contents();
var comment = _.find(contents, function (item) {
return item.nodeType === 8;
});
@@ -724,7 +560,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
var syntax = comment.textContent.trim();
var parsed = macroService.parseMacroSyntax(syntax);
dialogData = {
macroData: parsed
macroData: parsed,
activeMacroElement: activeMacroElement //pass the active element along so we can retrieve it later
};
}
@@ -737,7 +574,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
});
},
insertMacroInEditor: function (editor, macroObject) {
insertMacroInEditor: function (editor, macroObject, activeMacroElement) {
//Important note: the TinyMce plugin "noneditable" is used here so that the macro cannot be edited,
// for this to work the mceNonEditable class needs to come last and we also need to use the attribute contenteditable = false
// (even though all the docs and examples say that is not necessary)
//put the macro syntax in comments, we will parse this out on the server side to be used
//for persisting.
@@ -746,11 +587,18 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
var uniqueId = "umb-macro-" + editor.dom.uniqueId();
var macroDiv = editor.dom.create('div',
{
'class': 'umb-macro-holder ' + macroObject.macroAlias + ' mceNonEditable ' + uniqueId
'class': 'umb-macro-holder ' + macroObject.macroAlias + " " + uniqueId + ' mceNonEditable',
'contenteditable': 'false'
},
macroSyntaxComment + '<ins>Macro alias: <strong>' + macroObject.macroAlias + '</strong></ins>');
editor.selection.setNode(macroDiv);
//if there's an activeMacroElement then replace it, otherwise set the contents of the selected node
if (activeMacroElement) {
activeMacroElement.replaceWith(macroDiv); //directly replaces the html node
}
else {
editor.selection.setNode(macroDiv);
}
var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId));
@@ -1167,10 +1015,15 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
pinToolbar : function (editor) {
//we can't pin the toolbar if this doesn't exist (i.e. when in distraction free mode)
if (!editor.editorContainer) {
return;
}
var tinyMce = $(editor.editorContainer);
var toolbar = tinyMce.find(".mce-toolbar");
var toolbarHeight = toolbar.height();
var tinyMceRect = tinyMce[0].getBoundingClientRect();
var tinyMceRect = editor.editorContainer.getBoundingClientRect();
var tinyMceTop = tinyMceRect.top;
var tinyMceBottom = tinyMceRect.bottom;
var tinyMceWidth = tinyMceRect.width;
@@ -1288,7 +1141,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
}
});
var self = this;
let self = this;
//create link picker
self.createLinkPicker(args.editor, function (currentTarget, anchorElement) {
@@ -1346,7 +1199,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
dialogData: dialogData,
submit: function (model) {
var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
self.insertMacroInEditor(args.editor, macroObject);
self.insertMacroInEditor(args.editor, macroObject, dialogData.activeMacroElement);
editorService.close();
},
close: function () {

View File

@@ -25,13 +25,6 @@
padding:10px;
}
/* loader for macro loading in tinymce*/
.umb-rte .mce-content-body .umb-macro-holder.loading {
background: url(img/loader.gif) right no-repeat;
background-size: 18px;
background-position-x: 99%;
}
.umb-rte .mce-container {
box-sizing: border-box;
}