scribo 1.0.38
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +93 -0
- data/Rakefile +40 -0
- data/app/assets/config/scribo_manifest.js +2 -0
- data/app/assets/javascripts/scribo/scribo.js +1 -0
- data/app/assets/stylesheets/scribo/scribo.css +1 -0
- data/app/controllers/concerns/maintenance_standards.rb +24 -0
- data/app/controllers/scribo/admin/sites/contents_controller.rb +86 -0
- data/app/controllers/scribo/admin/sites_controller.rb +61 -0
- data/app/controllers/scribo/api/sites_controller.rb +23 -0
- data/app/controllers/scribo/application_admin_controller.rb +11 -0
- data/app/controllers/scribo/application_controller.rb +6 -0
- data/app/controllers/scribo/contents_controller.rb +20 -0
- data/app/drops/scribo/action_dispatch/request_drop.rb +15 -0
- data/app/drops/scribo/active_model/errors_drop.rb +16 -0
- data/app/drops/scribo/application_drop.rb +9 -0
- data/app/drops/scribo/array_drop.rb +13 -0
- data/app/drops/scribo/content_drop.rb +73 -0
- data/app/drops/scribo/data_drop.rb +35 -0
- data/app/drops/scribo/form_drop.rb +26 -0
- data/app/drops/scribo/include_drop.rb +13 -0
- data/app/drops/scribo/paginator_drop.rb +73 -0
- data/app/drops/scribo/site_drop.rb +83 -0
- data/app/helpers/scribo/application_helper.rb +13 -0
- data/app/jobs/scribo/application_job.rb +6 -0
- data/app/models/scribo/application_record.rb +11 -0
- data/app/models/scribo/content.rb +378 -0
- data/app/models/scribo/site.rb +161 -0
- data/app/services/scribo/application_service.rb +9 -0
- data/app/services/scribo/content_find_service.rb +69 -0
- data/app/services/scribo/content_render_service.rb +127 -0
- data/app/services/scribo/site_export_service.rb +41 -0
- data/app/services/scribo/site_find_service.rb +26 -0
- data/app/services/scribo/site_import_service.rb +96 -0
- data/app/views/layouts/scribo.html.slim +1 -0
- data/app/views/scribo/admin/sites/_create_sites.html.slim +22 -0
- data/app/views/scribo/admin/sites/_sites.html.slim +18 -0
- data/app/views/scribo/admin/sites/contents/_form.html.slim +17 -0
- data/app/views/scribo/admin/sites/contents/create.json.jbuilder +11 -0
- data/app/views/scribo/admin/sites/contents/edit.html.slim +2 -0
- data/app/views/scribo/admin/sites/contents/edit.json.jbuilder +9 -0
- data/app/views/scribo/admin/sites/contents/index.html.slim +1 -0
- data/app/views/scribo/admin/sites/contents/move.json.jbuilder +9 -0
- data/app/views/scribo/admin/sites/contents/rename.json.jbuilder +9 -0
- data/app/views/scribo/admin/sites/contents/upload.json.jbuilder +9 -0
- data/app/views/scribo/admin/sites/import.json.jbuilder +4 -0
- data/app/views/scribo/admin/sites/index.html.slim +7 -0
- data/app/views/scribo/shared/_entry.html.slim +17 -0
- data/app/views/scribo/shared/_ide.html.slim +14 -0
- data/app/views/scribo/shared/_tree-view.html.slim +32 -0
- data/config/locales/en.yml +39 -0
- data/config/routes.rb +35 -0
- data/db/migrate/20200123095630_initial_scribo.rb +34 -0
- data/db/migrate/20220919124119_add_ancestry_to_scribo_contents.rb +13 -0
- data/db/migrate/20220919124749_remove_parent_id_from_scribo_contents.rb +8 -0
- data/lib/scribo/action_controller_helpers.rb +51 -0
- data/lib/scribo/action_controller_renderers.rb +71 -0
- data/lib/scribo/action_view_helpers.rb +17 -0
- data/lib/scribo/active_record_helpers.rb +11 -0
- data/lib/scribo/configuration.rb +97 -0
- data/lib/scribo/engine.rb +24 -0
- data/lib/scribo/i18n_store.rb +100 -0
- data/lib/scribo/liquid/filters/jekyll_filters.rb +94 -0
- data/lib/scribo/liquid/filters/markdownify.rb +11 -0
- data/lib/scribo/liquid/filters/url_filters.rb +76 -0
- data/lib/scribo/liquid/parser.rb +32 -0
- data/lib/scribo/liquid/tags/application_assets_tag.rb +14 -0
- data/lib/scribo/liquid/tags/application_js_tag.rb +16 -0
- data/lib/scribo/liquid/tags/asset_tag.rb +56 -0
- data/lib/scribo/liquid/tags/button_tag.rb +24 -0
- data/lib/scribo/liquid/tags/check_box_tag.rb +31 -0
- data/lib/scribo/liquid/tags/csrf_meta_tags_tag.rb +16 -0
- data/lib/scribo/liquid/tags/date_field_tag.rb +23 -0
- data/lib/scribo/liquid/tags/editable_url_tag.rb +19 -0
- data/lib/scribo/liquid/tags/email_field_tag.rb +23 -0
- data/lib/scribo/liquid/tags/feed_meta_tag.rb +22 -0
- data/lib/scribo/liquid/tags/field_error_tag.rb +26 -0
- data/lib/scribo/liquid/tags/fields_for_tag.rb +82 -0
- data/lib/scribo/liquid/tags/form_tag.rb +57 -0
- data/lib/scribo/liquid/tags/google_analytics_javascript_tag.rb +30 -0
- data/lib/scribo/liquid/tags/google_tag_manager_javascript_tag.rb +28 -0
- data/lib/scribo/liquid/tags/hidden_field_tag.rb +23 -0
- data/lib/scribo/liquid/tags/highlight_tag.rb +106 -0
- data/lib/scribo/liquid/tags/include_tag.rb +28 -0
- data/lib/scribo/liquid/tags/label_tag.rb +24 -0
- data/lib/scribo/liquid/tags/number_field_tag.rb +23 -0
- data/lib/scribo/liquid/tags/options_for_select.rb +36 -0
- data/lib/scribo/liquid/tags/password_field_tag.rb +23 -0
- data/lib/scribo/liquid/tags/search_field_tag.rb +23 -0
- data/lib/scribo/liquid/tags/search_tag.rb +29 -0
- data/lib/scribo/liquid/tags/select_tag.rb +22 -0
- data/lib/scribo/liquid/tags/seo_tag.rb +35 -0
- data/lib/scribo/liquid/tags/set_session.rb +19 -0
- data/lib/scribo/liquid/tags/telephone_field_tag.rb +23 -0
- data/lib/scribo/liquid/tags/text_field_tag.rb +35 -0
- data/lib/scribo/liquid/tags/textarea_tag.rb +28 -0
- data/lib/scribo/liquid/tags/time_field_tag.rb +23 -0
- data/lib/scribo/liquid/tags/url_field_tag.rb +23 -0
- data/lib/scribo/preamble.rb +89 -0
- data/lib/scribo/sassc/importer.rb +72 -0
- data/lib/scribo/utility.rb +94 -0
- data/lib/scribo/version.rb +5 -0
- data/lib/scribo.rb +59 -0
- data/lib/tasks/scribo_tasks.rake +28 -0
- 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,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,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)
|