Quill Editor inserts erroneous tab character when loading saved data with inline indent style - quill

I'm wondering if the behavior I'm seeing is a bug and figured I'd ask here before submitting to github.
I need to be able to create, save, and edit saved email templates in the Quill editor. I also need to be able to use the output from Quill in an email, formatted how it was entered in the WYSIWYG editor, so I'm saving the HTML output with inline styles.
I have an issue with the indentation not rendering as it was entered when I load in a saved email template. I'm implementing the IndentAttributor like the following comment:
https://github.com/quilljs/quill/issues/1274#issuecomment-303619625
I dug into what Quill is doing and found in matchStyles() a tab character is getting inserted if textIndent is found.
Offending code in quill.js:
function matchStyles(node, delta) {
var formats = {};
var style = node.style || {};
if (style.fontStyle && computeStyle(node).fontStyle === 'italic') {
formats.italic = true;
}
if (style.fontWeight && (computeStyle(node).fontWeight.startsWith('bold') || parseInt(computeStyle(node).fontWeight) >= 700)) {
formats.bold = true;
}
if (Object.keys(formats).length > 0) {
delta = applyFormat(delta, formats);
}
if (parseFloat(style.textIndent || 0) > 0) {
// Could be 0.5in
delta = new _quillDelta2.default().insert('\t').concat(delta);
}
return delta;
}
I tested a change to the quill.js node_module to check for text-indent style. However, I'm not trying to hack the editor for a specific need and I'm not familiar enough with the editor to know whether or not this will have a negative impact on anything else.
function matchStyles(node, delta) {
console.log('in matchStyles()');
var formats = {};
var style = node.style || {};
if (style.fontStyle && computeStyle(node).fontStyle === 'italic') {
formats.italic = true;
}
if (style.fontWeight && (computeStyle(node).fontWeight.startsWith('bold') || parseInt(computeStyle(node).fontWeight) >= 700)) {
formats.bold = true;
}
if (Object.keys(formats).length > 0) {
delta = applyFormat(delta, formats);
}
if ((parseFloat(style.textIndent || 0) > 0) && !style.cssText.includes('text-indent')) { //fix to check for inline style
// Could be 0.5in
delta = new _quillDelta2.default().insert('\t').concat(delta); //problem here...
}
return delta;
}
Example project showing the issue:
https://stackblitz.com/edit/github-acpnmm
Highlight or inspect the initial text (coming from app.reducer.ts initial state) and you will see the tab character that was inserted.
Should Quill be recognizing the IndentAttributor and not insert the tab character?

I see you are using Quill in Angular project. I would suggest you to check this package: https://www.npmjs.com/package/ngx-quill. With this package you can choose the type of data (Text, HTML or Delta) that the Quill will process, and you can bind the Quill content to some variable. That way you can just store that value of that variable in the database. When you need to load the content again, fetch that value from the database, and again bind the Quill content to that value.

Related

How can we override Ext.Base?

I am using Ext JS v7.1 and I have overridden Ext.Base to set my naming scheme for the classes that inherits from Ext.Base: This eases my debugging.
Ext.define('App.class.Base', {
override: 'Ext.Base',
constructor: function() {
var me = this
/**
* App.base.store.Base => store-base-
* App.store.Menu => store-menu-
*/
if (me.isIdentifiable) {
if (!me.self.prototype.hasOwnProperty('identifiablePrefix')) {
const classNameParts = me.$className.match(/([^\.]+)/g)
if (classNameParts && classNameParts[0] === 'App') {
classNameParts.splice(0, classNameParts.length - 2)
me.self.prototype.identifiablePrefix = classNameParts.reduce((i, j) => i + '-' + j).toLocaleLowerCase() + '-'
}
}
}
return me.callParent()
}
})
This code was building before without an error but, after I upgraded Sencha Cmd to v7.3.0.19, I started the get the following error:
[ERR] C2016: Override target not found -- /...../packages/local/module-core/overrides/class/Base.js:2:64
[WRN] Override App.class.Base in file /..../packages/local/module-core/overrides/class/Base.js had no target detected
I don't know whether this is the right place/way to do this override, if not I can change my implementation. However, if there is no other way, how can get rid of the build error?
Thanks in advance,
Ipek
Because i am not using sencha build tools anymore, i can not help you directly but i would like to share another approach:
In case you have loaded the framework (ext-debug-all or ext-all, etc.) first and the class which should get overwritten is already defined you can do it like that:
Ext.Component.override({
initComponent: function () {
Ext.log('bootstraping ' + this.self.getName());
var me = this,
width = me.width,
height = me.height;
// If plugins have been added by a subclass's initComponent before calling up to here (or any components
// that don't have a table view), the processed flag will not have been set, and we must process them again.
// We could just call getPlugins here however most components don't have them so prevent the extra function call.
if (me.plugins && !me.plugins.processed) {
me.plugins = me.constructPlugins();
}
me.pluginsInitialized = true;
// this will properly (ignore or) constrain the configured width/height to their
// min/max values for consistency.
if (width != null || height != null) {
me.setSize(width, height);
}
if (me.listeners) {
me.on(me.listeners);
me.listeners = null; //change the value to remove any on prototype
}
if (me.focusable) {
me.initFocusable();
}
}
});
Depending on the further internal processing you can call callParent or callSuper.
More details here:
https://docs.sencha.com/extjs/6.5.3/classic/Ext.Class.html#cfg-override
You may be able to move this upper code inside a function and call it later, for example - when Ext.isReady. I guess this can solve or tackle some of the open tooling issues you are facing.
UPDATE:
Coming back to your question you can do the following and define it like that:
Ext.Base.override({
constructor: function() {
var me = this
/**
* App.base.store.Base => store-base-
* App.store.Menu => store-menu-
*/
if (me.isIdentifiable) {
if (!me.self.prototype.hasOwnProperty('identifiablePrefix')) {
const classNameParts = me.$className.match(/([^\.]+)/g)
if (classNameParts && classNameParts[0] === 'App') {
classNameParts.splice(0, classNameParts.length - 2)
me.self.prototype.identifiablePrefix = classNameParts.reduce((i, j) => i + '-' + j).toLocaleLowerCase() + '-'
}
}else{
console.log('isIdentifiable');
console.log(me.identifiablePrefix);
}
}
return me.callParent(arguments)
}
});
I have added an exampole fiddle here. It should log "helloWorld" in case identifiablePrefix is set.
https://fiddle.sencha.com/#view/editor&fiddle/3a8i

EXTJS Grid inside a tab - filter only works on page refresh

we are using extjs 4.1 and i have run into a weird issue. We have a group of 3 tabs and those tabs contains extjs grids. extjs grids in the first 2 are loading the data fine and also have filter option. extjs grid in the 3rd tab, loads the data but does not show the filter menu. However, when the page is refreshed while on the 3rd tab, the grid shows the filter menu. i am new to extjs. please help.
ext-all-debug-w-comments-v4.1.2a.js:144232 Uncaught TypeError: Cannot read property 'enable' of null
at constructor.showMenuBy (ext-all-debug-w-comments-v4.1.2a.js:144232)
at constructor.onHeaderTriggerClick (ext-all-debug-w-comments-v4.1.2a.js:141028)
at constructor.onElClick (ext-all-debug-w-comments-v4.1.2a.js:142124)
at HTMLDivElement.eval (eval at cacheableFunctionFactory (ext-all-debug-w-comments-v4.1.2a.js:683), <anonymous>:6:13)
at HTMLDivElement.wrap (ext-all-debug-w-comments-v4.1.2a.js:15197)
the following line is where the error is happening but this line is within extjs
showMenuBy: function(t, header) {
var menu = this.getMenu(),
groupMenuItem = menu.down('#groupMenuItem'), //this menu is null and therefore the error but this is auto generated by extjs
groupMenuMeth = header.groupable === false ? 'disable' : 'enable',
groupToggleMenuItem = menu.down('#groupToggleMenuItem');
groupMenuItem[groupMenuMeth](); //this statement is null and throws the error
if (groupToggleMenuItem) {
groupToggleMenuItem[this.view.store.isGrouped() ? 'enable' : 'disable']();
}
Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
}
//code from the onHeaderTrigerClick stackTrace:
onHeaderTriggerClick: function(header, e, t) {
// generate and cache menu, provide ability to cancel/etc
var me = this;
if (header.fireEvent('headertriggerclick', me, header, e, t) !== false && me.fireEvent("headertriggerclick", me, header, e, t) !== false) {
me.showMenuBy(t, header);
}
}
//code from onElClick:
onElClick: function(e, t) {
// The grid's docked HeaderContainer.
var me = this,
ownerHeaderCt = me.getOwnerHeaderCt();
if (ownerHeaderCt && !ownerHeaderCt.ddLock) {
// Firefox doesn't check the current target in a within check.
// Therefore we check the target directly and then within (ancestors)
if (me.triggerEl && (e.target === me.triggerEl.dom || t === me.triggerEl.dom || e.within(me.triggerEl))) {
ownerHeaderCt.onHeaderTriggerClick(me, e, t);
// if its not on the left hand edge, sort
} else if (e.getKey() || (!me.isOnLeftEdge(e) && !me.isOnRightEdge(e))) {
me.toggleSortState();
ownerHeaderCt.onHeaderClick(me, e, t);
}
}
}
in every case, parameter header is null.
see Ext.grid.feature.Grouping.
It seems probably a css issue, might be caused by the order in which components load and height applied to header.
Use below css:
.ui-grid-header-cell{
height:60px;
max-height:60px;
}
Identify the id of header component and use it in place of .ui-grid-header-cell

CQ/AEM extjs get selection dropdown box text, and get page path

Say I have a component with dialog drop to parsys on a content page /content/phonegap/ss/en_gb/login/home/test1/jcr:content/par/productimage
now inside the dialog i have something like
I wish to get $PATH append to URL and send selected device e.g the text 'Device ID:HTC_10_GOLD', to servlet in this dialog listener extjs:
<deviceAndColour
jcr:primaryType="cq:Widget"
allowBlank="{Boolean}false"
fieldLabel="Device and Colour"
name="./deviceAndColour"
options="/bin/reference/data/device.devices.json$PATH"
type="select"
xtype="selection">
<listeners
jcr:primaryType="nt:unstructured"
selectionchanged="function(pathfield) {
var selected = this.findParentByType('form').find('name', './deviceAndColour')[0].getText();
console.log( this.findParentByType('form').find('name', './deviceAndColour')[0].getText());
$.getJSON('/bin/reference/data/device/availablecolour.availablecolour.json$PATH?selectedDevice=' + selected + '&colour=red', function(jsonData){
selectBox.setOptions(jsonData);
});
}" />
</deviceAndColour>
So bascially, the console.log( this.findParentByType('form').find('name', './deviceAndColour')[0].getText()); is not working as I expected, neither the $PATH inside the dialog listener js, it does not retrieve the path at all.
Apart from above attempt, I know var selected = this.findParentByType('form').find('name', './deviceAndColour')[0].getValue(); this will get the value asscociated with the select correctly, but I do not need value, I just wish to getText(), and get the current $PATH in extjs.
another questions, you may noticed $.getJSON('/bin/reference/data/device/availablecolour.availablecolour.json$PATH?selectedDevice=' + selected + '&colour=red'
how do I skip the & in this listner, as if i use & directly, project won't even build. there must be someway to skip & and let extjs treat as part of the string to send request
Anyone expereinced this before? please suggest with code example.
Thanks
Getting the text of the option selected:
function(field,value){
for(var i = 0; i < field.options.length; i++) {
if(field.options[i].value === value) {
console.log('Selected: ' + field.options[i].text);
break;
}
}
}
Getting the path to the resource being edited:
function(field,value){
console.log("Resource:" + field.findParentByType("dialog").path);
}
Documentation: https://docs.adobe.com/docs/en/cq/5-6/widgets-api/index.html?class=CQ.form.Selection
UPDATE
Please try the following code adapted to your scenario (I also refactored the code to make use of params when providing query parameters. There is no reason why this shouldn't work.
function(field, value) {
var selected = '';
var path = field.findParentByType("dialog").path;
// get text of option selected
for(var i = 0; i < field.options.length; i++) {
if(field.options[i].value === value) {
selected = field.options[i].text;
break;
}
}
var params = {
selectedDevice: selected,
colour: 'red'
}
$.getJSON('/bin/reference/data/device/availablecolour.availablecolour.json'+path, params, function(jsonData){
// FIXME: how are you getting the "selectBox" reference?
selectBox.setOptions(jsonData);
});
}

Excel-like behaviour of Grids in Ext JS

I'm trying to figure out a way to have an Excel-like behavior with the Grids on Ext JS.
Here is the sample grid I am working with. So far we can already naviguate through the cells with the arrows but only in edit mode.
However what I am trying to reach is the naviguation with the arrows, TAB and Enter keys outside of the edit mode, just like excel.
I tried to integrate this piece of code which overrides the Editor class, hoping that it would change the behavior of the cells but it doesn't change a thing.
I believe this is the most important part that overrides the Editor class and tries to include the keys input :
Ext.override(Ext.Editor, {
startEdit: function (el, value) {
var me = this,
field = me.field;
me.completeEdit();
me.boundEl = Ext.get(el);
value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
if (!me.rendered) {
me.render(me.parentEl || document.body);
}
if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
me.startValue = value;
me.show();
field.reset();
if (deleteGridCellValue) {
field.setValue('');
me.editing = true;
me.completeEdit();
deleteGridCellValue = false; // reset global variable
}
else {
if (newGridCellValue == '') {
// default behaviour of Ext.Editor (see source if needed)
field.setValue(value);
}
else {
// custom behaviour to handle an alphanumeric key press from non-edit mode
field.setRawValue(newGridCellValue);
newGridCellValue = ''; // reset global variable
if (field instanceof Ext.form.field.ComboBox) {
// force the combo box's filtered dropdown list to be displayed (some browsers need this)
field.doQueryTask.delay(field.queryDelay);
}
}
me.realign(true);
field.focus(false, 10);
if (field.autoSize) {
field.autoSize();
}
me.editing = true;
}
}
}
});
This is the first time that I am working on a project that is outside of Comp-Sci classes so any help would be very much appreciated. Thanks !

ExtJS 4 Temporarily disable loading mask appearance for a grid

I have a grid and I want to disable appearance of loading mask on it (to prevent double loading mask because I add loading mask to its parent component) at the the execution of certain scripts.
I've tried something like this
var myGridView = myGrid.getView();
myGridView.loadMask = false;
// I want so at this data loding did not appear loading mask
myGrid.getStore().load();
myGridView.loadMask = true;
but it doesnt work.
Any suggestions?
You can use setDisabled() method for LoadMask instance:
var myGridView = myGrid.getView();
myGridView.loadMask.setDisabled(true);
myGrid.getStore().load(function () {
myGridView.loadMask.setDisabled(false);
});
As well you can use enable(), disable() methods.
After carefully reading source code of grid, load mask and store I can suggest this small override
Ext.override(Ext.grid.Panel, {
setLoading: function (load) {
var me = this,
view = me.getView(),
store = me.getStore();
me.callParent(arguments);
if (Ext.isBoolean(load) && view && view.loadMask && view.loadMask.isLoadMask && store) {
if (load) {
view.loadMask.bindStore && view.loadMask.bindStore(store);
} else {
view.loadMask.unbindStoreListeners(store);
}
}
}
});
This sounds crazy, but spinner knows about grid's store. And even has (protected) methods to work with http://docs.sencha.com/extjs/5.1.1/Ext.LoadMask.html#method-unbindStoreListeners
http://docs.sencha.com/extjs/4.1.3/#!/api/Ext.LoadMask-method-unbindStoreListeners

Resources