forked from radpet/ckeditor-layoutmanager
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplugin.js
467 lines (418 loc) · 15.6 KB
/
plugin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
/**
* This plugin adds various custom layouts using twitter bootstrap grid system.
* Author: Radoslav Petkov
**/
'use strict';
CKEDITOR.plugins.add('layoutmanager', {
requires: 'basewidget',
icons: 'addlayout',
lang: 'en,bg',
init: pluginInit
});
/**
* Config variables
* config.layoutmanager_loadbootstrap By default is set to false, otherwise loads the embedded bootstrap style.
* config.layoutmanager_allowedContent By default is set to allow all tags.
* config.layoutmanager_buttonBoxWidth The width of each layout-preview button in the dialog.
* config.layoutmanager_removeLayoutMsg The message displayed on the window for confirmation of the remove layout operation.
* config.layoutmanager_columnStart The Bootstrap grid device breakpoint: xs, sm, md, lg.
*/
function pluginInit(editor) {
if (editor.config.layoutmanager_loadbootstrap) {
if (typeof editor.config.contentsCss == 'object') {
editor.config.contentsCss.push(CKEDITOR.getUrl(this.path + 'css/bootstrap.css'));
} else {
editor.config.contentsCss = [CKEDITOR.getUrl(this.path + 'css/bootstrap.css')];
}
}
if (typeof editor.config.contentsCss == 'object') {
editor.config.contentsCss.push(CKEDITOR.getUrl(this.path + 'css/style.css'));
} else {
editor.config.contentsCss = [CKEDITOR.getUrl(this.path + 'css/style.css')];
}
var layoutManager = new LayoutManager(editor);
layoutManager.generateWidgets();
//Creates local namespace for the plugin to share data.
editor.layoutmanager = {};
var allowedContent;
if (editor.config.layoutmanager_allowedContent) {
allowedContent = editor.config.layoutmanager_allowedContent();
} else {
allowedContent = 'p a div span h2 h3 h4 h5 h6 section article iframe object embed strong b i em cite pre blockquote small sub sup code ul ol li dl dt dd table thead tbody th tr td img caption mediawrapper br[href,src,target,width,height,colspan,span,alt,name,title,class,id,data-options]{text-align,float,margin}(*)';
}
layoutManager.setAllowedContent(allowedContent);
var addLayoutDialog = layoutManager.addLayoutDialog.bind(layoutManager);
var manageLayoutDialog = layoutManager.manageLayoutDialog.bind(layoutManager);
CKEDITOR.dialog.add('addLayoutDialog' + editor.name, addLayoutDialog);
CKEDITOR.dialog.add('manageLayoutDialog' + editor.name, manageLayoutDialog);
editor.addCommand('addlayout', {
exec: function (editor) {
editor.openDialog('addLayoutDialog' + editor.name);
}
});
editor.addCommand('managelayout', {
exec: function (editor, data) {
editor.openDialog('manageLayoutDialog' + editor.name);
}
});
editor.ui.addButton('AddLayout', {
title: editor.lang.layoutmanager.title,
command: 'addlayout'
});
}
/**
* LayoutManager class implementing all layout functionalities.
* Author: Radoslav Petkov
*
* Variables stored into the editor's object:
* {ckeditor.dom.element} editor.layoutmanager.selectedLayout.wrappet The selected widget wrapper element.
* {ckeditor.dom.element} editor.layoutmanager.selectedLayout.widget The selected widget instance.
*/
function LayoutManager(editor) {
this.editor = editor;
var columnStart = this.editor.config.layoutmanager_columnStart;
if (columnStart === undefined) {
var columnStart = 'xs';
}
this.layoutBuilder = new LayoutBuilder(columnStart);
if (!editor.config.layoutmanager_buildDefaultLayouts) {
this.layoutBuilder.buildDefaultLayouts();
}
}
LayoutManager.prototype.setAllowedContent = function (allowedContent) {
this.allowedContent = allowedContent;
};
/**
* The button's view should be small representation of the actual layout.
* In order to accomplish it ckeditor's styles should be overrided by adding hardcoded styles to the elements
* such as width,height,border and position.
*
* @param {integer} columns The count of the columns.
* @param {integer array} columnsSizes Holds the size of each column in ratio columnsSizes[i] : 12.
*/
LayoutManager.prototype.createButtonView = function (columns, columnsSizes) {
var colWidth = [];
var boxWidth = 58;
if (this.editor.config.layoutmanager_buttonBoxWidth) {
boxWidth = editor.config.layoutmanager_buttonBoxWidth;
}
var seedWidth = ((boxWidth - 2) / 12); // Substracts two pixels for the left and right border
for (var i = 1; i <= 12; i++) {
colWidth[i] = (seedWidth * i);
}
var html = '<div class="container-fluid">';
for (var i = 0; i < columns; i++) {
// If the column is not in the beginning set the border-left to 0.
// The height of the button is set on 30px.
html = html.concat('<div style="cursor:pointer;border:1px solid #778899;float:left;position:relative;background:#B0C4DE;text-align:center;height:30px;line-height: 30px;width:' + (colWidth[columnsSizes[i]] - 1) + 'px;' + ((i != 0) ? 'border-left:none' : '') + ' "> ' + '</div>');
}
html = html.concat('</div>');
return {
html: html,
width: boxWidth
}
};
LayoutManager.prototype.createButton = function (type, action) {
var cols = type.split("/");
var button = this.createButtonView(cols.length, cols);
return {
type: 'html',
id: type,
html: button.html,
className: " ",
style: 'width:' + button.width + 'px;',
onClick: function (evt) {
action(type);
this._.dialog.hide();
}
};
};
/**
* Create button for each layout
*/
LayoutManager.prototype.generateButtonObjects = function (action) {
var layouts = this.layoutBuilder.getLayouts();
var rows = {};
for (var type in layouts) {
if (layouts.hasOwnProperty(type)) {
var button = this.createButton(type, action);
if (!rows.hasOwnProperty(layouts[type].rowPosition)) {
rows[layouts[type].rowPosition] = [];
}
rows[layouts[type].rowPosition].push(button);
}
}
return rows;
};
LayoutManager.prototype.createDialogDefinition = function (title, minWidth, minHeight, buttonsObjects) {
var elementsRows = [];
for (var row in buttonsObjects) {
if (buttonsObjects.hasOwnProperty(row)) {
elementsRows.push({
type: 'hbox',
id: ('row' + row),
children: buttonsObjects[row]
});
}
}
return {
title: title,
minWidth: minWidth,
minHeight: minHeight,
resizable: CKEDITOR.DIALOG_RESIZE_NONE,
buttons: [CKEDITOR.dialog.cancelButton],
contents: [{
elements: elementsRows
}]
};
};
LayoutManager.prototype.insertLayoutAction = function (name) {
this.editor.execCommand(name);
this.editor.fire('layoutmanager:layout-inserted');
};
LayoutManager.prototype.addLayoutDialog = function () {
var width = 200;
var height = 100;
var layouts = this.generateButtonObjects(this.insertLayoutAction.bind(this));
return this.createDialogDefinition(this.editor.lang.layoutmanager.addLayoutDialogTitle, width, height, layouts);
};
LayoutManager.prototype.changeLayoutAction = function (newLayoutName) {
var selectedWidget = this.editor.layoutmanager.selectedWidget;
selectedWidget.name = newLayoutName;
var currentlySelectedLayoutWidgetElement = selectedWidget.element;
var $row = $(selectedWidget.element.$).find('.layout-row');
var row = currentlySelectedLayoutWidgetElement.getChildren().getItem(0).getChildren().getItem(0);
var $columns = $row.find('.layout-column');
var columns = row.getChildren();
var columnsCount = $columns.length;
var newColumns = newLayoutName.split('/');
var newColumnsCount = newColumns.length;
var attributeTemplate = [];
attributeTemplate.push('col-' + columnStart + '-{size}');
var pattern = /col-(xs|sm|md|lg)-/;
if (newColumnsCount <= columnsCount) {
for (var i = 0; i < newColumnsCount; i++) {
var $currentColumn = $($columns[i]);
var attributeString = $currentColumn.attr('class');
var attributes = attributeString.split(' ');
for (var j = 0; j < attributes.length; j++) {
if (pattern.test(attributes[j])) {
$currentColumn.removeClass(attributes[j]);
}
}
for (var j = 0; j < attributeTemplate.length; j++) {
$currentColumn.addClass(attributeTemplate[j].replace('{size}', newColumns[i]));
}
}
for (var i = columnsCount - 1; i >= newColumnsCount; i--) {
var lastColumn = $($columns[i]);
var newLast = $($columns[i - 1]);
$(lastColumn.children()[0]).children().appendTo($(newLast.children()[0]));
lastColumn.remove();
}
} else {
for (var i = 0; i < columnsCount; i++) {
var $currentColumn = $($columns[i]);
var attributeString = $currentColumn.attr('class');
var attributes = attributeString.split(' ');
for (var j = 0; j < attributes.length; j++) {
if (pattern.test(attributes[j])) {
$currentColumn.removeClass(attributes[j]);
}
}
for (var j = 0; j < attributeTemplate.length; j++) {
$currentColumn.addClass(attributeTemplate[j].replace('{size}', newColumns[i]));
}
}
var dict = ['one', 'two', 'three'];
for (var i = columnsCount; i < newColumnsCount; i++) {
var $newCol = $('<div>').addClass('layout-column');
for (var j = 0; j < attributeTemplate.length; j++) {
$newCol.addClass(attributeTemplate[j].replace('{size}', newColumns[i]));
}
// Generate unique id for the editable
// Due to this issue http://dev.ckeditor.com/ticket/12524
var uniqueId = new Date().getTime() + Math.floor((Math.random() * 100 ) + 1);
var insertedColEditable = $('<div>').addClass('layout-column-editable').addClass('layout-column-' + dict[i]);
$newCol.append(insertedColEditable);
$row.append($newCol);
selectedWidget.initEditable(uniqueId, {
selector: '.layout-column-' + dict[i]
});
}
}
};
LayoutManager.prototype.manageLayoutDialog = function () {
var width = 200;
var height = 100;
var layouts = this.generateButtonObjects(this.changeLayoutAction.bind(this));
return this.createDialogDefinition(this.editor.lang.layoutmanager.manageLayoutDialogTitle, width, height, layouts);
};
LayoutManager.prototype.createWidget = function (name, definition) {
CKEDITOR.basewidget.addWidget(this.editor, name, definition);
};
LayoutManager.prototype.createWidgetDefinition = function (_template, _editables, _upcast, _allowedContent) {
return {
template: _template,
extend: {
init: function () {
}
},
configuration: {
init: {
blockEvents: false,
configToolbar: {
defaultButtons: {
edit: {
onClick: function () {
this.editor.layoutmanager.selectedWidget = this;
this.editor.execCommand('managelayout', this);
}
}
}
},
onDestroy: function () {
}
}
},
editables: _editables,
upcast: _upcast,
allowedContent: _allowedContent
};
};
LayoutManager.prototype.generateWidgets = function () {
var layouts = this.layoutBuilder.getLayouts();
this.editables = {
layoutColumn1: {
selector: '.layout-column-one',
allowedContent: this.allowedContent
},
layoutColumn2: {
selector: '.layout-column-two',
allowedContent: this.allowedContent
},
layoutColumn3: {
selector: '.layout-column-three',
allowedContent: this.allowedContent
}
};
var upcast = function (element) {
return (element.hasClass('layout-container'));
};
for (var type in layouts) {
if (layouts.hasOwnProperty(type)) {
var widgetDefinition = this.createWidgetDefinition(layouts[type].template, this.editables, upcast, this.allowedContent);
this.createWidget(type, widgetDefinition);
}
}
};
LayoutManager.prototype.removeLayoutWidget = function () {
this.editor.layoutmanager.selectedLayout.widget.repository.del(this.editor.layoutmanager.selectedLayout.widget);
};
/**
* Class that builds the default templates of the layouts. It is also used to hold all available
* layout templates and provide them for use in the widget creation.
*/
function LayoutBuilder(columnStart) {
var defaultLayoutTemplates = [];
defaultLayoutTemplates.push(new CKEDITOR.template('<div class="layoutmanager">' +
'<div class="container-fluid layout-container">' +
'<div class="row layout-row" >' +
'<div class="col-' + columnStart + '-{size1} layout-column">' +
'<div class="layout-column-one layout-column-editable"><p></p></div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>'));
defaultLayoutTemplates.push(new CKEDITOR.template('<div class="layoutmanager">' +
'<div class="container-fluid layout-container">' +
'<div class="row layout-row">' +
'<div class="col-' + columnStart + '-{size1} layout-column ">' +
'<div class="layout-column-one layout-column-editable"><p></p></div>' +
'</div>' +
'<div class="col-' + columnStart + '-{size2} layout-column">' +
'<div class="layout-column-two layout-column-editable"><p></p></div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>'));
defaultLayoutTemplates.push(new CKEDITOR.template('<div class="layoutmanager">' +
'<div class="container-fluid layout-container">' +
'<div class="row layout-row">' +
'<div class="col-' + columnStart + '-{size1} layout-column">' +
'<div class="layout-column-one layout-column-editable"><p></p></div>' +
'</div>' +
'<div class="col-' + columnStart + '-{size2} layout-column">' +
'<div class="layout-column-two layout-column-editable"><p></p></div>' +
'</div>' +
'<div class="col-' + columnStart + '-{size3} layout-column">' +
'<div class="layout-column-three layout-column-editable"><p></p></div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>'));
var defaultLayoutTypes = [];
defaultLayoutTypes.push("12");
defaultLayoutTypes.push("6/6");
defaultLayoutTypes.push("9/3");
defaultLayoutTypes.push("3/9");
defaultLayoutTypes.push("8/4");
defaultLayoutTypes.push("4/8");
defaultLayoutTypes.push("7/5");
defaultLayoutTypes.push("5/7");
defaultLayoutTypes.push("4/4/4");
defaultLayoutTypes.push("6/3/3");
defaultLayoutTypes.push("3/6/3");
defaultLayoutTypes.push("3/3/6");
var layouts = {};
var trim = function (str) {
// removes newline / carriage return
str = str.replace(/\n/g, "");
// removes whitespace (space and tabs) before tags
str = str.replace(/[\t ]+\</g, "<");
// removes whitespace between tags
str = str.replace(/\>[\t ]+\</g, "><");
// removes whitespace after tags
str = str.replace(/\>[\t ]+$/g, ">");
return str;
};
/**
* Injects columns sizes taken from the type of the layout.
* @param {CKEDITOR.template} template - The template that will be injected with values.
*/
var getTemplateWithValue = function (template, type) {
var cols = type.split("/");
var injectTemplate = {};
for (var i = 0; i < cols.length; i++) {
injectTemplate["size" + (i + 1)] = cols[i];
}
return trim(template.output(injectTemplate));
};
var addLayout = function (template, type, buttonRowPosition) {
layouts[type] = {
template: getTemplateWithValue(template, type),
rowPosition: buttonRowPosition
};
};
this.addLayout = function (type, buttonRowPosition) {
var columnCount = type.split('/').length;
addLayout(defaultLayoutTemplates[columnCount - 1], type, buttonRowPosition);
};
this.buildDefaultLayouts = function () {
var row = 0;
for (var i = 0; i < defaultLayoutTypes.length; i++) {
// This count to which row in the choose dialog should the layout button be added
if (i % 4 == 0) {
row += 1;
}
var columnCount = defaultLayoutTypes[i].split('/').length;
addLayout(defaultLayoutTemplates[columnCount - 1], defaultLayoutTypes[i], row);
}
};
this.getLayout = function (type) {
return layouts[type];
};
this.getLayouts = function () {
return layouts;
};
}