In Quilljs Editor, How to insert an undeletable block-level element? - javascript

Here I customize a block element by Quill.import('blots/block/embed') which I insert into the editor content. I would like to know that if there is any way to make it undeletable, therefore the user could not delete it or edit it? Thanks a lot.

I had a similar issue and the solution I came up with was to intercept the keyboard binding for backspace. In the example here I have a custom 'video' blot. So, if backspace is entered and the cursor is on or directly after a video, it does nothing. Here is the documentation for the Keyboard Module for reference: https://quilljs.com/docs/modules/keyboard/
let _this = this;
this.quill = new Quill(this.contentElement, {
modules: {
keyboard: {
bindings: {
video: {
key: 'backspace',
handler: function(range, keycontext) {
let format = _this.quill.getFormat(range.index - 1);
if (!format.video && !keycontext.format.video) {
// propogate to Quill's default
return true;
} // else do nothing to prevent deleting video
}
}
}
}
},
theme: 'snow'
});
Also, another thing to keep in mind, the editor has contenteditable="true", which your custom blot will inherit. So you'll probably want to set contenteditable="false" on the node in your custom blot.

Related

Open Ace.js code editor and select content on click

I have a page containing multiple instances of ace.js editors, all on read-only, displaying code.
I wish to open the editor like so and highlight its content to allow my user to easily copy it, like so:
My current approach does not show the wanted result, it does open an editor with the code inside of it BUT its height is not at the size of its content:
Heres's how I do it:
function openCodeEditor(event, domElement, language) {
event.preventDefault();
getInstance().then((ace) => {
ace.edit(domElement, {
mode: `ace/mode/${language}`,
theme: 'ace/theme/monokai',
startLineNumber: 1,
trim: true,
});
});
}
// STARTS HERE
// Called on page load to highlight each block of code
function syntaxHighlight(domElement, language, showGutter) {
return getInstance().then((ace) => {
ace.require('ace/ext/static_highlight')(domElement, {
mode: `ace/mode/${language}`,
theme: 'ace/theme/monokai',
startLineNumber: 1,
showGutter,
trim: true,
});
// Catch clicks here, and open the editor for selection
domElement.addEventListener('click', (event) =>
openCodeEditor(event, domElement, language)
);
});
}
Does anyone of you have recommendations or perhaps my approach is faulty?
Thank you !
You can set the height of editor to be same as the height of replaced element.
var editor = ace.edit(null, {
value: domElement.textContent,
theme: 'ace/theme/monokai',
mode: `ace/mode/${language}`,
});
editor.container.style.height = domElement.clientHeight + "px"
domElement.replaceWith(editor.container)
but if the goal is simply to allow copying, selecting contents of domElement should work too.

Set Kendo UI Window values globally

I'm working with a lot of Kendo UI windows. Is there some way to specify default values somehow globally? Or maybe a more realistic version, can I create some parent with predefined values and then just overwrite the values I need to change?
For example, I want the same error behavior and a modal parameter for all of the windows, so I would like to do something like:
$("#parentWindow").kendoWindow({
modal: true,
error: function () {
this.close();
new Notification().error();
}
});
And then use the parent window as a base for new windows:
$("#newWindow").kendoWindow({
title: "This window should have the options (modal and error) of the parentWindow",
}).??getTheRestOfTheValuesFromParent()??;
Or rewrite some parameter:
$("#newWindow2").kendoWindow({
modal: false,
title: "A window with overwritten modal parameter",
}).??getTheRestOfTheValuesFromParent()??;
Is it somehow possible to achieve this, is there any possibility of something like C# inheritance?
Maybe it's a stupid question, but I'm not so familiar with JS.
I highly encourage you to create your own wrapper code over all - or at least those more complex - kendo widgets. My team has been doing it for years in a project we use kendo for everything and we are having very positivelly results. The main purpose is what you need: a global behaviour. If after thousand windows coded over your project, you need to change them all, just change the wrapper. It's just a simple jQuery function:
$.fn.MyWindow = function(options) {
var $target = $(this);
var widget = {
_defaultOptions: {
actions: ["Minimize", "Maximize", "Close"],
visible: false,
width: 400,
height: 400,
modal: true
},
_options: options || {},
_target: $target,
_widget: null,
_init: function() {
this._manageOptions();
this._createWidget();
return this;
},
_manageOptions: function() {
// Here you can perform some validations like displaying an error when a parameter is missing or whatever
this._options = $.extend(this._options, this._defaultOptions);
},
_createWidget: function() {
this._widget = this._target.kendoWindow(this._options).data("kendoWindow");
// Create here some behaviours that the widget doesn't haves, like closing the window when user click the black overlay
if (this._options.closeOnOverlayClick) {
$('body').off('click', '.k-overlay').on('click', '.k-overlay', function() {
this._widget.close();
}.bind(this));
}
},
Show: function(center) {
if (center) {
this._widget.center();
}
this._widget.open();
}
};
return widget._init();
};
var wnd = $("#wnd").MyWindow({
title: "My first window",
closeOnOverlayClick: true // Your own parameter
});
// Now you work with your own functions:
wnd.Show(true);
Demo.
There are so many customizations, like your own events - some of those kendo's widgets doesn't haves - etc..
I will just add that there is an article(here) about creating custom Kendo widgets where you can find more information about the specifics of different scenarios that may be implemented.
Ι had a case like yours with kendo windows, kendo grids and kendo dropdownlists. For that I created HtmlHelpers for all my elements and called them when I needed to. Since you are using kendo asp.net-mvc I would recommend to look at this way.
public static WindowBuilder GlobalKendoWindow(this HtmlHelper helper)
{
return helper.Kendo().Window()
.Draggable()
.Animation(true)
.Visible(false)
.AutoFocus(true)
.Modal(true)
.Scrollable(true)
.HtmlAttributes(new { #class = "atn-modal-container" })
.Actions(actions => actions.Minimize().Close())
.Deferred();
}
and render it in my Html like this
#(Html.GlobalKendoWindow()
.Name("addCandidateDialog")
.Title(Html.GetResource(cps, "AddCandidateDialogTitle"))
.LoadContentFrom("AddCandidate", "Candidate")
.Events(events => events.Open("athena.addCandidacy.onAddCandidateOpen").Close("athena.addCandidacy.onAddCandidateClose"))
)

CKEditor - Button to switch shown Toolbar

I'm trying to give a user the ability to swap around the toolbar between presets based on a button press. Ideally that button would be in the CKEditor screen, but I can play around with it.
I've found this SO Article in which I get the concept that you destroy your instance, and re-initialize it with a 'New Config'.
To follow suit I took one of the higher ranked responses, modified it to match my table, and threw it onto a button.
function toolbarSwap(){
var editor = CKEDITOR.instances.editor;
if (editor) { editor.destroy(true); }
CKEDITOR.config.toolbar_Basic = [['Bold','Italic','Underline',
'-','JustifyLeft','JustifyCenter','JustifyRight','-','Undo','Redo']];
CKEDITOR.config.toolbar = 'Basic';
CKEDITOR.config.width=400;
CKEDITOR.config.height=300;
CKEDITOR.replace('editor', CKEDITOR.config);
}
The replace command concerns me since I can't see it working, as to if the data within the editor will go away but either way nothing is happening when I click the button that runs this function.
Is this the best method, or is there a way I can do this within CKEditor directly?
Update
function toolbarSwap(){
var editor = CKEDITOR.instances['editor'];
if (editor) { editor.destroy(true); }
CKEDITOR.config.toolbar_Basic = [['Bold','Italic','Underline',
'-','JustifyLeft','JustifyCenter','JustifyRight','-','Undo','Redo']];
CKEDITOR.config.toolbar = 'Basic';
CKEDITOR.config.width=400;
CKEDITOR.config.height=300;
CKEDITOR.replace('editor', CKEDITOR.config);
}
It seems like modified the instantiation of editor with the ID resolves the issue of it finding it, but the Editor is being emptied every time I click it. Is there a way to reload the config, instead of destroying the instance?
Update 2
function changeToolBar() {
var expanded = true;
if (expanded === true) {
var myToolBar = [{ name: 'verticalCustomToolbar', groups: [ 'basicstyles'], items: [ 'Bold', 'Italic'] }];
var config = {};
config.toolbar = myToolBar;
CKEDITOR.instances.editor.destroy();//destroy the existing editor
CKEDITOR.replace('editor', config);
expanded = false;
} else {
CKEDITOR.instances.editor.destroy();//destroy the existing editor
CKEDITOR.replace('editor', {toolbar: 'Full'});
expanded = true;
console.log("Expand me")
};
}
Here is where I'm at so far. I am not losing the data during the re-init, but I am unable to ever get the 'else' statement to trigger.
My Function was correct, but the var being initialized -in- the function was resetting it's purpose everything. AKA It's -always- true on click
<button type="button" onclick="changeToolBar()">Swap It</button>
function changeToolBar() {
if (expanded === true) {
var myToolBar = [{ name: 'verticalCustomToolbar', groups: [ 'basicstyles'], items: [ 'Bold', 'Italic'] }]; //Generic placeholder
var config = {};
config.toolbar = myToolBar;
CKEDITOR.instances.editor.destroy();//destroy the existing editor
CKEDITOR.replace('editor', config);
console.log(expanded); // Logging to ensure change
expanded = false;
console.log(expanded); // Logging to ensure the change
} else {
CKEDITOR.instances.editor.destroy();//destroy the existing editor
CKEDITOR.replace('editor', {toolbar: 'Full'}); // Toolbar 'full' is a default one
expanded = true;
console.log("Expand me") // Logging if it works
};
}
Can also swap in Full with a predefined config for toolbars.

TinyMCE Enable button while in read only mode

I have a TinyMCE 4.x instance where the text should be in read only mode. But I still have some buttons that I want to have enabled. For example, one button could provide a character count for the part of the text I've selected.
But when I turn on read only mode for TinyMCE all buttons are disabled. Can I enable just my buttons while still retaining read only mode?
It's probably too late for you but other people may pass by here.
I came up by writing this function
function enableTinyMceEditorPlugin(editorId, pluginName, commandName) {
var htmlEditorDiv = document.getElementById(editorId).previousSibling;
var editor = tinymce.get(editorId);
var buttonDiv = htmlEditorDiv.querySelectorAll('.mce-i-' + pluginName.toLowerCase())[0].parentElement.parentElement;
buttonDiv.className = buttonDiv.className.replace(' mce-disabled', '');
buttonDiv.removeAttribute('aria-disabled');
buttonDiv.firstChild.onclick = function () {
editor.execCommand(commandName);
};
}
It does the trick in 2 steps:
make the button clickable (remove mce-disabled CSS class and remove the aria-disabled property)
assign the good command to the click event
And in my editor init event I call the function.
editor.on('init', function () {
if (readOnly) {
editor.setMode('readonly');
enableTinyMceEditorPlugin(htmlEditorId, 'preview', 'mcePreview');
enableTinyMceEditorPlugin(htmlEditorId, 'code', 'mceCodeEditor');
}
});
Current version of TinyMCE for which I wrote this code is 4.4.3. It may break in a future version, specifically about the selectors to get and modify the good HTML elements.
Command identifiers can be found at this page otherwise you can also find them under tinymce\plugins\PluginName\plugin(.min).js
Here is a simple way to enable your custom toolbar button and attach a click event handler inside a read only TinyMCE editor using JQUERY:
//Initialize read only Tinymce editor so that Lock button is also disabled
function initReadOnlyTinyMCE() {
tinymce.init({
selector: '#main'
, toolbar: 'myLockButton'
, body_class: 'main-div'
, content_css: 'stylesheets/index.css'
, readonly: true
, setup: function (readOnlyMain) {
readOnlyMain.addButton('myLockButton', { //Lock button is disabled because readonly is set to true
image: 'images/lock.png'
, tooltip: 'Lock Editor'
});
}
});
}
function displayReadOnlyTinyMCEwithLockButtonEnabled() {
var edContent = $('main').html();
$("#main").empty();
initReadOnlyTinyMCE(true);
tinyMCE.activeEditor.setContent(edContent);
//enable the lock button and attach a click event handler
$('[aria-label="Lock Editor"]').removeClass("mce-disabled");
$('[aria-label="Lock Editor"]').removeAttr("aria-disabled");
$('[aria-label="Lock Editor"]').attr("onclick", "LockEditor()");
}
function LockEditor() {
alert("Tiny mce editor is locked by the current user!!");
//Write your logic to lock the editor...
}
I couldn't find an easy way to do this. The simplest way is to remove the contenteditable attribute from the iframe body instead and substitute a read only toolbar set. It also means that people will still be able to copy content from the editor.
$("iframe").contents().find("body").removeAttr("contenteditable");
How about this :
editor.addButton('yourButton', {
title: 'One can Enable/disable TinyMCE',
text: "Disable",
onclick: function (ee) {
editor.setMode('readonly');
if($(ee.target).text() == "Disable"){
var theEle = $(ee.target).toggle();
var edit = editor;
var newBut = "<input type='button' style='opacity:1;color:white; background-color:orange;' value='Enable'/>";
$(newBut).prependTo($(theEle).closest("div")).click(function(e){
edit.setMode('design');
$(e.target).remove();
$(theEle).toggle();
});
}
}
});
You can try to run the code below:
$("#tinymce").contentEditable="false";
if you have more than one editors, you can use their id like below
$("#tinymce[data-id='idOfTheEditor']").contentEditable="false";

CKEditor: How to show a block of code differently in preview

I want to create an element that users can insert it in the editor.
The hard part is that it needs to be shown differently from its source.
For example, I want to users insert this in the preview part:
23
And in the source part it should be shown like this:
<span class="tagSpecialClass">{{birthYear}}</span>
The main purpose is to show the mustache tags in a way that users can avoid writing them and just insert them from toolbar that automatically inserts the correct html and preview to it.
My issue is my understanding over CKEditor and the terms I need to read about them in order to implement such a plugin.
How can I force CKEditor to parse/compile/preview a specific tag differently?
Please tell me if my question is too generic!
This sounds like a job for CKEditor widgets (demos)!
Please take a look at the following example (JSFiddle). It will give you some basic idea how to use widgets to solve your problem. If you follow this official tutorial, you'll know how to implement editable parts in your widgets and enable editing with dialogs, which also might be helpful.
// Some CSS for the widget to make it more visible.
CKEDITOR.addCss( '.tagSpecialClass { background: green; padding: 3px; color: #fff } ' );
CKEDITOR.replace( 'editor', {
// A clean-up in the toolbar to focus on essentials.
toolbarGroups: [
{ name: 'document', groups: [ 'mode' ] },
{ name: 'basicstyles' },
{ name: 'insert' }
],
removeButtons: 'Image,Table,HorizontalRule,SpecialChar',
extraPlugins: 'widget',
on: {
pluginsLoaded: function() {
var editor = this;
// Define a new widget. This should be done in a separate plugin
// to keep things clear and maintainable.
editor.widgets.add( 'sampleWidget', {
// This will be inserted into the editor if the button is clicked.
template: '<span class="tagSpecialClass">23</span>',
// A rule for ACF, which permits span.tagSpecialClass in this editor.
allowedContent: 'span(tagSpecialClass)',
// When editor is initialized, this function will be called
// for every single element. If element matches, it will be
// upcasted as a "sampleWidget".
upcast: function( el ) {
return el.name == 'span' && el.hasClass( 'tagSpecialClass' );
},
// This is what happens with existing widget, when
// editor data is returned (called editor.getData() or viewing source).
downcast: function( el ) {
el.setHtml( '{{birthYear}}' );
},
// This could be done in upcast. But let's do it here
// to show that there's init function, which, unlike
// upcast, works on real DOM elements.
init: function() {
this.element.setHtml( '23' );
}
} );
// Just a button to show that "sampleWidget"
// becomes a command.
editor.ui.addButton && editor.ui.addButton( 'SampleWidget', {
label: 'Some label',
command: 'sampleWidget',
toolbar: 'insert,0'
} );
}
}
} );
HTML
<textarea id="editor">
<p>Some text.</p>
<p>And there's the widget <span class="tagSpecialClass">{{birthYear}}</span></p>
<p>Some text <span class="tagSpecialClass">{{birthYear}}</span>.</p>
</textarea>
Happy coding!

Categories

Resources