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.
@@ -0,0 +1,443 @@
1
+ :root {
2
+ --border-color: #d2d5d8;
3
+ --border-radius: 8px;
4
+ --bg-color: rgb(246, 246, 247);
5
+ --font-color: #141414;
6
+ --link-color: rgb(71, 95, 145);
7
+ --accent-color: <%= @config[:accent_color] %>;
8
+ --accent-color-light: <%= @config[:accent_color] %>1e;
9
+ --danger-color: #f85149;
10
+ --danger-color-light: #f851491e;
11
+ --draft-color: #fd8a13;
12
+ --draft-color-light: #fd8a131e;
13
+ --action-color: #4493f8;
14
+ --action-color-light: #4493f81e;
15
+ --padding-xxs: 0.25rem;
16
+ --padding-xs: 0.5rem;
17
+ --padding-sm: 0.75rem;
18
+ --padding-md: 1rem;
19
+ --padding-lg: 1.5rem;
20
+ --padding: var(--padding-md);
21
+ --content-width: 60rem;
22
+ --content-left-margin: 16rem;
23
+ --box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 0px 0px;
24
+ --box-shadow-input: inset rgba(0, 0, 0, 0.1) 0px 1px 0px 0px;
25
+ }
26
+
27
+ test {
28
+ te
29
+ }
30
+
31
+ @media only screen and (max-width: 1250px) {
32
+ :root {
33
+ --content-left-margin: 3rem;
34
+ }
35
+ }
36
+
37
+ body {
38
+ margin: 0;
39
+ font-family: sans-serif;
40
+ color: var(--font-color);
41
+ background-color: var(--bg-color);
42
+ }
43
+
44
+ header {
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: space-between;
48
+ }
49
+
50
+ header h1,
51
+ header h2 {
52
+ margin: 0;
53
+ }
54
+
55
+ header h1 a {
56
+ text-decoration: none;
57
+ color: var(--font-color);
58
+ }
59
+
60
+ h1 {
61
+ font-size: 1.25rem;
62
+ }
63
+
64
+ h2 {
65
+ font-size: 1rem;
66
+ font-weight: 500;
67
+ color: var(--link-color);
68
+ }
69
+
70
+ a {
71
+ color: var(--link-color);
72
+ }
73
+
74
+ table {
75
+ width: 100%;
76
+ border-spacing: 0; /* border-collapse doesn't work with border radii */
77
+ }
78
+
79
+ th {
80
+ font-weight: 500;
81
+ }
82
+
83
+ th,
84
+ td {
85
+ white-space: nowrap;
86
+ overflow: hidden;
87
+ text-overflow: ellipsis;
88
+ }
89
+
90
+ td a {
91
+ display: block;
92
+ text-decoration: none;
93
+ color: initial;
94
+ }
95
+
96
+ th,
97
+ td > * {
98
+ padding: var(--padding-sm);
99
+ text-align: left;
100
+ }
101
+
102
+ th:first-child,
103
+ td:first-child > * {
104
+ padding-left: var(--padding);
105
+ }
106
+
107
+ th:last-child,
108
+ td:last-child > * {
109
+ padding-right: var(--padding);
110
+ }
111
+
112
+ tbody td {
113
+ background-color: white;
114
+ }
115
+
116
+ tbody tr:first-child td:first-child {
117
+ border-top-left-radius: var(--border-radius);
118
+ }
119
+
120
+ tbody tr:first-child td:last-child {
121
+ border-top-right-radius: var(--border-radius);
122
+ }
123
+
124
+ tbody tr:last-child td:first-child {
125
+ border-bottom-left-radius: var(--border-radius);
126
+ }
127
+
128
+ tbody tr:last-child td:last-child {
129
+ border-bottom-right-radius: var(--border-radius);
130
+ }
131
+
132
+ tbody tr td:first-child {
133
+ border-left: 1px solid var(--border-color);
134
+ }
135
+
136
+ tbody tr td:last-child {
137
+ border-right: 1px solid var(--border-color);
138
+ }
139
+
140
+ tbody tr td {
141
+ border-top: 1px solid var(--border-color);
142
+ }
143
+
144
+ tbody tr:last-child td {
145
+ border-bottom: 1px solid var(--border-color);
146
+ transform: scale(1); /* Weird hack to get box shadows to work on FF */
147
+ box-shadow: var(--box-shadow);
148
+ }
149
+
150
+ tbody tr:hover td {
151
+ background-color: var(--bg-color);
152
+ }
153
+
154
+ label {
155
+ display: block;
156
+ margin-bottom: var(--padding-xs);
157
+ }
158
+
159
+ input {
160
+ border: 1px solid var(--border-color);
161
+ border-radius: var(--border-radius);
162
+ width: 100%;
163
+ box-sizing: border-box;
164
+ padding: var(--padding-xs);
165
+ box-shadow: var(--box-shadow-input);
166
+ }
167
+
168
+ input[type="search"] {
169
+ width: initial;
170
+ }
171
+
172
+ input[type="file"] {
173
+ box-shadow: none;
174
+ }
175
+
176
+ input[name="image"] {
177
+ border: none;
178
+ }
179
+
180
+ input::placeholder {
181
+ color: var(--font-color);
182
+ }
183
+
184
+ select {
185
+ border: 1px solid var(--border-color);
186
+ border-radius: var(--border-radius);
187
+ box-sizing: border-box;
188
+ padding: var(--padding-xs);
189
+ background-color: white;
190
+ box-shadow: var(--box-shadow-input);
191
+ }
192
+
193
+ button {
194
+ padding: var(--padding-xxs) var(--padding-xs);
195
+ color: var(--accent-color);
196
+ border: 1px solid var(--accent-color);
197
+ border-radius: var(--border-radius);
198
+ background-color: var(--accent-color-light);
199
+ box-shadow: var(--box-shadow);
200
+ }
201
+
202
+ button.--danger {
203
+ color: var(--danger-color);
204
+ border: 1px solid var(--danger-color);
205
+ background-color: var(--danger-color-light);
206
+ }
207
+
208
+ button.--action {
209
+ color: var(--action-color);
210
+ border: 1px solid var(--action-color);
211
+ background-color: var(--action-color-light);
212
+ }
213
+
214
+ fieldset {
215
+ border: 0;
216
+ }
217
+
218
+ dialog {
219
+ border: 1px solid var(--border-color);
220
+ border-radius: var(--border-radius);
221
+ width: 20rem;
222
+ white-space: wrap;
223
+ }
224
+
225
+ dialog::backdrop {
226
+ background: rgba(0, 0, 0, .15);
227
+ backdrop-filter: blur(1px);
228
+ }
229
+
230
+ dialog .controls {
231
+ justify-content: flex-end;
232
+ background-color: var(--bg-color);
233
+ margin: 0 calc(-1 * var(--padding)) calc(-1 * var(--padding)) calc(-1 * var(--padding));
234
+ padding: var(--padding);
235
+ }
236
+
237
+ hr {
238
+ margin: var(--padding-xs) var(--padding);
239
+ border: 0;
240
+ height: 1px;
241
+ background-color: var(--border-color);
242
+ }
243
+
244
+ .flash {
245
+ display: block;
246
+ margin-bottom: var(--padding);
247
+ }
248
+
249
+ *.--danger {
250
+ color: var(--danger-color);
251
+ }
252
+
253
+ *.--success {
254
+ color: var(--action-color);
255
+ }
256
+
257
+ #robin-logo {
258
+ width: 80px;
259
+ margin-right: var(--padding-xs);
260
+ }
261
+
262
+ #site-header {
263
+ height: 3.5rem;
264
+ width: var(--content-width);
265
+ padding: 0 var(--padding);
266
+ margin-left: var(--content-left-margin);
267
+ margin-bottom: var(--padding);
268
+ box-sizing: border-box;
269
+ }
270
+
271
+ #site-nav ul {
272
+ list-style-type: none;
273
+ padding-left: 0;
274
+ margin: 0;
275
+ }
276
+
277
+ #site-nav a {
278
+ text-decoration: none;
279
+ font-weight: 500;
280
+ padding: var(--padding-xxs) 0;
281
+ }
282
+
283
+ #site-nav li.current a,
284
+ #site-nav li:hover a {
285
+ border-bottom: 3px solid var(--link-color);
286
+ }
287
+
288
+ #site-content {
289
+ max-width: var(--content-width);
290
+ margin-left: var(--content-left-margin);
291
+
292
+ <% unless session[:auth_user] %>
293
+ max-width: 30rem;
294
+ margin: 5.5rem auto 0 auto;
295
+ <% end %>
296
+ }
297
+
298
+ #site-content > * {
299
+ margin-bottom: var(--padding);
300
+ }
301
+
302
+ #site-footer {
303
+ display: flex;
304
+ justify-content: center;
305
+ align-items: center;
306
+ column-gap: var(--padding);
307
+ <% unless session[:auth_user] %>
308
+ text-align: center;
309
+ <% end %>
310
+ }
311
+
312
+ #filter-form {
313
+ margin-bottom: 0;
314
+ }
315
+
316
+ #filter-form select,
317
+ #filter-form input {
318
+ padding: 0.25rem;
319
+ border-radius: 12px;
320
+ }
321
+
322
+ #filter-form button {
323
+ height: 24px;
324
+ border-radius: 12px;
325
+ padding: 0 var(--padding-xs);
326
+ }
327
+
328
+ #login-form {
329
+ display: flex;
330
+ align-items: center;
331
+ justify-content: space-between;
332
+ }
333
+
334
+ #login-form fieldset {
335
+ margin-left: 2rem;
336
+ flex: 1 1 15rem;
337
+ }
338
+
339
+ .card {
340
+ background-color: white;
341
+ box-shadow: var(--box-shadow);
342
+ border-radius: var(--border-radius);
343
+ border: 1px solid var(--border-color);
344
+ padding: var(--padding);
345
+ }
346
+
347
+ .card.--no-padding {
348
+ padding: unset;
349
+ }
350
+
351
+ .card.--clear {
352
+ background-color: var(--bg-color);
353
+ box-shadow: none;
354
+ border: 0;
355
+ }
356
+
357
+ .card > p {
358
+ margin-top: 0;
359
+ }
360
+
361
+ .controls {
362
+ display: flex;
363
+ align-items: center;
364
+ column-gap: var(--padding);
365
+ }
366
+
367
+ .controls.--align-right {
368
+ justify-content: flex-end;
369
+ }
370
+
371
+ .controls.--align-space {
372
+ justify-content: space-between;
373
+ }
374
+
375
+ .controls.--gap-md {
376
+ column-gap: var(--padding-lg);
377
+ }
378
+
379
+ .badge {
380
+ font-size: 0.825rem;
381
+ border-radius: 0.625rem;
382
+ text-align: center;
383
+ padding: 0 0.5rem;
384
+ display: block;
385
+ height: 1.25rem;
386
+ line-height: 1.25rem;
387
+ }
388
+
389
+ .badge.--draft {
390
+ background-color: var(--draft-color-light);
391
+ color: var(--draft-color);
392
+ }
393
+
394
+ .badge.--published {
395
+ background-color: var(--action-color-light);
396
+ color: var(--action-color);
397
+ }
398
+
399
+ .field {
400
+ margin: var(--padding) 0;
401
+ }
402
+
403
+ .no-items {
404
+ text-align: center;
405
+ padding: 2rem;
406
+ }
407
+
408
+ .grow-column {
409
+ width: 100%;
410
+ }
411
+
412
+ .image-preview {
413
+ width: 8rem;
414
+ height: 8rem;
415
+ border: 1px solid var(--border-color);
416
+ object-fit: cover;
417
+ display: block;
418
+ }
419
+
420
+ /* trix editor overrides */
421
+
422
+ trix-editor {
423
+ height: 16rem;
424
+ border: 1px solid var(--border-color) !important;
425
+ border-radius: var(--border-radius) !important;
426
+ box-shadow: var(--box-shadow-input);
427
+ overflow: scroll;
428
+ }
429
+
430
+ .trix-button-group {
431
+ border: 1px solid var(--border-color) !important;
432
+ border-radius: var(--border-radius) !important;
433
+ box-shadow: var(--box-shadow-input);
434
+ }
435
+
436
+ .trix-button {
437
+ border-bottom: 0 !important;
438
+ box-shadow: none;
439
+ }
440
+
441
+ .trix-button.trix-active {
442
+ background: var(--accent-color-light) !important;
443
+ }
data/lib/robin_cms.rb CHANGED
@@ -27,12 +27,13 @@ end
27
27
 
28
28
  require_relative "robin_cms/version"
29
29
  require_relative "robin_cms/configuration"
30
- require_relative "robin_cms/itemable"
31
- require_relative "robin_cms/queryable"
30
+ require_relative "robin_cms/editable"
32
31
  require_relative "robin_cms/sluggable"
33
- require_relative "robin_cms/collection_item"
34
- require_relative "robin_cms/data_item"
35
- require_relative "robin_cms/static_item"
32
+ require_relative "robin_cms/queryable"
33
+ require_relative "robin_cms/item"
34
+ require_relative "robin_cms/library"
35
+ require_relative "robin_cms/collection_library"
36
+ require_relative "robin_cms/data_library"
36
37
  require_relative "robin_cms/item"
37
38
  require_relative "robin_cms/auth"
38
39
  require_relative "robin_cms/flash"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: robin_cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aron Lebani
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-09-10 00:00:00.000000000 Z
11
+ date: 2025-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bcrypt
@@ -79,17 +79,17 @@ files:
79
79
  - lib/robin_cms.rb
80
80
  - lib/robin_cms/auth.rb
81
81
  - lib/robin_cms/cms.rb
82
- - lib/robin_cms/collection_item.rb
82
+ - lib/robin_cms/collection_library.rb
83
83
  - lib/robin_cms/configuration-schema.json
84
84
  - lib/robin_cms/configuration.rb
85
- - lib/robin_cms/data_item.rb
85
+ - lib/robin_cms/data_library.rb
86
+ - lib/robin_cms/editable.rb
86
87
  - lib/robin_cms/flash.rb
87
88
  - lib/robin_cms/helpers.rb
88
89
  - lib/robin_cms/item.rb
89
- - lib/robin_cms/itemable.rb
90
+ - lib/robin_cms/library.rb
90
91
  - lib/robin_cms/queryable.rb
91
92
  - lib/robin_cms/sluggable.rb
92
- - lib/robin_cms/static_item.rb
93
93
  - lib/robin_cms/version.rb
94
94
  - lib/robin_cms/views/change_password.erb
95
95
  - lib/robin_cms/views/delete_dialog.erb
@@ -104,13 +104,13 @@ files:
104
104
  - lib/robin_cms/views/library_actions.erb
105
105
  - lib/robin_cms/views/library_item.erb
106
106
  - lib/robin_cms/views/login.erb
107
- - lib/robin_cms/views/logo.erb
107
+ - lib/robin_cms/views/logo.svg.erb
108
108
  - lib/robin_cms/views/nav.erb
109
- - lib/robin_cms/views/new_tab.erb
109
+ - lib/robin_cms/views/new_tab.svg.erb
110
110
  - lib/robin_cms/views/profile.erb
111
111
  - lib/robin_cms/views/richtext_field.erb
112
112
  - lib/robin_cms/views/select_field.erb
113
- - lib/robin_cms/views/style.erb
113
+ - lib/robin_cms/views/stylesheet.css.erb
114
114
  homepage: https://codeberg.org/evencuriouser/robin_cms
115
115
  licenses:
116
116
  - MIT
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RobinCMS
4
- class CollectionItem
5
- include Itemable
6
- extend Queryable
7
- extend Sluggable
8
-
9
- def self.blank(library)
10
- new(nil, library)
11
- end
12
-
13
- def self.create!(library, attributes)
14
- id = make_slug(attributes[:title], library[:pattern])
15
- new(id, library, attributes).tap do |item|
16
- item.save!
17
- end
18
- end
19
-
20
- def self.find_one(library, id)
21
- location = library[:location]
22
- ext = library[:filetype]
23
- path = File.join(location, "#{id}.#{ext}")
24
-
25
- return unless File.exist?(path)
26
-
27
- deserialize(library, path)
28
- end
29
-
30
- def self.all(library)
31
- location = library[:location]
32
- ext = library[:filetype]
33
- path = File.join(location, "*.#{ext}")
34
-
35
- Dir.glob(path).filter { |f| File.file?(f) }.map do |path|
36
- deserialize(library, path)
37
- end
38
- end
39
-
40
- def self.deserialize(library, path)
41
- raw = File.read(path)
42
- id = File.basename(path, File.extname(path))
43
-
44
- _, frontmatter, content = raw.split("---")
45
-
46
- attributes = YAML.load(frontmatter, symbolize_names: true)
47
- attributes[:content] = content.strip
48
-
49
- new(id, library, attributes)
50
- end
51
-
52
- private_class_method :deserialize
53
-
54
- def filepath
55
- location = @library[:location]
56
- name = @id
57
- ext = @library[:filetype]
58
- File.join(location, "#{name}.#{ext}")
59
- end
60
-
61
- def save!
62
- if File.exist?(filepath)
63
- raise ItemExistsError, "An item with the same name already exists"
64
- end
65
-
66
- super
67
- end
68
-
69
- private
70
-
71
- def serialize
72
- # The Psych module (for which YAML is an alias) has a
73
- # stringify_names option which does exactly this. However it was
74
- # only introduced in Ruby 3.4. Using transform_keys is a workaround
75
- # to support earlier versions of Ruby.
76
- fm = frontmatter.to_h.transform_keys(&:to_s)
77
-
78
- content = @attributes[:content]
79
- YAML.dump(fm, stringify_names: true) << "---\n" << content
80
- end
81
- end
82
- end
@@ -1,88 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RobinCMS
4
- class DataItem
5
- include Itemable
6
- extend Queryable
7
-
8
- def self.blank(library)
9
- new(nil, library)
10
- end
11
-
12
- def self.create!(library, attributes)
13
- new(nil, library, attributes).tap do |item|
14
- item.save!
15
- end
16
- end
17
-
18
- def self.find_one(library, id)
19
- all(library)[id.to_i]
20
- end
21
-
22
- def self.all(library)
23
- location = library[:location]
24
- name = library[:id]
25
- ext = library[:filetype]
26
- path = File.join(location, "#{name}.#{ext}")
27
-
28
- return [] unless File.exist?(path)
29
-
30
- deserialize(library, path)
31
- end
32
-
33
- def self.deserialize(library, path)
34
- raw = File.read(path)
35
- items = YAML.load(raw, symbolize_names: true)
36
-
37
- return [] unless items
38
-
39
- items.each_with_index.map do |attrs, i|
40
- new(i, library, attrs)
41
- end
42
- end
43
-
44
- private_class_method :deserialize
45
-
46
- def initialize(id, library, attrs = {})
47
- super
48
-
49
- @mark_for_deletion = false
50
- end
51
-
52
- def filepath
53
- location = @library[:location]
54
- name = @library[:id]
55
- ext = @library[:filetype]
56
- File.join(location, "#{name}.#{ext}")
57
- end
58
-
59
- def delete!
60
- @mark_for_deletion = true
61
- save!
62
- end
63
-
64
- private
65
-
66
- def serialize
67
- # See comment in corresponding method in lib/collection_item.rb.
68
- fm = frontmatter.to_h.transform_keys(&:to_s)
69
-
70
- items = if File.exist?(filepath)
71
- raw = File.read(filepath)
72
- YAML.load(raw)
73
- else
74
- []
75
- end
76
-
77
- if @mark_for_deletion
78
- items[@id.to_i] = nil
79
- elsif @id.nil?
80
- items.append(fm)
81
- else
82
- items[@id.to_i] = fm
83
- end
84
-
85
- YAML.dump(items.compact, stringify_names: true)
86
- end
87
- end
88
- end