robin_cms 0.1.2 → 0.1.4
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 +4 -0
- data/lib/robin_cms/cms.rb +56 -59
- data/lib/robin_cms/collection_library.rb +70 -0
- data/lib/robin_cms/configuration-schema.json +2 -0
- data/lib/robin_cms/configuration.rb +2 -16
- data/lib/robin_cms/data_library.rb +58 -0
- data/lib/robin_cms/editable.rb +40 -0
- data/lib/robin_cms/item.rb +89 -19
- data/lib/robin_cms/library.rb +94 -0
- data/lib/robin_cms/queryable.rb +9 -7
- data/lib/robin_cms/version.rb +1 -1
- data/lib/robin_cms/views/delete_dialog.erb +1 -1
- data/lib/robin_cms/views/filter_form.erb +2 -2
- data/lib/robin_cms/views/hidden_field.erb +1 -1
- data/lib/robin_cms/views/input_field.erb +1 -1
- data/lib/robin_cms/views/layout.erb +6 -3
- data/lib/robin_cms/views/library.erb +9 -9
- data/lib/robin_cms/views/library_actions.erb +4 -1
- data/lib/robin_cms/views/library_item.erb +11 -16
- data/lib/robin_cms/views/richtext_field.erb +1 -1
- data/lib/robin_cms/views/select_field.erb +2 -2
- data/lib/robin_cms/views/stylesheet.css.erb +443 -0
- data/lib/robin_cms.rb +6 -5
- metadata +9 -9
- data/lib/robin_cms/collection_item.rb +0 -82
- data/lib/robin_cms/data_item.rb +0 -87
- data/lib/robin_cms/itemable.rb +0 -124
- data/lib/robin_cms/static_item.rb +0 -92
- data/lib/robin_cms/views/style.erb +0 -441
- /data/lib/robin_cms/views/{logo.erb → logo.svg.erb} +0 -0
- /data/lib/robin_cms/views/{new_tab.erb → new_tab.svg.erb} +0 -0
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.
|
|
4
|
+
version: 0.1.4
|
|
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-
|
|
11
|
+
date: 2025-10-17 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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
data/lib/robin_cms/data_item.rb
DELETED
|
@@ -1,87 +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
|
-
YAML.load_file(filepath) || []
|
|
72
|
-
else
|
|
73
|
-
[]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
if @mark_for_deletion
|
|
77
|
-
items[@id.to_i] = nil
|
|
78
|
-
elsif @id.nil?
|
|
79
|
-
items.append(fm)
|
|
80
|
-
else
|
|
81
|
-
items[@id.to_i] = fm
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
YAML.dump(items.compact, stringify_names: true)
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
data/lib/robin_cms/itemable.rb
DELETED
|
@@ -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 = [:id, :kind, :content, :image, :captures].freeze
|
|
11
|
-
|
|
12
|
-
def initialize(id, library, attrs = {})
|
|
13
|
-
[: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
|