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,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'scribo/application_record'
4
+ require_dependency 'scribo/liquid/parser'
5
+
6
+ module Scribo
7
+ class Site < ApplicationRecord
8
+ NEW_SITE_NAME = 'Untitled site'
9
+
10
+ belongs_to :scribable, polymorphic: true, optional: true
11
+
12
+ has_many :contents, class_name: 'Content', foreign_key: 'scribo_site_id', dependent: :destroy
13
+
14
+ attr_accessor :zip_file
15
+
16
+ scope :adminable, -> { where(scribable: Scribo.config.scribable_objects) }
17
+ scope :owned_by, ->(owner) { where(scribable: owner) }
18
+ scope :titled, ->(title) { where("properties->>'title' = ?", title).first }
19
+
20
+ def filter_cache
21
+ @filter_cache ||= {}
22
+ end
23
+
24
+ def reshuffle!
25
+ contents.roots.each(&:store_full_path)
26
+ end
27
+
28
+ # See https://jekyllrb.com/docs/permalinks/
29
+ def perma_link
30
+ properties['permalink'] || '/:year/:month/:day/:title:output_ext'
31
+ end
32
+
33
+ def collections
34
+ result = begin
35
+ properties['collections'].to_h.keys.map(&:to_s)
36
+ rescue StandardError
37
+ []
38
+ end
39
+ result += %w[posts]
40
+ result
41
+ end
42
+
43
+ def output_collection?(collection)
44
+ return true if collection.to_s == 'posts'
45
+
46
+ col = properties['collections'].to_h[collection.to_s]
47
+ col&.[]('output') == true
48
+ end
49
+
50
+ def title
51
+ properties['title'] || NEW_SITE_NAME
52
+ end
53
+
54
+ def baseurl
55
+ properties['baseurl'] || '/'
56
+ end
57
+
58
+ # This returns the full thumbnail URL
59
+ def thumbnail
60
+ return unless properties['thumbnail']
61
+
62
+ url + baseurl + properties['thumbnail'].to_s
63
+ end
64
+
65
+ def url
66
+ properties['url'].to_s
67
+ end
68
+
69
+ def properties
70
+ attributes['properties'].present? ? attributes['properties'] : { 'title' => NEW_SITE_NAME, 'baseurl' => '/' }
71
+ end
72
+
73
+ def sass_dir
74
+ result = properties.value_at_keypath('sass.sass_dir') || '/_sass/'
75
+ result = "/#{result}" unless result.start_with?('/')
76
+ result = "#{result}/" unless result.ends_with?('/')
77
+
78
+ result
79
+ end
80
+
81
+ def defaults_for(content)
82
+ site_defaults = properties&.[]('defaults')
83
+ return {} unless site_defaults
84
+ return {} unless content.full_path
85
+ return {} if !content.page? && !content.collection_name # scoping only possible for pages & collections
86
+
87
+ props = site_defaults.select do |d|
88
+ s = d['scope']
89
+ next unless s
90
+
91
+ result = false
92
+
93
+ if s['path']
94
+ result = true
95
+ p = s['path']
96
+ unless p.include?('*')
97
+ p = "/#{p}" unless p.start_with?('/')
98
+ p = "#{p}/" unless p.ends_with?('/')
99
+ p += '*'
100
+ end
101
+
102
+ result &= File.fnmatch?(p, content.full_path.to_s, File::FNM_EXTGLOB)
103
+ end
104
+
105
+ result &= s['type'] == content.type if s['type']
106
+
107
+ result
108
+ end
109
+ # Sort by longest scope array (ie most specific-ish) and return the first
110
+ props = props.min_by { |p| -p['scope'].to_a.size }
111
+
112
+ (props || {}).fetch('values', {})
113
+ end
114
+
115
+ #
116
+ # Calculates the total size of the site in bytes, including assets
117
+ #
118
+ # @return [Integer] size in bytes
119
+ #
120
+ def total_size
121
+ contents.map { |c| c.data ? c.data.size : c.asset.attachment&.download&.size || 0 }.sum
122
+ end
123
+
124
+ class << self
125
+ def for_path(path)
126
+ return none if path.blank?
127
+
128
+ # Remove any segment which does not end in /
129
+ search_path = File.dirname(path)
130
+
131
+ paths = []
132
+ paths.concat(search_path.split('/').map.with_index { |_, i| search_path.split('/')[0..i].join('/') }.reject(&:empty?))
133
+ paths <<= '/'
134
+ paths <<= path.gsub(%r[/$], '')
135
+
136
+ where("properties->>'baseurl' IN (?) OR properties->>'baseurl' = '' OR properties->>'baseurl' IS NULL",
137
+ paths.uniq).order(Arel.sql("COALESCE(LENGTH(scribo_sites.properties->>'baseurl'), 0) DESC"))
138
+ end
139
+
140
+ def for_host(host)
141
+ where("properties->>'host' = ? OR properties->>'host' = '' OR properties->>'host' IS NULL", host).order(Arel.sql("COALESCE(LENGTH(scribo_sites.properties->>'host'), 0) DESC"))
142
+ end
143
+
144
+ def all_translation_keys
145
+ parser = Scribo::LiquidParser.new
146
+ result = {}
147
+ Scribo::Content.includes(:site).where(content_type: Scribo.config.supported_mime_types[:text]).each do |content|
148
+ parts = parser.parse(content.data)
149
+ [*parts].select { |part| part.is_a?(Hash) && part[:filter] == 't' }.each do |part|
150
+ result[content.translation_scope + part[:value].to_s] = part[:value].to_s[1..-1].humanize
151
+ end
152
+ end
153
+ result
154
+ end
155
+
156
+ def default(request: nil)
157
+ Scribo.config.default_site(request) || new
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scribo
4
+ class ApplicationService
5
+ def call
6
+ perform
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'scribo/application_service'
4
+
5
+ module Scribo
6
+ class ContentFindService < ApplicationService
7
+ attr_reader :options, :site
8
+
9
+ def initialize(site, options = {})
10
+ super()
11
+ @site = site
12
+ @options = options
13
+ end
14
+
15
+ def perform
16
+ return options[:content] if options[:content].is_a?(Scribo::Content)
17
+
18
+ scope = site.contents
19
+
20
+ if options[:path]
21
+ path = CGI.unescape(options[:path])
22
+ path = path[site.baseurl.length..-1] if path.start_with?(site.baseurl)
23
+
24
+ # Deal with collections
25
+ path_parts = path.split('/')
26
+ first_path_part = path_parts[0]
27
+
28
+ if site.collections.include?(first_path_part)
29
+ path_parts[0] = '_' + path_parts[0]
30
+ options[:restricted] = false
31
+ path = path_parts.join('/')
32
+ end
33
+ # End - Deal with collections
34
+
35
+ scope = scope.located(path, restricted: options[:restricted])
36
+ end
37
+
38
+ # FIXME: Not to pretty
39
+ content = if options[:root]
40
+ scope.roots.first
41
+ else
42
+ result = scope.where.not(kind: 'folder').first
43
+
44
+ result = scope.where(kind: 'folder').first unless scope.present?
45
+
46
+ result = Scribo::Content.new(kind: 'text', path: '/directory.link', full_path: '/directory.link', data: "#{options[:path]}/") if result&.folder?
47
+
48
+ result
49
+ end
50
+
51
+ # Find by content id
52
+ content ||= Scribo::Content&.published&.find_by_id(options[:path][1..-1]) if options[:path] && options[:path][1..-1].length == 36
53
+
54
+ if options[:path] == '/humans.txt'
55
+ content ||= Scribo::Content.new(kind: 'text', path: '/humans.txt', full_path: '/humans.txt', data: Scribo.config.default_humans_txt)
56
+ elsif options[:path] == '/robots.txt'
57
+ content ||= Scribo::Content.new(kind: 'text', path: '/robots.txt', full_path: '/robots.txt', data: Scribo.config.default_robots_txt)
58
+ elsif options[:path] == '/favicon.ico'
59
+ content ||= Scribo::Content.new(kind: 'asset', path: '/favicon.ico', full_path: '/favicon.ico', data: Base64.decode64(Scribo.config.default_favicon_ico))
60
+ end
61
+
62
+ # FIXME: Find a better way for this
63
+ content ||= site&.contents&.located('/404')&.first
64
+ content ||= site&.contents&.located('/404.html')&.first
65
+ content ||= site&.contents&.located('/404.md')&.first
66
+ content
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'scribo/application_service'
4
+
5
+ module Scribo
6
+ class ContentRenderService < ApplicationService
7
+ attr_reader :content, :context, :options
8
+
9
+ def initialize(content, context, options = {})
10
+ super()
11
+ @content = content
12
+ @context = context
13
+ @options = options
14
+ @assigns = nil
15
+ @registers = nil
16
+ end
17
+
18
+ def perform
19
+ if content.kind == 'asset'
20
+ render_asset
21
+ else
22
+ layout = options[:layout] == false ? nil : content.layout
23
+
24
+ # FIXME: Though this works for layout, we need to be able to merge all properties with defaults
25
+ # Luckily this is mostly used for layouts
26
+ if options[:site]
27
+ layout_name = options[:site].defaults_for(content)['layout']
28
+ layout ||= options[:site].contents.layout(layout_name).first if layout_name
29
+ end
30
+
31
+ render_liquidum(options[:data] || content.data, layout)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def render_liquidum(data, layout)
38
+ result = Liquidum.render(data, assigns: assigns.merge!('content' => data), registers: registers, filter: filter,
39
+ filter_options: filter_options, layout: layout&.data)
40
+
41
+ while layout&.layout
42
+ next unless layout&.layout
43
+
44
+ layout = layout.layout
45
+ result = Liquidum.render(layout.data, assigns: assigns.merge('content' => result), registers: registers)
46
+ end
47
+
48
+ result
49
+ end
50
+
51
+ def render_asset
52
+ return unless content.kind == 'asset'
53
+ return content.data if content.data.present?
54
+ return unless content.asset.attached?
55
+
56
+ content.asset.download
57
+ end
58
+
59
+ def filter
60
+ return options[:filter] if options.key?(:filter)
61
+
62
+ if content.properties&.key?('filter')
63
+ content.properties['filter']
64
+ elsif content.path
65
+ Scribo::Utility.filter_for_path(content.path)
66
+ end
67
+ end
68
+
69
+ def filter_options
70
+ @filter_options = { full_path: content.full_path, content: content }
71
+ @filter_options[:importer] = Scribo::SassC::Importer if %w[sass scss].include?(filter)
72
+ @filter_options.merge!(markdown_filter_options) if %w[markdown].include?(filter)
73
+ @filter_options
74
+ end
75
+
76
+ def assigns
77
+ return @assigns if @assigns
78
+
79
+ @assigns = {}
80
+ @assigns.merge!(options[:assigns]) if options[:assigns]
81
+
82
+ context.instance_variables.reject { |i| i.to_s.starts_with?('@_') }.each do |i|
83
+ @assigns[i.to_s[1..-1]] = context.instance_variable_get(i)
84
+ end
85
+
86
+ @assigns['request'] = Scribo::ActionDispatch::RequestDrop.new(context.request) if context.respond_to?(:request)
87
+ @assigns['site'] = options[:site] || content.site
88
+ @assigns['page'] = content
89
+ @assigns['paginator'] = Scribo::PaginatorDrop.new(@assigns['site'], content)
90
+
91
+ @assigns = @assigns.stringify_keys
92
+ @assigns
93
+ end
94
+
95
+ def registers
96
+ return @registers if @registers
97
+
98
+ @registers = { 'controller' => context, 'content' => content, 'site' => content.site }
99
+ @registers.merge!(options[:registers]) if options[:registers]
100
+ @registers = @registers.stringify_keys
101
+ @registers
102
+ end
103
+
104
+ def markdown_filter_options
105
+ {
106
+ auto_ids: true,
107
+ toc_levels: '1..6',
108
+ entity_output: 'as_char',
109
+ smart_quotes: 'lsquo,rsquo,ldquo,rdquo',
110
+ input: 'GFM',
111
+ hard_wrap: false,
112
+ guess_lang: true,
113
+ footnote_nr: 1,
114
+ show_warnings: false,
115
+ syntax_highlighter: 'rouge'
116
+
117
+ # syntax_highlighter_opts: {
118
+ # bold_every: 8,
119
+ # css: :class,
120
+ # css_class: 'highlight',
121
+ # formatter: ::Rouge::Formatters::HTMLLegacy,
122
+ # foobar: 'lipsum'
123
+ # }
124
+ }
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'scribo/application_service'
4
+
5
+ module Scribo
6
+ class SiteExportService < ApplicationService
7
+ attr_reader :site
8
+
9
+ def initialize(site)
10
+ super()
11
+ @site = site
12
+ end
13
+
14
+ def perform
15
+ return unless site.contents.count.positive?
16
+
17
+ zip_name = (site.properties['title'] || 'untitled').to_s
18
+ base_path = "#{zip_name}/"
19
+
20
+ stringio = Zip::OutputStream.write_buffer do |zio|
21
+ site.contents.each do |content|
22
+ content_path = content_path_for_zip(content)
23
+ next unless content_path
24
+
25
+ next if content.kind == 'folder'
26
+
27
+ zio.put_next_entry(base_path + content_path)
28
+ zio.write content.data_with_frontmatter
29
+ end
30
+ end
31
+
32
+ ["#{zip_name}.zip", stringio.string]
33
+ end
34
+
35
+ private
36
+
37
+ def content_path_for_zip(content)
38
+ content.tree_path[0] == '/' ? content.tree_path[1..-1] : content.tree_path
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'scribo/application_service'
4
+
5
+ module Scribo
6
+ class SiteFindService < ApplicationService
7
+ attr_reader :options
8
+
9
+ def initialize(options = {})
10
+ super()
11
+ @options = options
12
+ end
13
+
14
+ def perform
15
+ return options[:site] if options[:site].is_a?(Scribo::Site)
16
+
17
+ scribable = Scribo.config.scribable_for_request(options[:request])
18
+ return nil unless scribable
19
+
20
+ scope = scribable.sites
21
+ scope = scope.for_host(options[:host])
22
+ scope = scope.for_path(options[:path]) if options[:path]
23
+ scope.first
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'scribo/application_service'
4
+ require 'mime/types'
5
+ require 'yaml'
6
+
7
+ module Scribo
8
+ class SiteImportService < ApplicationService
9
+ attr_reader :path, :scribable, :properties_override
10
+
11
+ IGNORED_FILES = [%r[^__MACOSX/], %r[/\.DS_Store], %r[^/_site], /^node_modules/].freeze
12
+
13
+ def initialize(path, scribable: nil, properties: {})
14
+ super()
15
+ @path = path
16
+ @scribable = scribable
17
+ @properties_override = properties
18
+ end
19
+
20
+ def perform
21
+ Dir.mktmpdir do |dir|
22
+ Dir.chdir(dir) do
23
+ unzip(dir)
24
+
25
+ all_files = Dir.glob('**/*')
26
+ all_files = all_files.reject { |p| p == '_config.yml' } if properties_override.present?
27
+ all_files.each do |name|
28
+ parent = if File.dirname(name) == name
29
+ nil
30
+ else
31
+ site.contents.located(File.dirname(name), restricted: false).first
32
+ end
33
+ content = parent.children.find_by(path: File.basename(name)) if parent
34
+ content = site.contents.find_by(path: File.basename(name), ancestry: nil) if parent.nil? && content.nil?
35
+ content = site.contents.create!(path: File.basename(name), parent: parent) if content.nil?
36
+ if File.directory?(name)
37
+ content.kind = 'folder'
38
+ else
39
+ File.open(name) do |f|
40
+ content.kind = Scribo::Utility.kind_for_path(name)
41
+ content.data_with_frontmatter = f.read
42
+ end
43
+ end
44
+ content.save!
45
+ end
46
+ end
47
+ end
48
+ site.contents.create(path: '_config.yml', data: YAML.dump(site.properties)) if properties_override.present?
49
+ site
50
+ end
51
+
52
+ private
53
+
54
+ def unzip(dir)
55
+ Zip::File.open(path) do |zipfile|
56
+ zipfile.reject { |e| IGNORED_FILES.any? { |r| r.match(e.name) } }.each do |f|
57
+ file_path = File.join(dir, f.name[base_path(zipfile).length..-1])
58
+ FileUtils.mkdir_p(File.dirname(file_path)) unless File.exist?(File.dirname(file_path))
59
+
60
+ f.extract(file_path) unless File.exist?(file_path)
61
+ end
62
+ end
63
+ end
64
+
65
+ def base_path(zipfile)
66
+ return @base_path if @base_path
67
+
68
+ base_paths = zipfile.reject { |e| IGNORED_FILES.any? { |r| r.match(e.name) } }
69
+ .map { |f| f.name.split('/').first }.uniq
70
+ @base_path = base_paths.size == 1 ? base_paths.first : ''
71
+ end
72
+
73
+ def site
74
+ return @site if @site
75
+
76
+ @site = Site.where(scribable: scribable)
77
+
78
+ %w[host title baseurl].each do |property|
79
+ if properties[property].nil?
80
+ @site = @site.where("properties->>'#{property}' IS NULL")
81
+ else
82
+ @site = @site.where("properties->>'#{property}' = ?", properties[property])
83
+ end
84
+ end
85
+
86
+ @site = @site.first
87
+
88
+ @site ||= Site.create!(scribable: scribable, properties: properties.merge(properties_override))
89
+ end
90
+
91
+ def properties
92
+ @properties = Scribo::Utility.yaml_safe_parse(File.read('_config.yml')) if File.exist?('_config.yml')
93
+ @properties ||= {}
94
+ end
95
+ end
96
+ end
@@ -0,0 +1 @@
1
+ = layout_with_scribo(scribo_layout_identifier, yield)
@@ -0,0 +1,22 @@
1
+ - if Scribo.config.scribable_for_request(request)
2
+ = link_to(scribo.new_admin_site_path, { 'data-turbolinks' => false }) do
3
+ .site
4
+ .icon
5
+ i.fal.fa-plus.fa-7x
6
+ .title = t('scribo.sites.create')
7
+
8
+ - if Scribo.config.templates
9
+ - for template in Scribo.config.templates
10
+ = link_to(scribo.new_admin_site_path(template_id: template[:id]), { 'data-turbolinks' => false }) do
11
+ .site
12
+ .icon
13
+ img src=template[:thumbnail]
14
+ .title = t('scribo.sites.create_from_template')
15
+
16
+ .site.site--upload data-controller="upload" data-upload-url="#{scribo.import_admin_sites_path}" data-upload-param-name="files[]" data-upload-extra-data='{}'
17
+ .icon.upload
18
+ i.fal.fa-upload.fa-7x
19
+ .icon.uploading
20
+ i.fal.fa-circle-notch.fa-7x.fa-spin
21
+ .title
22
+ = t('scribo.sites.upload')
@@ -0,0 +1,18 @@
1
+ - for site in @sites
2
+ = link_to scribo.admin_site_contents_path(site), title: t('scribo.sites.ide') do
3
+ div.site
4
+ .icon
5
+ - if site.thumbnail
6
+ img src=site.thumbnail
7
+ - else
8
+ i.fal.fa-globe.fa-7x
9
+ .title
10
+ = site.title
11
+ - if site.url
12
+ =< link_to site.url, title: t('scribo.sites.preview') do
13
+ i.fal.fa-eye
14
+ - if site.contents.count > 0
15
+ =< link_to(scribo.export_admin_site_path(site), 'data-turbolinks' => 'false', title: t('scribo.sites.export')) do
16
+ i.fal.fa-download
17
+ =< link_to scribo.admin_site_path(site), title: t('scribo.sites.delete'), data: { turbo_method: :delete, turbo_confirm: t('scribo.sites.delete_confirm') } do
18
+ i.fal.fa-trash
@@ -0,0 +1,17 @@
1
+ div style="position: absolute; width: 100%; height: 100%;"
2
+ - if @content.kind == 'asset'
3
+ - if @content.media_type == 'image'
4
+ div id="content-editor-#{@content.id}" data-controller="image-editor" data-image-editor-content-id=@content.id data-image-editor-url=content_url(@content) data-image-editor-mime-type=@content.mime_type data-image-editor-save-url=admin_site_content_path(@site, @content) style="max-width: 100%;"
5
+ - if @content.media_type == 'font' || @content.mime_type == "application/vnd.ms-fontobject"
6
+ css:
7
+ @font-face {
8
+ font-family: 'MyWebFont';
9
+ src: url(#{"#{content_path(@content)}"}); /* IE9 Compat Modes */
10
+ }
11
+ - for i in [10, 12, 14, 16, 18, 20, 22, 24]
12
+ div style="font-family: MyWebFont; font-size: #{i}px;"
13
+ | #{i} - THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG. the quick brown fox jumped over the lazy dog.
14
+
15
+ - else
16
+ textarea id="content-editor-#{@content.id}" name='content[data_with_frontmatter]' data-controller="text-editor" data-text-editor-content-id=@content.id data-text-editor-target="textarea" data-text-editor-mode="#{@content.content_type}" data-text-editor-save-url=admin_site_content_path(@site, @content) style="display: block; height: 100%;"
17
+ = @content.data_with_frontmatter || 'Start typing here'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ json.content do |content|
4
+ content.id @content.id
5
+ content.kind @content.kind
6
+ content.path @content.path
7
+ content.full_path @content.full_path
8
+ content.url admin_site_content_path(@site, @content)
9
+ end
10
+ json.html render partial: 'scribo/admin/sites/contents/form', layout: false, formats: [:html]
11
+ json.itemHtml render partial: 'scribo/shared/entry', layout: false, formats: [:html], locals: { content: @content, state: 'open' }
@@ -0,0 +1,2 @@
1
+ = render 'scribo/shared/ide' do
2
+ = render partial: 'form'
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ json.content do |content|
4
+ content.id @content.id
5
+ content.path @content.path
6
+ content.full_path @content.full_path
7
+ content.url admin_site_content_path(@site, @content)
8
+ end
9
+ json.html render partial: 'scribo/admin/sites/contents/form', layout: false, formats: [:html]
@@ -0,0 +1 @@
1
+ = render 'scribo/shared/ide'
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ json.content do |content|
4
+ content.id @content.id
5
+ content.kind @content.kind
6
+ content.path @content.path
7
+ content.full_path @content.full_path
8
+ content.url admin_site_content_path(@site, @content)
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ json.content do |content|
4
+ content.id @content.id
5
+ content.kind @content.kind
6
+ content.path @content.path
7
+ content.full_path @content.full_path
8
+ content.url admin_site_content_path(@site, @content)
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ if @parent
4
+ json.selector "li.entry.directory[data-content=\"#{@parent.id}\"]"
5
+ json.html render partial: 'scribo/shared/entry', layout: false, formats: [:html], locals: { site: @site, content: @parent, state: 'open' }
6
+ else
7
+ json.selector '.tree-view'
8
+ json.html render partial: 'scribo/shared/tree-view', layout: false, locals: { site: @site }
9
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ json.selector '#sites'
4
+ json.html render partial: 'sites', layout: false, locals: { site: @site }
@@ -0,0 +1,7 @@
1
+ h4= t('.existing_sites')
2
+ .scribo-sites.scribo-sites--existing
3
+ = render partial: 'sites'
4
+
5
+ h4= t('.create_site')
6
+ .scribo-sites.scribo-sites--create
7
+ = render partial: 'create_sites'