scribo 1.0.38

Sign up to get free protection for your applications and to get access to all the features.
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,17 @@
1
+ li.entry class="#{content.kind == 'folder' ? "directory #{state || 'closed'}" : 'file'}" data-content="#{content.id}" data-at-content="#{content.id}" data-controller="#{content.kind == 'folder' ? 'upload' : ''}" data-upload-url="#{upload_admin_site_contents_url(@site)}" data-upload-param-name="content[files][]" data-upload-extra-data=%[{"content[parent_id]": "#{content.id}"}]
2
+ - if content.kind == 'folder'
3
+ a.list-item data-action="dblclick->tree-view#rename" data-tree-view-rename-url="#{rename_admin_site_content_url(content.site, content)}"
4
+ span.name data-path="#{content.path}" = content.path
5
+ .tools
6
+ i.fal.fa-file-plus data-action="click->tree-view#create" data-kind="text" data-url="#{admin_site_contents_url(@site)}" title=I18n.t('scribo.tools.new_file')
7
+ i.fal.fa-folder-plus data-action="click->tree-view#create" data-kind="folder" data-url="#{admin_site_contents_url(@site)}" title=I18n.t('scribo.tools.new_folder')
8
+ i.fal.fa-trash data-action="click->tree-view#delete" data-url="#{admin_site_content_path(@site, content)}" data-confirm=I18n.t('scribo.confirm.delete_folder') title=I18n.t('scribo.tools.delete_folder')
9
+
10
+ - else
11
+ a.list-item data-action="click->tree-view#open dblclick->tree-view#rename" data-tree-view-url="#{edit_admin_site_content_url(content.site, content)}" data-tree-view-rename-url="#{rename_admin_site_content_url(content.site, content)}"
12
+ span.name data-path="#{content.path}" = content.path
13
+ .tools
14
+ i.fal.fa-trash data-action="click->tree-view#delete" data-url="#{admin_site_content_path(@site, content)}" data-confirm=I18n.t('scribo.confirm.delete_file') title=I18n.t('scribo.tools.delete_file')
15
+ ul data-parent="#{content.id}"
16
+ - (content.children.where(kind: 'folder').reorder(:path) + content.children.where("kind <> 'folder'").reorder(:path)).each do |child|
17
+ = render partial: 'scribo/shared/entry', locals: {content: child, state: 'closed'}
@@ -0,0 +1,14 @@
1
+ .scribo-ide id="ide" data-controller='open-editors'
2
+ .explorer-pane data-controller="tree-view upload" data-tree-view-update-url="#{move_admin_site_contents_path(@site)}" data-upload-url="#{upload_admin_site_contents_url(@site)}" data-upload-param-name="content[files][]"
3
+ .tree-view
4
+ = render partial: 'scribo/shared/tree-view'
5
+
6
+ .editors-pane
7
+ .editors-tabs data-open-editors-target='tabs' data-action="click->open-editors#clickTabs"
8
+ .editors-contents data-open-editors-target='contents'
9
+
10
+ - if @readme
11
+ #readme style="display: none;" data-id=@readme.id data-path=@readme.path data-full-path=@readme.full_path data-url=@readme.url
12
+ div style="position: absolute; width: 100%; height: 100%;"
13
+ textarea id="content-editor-#{@readme.id}" name='content[data_with_frontmatter]' data-controller="text-editor" data-text-editor-content-id=@readme.id data-text-editor-target="textarea" data-text-editor-mode="#{@readme.content_type}" data-text-editor-save-url=admin_site_content_path(@site, @readme) style="display: block; height: 100%;"
14
+ = @readme.data_with_frontmatter || 'Start typing here'
@@ -0,0 +1,32 @@
1
+ div.section
2
+ span.title Open editors
3
+ .tools
4
+ i.fal.fa-floppy-disks data-action="click->open-editors#saveAll" title=I18n.t('scribo.tools.save_all')
5
+ i.fal.fa-square-xmark data-action="click->open-editors#closeAll" title=I18n.t('scribo.tools.close_all')
6
+
7
+ ul.openEditors data-open-editors-target='list' data-action="click->open-editors#clickList"
8
+
9
+ div.section
10
+ span.title = @site.title
11
+ .tools
12
+ i.fal.fa-file-plus data-action="click->tree-view#create" data-kind="text" data-url="#{admin_site_contents_url(@site)}" title=I18n.t('scribo.tools.new_file')
13
+ i.fal.fa-folder-plus data-action="click->tree-view#create" data-kind="folder" data-url="#{admin_site_contents_url(@site)}" title=I18n.t('scribo.tools.new_folder')
14
+ i.fal.fa-square-plus data-action="click->tree-view#collapseExpandAll" data-tree-view-target="collapseExpand" title=I18n.t('scribo.tools.collapse_all')
15
+
16
+ ul data-tree-view-target="contentItems" data-open-editors-target='contentItems'
17
+ - @contents.each do |content|
18
+ = render partial: 'scribo/shared/entry', locals: {content: content, state: 'closed'}
19
+
20
+ template data-tree-view-target="folderTemplate"
21
+ li.entry.directory.closed
22
+ a.list-item
23
+ span.name
24
+ input type="text" value="untitled"
25
+ ul
26
+
27
+ template data-tree-view-target="entryTemplate"
28
+ li.entry.file
29
+ a.list-item
30
+ span.name data-path="untitled.html"
31
+ input type="text" value="untitled.html"
32
+ ul
@@ -0,0 +1,39 @@
1
+ en:
2
+ scribo:
3
+ breadcrumbs:
4
+ admin:
5
+ sites: Sites
6
+ contents: Contents
7
+ translations: Translations
8
+ sites:
9
+ export: Download site
10
+ ide: Open IDE
11
+ preview: Preview
12
+ delete: Delete site
13
+ delete_confirm: Are you sure you want to delete this site?
14
+ upload: Drop a zip to import a site
15
+ create: Create a new site
16
+ create_from_template: Create from template
17
+ tools:
18
+ new_file: New file
19
+ new_folder: New folder
20
+ delete_file: Delete file
21
+ delete_folder: Delete folder
22
+ collapse_all: Collapse all
23
+ close_all: Close all
24
+ save_all: Save all
25
+ save: Save
26
+ confirm:
27
+ delete_file: Are you sure you want to delete this file?
28
+ delete_folder: Are you sure you want to delete this folder (and all it's subfiles and folders)?
29
+ admin:
30
+ sites:
31
+ contents:
32
+ create:
33
+ create_fail: 'Failed to create %{kind}'
34
+ upload:
35
+ upload_fail: Failed to upload
36
+ move:
37
+ move_fail: Failed to move file
38
+
39
+
data/config/routes.rb ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ Scribo::Engine.routes.draw do
4
+ namespace :admin, path: Scribo.config.admin_mount_point do
5
+ resources :sites do
6
+ resources :contents, controller: 'sites/contents' do
7
+ member do
8
+ put 'rename', as: :rename
9
+ end
10
+ collection do
11
+ post 'upload'
12
+ put 'move', as: :move
13
+ end
14
+ end
15
+
16
+ member do
17
+ get 'export'
18
+ end
19
+ collection do
20
+ get 'import'
21
+ post 'import'
22
+ end
23
+ end
24
+ end
25
+ namespace :api do
26
+ resources :sites do
27
+ collection do
28
+ post 'import'
29
+ end
30
+ end
31
+ end
32
+
33
+ root to: 'contents#show'
34
+ get '(*path)', to: 'contents#show', as: 'content', constraints: ->(request) { !request.path.starts_with?('/rails') }
35
+ end
@@ -0,0 +1,34 @@
1
+ class InitialScribo < ActiveRecord::Migration[5.2]
2
+ def change
3
+ return if ActiveRecord::Base.connection.table_exists? 'scribo_contents'
4
+
5
+ create_table "scribo_contents", id: :uuid, default: -> { "uuid_generate_v4()" }, force: :cascade do |t|
6
+ t.uuid "scribo_site_id"
7
+ t.string "kind", default: "text"
8
+ t.string "path"
9
+ t.text "data"
10
+ t.jsonb "properties", default: {}
11
+ t.uuid "parent_id"
12
+ t.datetime "created_at", null: false
13
+ t.datetime "updated_at", null: false
14
+ t.integer "lft"
15
+ t.integer "rgt"
16
+ t.integer "depth"
17
+ t.integer "children_count"
18
+ t.string "full_path"
19
+ t.index ["parent_id"], name: "index_scribo_contents_on_parent_id"
20
+ t.index ["scribo_site_id", "full_path"], name: "index_scribo_contents_full_path", unique: true
21
+ t.index ["scribo_site_id", "path"], name: "index_scribo_contents_path"
22
+ t.index ["scribo_site_id"], name: "index_scribo_contents_on_scribo_site_id"
23
+ end
24
+
25
+ create_table "scribo_sites", id: :uuid, default: -> { "uuid_generate_v4()" }, force: :cascade do |t|
26
+ t.string "scribable_type"
27
+ t.uuid "scribable_id"
28
+ t.datetime "created_at", null: false
29
+ t.datetime "updated_at", null: false
30
+ t.jsonb "properties", default: {}, null: false
31
+ t.index ["scribable_type", "scribable_id"], name: "index_scribo_sites_on_scribable_type_and_scribable_id"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ class AddAncestryToScriboContents < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :scribo_contents, :ancestry, :text
4
+ add_index :scribo_contents, :ancestry, order: {ancestry: :text_pattern_ops}
5
+ add_column :scribo_contents, :ancestry_depth, :integer, default: 0
6
+ add_index :scribo_contents, :ancestry_depth
7
+ remove_column :scribo_contents, :children_count
8
+ add_column :scribo_contents, :children_count, :integer, default: 0
9
+ add_index :scribo_contents, :children_count
10
+
11
+ Scribo::Content.build_ancestry_from_parent_ids!
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ class RemoveParentIdFromScriboContents < ActiveRecord::Migration[6.0]
2
+ def change
3
+ remove_column :scribo_contents, :parent_id
4
+ remove_column :scribo_contents, :lft
5
+ remove_column :scribo_contents, :rgt
6
+ remove_column :scribo_contents, :depth
7
+ end
8
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scribo
4
+ module ActionControllerHelpers
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ attr_accessor :scribo_value_purpose, :scribo_value_layout, :scribo_value_application_assets
9
+
10
+ if respond_to? :helper_method
11
+ helper_method :scribo_layout_identifier, :scribo_application_assets, :scribo_purpose
12
+ end
13
+
14
+ def scribo_layout_identifier
15
+ scribo_value_for(scribo_value_layout)
16
+ end
17
+
18
+ def scribo_purpose
19
+ scribo_value_for(scribo_value_purpose)
20
+ end
21
+
22
+ def scribo_application_assets
23
+ scribo_value_for(scribo_value_application_assets)
24
+ end
25
+
26
+ private
27
+
28
+ def scribo_value_for(value)
29
+ return instance_eval(&value) if value.is_a? Proc
30
+ return send(value) if value.is_a? Symbol
31
+ value
32
+ end
33
+ end
34
+
35
+ class_methods do
36
+ def scribo(*args)
37
+ options = args.extract_options!
38
+
39
+ if options.present?
40
+ prepend_before_action do |controller|
41
+ controller.send(:scribo_value_layout=, options[:layout]) if options[:layout]
42
+ controller.send(:scribo_value_purpose=, options[:purpose]) if options[:purpose]
43
+ controller.send(:scribo_value_application_assets=, options[:assets])
44
+ end
45
+
46
+ layout 'scribo' if options[:layout]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add scribo as a renderer
4
+ module ActionController::Renderers
5
+ add :scribo do |request, options|
6
+ options.merge!(request: request, uri: URI.parse(request.original_url), host: request.host, path: URI.parse(request.original_url).path)
7
+
8
+ site = Scribo::SiteFindService.new(options).call
9
+ content = nil
10
+
11
+ unless site
12
+ # If we have no site, see if we have one when we add an /
13
+ site = Scribo::SiteFindService.new(options.merge(path: "#{options[:path]}/")).call
14
+ redirect_to("#{options[:path]}/") && return if site
15
+ end
16
+
17
+ if site && options[:path] == site.baseurl && !options[:path].ends_with?('/')
18
+ redirect_to("#{site.baseurl}/")
19
+ end
20
+
21
+ options.merge!(site: site)
22
+ site ||= Scribo::Site.default(request: request)
23
+ content ||= Scribo::ContentFindService.new(site, options).call
24
+
25
+ if content
26
+ self.content_type ||= Scribo::Utility.output_content_type(content)
27
+
28
+ Scribo.config.logger.info "Scribo: rendering #{content.id} last-updated #{content.updated_at} cache-key #{content.cache_key} path #{content.path} identifier #{content.identifier}"
29
+ if content.redirect?
30
+ redirect_options = Scribo::Content.redirect_options(Scribo::ContentRenderService.new(content, self, options).call)
31
+ redirect_to redirect_options.last, status: redirect_options.first
32
+ elsif stale?(etag: content.cache_key, public: true)
33
+ if content.kind == 'asset'
34
+ data = Rails.cache.fetch("#{content.cache_key}/asset", expires_in: 12.hours) { Scribo::ContentRenderService.new(content, self, options).call }
35
+ stream_data(data, type: content.content_type)
36
+ else
37
+ Scribo::ContentRenderService.new(content, self, options).call
38
+ end
39
+ else
40
+ head 304
41
+ end
42
+ else
43
+ render body: Scribo.config.default_404_txt, status: 404
44
+ end
45
+ end
46
+
47
+ # See: https://stackoverflow.com/a/57786143
48
+ def stream_data(data, options = {})
49
+ range_start = 0
50
+ file_size = data.length
51
+ range_end = file_size
52
+ status_code = '200'
53
+
54
+ if request.headers['Range']
55
+ status_code = '206'
56
+ request.headers['range'].match(/bytes=(\d+)-(\d*)/).try do |match|
57
+ range_start = match[1].to_i
58
+ range_end = match[2].to_i unless match[2] && match[2].empty?
59
+ end
60
+ response.header['Content-Range'] = "bytes #{range_start}-#{range_end}/#{file_size}"
61
+ end
62
+
63
+ response.header['Accept-Ranges'] = 'bytes'
64
+
65
+ send_data(data[range_start, range_end],
66
+ filename: options[:filename],
67
+ type: options[:type],
68
+ disposition: 'inline',
69
+ status: status_code)
70
+ end
71
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'app', 'drops', 'scribo', 'action_dispatch', 'request_drop.rb'))
4
+
5
+ module ActionViewHelpers
6
+ def layout_with_scribo(layout_name, yield_content)
7
+ options = { request: request, uri: URI.parse(request.original_url), host: request.host, path: URI.parse(request.original_url).path }
8
+ site = Scribo::SiteFindService.new(options).call
9
+
10
+ application_js = content_for?(:js) && content_for(:js)
11
+
12
+ content = site.contents.new(kind: 'text', data: yield_content, properties: { layout: layout_name })
13
+
14
+ registers = { controller: controller, application_assets: scribo_application_assets, application_js: application_js }
15
+ Scribo::ContentRenderService.new(content, self, registers: registers).call.html_safe
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordHelpers
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def scribable
8
+ has_many :sites, as: :scribable, class_name: 'Scribo::Site'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,97 @@
1
+ module Scribo
2
+ class Configuration
3
+ attr_accessor :admin_authentication_module, :base_controller, :supported_mime_types, :default_404_txt,
4
+ :default_humans_txt, :default_robots_txt, :default_favicon_ico, :templates
5
+ attr_writer :logger, :scribable_objects, :scribable_for_request, :after_site_create, :admin_mount_point, :default_site
6
+
7
+ def initialize
8
+ @logger = Logger.new(STDOUT)
9
+ @logger.level = Logger::INFO
10
+ @base_controller = '::ApplicationController'
11
+ @supported_mime_types = {
12
+ image: %w[image/gif image/png image/jpeg image/bmp image/webp image/svg+xml],
13
+ text: %w[text/plain text/html application/json application/xml],
14
+ style: %w[text/css],
15
+ script: %w[text/javascript application/javascript application/x-javascript],
16
+ audio: %w[audio/midi audio/mpeg audio/webm audio/ogg audio/wav],
17
+ video: %w[video/webm video/ogg video/mp4],
18
+ document: %w[application/msword application/vnd.ms-powerpoint application/vnd.ms-excel application/pdf
19
+ application/zip],
20
+ font: %w[font/collection font/otf font/sfnt font/ttf font/woff font/woff2 application/font-ttf
21
+ application/x-font-ttf application/vnd.ms-fontobject application/font-woff],
22
+ other: %w[application/octet-stream]
23
+ }
24
+ @default_404_txt = '404 Not Found'
25
+ @default_humans_txt = <<~HUMANS_TXT
26
+ /* TEAM */
27
+ Your title: Your name.
28
+ Site: email, link to a contact form, etc.
29
+ Twitter: your Twitter username.
30
+ Location: City, Country.
31
+
32
+ [...]
33
+
34
+ /* THANKS */
35
+ Name: name or url
36
+
37
+ [...]
38
+
39
+ /* SITE */
40
+ Last update: YYYY/MM/DD
41
+ Standards: HTML5, CSS3,..
42
+ Components: Modernizr, jQuery, etc.
43
+ Software: Software used for the development
44
+ HUMANS_TXT
45
+ @default_robots_txt = <<~ROBOTS_TXT
46
+ # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
47
+ #
48
+ # To ban all spiders from the entire site uncomment the next two lines:
49
+ # User-agent: *
50
+ # Disallow: /
51
+ ROBOTS_TXT
52
+
53
+ # Base64 encoded image/x-icon
54
+ @default_favicon_ico = 'AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEcrEvoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsHIH/7d2Bv+3dgb/t3YG/7d2Bv0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt3YG/0crEv+3dgb/t3YG/7d2Bv8AAAAAt3YG/7d2Bv90Vx0DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALd2Bv+3dgb/t3YG/7d2Bv+3dgb/t3YG/7d2Bv+3dgb/t3YG/3VLBEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3dgb/t3YG/0crEv+3dgb/t3YG/7d2Bv+3dgb/t3YG/7d2Bv+3dgb+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt3YG/bd2Bv+3dgb/qGwI/7d2Bv+3dgb/t3YG/7d2Bv8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3dgb/t3YG/7d2Bv+3dgb/t3YG/7d2Bv+3dgb/t3YG/7d2Bv+3dgb/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt3YG/7d2Bv+3dgb/lV8K/7d2Bv+3dgb/t3YG/7d2Bv+3dgb/t3YG/7h2B9EAAAAAAAAAAAAAAAAAAAAAAAAAALd2Bv+3dgb/t3YG/7d2Bv+JWAv/t3YG/7d2Bv+3dgb/t3YG/7d2Bv+3dgb/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt3YG/7d2Bv+3dgb/t3YG/7d2Bv+3dgb/t3YG/7d2Bv+3dgb/t3YG/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALd2Bvi3dgb/t3YG+7d2Bv+3dgb/t3YG/7d2Bv+3dgb/t3YG/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt3YG/wAAAAC3dgb/t3YG/7d2Bv+3dgb/t3YG/7d2Bv+3dgb/t3YG8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt3YG/7d2Bv+3dgb/t3YG/7d2Bv+3dgb/t3YG/7d2Bv8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3dgb/t3YG/2hDA3O3dgb/t3YG/7d2Bv+3dgb/t3YG/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4dgeJAAAAALd2Bv+3dgb/t3YG/7d2Bv+zcgZ3//8AAL//AADB/wAAgn8AAIA/AACAHwAAgH8AAMAPAADABwAAwAcAAOAHAADgDwAA9AMAAPwDAAD+QQAA/6EAAA=='
55
+
56
+ # This needs an array of hashes, each hash MUST include an id, thumbnail and a url:
57
+ # [
58
+ # { id: '7becd952-ae77-43ad-9bf7-ed4f8feb59fa', thumbnail: 'https://mysite.net/template1.png', url: 'https://mysite.net/template1.zip' }
59
+ # ]
60
+ #
61
+ # The id can be generated using: `SecureRandom.uuid`, or you can use integers
62
+ @templates = []
63
+
64
+ # Default site if no site is found, this can be nil, an actual site instance or a proc which will resolve a site instance.
65
+ @default_site = nil
66
+ end
67
+
68
+ # logger [Object].
69
+ def logger
70
+ @logger.is_a?(Proc) ? instance_exec(&@logger) : @logger
71
+ end
72
+
73
+ def default_site(request = nil)
74
+ @default_site.is_a?(Proc) ? instance_exec(request, &@default_site) : @default_site
75
+ end
76
+
77
+ # admin_mount_point [String].
78
+ def admin_mount_point
79
+ @admin_mount_point ||= '/'
80
+ end
81
+
82
+ # Only used to limit what users can see when using admin
83
+ def scribable_objects
84
+ [*instance_exec(&@scribable_objects)] if @scribable_objects
85
+ end
86
+
87
+ # Which scribable to use for the current request, should only return one scribable
88
+ def scribable_for_request(request)
89
+ instance_exec(request, &@scribable_for_request) if @scribable_for_request
90
+ end
91
+
92
+ # What to do after a site create (only for new sites, not imported sites)
93
+ def after_site_create(site)
94
+ instance_exec(site, &@after_site_create) if @after_site_create
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n'
4
+
5
+ module Scribo
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace Scribo
8
+
9
+ initializer 'scribo.config' do |_app|
10
+ path = File.expand_path(File.join(File.dirname(__FILE__), '.', 'liquid', '{tags,filters}', '*.rb'))
11
+ Dir.glob(path).each do |c|
12
+ require_dependency(c)
13
+ end
14
+ end
15
+
16
+ initializer :append_migrations do |app|
17
+ unless app.root.to_s.match? root.to_s
18
+ config.paths['db/migrate'].expanded.each do |expanded_path|
19
+ app.config.paths['db/migrate'] << expanded_path
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'i18n/core_ext/hash'
4
+
5
+ module Scribo
6
+ begin
7
+ require 'oj'
8
+ class JSON
9
+ class << self
10
+ def encode(value)
11
+ Oj::Rails.encode(value)
12
+ end
13
+
14
+ def decode(value)
15
+ Oj.load(value)
16
+ end
17
+ end
18
+ end
19
+ rescue LoadError
20
+ require 'active_support/json'
21
+ JSON = ActiveSupport::JSON
22
+ end
23
+
24
+ class I18nStore
25
+ def initialize; end
26
+
27
+ def keys
28
+ return [] unless content
29
+
30
+ result = []
31
+ locales.each do |locale|
32
+ result += flat_hash(YAML.safe_load(locale.data)).keys
33
+ end
34
+ result
35
+ end
36
+
37
+ def [](key)
38
+ return unless content
39
+
40
+ result = {}
41
+ locales.each do |locale|
42
+ hash = flat_hash(YAML.safe_load(locale.data)).transform_values { |v| JSON.encode(v) }
43
+ result.merge! hash
44
+ end
45
+ result[key]
46
+ end
47
+
48
+ def []=(key, value)
49
+ # NOOP
50
+ end
51
+
52
+ def with(content)
53
+ with_custom_backend do
54
+ self.content = content
55
+ yield(content)
56
+ self.content = nil
57
+ end
58
+ end
59
+
60
+ def content
61
+ Thread.current[:scribo_i18n_store_content]
62
+ end
63
+
64
+ def content=(content)
65
+ Thread.current[:scribo_i18n_store_content] = content
66
+ end
67
+
68
+ private
69
+
70
+ def with_custom_backend
71
+ Thread.current[:scribo_i18n_store_old_backend] = I18n.backend
72
+ I18n.backend = I18n::Backend::Chain.new(custom_i18n_backend, I18n.backend)
73
+
74
+ yield
75
+
76
+ I18n.backend = Thread.current[:scribo_i18n_store_old_backend]
77
+ end
78
+
79
+ def locales
80
+ content.site.contents.locales
81
+ end
82
+
83
+ def flat_hash(hash)
84
+ hash.reduce({}) do |a, (k, v)|
85
+ tmp = v.is_a?(Hash) ? flat_hash(v).map { |k2, v2| ["#{k}.#{k2}", v2] }.to_h : { k => v }
86
+ a.merge(tmp)
87
+ end
88
+ end
89
+
90
+ # Yield our own custom backend
91
+ def custom_i18n_backend
92
+ return @custom_i18n_backend if @custom_i18n_backend
93
+
94
+ @custom_i18n_backend = I18n::Backend::KeyValue.new(Scribo.i18n_store)
95
+ @custom_i18n_backend.class.send(:include, I18n::Backend::Cascade)
96
+
97
+ @custom_i18n_backend
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,94 @@
1
+ module JekyllFilters
2
+ # Filter an array of objects
3
+ #
4
+ # input - the object array.
5
+ # property - the property within each object to filter by.
6
+ # value - the desired value.
7
+ # Cannot be an instance of Array nor Hash since calling #to_s on them returns
8
+ # their `#inspect` string object.
9
+ #
10
+ # Returns the filtered array of objects
11
+ def where(input, property, value)
12
+ return input if !property || value.is_a?(Array) || value.is_a?(Hash)
13
+ return input unless input.respond_to?(:select)
14
+
15
+ input = input.values if input.is_a?(Hash)
16
+ input_id = input.hash
17
+
18
+ # implement a hash based on method parameters to cache the end-result
19
+ # for given parameters.
20
+ @where_filter_cache ||= {}
21
+ @where_filter_cache[input_id] ||= {}
22
+ @where_filter_cache[input_id][property] ||= {}
23
+
24
+ # stash or retrive results to return
25
+ @where_filter_cache[input_id][property][value] ||= input.select do |object|
26
+ compare_property_vs_target(item_property(object, property), value)
27
+ end.to_a
28
+ end
29
+
30
+ # `where` filter helper
31
+ #
32
+ def compare_property_vs_target(property, target)
33
+ case target
34
+ when NilClass
35
+ return true if property.nil?
36
+ when Liquid::Expression::MethodLiteral # `empty` or `blank`
37
+ target = target.to_s
38
+ return true if property == target || Array(property).join == target
39
+ else
40
+ target = target.to_s
41
+ if property.is_a? String
42
+ return true if property == target
43
+ else
44
+ Array(property).each do |prop|
45
+ return true if prop.to_s == target
46
+ end
47
+ end
48
+ end
49
+
50
+ false
51
+ end
52
+
53
+ def item_property(item, property)
54
+ # Jekyll uses :site here, but we use strings?
55
+ @item_property_cache ||= @context.registers['site'].filter_cache[:item_property] ||= {}
56
+ @item_property_cache[property] ||= {}
57
+ @item_property_cache[property][item] ||= begin
58
+ property = property.to_s
59
+ property = if item.respond_to?(:to_liquid)
60
+ read_liquid_attribute(item.to_liquid, property)
61
+ elsif item.respond_to?(:data)
62
+ item.data[property]
63
+ else
64
+ item[property]
65
+ end
66
+
67
+ parse_sort_input(property)
68
+ end
69
+ end
70
+
71
+ def read_liquid_attribute(liquid_data, property)
72
+ return liquid_data[property] unless property.include?('.')
73
+
74
+ property.split('.').reduce(liquid_data) do |data, key|
75
+ data.respond_to?(:[]) && data[key]
76
+ end
77
+ end
78
+
79
+ FLOAT_LIKE = /\A\s*-?(?:\d+\.?\d*|\.\d+)\s*\Z/.freeze
80
+ INTEGER_LIKE = /\A\s*-?\d+\s*\Z/.freeze
81
+ private_constant :FLOAT_LIKE, :INTEGER_LIKE
82
+
83
+ # return numeric values as numbers for proper sorting
84
+ def parse_sort_input(property)
85
+ stringified = property.to_s
86
+ return property.to_i if INTEGER_LIKE.match?(stringified)
87
+ return property.to_f if FLOAT_LIKE.match?(stringified)
88
+
89
+ property
90
+ end
91
+ end
92
+
93
+ # TDG: Friday Sept 17th 2021 - Removed these, they bite the built-in where filter.
94
+ # Liquid::Template.register_filter(JekyllFilters)