@themarioga/grid-editor 5.0.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +64 -3
- package/dist/grideditor.css +5 -0
- package/dist/grideditor.min.css +1 -1
- package/dist/grideditor.min.css.map +1 -1
- package/dist/jquery.grideditor.bundle.min.js +2 -2
- package/dist/jquery.grideditor.js +110 -20
- package/dist/jquery.grideditor.min.js +1 -1
- package/dist/jquery.grideditor.min.js.map +1 -1
- package/dist/locales/grideditor.es.js +4 -0
- package/dist/locales/grideditor.es.min.js +1 -1
- package/dist/locales/grideditor.es.min.js.map +1 -1
- package/dist/plugins/grideditor.clipboard.js +274 -0
- package/dist/plugins/grideditor.clipboard.min.js +2 -0
- package/dist/plugins/grideditor.clipboard.min.js.map +1 -0
- package/dist/plugins/grideditor.sections.js +4 -4
- package/dist/plugins/grideditor.sections.min.js +1 -1
- package/dist/plugins/grideditor.sections.min.js.map +1 -1
- package/docs/events.md +4 -1
- package/docs/locale-keys.md +13 -0
- package/docs/plugins.md +10 -1
- package/example/clipboard.html +99 -0
- package/example/index.html +8 -0
- package/example/wrap_content.html +66 -16
- package/package.json +1 -1
- package/src/js/jquery.grideditor.js +110 -20
- package/src/js/locales/grideditor.es.js +4 -0
- package/src/js/plugins/grideditor.clipboard.js +274 -0
- package/src/js/plugins/grideditor.sections.js +4 -4
- package/src/less/grideditor.less +6 -0
|
@@ -2,37 +2,50 @@
|
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
<!-- CSS dependencies -->
|
|
7
7
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
|
8
8
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.css">
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
<!-- Javascript dependencies -->
|
|
11
11
|
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
|
|
12
12
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
|
13
13
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/6.8.6/tinymce.min.js"></script>
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
<!-- Grid editor CSS -->
|
|
16
16
|
<link rel="stylesheet" type="text/css" href="../dist/grideditor.css" />
|
|
17
17
|
<!-- Grid editor javascript -->
|
|
18
18
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js"></script>
|
|
19
19
|
<script src="../dist/jquery.grideditor.min.js"></script>
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
<script>
|
|
22
22
|
$(function() {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
var grid = $('#myGrid');
|
|
24
|
+
|
|
25
|
+
// 1. The markup as the page wrote it, captured before the editor
|
|
26
|
+
// touches it
|
|
27
|
+
$('#before').text(grid.html().trim());
|
|
28
|
+
|
|
29
|
+
// 2. The editor finds no .row in there, so it wraps everything
|
|
30
|
+
// into one row with a single full-width column
|
|
31
|
+
grid.gridEditor({
|
|
25
32
|
new_row_layouts: [[12], [6, 6], [9, 3], [3, 9]],
|
|
26
33
|
content_types: ['tinymce'],
|
|
27
34
|
});
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
|
|
36
|
+
// 3. What getHtml hands back: the same content, now a grid, with
|
|
37
|
+
// the marking the editor needs to read it back
|
|
38
|
+
// 4. What getPlainHtml hands back: the same, without that marking
|
|
39
|
+
function showResult() {
|
|
40
|
+
$('#after').text(grid.gridEditor('getHtml').trim());
|
|
41
|
+
$('#plain').text(grid.gridEditor('getPlainHtml').trim());
|
|
42
|
+
}
|
|
43
|
+
showResult();
|
|
44
|
+
$('#refresh').on('click', showResult);
|
|
32
45
|
});
|
|
33
46
|
</script>
|
|
34
|
-
|
|
35
|
-
<title>Grid Editor example</title>
|
|
47
|
+
|
|
48
|
+
<title>Grid Editor example - wrapped content</title>
|
|
36
49
|
</head>
|
|
37
50
|
<body>
|
|
38
51
|
<!-- Back to the demo index -->
|
|
@@ -40,10 +53,47 @@
|
|
|
40
53
|
<a class="link-secondary" href="index.html"><i class="bi bi-arrow-left"></i> All demos</a>
|
|
41
54
|
</div>
|
|
42
55
|
|
|
43
|
-
<div
|
|
44
|
-
<
|
|
45
|
-
<p>
|
|
56
|
+
<div class="container py-3">
|
|
57
|
+
<h2>Wrapped content</h2>
|
|
58
|
+
<p class="text-body-secondary">
|
|
59
|
+
The editor below was started on plain markup — a heading, a
|
|
60
|
+
paragraph and a list, with no <code>.row</code> or
|
|
61
|
+
<code>.col-*</code> anywhere. Grid editor does not refuse it: it
|
|
62
|
+
wraps all of it into one row with a single
|
|
63
|
+
<code>col-lg-12</code> column, and from there it is an ordinary
|
|
64
|
+
grid. Add rows, split the column, edit the text, then press
|
|
65
|
+
<em>Refresh</em> to see the html that comes out: as
|
|
66
|
+
<code>getHtml</code> returns it, to save and edit again, and as
|
|
67
|
+
<code>getPlainHtml</code> returns it, to publish.
|
|
68
|
+
</p>
|
|
69
|
+
|
|
70
|
+
<h5 class="mt-4">1. The markup in the page</h5>
|
|
71
|
+
<pre class="bg-body-tertiary border rounded p-3 small"><code id="before"></code></pre>
|
|
72
|
+
|
|
73
|
+
<h5 class="mt-4">2. The editor, started on that markup</h5>
|
|
74
|
+
<div id="myGrid">
|
|
75
|
+
<h1>Hello!</h1>
|
|
76
|
+
<p>This page was written as plain html, with no Bootstrap grid.</p>
|
|
77
|
+
<ul>
|
|
78
|
+
<li>No row</li>
|
|
79
|
+
<li>No column</li>
|
|
80
|
+
</ul>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<h5 class="mt-4 d-flex align-items-center gap-2">
|
|
84
|
+
3. The result of <code>getHtml</code>
|
|
85
|
+
<button id="refresh" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-clockwise"></i> Refresh</button>
|
|
86
|
+
</h5>
|
|
87
|
+
<pre class="bg-body-tertiary border rounded p-3 small"><code id="after"></code></pre>
|
|
88
|
+
|
|
89
|
+
<h5 class="mt-4">4. The result of <code>getPlainHtml</code></h5>
|
|
90
|
+
<p class="text-body-secondary small">
|
|
91
|
+
The same markup without the editor’s <code>ge-*</code> classes,
|
|
92
|
+
<code>column</code> class and <code>data-ge-*</code> attributes.
|
|
93
|
+
For publishing: the editor cannot read it back as it was.
|
|
94
|
+
</p>
|
|
95
|
+
<pre class="bg-body-tertiary border rounded p-3 small"><code id="plain"></code></pre>
|
|
46
96
|
</div>
|
|
47
97
|
|
|
48
98
|
</body>
|
|
49
|
-
</html>
|
|
99
|
+
</html>
|
package/package.json
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
var METHODS = {
|
|
20
20
|
getHtml: { value: true, noInstance: function(element) { return element.html(); } },
|
|
21
|
+
getPlainHtml: { value: true, noInstance: function(element) { return plainHtml(element.html()); } },
|
|
21
22
|
init: {},
|
|
22
23
|
deinit: {},
|
|
23
24
|
reset: {},
|
|
@@ -247,6 +248,56 @@ function dispatch(set, name, args) {
|
|
|
247
248
|
return set;
|
|
248
249
|
}
|
|
249
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Html with grid-editor's own marking taken off: the ge-* classes, the
|
|
253
|
+
* column class and the data-ge-* attributes. A div that was only there for
|
|
254
|
+
* the editor - a content area, a bare element or container wrapper - is left
|
|
255
|
+
* with nothing on it, and goes, its children taking its place.
|
|
256
|
+
*
|
|
257
|
+
* The marking is what lets the editor read its markup back, so this is for
|
|
258
|
+
* publishing, not for saving something to edit again.
|
|
259
|
+
*/
|
|
260
|
+
function plainHtml(html) {
|
|
261
|
+
// Parsed in a document of its own, which is inert: no script in the
|
|
262
|
+
// markup runs and no image starts loading while it is being cleaned
|
|
263
|
+
var root = $(document.implementation.createHTMLDocument('').body);
|
|
264
|
+
root[0].innerHTML = html;
|
|
265
|
+
var emptied = [];
|
|
266
|
+
|
|
267
|
+
root.find('*').each(function() {
|
|
268
|
+
var node = $(this);
|
|
269
|
+
var marked = false;
|
|
270
|
+
|
|
271
|
+
$.each($.makeArray(this.attributes), function(i, attribute) {
|
|
272
|
+
if (attribute.name.indexOf('data-ge-') === 0) {
|
|
273
|
+
node.removeAttr(attribute.name);
|
|
274
|
+
marked = true;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
var classes = (node.attr('class') || '').split(/\s+/).filter(Boolean);
|
|
279
|
+
var kept = classes.filter(function(name) {
|
|
280
|
+
return name !== 'column' && name.indexOf('ge-') !== 0;
|
|
281
|
+
});
|
|
282
|
+
if (kept.length !== classes.length) { marked = true; }
|
|
283
|
+
if (kept.length) {
|
|
284
|
+
node.attr('class', kept.join(' '));
|
|
285
|
+
} else {
|
|
286
|
+
node.removeAttr('class');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (marked && this.tagName === 'DIV' && !this.attributes.length) {
|
|
290
|
+
emptied.push(this);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
emptied.forEach(function(div) {
|
|
295
|
+
$(div).replaceWith($(div).contents());
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return root[0].innerHTML;
|
|
299
|
+
}
|
|
300
|
+
|
|
250
301
|
$.fn.gridEditor = function( optionsOrMethod ) {
|
|
251
302
|
|
|
252
303
|
var self = this;
|
|
@@ -918,14 +969,14 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
918
969
|
.attr('data-ge-toolbar', 'feature')
|
|
919
970
|
.attr('data-ge-feature', name)
|
|
920
971
|
.attr('data-ge-item', index)
|
|
921
|
-
.append('<i
|
|
972
|
+
.append($('<i />').addClass(item.iconClass || 'bi bi-plus'))
|
|
922
973
|
.append($('<span />').text(t(item.labelKey)))
|
|
923
974
|
.on('click', function() {
|
|
924
975
|
var made = item.create();
|
|
925
976
|
|
|
926
977
|
addNode(item.kind, made, function() {
|
|
927
978
|
made.appendTo(canvas);
|
|
928
|
-
}, { parent: canvas, source: 'tool' });
|
|
979
|
+
}, { parent: canvas, source: item.source || 'tool' });
|
|
929
980
|
})
|
|
930
981
|
.appendTo(addContainerGroup)
|
|
931
982
|
;
|
|
@@ -1145,7 +1196,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1145
1196
|
} else {
|
|
1146
1197
|
made.appendTo(where.region);
|
|
1147
1198
|
}
|
|
1148
|
-
}, { parent: where.region, source: 'dragdrop' });
|
|
1199
|
+
}, { parent: where.region, source: item.source || 'dragdrop' });
|
|
1149
1200
|
}
|
|
1150
1201
|
|
|
1151
1202
|
/** A line where the block would go, following the pointer. */
|
|
@@ -1395,6 +1446,23 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1395
1446
|
return html;
|
|
1396
1447
|
}
|
|
1397
1448
|
|
|
1449
|
+
/**
|
|
1450
|
+
* One node's markup, as getHtml would give it: the canvas goes out of
|
|
1451
|
+
* editing to read it and comes back, as it does for getHtml.
|
|
1452
|
+
*/
|
|
1453
|
+
function nodeHtml(node) {
|
|
1454
|
+
deinit();
|
|
1455
|
+
stripPixelWidths(node);
|
|
1456
|
+
var html = node[0].outerHTML;
|
|
1457
|
+
init();
|
|
1458
|
+
return html;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
/** getHtml with grid-editor's own classes and attributes taken off. */
|
|
1462
|
+
function getPlainHtml() {
|
|
1463
|
+
return plainHtml(getHtml());
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1398
1466
|
function destroy() {
|
|
1399
1467
|
deinit();
|
|
1400
1468
|
removeConfirmModal();
|
|
@@ -1535,6 +1603,12 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
1535
1603
|
utilityField: utilityField,
|
|
1536
1604
|
bareStyle: bareStyle,
|
|
1537
1605
|
rowFromLayout: rowFromLayoutValue,
|
|
1606
|
+
nodeHtml: nodeHtml,
|
|
1607
|
+
toolbarItems: function(name) {
|
|
1608
|
+
return mainControls
|
|
1609
|
+
? mainControls.find('[data-ge-toolbar="feature"][data-ge-feature="' + name + '"]')
|
|
1610
|
+
: $();
|
|
1611
|
+
},
|
|
1538
1612
|
};
|
|
1539
1613
|
}
|
|
1540
1614
|
|
|
@@ -2128,17 +2202,6 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2128
2202
|
|
|
2129
2203
|
createMoveTool(drawer);
|
|
2130
2204
|
addSettingsTool(drawer, container, settings.container_classes);
|
|
2131
|
-
if (definition.addPane) {
|
|
2132
|
-
createTool(drawer, t(definition.addPaneKey), 'ge-add-pane', 'bi bi-plus-circle', function() {
|
|
2133
|
-
var pane = definition.addPane(container, {});
|
|
2134
|
-
|
|
2135
|
-
addNode(definition.paneKind, pane, function() {}, {
|
|
2136
|
-
parent: container,
|
|
2137
|
-
source: 'tool',
|
|
2138
|
-
container: container,
|
|
2139
|
-
});
|
|
2140
|
-
});
|
|
2141
|
-
}
|
|
2142
2205
|
|
|
2143
2206
|
settings.container_tools.forEach(function(hostTool) {
|
|
2144
2207
|
createTool(drawer, hostTool.title || '', hostTool.className || '',
|
|
@@ -2152,6 +2215,19 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2152
2215
|
container.slideUp(removed);
|
|
2153
2216
|
});
|
|
2154
2217
|
});
|
|
2218
|
+
|
|
2219
|
+
// Delete then add, the order rows and columns have
|
|
2220
|
+
if (definition.addPane) {
|
|
2221
|
+
createTool(drawer, t(definition.addPaneKey), 'ge-add-pane', 'bi bi-plus-circle', function() {
|
|
2222
|
+
var pane = definition.addPane(container, {});
|
|
2223
|
+
|
|
2224
|
+
addNode(definition.paneKind, pane, function() {}, {
|
|
2225
|
+
parent: container,
|
|
2226
|
+
source: 'tool',
|
|
2227
|
+
container: container,
|
|
2228
|
+
});
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2155
2231
|
}
|
|
2156
2232
|
|
|
2157
2233
|
/**
|
|
@@ -2673,11 +2749,16 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
2673
2749
|
});
|
|
2674
2750
|
|
|
2675
2751
|
// Beside the gear, because every node that has one is a node a
|
|
2676
|
-
// utility may apply to, whichever plugin built its drawer
|
|
2752
|
+
// utility may apply to, whichever plugin built its drawer. A
|
|
2753
|
+
// feature plugin's tools go there too, for the same reason: the
|
|
2754
|
+
// gear is the one tool every drawer has.
|
|
2677
2755
|
var kind = kindOf(node);
|
|
2678
2756
|
$.each(UTILITIES, function(name, utility) {
|
|
2679
2757
|
if (utility.drawerTools) { utility.drawerTools(drawer, node, kind); }
|
|
2680
2758
|
});
|
|
2759
|
+
$.each(FEATURES, function(name, feature) {
|
|
2760
|
+
if (feature.drawerTools) { feature.drawerTools(drawer, node, kind); }
|
|
2761
|
+
});
|
|
2681
2762
|
|
|
2682
2763
|
return details.appendTo(drawer);
|
|
2683
2764
|
}
|
|
@@ -3544,7 +3625,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
3544
3625
|
var add = function() {
|
|
3545
3626
|
return addNode(kind, node, function() {
|
|
3546
3627
|
node[placement](options[placement]);
|
|
3547
|
-
}, { parent: parent, source: 'api' });
|
|
3628
|
+
}, { parent: parent, source: options.source || 'api' });
|
|
3548
3629
|
};
|
|
3549
3630
|
|
|
3550
3631
|
if (operationDepth > 0) {
|
|
@@ -3738,11 +3819,19 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
3738
3819
|
return $();
|
|
3739
3820
|
}
|
|
3740
3821
|
|
|
3822
|
+
/**
|
|
3823
|
+
* A content area for the first content type. With no content types
|
|
3824
|
+
* it is a content area and nothing more: there is no type to name.
|
|
3825
|
+
*/
|
|
3741
3826
|
function createDefaultContentWrapper() {
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3827
|
+
var type = settings.content_types[0];
|
|
3828
|
+
var contentArea = $('<div class="ge-content"/>');
|
|
3829
|
+
|
|
3830
|
+
if (type) {
|
|
3831
|
+
contentArea.addClass('ge-content-type-' + type).attr('data-ge-content-type', type);
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
return contentArea;
|
|
3746
3835
|
}
|
|
3747
3836
|
|
|
3748
3837
|
/**
|
|
@@ -3810,6 +3899,7 @@ $.fn.gridEditor = function( optionsOrMethod ) {
|
|
|
3810
3899
|
*/
|
|
3811
3900
|
var handle = {
|
|
3812
3901
|
getHtml: getHtml,
|
|
3902
|
+
getPlainHtml: getPlainHtml,
|
|
3813
3903
|
// init and reset are deferred when a handler calls them, so an
|
|
3814
3904
|
// operation in flight finishes before the canvas is rebuilt
|
|
3815
3905
|
init: function() { defer(init); },
|
|
@@ -84,6 +84,10 @@
|
|
|
84
84
|
'tool.add_row_to_section': 'Añadir fila',
|
|
85
85
|
'tool.delete_section': 'Quitar sección',
|
|
86
86
|
'confirm.delete_section': '¿Seguro que quieres borrar esta sección y todo su contenido?',
|
|
87
|
+
'tool.copy': 'Copiar',
|
|
88
|
+
'tool.paste': 'Pegar',
|
|
89
|
+
'clipboard.paste_row': 'Pegar fila',
|
|
90
|
+
'clipboard.paste_section': 'Pegar sección',
|
|
87
91
|
'utility.col_from_row': 'De la fila: {count}',
|
|
88
92
|
'utility.row_cols': 'Columnas por fila',
|
|
89
93
|
'badge.row_cols': '{count} por fila',
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy and paste for grid-editor.
|
|
3
|
+
*
|
|
4
|
+
* A feature plugin: load this file after the editor and every row, column,
|
|
5
|
+
* section, container and element gets a copy tool, and every place one of
|
|
6
|
+
* them can go gets a paste tool while there is something copied that fits.
|
|
7
|
+
* What it can ask the editor for is the handle its factory is called with,
|
|
8
|
+
* described in docs/plugins.md.
|
|
9
|
+
*
|
|
10
|
+
* <script src="dist/jquery.grideditor.min.js"></script>
|
|
11
|
+
* <script src="dist/plugins/grideditor.clipboard.min.js"></script>
|
|
12
|
+
*
|
|
13
|
+
* What is copied is kept in localStorage, so it survives a reload and can be
|
|
14
|
+
* pasted in another tab or another editor of the same site. A browser that
|
|
15
|
+
* will not give the page its storage keeps it in memory, for this page only.
|
|
16
|
+
*
|
|
17
|
+
* A paste is an add like any other - before-add-* and after-add-*, with
|
|
18
|
+
* `source: 'paste'` - so a host can turn one away.
|
|
19
|
+
*/
|
|
20
|
+
(function($) {
|
|
21
|
+
|
|
22
|
+
$.extend($.fn.gridEditor.locales.en, {
|
|
23
|
+
'tool.copy': 'Copy',
|
|
24
|
+
'tool.paste': 'Paste',
|
|
25
|
+
'clipboard.paste_row': 'Paste row',
|
|
26
|
+
'clipboard.paste_section': 'Paste section',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
var STORAGE_ITEM = 'grideditor.clipboard';
|
|
30
|
+
var CHANGE = 'grideditor-clipboard';
|
|
31
|
+
var VERSION = 1;
|
|
32
|
+
|
|
33
|
+
/** Where nothing can be stored, what was copied lives here, for this page. */
|
|
34
|
+
var memory = null;
|
|
35
|
+
var editors = 0;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* What each drawer takes when something is pasted into it, by the kind of the
|
|
39
|
+
* node the drawer belongs to. A category is what was copied: a row, a column,
|
|
40
|
+
* a section, a container of any type, an element.
|
|
41
|
+
*/
|
|
42
|
+
var TARGETS = {
|
|
43
|
+
column: ['row', 'container', 'element'],
|
|
44
|
+
row: ['column'],
|
|
45
|
+
section: ['row'],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The attributes that point at an id, as the id itself or as a #selector.
|
|
50
|
+
* A pasted copy that has to take new ids takes them here too, so its tabs,
|
|
51
|
+
* its accordion and its popups still point at their own panes.
|
|
52
|
+
*/
|
|
53
|
+
var REFERENCES = ['data-bs-target', 'data-bs-parent', 'href', 'aria-controls', 'aria-labelledby',
|
|
54
|
+
'aria-describedby', 'for', 'data-ge-popup-id', 'data-ge-popup-target'];
|
|
55
|
+
|
|
56
|
+
/** An id grid-editor generated: ge-{type}-{counter}-{random}. */
|
|
57
|
+
var GENERATED_ID = /^ge-([a-z][a-z-]*?)-\d+-[a-z0-9]+$/;
|
|
58
|
+
|
|
59
|
+
function read() {
|
|
60
|
+
var raw;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
raw = window.localStorage.getItem(STORAGE_ITEM);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return memory;
|
|
66
|
+
}
|
|
67
|
+
if (raw === null) { return memory; }
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
var clip = JSON.parse(raw);
|
|
71
|
+
return clip && clip.version === VERSION && clip.html ? clip : null;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function write(clip) {
|
|
78
|
+
try {
|
|
79
|
+
window.localStorage.setItem(STORAGE_ITEM, JSON.stringify(clip));
|
|
80
|
+
memory = null;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
memory = clip;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
$(document).trigger(CHANGE);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Another tab copied something: every editor here looks again
|
|
89
|
+
$(window).on('storage', function(e) {
|
|
90
|
+
if (e.originalEvent && e.originalEvent.key === STORAGE_ITEM) { $(document).trigger(CHANGE); }
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
$.fn.gridEditor.features.clipboard = function(ge) {
|
|
94
|
+
|
|
95
|
+
var namespace = '.ge-clipboard-' + (++editors);
|
|
96
|
+
var shown = null; // What the toolbar's paste buttons stand for
|
|
97
|
+
|
|
98
|
+
function used(name) {
|
|
99
|
+
return !ge.settings.plugins || ge.settings.plugins.indexOf(name) !== -1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** A category this editor can take: the plugin that makes it is here. */
|
|
103
|
+
function available(clip) {
|
|
104
|
+
if (clip.category === 'container') {
|
|
105
|
+
return !!$.fn.gridEditor.containers[clip.kind] && used(clip.kind);
|
|
106
|
+
}
|
|
107
|
+
if (clip.category === 'section') { return !!$.fn.gridEditor.features.sections && used('sections'); }
|
|
108
|
+
if (clip.category === 'element') { return !!$.fn.gridEditor.features.elements && used('elements'); }
|
|
109
|
+
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function fits(clip, categories) {
|
|
114
|
+
return !!clip && categories.indexOf(clip.category) !== -1 && available(clip);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function categoryOf(kind) {
|
|
118
|
+
if ($.fn.gridEditor.containers[kind]) { return 'container'; }
|
|
119
|
+
|
|
120
|
+
return ['row', 'column', 'section', 'element'].indexOf(kind) !== -1 ? kind : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function copy(node, kind) {
|
|
124
|
+
var html = ge.nodeHtml(node);
|
|
125
|
+
|
|
126
|
+
write({ version: VERSION, category: categoryOf(kind), kind: kind, html: html });
|
|
127
|
+
ge.emit('after-copy', ge.payloadFor(kind, node, { source: 'tool' }));
|
|
128
|
+
|
|
129
|
+
// The canvas came back with new drawers: the one to flash is the
|
|
130
|
+
// node's new copy tool
|
|
131
|
+
var tool = node.children('.ge-tools-drawer').find('> .ge-copy');
|
|
132
|
+
tool.addClass('ge-copied').find('i').attr('class', 'bi bi-check2');
|
|
133
|
+
setTimeout(function() {
|
|
134
|
+
tool.removeClass('ge-copied').find('i').attr('class', 'bi bi-copy');
|
|
135
|
+
}, 1200);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function paste(target, categories) {
|
|
139
|
+
var clip = read();
|
|
140
|
+
if (!fits(clip, categories)) { return; }
|
|
141
|
+
|
|
142
|
+
var node = fresh(clip.html);
|
|
143
|
+
var into = target;
|
|
144
|
+
|
|
145
|
+
// An element goes into the column's last content area. With none,
|
|
146
|
+
// the column's next init wraps it in one of its own
|
|
147
|
+
if (clip.category === 'element') {
|
|
148
|
+
var area = target.children('.ge-content').last();
|
|
149
|
+
if (area.length) { into = area; }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
ge.place(node, clip.kind, { appendTo: into, source: 'paste' });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The copied markup as a node, with new ids where the page already has
|
|
157
|
+
* them: two copies of a tab strip pointing at one set of panes is a tab
|
|
158
|
+
* strip that opens the other copy's tabs. An id the page does not have
|
|
159
|
+
* is kept, so pasting into another page changes nothing.
|
|
160
|
+
*/
|
|
161
|
+
function fresh(html) {
|
|
162
|
+
var node = $($.parseHTML(html.trim(), document, true)).first();
|
|
163
|
+
var renamed = {};
|
|
164
|
+
var taken = function(id) {
|
|
165
|
+
return !!document.getElementById(id) || Object.keys(renamed).some(function(old) {
|
|
166
|
+
return renamed[old] === id;
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
node.find('[id]').addBack('[id]').each(function() {
|
|
171
|
+
var id = this.id;
|
|
172
|
+
if (!document.getElementById(id)) { return; }
|
|
173
|
+
|
|
174
|
+
var generated = GENERATED_ID.exec(id);
|
|
175
|
+
var next;
|
|
176
|
+
|
|
177
|
+
if (generated) {
|
|
178
|
+
do { next = ge.containerId(generated[1]); } while (taken(next));
|
|
179
|
+
} else {
|
|
180
|
+
var n = 2;
|
|
181
|
+
while (taken(id + '-' + n)) { n++; }
|
|
182
|
+
next = id + '-' + n;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
renamed[id] = next;
|
|
186
|
+
this.id = next;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (!Object.keys(renamed).length) { return node; }
|
|
190
|
+
|
|
191
|
+
node.find('*').addBack().each(function() {
|
|
192
|
+
var element = this;
|
|
193
|
+
|
|
194
|
+
REFERENCES.forEach(function(name) {
|
|
195
|
+
var value = element.getAttribute(name);
|
|
196
|
+
if (value === null) { return; }
|
|
197
|
+
if (name === 'href' && value.charAt(0) !== '#') { return; }
|
|
198
|
+
|
|
199
|
+
var rewritten = value.split(/(\s+)/).map(function(token) {
|
|
200
|
+
if (renamed[token]) { return renamed[token]; }
|
|
201
|
+
if (token.charAt(0) === '#' && renamed[token.slice(1)]) { return '#' + renamed[token.slice(1)]; }
|
|
202
|
+
|
|
203
|
+
return token;
|
|
204
|
+
}).join('');
|
|
205
|
+
|
|
206
|
+
if (rewritten !== value) { element.setAttribute(name, rewritten); }
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return node;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Show the paste tools, and the toolbar's paste buttons, that fit what is copied. */
|
|
214
|
+
function refresh() {
|
|
215
|
+
var clip = read();
|
|
216
|
+
|
|
217
|
+
ge.canvas.find('.ge-paste').each(function() {
|
|
218
|
+
$(this).toggle(fits(clip, $(this).attr('data-ge-paste').split(' ')));
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
ge.toolbarItems('clipboard').each(function() {
|
|
222
|
+
var item = TOOLBAR[parseInt($(this).attr('data-ge-item'), 10)];
|
|
223
|
+
$(this).toggle(fits(clip, [item.kind]));
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
shown = clip;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
var TOOLBAR = [
|
|
230
|
+
{ kind: 'row', labelKey: 'clipboard.paste_row' },
|
|
231
|
+
{ kind: 'section', labelKey: 'clipboard.paste_section' },
|
|
232
|
+
].map(function(item) {
|
|
233
|
+
return $.extend(item, {
|
|
234
|
+
iconClass: 'bi bi-clipboard-plus',
|
|
235
|
+
source: 'paste',
|
|
236
|
+
// What the button showed, even if another tab has copied
|
|
237
|
+
// something else since
|
|
238
|
+
create: function() { return fresh(shown.html); },
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
drawerTools: function(drawer, node, kind) {
|
|
244
|
+
if (categoryOf(kind)) {
|
|
245
|
+
ge.createTool(drawer, ge.t('tool.copy'), 'ge-copy', 'bi bi-copy', function() {
|
|
246
|
+
copy(node, kind);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
var categories = TARGETS[kind];
|
|
251
|
+
if (!categories) { return; }
|
|
252
|
+
|
|
253
|
+
ge.createTool(drawer, ge.t('tool.paste'), 'ge-paste', 'bi bi-clipboard-plus', function() {
|
|
254
|
+
paste(node, categories);
|
|
255
|
+
});
|
|
256
|
+
drawer.children('.ge-paste').last()
|
|
257
|
+
.attr('data-ge-paste', categories.join(' '))
|
|
258
|
+
.toggle(fits(read(), categories));
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
toolbar: TOOLBAR,
|
|
262
|
+
|
|
263
|
+
onInit: function() {
|
|
264
|
+
refresh();
|
|
265
|
+
$(document).off(CHANGE + namespace).on(CHANGE + namespace, refresh);
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
onDeinit: function() {
|
|
269
|
+
$(document).off(CHANGE + namespace);
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
})(jQuery);
|
|
@@ -124,15 +124,15 @@ $.fn.gridEditor.features.sections = function(ge) {
|
|
|
124
124
|
ge.createMoveTool(drawer);
|
|
125
125
|
ge.addSettingsTool(drawer, section, ge.settings.section_classes || []).append(widthField(section));
|
|
126
126
|
|
|
127
|
-
ge.createTool(drawer, ge.t('tool.add_row_to_section'), 'ge-add-row', 'bi bi-plus-circle', function() {
|
|
128
|
-
ge.place(ge.rowFromLayout([12]), 'row', { appendTo: section });
|
|
129
|
-
});
|
|
130
|
-
|
|
131
127
|
ge.createTool(drawer, ge.t('tool.delete_section'), 'ge-delete-section', 'bi bi-trash', function() {
|
|
132
128
|
ge.deleteNode('section', section, ge.t('confirm.delete_section'), function(removed) {
|
|
133
129
|
section.slideUp(removed);
|
|
134
130
|
});
|
|
135
131
|
});
|
|
132
|
+
|
|
133
|
+
ge.createTool(drawer, ge.t('tool.add_row_to_section'), 'ge-add-row', 'bi bi-plus-circle', function() {
|
|
134
|
+
ge.place(ge.rowFromLayout([12]), 'row', { appendTo: section });
|
|
135
|
+
});
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
function mark() {
|
package/src/less/grideditor.less
CHANGED
|
@@ -347,6 +347,12 @@
|
|
|
347
347
|
position: relative;
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
/* The copy tool says it copied, for a moment, with the clipboard
|
|
351
|
+
plugin */
|
|
352
|
+
.ge-tools-drawer > a.ge-copied {
|
|
353
|
+
color: #198754;
|
|
354
|
+
}
|
|
355
|
+
|
|
350
356
|
.ge-size-picker {
|
|
351
357
|
position: absolute;
|
|
352
358
|
top: 100%;
|