robin_cms 0.1.7 → 0.3.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.
- checksums.yaml +4 -4
- data/README.md +8 -3
- data/lib/robin_cms/cms.rb +34 -20
- data/lib/robin_cms/configuration.rb +61 -16
- data/lib/robin_cms/configuration_schema.rb +109 -0
- data/lib/robin_cms/data_library.rb +10 -3
- data/lib/robin_cms/helpers.rb +46 -10
- data/lib/robin_cms/item.rb +65 -24
- data/lib/robin_cms/library.rb +26 -1
- data/lib/robin_cms/singular_library.rb +68 -0
- data/lib/robin_cms/version.rb +1 -1
- data/lib/robin_cms/views/change_password.erb +1 -1
- data/lib/robin_cms/views/image_field.erb +0 -1
- data/lib/robin_cms/views/layout.erb +10 -1
- data/lib/robin_cms/views/library.erb +4 -5
- data/lib/robin_cms/views/library_actions.erb +3 -1
- data/lib/robin_cms/views/library_item.erb +3 -2
- data/lib/robin_cms/views/new_tab.svg.erb +0 -1
- data/lib/robin_cms/views/profile.erb +1 -1
- data/lib/robin_cms/views/richtext_field.erb +88 -2
- data/lib/robin_cms/views/select_field.erb +1 -1
- data/lib/robin_cms/views/stylesheet.css.erb +2 -2
- data/lib/robin_cms/views/trix.css.erb +75 -0
- data/lib/robin_cms.rb +5 -0
- metadata +6 -4
- data/lib/robin_cms/configuration-schema.json +0 -120
data/lib/robin_cms/library.rb
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RobinCMS
|
|
4
|
+
# The data access layer for Items.
|
|
5
|
+
#
|
|
6
|
+
# This class implements a Data Mapper pattern for persisting items to disk.
|
|
7
|
+
# It works well for this domain because there are two ways that items are
|
|
8
|
+
# written to disk, depending on whether they are a Data or Collection item.
|
|
9
|
+
# Since the Data Mapper is independent of the in-memory representation of
|
|
10
|
+
# Items, Items don't need to know about which 'type' they are.
|
|
4
11
|
class Library
|
|
5
12
|
include Editable
|
|
6
13
|
include Sluggable
|
|
@@ -22,11 +29,13 @@ module RobinCMS
|
|
|
22
29
|
extend CollectionLibrary
|
|
23
30
|
when "data"
|
|
24
31
|
extend DataLibrary
|
|
32
|
+
when "singular"
|
|
33
|
+
extend SingularLibrary
|
|
25
34
|
end
|
|
26
35
|
end
|
|
27
36
|
|
|
28
37
|
def blank
|
|
29
|
-
Item.new(nil, **@schema)
|
|
38
|
+
Item.new(nil, {}, **@schema)
|
|
30
39
|
end
|
|
31
40
|
|
|
32
41
|
def create_static(filename, tempfile)
|
|
@@ -79,5 +88,21 @@ module RobinCMS
|
|
|
79
88
|
def kind
|
|
80
89
|
@schema[:id]
|
|
81
90
|
end
|
|
91
|
+
|
|
92
|
+
def type
|
|
93
|
+
@schema[:type]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def collection?
|
|
97
|
+
type == "collection"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def data?
|
|
101
|
+
type == "data"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def singular?
|
|
105
|
+
type == "singular"
|
|
106
|
+
end
|
|
82
107
|
end
|
|
83
108
|
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RobinCMS
|
|
4
|
+
module SingularLibrary
|
|
5
|
+
# Since there is only one of these, give it the special id 0.
|
|
6
|
+
ID = 0
|
|
7
|
+
|
|
8
|
+
def self.extended(target)
|
|
9
|
+
# Since singular files can't be created or delete by users, create an
|
|
10
|
+
# empty one automatically if it doesn't already exist.
|
|
11
|
+
|
|
12
|
+
filename = target.schema[:id]
|
|
13
|
+
filetype = target.schema[:filetype]
|
|
14
|
+
filepath = File.join(target.schema[:location], "#{filename}.#{filetype}")
|
|
15
|
+
|
|
16
|
+
return if File.exist?(filepath)
|
|
17
|
+
|
|
18
|
+
FileUtils.mkdir_p(File.dirname(filepath))
|
|
19
|
+
item = Item.new(nil, {title: target.schema[:label]}, **target.schema)
|
|
20
|
+
item.update_timestamps!
|
|
21
|
+
serialized = YAML.dump(item.frontmatter, stringify_names: true)
|
|
22
|
+
File.write(filepath, serialized)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create(_attributes = nil)
|
|
26
|
+
raise InvalidOperationError, "Invalid operation for Singular item"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def find_one(_id = nil)
|
|
30
|
+
# Ignore the id parameter. For singular libraries, there is only a single
|
|
31
|
+
# item. The id parameter is left for API compatibility with the other
|
|
32
|
+
# library types.
|
|
33
|
+
|
|
34
|
+
item = YAML.load_file(filepath, symbolize_names: true, permitted_classes: [Date])
|
|
35
|
+
|
|
36
|
+
return nil unless item
|
|
37
|
+
|
|
38
|
+
Item.new(ID, item, **@schema)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def all
|
|
42
|
+
# Returns an array of the only item. For API compatibility with the other
|
|
43
|
+
# library types.
|
|
44
|
+
[find_one].compact
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def write(item)
|
|
48
|
+
FileUtils.mkdir_p(File.dirname(filepath))
|
|
49
|
+
FileUtils.touch(filepath)
|
|
50
|
+
|
|
51
|
+
item.update_timestamps!
|
|
52
|
+
serialized = YAML.dump(item.frontmatter, stringify_names: true)
|
|
53
|
+
File.write(filepath, serialized)
|
|
54
|
+
|
|
55
|
+
filepath
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def delete(_id = nil)
|
|
59
|
+
raise InvalidOperationError, "Invalid operation for Singular item"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def filepath
|
|
65
|
+
File.join(@schema[:location], "#{@schema[:id]}.#{@schema[:filetype]}")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/robin_cms/version.rb
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<% if session[:auth_user] %>
|
|
20
20
|
<header id="site-header">
|
|
21
21
|
<span class="controls">
|
|
22
|
-
<h1><a href="<%=
|
|
22
|
+
<h1><a href="<%= home_url %>"><%= @config[:title] %> admin</a></h1>
|
|
23
23
|
</span>
|
|
24
24
|
<span class="controls --gap-md">
|
|
25
25
|
<a href="<%= @config[:url] %>" target="_blank">
|
|
@@ -35,7 +35,13 @@
|
|
|
35
35
|
</span>
|
|
36
36
|
</header>
|
|
37
37
|
<% end %>
|
|
38
|
+
|
|
38
39
|
<main id="site-content">
|
|
40
|
+
<% if session[:auth_user] %>
|
|
41
|
+
<header class="card --clear">
|
|
42
|
+
<%= erb :nav %>
|
|
43
|
+
</header>
|
|
44
|
+
<% end %>
|
|
39
45
|
<%= yield %>
|
|
40
46
|
<footer id="site-footer" class="card --clear">
|
|
41
47
|
<%= erb :"logo.svg" %>
|
|
@@ -46,6 +52,7 @@
|
|
|
46
52
|
</p>
|
|
47
53
|
</footer>
|
|
48
54
|
</main>
|
|
55
|
+
|
|
49
56
|
<dialog id="successdialog" class="card">
|
|
50
57
|
<h2>Success!</h2>
|
|
51
58
|
<p><%= flash[:success_dialog] %></p>
|
|
@@ -55,6 +62,7 @@
|
|
|
55
62
|
</form>
|
|
56
63
|
</div>
|
|
57
64
|
</dialog>
|
|
65
|
+
|
|
58
66
|
<dialog id="errordialog" class="card">
|
|
59
67
|
<h2>Oops, something went wrong.</h2>
|
|
60
68
|
<p><%= flash[:error_dialog] %></p>
|
|
@@ -65,6 +73,7 @@
|
|
|
65
73
|
</div>
|
|
66
74
|
</dialog>
|
|
67
75
|
</body>
|
|
76
|
+
|
|
68
77
|
<script>
|
|
69
78
|
const image = document.getElementById("__field_image")
|
|
70
79
|
if (image) {
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
<header class="card --clear">
|
|
2
|
-
<%= erb :nav %>
|
|
3
|
-
</header>
|
|
4
1
|
<div class="card">
|
|
5
2
|
<% if @library[:description] %>
|
|
6
3
|
<p><%= @library[:description] %></p>
|
|
@@ -13,7 +10,9 @@
|
|
|
13
10
|
</form>
|
|
14
11
|
<% end %>
|
|
15
12
|
</div>
|
|
13
|
+
|
|
16
14
|
<%= erb :filter_form %>
|
|
15
|
+
|
|
17
16
|
<% if @items.empty? %>
|
|
18
17
|
<div class="no-items">
|
|
19
18
|
Nothing to show.
|
|
@@ -55,12 +54,12 @@
|
|
|
55
54
|
<% end %>
|
|
56
55
|
<td class="date">
|
|
57
56
|
<a href="<%= library_item_url(@library, item) %>">
|
|
58
|
-
<%= item.created_at
|
|
57
|
+
<%= item.created_at&.strftime("%b %d, %Y") %>
|
|
59
58
|
</a>
|
|
60
59
|
</td>
|
|
61
60
|
<td class="date">
|
|
62
61
|
<a href="<%= library_item_url(@library, item) %>">
|
|
63
|
-
<%= item.updated_at
|
|
62
|
+
<%= item.updated_at&.strftime("%b %d, %Y") %>
|
|
64
63
|
</a>
|
|
65
64
|
</td>
|
|
66
65
|
</tr>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<span class="controls">
|
|
2
|
-
|
|
2
|
+
<% unless @library.singular? %>
|
|
3
|
+
<a href="<%= library_url(@library) %>">Back</a>
|
|
4
|
+
<% end %>
|
|
3
5
|
<button form="<%= safe_id(@item.id, 'edit_item') %>" type="submit">Save</button>
|
|
4
6
|
<%= erb :delete_dialog if has_delete %>
|
|
5
7
|
</span>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
</h2>
|
|
5
5
|
<%= erb :library_actions, locals: { has_delete: @item.can_delete? } %>
|
|
6
6
|
</header>
|
|
7
|
+
|
|
7
8
|
<form
|
|
8
9
|
id="<%= safe_id(@item.id, 'edit_item') %>"
|
|
9
10
|
class="card"
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
<div class="field">
|
|
20
21
|
<%=
|
|
21
22
|
case field[:type]
|
|
22
|
-
when "text", "date", "number", "color", "email", "url"
|
|
23
|
+
when "text", "date", "number", "color", "email", "url", "array"
|
|
23
24
|
erb :input_field, locals: { field: field }
|
|
24
25
|
when "hidden"
|
|
25
26
|
erb :hidden_field, locals: { field: field }
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
when "image"
|
|
29
30
|
erb :image_field, locals: { field: field }
|
|
30
31
|
when "richtext"
|
|
31
|
-
erb :richtext_field, locals: { field: field }
|
|
32
|
+
erb :richtext_field, locals: { field: field, library: @library }
|
|
32
33
|
else
|
|
33
34
|
""
|
|
34
35
|
end
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
2
1
|
<svg fill="#000000" width="12px" height="12px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
3
2
|
<path fill-rule="evenodd" d="M5,2 L7,2 C7.55228475,2 8,2.44771525 8,3 C8,3.51283584 7.61395981,3.93550716 7.11662113,3.99327227 L7,4 L5,4 C4.48716416,4 4.06449284,4.38604019 4.00672773,4.88337887 L4,5 L4,19 C4,19.5128358 4.38604019,19.9355072 4.88337887,19.9932723 L5,20 L19,20 C19.5128358,20 19.9355072,19.6139598 19.9932723,19.1166211 L20,19 L20,17 C20,16.4477153 20.4477153,16 21,16 C21.5128358,16 21.9355072,16.3860402 21.9932723,16.8833789 L22,17 L22,19 C22,20.5976809 20.75108,21.9036609 19.1762728,21.9949073 L19,22 L5,22 C3.40231912,22 2.09633912,20.75108 2.00509269,19.1762728 L2,19 L2,5 C2,3.40231912 3.24891996,2.09633912 4.82372721,2.00509269 L5,2 L7,2 L5,2 Z M21,2 L21.081,2.003 L21.2007258,2.02024007 L21.2007258,2.02024007 L21.3121425,2.04973809 L21.3121425,2.04973809 L21.4232215,2.09367336 L21.5207088,2.14599545 L21.5207088,2.14599545 L21.6167501,2.21278596 L21.7071068,2.29289322 L21.7071068,2.29289322 L21.8036654,2.40469339 L21.8036654,2.40469339 L21.8753288,2.5159379 L21.9063462,2.57690085 L21.9063462,2.57690085 L21.9401141,2.65834962 L21.9401141,2.65834962 L21.9641549,2.73400703 L21.9641549,2.73400703 L21.9930928,2.8819045 L21.9930928,2.8819045 L22,3 L22,3 L22,9 C22,9.55228475 21.5522847,10 21,10 C20.4477153,10 20,9.55228475 20,9 L20,5.414 L13.7071068,11.7071068 C13.3466228,12.0675907 12.7793918,12.0953203 12.3871006,11.7902954 L12.2928932,11.7071068 C11.9324093,11.3466228 11.9046797,10.7793918 12.2097046,10.3871006 L12.2928932,10.2928932 L18.584,4 L15,4 C14.4477153,4 14,3.55228475 14,3 C14,2.44771525 14.4477153,2 15,2 L21,2 Z"/>
|
|
4
3
|
</svg>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<section class="card">
|
|
5
5
|
<p>You are currently logged in as <b><%= @username %></b>.</p>
|
|
6
6
|
<div class="controls --align-right">
|
|
7
|
-
<a href="<%=
|
|
7
|
+
<a href="<%= home_url %>">Back</a>
|
|
8
8
|
<form action="<%= change_password_url %>">
|
|
9
9
|
<button type="submit">Change password</button>
|
|
10
10
|
</form>
|
|
@@ -1,10 +1,96 @@
|
|
|
1
1
|
<link rel="stylesheet" type="text/css" href="https://unpkg.com/trix@2.0.8/dist/trix.css">
|
|
2
|
+
|
|
3
|
+
<style>
|
|
4
|
+
<%= erb :"trix.css" %>
|
|
5
|
+
</style>
|
|
6
|
+
|
|
2
7
|
<script type="text/javascript" src="https://unpkg.com/trix@2.0.8/dist/trix.umd.min.js"></script>
|
|
8
|
+
<script>
|
|
9
|
+
addEventListener("trix-before-initialize", function () {
|
|
10
|
+
Trix.config.attachments.preview.caption = false;
|
|
11
|
+
Trix.config.attachments.file.caption = false;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
addEventListener("trix-attachment-add", function (e) {
|
|
15
|
+
if (e.attachment.file) {
|
|
16
|
+
uploadFileAttachment(e.attachment);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
addEventListener("trix-attachment-remove", function (e) {
|
|
21
|
+
if (e.attachment.file) {
|
|
22
|
+
removeFile(e.attachment);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function uploadFileAttachment(attachment) {
|
|
27
|
+
uploadFile(attachment.file, setProgress, setAttributes);
|
|
28
|
+
|
|
29
|
+
function setProgress(progress) {
|
|
30
|
+
attachment.setUploadProgress(progress);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function setAttributes(attributes) {
|
|
34
|
+
attachment.setAttributes(attributes);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function uploadFile(file, progressCallback, successCallback) {
|
|
39
|
+
var formData = new FormData();
|
|
40
|
+
formData.append("key", createStorageKey(file));
|
|
41
|
+
formData.append("Content-Type", file.type);
|
|
42
|
+
formData.append("file", file);
|
|
43
|
+
|
|
44
|
+
var xhr = new XMLHttpRequest();
|
|
45
|
+
|
|
46
|
+
xhr.open("POST", "<%= library_attachment_url(library) %>", true);
|
|
47
|
+
|
|
48
|
+
xhr.upload.addEventListener("progress", function (e) {
|
|
49
|
+
var progress = e.loaded / e.total * 100;
|
|
50
|
+
progressCallback(progress);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
xhr.addEventListener("load", function (e) {
|
|
54
|
+
if (xhr.status == 200) {
|
|
55
|
+
var res = JSON.parse(xhr.response);
|
|
56
|
+
var attributes = {
|
|
57
|
+
url: res.url,
|
|
58
|
+
href: res.url + "?content-disposition=attachment"
|
|
59
|
+
};
|
|
60
|
+
successCallback(attributes);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
xhr.send(formData);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function removeFile(attachment) {
|
|
68
|
+
var url = attachment.getAttribute("url");
|
|
69
|
+
var parts = url.split("/");
|
|
70
|
+
var key = parts[parts.length - 1];
|
|
71
|
+
|
|
72
|
+
var formData = new FormData();
|
|
73
|
+
formData.append("key", key);
|
|
74
|
+
|
|
75
|
+
var xhr = new XMLHttpRequest();
|
|
76
|
+
|
|
77
|
+
xhr.open("POST", "<%= delete_library_attachment_url(library) %>", true);
|
|
78
|
+
xhr.send(formData);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function createStorageKey(file) {
|
|
82
|
+
var date = new Date();
|
|
83
|
+
var day = date.toISOString().slice(0, 10);
|
|
84
|
+
return date.getTime() + "-" + file.name;
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
3
88
|
<%= erb :field_label, locals: { field: field } %>
|
|
89
|
+
<%# TODO: label for attribute doesn't reference correct id %>
|
|
4
90
|
<input
|
|
5
91
|
id="richtext-content"
|
|
6
92
|
type="hidden"
|
|
7
93
|
name="<%= field[:id] %>"
|
|
8
|
-
value="<%= @item.field_value_or_default(field[:id]) %>"
|
|
94
|
+
value="<%= escape_for_trix(@item.field_value_or_default(field[:id])) %>"
|
|
9
95
|
>
|
|
10
|
-
<trix-editor input="richtext-content"></trix-editor>
|
|
96
|
+
<trix-editor class="trix-content" input="richtext-content"></trix-editor>
|
|
@@ -194,7 +194,7 @@ select {
|
|
|
194
194
|
input,
|
|
195
195
|
select,
|
|
196
196
|
trix-toolbar,
|
|
197
|
-
img
|
|
197
|
+
img.image-preview {
|
|
198
198
|
display: block;
|
|
199
199
|
margin-top: var(--padding-xs);
|
|
200
200
|
}
|
|
@@ -425,7 +425,6 @@ hr {
|
|
|
425
425
|
/* trix editor overrides */
|
|
426
426
|
|
|
427
427
|
trix-editor {
|
|
428
|
-
height: 16rem;
|
|
429
428
|
border: 1px solid var(--border-color) !important;
|
|
430
429
|
border-radius: var(--border-radius) !important;
|
|
431
430
|
box-shadow: var(--box-shadow-input);
|
|
@@ -446,3 +445,4 @@ trix-editor {
|
|
|
446
445
|
.trix-button.trix-active {
|
|
447
446
|
background: var(--accent-color-light) !important;
|
|
448
447
|
}
|
|
448
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
.trix-content {
|
|
2
|
+
line-height: 1.5; }
|
|
3
|
+
.trix-content * {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
margin: 0;
|
|
6
|
+
padding: 0; }
|
|
7
|
+
.trix-content h1 {
|
|
8
|
+
font-size: 1.2em;
|
|
9
|
+
line-height: 1.2; }
|
|
10
|
+
.trix-content blockquote {
|
|
11
|
+
border: 0 solid #ccc;
|
|
12
|
+
border-left-width: 0.3em;
|
|
13
|
+
margin-left: 0.3em;
|
|
14
|
+
padding-left: 0.6em; }
|
|
15
|
+
.trix-content [dir=rtl] blockquote,
|
|
16
|
+
.trix-content blockquote[dir=rtl] {
|
|
17
|
+
border-width: 0;
|
|
18
|
+
border-right-width: 0.3em;
|
|
19
|
+
margin-right: 0.3em;
|
|
20
|
+
padding-right: 0.6em; }
|
|
21
|
+
.trix-content li {
|
|
22
|
+
margin-left: 1em; }
|
|
23
|
+
.trix-content [dir=rtl] li {
|
|
24
|
+
margin-right: 1em; }
|
|
25
|
+
.trix-content pre {
|
|
26
|
+
display: inline-block;
|
|
27
|
+
width: 100%;
|
|
28
|
+
vertical-align: top;
|
|
29
|
+
font-family: monospace;
|
|
30
|
+
font-size: 0.9em;
|
|
31
|
+
padding: 0.5em;
|
|
32
|
+
white-space: pre;
|
|
33
|
+
background-color: #eee;
|
|
34
|
+
overflow-x: auto; }
|
|
35
|
+
.trix-content img {
|
|
36
|
+
max-width: 100%;
|
|
37
|
+
height: auto; }
|
|
38
|
+
.trix-content .attachment {
|
|
39
|
+
display: inline-block;
|
|
40
|
+
position: relative;
|
|
41
|
+
max-width: 100%; }
|
|
42
|
+
.trix-content .attachment a {
|
|
43
|
+
color: inherit;
|
|
44
|
+
text-decoration: none; }
|
|
45
|
+
.trix-content .attachment a:hover, .trix-content .attachment a:visited:hover {
|
|
46
|
+
color: inherit; }
|
|
47
|
+
.trix-content .attachment__caption {
|
|
48
|
+
text-align: center; }
|
|
49
|
+
.trix-content .attachment__caption .attachment__name + .attachment__size::before {
|
|
50
|
+
content: ' \2022 '; }
|
|
51
|
+
.trix-content .attachment--preview {
|
|
52
|
+
width: 50%;
|
|
53
|
+
text-align: center; }
|
|
54
|
+
.trix-content .attachment--preview .attachment__caption {
|
|
55
|
+
color: #666;
|
|
56
|
+
font-size: 0.9em;
|
|
57
|
+
line-height: 1.2; }
|
|
58
|
+
.trix-content .attachment--file {
|
|
59
|
+
color: #333;
|
|
60
|
+
line-height: 1;
|
|
61
|
+
margin: 0 2px 2px 2px;
|
|
62
|
+
padding: 0.4em 1em;
|
|
63
|
+
border: 1px solid #bbb;
|
|
64
|
+
border-radius: 5px; }
|
|
65
|
+
.trix-content .attachment-gallery {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-wrap: wrap;
|
|
68
|
+
position: relative; }
|
|
69
|
+
.trix-content .attachment-gallery .attachment {
|
|
70
|
+
flex: 1 0 33%;
|
|
71
|
+
padding: 0 0.5em;
|
|
72
|
+
max-width: 33%; }
|
|
73
|
+
.trix-content .attachment-gallery.attachment-gallery--2 .attachment, .trix-content .attachment-gallery.attachment-gallery--4 .attachment {
|
|
74
|
+
flex-basis: 50%;
|
|
75
|
+
max-width: 50%; }
|
data/lib/robin_cms.rb
CHANGED
|
@@ -23,10 +23,14 @@ module RobinCMS
|
|
|
23
23
|
|
|
24
24
|
class ConversionError < StandardError
|
|
25
25
|
end
|
|
26
|
+
|
|
27
|
+
class InvalidOperationError < StandardError
|
|
28
|
+
end
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
require_relative "robin_cms/version"
|
|
29
32
|
require_relative "robin_cms/configuration"
|
|
33
|
+
require_relative "robin_cms/configuration_schema"
|
|
30
34
|
require_relative "robin_cms/editable"
|
|
31
35
|
require_relative "robin_cms/sluggable"
|
|
32
36
|
require_relative "robin_cms/queryable"
|
|
@@ -34,6 +38,7 @@ require_relative "robin_cms/item"
|
|
|
34
38
|
require_relative "robin_cms/library"
|
|
35
39
|
require_relative "robin_cms/collection_library"
|
|
36
40
|
require_relative "robin_cms/data_library"
|
|
41
|
+
require_relative "robin_cms/singular_library"
|
|
37
42
|
require_relative "robin_cms/item"
|
|
38
43
|
require_relative "robin_cms/auth"
|
|
39
44
|
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aron Lebani
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-09-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bcrypt
|
|
@@ -80,8 +80,8 @@ files:
|
|
|
80
80
|
- lib/robin_cms/auth.rb
|
|
81
81
|
- lib/robin_cms/cms.rb
|
|
82
82
|
- lib/robin_cms/collection_library.rb
|
|
83
|
-
- lib/robin_cms/configuration-schema.json
|
|
84
83
|
- lib/robin_cms/configuration.rb
|
|
84
|
+
- lib/robin_cms/configuration_schema.rb
|
|
85
85
|
- lib/robin_cms/data_library.rb
|
|
86
86
|
- lib/robin_cms/editable.rb
|
|
87
87
|
- lib/robin_cms/flash.rb
|
|
@@ -89,6 +89,7 @@ files:
|
|
|
89
89
|
- lib/robin_cms/item.rb
|
|
90
90
|
- lib/robin_cms/library.rb
|
|
91
91
|
- lib/robin_cms/queryable.rb
|
|
92
|
+
- lib/robin_cms/singular_library.rb
|
|
92
93
|
- lib/robin_cms/sluggable.rb
|
|
93
94
|
- lib/robin_cms/version.rb
|
|
94
95
|
- lib/robin_cms/views/change_password.erb
|
|
@@ -112,6 +113,7 @@ files:
|
|
|
112
113
|
- lib/robin_cms/views/richtext_field.erb
|
|
113
114
|
- lib/robin_cms/views/select_field.erb
|
|
114
115
|
- lib/robin_cms/views/stylesheet.css.erb
|
|
116
|
+
- lib/robin_cms/views/trix.css.erb
|
|
115
117
|
homepage: https://robincms.org
|
|
116
118
|
licenses:
|
|
117
119
|
- MIT
|
|
@@ -133,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
133
135
|
- !ruby/object:Gem::Version
|
|
134
136
|
version: '0'
|
|
135
137
|
requirements: []
|
|
136
|
-
rubygems_version: 3.5.
|
|
138
|
+
rubygems_version: 3.5.22
|
|
137
139
|
signing_key:
|
|
138
140
|
specification_version: 4
|
|
139
141
|
summary: A simple, flat-file CMS for Static Site Generators.
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "http://json-schema.org/draft-06/schema#",
|
|
3
|
-
"title": "RobinCMS configuration schema",
|
|
4
|
-
"type": "object",
|
|
5
|
-
"required": ["url", "libraries"],
|
|
6
|
-
"properties": {
|
|
7
|
-
"title": { "type": "string" },
|
|
8
|
-
"url": { "type": "string" },
|
|
9
|
-
"build_command": { "type": "string" },
|
|
10
|
-
"accent_color": {
|
|
11
|
-
"type": "string",
|
|
12
|
-
"pattern": "^\#{1}[a-fA-F0-9]{6}"
|
|
13
|
-
},
|
|
14
|
-
"libraries": {
|
|
15
|
-
"type": "array",
|
|
16
|
-
"items": {
|
|
17
|
-
"anyOf": [
|
|
18
|
-
{ "$ref": "#/$defs/library_schema" },
|
|
19
|
-
{ "$ref": "#/$defs/data_schema" }
|
|
20
|
-
]
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"$defs": {
|
|
25
|
-
"options_schema": {
|
|
26
|
-
"type": "array",
|
|
27
|
-
"items": {
|
|
28
|
-
"type": "object",
|
|
29
|
-
"required": ["label", "value"],
|
|
30
|
-
"properties": {
|
|
31
|
-
"label": { "type": "string" },
|
|
32
|
-
"value": { "type": "string" }
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
"image_dimensions_schema": { "type": "string", "pattern": "^[0-9]+x[0-9]+$" },
|
|
37
|
-
"image_filetype_schema": { "type": "string", "enum": ["png", "jpg"] },
|
|
38
|
-
"library_schema": {
|
|
39
|
-
"type": "object",
|
|
40
|
-
"required": ["id", "label", "label_singular"],
|
|
41
|
-
"properties": {
|
|
42
|
-
"id": { "type": "string" },
|
|
43
|
-
"type": { "type": "string", "const": "collection" },
|
|
44
|
-
"filetype": { "type": "string", "enum": ["html"] },
|
|
45
|
-
"label": { "type": "string" },
|
|
46
|
-
"label_singular": { "type": "string" },
|
|
47
|
-
"location": { "type": "string" },
|
|
48
|
-
"static_location": { "type": "string" },
|
|
49
|
-
"description": { "type": "string" },
|
|
50
|
-
"can_create": { "type": "boolean" },
|
|
51
|
-
"can_delete": { "type": "boolean" },
|
|
52
|
-
"pattern": { "type": "string" },
|
|
53
|
-
"display_name_pattern": { "type": "string" },
|
|
54
|
-
"fields": {
|
|
55
|
-
"type": "array",
|
|
56
|
-
"items": {
|
|
57
|
-
"type": "object",
|
|
58
|
-
"required": ["id", "label"],
|
|
59
|
-
"properties": {
|
|
60
|
-
"id": { "type": "string" },
|
|
61
|
-
"label": { "type": "string" },
|
|
62
|
-
"type": {
|
|
63
|
-
"type": "string",
|
|
64
|
-
"enum": ["text", "richtext", "date", "hidden", "number", "color", "email", "url", "select", "image"]
|
|
65
|
-
},
|
|
66
|
-
"default": { "type": "string" },
|
|
67
|
-
"required": { "type": "boolean" },
|
|
68
|
-
"readonly": { "type": "boolean" },
|
|
69
|
-
"options": { "$ref": "#/$defs/options_schema" },
|
|
70
|
-
"dimensions": { "$ref": "#/$defs/image_dimensions_schema" },
|
|
71
|
-
"filetype": { "$ref": "#/$defs/image_filetype_schema" },
|
|
72
|
-
"description": { "type": "string" },
|
|
73
|
-
"order": { "type": "number" }
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
"data_schema": {
|
|
80
|
-
"type": "object",
|
|
81
|
-
"required": ["id", "label", "label_singular"],
|
|
82
|
-
"properties": {
|
|
83
|
-
"id": { "type": "string" },
|
|
84
|
-
"type": { "type": "string", "const": "data" },
|
|
85
|
-
"filetype": { "type": "string", "enum": ["yml"] },
|
|
86
|
-
"label": { "type": "string" },
|
|
87
|
-
"label_singular": { "type": "string" },
|
|
88
|
-
"location": { "type": "string" },
|
|
89
|
-
"static_location": { "type": "string" },
|
|
90
|
-
"description": { "type": "string" },
|
|
91
|
-
"can_create": { "type": "boolean" },
|
|
92
|
-
"can_delete": { "type": "boolean" },
|
|
93
|
-
"pattern": { "type": "string" },
|
|
94
|
-
"display_name_pattern": { "type": "string" },
|
|
95
|
-
"fields": {
|
|
96
|
-
"type": "array",
|
|
97
|
-
"items": {
|
|
98
|
-
"type": "object",
|
|
99
|
-
"required": ["id", "label"],
|
|
100
|
-
"properties": {
|
|
101
|
-
"id": { "type": "string" },
|
|
102
|
-
"label": { "type": "string" },
|
|
103
|
-
"type": {
|
|
104
|
-
"type": "string",
|
|
105
|
-
"enum": ["text", "date", "hidden", "number", "color", "email", "url", "select", "image"]
|
|
106
|
-
},
|
|
107
|
-
"default": { "type": "string" },
|
|
108
|
-
"required": { "type": "boolean" },
|
|
109
|
-
"readonly": { "type": "boolean" },
|
|
110
|
-
"options": { "$ref": "#/$defs/options_schema" },
|
|
111
|
-
"dimensions": { "$ref": "#/$defs/image_dimensions_schema" },
|
|
112
|
-
"filetype": { "$ref": "#/$defs/image_filetype_schema" },
|
|
113
|
-
"description": { "type": "string" }
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|