plum-cms 0.1.0
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/CHANGELOG.md +20 -0
- data/LICENSE.txt +21 -0
- data/README.md +252 -0
- data/app/assets/builds/tailwind.css +2 -0
- data/app/assets/images/plum-mark.svg +7 -0
- data/app/assets/images/table-needs-logo.svg +83 -0
- data/app/assets/javascripts/plum/application.js +15 -0
- data/app/assets/stylesheets/application.css +10 -0
- data/app/assets/stylesheets/plum/control_panel.css +2 -0
- data/app/assets/tailwind/application.css +47 -0
- data/app/controllers/plum/application_controller.rb +34 -0
- data/app/controllers/plum/cp/assets_controller.rb +84 -0
- data/app/controllers/plum/cp/base_controller.rb +45 -0
- data/app/controllers/plum/cp/content_types_controller.rb +59 -0
- data/app/controllers/plum/cp/dashboard_controller.rb +10 -0
- data/app/controllers/plum/cp/entries_controller.rb +240 -0
- data/app/controllers/plum/cp/form_definitions_controller.rb +105 -0
- data/app/controllers/plum/cp/form_submissions_controller.rb +26 -0
- data/app/controllers/plum/cp/globals_controller.rb +84 -0
- data/app/controllers/plum/cp/nav_items_controller.rb +68 -0
- data/app/controllers/plum/cp/nav_menus_controller.rb +56 -0
- data/app/controllers/plum/cp/site_settings_controller.rb +87 -0
- data/app/controllers/plum/cp/taxonomies_controller.rb +56 -0
- data/app/controllers/plum/cp/terms_controller.rb +55 -0
- data/app/controllers/plum/cp/theme_previews_controller.rb +27 -0
- data/app/controllers/plum/cp/themes_controller.rb +98 -0
- data/app/controllers/plum/form_submissions_controller.rb +44 -0
- data/app/controllers/plum/pages_controller.rb +161 -0
- data/app/controllers/plum/sessions_controller.rb +25 -0
- data/app/controllers/plum/theme_assets_controller.rb +14 -0
- data/app/helpers/application_helper.rb +2 -0
- data/app/helpers/markdown_helper.rb +21 -0
- data/app/helpers/plum/cp/assets_helper.rb +21 -0
- data/app/helpers/plum/cp/theme_settings_helper.rb +26 -0
- data/app/javascript/application.js +18 -0
- data/app/javascript/controllers/application.js +9 -0
- data/app/javascript/controllers/index.js +4 -0
- data/app/javascript/controllers/plum/blocks_editor_controller.js +275 -0
- data/app/javascript/controllers/plum/blueprint_controller.js +120 -0
- data/app/javascript/controllers/plum/form_fields_controller.js +84 -0
- data/app/javascript/controllers/plum/image_picker_controller.js +143 -0
- data/app/javascript/controllers/plum/theme_settings_controller.js +17 -0
- data/app/models/plum/application_record.rb +5 -0
- data/app/models/plum/asset.rb +69 -0
- data/app/models/plum/content_type.rb +35 -0
- data/app/models/plum/entry.rb +67 -0
- data/app/models/plum/entry_term.rb +8 -0
- data/app/models/plum/form_definition.rb +68 -0
- data/app/models/plum/form_submission.rb +76 -0
- data/app/models/plum/global.rb +24 -0
- data/app/models/plum/nav_item.rb +58 -0
- data/app/models/plum/nav_menu.rb +24 -0
- data/app/models/plum/site.rb +112 -0
- data/app/models/plum/site_scoped.rb +17 -0
- data/app/models/plum/site_setting.rb +35 -0
- data/app/models/plum/taxonomy.rb +24 -0
- data/app/models/plum/term.rb +23 -0
- data/app/models/plum/user.rb +16 -0
- data/app/services/plum/base_blocks.rb +161 -0
- data/app/services/plum/block_editor_config.rb +65 -0
- data/app/services/plum/block_library.rb +31 -0
- data/app/services/plum/builder_renderer.rb +63 -0
- data/app/services/plum/field_expander.rb +78 -0
- data/app/services/plum/form_renderer.rb +110 -0
- data/app/services/plum/liquid_context.rb +335 -0
- data/app/services/plum/liquid_filters.rb +58 -0
- data/app/services/plum/liquid_renderer.rb +49 -0
- data/app/services/plum/theme.rb +160 -0
- data/app/services/plum/theme_asset_path.rb +31 -0
- data/app/services/plum/theme_package_installer.rb +287 -0
- data/app/services/plum/theme_registry.rb +63 -0
- data/app/services/plum/theme_resolver.rb +55 -0
- data/app/services/plum/theme_settings_params.rb +54 -0
- data/app/themes/bagel-boy/assets/screenshot.svg +92 -0
- data/app/themes/bagel-boy/assets/theme.css +184 -0
- data/app/themes/bagel-boy/blocks/hours.liquid +4 -0
- data/app/themes/bagel-boy/blocks/menu_item.liquid +7 -0
- data/app/themes/bagel-boy/layouts/base.liquid +64 -0
- data/app/themes/bagel-boy/templates/collections/posts.liquid +32 -0
- data/app/themes/bagel-boy/templates/entries/_default.liquid +13 -0
- data/app/themes/bagel-boy/templates/entries/landing.liquid +4 -0
- data/app/themes/bagel-boy/templates/entries/pages.liquid +18 -0
- data/app/themes/bagel-boy/templates/entries/posts.liquid +8 -0
- data/app/themes/bagel-boy/templates/index.liquid +10 -0
- data/app/themes/bagel-boy/templates/search.liquid +28 -0
- data/app/themes/bagel-boy/theme.yml +47 -0
- data/app/themes/bagel-shop/assets/screenshot.svg +28 -0
- data/app/themes/bagel-shop/assets/theme.css +7 -0
- data/app/themes/bagel-shop/layouts/base.liquid +507 -0
- data/app/themes/bagel-shop/templates/entries/_default.liquid +15 -0
- data/app/themes/bagel-shop/templates/entries/pages.liquid +12 -0
- data/app/themes/bagel-shop/templates/entries/posts.liquid +19 -0
- data/app/themes/bagel-shop/templates/index.liquid +55 -0
- data/app/themes/bagel-shop/theme.yml +30 -0
- data/app/themes/default/assets/screenshot.svg +24 -0
- data/app/themes/default/assets/theme.css +89 -0
- data/app/themes/default/layouts/base.liquid +117 -0
- data/app/themes/default/templates/collections/_default.liquid +16 -0
- data/app/themes/default/templates/collections/posts.liquid +27 -0
- data/app/themes/default/templates/entries/_default.liquid +26 -0
- data/app/themes/default/templates/entries/landing.liquid +5 -0
- data/app/themes/default/templates/entries/pages.liquid +17 -0
- data/app/themes/default/templates/entries/posts.liquid +20 -0
- data/app/themes/default/templates/index.liquid +22 -0
- data/app/themes/default/templates/search.liquid +28 -0
- data/app/themes/default/templates/taxonomies/_default.liquid +25 -0
- data/app/themes/default/templates/taxonomies/_default_index.liquid +14 -0
- data/app/themes/default/theme.yml +23 -0
- data/app/views/layouts/plum/cp.html.erb +138 -0
- data/app/views/layouts/plum/session.html.erb +43 -0
- data/app/views/plum/cp/assets/_form.html.erb +44 -0
- data/app/views/plum/cp/assets/edit.html.erb +14 -0
- data/app/views/plum/cp/assets/index.html.erb +52 -0
- data/app/views/plum/cp/assets/new.html.erb +9 -0
- data/app/views/plum/cp/content_types/_form.html.erb +113 -0
- data/app/views/plum/cp/content_types/edit.html.erb +8 -0
- data/app/views/plum/cp/content_types/index.html.erb +48 -0
- data/app/views/plum/cp/content_types/new.html.erb +5 -0
- data/app/views/plum/cp/content_types/show.html.erb +59 -0
- data/app/views/plum/cp/dashboard/show.html.erb +50 -0
- data/app/views/plum/cp/entries/_form.html.erb +196 -0
- data/app/views/plum/cp/entries/edit.html.erb +9 -0
- data/app/views/plum/cp/entries/index.html.erb +51 -0
- data/app/views/plum/cp/entries/new.html.erb +5 -0
- data/app/views/plum/cp/form_definitions/_form.html.erb +90 -0
- data/app/views/plum/cp/form_definitions/edit.html.erb +11 -0
- data/app/views/plum/cp/form_definitions/index.html.erb +43 -0
- data/app/views/plum/cp/form_definitions/new.html.erb +5 -0
- data/app/views/plum/cp/form_definitions/show.html.erb +70 -0
- data/app/views/plum/cp/form_submissions/show.html.erb +20 -0
- data/app/views/plum/cp/globals/_form.html.erb +44 -0
- data/app/views/plum/cp/globals/edit.html.erb +11 -0
- data/app/views/plum/cp/globals/index.html.erb +41 -0
- data/app/views/plum/cp/globals/new.html.erb +5 -0
- data/app/views/plum/cp/globals/show.html.erb +14 -0
- data/app/views/plum/cp/nav_items/_form.html.erb +56 -0
- data/app/views/plum/cp/nav_items/edit.html.erb +11 -0
- data/app/views/plum/cp/nav_items/new.html.erb +6 -0
- data/app/views/plum/cp/nav_menus/_form.html.erb +33 -0
- data/app/views/plum/cp/nav_menus/_nav_item.html.erb +24 -0
- data/app/views/plum/cp/nav_menus/edit.html.erb +11 -0
- data/app/views/plum/cp/nav_menus/index.html.erb +41 -0
- data/app/views/plum/cp/nav_menus/new.html.erb +5 -0
- data/app/views/plum/cp/nav_menus/show.html.erb +24 -0
- data/app/views/plum/cp/shared/_icon.html.erb +39 -0
- data/app/views/plum/cp/shared/_image_picker.html.erb +38 -0
- data/app/views/plum/cp/site_settings/edit.html.erb +145 -0
- data/app/views/plum/cp/site_settings/show.html.erb +62 -0
- data/app/views/plum/cp/taxonomies/_form.html.erb +38 -0
- data/app/views/plum/cp/taxonomies/edit.html.erb +2 -0
- data/app/views/plum/cp/taxonomies/index.html.erb +38 -0
- data/app/views/plum/cp/taxonomies/new.html.erb +2 -0
- data/app/views/plum/cp/taxonomies/show.html.erb +46 -0
- data/app/views/plum/cp/terms/_form.html.erb +30 -0
- data/app/views/plum/cp/terms/edit.html.erb +2 -0
- data/app/views/plum/cp/terms/new.html.erb +2 -0
- data/app/views/plum/cp/themes/index.html.erb +160 -0
- data/app/views/plum/form_mailer/submission_notification.html.erb +13 -0
- data/app/views/plum/form_mailer/submission_notification.text.erb +7 -0
- data/app/views/plum/sessions/new.html.erb +20 -0
- data/compat/plum-cms.rb +1 -0
- data/config/locales/en.yml +31 -0
- data/config/plum_importmap.rb +4 -0
- data/config/plum_routes.rb +34 -0
- data/db/engine_migrate/20260101000001_create_plum_tables.rb +154 -0
- data/lib/generators/plum/install/install_generator.rb +54 -0
- data/lib/generators/plum/install/templates/plum_initializer.rb +37 -0
- data/lib/plum/configuration.rb +88 -0
- data/lib/plum/content_source.rb +45 -0
- data/lib/plum/content_source_context.rb +30 -0
- data/lib/plum/content_source_registry.rb +120 -0
- data/lib/plum/engine.rb +30 -0
- data/lib/plum/liquid_tags/form_tag.rb +17 -0
- data/lib/plum/version.rb +7 -0
- data/lib/plum.rb +11 -0
- data/vendor/javascript/lexxy.js +11859 -0
- metadata +405 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Plum
|
|
2
|
+
module Cp
|
|
3
|
+
class NavItemsController < BaseController
|
|
4
|
+
before_action :set_nav_menu
|
|
5
|
+
before_action :set_nav_item, only: [ :edit, :update, :destroy ]
|
|
6
|
+
before_action :set_form_options, only: [ :new, :create, :edit, :update ]
|
|
7
|
+
before_action :require_editor, only: [ :new, :create, :edit, :update, :destroy ]
|
|
8
|
+
|
|
9
|
+
def new
|
|
10
|
+
@nav_item = @nav_menu.nav_items.build(site: current_site, position: next_position)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create
|
|
14
|
+
@nav_item = @nav_menu.nav_items.build(nav_item_params)
|
|
15
|
+
@nav_item.site = current_site
|
|
16
|
+
|
|
17
|
+
if @nav_item.save
|
|
18
|
+
redirect_to cp_nav_menu_path(@nav_menu), notice: "Navigation item created"
|
|
19
|
+
else
|
|
20
|
+
render :new, status: :unprocessable_entity
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def edit
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def update
|
|
28
|
+
if @nav_item.update(nav_item_params)
|
|
29
|
+
redirect_to cp_nav_menu_path(@nav_menu), notice: "Navigation item updated"
|
|
30
|
+
else
|
|
31
|
+
render :edit, status: :unprocessable_entity
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def destroy
|
|
36
|
+
@nav_item.destroy
|
|
37
|
+
redirect_to cp_nav_menu_path(@nav_menu), notice: "Navigation item deleted"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def set_nav_menu
|
|
43
|
+
@nav_menu = current_site.nav_menus.find(params[:nav_menu_id])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def set_nav_item
|
|
47
|
+
@nav_item = @nav_menu.nav_items.find(params[:id])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def set_form_options
|
|
51
|
+
@entries = current_site.entries.includes(:content_type).order(:title)
|
|
52
|
+
@parent_options = @nav_menu.nav_items.where.not(id: @nav_item&.id).includes(:entry).order(:position, :label)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def next_position
|
|
56
|
+
@nav_menu.nav_items.maximum(:position).to_i + 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def nav_item_params
|
|
60
|
+
permitted = params.require(:nav_item).permit(:label, :url, :entry_id, :parent_id, :position)
|
|
61
|
+
permitted[:entry_id] = nil if permitted[:entry_id].blank?
|
|
62
|
+
permitted[:parent_id] = nil if permitted[:parent_id].blank?
|
|
63
|
+
permitted[:position] = next_position if permitted[:position].blank?
|
|
64
|
+
permitted
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Plum
|
|
2
|
+
module Cp
|
|
3
|
+
class NavMenusController < BaseController
|
|
4
|
+
before_action :set_nav_menu, only: [ :show, :edit, :update, :destroy ]
|
|
5
|
+
before_action :require_editor, only: [ :new, :create, :edit, :update, :destroy ]
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
@nav_menus = current_site.nav_menus.includes(:nav_items).order(:name)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def show
|
|
12
|
+
@nav_items = @nav_menu.root_items.includes(:entry, children: :entry)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def new
|
|
16
|
+
@nav_menu = current_site.nav_menus.build
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create
|
|
20
|
+
@nav_menu = current_site.nav_menus.build(nav_menu_params)
|
|
21
|
+
|
|
22
|
+
if @nav_menu.save
|
|
23
|
+
redirect_to cp_nav_menu_path(@nav_menu), notice: "Navigation menu created"
|
|
24
|
+
else
|
|
25
|
+
render :new, status: :unprocessable_entity
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def edit
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def update
|
|
33
|
+
if @nav_menu.update(nav_menu_params)
|
|
34
|
+
redirect_to cp_nav_menu_path(@nav_menu), notice: "Navigation menu updated"
|
|
35
|
+
else
|
|
36
|
+
render :edit, status: :unprocessable_entity
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def destroy
|
|
41
|
+
@nav_menu.destroy
|
|
42
|
+
redirect_to cp_nav_menus_path, notice: "Navigation menu deleted"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def set_nav_menu
|
|
48
|
+
@nav_menu = current_site.nav_menus.find(params[:id])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def nav_menu_params
|
|
52
|
+
params.require(:nav_menu).permit(:name, :handle)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Plum
|
|
2
|
+
module Cp
|
|
3
|
+
class SiteSettingsController < BaseController
|
|
4
|
+
helper_method :theme_setting_input_id,
|
|
5
|
+
:theme_setting_input_name,
|
|
6
|
+
:theme_setting_value,
|
|
7
|
+
:theme_setting_checked?
|
|
8
|
+
|
|
9
|
+
before_action :set_site_settings
|
|
10
|
+
before_action :set_theme_options
|
|
11
|
+
before_action :require_admin, only: [ :edit, :update ]
|
|
12
|
+
|
|
13
|
+
def show
|
|
14
|
+
redirect_to edit_cp_site_settings_path
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def edit
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update
|
|
21
|
+
settings_attributes = site_settings_params
|
|
22
|
+
theme_settings_by_theme = settings_attributes.delete(:theme_settings_by_theme).to_h
|
|
23
|
+
@site_settings.assign_attributes(settings_attributes)
|
|
24
|
+
|
|
25
|
+
if @site_settings.save
|
|
26
|
+
current_site.update!(theme_settings: theme_settings_for(@site_settings.theme_name, theme_settings_by_theme))
|
|
27
|
+
redirect_to edit_cp_site_settings_path, notice: "Site settings updated"
|
|
28
|
+
else
|
|
29
|
+
set_theme_options
|
|
30
|
+
render :edit, status: :unprocessable_entity
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def set_site_settings
|
|
37
|
+
@site_settings = SiteSetting.instance(current_site)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def set_theme_options
|
|
41
|
+
registry = ThemeRegistry.new
|
|
42
|
+
@themes = registry.all
|
|
43
|
+
@current_theme = registry.fetch(@site_settings.theme_name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def theme_settings_for(theme_name, theme_settings_by_theme)
|
|
47
|
+
theme = ThemeRegistry.new.fetch(theme_name)
|
|
48
|
+
raw_settings = theme_settings_by_theme[theme.handle] || {}
|
|
49
|
+
ThemeSettingsParams.new(theme).normalize(raw_settings)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def theme_setting_input_id(theme, field)
|
|
53
|
+
"theme_settings_#{theme.handle.parameterize}_#{field['handle'].to_s.parameterize}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def theme_setting_input_name(theme, field)
|
|
57
|
+
"site_setting[theme_settings_by_theme][#{theme.handle}][#{field['handle']}]"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def theme_setting_value(field)
|
|
61
|
+
settings = current_site.theme_settings.to_h
|
|
62
|
+
handle = field["handle"].to_s
|
|
63
|
+
|
|
64
|
+
settings.key?(handle) ? settings[handle] : field["default"]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def theme_setting_checked?(field)
|
|
68
|
+
ActiveModel::Type::Boolean.new.cast(theme_setting_value(field))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def site_settings_params
|
|
72
|
+
params.require(:site_setting).permit(
|
|
73
|
+
:name,
|
|
74
|
+
:tagline,
|
|
75
|
+
:logo,
|
|
76
|
+
:favicon,
|
|
77
|
+
:seo_title,
|
|
78
|
+
:seo_description,
|
|
79
|
+
:primary_color,
|
|
80
|
+
:support_email,
|
|
81
|
+
:theme_name,
|
|
82
|
+
theme_settings_by_theme: {}
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Plum
|
|
2
|
+
module Cp
|
|
3
|
+
class TaxonomiesController < BaseController
|
|
4
|
+
before_action :set_taxonomy, only: [ :show, :edit, :update, :destroy ]
|
|
5
|
+
before_action :require_admin, only: [ :new, :create, :edit, :update, :destroy ]
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
@taxonomies = current_site.taxonomies.order(:name)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def show
|
|
12
|
+
@terms = @taxonomy.terms.ordered
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def new
|
|
16
|
+
@taxonomy = current_site.taxonomies.build
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create
|
|
20
|
+
@taxonomy = current_site.taxonomies.build(taxonomy_params)
|
|
21
|
+
|
|
22
|
+
if @taxonomy.save
|
|
23
|
+
redirect_to "#{cp_prefix}/taxonomies/#{@taxonomy.id}", notice: "Taxonomy created"
|
|
24
|
+
else
|
|
25
|
+
render :new, status: :unprocessable_entity
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def edit
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def update
|
|
33
|
+
if @taxonomy.update(taxonomy_params)
|
|
34
|
+
redirect_to "#{cp_prefix}/taxonomies/#{@taxonomy.id}", notice: "Taxonomy updated"
|
|
35
|
+
else
|
|
36
|
+
render :edit, status: :unprocessable_entity
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def destroy
|
|
41
|
+
@taxonomy.destroy
|
|
42
|
+
redirect_to "#{cp_prefix}/taxonomies", notice: "Taxonomy deleted"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def set_taxonomy
|
|
48
|
+
@taxonomy = current_site.taxonomies.find(params[:id])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def taxonomy_params
|
|
52
|
+
params.require(:taxonomy).permit(:name, :handle, :slug)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Plum
|
|
2
|
+
module Cp
|
|
3
|
+
class TermsController < BaseController
|
|
4
|
+
before_action :set_taxonomy
|
|
5
|
+
before_action :set_term, only: [ :edit, :update, :destroy ]
|
|
6
|
+
before_action :require_admin, only: [ :new, :create, :edit, :update, :destroy ]
|
|
7
|
+
|
|
8
|
+
def new
|
|
9
|
+
@term = @taxonomy.terms.build(site: current_site)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create
|
|
13
|
+
@term = @taxonomy.terms.build(term_params)
|
|
14
|
+
@term.site = current_site
|
|
15
|
+
@term.position = @taxonomy.terms.maximum(:position).to_i + 1
|
|
16
|
+
|
|
17
|
+
if @term.save
|
|
18
|
+
redirect_to "#{cp_prefix}/taxonomies/#{@taxonomy.id}", notice: "Term created"
|
|
19
|
+
else
|
|
20
|
+
render :new, status: :unprocessable_entity
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def edit
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def update
|
|
28
|
+
if @term.update(term_params)
|
|
29
|
+
redirect_to "#{cp_prefix}/taxonomies/#{@taxonomy.id}", notice: "Term updated"
|
|
30
|
+
else
|
|
31
|
+
render :edit, status: :unprocessable_entity
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def destroy
|
|
36
|
+
@term.destroy
|
|
37
|
+
redirect_to "#{cp_prefix}/taxonomies/#{@taxonomy.id}", notice: "Term deleted"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def set_taxonomy
|
|
43
|
+
@taxonomy = current_site.taxonomies.find(params[:taxonomy_id])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def set_term
|
|
47
|
+
@term = @taxonomy.terms.find(params[:id])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def term_params
|
|
51
|
+
params.require(:term).permit(:name, :slug)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Plum
|
|
2
|
+
module Cp
|
|
3
|
+
class ThemePreviewsController < BaseController
|
|
4
|
+
def show
|
|
5
|
+
theme = ThemeRegistry.new.find(params[:handle])
|
|
6
|
+
return render file: Rails.public_path.join("404.html"), status: :not_found, layout: false unless theme
|
|
7
|
+
|
|
8
|
+
context = LiquidContext.new(
|
|
9
|
+
controller: self,
|
|
10
|
+
site: current_site,
|
|
11
|
+
theme_name: theme.handle,
|
|
12
|
+
theme_settings: preview_theme_settings(theme)
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
html = LiquidRenderer.render_template("index", context.to_h)
|
|
16
|
+
render html: html.html_safe, layout: false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def preview_theme_settings(theme)
|
|
22
|
+
raw_settings = params.fetch(:theme_settings, {}).permit!.to_h if params[:theme_settings].respond_to?(:permit!)
|
|
23
|
+
ThemeSettingsParams.new(theme).normalize(raw_settings.presence || current_site.theme_settings)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module Plum
|
|
2
|
+
module Cp
|
|
3
|
+
class ThemesController < BaseController
|
|
4
|
+
helper_method :theme_screenshot_url,
|
|
5
|
+
:theme_initials,
|
|
6
|
+
:theme_setting_input_id,
|
|
7
|
+
:theme_setting_input_name,
|
|
8
|
+
:theme_setting_value,
|
|
9
|
+
:theme_setting_checked?
|
|
10
|
+
|
|
11
|
+
before_action :set_themes
|
|
12
|
+
before_action :require_admin, only: [ :create, :update ]
|
|
13
|
+
|
|
14
|
+
def index
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create
|
|
18
|
+
installer = ThemePackageInstaller.new(params[:theme_package])
|
|
19
|
+
|
|
20
|
+
if installer.install
|
|
21
|
+
redirect_to cp_themes_path, notice: "Theme installed"
|
|
22
|
+
else
|
|
23
|
+
@theme_package_errors = installer.errors
|
|
24
|
+
render :index, status: :unprocessable_entity
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def update
|
|
29
|
+
theme = ThemeRegistry.new.find(params[:id])
|
|
30
|
+
|
|
31
|
+
if theme
|
|
32
|
+
site_settings.update!(theme_name: theme.handle)
|
|
33
|
+
current_site.update!(
|
|
34
|
+
theme_name: theme.handle,
|
|
35
|
+
theme_settings: ThemeSettingsParams.new(theme).normalize(theme_settings_params_for(theme))
|
|
36
|
+
)
|
|
37
|
+
redirect_to cp_themes_path, notice: theme_update_notice(theme)
|
|
38
|
+
else
|
|
39
|
+
redirect_to cp_themes_path, alert: "Theme not found"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def set_themes
|
|
46
|
+
registry = ThemeRegistry.new
|
|
47
|
+
|
|
48
|
+
@themes = registry.all
|
|
49
|
+
@active_theme = registry.fetch(site_settings.theme_name)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def theme_screenshot_url(theme)
|
|
53
|
+
return if theme.screenshot_path.blank?
|
|
54
|
+
|
|
55
|
+
theme_asset_path(theme.handle, theme.screenshot_path)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def theme_initials(theme)
|
|
59
|
+
theme.name.split.first(2).map { |part| part.first.to_s.upcase }.join.presence || theme.handle.first.to_s.upcase
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def theme_setting_input_id(theme, field)
|
|
63
|
+
"theme_settings_#{theme.handle.parameterize}_#{field['handle'].to_s.parameterize}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def theme_setting_input_name(field)
|
|
67
|
+
"theme_settings[#{field['handle']}]"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def theme_setting_value(field)
|
|
71
|
+
settings = current_site.theme_settings.to_h
|
|
72
|
+
handle = field["handle"].to_s
|
|
73
|
+
|
|
74
|
+
settings.key?(handle) ? settings[handle] : field["default"]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def theme_setting_checked?(field)
|
|
78
|
+
ActiveModel::Type::Boolean.new.cast(theme_setting_value(field))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def theme_settings_params_for(theme)
|
|
82
|
+
raw_settings = params[:theme_settings].respond_to?(:permit!) ? params[:theme_settings].permit!.to_h : nil
|
|
83
|
+
return raw_settings if raw_settings
|
|
84
|
+
return current_site.theme_settings if current_site.theme_name == theme.handle
|
|
85
|
+
|
|
86
|
+
{}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def theme_update_notice(theme)
|
|
90
|
+
params[:theme_settings].present? && current_site.theme_name == theme.handle ? "Theme settings updated" : "Theme activated"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def site_settings
|
|
94
|
+
@site_settings ||= SiteSetting.instance(current_site)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Plum
|
|
2
|
+
class FormSubmissionsController < ApplicationController
|
|
3
|
+
def create
|
|
4
|
+
form_definition = current_site.form_definitions.find_by!(handle: params[:handle])
|
|
5
|
+
submission = form_definition.form_submissions.build(
|
|
6
|
+
site: current_site,
|
|
7
|
+
data: submission_data(form_definition)
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
if submission.save
|
|
11
|
+
deliver_submission_notification(submission)
|
|
12
|
+
redirect_to safe_return_path, notice: "Form submitted"
|
|
13
|
+
else
|
|
14
|
+
redirect_to safe_return_path, alert: submission.errors.full_messages.to_sentence
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def deliver_submission_notification(submission)
|
|
21
|
+
return if submission.form_definition.notification_email.blank?
|
|
22
|
+
|
|
23
|
+
Plum::FormMailer.submission_notification(submission).deliver_later
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
Rails.logger.error("[Plum] form notification could not be enqueued: #{e.message}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def submission_data(form_definition)
|
|
29
|
+
raw_data = params.dig(:form_submission, :data)
|
|
30
|
+
data = raw_data.respond_to?(:permit!) ? raw_data.permit!.to_h : {}
|
|
31
|
+
|
|
32
|
+
form_definition.field_handles.index_with { |handle| data[handle] }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def safe_return_path
|
|
36
|
+
return root_path unless params[:return_to].is_a?(String)
|
|
37
|
+
|
|
38
|
+
return_to = params[:return_to]
|
|
39
|
+
return root_path if return_to.blank? || return_to.start_with?("//") || return_to.match?(%r{\A[a-z][a-z0-9+.-]*://}i)
|
|
40
|
+
|
|
41
|
+
return_to
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
module Plum
|
|
2
|
+
class PagesController < ApplicationController
|
|
3
|
+
HOMEPAGE_SLUG = "home".freeze
|
|
4
|
+
|
|
5
|
+
def home
|
|
6
|
+
@site_settings = SiteSetting.instance(current_site)
|
|
7
|
+
@entry = Entry.for_site(current_site).live
|
|
8
|
+
.includes(:content_type, terms: :taxonomy)
|
|
9
|
+
.find_by(slug: HOMEPAGE_SLUG)
|
|
10
|
+
|
|
11
|
+
template = @entry ? "entries/#{@entry.content_type.handle}" : "index"
|
|
12
|
+
html = Plum::LiquidRenderer.render_template(template, build_context(@entry).to_h)
|
|
13
|
+
render html: html.html_safe, layout: false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def search
|
|
17
|
+
query = params[:q].to_s.strip
|
|
18
|
+
context = build_context
|
|
19
|
+
results = Entry.for_site(current_site).live.search(query)
|
|
20
|
+
.includes(:content_type)
|
|
21
|
+
.order(published_at: :desc)
|
|
22
|
+
.limit(50)
|
|
23
|
+
|
|
24
|
+
context_hash = context.to_h.merge(
|
|
25
|
+
"search" => {
|
|
26
|
+
"query" => query,
|
|
27
|
+
"results" => results.map { |e| context.send(:entry_context, e) },
|
|
28
|
+
"total" => results.size
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
html = Plum::LiquidRenderer.render_template("search", context_hash)
|
|
32
|
+
render html: html.html_safe, layout: false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def show
|
|
36
|
+
slug = params[:slug]
|
|
37
|
+
|
|
38
|
+
@content_type = ContentType.for_site(current_site).find_by(handle: slug)
|
|
39
|
+
if @content_type
|
|
40
|
+
return render_collection(@content_type)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if slug.include?("/")
|
|
44
|
+
parts = slug.split("/", 2)
|
|
45
|
+
content_type = ContentType.for_site(current_site).find do |candidate|
|
|
46
|
+
candidate.route_prefix == parts[0]
|
|
47
|
+
end
|
|
48
|
+
if content_type
|
|
49
|
+
@entry = Entry.for_site(current_site).live
|
|
50
|
+
.includes(:content_type, terms: :taxonomy)
|
|
51
|
+
.find_by!(content_type: content_type, slug: parts[1])
|
|
52
|
+
html = Plum::LiquidRenderer.render_template("entries/#{content_type.handle}", build_context(@entry).to_h)
|
|
53
|
+
return render html: html.html_safe, layout: false
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
taxonomy = Taxonomy.for_site(current_site).find_by(slug: parts[0])
|
|
57
|
+
if taxonomy
|
|
58
|
+
term = taxonomy.terms.find_by!(slug: parts[1])
|
|
59
|
+
return render_term_page(taxonomy, term)
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
taxonomy = Taxonomy.for_site(current_site).find_by(slug: slug)
|
|
63
|
+
return render_taxonomy_index(taxonomy) if taxonomy
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@entry = Entry.for_site(current_site).live
|
|
67
|
+
.includes(:content_type, terms: :taxonomy)
|
|
68
|
+
.find_by!(slug: slug)
|
|
69
|
+
template = "entries/#{@entry.content_type.handle}"
|
|
70
|
+
html = Plum::LiquidRenderer.render_template(template, build_context(@entry).to_h)
|
|
71
|
+
render html: html.html_safe, layout: false
|
|
72
|
+
rescue ActiveRecord::RecordNotFound
|
|
73
|
+
render file: Rails.public_path.join("404.html"), status: :not_found, layout: false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
PER_PAGE = 12
|
|
79
|
+
|
|
80
|
+
def render_collection(content_type)
|
|
81
|
+
context = build_context
|
|
82
|
+
page = [ params.fetch(:page, 1).to_i, 1 ].max
|
|
83
|
+
all_entries = Entry.for_site(current_site).live
|
|
84
|
+
.where(content_type: content_type)
|
|
85
|
+
.order(published_at: :desc, created_at: :desc)
|
|
86
|
+
total = all_entries.count
|
|
87
|
+
total_pages = (total / PER_PAGE.to_f).ceil
|
|
88
|
+
page = [ page, [ total_pages, 1 ].max ].min
|
|
89
|
+
entries = all_entries.offset((page - 1) * PER_PAGE).limit(PER_PAGE)
|
|
90
|
+
|
|
91
|
+
context_hash = context.to_h.merge(
|
|
92
|
+
"collection" => {
|
|
93
|
+
"title" => content_type.name,
|
|
94
|
+
"handle" => content_type.handle,
|
|
95
|
+
"entries" => entries.map { |e| context.send(:entry_context, e) }
|
|
96
|
+
},
|
|
97
|
+
"pagination" => {
|
|
98
|
+
"current_page" => page,
|
|
99
|
+
"total_pages" => total_pages,
|
|
100
|
+
"total_entries" => total,
|
|
101
|
+
"per_page" => PER_PAGE,
|
|
102
|
+
"previous_url" => page > 1 ? "#{request.path}?page=#{page - 1}" : nil,
|
|
103
|
+
"next_url" => page < total_pages ? "#{request.path}?page=#{page + 1}" : nil
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
template = "collections/#{content_type.handle}"
|
|
107
|
+
html = Plum::LiquidRenderer.render_template(template, context_hash)
|
|
108
|
+
render html: html.html_safe, layout: false
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def render_term_page(taxonomy, term)
|
|
112
|
+
context = build_context
|
|
113
|
+
page = [ params.fetch(:page, 1).to_i, 1 ].max
|
|
114
|
+
all_entries = Entry.for_site(current_site).live
|
|
115
|
+
.joins(:entry_terms)
|
|
116
|
+
.where(plum_entry_terms: { term_id: term.id })
|
|
117
|
+
.order(published_at: :desc)
|
|
118
|
+
total = all_entries.count
|
|
119
|
+
total_pages = [ (total / PER_PAGE.to_f).ceil, 1 ].max
|
|
120
|
+
page = [ page, total_pages ].min
|
|
121
|
+
entries = all_entries.offset((page - 1) * PER_PAGE).limit(PER_PAGE)
|
|
122
|
+
|
|
123
|
+
context_hash = context.to_h.merge(
|
|
124
|
+
"taxonomy" => { "name" => taxonomy.name, "handle" => taxonomy.handle, "slug" => taxonomy.slug },
|
|
125
|
+
"term" => { "name" => term.name, "slug" => term.slug },
|
|
126
|
+
"collection" => {
|
|
127
|
+
"title" => "#{taxonomy.name}: #{term.name}",
|
|
128
|
+
"entries" => entries.map { |e| context.send(:entry_context, e) }
|
|
129
|
+
},
|
|
130
|
+
"pagination" => {
|
|
131
|
+
"current_page" => page, "total_pages" => total_pages, "total_entries" => total, "per_page" => PER_PAGE,
|
|
132
|
+
"previous_url" => page > 1 ? "#{request.path}?page=#{page - 1}" : nil,
|
|
133
|
+
"next_url" => page < total_pages ? "#{request.path}?page=#{page + 1}" : nil
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
template = "taxonomies/#{taxonomy.handle}"
|
|
137
|
+
html = Plum::LiquidRenderer.render_template(template, context_hash)
|
|
138
|
+
render html: html.html_safe, layout: false
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def render_taxonomy_index(taxonomy)
|
|
142
|
+
context = build_context
|
|
143
|
+
terms = taxonomy.terms.ordered
|
|
144
|
+
context_hash = context.to_h.merge(
|
|
145
|
+
"taxonomy" => {
|
|
146
|
+
"name" => taxonomy.name,
|
|
147
|
+
"handle" => taxonomy.handle,
|
|
148
|
+
"slug" => taxonomy.slug,
|
|
149
|
+
"terms" => terms.map { |t| { "name" => t.name, "slug" => t.slug, "url" => "/#{taxonomy.slug}/#{t.slug}", "entries_count" => t.entries.live.count } }
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
template = "taxonomies/#{taxonomy.handle}_index"
|
|
153
|
+
html = Plum::LiquidRenderer.render_template(template, context_hash)
|
|
154
|
+
render html: html.html_safe, layout: false
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def build_context(entry = nil)
|
|
158
|
+
LiquidContext.new(controller: self, site: current_site, entry: entry)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Plum
|
|
2
|
+
class SessionsController < ApplicationController
|
|
3
|
+
layout "plum/session"
|
|
4
|
+
|
|
5
|
+
def new
|
|
6
|
+
redirect_to cp_root_path if logged_in?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create
|
|
10
|
+
user = Plum::User.find_by(email: params[:email])
|
|
11
|
+
if user&.authenticate(params[:password])
|
|
12
|
+
session[:plum_user_id] = user.id
|
|
13
|
+
redirect_to cp_root_path, notice: "Welcome back!"
|
|
14
|
+
else
|
|
15
|
+
flash.now[:alert] = "Invalid email or password"
|
|
16
|
+
render :new, status: :unprocessable_entity
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def destroy
|
|
21
|
+
session[:plum_user_id] = nil
|
|
22
|
+
redirect_to login_path, notice: "You have been logged out"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require "rack/mime"
|
|
2
|
+
|
|
3
|
+
module Plum
|
|
4
|
+
class ThemeAssetsController < ApplicationController
|
|
5
|
+
def show
|
|
6
|
+
asset_path = ThemeResolver.new.find_asset(params[:theme_handle], params[:path])
|
|
7
|
+
return head :not_found unless asset_path
|
|
8
|
+
|
|
9
|
+
send_file asset_path,
|
|
10
|
+
type: Rack::Mime.mime_type(asset_path.extname, "application/octet-stream"),
|
|
11
|
+
disposition: "inline"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|