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,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)