How to configure simple links and image attaching in ckeditor? - django

I'm using django-ckeditor and I have some problems with the links and images.
Regarding Links:
In this interface you can see that this is not usable by the end users, as it is too complex and can lead to errors and security issues, as the button Browse Server literally permits the user browse uploaded content. What I want is something really simple: just an input text that automatically appends http (if not typed by user) and that opens the link in a new window aka target _blank.
I've tried to do so editing config.js with the following code. This has removed the Upload and Advanced tabs, removed unnecessary widgets from Info tab and made target _blank by default. But the Target tab is still present and the users can change it, as I apparently can't remove this tab, or else the default target is ignored I'm stuck with this. So, how can I set the target to _blank and remove the Target tab too? Is there a way to hide this tab, but not remove it?
CKEDITOR.on('dialogDefinition', function(ev) {
// Take the dialog name and its definition from the event data.
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
// Check if the definition is from the dialog we're
// interested in (the 'link' dialog).
if (dialogName == 'link') {
// Remove the 'Target', 'Upload' and 'Advanced' tabs from the 'Link' dialog.
// dialogDefinition.removeContents('target');
dialogDefinition.removeContents('upload');
dialogDefinition.removeContents('advanced');
// Get a reference to the 'Link Info' tab.
var infoTab = dialogDefinition.getContents('info');
// Remove unnecessary widgets from the 'Link Info' tab.
infoTab.remove('linkType');
infoTab.remove('protocol');
infoTab.remove('browse');
// Get a reference to the "Target" tab.
var targetTab = dialogDefinition.getContents('target');
// Set the default value for the URL field.
var targetField = targetTab.get('linkTargetType');
targetField['default'] = '_blank';
}
});
Regarding images:
There is a very similar situation: several tabs with too much options. What I need is something as easy as the option to attach images in Stackoverflow. Is there any free plugin that could allow me to add images through a link and by uploading them from the computer (with previsualization) using the ckeditor?
Thanks!

Finally I get simple dialogs for: including links, attaching images from a link or uploading from the computer and to include Youtube videos in a simple way. To do this I've edited the configuration file called config.js and it looks like this for my version CKeditor 4.1.2:
CKEDITOR.editorConfig = function( config ) {
// Define changes to default configuration here.
// For the complete reference:
// http://docs.ckeditor.com/#!/api/CKEDITOR.config
// Comment the following line in DEBUG mode:
config.removePlugins = 'devtools';
// See the most common block elements.
config.format_tags = 'p;h1;h2;h3;pre';
// Make dialogs simpler.
config.removeDialogTabs = 'image:advanced;image:Link;link:advanced;link:upload';
config.linkShowTargetTab = false;
// In CKEditor 4.1 or higher you need to disable ACF (Advanced Content Filter)
// to make Youtube plugin work:
config.allowedContent = true;
};
CKEDITOR.on('dialogDefinition', function(ev) {
// Take the dialog name and its definition from the event data.
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
// Check if the definition is from the dialog we're
// interested in (the 'link' dialog).
if (dialogName == 'link') {
// Remove the 'Upload' and 'Advanced' tabs from the 'Link' dialog.
// dialogDefinition.removeContents('upload');
// dialogDefinition.removeContents('advanced');
// Get a reference to the 'Link Info' tab.
var infoTab = dialogDefinition.getContents('info');
// Remove unnecessary widgets from the 'Link Info' tab.
infoTab.remove('linkType');
infoTab.remove('protocol');
infoTab.remove('browse');
// Get a reference to the "Target" tab and set default to '_blank'
var targetTab = dialogDefinition.getContents('target');
var targetField = targetTab.get('linkTargetType');
targetField['default'] = '_blank';
} else if (dialogName == 'image') {
// Remove the 'Link' and 'Advanced' tabs from the 'Image' dialog.
// dialogDefinition.removeContents('Link');
// dialogDefinition.removeContents('advanced');
// Get a reference to the 'Image Info' tab.
var infoTab = dialogDefinition.getContents('info');
// Remove unnecessary widgets/elements from the 'Image Info' tab.
infoTab.remove('browse');
infoTab.remove('txtHSpace');
infoTab.remove('txtVSpace');
infoTab.remove('txtBorder');
// infoTab.remove('cmbAlign');
}
});
To do this I've read a lot of documentation, but the best pages that have inpired me are the following:
http://ckeditor.com/ckeditor_4.1rc/samples/plugins/toolbar/toolbar.html
http://ckeditor.com/forums/Support/Removing-Tabs-Image-Dialog
http://ckeditor.com/forums/CKEditor/Complete-list-of-toolbar-items
http://khaledben.wordpress.com/2012/04/28/customize-ckeditor-dialog/
http://www.question2answer.org/qa/13255/simple-ckeditor-how-to-modify-it-to-be-simple-solution
I hope this helps someone else with the same problem. Cheers!

Here are a lot of tweaks I did for CKEditor v3.6.1 to make it usable (esp. image dialog and link dialog). They seem to work for CKEditor 4.x as well, just take what you need for your config.js:
CKEDITOR.editorConfig = function( config ) {
// Define changes to default configuration here. For example:
config.language = 'de';
config.extraPlugins = 'colordialog';
// config.extraPlugins = 'matheeditor';
// config.uiColor = '#AADC6E';
// config.image_previewText = CKEDITOR.tools.repeat('Custom lorem ipsum text here', 8 );
// config.contentsLanguage = 'de';
config.linkShowAdvancedTab = false;
config.linkShowTargetTab = false;
config.height = 350;
config.width = 680;
// change color palette
config.colorButton_colors = 'F00,11C11D,00F,B700B7,FF8C00,008080,808080,D3D3D3';
config.colorButton_enableMore = false;
// smaller editor-width for mobile devices
if (/iPhone|iPod/i.test(navigator.userAgent)) {
config.width = 300;
}
// for resizing the editor window
config.resize_minHeight = 350;
config.resize_maxHeight = 880;
config.resize_maxWidth = 910;
// remove all formatting from pasted text
config.forcePasteAsPlainText = true;
// remove font size, family, bg color from pasted text
config.pasteFromWordRemoveFontStyles = true;
// allow browser's spell checker
config.disableNativeSpellChecker = false;
// disable ckeditor context menu to allow native context menu (works on holding CTRL)
// open: http://stackoverflow.com/questions/2246631/how-to-disable-ckeditor-context-menu/12477378
// shortcuts for firefox and chrome (editor breaks if assigned in IE9)
// if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1 || navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
if ( !(/MSIE (\d+\.\d+);/.test(navigator.userAgent)) ) {
config.keystrokes = [
// [ CKEDITOR.SHIFT + 45, 'pastefromword' ], //INS
[ CKEDITOR.CTRL + 76, 'link' ], //L
[ CKEDITOR.CTRL + CKEDITOR.ALT + 66, 'image' ], //B
[ CKEDITOR.CTRL + CKEDITOR.SHIFT + 77, 'specialchar' ], //M
[ CKEDITOR.CTRL + CKEDITOR.SHIFT + 188, 'subscript' ], //COMMA
[ CKEDITOR.CTRL + CKEDITOR.SHIFT + 109, 'subscript' ], //-
[ CKEDITOR.CTRL + CKEDITOR.SHIFT + 191, 'subscript' ], //#
[ CKEDITOR.CTRL + CKEDITOR.SHIFT + 190, 'superscript' ], //PERIOD
[ CKEDITOR.CTRL + CKEDITOR.SHIFT + 107, 'superscript' ], //+
[ CKEDITOR.CTRL + 66, 'bold' ], //B
[ CKEDITOR.CTRL + 73, 'italic' ], //I
[ CKEDITOR.CTRL + 85, 'underline' ], //U
[ CKEDITOR.CTRL + CKEDITOR.SHIFT + 70, 'bold' ], //F
[ CKEDITOR.CTRL + CKEDITOR.SHIFT + 75, 'italic' ], //K
[ CKEDITOR.CTRL + CKEDITOR.SHIFT + 85, 'underline' ], //U
];
}
};
CKEDITOR.on( 'dialogDefinition', function( ev ) {
// take the dialog name and its definition from the event data
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
//var dialog = CKEDITOR.dialog.getCurrent();
//alert( dialog.getName() );
// check if the definition is from the dialog we are interested in (the 'link' dialog).
if(dialogName == 'link') {
dialogDefinition.onShow = function () {
var dialog = CKEDITOR.dialog.getCurrent();
//dialog.hidePage( 'target' ); // via config
//dialog.hidePage( 'advanced' ); // via config
elem = dialog.getContentElement('info','anchorOptions');
elem.getElement().hide();
elem = dialog.getContentElement('info','emailOptions');
elem.getElement().hide();
var elem = dialog.getContentElement('info','linkType');
elem.getElement().hide();
elem = dialog.getContentElement('info','protocol');
elem.disable();
};
}
else if(dialogName == 'image') {
// get a reference to the 'Link Info' tab.
var infoTab = dialogDefinition.getContents('info');
// remove unnecessary fields
infoTab.remove('ratioLock');
infoTab.remove('txtHeight');
infoTab.remove('txtWidth');
infoTab.remove('txtBorder');
infoTab.remove('txtHSpace');
infoTab.remove('txtVSpace');
infoTab.remove('cmbAlign');
//hide image preview (v2)
//field = infoTab.get( 'htmlPreview' );
//field.style = 'display:none';
// memo: dialogDefinition.onShow = ... throws JS error (C.preview not defined)
dialogDefinition.onLoad = function () {
var dialog = CKEDITOR.dialog.getCurrent();
// hide image preview
var elem = dialog.getContentElement('info','htmlPreview');
elem.getElement().hide();
// hide tabs and show only upload
dialog.hidePage('Link');
dialog.hidePage('advanced');
this.selectPage('Upload');
// hide url on start up, prevent user input external image URLs
// goes in onShow of image.js: dialog.hidePage('info');
// hide ok button so that upload button can only be used
// goes in onShow of image.js: document.getElementById(this.getButton('ok').domId).style.display='none';
// on tab switching or automatic after upload
this.on('selectPage', function (e) {
// show okay button of ckeditor dialog
document.getElementById(this.getButton('ok').domId).style.display='inline';
// after upload the selectPage is fired, show Bild-Info then
dialog.showPage( 'info' );
});
};
}
else if(dialogName == 'table') {
dialogDefinition.removeContents('advanced');
}
});

If you are using django-ckeditor, you can simply have the following configuration in the settings.py file. You can configure it to your needs. No need to mess with JS.
CKEDITOR_CONFIGS = {
'default': {
'toolbar': 'Custom',
'toolbar_Custom': [
['Bold', 'Italic', 'Underline', 'Strike'],
[
'NumberedList',
'BulletedList',
'Outdent',
'Indent',
'-',
'JustifyLeft',
'JustifyCenter',
'JustifyRight',
'JustifyBlock'
],
['Link', 'Unlink'],
['RemoveFormat', 'Source'],
],
'height': 300,
'width': 695,
'linkShowAdvancedTab': False,
'linkShowTargetTab': True,
},
}

Regarding links
Feel free to remove "Target" tab:
dialogDefinition.removeContents( 'target' );
Use the power of dataProcessor instead:
CKEDITOR.replace( 'editor1', {
on: {
instanceReady: function() {
this.dataProcessor.htmlFilter.addRules( {
elements: {
a: function( element ) {
element.attributes.target = '_blank';
}
}
});
}
}
} );
This will add target="_blank" to all <a> elements in editor output. See docs to know more.
Regarding images
There's nothing much beyond CKFinder (commercial), KCFinder, PDW File Browser and Jasfinder. At least I cannot recall any more.

Related

Compose desktop(JVM) BasicTextField, Korean input duplicating

I'm implementing a simple search input field on Compose desktop.
My code looks as below.
BasicTextField(
modifier = Modifier.align(Alignment.CenterVertically).onPreviewKeyEvent {
if(it.key == Key.Enter && it.type == KeyEventType.KeyDown){
println("enter down: $textFieldState")
true
}else {
false
}
},
value = textFieldState,
onValueChange = { input ->
textFieldState = input
},
textStyle = TextStyle(
fontSize = 14.sp,
textAlign = TextAlign.Start,
fontWeight = FontWeight.Normal,
fontFamily = NotoSans,
color = Color.Black
),
maxLines = 1,
decorationBox = { innerTextField ->
Row(modifier = Modifier.fillMaxWidth()) {
if (textFieldState.isEmpty()) {
Text(
text = "Search with user name.",
fontSize = 14.sp,
color = Color(0xFF909ba9),
textAlign = TextAlign.Start,
fontWeight = FontWeight.Normal,
fontFamily = NotoSans,
modifier = Modifier.fillMaxWidth()
.align(Alignment.CenterVertically),
)
}
}
innerTextField()
}
)
This code will create a textfield which has 1 max lines.
It works without any problem on english inputs.
But when I type in Korean inputs, keys such as space, enter, or even numbers will duplicate the last Korean character. For example, in english, if I type in H, I, !,
it will be HII!.
Is there some locale settings that can be done to the textField?
I found no working solution in here or in the Compose multiplatform git issue page. I found a workaround using SwingPanel and JTextField.
SwingPanel(background = Color(0xFFf5f6f6), modifier = Modifier.fillMaxSize(), factory = {
//Some JTextfield I've obtaines from stackoverflow to show place holder text.
//Can be replaced to JTextField(columnCount:Int)
HintTextField("Enter in name",1).apply {
background = java.awt.Color(0xf5, 0xf6, 0xf6)
border = null
}
}, update = {
//SimpleDocumentListener is an implementation of DocumentListener.
//Which means it can be replaced by it.
it.document.addDocumentListener(object : SimpleDocumentListener{
override fun update(e: DocumentEvent) {
try{
val text = it.text
textFieldState = text
} catch(e : Exception) {
e.printStackTrace()
}
}
})
//I need an enter key to trigger some search logics.
//textFieldState seems to print the value as I intended
it.addKeyListener(object : KeyAdapter(){
override fun keyPressed(e: KeyEvent?) {
if(e?.keyCode == KeyEvent.VK_ENTER){
println("ENTER : $textFieldState")
}
}
})
})
Really hope the compose multiplatform team comes up with a better solution.

Shiny downloadHandler two image format downloading error

radioButtons(inputId = "var1", label = "Select type", choices = list("pdf", "png"))
I used above radio button for choosing format and followed code for download file
output$down <- downloadHandler(
filename = function() {
paste("The Plot", input$var1,sep=".")
},
content = function(file) {
if(input$var1 == "pdf")
pdf(file)
else
png(file)
print(vals$gg1)
dev.off() } )
But it generating only PDF and no png out. Is there any problem with my loop
Need to add cairo
png(file),type="cairo"

How to highlight a column programmatically in AMCharts 4?

In AMCharts version 3, there is a demo showing how to highlight a particular column.
Is this possible using AMCharts version 4? For example, in the Simple Column demo, highlight the UK column based on its value (ie, where country = 'UK').
I tried modifying the example at https://stackoverflow.com/a/54358490/906814 but I can't get a handle on the columns in order to assess their values and then apply the active state highlight (JSFiddle).
// copied from https://stackoverflow.com/a/54358490/906814 but not working yet
var activeState = series.columns.template.states.create("active");
activeState.properties.fill = am4core.color("#E94F37");
series.columns.each(function(column) {
alert("column") // no alert is seen
column.setState("active");
column.isActive = true;
})
There are two approaches you can take.
1) Use an adapter on the column's fill and stroke and check the column value before modifying the color, e.g.
series.columns.template.adapter.add('fill', function(fill, target) {
if (target.dataItem && target.dataItem.categoryX == "UK") {
return "#ff0000";
}
return fill;
});
series.columns.template.adapter.add('stroke', function(stroke, target) {
if (target.dataItem && target.dataItem.categoryX == "UK") {
return "#ff0000";
}
return stroke;
})
Demo
2) Use a property field and set the stroke and fill from your data:
chart.data = [
// ...
{
"country": "UK",
"value": 1122,
"color": "#ff0000"
},
// ...
];
// ...
series.columns.template.propertyFields.fill = "color";
series.columns.template.propertyFields.stroke = "color";
Demo

How do I hide values past the x-axis in chartjs 2.0?

How do I hide values past the x-axis in chartjs 2.0? You will notice the chart juts past the -60 mark. The x-axis uses a time scale and I have the max and min values set.
Here's my chart configuration:
{
"type":"line",
"data":{
"datasets":[
{
"label":"Scatter Dataset",
"data":[
{
"x":"2016-09-16T16:36:53Z",
"y":88.46153846153845
},
...
{
"x":"2016-09-16T16:37:54Z",
"y":88.3076923076923
}
],
"pointRadius":0,
"backgroundColor":"rgba(0,0,255,0.5)",
"borderColor":"rgba(0,0,255,0.7)"
}
]
},
"options":{
"title":{
"display":true,
"text":"Water Level Over Last 60 Seconds"
},
"animation":false,
"scales":{
"xAxes":[
{
"type":"time",
"position":"bottom",
"display":true,
"time":{
"max":"2016-09-16T16:37:54Z",
"min":"2016-09-16T16:36:54.000Z",
"unit":"second",
"unitStepSize":5
},
"ticks":{
callback: function(value, index, values) {
return "-" + (60 - 5 * index);
}
}
}
],
"yAxes":[
{
"display":true,
"ticks":{
}
}
]
},
"legend":{
"display":false
}
}
}
You can achieve this using Chart.js plugins. They let you handle events occuring while creating, updating or drawing the chart.
Here, you'll need to affect before the chart is initialised :
// We first create the plugin
var cleanOutPlugin = {
// We affect the `beforeInit` event
beforeInit: function(chart) {
// Replace `ticks.min` by `time.min` if it is a time-type chart
var min = chart.config.options.scales.xAxes[0].ticks.min;
// Same here with `ticks.max`
var max = chart.config.options.scales.xAxes[0].ticks.max;
var ticks = chart.config.data.labels;
var idxMin = ticks.indexOf(min);
var idxMax = ticks.indexOf(max);
// If one of the indexes doesn't exist, it is going to bug
// So we better stop the program until it goes further
if (idxMin == -1 || idxMax == -1)
return;
var data = chart.config.data.datasets[0].data;
// We remove the data and the labels that shouldn't be on the graph
data.splice(idxMax + 1, ticks.length - idxMax);
data.splice(0, idxMin);
ticks.splice(idxMax + 1, ticks.length - idxMax);
ticks.splice(0, idxMin);
}
};
// We now register the plugin to the chart's plugin service to activate it
Chart.pluginService.register(cleanOutPlugin);
The plugin is basically a loop through the data to remove the values that shouldn't be displayed.
You can see this plugin working in a live example on jsFiddle.
For instance, the following chat with a min set to 2 and a max to 6 ...
... would give the following result :

Couchbase custom reduce function

I have some documents in my Couchbase with the following template:
{
"id": 102750,
"status": 5,
"updatedAt": "2014-09-10T10:50:39.297Z",
"points1": 1,
"points2": -3,
"user1": {
"id": 26522,
...
},
"user2": {
"id": 38383,
...
},
....
}
What I want to do is to group the documents on the user and sum the points for each user and then show the top 100 users in the last week. I have been circling around but I haven't come with any solution.
I have started with the following map function:
function (doc, meta) {
if (doc.user1 && doc.user2) {
emit(doc.user1.id, doc.points1);
emit(doc.user2.id, doc.points2);
}
}
and then tried the sum to reduce the results but clearly I was wrong because I wasn't able to sort on the points and I couldn't also include the date parameter
you need to see my exemple I was able to group by date and show the values with reduce. but calculate the sum I did it in my program.
see the response How can I groupBy and change content of the value in couchbase?
I have solved this issue by the help of a server side script.
What I have done is I changed my map function to be like this:
function (doc, meta) {
if (doc.user1 && doc.user2) {
emit(dateToArray(doc.createdAt), { 'userId': doc.user1.id, 'points': doc.points1});
emit(dateToArray(doc.createdAt), { 'userId': doc.user2.id, 'points': doc.points2});
}
}
And in the script I query the view with the desired parameters and then I group and sort them then send the top 100 users.
I am using Node JS so my script is like this: (the results are what I read from couchbase view)
function filterResults(results) {
debug('filtering ' + results.length + ' entries..');
// get the values
var values = _.pluck(results, 'value');
var groupedUsers = {};
// grouping users and sum their points in the games
// groupedUsers will be like the follwoing:
// {
// '443322': 33,
// '667788': 55,
// ...
// }
for (var val in values) {
var userId = values[val].userId;
var points = values[val].points;
if (_.has(groupedUsers, userId)) {
groupedUsers[userId] += points;
}
else
groupedUsers[userId] = points;
}
// changing the groupedUsers to array form so it can be sorted by points:
// [['443322', 33], ['667788', 55], ...]
var topUsers = _.pairs(groupedUsers);
// sort descending
topUsers.sort(function(a, b) {
return b[1] - a[1];
});
debug('Number of users: ' + topUsers.length + '. Returning top 100 users');
return _.first(topUsers, 100);
}