I have not found this on the CKEditor documentation and am reasonably sure it can be accomplished. I know that if you explicitly pass toolbar: to CKEditor when instantiating an editor, you can control the toolbar config.
What I would like to do is first GET the default toolbar and remove buttons based on some user requirements. How would I fetch the CKEDITOR toolbar array first? Thanks!
(NOTE: I thought it might be found in CKEDITOR.config.toolbar_Full like in this link here but that shows as undefined; I'm using CKE 4.7.1.)
(NOTE 2: Turns out you can construct a menu negatively using the removeButtons attribute, but I'll leave the question up as getting the entire menu as an array might still be useful. Example:
CKEDITOR.replace('my-region', { removeButtons: 'Save,Copy,About,Source' });
)
I am also using version 4.7.1. I also have customized toolbars related to user requirements. Here is the snippet
In the config definition:
initToolbarSets(config);
config.toolbar = 'Default';
The function for building the different toolbar sets(including custom plugins):
function initToolbarSets(config) {
config.toolbar_Empty = [];
config.toolbar_Default = [
{ name: '1', items: ['Cut','Copy','Paste','PasteText','PasteFromWord'] },
{ name: '2', items: ['Undo','Redo','-','Find','Replace'] },
{ name: '3', items: ['DataTF', 'DataCB', 'Select', 'Table', 'Image','HorizontalRule'] },
'/',
{ name: '4', items: ['Bold','Italic','Underline','Strike'] },
{ name: '5', items: ['NumberedList','BulletedList','-','Outdent','Indent'] },
{ name: '6', items: ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'] },
{ name: '7', items: ['SpecialChar','PageBreak'] },
{ name: '8', items: ['Font','FontSize'] },
{ name: '9', items: ['TextColor','BGColor'] },
{ name: '10', items: ['Maximize'] }
];
config.toolbar_Mail= [
{ name: '1', items: ['Cut','Copy','Paste','PasteText','Preview'] },
{ name: '2', items: ['Undo','Redo','-','Find','Replace'] },
{ name: '3', items: ['TextField', 'Checkbox', 'Select', 'Table','Image', 'HorizontalRule', 'Link'] },
'/',
{ name: '4', items: ['Bold', 'Italic', 'MyPlugin', 'Strike', 'Subscript', 'Superscript', 'RemoveFormat'] },
{ name: '5', items: ['NumberedList','BulletedList','-','Outdent','Indent'] },
{ name: '6', items: ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'] },
{ name: '7', items: ['SpecialChar', 'PageBreak'] },
{ name: '8', items: ['Font', 'FontSize', 'Styles'] },
{ name: '9', items: ['TextColor','BGColor'] },
{ name: '10', items: ['Maximize'] }
];
config.toolbar_Helpdoc= [
{ name: '1', items: ['Cut', 'Copy', 'Paste', 'PasteText', 'Preview'] },
{ name: '2', items: ['Undo', 'Redo', '-', 'Find', 'Replace'] },
{ name: '3', items: ['Table', 'HorizontalRule', 'Link'] },
'/',
{ name: '4', items: ['Bold', 'Underline', 'Strike', 'MyPlugin', 'Superscript', 'RemoveFormat'] },
{ name: '5', items: ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent'] },
{ name: '6', items: ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'] },
{ name: '7', items: ['SpecialChar', 'PageBreak'] },
{ name: '8', items: ['Styles']},
{ name: '9', items: ['Maximize'] }
];
//...some other toolbar profiles
};
Now it depends on you how to continue. Either you develope a logic (probably within the config) to decide which toolbar profile has to be shwon to the current user. Another approach could be a control that encapsulates the whole CKEditor. In my case (ASP.NET) the editor is embedded like this:
<mynamespace:MyHTMLTextEditor ID="ckE" runat="server" Toolbar="Helpdoc" StylesSet="helpdoc" ForcePasteAsPlainText="true"/>
Related
below is an array of objects.
const input = [
{
id: '1',
productType: [
{
id: '2',
itemTypes: [
{
id: '3',
},
],
},
]
itemTypes: [
{
id: '4',
}
],
},
{
id: '2',
productType: [
{
id: '7',
itemTypes: [
{
id: '8',
},
],
},
{
id: '9',
itemTypes: [],
},
],
itemTypes: [
{
id: '5',
},
{
id: '6',
},
],
},
{
id: '9',
productType: [
{
id: '11',
itemTypes: [],
},
],
itemTypes: [],
},
]
so from above array i want to filter out the object that doesnt has itemTypes within producttypes as empty and itemTypes array empty.
so the expected output is below,
const output = [
{
id: '1',
productType: [
{
id: '2',
itemTypes: [
{
id: '3',
},
],
},
]
itemTypes: [
{
id: '4',
}
],
},
{
id: '2',
productType: [
{
id: '7',
itemTypes: [
{
id: '8',
},
],
},
{
id: '9',
itemTypes: [],
},
],
itemTypes: [
{
id: '5',
},
{
id: '6',
},
],
},
];
i have tried something like below,
const output = input.filter(e => e.includes(!isEmpty(productTypes.itemTypes) && !isEmpty(itemTypes));
but the problem above is productTypes is an array and i need to check if every object in productType has itemTypes empty array.
how can i do that. could someone help me with this. thanks.
You can try this:
const output = input.filter(
item => item.productType.some(
product => product.itemTypes.length > 0));
The problem:
I'm trying to write a TypeScript function suitable for a previously written JEST test. There are 5 tests in total. The function I wrote passes all the tests, but unfortunately, I was asked to write again in the review. How to write this more efficiently?
Example case:
Imagine an array that contains folders. These folders can have files in it. move function moves a file to another folder and returns the new state of given list.
Example list:
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
]
If I run move(list, '4', '6') then I expect file with id 4 moved to the folder which has id 6. Function should return the new state below;
const result = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [
{ id: '7', name: 'File 5' },
{ id: '4', name: 'File 3' },
],
},
]
move.ts
type File = {
id: string;
name: string;
};
type Folder = {
id: string;
name: string;
files: File[];
};
type List = Folder[];
export default function move(list: List, source: string, destination: string): List {
let sourceFileIndex = -1;
let sourceFolderIndex = -1;
let destinationFolderIndex = -1;
for (let i = 0; i < list.length; i += 1) {
const { id, files } = list[i];
if (id === source) {
throw new Error('You cannot move a folder');
}
if (id === destination) {
destinationFolderIndex = i;
}
if (destinationFolderIndex > -1 && sourceFileIndex > -1) break;
for (let j = 0; j < files.length; j += 1) {
if (files[j].id === destination) {
throw new Error('You cannot specify a file as the destination');
}
if (files[j].id === source) {
sourceFileIndex = j;
sourceFolderIndex = i;
break;
}
}
}
if (destinationFolderIndex === -1) {
throw new Error('Given destination id did not match any folder');
}
if (sourceFolderIndex === -1) {
throw new Error('Given source id of file was not found in any folder');
}
list[destinationFolderIndex].files.push(
...list[sourceFolderIndex].files.splice(sourceFileIndex, 1),
);
return list;
}
move.spec.ts
import move from './move';
describe('move', () => {
it('moves given file to another folder', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
];
const result = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [
{ id: '7', name: 'File 5' },
{ id: '4', name: 'File 3' },
],
},
];
expect(move(list, '4', '6')).toStrictEqual(result);
});
it('throws error if given source is not a file', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [{ id: '2', name: 'File 1' }],
},
{ id: '3', name: 'Folder 2', files: [] },
];
expect(() => move(list, '3', '1')).toThrow('You cannot move a folder');
});
it('throws error if given destination is not a folder', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [{ id: '2', name: 'File 1' }],
},
{ id: '3', name: 'Folder 2', files: [{ id: '4', name: 'File 2' }] },
];
expect(() => move(list, '2', '4')).toThrow('You cannot specify a file as the destination');
});
it('throws error if given source was not found', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
];
expect(() => move(list, '9', '6')).toThrow(
'Given source id of file was not found in any folder',
);
});
it('throws error if given destination was not found', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
];
expect(() => move(list, '4', '9')).toThrow('Given destination id did not match any folder');
});
});
As I said, it passed 5 tests but was not accepted. What is the problem?
The toolbar for my editor is missing options. For example, I am not able to create headers.
Ive tried multiple different configurations in the JS file, though the editor seems to not change.
if (typeof(CKEDITOR) != 'undefined') {
CKEDITOR.editorConfig = function( config ) {
config.toolbar = [
{ name: 'document', items: [ 'Print' ] },
{ name: 'clipboard', items: [ 'Cut', 'Copy', 'Paste', '-', 'Undo', 'Redo' ] },
{ name: 'editing', items: [ 'Find', 'Replace', '-', 'SelectAll', '-', 'Scayt' ] },
'/',
{ name: 'basicstyles', items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-' ] },
{ name: 'paragraph', items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl', 'Language' ] },
{ name: 'links', items: [ 'Link', 'Unlink', 'Anchor' ] },
{ name: 'insert', items: [ 'Image', 'Table', 'HorizontalRule', 'Smiley', 'SpecialChar', 'PageBreak', ] },
'/',
{ name: 'styles', items: [ 'Styles', 'Format', 'Font', 'FontSize' ] },
{ name: 'colors', items: [ 'TextColor', 'BGColor' ] },
{ name: 'tools', items: [ 'Maximize', 'ShowBlocks' ] },
{ name: 'about', items: [ 'About' ] }
];
};
}
I'm trying to have a full toolbar as I am creating a website with blog articles and need all of the possible styling options.
It depends on your configuration, but I'm guessing you have some stale assets sitting around. You may need to run rake:assets:clobber. After that you can restart your development server, refresh the page, and the assets should re-compile.
Using Javascript or jQuery how can I change single-dimension array into multi-dimension array or nested array. Please assume that the nesting can go infinite level deeper.
This is how I get the data:
var beforeModifiedObj = [
{
id: '1',
title: 'Awesome Group',
type: '1',
parentID: '0',
},
{
id: '2',
title: 'Rockers',
type: '2',
parentID: '0'
},
{
id: '3',
title: 'Dazzlers',
type: '3',
parentID: '0'
},
{
id: '4',
title: 'Rock-n-Rolla',
type: '3',
parentID: '0'
},
{
id: '5',
title: 'Child in Level Two - A',
type: '2',
parentID: '1',
},
{
id: '6',
title: 'Child in Level Three - A',
type: '2',
parentID: '5',
},
{
id: '7',
title: 'Child in Level Two - B',
type: '2',
parentId: '1'
},
{
id: '8',
title: 'Child in Level Three - B',
type: '2',
parentID: '5',
}
];
Once processed it needs to look like below:
var AfterModifiedObj = [
{
id: '1',
title: 'Awesome Group',
type: '1',
parentID: '0',
groups: [
{
id: '5',
title: 'Child in Level Two - A',
type: '2',
parentID: '1',
groups: [
{
id: '6',
title: 'Child in Level Three - A',
type: '2',
parentID: '5',
},
{
id: '8',
title: 'Child in Level Three - B',
type: '2',
parentID: '5',
}
]
},
{
id: '7',
title: 'Child in Level Two - B',
type: '2',
parentID: '1'
}
]
},
{
id: '2',
title: 'Rockers',
type: '2',
parentID: '0'
},
{
id: '3',
title: 'Dazzlers',
type: '3',
parentID: '0'
},
{
id: '4',
title: 'Rock-n-Rolla',
type: '3',
parentID: '0'
},
];
Some time ago, I needed a similar function which I slightly adapted to meet your requirements. I commented it and hope that helps. Note in beforeModifiedObj element with ID 7 has a typo in parentID!
function findAndInsert(searchArray, toInsert) {
// We use an internal function in order to avoid that a developer has to pass an additional
// value to findAndInsert
function findAndInsertAcc(searchArray, toInsert, isTopLevel) {
var wasParentFound = false;
for (var i = 0; i < searchArray.length; i++) {
// Item was found => insert and create groups attribute
if (searchArray[i].id == toInsert.parentID) {
if (searchArray[i].groups === undefined) {
searchArray[i].groups = [];
}
searchArray[i].groups.push(toInsert);
wasParentFound = true;
break;
} else if ('groups' in searchArray[i]) {
// Recursively continue the search in the groups property
wasParentFound = findAndInsertAcc(searchArray[i].groups, toInsert, false);
if (wasParentFound) {
break;
}
}
}
// Insert into the top-level array if no item was found and we're in the top level
if (!wasParentFound && isTopLevel) {
searchArray.push(toInsert);
}
return wasParentFound;
}
findAndInsertAcc(searchArray, toInsert, true);
}
var afterModifiedObj = [];
beforeModifiedObj.forEach(function(next) {
findAndInsert(afterModifiedObj, next);
});
So I have little dilemma here. I have a nested json object that is inside ng-repeat and is sortable using AngularJS UI Sortable (based on JQuery UI Sortable):
$scope.rootItem = {
id: '1',
type: 'course',
title: 'Adobe Photoshop CC for beginners',
items: [{
id: '2',
type: 'label',
title:'label',
items:[{
id: '3',
type: 'module',
title:'Module title',
items: [{
id: '4',
type: 'topic',
title:'Topic title',
items: [{
id: '5',
type: 'content',
title:'Content title'
}, {
id: '6',
type: 'content',
title:'Content title'
}]
}]
},{
id: '7',
type: 'resources',
title:'Resources'
},{
id: '8',
type: 'module',
title:'Module title',
items: [{
id: '9',
type: 'topic',
title:'Topic',
items: [{
id: '10',
type: 'question',
title:'Question title'
}]
}, {
id: '11',
type: 'topic',
title:'Topic title',
items: [{
id: '12',
type: 'content',
title:'Content title'
}]
}]
}]
},{
id: '14',
type: 'assessmentLabel',
title: 'Assessment Label',
items: [{
id: '15',
type: 'assessment',
title: 'Assessment Title',
items: [{
id: '16',
type: 'courseAssessment',
title: 'Course Question Group',
items: []
}]
}]
}]
};
What I should be able to do is remove any of the items within this object, and if it has any children they need to be remove too.
So what I would generally think is passing either $index and use splice to remove it (if it was an array).
But for objects doesnt seem to work this way, I read online that delete should be used instead...
On my button I try to pass the object itself as in:
data-ng-click="removeItem(ngModelItem)"
and in my controller do something like this:
// Remove Item from the list
$scope.removeItem = function(item) {
};
Any suggestions?
Use ngModelItem
<li ng-repeat="innerItem in ngModelItem.items">
Delete me
in your controller,
$scope.deleteItem = function(collection, index){
collection.splice(index,1);
};
Demo
For removing json elements from a json object you use the delete operator. But in your case, I assume you want to remove a json object from a json array, so you should really use splice() instead.
You should receive both the list and the index in your removeItem() function so you can remove the indexed element and angularjs will update your view.