I have a quill editor with a quill-better-table module. I want it to be uneditable at certain times, so I set it to readOnly. This works for buttons and text, but the table is still editable. The context menu (operationMenu) is also available.
Is there any way to make the better-table uneditable?
const quill = new Quill('#editor-wrapper', {
theme: 'snow',
readOnly: this.readOnly || false,
modules: {
table: false, // disable table module
'better-table': {
operationMenu: {
items: {
unmergeCells: {
text: 'Another unmerge cells name'
}
}
},
toolbar: {
container: [
['tableCreate'], // custom button for create table
],
handlers: {
'tableCreate': () => this.addCreateTableBtnEvent()
}
},
}
}
})
addCreateTableBtnEvent: function () {
const table = quill.getModule('better-table');
table.insertTable(2, 2);
}
Maybe it's not an elegant solution and the modification must be taken into account if I upgrade quill-better-table.js in the future, but so far it works.
I edited the quill-better-table.js and put in checks whether the Quill editor is editable or not.
If it's not editable than there is no content menu or column tool on the quill-better-table.
You can skip any function of the quill-better-table in this way.
...
menuInitial(_ref)
{
let {
table,
left,
top
} = _ref;
var editable = this.quill.root.getAttribute("contenteditable")
if ( editable === 'true' )
{
this.domNode = document.createElement('div');
this.domNode.classList.add('qlbt-operation-menu');
..
Related
I'm building a Grapesjs plugin and have added a 'jscript' trait to a button component, which appears as a codemirror textarea. The idea is for users to be able to edit some javascript code associated with a button. I can't seem to intercept the codemirror area's change event, at least, not the proper codemirror specific version.
Happily, when I edit the codemirror area and change focus, a regular 'change' event triggers the Grapejs onEvent handler within my plugin's editor.TraitManager.addType('jcodemirror-editor', {} - good. I can then store the contents of the codemirror area into the trait.
onEvent({ elInput, component, event }) {
let code_to_run = elInput.querySelector(".CodeMirror").CodeMirror.getValue()
component.getTrait('jscript').set('value', code_to_run);
},
However if we paste or backspace or delete etc. in the codemirror area then the regular 'change' event is never issued!
So I'm trying to intercept the deeper codemirror specific 'change' event which is usually intercepted via cm.on("change", function (cm, changeObj) {} and which is triggered more reliably (unfortunately also on each keystroke). How do I wire this codemirror specific event to trigger the usual onEvent({ elInput, component, event }) {} code?
I have a workaround in place in my https://jsfiddle.net/tcab/1rh7mn5b/ but would like to know the proper way to do this.
My Plugin:
function customScriptPlugin(editor) {
const codemirrorEnabled = true // otherwise trait editor is just a plain textarea
const script = function (props) {
this.onclick = function () {
eval(props.jscript)
}
};
editor.DomComponents.addType("customScript", {
isComponent: el => el.tagName == 'BUTTON' && el.hasAttribute && el.hasAttribute("data-scriptable"),
model: {
defaults: {
traits: [
{
// type: 'text',
type: 'jcodemirror-editor', // defined below
name: 'jscript',
changeProp: true,
}
],
script,
jscript: `let res = 1 + 3; console.log('result is', res);`,
'script-props': ['jscript'],
},
},
});
editor.TraitManager.addType('jcodemirror-editor', {
createInput({ trait }) {
const el = document.createElement('div');
el.innerHTML = `
<form>
<textarea id="myjscript" name="myjscript" rows="14">
</textarea>
</form>
</div>
`
if (codemirrorEnabled) {
const textareaEl = el.querySelector('textarea');
var myCodeMirror = CodeMirror.fromTextArea(textareaEl, {
mode: "javascript",
lineWrapping: true,
});
// This is the 'more accurate' codemirror 'change' event
// which is triggered key by key. We need it cos if we paste
// or backspace or delete etc. in codemirror then the
// regular 'change' event is never issued! But how do we get
// this event to trigger the proper, usual 'onEvent' below?
// Currently cheating and doing the onEvent work here with
// this special handler.
myCodeMirror.on("change", function (cm, changeObj) { // HACK
const component = editor.getSelected()
const code_to_run = myCodeMirror.getValue()
component.getTrait('jscript').set('value', code_to_run);
console.log('onEvent hack - (myCodeMirror change event) updating jscript trait to be:', code_to_run)
})
}
return el;
},
// UI textarea & codemirror 'change' events trigger this function,
// so that we can update the component 'jscript' trait property.
onEvent({ elInput, component, event }) {
let code_to_run
if (codemirrorEnabled)
code_to_run = elInput.querySelector(".CodeMirror").CodeMirror.getValue()
else
code_to_run = elInput.querySelector('textarea').value
console.log('onEvent - updating jscript trait to be:', code_to_run)
component.getTrait('jscript').set('value', code_to_run);
}, // onEvent
// Updates the trait area UI based on what is in the component.
onUpdate({ elInput, component }) {
console.log('onUpdate - component trait jscript -> UI', component.get('jscript'))
if (codemirrorEnabled) {
const cm = elInput.querySelector(".CodeMirror").CodeMirror
cm.setValue(component.get('jscript'))
// codemirror content doesn't appear till you click on it - fix with this trick
setTimeout(function () {
cm.refresh();
}, 1);
}
else {
const textareaEl = elInput.querySelector('textarea');
textareaEl.value = component.get('jscript')
// actually is this even needed as things still update automatically without it?
// textareaEl.dispatchEvent(new CustomEvent('change'));
}
}, // onUpdate
}) // addType
editor.BlockManager.add(
'btnRegular',
{
category: 'Basic',
label: 'Regular Button',
attributes: { class: "fa fa-square-o" },
content: '<button type="button">Click Me</button>',
});
editor.BlockManager.add(
'btnScriptable',
{
category: 'Scriptable',
label: 'Scriptable Button',
attributes: { class: "fa fa-rocket" },
content: '<button type="button" data-scriptable="true">Run Script</button>',
});
}
const editor = grapesjs.init({
container: '#gjs',
fromElement: 1,
height: '100%',
storageManager: { type: 0 },
plugins: ['gjs-blocks-basic', 'customScriptPlugin']
});
According to official Grapesjs documentation on traits integrating external ui components you can trigger the onEvent event manually by calling this.onChange(ev).
So within createInput I continued to intercept the more reliable myCodeMirror.on("change", ... event and within that handler triggered the onEvent manually viz:
editor.TraitManager.addType('jcodemirror-editor', {
createInput({ trait }) {
const self = this // SOLUTION part 1
const el = document.createElement('div');
el.innerHTML = `
<form>
<textarea id="myjscript" name="myjscript" rows="14">
</textarea>
</form>
</div>
`
if (codemirrorEnabled) {
const textareaEl = el.querySelector('textarea');
var myCodeMirror = CodeMirror.fromTextArea(textareaEl, {
mode: "javascript",
lineWrapping: true,
});
myCodeMirror.on("change", function (cm, changeObj) {
self.onChange(changeObj) // SOLUTION part 2
})
}
return el;
},
I have 3 menu items in a custom TinyMCE 5.x dropdown that controls the width of the editor. I want to indicate what the current selection is, but can't find a way to interact with the menu items after they are initialized. When the menu is closed they don't seem to be in the DOM at all.
I would be happy if my custom dropdown behaved like the font-size dropdown, which displays a check mark next to the selected size. I would also be happy with it being like the font-family dropdown where the selected font is displayed as the menu toggle (not just when you open the menu).
editor.ui.registry.addMenuButton('maxWidth', {
text: 'Width',
fetch: function( callback ){
var items = [
{
type: 'menuitem',
text: 'Full Width',
onAction: function(){ changeSectionWidth("full"); }
},
{
type: 'menuitem',
text: '1600',
onAction: function(){ changeSectionWidth(1600); }
},
{
type: 'menuitem',
text: '1170',
onAction: function(){ changeSectionWidth(1170); }
},
];
callback(items);
},
});
After looking all over and finding nothing useful, I hacked it together, which I am not happy about, but it does work:
//LOCATE THE BUTTON GROUP (happens to be in group 12, starts on 0 though)
let btnGroup = $(editor.editorContainer).find(".tox-toolbar__group")[11];
if( btnGroup ){
return false;
}
//GET THE SPECIFIC BUTTON IN THAT GROUP (happens to be in slot 4, starts on 0)
let targetBTN = $(btnGroup).children()[3];
//CLICK HANDLER FOR THE SPECIFIC MENUBUTTON
$(targetBTN).on("click", function(){
//USE A TIMER SO TinyMCE HAS TIME TO RENDER THE MENU
window.setTimeout( function(){
//APPLY YOUR OWN LOGIC HERE TO DETERMINE WHICH OPTION TO SELECT
//this has to match the words in the button and is case-sensitive
let selectedOption = "Option 2";
//DESELECT OTHER OPTIONS
//NOTE THAT I AM USING AN EXTRA CLASS "my-selected" WHICH I APPLIED TO THE UI SKIN.min.css BECAUSE HOVER DESELECTED THEIR HIGHLIGHTS AND I WANTED IT MORE PERMANENT
$(".tox-selected-menu .tox-collection__item--active").removeClass("tox-collection__item--active tox-collection__item--enabled my-selected");
$('.tox-collection__item[title="'+selectedOption+'"]').addClass("tox-collection__item--active tox-collection__item--enabled my-selected");
}, 50); //WAIT 50 milliseconds so menu has time to be rendered
});
Edit: I know this is old, but might help others.
This can be done using formats and hooking into onSetup on each menu item. See below working example.
editor.ui.registry.addMenuButton('maxWidth', {
text: 'Width',
fetch: callback => {
// Define out options for width.
var widthOptions = [
{
title: 'Full width',
format: 'full_width',
},
{
title: 'Width 2',
format: 'width2',
},
{
title: 'Width 3',
format: 'width3',
},
];
var items = [];
// Add each option to menu items.
widthOptions.forEach(option => {
// Register a custom format for each option.
// See https://www.tiny.cloud/docs/configure/content-formatting/#formats
editor.formatter.register(option.format, {
inline: 'span',
classes: option.format,
});
items.push({
type: 'togglemenuitem',
text: option.title,
onAction: action => {
// We only allow one to be selected. Remove all but the one we clicked.
widthOptions.forEach(opt => {
if (
option.format !=
opt.format
) {
tinymce.activeEditor.formatter.remove(
opt.format,
);
}
});
// Now toggle the selected one.
editor.execCommand(
'FormatBlock',
false,
option.format,
);
},
onSetup: function(api) {
// When generating the item, check if this one is selected.
if (
editor.formatter.match(
option.format,
)
) {
api.setActive(true);
}
return function() {};
},
});
});
callback(items);
},
});
I am using angular-silkgrid with angular 7. I am using inline editing feature. I am using single select editor for inline edit. Currently I want to achieve this functionality:
As soon as user click on the editable field , the select drop down will be visible.On select any option from select dropdown the inline mode should exist and column value should be updated.
currently I need to click on other field to exit from inline mode(I want to achieve this on select option select)
editor: {
// display checkmark icon when True
enableRenderHtml: true,
// tslint:disable-next-line:max-line-length
collection: [{
value: 1,
label: 'Sucessful'
}, {
value: 2,
label: 'Unsucessful'
}],
model: Editors.singleSelect, // CustomSelectEditor
elementOptions: {
autoAdjustDropPosition: false,
onClick: (event, rr) => {
// here i want to write some code which can trigger to grid to start update
}
}
}
Thanks All for the answers. I have solved my issue as below:
editor: {
enableRenderHtml: true,
collection: [{ value: CCLStaus.Sucessfull, label: 'Sucessful' }, { value: CCLStaus.UnSucessfull, label: 'Unsucessful' }],
model: Editors.singleSelect,// CustomSelectEditor
elementOptions: {
onClick: (event) => {
const updateItem = this.angularSilkGrid.gridService.getDataItemByRowIndex(this.rowInEditMode);
updateItem.status = +event.value;
this.angularSilkGrid.gridService.updateItem(updateItem, { highlightRow: false });
this.angularSilkGrid.gridService.renderGrid();
}
}
}
Generally,
grid.getEditorLock().commitCurrentEdit()
will commit and close the editor.
Also, any of
grid.navigateRight()
grid.navigateLeft()
grid.navigateDown()
grid.navigateUp()
grid.navigateNext()
grid.navigatePrev()
will commit and exit gracefully. In the select2 editor, you'll notice:
this.init = function () {
...
// Set focus back to select2 element on closing.
$input.on('select2:close', function (e) {
if ((e.params.originalSelect2Event && e.params.originalSelect2Event.data)
|| e.params.key === 9) {
// item was selected with ENTER or no selection with TAB
args.grid.navigateNext();
} else {
// closed with no selection
setTimeout(function () {
$input.select2('focus');
}, 100);
}
});
};
this.destroy = function () {
$input.select2('destroy');
$input.remove();
};
, noting that args.grid.navigateNext() will commit and close the editor, including calling the destroy() method at the appropriate time.
From the Angular-Slickgrid Editor Example there's a checkbox in the example to auto commit and that is a setting to you need to enable in your Grid Options
this.gridOptions = {
autoEdit: true,
autoCommitEdit: true,
}
The lib will internally call grid.getEditorLock().commitCurrentEdit() like Ben wrote in his answer, in Angular-Slickgrid you can just set the autoCommitEdit flag that I added.
I am aware with the tweaks required for enabling focus inside the tinymce pop up when in bootstrap modal.
But currently I am working with a vuetify dialog. Which does'nt seem to focus the pop up fields of tinymce.
I have gone through this question but it does not work in context to vuetify
TinyMCE 4 links plugin modal in not editable
Below is my code I have removed some methods just for clean up and have kept basic things that I have tried in mounted event & editor init.
<no-ssr placeholder="Loading Editor..">
<tinymce
id="content"
:toolbar2="toolbar2"
:toolbar1="type=='BASIC'?'':toolbar1"
:plugins="plugins"
:other_options="other_options"
v-model="content"
#input="handleInput"
v-on:editorInit="initCallBack"
/>
</no-ssr>
</template>
<script>
//https://dyonir.github.io/vue-tinymce-editor/?en_US
export default {
props: {
value: { type: String },
type: { type: String }
},
data() {
return {
content: this.value,
plugins: this.getPlugins(),
toolbar2: "",
toolbar1: this.getToolbar1(),
other_options: {
menubar: this.getMenubar(),
height: "320",
file_browser_callback: this.browseFile,
auto_focus: '#content'
}
};
},
mounted(event) {
// window.tinyMCE.activeEditor.focus();
// window.tinymce.editors["content"]
console.log(this.$el, event);
let list=document.getElementsByClassName("mce-textbox");
for (let index = 0; index < list.length; ++index) {
list[index].setAttribute("tabindex", "-1");
}
// if ((event.target).closest(".mce-window").length) {
// e.stopImmediatePropagation();
// }
// this.$refs.refToElement ..$el.focus())
// this.el.addEventListener('focusin', e => e.stopPropagation());
},
methods: {
handleInput(e) {
this.$emit("input", this.content);
},
initCallBack(e) {
window.tinymce.editors["content"].setContent(this.value);
window.tinymce.editors["content"].getBody().focus();
// console.log(this.$refs);
// const focusable = this.$refs.content.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
// focusable.length && focusable[0].focus()
document.getElementById("content").addEventListener("onfocusin", console.log("fucssed"));
// tinymce.activeEditor.fire("focus");
this.$el.querySelector(".mce-tinymce").addEventListener('focusin', e =>{ e.stopImmediatePropagation();console.log('event',e)});
const element = this.$el.querySelector(".mce-tinymce");
let _this=this;
if (element)
this.$nextTick(() => {
element.focus();
console.log("FOCUSED",element,_this);
// element.stopImmediatePropagation();
});
// window.tinyMCE.activeEditor.focus();
// console.log(this.$el,e);
// if ((e).closest(".mce-window").length) {
// e.stopImmediatePropagation();
// }
}
};
</script>```
I am using the component : https://github.com/dyonir/vue-tinymce-editor
But fields of the pop are not getting focussed/edited.
From vuetify 2.0 onwards there is a new prop 'retain-focus' which you can set to false in order to fix the above issue.
<v-dialog :retain-focus="false">
Tab focus will return to the first child of the dialog by default. Disable this when using external tools that require focus such as TinyMCE or vue-clipboard.
Edit:
Here is the link to retain-focus prop implementation GitHub:
https://github.com/vuetifyjs/vuetify/issues/6892
Modify the z-index of v-dialog:
Current:
z-index: 202
Modified:
<style>
.v-dialog__content {z-index: 203 !important;}
</style>
Do not forget the !important to give priority to style.
So I'm making my own plugin to the ckeditor, since I need a special case. Anyway, I can't make textarea element editable. This is my whole code to my own dialog (for the plugin):
CKEDITOR.dialog.add('myDialog', function(editor) {
return {
title: 'My Plugin',
minWidth: 750,
minHeight: 500,
onShow: function(evt) {
var selection = editor.getSelection();
var widget = editor.widgets.selected[0];
var element = !!widget && !!widget.parts ? widget.parts['my'] : false;
var command = this.getName();
if(command == 'myDialog') {
var code = selection.getSelectedElement();
if(code && !!element) {
this.setupContent(code);
widget.data.myinput = element.getHtml();
}
}
},
contents: [{
id: 'info',
label: 'Info',
accessKey: 'I',
elements: [{
id: 'myinput',
type: 'textarea',
required: true,
label: 'Content',
rows: 42,
setup: function(widget) {
this.setValue(widget.data.myinput);
},
commit: function(widget) {
widget.setData('myinput', this.getValue());
}
}]
}],
};
});
Problem is only within contents.myinput. It type is textarea but when I open the dialog its not editable. When I change type to the text and remove rows, then text input shows up, working great and so on. Only textarea is problem. This is how it looks like after opening the dialog:
Version of my CKEditor is 4.5. I already made 3 plugins before, but never had to use textarea so all other plugins works except this one. I would append jsFiddle, if there was any site offering "ckeditor plugin tester" so I just post my code.
Problem is that I init ckeditor in the bootstrap's dialog. So the solution for my problem is to apply the following lines of code after initialization:
$.fn.modal.Constructor.prototype.enforceFocus = function() {
var $modalElement = this.$element;
$(document).on('focusin.modal', function(e) {
var $parent = $(e.target.parentNode);
if($modalElement[0] !== e.target
&& !$modalElement.has(e.target).length
&& !$parent.hasClass('cke_dialog_ui_input_select')
&& !$parent.hasClass('cke_dialog_ui_input_text')
&& !$parent.hasClass('cke_dialog_ui_input_textarea')) {
$modalElement.focus();
}
})
};
I had this code before the problem, but I was missing !$parent.hasClass('cke_dialog_ui_input_textarea') which I forget to add, so this was my fault :)