robin_cms 0.1.1 → 0.1.3

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.
@@ -1,124 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RobinCMS
4
- module Itemable
5
- attr_reader :id, :library, :attributes
6
-
7
- DATETIME_FORMAT = "%Y-%m-%d"
8
-
9
- # The keys which we don't want to serialize.
10
- SERIALIZE_IGNORE_KEYS = %i[id kind content image captures].freeze
11
-
12
- def initialize(id, library, attrs = {})
13
- %i[id location filetype].each do |key|
14
- unless library.has_key?(key)
15
- raise TypeError, "Missing required field #{key}"
16
- end
17
- end
18
-
19
- if !attrs.empty? && !attrs.has_key?(:title)
20
- raise TypeError, "Missing required field `title'"
21
- end
22
-
23
- @id = id
24
- @library = library
25
-
26
- # Be sure to use the setter here so the keys get converted to symbols.
27
- self.attributes = attrs
28
- end
29
-
30
- def attributes=(attributes)
31
- @attributes = attributes.to_h.transform_keys(&:to_sym)
32
-
33
- if attributes.has_key?(:published)
34
- @attributes[:published] = attributes[:published].to_s == "true"
35
- end
36
- end
37
-
38
- def kind
39
- @library[:id]
40
- end
41
-
42
- def inspect
43
- "<#{self.class} id=\"#{id}\" kind=\"#{kind}\">"
44
- end
45
-
46
- def published?
47
- if @attributes.has_key?(:published)
48
- @attributes[:published]
49
- else
50
- true
51
- end
52
- end
53
-
54
- def published_label
55
- if published?
56
- "Published"
57
- else
58
- "Draft"
59
- end
60
- end
61
-
62
- def created_at
63
- if @attributes[:created_at]
64
- Time.parse(@attributes[:created_at])
65
- else
66
- File.birthtime(filepath)
67
- end
68
- end
69
-
70
- def updated_at
71
- if @attributes[:updated_at]
72
- Time.parse(@attributes[:updated_at])
73
- else
74
- File.mtime(filepath)
75
- end
76
- end
77
-
78
- def display_name
79
- return @attributes[:title] unless @library[:display_name_pattern]
80
-
81
- @library[:display_name_pattern].clone.tap do |name|
82
- @attributes.each { |key, value| name.gsub!(":#{key}", value) }
83
- end
84
- end
85
-
86
- def save!
87
- timestamp = Time.now.strftime(DATETIME_FORMAT)
88
- @attributes[:created_at] = timestamp
89
- @attributes[:updated_at] = timestamp
90
-
91
- FileUtils.mkdir_p(File.dirname(filepath))
92
- File.write(filepath, serialize)
93
- end
94
-
95
- def update!
96
- timestamp = Time.now.strftime(DATETIME_FORMAT)
97
- @attributes[:updated_at] = timestamp
98
-
99
- if !@attributes.has_key?(:created_at) || @attributes[:created_at].empty?
100
- @attributes[:created_at] = timestamp
101
- end
102
-
103
- FileUtils.mkdir_p(File.dirname(filepath))
104
- File.write(filepath, serialize)
105
- end
106
-
107
- def delete!
108
- File.delete(filepath)
109
- end
110
-
111
- def frontmatter
112
- frontmatter = @attributes.clone
113
- SERIALIZE_IGNORE_KEYS.each { |key| frontmatter.delete(key) }
114
-
115
- if published?
116
- frontmatter.delete(:published)
117
- else
118
- frontmatter[:published] = false
119
- end
120
-
121
- frontmatter
122
- end
123
- end
124
- end
@@ -1,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RobinCMS
4
- class StaticItem
5
- attr_reader :library
6
-
7
- def initialize(library, filename, tempfile = nil)
8
- @library = library
9
- @filename = filename
10
- @tempfile = tempfile
11
- end
12
-
13
- def save!
14
- if File.exist?(filepath)
15
- raise ItemExistsError, "An item with the same name already exists"
16
- end
17
-
18
- image_field = @library[:fields].find { |f| f[:type] == "image" }
19
- dimensions = image_field && image_field[:dimensions]
20
- filetype = image_field && image_field[:filetype]
21
-
22
- resize_image!(dimensions) if dimensions
23
- format_image!(filetype) if filetype
24
-
25
- FileUtils.mkdir_p(File.dirname(filepath))
26
- FileUtils.cp(@tempfile, filepath)
27
- end
28
-
29
- def delete!
30
- File.delete(filepath)
31
- end
32
-
33
- def filepath
34
- File.join(@library[:static_location], File.basename(@filename))
35
- end
36
-
37
- class << self
38
- include Sluggable
39
-
40
- def create!(library, filename, tempfile)
41
- sluggified_filename = make_slug(filename)
42
- new(library, sluggified_filename, tempfile).tap do |item|
43
- item.save!
44
- end
45
- end
46
-
47
- def find_one(library, filename)
48
- path = File.join(library[:static_location], File.basename(filename))
49
-
50
- return unless File.exist?(path)
51
-
52
- new(library, filename)
53
- end
54
-
55
- def delete_if_exists!(library, filename)
56
- find_one(library, filename)&.delete!
57
- end
58
- end
59
-
60
- private
61
-
62
- def resize_image!(dimensions)
63
- # The mogrify command edits images in place. For more info, see
64
- # mogrify(1).
65
-
66
- system("mogrify -resize #{dimensions} #{@tempfile.to_path}")
67
- if $?.exitstatus != 0
68
- raise ConversionError, "Could not resize image #{@tempfile.to_path}"
69
- end
70
- end
71
-
72
- def format_image!(filetype)
73
- system("mogrify -format #{filetype} #{@tempfile.to_path}")
74
- if $?.exitstatus != 0
75
- raise ConversionError, "Could not format image #{@tempfile.to_path}"
76
- end
77
-
78
- # The name of the converted file will be the same as the original but
79
- # with a new file extension.
80
- converted = @tempfile.to_path.sub(/#{File.extname(@tempfile)}$/, ".#{filetype}")
81
-
82
- # Mogrify's format command creates a new file with the new extension.
83
- # Copy the contents of this file to the tempfile, then delete the newly
84
- # created file.
85
- IO.copy_stream(converted, @tempfile.to_path)
86
- File.delete(converted)
87
-
88
- # Update the file extension of the uploaded file.
89
- @filename.sub!(/#{File.extname(@filename)}$/, ".#{filetype}")
90
- end
91
- end
92
- end
@@ -1,441 +0,0 @@
1
- <style>
2
- :root {
3
- --border-color: #d2d5d8;
4
- --border-radius: 8px;
5
- --bg-color: rgb(246, 246, 247);
6
- --font-color: #141414;
7
- --link-color: rgb(71, 95, 145);
8
- --accent-color: <%= @config[:accent_color] %>;
9
- --accent-color-light: <%= @config[:accent_color] %>1e;
10
- --danger-color: #f85149;
11
- --danger-color-light: #f851491e;
12
- --draft-color: #fd8a13;
13
- --draft-color-light: #fd8a131e;
14
- --action-color: #4493f8;
15
- --action-color-light: #4493f81e;
16
- --padding-xxs: 0.25rem;
17
- --padding-xs: 0.5rem;
18
- --padding-sm: 0.75rem;
19
- --padding-md: 1rem;
20
- --padding-lg: 1.5rem;
21
- --padding: var(--padding-md);
22
- --content-width: 60rem;
23
- --content-left-margin: 16rem;
24
- --box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 0px 0px;
25
- --box-shadow-input: inset rgba(0, 0, 0, 0.1) 0px 1px 0px 0px;
26
- }
27
-
28
- @media only screen and (max-width: 1250px) {
29
- :root {
30
- --content-left-margin: 3rem;
31
- }
32
- }
33
-
34
- body {
35
- margin: 0;
36
- font-family: sans-serif;
37
- color: var(--font-color);
38
- background-color: var(--bg-color);
39
- }
40
-
41
- header {
42
- display: flex;
43
- align-items: center;
44
- justify-content: space-between;
45
- }
46
-
47
- header h1,
48
- header h2 {
49
- margin: 0;
50
- }
51
-
52
- header h1 a {
53
- text-decoration: none;
54
- color: var(--font-color);
55
- }
56
-
57
- h1 {
58
- font-size: 1.25rem;
59
- }
60
-
61
- h2 {
62
- font-size: 1rem;
63
- font-weight: 500;
64
- color: var(--link-color);
65
- }
66
-
67
- a {
68
- color: var(--link-color);
69
- }
70
-
71
- table {
72
- width: 100%;
73
- border-spacing: 0; /* border-collapse doesn't work with border radii */
74
- }
75
-
76
- th {
77
- font-weight: 500;
78
- }
79
-
80
- th,
81
- td {
82
- white-space: nowrap;
83
- overflow: hidden;
84
- text-overflow: ellipsis;
85
- }
86
-
87
- td a {
88
- display: block;
89
- text-decoration: none;
90
- color: initial;
91
- }
92
-
93
- th,
94
- td > * {
95
- padding: var(--padding-sm);
96
- text-align: left;
97
- }
98
-
99
- th:first-child,
100
- td:first-child > * {
101
- padding-left: var(--padding);
102
- }
103
-
104
- th:last-child,
105
- td:last-child > * {
106
- padding-right: var(--padding);
107
- }
108
-
109
- tbody td {
110
- background-color: white;
111
- }
112
-
113
- tbody tr:first-child td:first-child {
114
- border-top-left-radius: var(--border-radius);
115
- }
116
-
117
- tbody tr:first-child td:last-child {
118
- border-top-right-radius: var(--border-radius);
119
- }
120
-
121
- tbody tr:last-child td:first-child {
122
- border-bottom-left-radius: var(--border-radius);
123
- }
124
-
125
- tbody tr:last-child td:last-child {
126
- border-bottom-right-radius: var(--border-radius);
127
- }
128
-
129
- tbody tr td:first-child {
130
- border-left: 1px solid var(--border-color);
131
- }
132
-
133
- tbody tr td:last-child {
134
- border-right: 1px solid var(--border-color);
135
- }
136
-
137
- tbody tr td {
138
- border-top: 1px solid var(--border-color);
139
- }
140
-
141
- tbody tr:last-child td {
142
- border-bottom: 1px solid var(--border-color);
143
- transform: scale(1); /* Weird hack to get box shadows to work on FF */
144
- box-shadow: var(--box-shadow);
145
- }
146
-
147
- tbody tr:hover td {
148
- background-color: var(--bg-color);
149
- }
150
-
151
- label {
152
- display: block;
153
- margin-bottom: var(--padding-xs);
154
- }
155
-
156
- input {
157
- border: 1px solid var(--border-color);
158
- border-radius: var(--border-radius);
159
- width: 100%;
160
- box-sizing: border-box;
161
- padding: var(--padding-xs);
162
- box-shadow: var(--box-shadow-input);
163
- }
164
-
165
- input[type="search"] {
166
- width: initial;
167
- }
168
-
169
- input[type="file"] {
170
- box-shadow: none;
171
- }
172
-
173
- input[name="image"] {
174
- border: none;
175
- }
176
-
177
- input::placeholder {
178
- color: var(--font-color);
179
- }
180
-
181
- select {
182
- border: 1px solid var(--border-color);
183
- border-radius: var(--border-radius);
184
- box-sizing: border-box;
185
- padding: var(--padding-xs);
186
- background-color: white;
187
- box-shadow: var(--box-shadow-input);
188
- }
189
-
190
- button {
191
- padding: var(--padding-xxs) var(--padding-xs);
192
- color: var(--accent-color);
193
- border: 1px solid var(--accent-color);
194
- border-radius: var(--border-radius);
195
- background-color: var(--accent-color-light);
196
- box-shadow: var(--box-shadow);
197
- }
198
-
199
- button.--danger {
200
- color: var(--danger-color);
201
- border: 1px solid var(--danger-color);
202
- background-color: var(--danger-color-light);
203
- }
204
-
205
- button.--action {
206
- color: var(--action-color);
207
- border: 1px solid var(--action-color);
208
- background-color: var(--action-color-light);
209
- }
210
-
211
- fieldset {
212
- border: 0;
213
- }
214
-
215
- dialog {
216
- border: 1px solid var(--border-color);
217
- border-radius: var(--border-radius);
218
- width: 20rem;
219
- white-space: wrap;
220
- }
221
-
222
- dialog::backdrop {
223
- background: rgba(0, 0, 0, .15);
224
- backdrop-filter: blur(1px);
225
- }
226
-
227
- dialog .controls {
228
- justify-content: flex-end;
229
- background-color: var(--bg-color);
230
- margin: 0 calc(-1 * var(--padding)) calc(-1 * var(--padding)) calc(-1 * var(--padding));
231
- padding: var(--padding);
232
- }
233
-
234
- hr {
235
- margin: var(--padding-xs) var(--padding);
236
- border: 0;
237
- height: 1px;
238
- background-color: var(--border-color);
239
- }
240
-
241
- .flash {
242
- display: block;
243
- margin-bottom: var(--padding);
244
- }
245
-
246
- *.--danger {
247
- color: var(--danger-color);
248
- }
249
-
250
- *.--success {
251
- color: var(--action-color);
252
- }
253
-
254
- #robin-logo {
255
- width: 80px;
256
- margin-right: var(--padding-xs);
257
- }
258
-
259
- #site-header {
260
- height: 3.5rem;
261
- width: var(--content-width);
262
- padding: 0 var(--padding);
263
- margin-left: var(--content-left-margin);
264
- margin-bottom: var(--padding);
265
- box-sizing: border-box;
266
- }
267
-
268
- #site-nav ul {
269
- list-style-type: none;
270
- padding-left: 0;
271
- margin: 0;
272
- }
273
-
274
- #site-nav a {
275
- text-decoration: none;
276
- font-weight: 500;
277
- padding: var(--padding-xxs) 0;
278
- }
279
-
280
- #site-nav li.current a,
281
- #site-nav li:hover a {
282
- border-bottom: 3px solid var(--link-color);
283
- }
284
-
285
- #site-content {
286
- max-width: var(--content-width);
287
- margin-left: var(--content-left-margin);
288
-
289
- <% unless session[:auth_user] %>
290
- max-width: 30rem;
291
- margin: 5.5rem auto 0 auto;
292
- <% end %>
293
- }
294
-
295
- #site-content > * {
296
- margin-bottom: var(--padding);
297
- }
298
-
299
- #site-footer {
300
- display: flex;
301
- justify-content: center;
302
- align-items: center;
303
- column-gap: var(--padding);
304
- <% unless session[:auth_user] %>
305
- text-align: center;
306
- <% end %>
307
- }
308
-
309
- #filter-form {
310
- margin-bottom: 0;
311
- }
312
-
313
- #filter-form select,
314
- #filter-form input {
315
- padding: 0.25rem;
316
- border-radius: 12px;
317
- }
318
-
319
- #filter-form button {
320
- height: 24px;
321
- border-radius: 12px;
322
- padding: 0 var(--padding-xs);
323
- }
324
-
325
- #login-form {
326
- display: flex;
327
- align-items: center;
328
- justify-content: space-between;
329
- }
330
-
331
- #login-form fieldset {
332
- margin-left: 2rem;
333
- flex: 1 1 15rem;
334
- }
335
-
336
- .card {
337
- background-color: white;
338
- box-shadow: var(--box-shadow);
339
- border-radius: var(--border-radius);
340
- border: 1px solid var(--border-color);
341
- padding: var(--padding);
342
- }
343
-
344
- .card.--no-padding {
345
- padding: unset;
346
- }
347
-
348
- .card.--clear {
349
- background-color: var(--bg-color);
350
- box-shadow: none;
351
- border: 0;
352
- }
353
-
354
- .card > p {
355
- margin-top: 0;
356
- }
357
-
358
- .controls {
359
- display: flex;
360
- align-items: center;
361
- column-gap: var(--padding);
362
- }
363
-
364
- .controls.--align-right {
365
- justify-content: flex-end;
366
- }
367
-
368
- .controls.--align-space {
369
- justify-content: space-between;
370
- }
371
-
372
- .controls.--gap-md {
373
- column-gap: var(--padding-lg);
374
- }
375
-
376
- .badge {
377
- font-size: 0.825rem;
378
- border-radius: 0.625rem;
379
- text-align: center;
380
- padding: 0 0.5rem;
381
- display: block;
382
- height: 1.25rem;
383
- line-height: 1.25rem;
384
- }
385
-
386
- .badge.--draft {
387
- background-color: var(--draft-color-light);
388
- color: var(--draft-color);
389
- }
390
-
391
- .badge.--published {
392
- background-color: var(--action-color-light);
393
- color: var(--action-color);
394
- }
395
-
396
- .field {
397
- margin: var(--padding) 0;
398
- }
399
-
400
- .no-items {
401
- text-align: center;
402
- padding: 2rem;
403
- }
404
-
405
- .grow-column {
406
- width: 100%;
407
- }
408
-
409
- .image-preview {
410
- width: 8rem;
411
- height: 8rem;
412
- border: 1px solid var(--border-color);
413
- object-fit: cover;
414
- display: block;
415
- }
416
-
417
- /* trix editor overrides */
418
-
419
- trix-editor {
420
- height: 16rem;
421
- border: 1px solid var(--border-color) !important;
422
- border-radius: var(--border-radius) !important;
423
- box-shadow: var(--box-shadow-input);
424
- overflow: scroll;
425
- }
426
-
427
- .trix-button-group {
428
- border: 1px solid var(--border-color) !important;
429
- border-radius: var(--border-radius) !important;
430
- box-shadow: var(--box-shadow-input);
431
- }
432
-
433
- .trix-button {
434
- border-bottom: 0 !important;
435
- box-shadow: none;
436
- }
437
-
438
- .trix-button.trix-active {
439
- background: var(--accent-color-light) !important;
440
- }
441
- </style>
File without changes