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