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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68c3b702cf90bbb615ebeea6303a3f12e4e5da64b3360b295a198b9347ea11bb
4
- data.tar.gz: d266f45a2863ee190014bcd3174f0413fb70a5f745aaef74780f0a9ca7d6b3eb
3
+ metadata.gz: 26f13be1b3e97bce69e705daf56e969916467591b7c30b2877c2ce29e07e62d3
4
+ data.tar.gz: 7f34fbee635184285025e757e3c52ef8e25467255ed4a90647203bbfcd656756
5
5
  SHA512:
6
- metadata.gz: 12d3f8064c84a26a3e3899ca964e411609ca03de3c193466aab157530d1022ffa0ae45237545260ad9a35377e07ff5cb09325c00c682cfd790247c1879b6dde4
7
- data.tar.gz: d73739c5b1b1c83b4fc7f3d80363353318a0b69164fba07ad784a738d8e5c23c8cea6c73c1d55542279c6a38929dc65ffc673870669781cf9df2a631cb21bebd
6
+ metadata.gz: a420e205f0f06d34817b5cb1424f513051cddb7a665dcddb9d707c091088969e7031bef154542347691fe109a612f58741c229f622380281e4289a2bcfa1a268
7
+ data.tar.gz: 7d1e1bbd2b23d02c02a7dcca004c35015663bb2ece865f7d4ebfabbcd96e2897bc95bd758bd64145cb70d5ef2f7819717d195417f7121254080773b1f70a9f43
data/README.md CHANGED
@@ -72,7 +72,8 @@ standalone CMS for any Static Site Generator, you can embed it in a dynamic
72
72
  Sinatra app, or you can use it as a Jekyll plugin. If you're already using
73
73
  Jekyll as your SSG, this is the simplest approach.
74
74
 
75
- See the [documentation](./docs/index.adoc) for further information on usage.
75
+ See the [documentation](https://robincms.org/docs.html) for further information
76
+ on usage.
76
77
 
77
78
  ## Configuring
78
79
 
@@ -100,8 +101,8 @@ schema for your content. An example YAML file looks like this:
100
101
  - { label: Title, id: title, type: input }
101
102
  - { label: Author, id: author_name, type: input }
102
103
 
103
- See the [docs](./docs/index.adoc) for more details on configuration. You can
104
- also find some examples [here](./examples).
104
+ See the [docs](https://robincms.org/docs.html) for more details on
105
+ configuration. You can also find some examples [here](./examples).
105
106
 
106
107
  ## Testing
107
108
 
@@ -109,6 +110,10 @@ Unit test are written in RSpec. To run them:
109
110
 
110
111
  rake test
111
112
 
113
+ ## Screenshot
114
+
115
+ ![Robin CMS screenshot](./assets/screenshot.png)
116
+
112
117
  ## Roadmap
113
118
 
114
119
  * [ ] Support markdown content
data/lib/robin_cms/cms.rb CHANGED
@@ -37,7 +37,7 @@ module RobinCMS
37
37
  end
38
38
 
39
39
  get "/" do
40
- redirect to(root_url)
40
+ redirect to(home)
41
41
  end
42
42
 
43
43
  get "/login" do
@@ -47,7 +47,7 @@ module RobinCMS
47
47
  post "/login" do
48
48
  authenticate!(params[:username], params[:password])
49
49
  session[:auth_user] = params[:username]
50
- redirect to(root_url)
50
+ redirect to(home)
51
51
  rescue AuthenticationError
52
52
  flash[:error] = "Incorrect username or password"
53
53
  redirect to("/login")
@@ -90,6 +90,11 @@ module RobinCMS
90
90
  published: params[:published],
91
91
  q: params[:q]
92
92
  )
93
+
94
+ if @library.singular?
95
+ redirect to("/libraries/#{params[:kind]}/item/0")
96
+ end
97
+
93
98
  erb :library
94
99
  end
95
100
 
@@ -154,6 +159,21 @@ module RobinCMS
154
159
  redirect to("/libraries/#{params[:kind]}")
155
160
  end
156
161
 
162
+ # Handle uploading attachments from the trix editor. Responds with the path
163
+ # of the uploaded file in JSON format.
164
+ post "/libraries/:kind/attachment" do
165
+ static_path = @library.create_static(
166
+ params[:key],
167
+ params[:file][:tempfile].to_path
168
+ )
169
+
170
+ { :url => "/" + static_path }.to_json
171
+ end
172
+
173
+ post "/libraries/:kind/attachment/delete" do
174
+ @library.delete_static(params[:key])
175
+ end
176
+
157
177
  post "/publish" do
158
178
  system(@config[:build_command])
159
179
  if $?.exitstatus == 0
@@ -162,27 +182,21 @@ module RobinCMS
162
182
  flash[:error_dialog] = "There was an error publishing the site."
163
183
  end
164
184
 
165
- redirect to(root_url)
185
+ redirect to(home)
166
186
  end
167
187
 
168
- not_found do
169
- # This is a hack for showing previews of images uploaded in libraries.
170
- # It is needed because until you publish the site, the image is not
171
- # copied to the site output, and therefore requests for the file will
172
- # return a 404. Note that if using it as a Jekyll plugin, this is not
173
- # needed as the files will be served by Jekyll's dev server.
174
-
175
- # Need to use `PATH_INFO` for the same reason we need to use the `url`
176
- # and `to` helpers - we want the request URI relative to the path that
177
- # the app is mounted at i.e. the virtual location.
178
- uri = env["PATH_INFO"]
179
-
180
- library = @config[:libraries].find do |c|
181
- uri.start_with?("/#{c[:static_location]}")
182
- end
188
+ # Explicitly serve static files for previewing in the CMS. This is
189
+ # necessary because until you publish the site, static files are not copied
190
+ # to the site output, and therefore requests for the file will return a
191
+ # 404. Note that if using it as a Jekyll plugin, this is not needed as the
192
+ # files will be served by Jekyll's dev server.
193
+ #
194
+ # This route needs to be defined last since it catches a wide range of URLs.
195
+ get "/:static_location/:key" do
196
+ static_locations = @config[:libraries].map { |l| l[:static_location] }.uniq
183
197
 
184
- if library
185
- send_file(File.join(library[:static_location], File.basename(uri)))
198
+ if static_locations.include?(params[:static_location])
199
+ send_file(File.join(params[:static_location], params[:key]))
186
200
  else
187
201
  pass
188
202
  end
@@ -10,9 +10,10 @@ module RobinCMS
10
10
 
11
11
  COLLECTION_DEFAULTS = {
12
12
  type: "collection",
13
- location: ".",
14
- static_location: "assets",
13
+ location: "_posts",
14
+ static_location: "uploads",
15
15
  filetype: "html",
16
+ pattern: ":year-:month-:day-:title",
16
17
  description: "",
17
18
  can_create: true,
18
19
  can_delete: true,
@@ -21,8 +22,8 @@ module RobinCMS
21
22
 
22
23
  DATA_DEFAULTS = {
23
24
  type: "data",
24
- location: ".",
25
- static_location: "assets",
25
+ location: "_data",
26
+ static_location: "uploads",
26
27
  filetype: "yml",
27
28
  description: "",
28
29
  can_create: true,
@@ -30,6 +31,17 @@ module RobinCMS
30
31
  fields: []
31
32
  }.freeze
32
33
 
34
+ SINGULAR_DEFAULTS = {
35
+ type: "singular",
36
+ location: "_data",
37
+ static_location: "uploads",
38
+ filetype: "yml",
39
+ description: "",
40
+ can_create: false,
41
+ can_delete: false,
42
+ fields: []
43
+ }.freeze
44
+
33
45
  FIELD_DEFAULTS = {
34
46
  type: "text",
35
47
  default: nil,
@@ -54,11 +66,10 @@ module RobinCMS
54
66
  type: "hidden"
55
67
  }].freeze
56
68
 
57
- AUTOMATIC_DRAFT_FIELDS = [{
69
+ AUTOMATIC_COLLECTION_FIELDS = [{
58
70
  label: "Published",
59
71
  id: "published",
60
72
  type: "select",
61
- default: false,
62
73
  options: [{
63
74
  label: "Draft",
64
75
  value: false
@@ -72,6 +83,14 @@ module RobinCMS
72
83
  order: 2
73
84
  }].freeze
74
85
 
86
+ AUTOMATIC_SINGULAR_FIELDS = [{
87
+ label: "Title",
88
+ id: "title",
89
+ type: "hidden",
90
+ required: true,
91
+ order: 1
92
+ }]
93
+
75
94
  AUTOMATIC_IMAGE_FIELDS = [{
76
95
  id: "image_src",
77
96
  type: "hidden"
@@ -94,9 +113,13 @@ module RobinCMS
94
113
  YAML.load_file(config_file, symbolize_names: true)
95
114
  end
96
115
 
97
- schema_file = File.join(__dir__, "configuration-schema.json")
98
- schema = JSON.parse(File.read(schema_file))
99
- JSON::Validator.validate!(schema, data)
116
+ schema = ConfigurationSchema.schema
117
+ errors = JSON::Validator.fully_validate(schema, data)
118
+
119
+ unless errors.empty?
120
+ puts errors
121
+ raise ValidationError, "There is an error in your configuration file"
122
+ end
100
123
 
101
124
  config = from_h(DEFAULTS).merge_config(data).merge_automatic_fields
102
125
  config.validate!
@@ -115,15 +138,24 @@ module RobinCMS
115
138
  library[:type].nil? || library[:type] == "collection"
116
139
  end
117
140
 
118
- def has_image?(library)
119
- library[:fields].find { |f| f[:type] == "image" }
141
+ def data?(library)
142
+ library[:type] == "data"
143
+ end
144
+
145
+ def singular?(library)
146
+ library[:type] == "singular"
120
147
  end
121
148
 
122
149
  def merge_config(config)
123
150
  merge(config, {
124
151
  libraries: config[:libraries].map do |library|
125
- library_defaults =
126
- collection?(library) ? COLLECTION_DEFAULTS : DATA_DEFAULTS
152
+ library_defaults = if collection?(library)
153
+ COLLECTION_DEFAULTS
154
+ elsif data?(library)
155
+ DATA_DEFAULTS
156
+ elsif singular?(library)
157
+ SINGULAR_DEFAULTS
158
+ end
127
159
 
128
160
  library_defaults.merge(library, {
129
161
  fields: library[:fields].map do |field|
@@ -141,11 +173,22 @@ module RobinCMS
141
173
  library[:fields].append(*AUTOMATIC_FIELDS)
142
174
 
143
175
  if collection?(library)
144
- library[:fields].append(*AUTOMATIC_DRAFT_FIELDS)
176
+ library[:fields].append(*AUTOMATIC_COLLECTION_FIELDS)
177
+ end
178
+
179
+ # Replace the automatic title field with a hidden one. The title is not
180
+ # used for singular fields, but every item is required to have one, so
181
+ # hide it in the UI.
182
+ if singular?(library)
183
+ # Using prepend ensures this one stays when we call uniq! below.
184
+ library[:fields].prepend(*AUTOMATIC_SINGULAR_FIELDS)
145
185
  end
146
186
 
147
- if has_image?(library)
148
- library[:fields].append(*AUTOMATIC_IMAGE_FIELDS)
187
+ # Ensure the automatic image fields are inserted directly after the
188
+ # declared image field.
189
+ image_index = library[:fields].find_index { |f| f[:type] == "image" }
190
+ if image_index
191
+ library[:fields].insert(image_index + 1, *AUTOMATIC_IMAGE_FIELDS)
149
192
  end
150
193
 
151
194
  library[:fields].uniq! { |f| f[:id] }
@@ -156,6 +199,8 @@ module RobinCMS
156
199
 
157
200
  # This method provides custom validation which can't be expressed (or is
158
201
  # too difficult to express) using the Json Schema standard.
202
+ #
203
+ # TODO: validate that options are given if the field type is select.
159
204
  def validate!
160
205
  self[:libraries].each do |library|
161
206
  next if library[:fields].nil?
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal => true
2
+
3
+ module RobinCMS
4
+ module ConfigurationSchema
5
+ def self.field_schema(kind)
6
+ field_types = [
7
+ "text",
8
+ "date",
9
+ "hidden",
10
+ "number",
11
+ "color",
12
+ "email",
13
+ "url",
14
+ "select",
15
+ "image"
16
+ ]
17
+
18
+ if kind == "collection"
19
+ field_types << "richtext"
20
+ field_types << "array"
21
+ end
22
+
23
+ {
24
+ "type" => "object",
25
+ "required" => ["id", "label"],
26
+ "properties" => {
27
+ "id" => { "type" => "string" },
28
+ "label" => { "type" => "string" },
29
+ "type" => { "type" => "string", "enum" => field_types },
30
+ "default" => { "type" => "string" },
31
+ "required" => { "type" => "boolean" },
32
+ "readonly" => { "type" => "boolean" },
33
+ "option" => {
34
+ "type" => "array",
35
+ "items" => {
36
+ "type" => "object",
37
+ "required" => ["label", "value"],
38
+ "properties" => {
39
+ "label" => { "type" => "string" },
40
+ "value" => { "type" => "string" }
41
+ }
42
+ }
43
+ },
44
+ "dimensions" => { "type" => "string", "pattern" => "^[0-9]+x[0-9]+$" },
45
+ "filetype" => { "type" => "string", "enum" => ["png", "jpg"] },
46
+ "description" => { "type" => "string" },
47
+ "order" => { "type" => "number" }
48
+ }
49
+ }
50
+ end
51
+
52
+ def self.library_schema(kind)
53
+ filetypes = case kind
54
+ when "collection"
55
+ ["html"]
56
+ when "data", "singular"
57
+ ["yml"]
58
+ end
59
+
60
+ {
61
+ "type" => "object",
62
+ "required" => ["id", "label", "label_singular"],
63
+ "properties" => {
64
+ "id" => { "type" => "string" },
65
+ "type" => { "type" => "string", "const" => kind },
66
+ "filetype" => { "type" => "string", "enum" => filetypes },
67
+ "label" => { "type" => "string" },
68
+ "label_singular" => { "type" => "string" },
69
+ "location" => { "type" => "string" },
70
+ "static_location" => { "type" => "string" },
71
+ "description" => { "type" => "string" },
72
+ "can_create" => { "type" => "boolean" },
73
+ "can_delete" => { "type" => "boolean" },
74
+ "pattern" => { "type" => "string" },
75
+ "display_name_pattern" => { "type" => "string" },
76
+ "fields" => { "type" => "array", "items" => field_schema(kind) }
77
+ }
78
+ }
79
+ end
80
+
81
+ def self.schema
82
+ {
83
+ "$schema" => "http://json-schema.org/draft-06/schema#",
84
+ "title" => "RobinCMS configuration schema",
85
+ "type" => "object",
86
+ "required" => ["url", "libraries"],
87
+ "properties" => {
88
+ "title" => { "type" => "string" },
89
+ "url" => { "type" => "string" },
90
+ "build_command" => { "type" => "string" },
91
+ "accent_color" => {
92
+ "type" => "string",
93
+ "pattern" => '^\\#{1}[a-fA-F0-9]{6}'
94
+ },
95
+ "libraries" => {
96
+ "type" => "array",
97
+ "items" => {
98
+ "anyOf" => [
99
+ library_schema("collection"),
100
+ library_schema("data"),
101
+ library_schema("singular")
102
+ ]
103
+ }
104
+ }
105
+ }
106
+ }
107
+ end
108
+ end
109
+ end
@@ -14,10 +14,11 @@ module RobinCMS
14
14
  def all
15
15
  return [] unless File.exist?(filepath)
16
16
 
17
- items = YAML.load_file(filepath, symbolize_names: true)
17
+ items = YAML.load_file(filepath, symbolize_names: true, permitted_classes: [Date])
18
18
 
19
19
  return [] unless items
20
20
 
21
+ # Use the index of the item as it's id.
21
22
  items.each_with_index.map do |attrs, i|
22
23
  Item.new(i, attrs, **@schema)
23
24
  end
@@ -29,11 +30,17 @@ module RobinCMS
29
30
 
30
31
  item.update_timestamps!
31
32
 
33
+ # The item's id is it's index in the YAML array. If the id is nil, we are
34
+ # creating a new item; append it to the end of the array. If the id is
35
+ # not nil, we are editing an item; swap out the item at the index
36
+ # corresponding to it's id.
32
37
  all_items = YAML.load_file(filepath) || []
33
38
  if item.id.nil?
34
- all_items.append(item.frontmatter)
39
+ # Data items don't have a published state.
40
+ all_items.append(item.frontmatter(ignore_published: true))
35
41
  else
36
- all_items[item.id.to_i] = item.frontmatter
42
+ # As above.
43
+ all_items[item.id.to_i] = item.frontmatter(ignore_published: true)
37
44
  end
38
45
 
39
46
  serialized = YAML.dump(all_items, stringify_names: true)
@@ -37,44 +37,64 @@ module RobinCMS
37
37
  URI.decode_www_form(request.query_string).to_h
38
38
  end
39
39
 
40
+ def home
41
+ "/libraries/#{@config[:libraries].first[:id]}"
42
+ end
43
+
44
+ def path(addr)
45
+ url(addr, absolute = false)
46
+ end
47
+
40
48
  def root_url
41
- url("/libraries/#{@config[:libraries].first[:id]}")
49
+ path("/")
50
+ end
51
+
52
+ def home_url
53
+ path(home)
42
54
  end
43
55
 
44
56
  def profile_url
45
- url("/profile")
57
+ path("/profile")
46
58
  end
47
59
 
48
60
  def logout_url
49
- url("/logout")
61
+ path("/logout")
50
62
  end
51
63
 
52
64
  def change_password_url
53
- url("/change-password")
65
+ path("/change-password")
54
66
  end
55
67
 
56
68
  def library_url(library)
57
- url("/libraries/#{library[:id]}")
69
+ path("/libraries/#{library[:id]}")
58
70
  end
59
71
 
60
72
  def library_item_url(library, item = nil)
61
73
  if item
62
- url("/libraries/#{library[:id]}/item/#{item.id}")
74
+ path("/libraries/#{library[:id]}/item/#{item.id}")
63
75
  else
64
- url("/libraries/#{library[:id]}/item")
76
+ path("/libraries/#{library[:id]}/item")
65
77
  end
66
78
  end
67
79
 
68
80
  def delete_library_item_url(library, item)
69
- url("/libraries/#{library[:id]}/item/#{item.id}/delete")
81
+ path("/libraries/#{library[:id]}/item/#{item.id}/delete")
82
+ end
83
+
84
+ def library_attachment_url(library)
85
+ path("/libraries/#{library[:id]}/attachment")
86
+ end
87
+
88
+ def delete_library_attachment_url(library)
89
+ path("/libraries/#{library[:id]}/attachment/delete")
70
90
  end
71
91
 
72
92
  def item_image_url(item)
73
- url(item.image_src)
93
+ path(item.image_src)
74
94
  end
75
95
 
76
96
  def publish_url
77
- url("/publish")
97
+ path("/publish")
78
98
  end
79
99
 
80
100
  def current_page?(library)
@@ -90,6 +110,22 @@ module RobinCMS
90
110
  "__#{prefix}_#{id}"
91
111
  end
92
112
 
113
+ # The attachments for Trix need to be served from the admin site root
114
+ # (usually "/admin") in order for the catch-all route handler to be able
115
+ # to serve them.
116
+ #
117
+ # TODO: You can set a "preview URL" for Trix (see
118
+ # https://github.com/basecamp/trix#previewing-attached-files). But I
119
+ # couldn't get it to work.
120
+ def escape_for_trix(value)
121
+ value = value
122
+ .sub("&quot;href&quot;:&quot;/uploads", "&quot;href&quot;:&quot;#{root_url}uploads")
123
+ .sub("&quot;url&quot;:&quot;/uploads", "&quot;url&quot;:&quot;#{root_url}uploads")
124
+ .sub("href=\"/uploads", "href=\"#{root_url}uploads")
125
+ .sub("url=\"/uploads", "url=\"#{root_url}uploads")
126
+ CGI::escapeHTML(value)
127
+ end
128
+
93
129
  def flash
94
130
  @flash ||= Flash.new(session ? session[:flash] : {})
95
131
  end
@@ -1,8 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RobinCMS
4
- class Item
5
- attr_reader :id, :attributes, :schema
4
+ # The in-memory data model for all items.
5
+ #
6
+ # The Item objects model the domain, while the Library objects model disk
7
+ # IO/persistence. Since the distinction between Data and Collection items is
8
+ # purely a distinction of how they are written to disk, this is abstracted
9
+ # entirely by the Library objects and therefore Item's don't care what type
10
+ # they are.
11
+ #
12
+ # There are two boundaries for serialization/deserialization of items. One is
13
+ # to/from the file used to store the data on disk. This happens in the
14
+ # find_one, all, and write methods of the Library classes. The other is
15
+ # to/from the html representation of the item. This happens in the
16
+ # attributes= and field_value_or_default methods in this class.
17
+ class Item attr_reader :id, :attributes, :schema
6
18
 
7
19
  DATETIME_FORMAT = "%Y-%m-%d"
8
20
 
@@ -17,15 +29,49 @@ module RobinCMS
17
29
  @id = id
18
30
  @schema = schema
19
31
 
20
- # Be sure to use the setter here so the keys get converted to symbols.
32
+ # Be sure to use the setter here.
21
33
  self.attributes = attrs
22
34
  end
23
35
 
24
36
  def attributes=(attributes)
25
37
  @attributes = attributes.to_h.transform_keys(&:to_sym)
26
38
 
27
- if attributes.has_key?(:published)
28
- @attributes[:published] = attributes[:published].to_s == "true"
39
+ # Parse array fields.
40
+ @schema[:fields].each do |f|
41
+ next unless f[:type] == "array"
42
+
43
+ field_id = f[:id].to_sym
44
+ if @attributes[field_id] && !@attributes[field_id].is_a?(Array)
45
+ @attributes[field_id] = @attributes[field_id].split(",").map(&:strip)
46
+ end
47
+ end
48
+
49
+ # Parse published status. The logic here is a bit weird, but basically an
50
+ # item without a 'published' key is considered published. Draft times are
51
+ # marked with 'published: false'. This is in line with how it works in
52
+ # Jekyll.
53
+ if blank?
54
+ @attributes[:published] = false # New items always default to draft.
55
+ elsif attributes.has_key?(:published)
56
+ @attributes[:published] = attributes[:published].to_s != "false"
57
+ else
58
+ @attributes[:published] = true
59
+ end
60
+ end
61
+
62
+ def field_value_or_default(field_id)
63
+ field = @schema[:fields].find { |f| f[:id] == field_id }
64
+ value = @attributes[field[:id].to_sym]
65
+
66
+ # Explicitly check for nil here because a field might have a value of
67
+ # false.
68
+ value = field[:default] if value.nil?
69
+
70
+ case field[:type]
71
+ when "array"
72
+ value&.join(",")
73
+ else
74
+ value.to_s
29
75
  end
30
76
  end
31
77
 
@@ -34,11 +80,11 @@ module RobinCMS
34
80
  end
35
81
 
36
82
  def published?
37
- if @attributes.has_key?(:published)
38
- @attributes[:published]
39
- else
40
- true
41
- end
83
+ @attributes[:published]
84
+ end
85
+
86
+ def blank?
87
+ @id.nil?
42
88
  end
43
89
 
44
90
  def published_label
@@ -73,8 +119,8 @@ module RobinCMS
73
119
  def display_name
74
120
  return @attributes[:title] unless @schema[:display_name_pattern]
75
121
 
76
- @schema[:display_name_pattern].clone.tap do |name|
77
- @attributes.each { |key, value| name.gsub!(":#{key}", value) }
122
+ @schema[:display_name_pattern].dup.tap do |name|
123
+ @attributes.each { |key, value| name.gsub!(":#{key}", value.to_s) }
78
124
  end
79
125
  end
80
126
 
@@ -86,12 +132,17 @@ module RobinCMS
86
132
  @attributes[:image_alt]
87
133
  end
88
134
 
135
+ def can_delete?
136
+ @id && @schema[:can_delete]
137
+ end
138
+
89
139
  # Get the frontmatter as a hash with stringified keys.
90
- def frontmatter
140
+ def frontmatter(ignore_published: false)
91
141
  frontmatter = @attributes.clone
92
142
  FRONTMATTER_IGNORE_KEYS.each { |key| frontmatter.delete(key) }
93
143
 
94
- if published?
144
+ # See comment in the attributes= method.
145
+ if ignore_published || published?
95
146
  frontmatter.delete(:published)
96
147
  else
97
148
  frontmatter[:published] = false
@@ -103,15 +154,5 @@ module RobinCMS
103
154
  # to support earlier versions of Ruby.
104
155
  frontmatter.to_h.transform_keys(&:to_s)
105
156
  end
106
-
107
- def field_value_or_default(field_id)
108
- field = @schema[:fields].find { |f| f[:id] == field_id }
109
- value = @attributes[field[:id].to_sym] || field[:default]
110
- value.to_s
111
- end
112
-
113
- def can_delete?
114
- @id && @schema[:can_delete]
115
- end
116
157
  end
117
158
  end