ivaldi-content-builder 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +33 -0
- data/app/assets/javascripts/html.sortable.min.js +349 -0
- data/app/assets/javascripts/ivaldi_content_builder.js +23 -0
- data/app/controllers/pages_controller.rb +52 -0
- data/app/models/content_block.rb +14 -0
- data/app/models/page.rb +9 -0
- data/app/views/ivaldi_content_blocks/_field.html.erb +1 -0
- data/app/views/pages/_content_block_fields.html.erb +61 -0
- data/app/views/pages/_form.html.erb +43 -0
- data/app/views/pages/edit.html.erb +21 -0
- data/app/views/pages/index.html.erb +34 -0
- data/app/views/pages/new.html.erb +21 -0
- data/app/views/pages/new.js.erb +15 -0
- data/db/migrate/create_ivaldi_content_blocks.rb +16 -0
- data/lib/generators/icb/eject_generator.rb +9 -0
- data/lib/generators/icb/install_generator.rb +12 -0
- data/lib/generators/icb/new_block_generator.rb +41 -0
- data/lib/ivaldi_content_builder.rb +3 -0
- data/lib/ivaldi_content_builder/engine.rb +9 -0
- data/lib/tasks/ivaldi_content_builder_tasks.rake +4 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 567ce814b24744949448b8b0f6606b103ef35705
|
4
|
+
data.tar.gz: 6fb3b921848f31f5712e5780d6e6deff784361f0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dbb75e538dbb9a7c322185e4f08564d88c088a7edc80e681bf1700f540ca7db6feaff5378c5a270db776e08b818ef45ce51fc110d05bcc0c38f1da6292654636
|
7
|
+
data.tar.gz: f382254a25d8c16f9c60b9adbb8fc259cfee8fd59d5d9e75b273c13aa756cdc7f9cb286a6874fa903a28da0165ccbfea29ad73ea8aeefe704298f9b8b7d6dec9
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2017 Bas Schoenmakers
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# IvaldiContentBuilder
|
2
|
+
Short description and motivation.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
How to use my plugin.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'ivaldi_content_builder'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```bash
|
21
|
+
$ gem install ivaldi_content_builder
|
22
|
+
```
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
Contribution directions go here.
|
26
|
+
|
27
|
+
## License
|
28
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'IvaldiContentBuilder'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'test'
|
28
|
+
t.pattern = 'test/**/*_test.rb'
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
task default: :test
|
@@ -0,0 +1,349 @@
|
|
1
|
+
!(function(e, t) {
|
2
|
+
"function" == typeof define && define.amd
|
3
|
+
? define([], t)
|
4
|
+
: "object" == typeof exports ? (module.exports = t()) : (e.sortable = t());
|
5
|
+
})(this, function() {
|
6
|
+
"use strict";
|
7
|
+
function e(e, t, n) {
|
8
|
+
var r = null;
|
9
|
+
return 0 === t
|
10
|
+
? e
|
11
|
+
: function() {
|
12
|
+
var a = n || this,
|
13
|
+
o = arguments;
|
14
|
+
clearTimeout(r), (r = setTimeout(function() {
|
15
|
+
e.apply(a, o);
|
16
|
+
}, t));
|
17
|
+
};
|
18
|
+
}
|
19
|
+
var t,
|
20
|
+
n,
|
21
|
+
r = [],
|
22
|
+
a = [],
|
23
|
+
o = function(e, t, n) {
|
24
|
+
return void 0 === n
|
25
|
+
? e && e.h5s && e.h5s.data && e.h5s.data[t]
|
26
|
+
: (
|
27
|
+
(e.h5s = e.h5s || {}),
|
28
|
+
(e.h5s.data = e.h5s.data || {}),
|
29
|
+
(e.h5s.data[t] = n),
|
30
|
+
void 0
|
31
|
+
);
|
32
|
+
},
|
33
|
+
i = function(e) {
|
34
|
+
e.h5s && delete e.h5s.data;
|
35
|
+
},
|
36
|
+
s = function(e, t) {
|
37
|
+
return (e.matches ||
|
38
|
+
e.matchesSelector ||
|
39
|
+
e.msMatchesSelector ||
|
40
|
+
e.mozMatchesSelector ||
|
41
|
+
e.webkitMatchesSelector ||
|
42
|
+
e.oMatchesSelector)
|
43
|
+
.call(e, t);
|
44
|
+
},
|
45
|
+
l = function(e, t) {
|
46
|
+
if (!t) return Array.prototype.slice.call(e);
|
47
|
+
for (var n = [], r = 0; r < e.length; ++r)
|
48
|
+
"string" == typeof t && s(e[r], t) && n.push(e[r]), t.indexOf(e[r]) !==
|
49
|
+
-1 && n.push(e[r]);
|
50
|
+
return n;
|
51
|
+
},
|
52
|
+
d = function(e, t, n) {
|
53
|
+
if (e instanceof Array) for (var r = 0; r < e.length; ++r) d(e[r], t, n);
|
54
|
+
else
|
55
|
+
e.addEventListener(t, n), (e.h5s = e.h5s || {}), (e.h5s.events =
|
56
|
+
e.h5s.events || {}), (e.h5s.events[t] = n);
|
57
|
+
},
|
58
|
+
c = function(e, t) {
|
59
|
+
if (e instanceof Array) for (var n = 0; n < e.length; ++n) c(e[n], t);
|
60
|
+
else
|
61
|
+
e.h5s &&
|
62
|
+
e.h5s.events &&
|
63
|
+
e.h5s.events[t] &&
|
64
|
+
(e.removeEventListener(t, e.h5s.events[t]), delete e.h5s.events[t]);
|
65
|
+
},
|
66
|
+
f = function(e, t, n) {
|
67
|
+
if (e instanceof Array) for (var r = 0; r < e.length; ++r) f(e[r], t, n);
|
68
|
+
else e.setAttribute(t, n);
|
69
|
+
},
|
70
|
+
u = function(e, t) {
|
71
|
+
if (e instanceof Array) for (var n = 0; n < e.length; ++n) u(e[n], t);
|
72
|
+
else e.removeAttribute(t);
|
73
|
+
},
|
74
|
+
p = function(e) {
|
75
|
+
var t = e.getClientRects()[0];
|
76
|
+
return { left: t.left + window.scrollX, top: t.top + window.scrollY };
|
77
|
+
},
|
78
|
+
h = function(e) {
|
79
|
+
c(e, "dragstart"), c(e, "dragend"), c(e, "selectstart"), c(
|
80
|
+
e,
|
81
|
+
"dragover"
|
82
|
+
), c(e, "dragenter"), c(e, "drop");
|
83
|
+
},
|
84
|
+
g = function(e) {
|
85
|
+
c(e, "dragover"), c(e, "dragenter"), c(e, "drop");
|
86
|
+
},
|
87
|
+
m = function(e, t) {
|
88
|
+
(e.dataTransfer.effectAllowed = "move"), e.dataTransfer.setData(
|
89
|
+
"text",
|
90
|
+
""
|
91
|
+
), e.dataTransfer.setDragImage &&
|
92
|
+
e.dataTransfer.setDragImage(t.draggedItem, t.x, t.y);
|
93
|
+
},
|
94
|
+
v = function(e, t) {
|
95
|
+
return t.x || (t.x = parseInt(e.pageX - p(t.draggedItem).left)), t.y ||
|
96
|
+
(t.y = parseInt(e.pageY - p(t.draggedItem).top)), t;
|
97
|
+
},
|
98
|
+
y = function(e) {
|
99
|
+
return { draggedItem: e };
|
100
|
+
},
|
101
|
+
b = function(e, t) {
|
102
|
+
var n = y(t);
|
103
|
+
(n = v(e, n)), m(e, n);
|
104
|
+
},
|
105
|
+
E = function(e) {
|
106
|
+
i(e), u(e, "aria-dropeffect");
|
107
|
+
},
|
108
|
+
x = function(e) {
|
109
|
+
u(e, "aria-grabbed"), u(e, "draggable"), u(e, "role");
|
110
|
+
},
|
111
|
+
w = function(e, t) {
|
112
|
+
return (
|
113
|
+
e === t ||
|
114
|
+
(void 0 !== o(e, "connectWith") &&
|
115
|
+
o(e, "connectWith") === o(t, "connectWith"))
|
116
|
+
);
|
117
|
+
},
|
118
|
+
C = function(e, t) {
|
119
|
+
var n,
|
120
|
+
r = [];
|
121
|
+
if (!t) return e;
|
122
|
+
for (var a = 0; a < e.length; ++a)
|
123
|
+
(n = e[a].querySelectorAll(t)), (r = r.concat(
|
124
|
+
Array.prototype.slice.call(n)
|
125
|
+
));
|
126
|
+
return r;
|
127
|
+
},
|
128
|
+
A = function(e) {
|
129
|
+
var t = o(e, "opts") || {},
|
130
|
+
n = l(q(e), t.items),
|
131
|
+
r = C(n, t.handle);
|
132
|
+
g(e), E(e), c(r, "mousedown"), h(n), x(n);
|
133
|
+
},
|
134
|
+
I = function(e) {
|
135
|
+
var t = o(e, "opts"),
|
136
|
+
n = l(q(e), t.items),
|
137
|
+
r = C(n, t.handle);
|
138
|
+
f(e, "aria-dropeffect", "move"), f(r, "draggable", "true");
|
139
|
+
var a = (document || window.document).createElement("span");
|
140
|
+
"function" != typeof a.dragDrop ||
|
141
|
+
t.disableIEFix ||
|
142
|
+
d(r, "mousedown", function() {
|
143
|
+
if (n.indexOf(this) !== -1) this.dragDrop();
|
144
|
+
else {
|
145
|
+
for (var e = this.parentElement; n.indexOf(e) === -1; )
|
146
|
+
e = e.parentElement;
|
147
|
+
e.dragDrop();
|
148
|
+
}
|
149
|
+
});
|
150
|
+
},
|
151
|
+
D = function(e) {
|
152
|
+
var t = o(e, "opts"),
|
153
|
+
n = l(q(e), t.items),
|
154
|
+
r = C(n, t.handle);
|
155
|
+
f(e, "aria-dropeffect", "none"), f(r, "draggable", "false"), c(
|
156
|
+
r,
|
157
|
+
"mousedown"
|
158
|
+
);
|
159
|
+
},
|
160
|
+
S = function(e) {
|
161
|
+
var t = o(e, "opts"),
|
162
|
+
n = l(q(e), t.items),
|
163
|
+
r = C(n, t.handle);
|
164
|
+
h(n), c(r, "mousedown"), g(e);
|
165
|
+
},
|
166
|
+
L = function(e) {
|
167
|
+
return e.parentElement
|
168
|
+
? Array.prototype.indexOf.call(e.parentElement.children, e)
|
169
|
+
: 0;
|
170
|
+
},
|
171
|
+
O = function(e) {
|
172
|
+
return !!e.parentNode;
|
173
|
+
},
|
174
|
+
T = function(e, t) {
|
175
|
+
if ("string" != typeof e) return e;
|
176
|
+
var n = document.createElement(t);
|
177
|
+
return (n.innerHTML = e), n.firstChild;
|
178
|
+
},
|
179
|
+
W = function(e, t) {
|
180
|
+
e.parentElement.insertBefore(t, e);
|
181
|
+
},
|
182
|
+
M = function(e, t) {
|
183
|
+
e.parentElement.insertBefore(t, e.nextElementSibling);
|
184
|
+
},
|
185
|
+
N = function(e) {
|
186
|
+
e.parentNode && e.parentNode.removeChild(e);
|
187
|
+
},
|
188
|
+
P = function(e, t) {
|
189
|
+
var n = document.createEvent("Event");
|
190
|
+
return t && (n.detail = t), n.initEvent(e, !1, !0), n;
|
191
|
+
},
|
192
|
+
Y = function(e, t) {
|
193
|
+
a.forEach(function(n) {
|
194
|
+
w(e, n) && n.dispatchEvent(t);
|
195
|
+
});
|
196
|
+
},
|
197
|
+
q = function(e) {
|
198
|
+
return e.children;
|
199
|
+
},
|
200
|
+
z = function(i, c) {
|
201
|
+
var u = String(c);
|
202
|
+
return (c = (function(e) {
|
203
|
+
var t = {
|
204
|
+
connectWith: !1,
|
205
|
+
placeholder: null,
|
206
|
+
disableIEFix: !1,
|
207
|
+
placeholderClass: "sortable-placeholder",
|
208
|
+
draggingClass: "sortable-dragging",
|
209
|
+
hoverClass: !1,
|
210
|
+
debounce: 0
|
211
|
+
};
|
212
|
+
for (var n in e) t[n] = e[n];
|
213
|
+
return t;
|
214
|
+
})(c)), c &&
|
215
|
+
"function" == typeof c.getChildren &&
|
216
|
+
(q = c.getChildren), "string" == typeof i &&
|
217
|
+
(i = document.querySelectorAll(i)), i instanceof window.Element &&
|
218
|
+
(i = [i]), (i = Array.prototype.slice.call(i)), i.forEach(function(i) {
|
219
|
+
if (/enable|disable|destroy/.test(u)) return void z[u](i);
|
220
|
+
(c = o(i, "opts") || c), o(i, "opts", c), S(i);
|
221
|
+
var h,
|
222
|
+
g,
|
223
|
+
m = l(q(i), c.items),
|
224
|
+
v = c.placeholder;
|
225
|
+
if (
|
226
|
+
(
|
227
|
+
v ||
|
228
|
+
(v = document.createElement(
|
229
|
+
/^ul|ol$/i.test(i.tagName) ? "li" : "div"
|
230
|
+
)),
|
231
|
+
(v = T(v, i.tagName)),
|
232
|
+
v.classList.add.apply(v.classList, c.placeholderClass.split(" ")),
|
233
|
+
!i.getAttribute("data-sortable-id")
|
234
|
+
)
|
235
|
+
) {
|
236
|
+
var y = a.length;
|
237
|
+
(a[y] = i), f(i, "data-sortable-id", y), f(
|
238
|
+
m,
|
239
|
+
"data-item-sortable-id",
|
240
|
+
y
|
241
|
+
);
|
242
|
+
}
|
243
|
+
if (
|
244
|
+
(
|
245
|
+
o(i, "items", c.items),
|
246
|
+
r.push(v),
|
247
|
+
c.connectWith && o(i, "connectWith", c.connectWith),
|
248
|
+
I(i),
|
249
|
+
f(m, "role", "option"),
|
250
|
+
f(m, "aria-grabbed", "false"),
|
251
|
+
c.hoverClass
|
252
|
+
)
|
253
|
+
) {
|
254
|
+
var E = "sortable-over";
|
255
|
+
"string" == typeof c.hoverClass &&
|
256
|
+
(E = c.hoverClass), d(m, "mouseenter", function() {
|
257
|
+
this.classList.add(E);
|
258
|
+
}), d(m, "mouseleave", function() {
|
259
|
+
this.classList.remove(E);
|
260
|
+
});
|
261
|
+
}
|
262
|
+
d(m, "dragstart", function(e) {
|
263
|
+
e.stopImmediatePropagation(), (c.handle && !s(e.target, c.handle)) || "false" === this.getAttribute("draggable") || (b(e, this), this.classList.add(c.draggingClass), (t = this), f(t, "aria-grabbed", "true"), (h = L(t)), (n = parseInt(window.getComputedStyle(t).height)), (g = this.parentElement), Y(i, P("sortstart", { item: t, placeholder: v, startparent: g })));
|
264
|
+
}), d(m, "dragend", function() {
|
265
|
+
var e;
|
266
|
+
t &&
|
267
|
+
(
|
268
|
+
t.classList.remove(c.draggingClass),
|
269
|
+
f(t, "aria-grabbed", "false"),
|
270
|
+
(t.style.display = t.oldDisplay),
|
271
|
+
delete t.oldDisplay,
|
272
|
+
r.forEach(N),
|
273
|
+
(e = this.parentElement),
|
274
|
+
Y(i, P("sortstop", { item: t, startparent: g })),
|
275
|
+
(h === L(t) && g === e) ||
|
276
|
+
Y(
|
277
|
+
i,
|
278
|
+
P("sortupdate", {
|
279
|
+
item: t,
|
280
|
+
index: l(q(e), o(e, "items")).indexOf(t),
|
281
|
+
oldindex: m.indexOf(t),
|
282
|
+
elementIndex: L(t),
|
283
|
+
oldElementIndex: h,
|
284
|
+
startparent: g,
|
285
|
+
endparent: e
|
286
|
+
})
|
287
|
+
),
|
288
|
+
(t = null),
|
289
|
+
(n = null)
|
290
|
+
);
|
291
|
+
}), d([i, v], "drop", function(e) {
|
292
|
+
var n;
|
293
|
+
w(i, t.parentElement) &&
|
294
|
+
(
|
295
|
+
e.preventDefault(),
|
296
|
+
e.stopPropagation(),
|
297
|
+
(n = r.filter(O)[0]),
|
298
|
+
M(n, t),
|
299
|
+
t.dispatchEvent(P("dragend"))
|
300
|
+
);
|
301
|
+
});
|
302
|
+
var x = e(function(e, a) {
|
303
|
+
if (t)
|
304
|
+
if (m.indexOf(e) !== -1) {
|
305
|
+
var o = parseInt(window.getComputedStyle(e).height),
|
306
|
+
i = L(v),
|
307
|
+
s = L(e);
|
308
|
+
if (
|
309
|
+
(c.forcePlaceholderSize && (v.style.height = n + "px"), o > n)
|
310
|
+
) {
|
311
|
+
var d = o - n,
|
312
|
+
f = p(e).top;
|
313
|
+
if (i < s && a < f + d) return;
|
314
|
+
if (i > s && a > f + o - d) return;
|
315
|
+
}
|
316
|
+
void 0 === t.oldDisplay &&
|
317
|
+
(t.oldDisplay = t.style.display), (t.style.display =
|
318
|
+
"none"), i < s ? M(e, v) : W(e, v), r
|
319
|
+
.filter(function(e) {
|
320
|
+
return e !== v;
|
321
|
+
})
|
322
|
+
.forEach(N);
|
323
|
+
} else
|
324
|
+
r.indexOf(e) !== -1 ||
|
325
|
+
l(q(e), c.items).length ||
|
326
|
+
(r.forEach(N), e.appendChild(v));
|
327
|
+
}, c.debounce),
|
328
|
+
C = function(e) {
|
329
|
+
t &&
|
330
|
+
w(i, t.parentElement) &&
|
331
|
+
(
|
332
|
+
e.preventDefault(),
|
333
|
+
e.stopPropagation(),
|
334
|
+
(e.dataTransfer.dropEffect = "move"),
|
335
|
+
x(this, e.pageY)
|
336
|
+
);
|
337
|
+
};
|
338
|
+
d(m.concat(i), "dragover", C), d(m.concat(i), "dragenter", C);
|
339
|
+
}), i;
|
340
|
+
};
|
341
|
+
return (z.destroy = function(e) {
|
342
|
+
A(e);
|
343
|
+
}), (z.enable = function(e) {
|
344
|
+
I(e);
|
345
|
+
}), (z.disable = function(e) {
|
346
|
+
D(e);
|
347
|
+
}), z;
|
348
|
+
});
|
349
|
+
//# sourceMappingURL=html.sortable.min.js.map
|
@@ -0,0 +1,23 @@
|
|
1
|
+
//= require jquery
|
2
|
+
//= require jquery_ujs
|
3
|
+
//= require cocoon
|
4
|
+
//= require html.sortable.min
|
5
|
+
|
6
|
+
(function($) {
|
7
|
+
function setCorrectOrder() {
|
8
|
+
$("[data-ivaldi-content-blocks] .nested-fields").each(function(index) {
|
9
|
+
$(this).find("[data-sequence]").val(index);
|
10
|
+
});
|
11
|
+
}
|
12
|
+
|
13
|
+
$(document).on("ready page:load turbolinks:load", function() {
|
14
|
+
if ($("[data-ivaldi-content-blocks]").length > 0) {
|
15
|
+
sortable("[data-ivaldi-content-blocks]", {
|
16
|
+
placeholderClass: "sortable-placeholder",
|
17
|
+
forcePlaceholderSize: true
|
18
|
+
})[0].addEventListener("sortupdate", function(e) {
|
19
|
+
setCorrectOrder();
|
20
|
+
});
|
21
|
+
}
|
22
|
+
});
|
23
|
+
})(jQuery);
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class PagesController < ApplicationController
|
2
|
+
|
3
|
+
before_action :pages_append_view_path
|
4
|
+
|
5
|
+
def index
|
6
|
+
@pages = Page.ordered
|
7
|
+
end
|
8
|
+
|
9
|
+
def new
|
10
|
+
@page = Page.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def edit
|
14
|
+
@page = Page.find params[:id]
|
15
|
+
end
|
16
|
+
|
17
|
+
def create
|
18
|
+
@page = Page.new page_params
|
19
|
+
if @page.save
|
20
|
+
#TODO: set flash message?
|
21
|
+
redirect_to edit_page_url(@page.id)
|
22
|
+
else
|
23
|
+
render 'new'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def update
|
28
|
+
@page = Page.find params[:id]
|
29
|
+
if @page.update(page_params)
|
30
|
+
#TODO: set flash message?
|
31
|
+
redirect_to edit_page_url(@page.id)
|
32
|
+
else
|
33
|
+
render 'edit'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def destroy
|
38
|
+
@page.destroy
|
39
|
+
redirect_to admin_pages_url
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def pages_append_view_path
|
45
|
+
append_view_path IvaldiContentBuilder::Engine.root.join('app', 'views')
|
46
|
+
end
|
47
|
+
|
48
|
+
def page_params
|
49
|
+
ret = params.require(:page).permit!
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class ContentBlock < ApplicationRecord
|
2
|
+
belongs_to :page
|
3
|
+
belongs_to :blockable, polymorphic: true, dependent: :destroy
|
4
|
+
|
5
|
+
accepts_nested_attributes_for :blockable, reject_if: :all_blank, allow_destroy: true
|
6
|
+
validates :blockable_type, presence: true
|
7
|
+
|
8
|
+
scope :ordered, -> { order(:sequence) }
|
9
|
+
|
10
|
+
def build_blockable(params)
|
11
|
+
self.blockable = blockable_type.constantize.new(params)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
data/app/models/page.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
<% #add fields_for for this type %>
|
@@ -0,0 +1,61 @@
|
|
1
|
+
<div class="nested-fields">
|
2
|
+
<div class="panel panel-default">
|
3
|
+
<% if f.object.blockable_type.present? %>
|
4
|
+
<div class="panel-heading u-cursor-move">
|
5
|
+
<%= link_to_remove_association f, class: 'pull-right', title: 'Verwijder' do %>
|
6
|
+
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
7
|
+
<% end %>
|
8
|
+
<h3 class="panel-title">
|
9
|
+
<%= t("ivaldi_content_blocks.#{f.object.blockable_type}") %>
|
10
|
+
</h3>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
13
|
+
<div class="panel-body">
|
14
|
+
<%= f.hidden_field :sequence, data: { sequence: true } %>
|
15
|
+
<div data-blockable-type>
|
16
|
+
<% if f.object.blockable_type.nil? %>
|
17
|
+
|
18
|
+
<% items = [] %>
|
19
|
+
<% if t('ivaldi_content_blocks').is_a? Hash %>
|
20
|
+
<% items = t('ivaldi_content_blocks').to_a %>
|
21
|
+
<% end %>
|
22
|
+
|
23
|
+
<%= f.collection_select :blockable_type,
|
24
|
+
items,
|
25
|
+
:first,
|
26
|
+
:last,
|
27
|
+
{
|
28
|
+
include_blank: 'Select type',
|
29
|
+
},
|
30
|
+
data: {
|
31
|
+
remote: true,
|
32
|
+
url: new_page_url
|
33
|
+
}
|
34
|
+
%>
|
35
|
+
<% end %>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
<% t('ivaldi_content_blocks').keys.each do |blockable_type| %>
|
39
|
+
|
40
|
+
<% blockable = blockable_type.to_s.constantize.new %>
|
41
|
+
|
42
|
+
<% if f.object.blockable_type == blockable_type.to_s %>
|
43
|
+
<% blockable = f.object.blockable %>
|
44
|
+
<% elsif f.object.blockable_type.present? %>
|
45
|
+
<% next %>
|
46
|
+
<% end %>
|
47
|
+
|
48
|
+
<%= f.fields_for :blockable, blockable do |ff| %>
|
49
|
+
<div style="display: <%= f.object.blockable_type.nil? ? 'none' : 'block' %>;" data-content-block>
|
50
|
+
<%= render "ivaldi_content_blocks/#{blockable_type.to_s.underscore}_fields", f: ff %>
|
51
|
+
<% unless f.object.blockable_type.blank? %>
|
52
|
+
<%= f.hidden_field :blockable_type, value: f.object.blockable_type %>
|
53
|
+
<% end %>
|
54
|
+
<%= ff.hidden_field :_destroy, value: ff.object.new_record? ? 1 : 0, data: { 'field-type' => blockable_type } %>
|
55
|
+
</div>
|
56
|
+
<% end %>
|
57
|
+
|
58
|
+
<% end %>
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
</div>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<%= form_for @page do |f| %>
|
2
|
+
<div class="row">
|
3
|
+
<div class="col-lg-9">
|
4
|
+
|
5
|
+
<%= f.label :title %>
|
6
|
+
<%= f.text_field :title %>
|
7
|
+
|
8
|
+
<% unless @page.new_record? %>
|
9
|
+
<div class="form-group">
|
10
|
+
<div class="col-sm-12">
|
11
|
+
<div class="input-group">
|
12
|
+
<span class="input-group-addon">https://example.com/</span>
|
13
|
+
<%= f.label :slug %>
|
14
|
+
<%= f.text_field :slug %>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
<% end %>
|
19
|
+
|
20
|
+
<hr>
|
21
|
+
|
22
|
+
<div data-ivaldi-content-blocks>
|
23
|
+
<%= f.fields_for :content_blocks, @page.content_blocks.sort_by{ |cb| cb.sequence } do |content_block| %>
|
24
|
+
<div data-content-block-wrapper>
|
25
|
+
<%= render 'content_block_fields', f: content_block %>
|
26
|
+
</div>
|
27
|
+
<% end %>
|
28
|
+
</div>
|
29
|
+
|
30
|
+
<div class="well text-center">
|
31
|
+
<%= link_to_add_association 'Add block', f,
|
32
|
+
:content_blocks,
|
33
|
+
'data-association-insertion-node' => '[data-ivaldi-content-blocks]',
|
34
|
+
'data-association-insertion-method' => 'append'
|
35
|
+
%>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
<%= f.submit %>
|
39
|
+
|
40
|
+
</div>
|
41
|
+
|
42
|
+
</div>
|
43
|
+
<% end %>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<nav class="navbar navbar-static-top navbar-default">
|
2
|
+
<div class="container-fluid">
|
3
|
+
<div class="navbar-header">
|
4
|
+
<a href="<%= pages_path %>" class="navbar-brand">
|
5
|
+
← Back
|
6
|
+
</a>
|
7
|
+
</span>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
</nav>
|
11
|
+
|
12
|
+
<div class="container-fluid">
|
13
|
+
<div class="panel panel-default">
|
14
|
+
<div class="panel-heading">
|
15
|
+
<h3 class="panel-title">Edit page</h3>
|
16
|
+
</div>
|
17
|
+
<div class="panel-body">
|
18
|
+
<%= render 'form' %>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
</div>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<nav class="navbar navbar-static-top navbar-default">
|
2
|
+
<div class="container-fluid">
|
3
|
+
<div class="navbar-header">
|
4
|
+
<span class="navbar-brand">
|
5
|
+
<%= @pages.count %> pages
|
6
|
+
</span>
|
7
|
+
</div>
|
8
|
+
<div class="collapse navbar-collapse">
|
9
|
+
<ul class="nav navbar-nav">
|
10
|
+
<li>
|
11
|
+
<a href="<%= new_page_path %>">
|
12
|
+
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Nieuw
|
13
|
+
</a>
|
14
|
+
</li>
|
15
|
+
</ul>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
</nav>
|
19
|
+
|
20
|
+
<div class="container-fluid">
|
21
|
+
<table class="table table-hover">
|
22
|
+
<thead>
|
23
|
+
<tr>
|
24
|
+
<th>Titel</th>
|
25
|
+
</tr>
|
26
|
+
</thead>
|
27
|
+
<tbody>
|
28
|
+
<% @pages.each do |page| %>
|
29
|
+
<tr>
|
30
|
+
<td><a href="<%= edit_page_path(page.id) %>"><%= page.title %></a></td>
|
31
|
+
</tr>
|
32
|
+
<% end %>
|
33
|
+
</table>
|
34
|
+
</div>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<nav class="navbar navbar-static-top navbar-default">
|
2
|
+
<div class="container-fluid">
|
3
|
+
<div class="navbar-header">
|
4
|
+
<a href="<%= pages_path %>" class="navbar-brand">
|
5
|
+
← Back
|
6
|
+
</a>
|
7
|
+
</span>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
</nav>
|
11
|
+
|
12
|
+
<div class="container-fluid">
|
13
|
+
<div class="panel panel-default">
|
14
|
+
<div class="panel-heading">
|
15
|
+
<h3 class="panel-title">New page</h3>
|
16
|
+
</div>
|
17
|
+
<div class="panel-body">
|
18
|
+
<%= render 'form' %>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
</div>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<% blockable_id = params[:page][:content_blocks_attributes].keys.first %>
|
2
|
+
|
3
|
+
var content_block_id = <%= blockable_id %>;
|
4
|
+
var blockable_type = '<%= params[:page][:content_blocks_attributes][blockable_id]["blockable_type"] %>';
|
5
|
+
|
6
|
+
var destroy_input = jQuery('[name="page[content_blocks_attributes][' + content_block_id + '][blockable_attributes][_destroy]"][data-field-type="' + blockable_type + '"]');
|
7
|
+
var fieldset = destroy_input.closest('[data-ivaldi-content-blocks]');
|
8
|
+
|
9
|
+
destroy_input.val(0);
|
10
|
+
destroy_input.closest('[data-content-block]').show();
|
11
|
+
|
12
|
+
fieldset.find('[data-blockable-type]').hide();
|
13
|
+
fieldset.find('[data-content-block]:hidden').remove();
|
14
|
+
|
15
|
+
setCorrectOrder();
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateIvaldiContentBlocks < ActiveRecord::Migration[5.1]
|
2
|
+
def change
|
3
|
+
create_table :pages do |t|
|
4
|
+
t.string :title
|
5
|
+
t.string :slug
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table :content_blocks do |t|
|
10
|
+
t.references :page, foreign_key: true
|
11
|
+
t.references :blockable, polymorphic: true
|
12
|
+
t.integer :sequence, default: 0, null: false
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Icb
|
2
|
+
class InstallGenerator < Rails::Generators::Base
|
3
|
+
|
4
|
+
def generate_migrations
|
5
|
+
timestamp = I18n.l Time.zone.now, format: '%Y%m%d%H%M%S'
|
6
|
+
create_file "db/migrate/#{timestamp}_create_ivaldi_content_blocks.rb",
|
7
|
+
File.read('../../db/migrate/create_ivaldi_content_blocks.rb')
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Icb
|
2
|
+
class NewBlockGenerator < Rails::Generators::Base
|
3
|
+
|
4
|
+
argument :attributes, type: :array
|
5
|
+
|
6
|
+
def call
|
7
|
+
@model_name = attributes.shift
|
8
|
+
|
9
|
+
create_migration
|
10
|
+
create_form_fields
|
11
|
+
add_field_to_translation
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def create_migration
|
17
|
+
generate "model #{@model_name}", attributes.join(' ')
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_form_fields
|
21
|
+
file_name = "app/views/ivaldi_content_blocks/_#{@model_name.underscore}_fields.html.erb"
|
22
|
+
create_file file_name, File.read('../../app/views/ivaldi_content_blocks/_field.html.erb')
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_field_to_translation
|
26
|
+
|
27
|
+
file = File.open("./config/locales/#{I18n.locale}.yml")
|
28
|
+
|
29
|
+
translations = YAML.load_file(file)
|
30
|
+
|
31
|
+
if translations[I18n.locale.to_s][:ivaldi_content_blocks].nil?
|
32
|
+
translations[I18n.locale.to_s].merge!({ "ivaldi_content_blocks" => {} })
|
33
|
+
end
|
34
|
+
|
35
|
+
translations[I18n.locale.to_s]["ivaldi_content_blocks"].merge!(Hash[@model_name, @model_name])
|
36
|
+
|
37
|
+
File.open(file, 'w') {|f| f.write translations.to_yaml }
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ivaldi-content-builder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bas Schoenmakers
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.1'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 5.1.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '5.1'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 5.1.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: cocoon
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.2'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.2'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: jquery-rails
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '4.3'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '4.3'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: sqlite3
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '1.3'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.3'
|
75
|
+
description: Create contentblocks and pages for admin environments.
|
76
|
+
email:
|
77
|
+
- bas@ivaldi.nl
|
78
|
+
executables: []
|
79
|
+
extensions: []
|
80
|
+
extra_rdoc_files: []
|
81
|
+
files:
|
82
|
+
- MIT-LICENSE
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- app/assets/javascripts/html.sortable.min.js
|
86
|
+
- app/assets/javascripts/ivaldi_content_builder.js
|
87
|
+
- app/controllers/pages_controller.rb
|
88
|
+
- app/models/content_block.rb
|
89
|
+
- app/models/page.rb
|
90
|
+
- app/views/ivaldi_content_blocks/_field.html.erb
|
91
|
+
- app/views/pages/_content_block_fields.html.erb
|
92
|
+
- app/views/pages/_form.html.erb
|
93
|
+
- app/views/pages/edit.html.erb
|
94
|
+
- app/views/pages/index.html.erb
|
95
|
+
- app/views/pages/new.html.erb
|
96
|
+
- app/views/pages/new.js.erb
|
97
|
+
- db/migrate/create_ivaldi_content_blocks.rb
|
98
|
+
- lib/generators/icb/eject_generator.rb
|
99
|
+
- lib/generators/icb/install_generator.rb
|
100
|
+
- lib/generators/icb/new_block_generator.rb
|
101
|
+
- lib/ivaldi_content_builder.rb
|
102
|
+
- lib/ivaldi_content_builder/engine.rb
|
103
|
+
- lib/tasks/ivaldi_content_builder_tasks.rake
|
104
|
+
homepage: https://ivaldi.nl
|
105
|
+
licenses:
|
106
|
+
- MIT
|
107
|
+
metadata: {}
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 2.6.11
|
125
|
+
signing_key:
|
126
|
+
specification_version: 4
|
127
|
+
summary: Create contentblocks and pages.
|
128
|
+
test_files: []
|