scribo 1.0.38

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.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +93 -0
  4. data/Rakefile +40 -0
  5. data/app/assets/config/scribo_manifest.js +2 -0
  6. data/app/assets/javascripts/scribo/scribo.js +1 -0
  7. data/app/assets/stylesheets/scribo/scribo.css +1 -0
  8. data/app/controllers/concerns/maintenance_standards.rb +24 -0
  9. data/app/controllers/scribo/admin/sites/contents_controller.rb +86 -0
  10. data/app/controllers/scribo/admin/sites_controller.rb +61 -0
  11. data/app/controllers/scribo/api/sites_controller.rb +23 -0
  12. data/app/controllers/scribo/application_admin_controller.rb +11 -0
  13. data/app/controllers/scribo/application_controller.rb +6 -0
  14. data/app/controllers/scribo/contents_controller.rb +20 -0
  15. data/app/drops/scribo/action_dispatch/request_drop.rb +15 -0
  16. data/app/drops/scribo/active_model/errors_drop.rb +16 -0
  17. data/app/drops/scribo/application_drop.rb +9 -0
  18. data/app/drops/scribo/array_drop.rb +13 -0
  19. data/app/drops/scribo/content_drop.rb +73 -0
  20. data/app/drops/scribo/data_drop.rb +35 -0
  21. data/app/drops/scribo/form_drop.rb +26 -0
  22. data/app/drops/scribo/include_drop.rb +13 -0
  23. data/app/drops/scribo/paginator_drop.rb +73 -0
  24. data/app/drops/scribo/site_drop.rb +83 -0
  25. data/app/helpers/scribo/application_helper.rb +13 -0
  26. data/app/jobs/scribo/application_job.rb +6 -0
  27. data/app/models/scribo/application_record.rb +11 -0
  28. data/app/models/scribo/content.rb +378 -0
  29. data/app/models/scribo/site.rb +161 -0
  30. data/app/services/scribo/application_service.rb +9 -0
  31. data/app/services/scribo/content_find_service.rb +69 -0
  32. data/app/services/scribo/content_render_service.rb +127 -0
  33. data/app/services/scribo/site_export_service.rb +41 -0
  34. data/app/services/scribo/site_find_service.rb +26 -0
  35. data/app/services/scribo/site_import_service.rb +96 -0
  36. data/app/views/layouts/scribo.html.slim +1 -0
  37. data/app/views/scribo/admin/sites/_create_sites.html.slim +22 -0
  38. data/app/views/scribo/admin/sites/_sites.html.slim +18 -0
  39. data/app/views/scribo/admin/sites/contents/_form.html.slim +17 -0
  40. data/app/views/scribo/admin/sites/contents/create.json.jbuilder +11 -0
  41. data/app/views/scribo/admin/sites/contents/edit.html.slim +2 -0
  42. data/app/views/scribo/admin/sites/contents/edit.json.jbuilder +9 -0
  43. data/app/views/scribo/admin/sites/contents/index.html.slim +1 -0
  44. data/app/views/scribo/admin/sites/contents/move.json.jbuilder +9 -0
  45. data/app/views/scribo/admin/sites/contents/rename.json.jbuilder +9 -0
  46. data/app/views/scribo/admin/sites/contents/upload.json.jbuilder +9 -0
  47. data/app/views/scribo/admin/sites/import.json.jbuilder +4 -0
  48. data/app/views/scribo/admin/sites/index.html.slim +7 -0
  49. data/app/views/scribo/shared/_entry.html.slim +17 -0
  50. data/app/views/scribo/shared/_ide.html.slim +14 -0
  51. data/app/views/scribo/shared/_tree-view.html.slim +32 -0
  52. data/config/locales/en.yml +39 -0
  53. data/config/routes.rb +35 -0
  54. data/db/migrate/20200123095630_initial_scribo.rb +34 -0
  55. data/db/migrate/20220919124119_add_ancestry_to_scribo_contents.rb +13 -0
  56. data/db/migrate/20220919124749_remove_parent_id_from_scribo_contents.rb +8 -0
  57. data/lib/scribo/action_controller_helpers.rb +51 -0
  58. data/lib/scribo/action_controller_renderers.rb +71 -0
  59. data/lib/scribo/action_view_helpers.rb +17 -0
  60. data/lib/scribo/active_record_helpers.rb +11 -0
  61. data/lib/scribo/configuration.rb +97 -0
  62. data/lib/scribo/engine.rb +24 -0
  63. data/lib/scribo/i18n_store.rb +100 -0
  64. data/lib/scribo/liquid/filters/jekyll_filters.rb +94 -0
  65. data/lib/scribo/liquid/filters/markdownify.rb +11 -0
  66. data/lib/scribo/liquid/filters/url_filters.rb +76 -0
  67. data/lib/scribo/liquid/parser.rb +32 -0
  68. data/lib/scribo/liquid/tags/application_assets_tag.rb +14 -0
  69. data/lib/scribo/liquid/tags/application_js_tag.rb +16 -0
  70. data/lib/scribo/liquid/tags/asset_tag.rb +56 -0
  71. data/lib/scribo/liquid/tags/button_tag.rb +24 -0
  72. data/lib/scribo/liquid/tags/check_box_tag.rb +31 -0
  73. data/lib/scribo/liquid/tags/csrf_meta_tags_tag.rb +16 -0
  74. data/lib/scribo/liquid/tags/date_field_tag.rb +23 -0
  75. data/lib/scribo/liquid/tags/editable_url_tag.rb +19 -0
  76. data/lib/scribo/liquid/tags/email_field_tag.rb +23 -0
  77. data/lib/scribo/liquid/tags/feed_meta_tag.rb +22 -0
  78. data/lib/scribo/liquid/tags/field_error_tag.rb +26 -0
  79. data/lib/scribo/liquid/tags/fields_for_tag.rb +82 -0
  80. data/lib/scribo/liquid/tags/form_tag.rb +57 -0
  81. data/lib/scribo/liquid/tags/google_analytics_javascript_tag.rb +30 -0
  82. data/lib/scribo/liquid/tags/google_tag_manager_javascript_tag.rb +28 -0
  83. data/lib/scribo/liquid/tags/hidden_field_tag.rb +23 -0
  84. data/lib/scribo/liquid/tags/highlight_tag.rb +106 -0
  85. data/lib/scribo/liquid/tags/include_tag.rb +28 -0
  86. data/lib/scribo/liquid/tags/label_tag.rb +24 -0
  87. data/lib/scribo/liquid/tags/number_field_tag.rb +23 -0
  88. data/lib/scribo/liquid/tags/options_for_select.rb +36 -0
  89. data/lib/scribo/liquid/tags/password_field_tag.rb +23 -0
  90. data/lib/scribo/liquid/tags/search_field_tag.rb +23 -0
  91. data/lib/scribo/liquid/tags/search_tag.rb +29 -0
  92. data/lib/scribo/liquid/tags/select_tag.rb +22 -0
  93. data/lib/scribo/liquid/tags/seo_tag.rb +35 -0
  94. data/lib/scribo/liquid/tags/set_session.rb +19 -0
  95. data/lib/scribo/liquid/tags/telephone_field_tag.rb +23 -0
  96. data/lib/scribo/liquid/tags/text_field_tag.rb +35 -0
  97. data/lib/scribo/liquid/tags/textarea_tag.rb +28 -0
  98. data/lib/scribo/liquid/tags/time_field_tag.rb +23 -0
  99. data/lib/scribo/liquid/tags/url_field_tag.rb +23 -0
  100. data/lib/scribo/preamble.rb +89 -0
  101. data/lib/scribo/sassc/importer.rb +72 -0
  102. data/lib/scribo/utility.rb +94 -0
  103. data/lib/scribo/version.rb +5 -0
  104. data/lib/scribo.rb +59 -0
  105. data/lib/tasks/scribo_tasks.rake +28 -0
  106. metadata +498 -0
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'scribo/application_drop'
4
+
5
+ module Scribo
6
+ # https://jekyllrb.com/docs/pagination/#liquid-attributes-available
7
+ class PaginatorDrop < ApplicationDrop
8
+ def initialize(site, content)
9
+ @site = site
10
+ @object = content
11
+ @per_page = site&.properties&.[]('paginate') ? site.properties['paginate'].to_i : 5
12
+ end
13
+
14
+ def posts
15
+ @site.contents.posts.order(created_at: :desc).page(page).per(@per_page).to_a
16
+ end
17
+
18
+ def page
19
+ page = 1
20
+ current_path = @context.registers['controller'].request.original_fullpath
21
+ page = current_path.match(%r[/(\d+)/$])[1].to_i if Scribo::Content.paginated?(current_path)
22
+ page
23
+ end
24
+
25
+ attr_reader :per_page
26
+
27
+ def total_posts
28
+ @site.contents.posts.page(page).per(@per_page).total_count
29
+ end
30
+
31
+ def total_pages
32
+ @site.contents.posts.page(page).per(@per_page).total_pages
33
+ end
34
+
35
+ def previous_page
36
+ page > 1 ? page - 1 : nil
37
+ end
38
+
39
+ def previous_page_path
40
+ current_path = @context.registers['controller'].request.original_fullpath
41
+ previous_path = nil
42
+ if previous_page
43
+ if Scribo::Content.paginated?(current_path)
44
+ previous_path = current_path.gsub(%r[/(\d+)/$], "/#{previous_page}/")
45
+ else
46
+ previous_path = current_path
47
+ previous_path += '/' unless previous_path.ends_with?('/')
48
+ previous_path += "#{next_page}/"
49
+ end
50
+ end
51
+ previous_path
52
+ end
53
+
54
+ def next_page
55
+ page < total_pages ? page + 1 : nil
56
+ end
57
+
58
+ def next_page_path
59
+ current_path = @context.registers['controller'].request.original_fullpath
60
+ next_path = nil
61
+ if next_page
62
+ if Scribo::Content.paginated?(current_path)
63
+ next_path = current_path.gsub(%r[/(\d+)/$], "/#{next_page}/")
64
+ else
65
+ next_path = current_path
66
+ next_path += '/' unless next_path.ends_with?('/')
67
+ next_path += "#{next_page}/"
68
+ end
69
+ end
70
+ next_path
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'scribo/application_drop'
4
+ require 'pry'
5
+ module Scribo
6
+ # See https://jekyllrb.com/docs/variables/#site-variables
7
+ class SiteDrop < ApplicationDrop
8
+ delegate :collections, to: :@object
9
+
10
+ def initialize(object)
11
+ @object = object
12
+ @properties = object.properties
13
+ end
14
+
15
+ def time
16
+ Time.current
17
+ end
18
+
19
+ def pages
20
+ @object.contents.pages.to_a
21
+ end
22
+
23
+ def posts
24
+ @object.contents.posts.sort_by { |p| - p.date.to_i }.to_a
25
+ end
26
+
27
+ # TODO
28
+ def related_posts
29
+ []
30
+ end
31
+
32
+ def static_files
33
+ @object.contents.assets.to_a
34
+ end
35
+
36
+ def html_pages
37
+ @object.contents.html_pages.to_a
38
+ end
39
+
40
+ def html_files
41
+ @object.contents.html_files.to_a
42
+ end
43
+
44
+ def data
45
+ Scribo::DataDrop.new(@object)
46
+ end
47
+
48
+ # TODO
49
+ def documents
50
+ []
51
+ end
52
+
53
+ # TODO
54
+ def categories
55
+ []
56
+ end
57
+
58
+ # TODO
59
+ def tags
60
+ []
61
+ end
62
+
63
+ def url
64
+ @properties['url']
65
+ end
66
+
67
+ def current_locale
68
+ I18n.locale.to_s
69
+ end
70
+
71
+ def locale
72
+ @properties['locale']
73
+ end
74
+
75
+ def liquid_method_missing(method)
76
+ if collections.include?(method)
77
+ @object.contents.in_folder("_#{method}").order(:created_at).to_a
78
+ else
79
+ @properties[method.to_s]
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scribo
4
+ module ApplicationHelper
5
+ def method_missing(method, *args, &block)
6
+ if main_app.respond_to?(method)
7
+ main_app.send(method, *args)
8
+ else
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scribo
4
+ class ApplicationJob < ActiveJob::Base
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scribo
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+
7
+ def to_liquid
8
+ Kernel.const_get("#{self.class.name}Drop").new(self)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,378 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'scribo/application_record'
4
+
5
+ module Scribo
6
+ # Represents any content in the system
7
+ class Content < ApplicationRecord
8
+ has_ancestry(primary_key_format: /\A[\w\-]+(\/[\w\-]+)*\z/, counter_cache: true, cache_depth: true)
9
+ belongs_to :site, class_name: 'Site', foreign_key: 'scribo_site_id'
10
+ has_one_attached :asset
11
+
12
+ validate :post_path
13
+ validate :layout_cant_be_current_content
14
+
15
+ before_save :store_properties, if: :config?
16
+ before_save :upload_asset
17
+
18
+ after_save -> { store_full_path(true) }
19
+ after_update -> {store_full_path(true)}
20
+
21
+ scope :layouts, -> { in_folder('_layouts') }
22
+ scope :posts, -> { in_folder('_posts') }
23
+ scope :pages, -> { not_in_folder('_posts').restricted.where(kind: 'text') }
24
+ scope :assets, -> { where(kind: 'asset') }
25
+ scope :html_pages, -> { where("full_path LIKE '%.html' OR full_path LIKE '%.md' OR full_path LIKE '%.markdown'") }
26
+ # html files should be non-filtered html files
27
+ scope :html_files, -> { where("full_path LIKE '%.html'") }
28
+ scope :include, ->(name) { published.where(full_path: ["/_includes/#{name}"]) }
29
+ scope :layout, lambda { |name|
30
+ published.where(full_path: %W[/_layouts/#{name}.html /_layouts/#{name}.md /_layouts/#{name}.xml /_layouts/#{name}.css])
31
+ }
32
+ scope :data, lambda { |name|
33
+ published.where(full_path: %W[/_data/#{name}.yml /_data/#{name}.yaml /_data/#{name}.json /_data/#{name}.csv /_data/#{name}])
34
+ }
35
+
36
+ scope :locale, ->(name) { published.where(full_path: "/_locales/#{name}.yml") }
37
+ scope :locales, -> { published.in_folder('_locales') }
38
+
39
+ scope :published, lambda {
40
+ where("properties->>'published' = 'true' OR properties->>'published' IS NULL").where("properties->>'published_at' IS NULL OR properties->>'published_at' <= :now", now: Time.current.utc)
41
+ }
42
+ scope :restricted, -> { where("full_path NOT LIKE '/\\_%'") }
43
+
44
+ scope :not_in_folder, lambda { |folder_name|
45
+ where.not(id: in_folder(folder_name).pluck(:id))
46
+ }
47
+ scope :in_folder, lambda { |folder_name|
48
+ where(kind: 'folder').find_by(path: folder_name)&.descendants || none
49
+ }
50
+
51
+ scope :permalinked, ->(paths) { where("properties->>'permalink' IN (?)", paths) }
52
+
53
+ def self.located(path, restricted: true)
54
+ restricted = true if restricted.nil? # If blank it's still restricted
55
+
56
+ result = published.where(full_path: search_paths_for(path))
57
+ result = result.restricted if restricted
58
+ result.or(published.permalinked(search_paths_for(path)))
59
+ end
60
+
61
+ def path
62
+ self[:path]
63
+ end
64
+
65
+ # Uses https://www.postgresql.org/docs/current/textsearch-controls.html
66
+ def self.search(search_string)
67
+ where(
68
+ "to_tsvector(scribo_contents.data || ' ' || COALESCE(scribo_contents.properties::text, '')) @@ to_tsquery(?)", search_string
69
+ )
70
+ end
71
+
72
+ # Name of the currently in use layout
73
+ def layout_name
74
+ properties&.key?('layout') ? properties&.[]('layout') : ''
75
+ end
76
+
77
+ # Layout as content
78
+ def layout
79
+ return nil unless layout_name.present?
80
+
81
+ site.contents.layout(layout_name).first
82
+ end
83
+
84
+ def identifier
85
+ File.basename(path, File.extname(path))
86
+ end
87
+
88
+ # Data with frontmatter, used for maintenance and import/export
89
+ def data_with_frontmatter
90
+ return asset.attachment&.download || data if kind != 'text'
91
+
92
+ result = ''
93
+ # Use attributes['properties'] here, to always use content-local properties
94
+ result += (YAML.dump(attributes['properties']) + "---\n") if attributes['properties'].present?
95
+
96
+ result + data.to_s
97
+ end
98
+
99
+ # Data with frontmatter setter
100
+ def data_with_frontmatter=(text)
101
+ if kind == 'text'
102
+ data_with_metadata = Scribo::Preamble.parse(text)
103
+ self.properties = data_with_metadata.metadata
104
+ self.data = data_with_metadata.content
105
+ else
106
+ self.data = text
107
+ end
108
+ end
109
+
110
+ # Used for merging with defaults
111
+ def properties
112
+ attributes['properties']
113
+ # defaults.merge(attributes['properties'] || {})
114
+ end
115
+
116
+ def properties=(text)
117
+ props = text.is_a?(String) ? Scribo::Utility.yaml_safe_parse(text.gsub("\t", ' ')) : text
118
+ write_attribute :properties, props
119
+ end
120
+
121
+ def type
122
+ collection_name
123
+ end
124
+
125
+ def permalink
126
+ properties&.[]('permalink')
127
+ end
128
+
129
+ def url
130
+ result = permalink || Scribo::Utility.switch_extension(full_path)
131
+ result += '/' unless result.end_with?('/')
132
+ result
133
+ end
134
+
135
+ def date
136
+ # return nil unless post?
137
+
138
+ prop_date = begin
139
+ Time.zone.parse(properties['date'])
140
+ rescue StandardError
141
+ nil
142
+ end
143
+
144
+ prop_date || post_date
145
+ end
146
+
147
+ def post_date
148
+ Time.zone.strptime(path[0, 10], '%Y-%m-%d')
149
+ rescue StandardError
150
+ nil
151
+ end
152
+
153
+ def data
154
+ return attributes['data'] if kind == 'asset'
155
+
156
+ attributes['data']&.force_encoding('utf-8')
157
+ end
158
+
159
+ def excerpt
160
+ # FIXME: This is a terrible implementation
161
+ excerpt_part = data.gsub("\r\n", "\n\n").split("\n\n").reject(&:empty?).reject { |p| p.start_with?('#') }.first
162
+ Scribo::ContentRenderService.new(self, {}, data: excerpt_part, layout: false).call
163
+ end
164
+
165
+ def render(context = {}, options = {})
166
+ Scribo::ContentRenderService.new(self, context, options).call
167
+ end
168
+
169
+ def categories
170
+ if properties&.[]('categories').is_a? Array
171
+ properties&.[]('categories')
172
+ else
173
+ (properties&.[]('categories') || '').split
174
+ end
175
+ end
176
+
177
+ def tags
178
+ if properties&.[]('tags').is_a? Array
179
+ properties&.[]('tags')
180
+ else
181
+ (properties&.[]('tags') || '').split
182
+ end
183
+ end
184
+
185
+ def content_type
186
+ properties&.[]('content_type') || mime_type&.content_type || 'application/octet-stream'
187
+ end
188
+
189
+ def media_type
190
+ mime_type&.media_type
191
+ end
192
+
193
+ def extension
194
+ mime_type&.extensions&.first
195
+ end
196
+
197
+ def mime_type
198
+ MIME::Types.type_for(path).first
199
+ end
200
+
201
+ def dir
202
+ File.dirname(full_path)
203
+ end
204
+
205
+ def extname
206
+ File.extname(full_path).tr('.', '')
207
+ end
208
+
209
+ def translation_scope
210
+ scope = File.dirname(full_path).split('/')
211
+ scope << File.basename(full_path, File.extname(full_path))
212
+ scope.join('.')
213
+ end
214
+
215
+ def cache_key
216
+ "#{super}-#{updated_at}-#{I18n.locale}"
217
+ end
218
+
219
+ def collection_name
220
+ return nil unless part_of_collection?
221
+
222
+ ancestors.first.path[1..-1]
223
+ end
224
+
225
+ def part_of_collection?
226
+ return false unless ancestors.first&.path&.start_with?('_')
227
+
228
+ site.collections.include?(ancestors.first.path[1..-1])
229
+ end
230
+
231
+ def redirect?
232
+ extname == 'link'
233
+ end
234
+
235
+ def layout?
236
+ full_path.start_with?('/_layouts/')
237
+ end
238
+
239
+ def post?
240
+ ancestors.map(&:path).join('/').start_with?('_posts')
241
+ end
242
+
243
+ def page?
244
+ Scribo::Utility.output_content_type(self) == 'text/html'
245
+ end
246
+
247
+ def config?
248
+ path == '_config.yml' && parent.nil?
249
+ end
250
+
251
+ def asset?
252
+ kind == 'asset'
253
+ end
254
+
255
+ def folder?
256
+ kind == 'folder'
257
+ end
258
+
259
+ def self.paginated?(path)
260
+ path.match(%r[/(\d+)/$])
261
+ end
262
+
263
+ def self.search_paths_for(path)
264
+ search_paths = []
265
+
266
+ search_path = path
267
+ search_path = "/#{search_path}" unless search_path.start_with?('/')
268
+ search_path.gsub!(%r[/\d+/$], '/') if paginated?(search_path)
269
+ search_path = "#{search_path}index.html" if search_path.ends_with?('/')
270
+
271
+ search_paths.concat(alternative_paths_for(search_path))
272
+
273
+ secondary_search_path = path.sub(%r[/$], '')
274
+ secondary_search_path = "/#{secondary_search_path}" unless secondary_search_path.start_with?('/')
275
+ search_paths.concat(alternative_paths_for(secondary_search_path)) if secondary_search_path != '' && secondary_search_path != search_path
276
+
277
+ permalink_paths = [path]
278
+
279
+ normalized_path = path
280
+ normalized_path = "/#{normalized_path}" unless normalized_path.start_with?('/')
281
+ normalized_path = "#{normalized_path}/" unless normalized_path.ends_with?('/')
282
+ permalink_paths << normalized_path
283
+ search_paths.concat(permalink_paths) # deal with permalinks
284
+
285
+ search_paths.uniq
286
+ end
287
+
288
+ def self.alternative_paths_for(search_path)
289
+ search_paths = []
290
+ search_path = Scribo::Utility.switch_extension(search_path, 'html') unless File.extname(search_path).present?
291
+ search_paths.concat(Scribo::Utility.variations_for_path(search_path))
292
+ search_paths << Scribo::Utility.switch_extension(search_path, 'link')
293
+ end
294
+
295
+ def store_full_path(force = false)
296
+ if force || saved_changes.include?(:path) || saved_changes.include?(:ancestry)
297
+ if post?
298
+ result = categories.join('/') + '/'
299
+ result += date.strftime('%Y/%m/%d/') if date
300
+ result += path[11..-1]
301
+ elsif part_of_collection? && site.output_collection?(collection_name)
302
+ result = "#{collection_name}/#{path}"
303
+ else
304
+ result = (ancestors.map(&:path) << path).join('/')
305
+ end
306
+ result = '/' + result unless result.start_with?('/')
307
+
308
+ update_column(:full_path, result)
309
+
310
+ children.reload.each do |child|
311
+ child.store_full_path(true)
312
+ end
313
+
314
+ end
315
+ end
316
+
317
+ def tree_path
318
+ result = (ancestors.map(&:path) << path).join('/')
319
+ result = '/' + result unless result.start_with?('/')
320
+ result
321
+ end
322
+
323
+ private
324
+
325
+ def upload_asset
326
+ return unless asset?
327
+ # return unless data.present? -> Breaks with utf8?
328
+ return unless data
329
+ return unless data.size.positive?
330
+
331
+ si = StringIO.new
332
+ si.write(data)
333
+ si.rewind
334
+
335
+ asset.attach(io: si, filename: path, content_type: content_type)
336
+
337
+ self.data = nil
338
+ end
339
+
340
+ def store_properties
341
+ return unless attributes['data'].present?
342
+
343
+ new_properties = Scribo::Utility.yaml_safe_parse(attributes['data'])
344
+ old_properties = site.properties
345
+ site.update(properties: new_properties)
346
+
347
+ # Only reshuffle if they need to
348
+ if old_properties['collections'] != new_properties['collections'] ||
349
+ old_properties['permalink'] != new_properties['permalink']
350
+ site.reshuffle!
351
+ end
352
+ end
353
+
354
+ def post_path
355
+ return unless post?
356
+
357
+ errors.add(:path, 'path must be of format YYYY-MM-DD-title') unless path.match(/[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}-.*/)
358
+ end
359
+
360
+ def layout_cant_be_current_content
361
+ return unless layout
362
+
363
+ errors.add(:base, "layout can't be layout of itself") if layout.full_path == full_path
364
+ end
365
+
366
+ class << self
367
+ def redirect_options(redirect_data)
368
+ options = redirect_data.split
369
+ if options.length == 2
370
+ options[0] = options[0].to_i
371
+ else
372
+ options.unshift 302
373
+ end
374
+ options
375
+ end
376
+ end
377
+ end
378
+ end