journeybook 0.1.6.pre.alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +9 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +354 -0
  5. data/Rakefile +6 -0
  6. data/app/assets/images/journeybook/.keep +0 -0
  7. data/app/assets/images/journeybook/logo.svg +5 -0
  8. data/app/assets/javascripts/journeybook/sortable.js +96 -0
  9. data/app/assets/stylesheets/journeybook/application.css +1556 -0
  10. data/app/controllers/concerns/.keep +0 -0
  11. data/app/controllers/concerns/journeybook/public_pages.rb +31 -0
  12. data/app/controllers/journeybook/admin/ai_pages_controller.rb +65 -0
  13. data/app/controllers/journeybook/admin/ai_style_prompts_controller.rb +51 -0
  14. data/app/controllers/journeybook/admin/components_controller.rb +57 -0
  15. data/app/controllers/journeybook/admin/dashboard_controller.rb +13 -0
  16. data/app/controllers/journeybook/admin/media_assets_controller.rb +59 -0
  17. data/app/controllers/journeybook/admin/navigation_items_controller.rb +91 -0
  18. data/app/controllers/journeybook/admin/navigation_menus_controller.rb +59 -0
  19. data/app/controllers/journeybook/admin/pages_controller.rb +304 -0
  20. data/app/controllers/journeybook/admin/redirects_controller.rb +51 -0
  21. data/app/controllers/journeybook/admin/sites_controller.rb +51 -0
  22. data/app/controllers/journeybook/application_controller.rb +52 -0
  23. data/app/controllers/journeybook/form_submissions_controller.rb +55 -0
  24. data/app/helpers/journeybook/application_helper.rb +4 -0
  25. data/app/helpers/journeybook/navigation_helper.rb +11 -0
  26. data/app/javascript/journeybook/application.js +5 -0
  27. data/app/javascript/journeybook/component_search_controller.js +13 -0
  28. data/app/jobs/journeybook/application_job.rb +4 -0
  29. data/app/models/concerns/.keep +0 -0
  30. data/app/models/journeybook/ai_style_prompt.rb +6 -0
  31. data/app/models/journeybook/application_record.rb +5 -0
  32. data/app/models/journeybook/media_asset.rb +26 -0
  33. data/app/models/journeybook/navigation_item.rb +45 -0
  34. data/app/models/journeybook/navigation_menu.rb +23 -0
  35. data/app/models/journeybook/page.rb +96 -0
  36. data/app/models/journeybook/redirect.rb +31 -0
  37. data/app/models/journeybook/site.rb +62 -0
  38. data/app/services/journeybook/ai/markdown_generator.rb +15 -0
  39. data/app/services/journeybook/ai/openai_client.rb +54 -0
  40. data/app/services/journeybook/ai/prompt_builder.rb +154 -0
  41. data/app/services/journeybook/ai/template_validator.rb +17 -0
  42. data/app/services/journeybook/component_parser.rb +53 -0
  43. data/app/services/journeybook/component_renderer.rb +108 -0
  44. data/app/services/journeybook/component_tag_updater.rb +132 -0
  45. data/app/services/journeybook/component_validator.rb +111 -0
  46. data/app/services/journeybook/form_mailer.rb +69 -0
  47. data/app/services/journeybook/html_sanitizer.rb +46 -0
  48. data/app/services/journeybook/letter_opener_client.rb +14 -0
  49. data/app/services/journeybook/markdown_renderer.rb +29 -0
  50. data/app/services/journeybook/page_renderer.rb +58 -0
  51. data/app/services/journeybook/page_resolver.rb +55 -0
  52. data/app/services/journeybook/postmark_client.rb +48 -0
  53. data/app/services/journeybook/storage/s3_client.rb +96 -0
  54. data/app/views/journeybook/admin/ai_pages/_draft_form.html.erb +73 -0
  55. data/app/views/journeybook/admin/ai_pages/new.html.erb +43 -0
  56. data/app/views/journeybook/admin/ai_style_prompts/_form.html.erb +16 -0
  57. data/app/views/journeybook/admin/ai_style_prompts/edit.html.erb +2 -0
  58. data/app/views/journeybook/admin/ai_style_prompts/index.html.erb +26 -0
  59. data/app/views/journeybook/admin/ai_style_prompts/new.html.erb +2 -0
  60. data/app/views/journeybook/admin/components/_component.html.erb +17 -0
  61. data/app/views/journeybook/admin/components/_reference.html.erb +9 -0
  62. data/app/views/journeybook/admin/components/index.html.erb +46 -0
  63. data/app/views/journeybook/admin/components/preview.html.erb +13 -0
  64. data/app/views/journeybook/admin/dashboard/show.html.erb +34 -0
  65. data/app/views/journeybook/admin/media_assets/_reference.html.erb +16 -0
  66. data/app/views/journeybook/admin/media_assets/index.html.erb +43 -0
  67. data/app/views/journeybook/admin/media_assets/new.html.erb +41 -0
  68. data/app/views/journeybook/admin/navigation_items/_form.html.erb +46 -0
  69. data/app/views/journeybook/admin/navigation_items/edit.html.erb +2 -0
  70. data/app/views/journeybook/admin/navigation_items/new.html.erb +2 -0
  71. data/app/views/journeybook/admin/navigation_menus/_form.html.erb +10 -0
  72. data/app/views/journeybook/admin/navigation_menus/edit.html.erb +38 -0
  73. data/app/views/journeybook/admin/navigation_menus/index.html.erb +28 -0
  74. data/app/views/journeybook/admin/navigation_menus/new.html.erb +2 -0
  75. data/app/views/journeybook/admin/pages/_collection_field.html.erb +58 -0
  76. data/app/views/journeybook/admin/pages/_form.html.erb +77 -0
  77. data/app/views/journeybook/admin/pages/_image_picker_modal.html.erb +23 -0
  78. data/app/views/journeybook/admin/pages/_table.html.erb +37 -0
  79. data/app/views/journeybook/admin/pages/_url_field.html.erb +25 -0
  80. data/app/views/journeybook/admin/pages/edit.html.erb +547 -0
  81. data/app/views/journeybook/admin/pages/errors.html.erb +25 -0
  82. data/app/views/journeybook/admin/pages/index.html.erb +52 -0
  83. data/app/views/journeybook/admin/pages/new.html.erb +27 -0
  84. data/app/views/journeybook/admin/pages/new_component.html.erb +70 -0
  85. data/app/views/journeybook/admin/pages/public_preview.html.erb +27 -0
  86. data/app/views/journeybook/admin/pages/source.html.erb +23 -0
  87. data/app/views/journeybook/admin/redirects/_form.html.erb +9 -0
  88. data/app/views/journeybook/admin/redirects/edit.html.erb +2 -0
  89. data/app/views/journeybook/admin/redirects/index.html.erb +15 -0
  90. data/app/views/journeybook/admin/redirects/new.html.erb +2 -0
  91. data/app/views/journeybook/admin/shared/_drag_handle.html.erb +3 -0
  92. data/app/views/journeybook/admin/shared/_errors.html.erb +10 -0
  93. data/app/views/journeybook/admin/sites/_form.html.erb +12 -0
  94. data/app/views/journeybook/admin/sites/edit.html.erb +2 -0
  95. data/app/views/journeybook/admin/sites/index.html.erb +26 -0
  96. data/app/views/journeybook/admin/sites/new.html.erb +2 -0
  97. data/app/views/layouts/journeybook/application.html.erb +57 -0
  98. data/config/importmap.rb +3 -0
  99. data/config/routes.rb +39 -0
  100. data/db/migrate/20260627000001_create_journeybook_sites.rb +13 -0
  101. data/db/migrate/20260627000002_create_journeybook_pages.rb +23 -0
  102. data/db/migrate/20260627000003_create_journeybook_redirects.rb +13 -0
  103. data/db/migrate/20260628000004_add_context_fields_to_journeybook_sites.rb +38 -0
  104. data/db/migrate/20260628000005_create_journeybook_media_assets.rb +24 -0
  105. data/db/migrate/20260629000006_create_journeybook_navigation.rb +28 -0
  106. data/db/migrate/20260731000007_add_open_in_new_window_to_journeybook_navigation_items.rb +5 -0
  107. data/db/migrate/20260731000008_create_journeybook_ai_style_prompts.rb +12 -0
  108. data/db/migrate/20260801000009_add_blog_fields_to_journeybook_pages.rb +9 -0
  109. data/db/migrate/20260801000010_add_knowledge_base_fields_to_journeybook_pages.rb +8 -0
  110. data/db/migrate/20260801000011_add_position_to_journeybook_pages.rb +6 -0
  111. data/lib/generators/journeybook/install/install_generator.rb +72 -0
  112. data/lib/generators/journeybook/install/templates/add_blog_fields_to_journeybook_pages.rb +9 -0
  113. data/lib/generators/journeybook/install/templates/add_context_fields_to_journeybook_sites.rb +38 -0
  114. data/lib/generators/journeybook/install/templates/add_knowledge_base_fields_to_journeybook_pages.rb +8 -0
  115. data/lib/generators/journeybook/install/templates/add_open_in_new_window_to_journeybook_navigation_items.rb +5 -0
  116. data/lib/generators/journeybook/install/templates/add_position_to_journeybook_pages.rb +6 -0
  117. data/lib/generators/journeybook/install/templates/create_journeybook_ai_style_prompts.rb +12 -0
  118. data/lib/generators/journeybook/install/templates/create_journeybook_media_assets.rb +24 -0
  119. data/lib/generators/journeybook/install/templates/create_journeybook_navigation.rb +28 -0
  120. data/lib/generators/journeybook/install/templates/create_journeybook_pages.rb +33 -0
  121. data/lib/generators/journeybook/install/templates/create_journeybook_redirects.rb +13 -0
  122. data/lib/generators/journeybook/install/templates/create_journeybook_sites.rb +13 -0
  123. data/lib/generators/journeybook/install/templates/journeybook.rb +42 -0
  124. data/lib/generators/journeybook/install/templates/pages_controller.rb +3 -0
  125. data/lib/generators/journeybook/install/templates/show.html.erb +10 -0
  126. data/lib/journeybook/component_registry.rb +136 -0
  127. data/lib/journeybook/configuration.rb +55 -0
  128. data/lib/journeybook/engine.rb +19 -0
  129. data/lib/journeybook/page_template_registry.rb +67 -0
  130. data/lib/journeybook/public_page_constraint.rb +23 -0
  131. data/lib/journeybook/version.rb +3 -0
  132. data/lib/journeybook.rb +62 -0
  133. data/lib/tasks/journeybook_tasks.rake +8 -0
  134. metadata +266 -0
File without changes
@@ -0,0 +1,31 @@
1
+ module Journeybook
2
+ module PublicPages
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ helper Journeybook::NavigationHelper
7
+ helper_method :journeybook_page, :journeybook_html, :journeybook_site
8
+ end
9
+
10
+ def show
11
+ resolver = Journeybook::PageResolver.from_request(request, path: params[:path])
12
+ @journeybook_page = resolver.call
13
+
14
+ redirect = Journeybook::Redirect.for_path(params[:path]) if @journeybook_page.blank? && resolver.site
15
+ return redirect_to(redirect.to, status: redirect.status_code) if redirect
16
+
17
+ return render_not_found unless @journeybook_page
18
+
19
+ @journeybook_site = @journeybook_page.site
20
+ @journeybook_html = Journeybook::PageRenderer.new.render(@journeybook_page, form_authenticity_token: form_authenticity_token)
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :journeybook_page, :journeybook_html, :journeybook_site
26
+
27
+ def render_not_found
28
+ render file: Rails.public_path.join("404.html"), layout: false, status: :not_found
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,65 @@
1
+ module Journeybook
2
+ module Admin
3
+ class AiPagesController < ApplicationController
4
+ before_action :set_sites
5
+
6
+ def new
7
+ @page = Page.new(site: current_site || @sites.first, locale: current_site&.default_locale || "en", status: "draft")
8
+ set_templates
9
+ set_style_prompts
10
+ end
11
+
12
+ def create
13
+ @page = Page.new(page_params)
14
+ @selected_template = template_for(params[:template_key])
15
+ @page.page_type = @selected_template.page_type if @selected_template
16
+ @prompt = params[:prompt].to_s
17
+ @style_prompt = AiStylePrompt.find_by(id: params[:ai_style_prompt_id])
18
+ set_templates
19
+ set_style_prompts
20
+
21
+ if @prompt.blank?
22
+ @page.errors.add(:base, "Prompt is required.")
23
+ return render :new, status: :unprocessable_entity
24
+ end
25
+
26
+ @page.markdown = Ai::MarkdownGenerator.new(site: @page.site, style_prompt: @style_prompt&.prompt, template: @selected_template).generate(prompt: @prompt)
27
+ add_generation_errors
28
+ render :new
29
+ rescue Ai::OpenaiClient::Error => error
30
+ @page.errors.add(:base, error.message)
31
+ render :new, status: :unprocessable_entity
32
+ end
33
+
34
+ private
35
+
36
+ def set_sites
37
+ @sites = Site.order(:name)
38
+ end
39
+
40
+ def set_style_prompts
41
+ @style_prompts = AiStylePrompt.order(:name)
42
+ end
43
+
44
+ def set_templates
45
+ @templates = Journeybook.templates.for_site(@page&.site || current_site || @sites.first)
46
+ end
47
+
48
+ def template_for(key)
49
+ return unless key.present?
50
+
51
+ Journeybook.templates.for_site(@page.site).find { |template| template.key.to_s == key.to_s }
52
+ end
53
+
54
+ def add_generation_errors
55
+ errors = @page.component_validation_errors
56
+ errors.concat(Journeybook::Ai::TemplateValidator.new.errors_for(@page.markdown, @selected_template)) if @selected_template
57
+ errors.uniq.each { |message| @page.errors.add(:markdown, message) }
58
+ end
59
+
60
+ def page_params
61
+ params.fetch(:page, {}).permit(:site_id, :title, :path, :meta_title, :meta_description, :status, :locale, :page_type, :excerpt, :author_name, :topic).with_defaults(site_id: current_site&.id, status: "draft", locale: current_site&.default_locale || "en")
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,51 @@
1
+ module Journeybook
2
+ module Admin
3
+ class AiStylePromptsController < ApplicationController
4
+ before_action :set_style_prompt, only: %i[edit update destroy]
5
+
6
+ def index
7
+ @style_prompts = AiStylePrompt.order(:name)
8
+ end
9
+
10
+ def new
11
+ @style_prompt = AiStylePrompt.new
12
+ end
13
+
14
+ def create
15
+ @style_prompt = AiStylePrompt.new(style_prompt_params)
16
+
17
+ if @style_prompt.save
18
+ redirect_to admin_ai_style_prompts_path, notice: "AI style prompt 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 @style_prompt.update(style_prompt_params)
29
+ redirect_to admin_ai_style_prompts_path, notice: "AI style prompt updated."
30
+ else
31
+ render :edit, status: :unprocessable_entity
32
+ end
33
+ end
34
+
35
+ def destroy
36
+ @style_prompt.destroy
37
+ redirect_to admin_ai_style_prompts_path, notice: "AI style prompt deleted."
38
+ end
39
+
40
+ private
41
+
42
+ def set_style_prompt
43
+ @style_prompt = AiStylePrompt.find(params[:id])
44
+ end
45
+
46
+ def style_prompt_params
47
+ params.require(:ai_style_prompt).permit(:name, :prompt)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,57 @@
1
+ module Journeybook
2
+ module Admin
3
+ class ComponentsController < ApplicationController
4
+ def index
5
+ @components = Journeybook.components.for_site(current_site).to_a
6
+ @selected_component = selected_component
7
+ end
8
+
9
+ def preview
10
+ @components = Journeybook.components.for_site(current_site).to_a
11
+ @selected_component = component_for(params[:component])
12
+ return head :not_found unless @selected_component
13
+
14
+ @preview_props = preview_props(@selected_component)
15
+ @preview_html = render_preview(@selected_component, @preview_props)
16
+
17
+ render :preview, layout: preview_layout
18
+ rescue StandardError => error
19
+ @preview_error = "#{error.class}: #{error.message}"
20
+ render :preview, layout: preview_layout, status: :unprocessable_entity
21
+ end
22
+
23
+ private
24
+
25
+ def selected_component
26
+ return if @components.empty?
27
+
28
+ component_for(params[:component]) || @components.first
29
+ end
30
+
31
+ def component_for(name)
32
+ @components.find { |component| component.name.to_s == name.to_s }
33
+ end
34
+
35
+ def preview_props(component)
36
+ component_class = component.component_class
37
+ return component_class.preview_props if component_class.respond_to?(:preview_props)
38
+
39
+ raise ArgumentError, "#{component.name} must define .preview_props for its component preview."
40
+ end
41
+
42
+ def render_preview(component, props)
43
+ component_class = component.component_class
44
+ html = if component_class.respond_to?(:render)
45
+ component_class.render(**props)
46
+ else
47
+ component_class.new(**props).render
48
+ end
49
+ HtmlSanitizer.new.sanitize(html)
50
+ end
51
+
52
+ def preview_layout
53
+ current_site&.layout_name || Journeybook.configuration.default_layout
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,13 @@
1
+ module Journeybook
2
+ module Admin
3
+ class DashboardController < ApplicationController
4
+ def show
5
+ @site_count = Site.count
6
+ @page_count = current_site ? current_site.pages.count : 0
7
+ @published_page_count = current_site ? current_site.pages.published.count : 0
8
+ @component_count = Journeybook.components.for_site(current_site).count
9
+ @recent_pages = current_site ? current_site.pages.includes(:site).order(updated_at: :desc).limit(5) : Page.none
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,59 @@
1
+ module Journeybook
2
+ module Admin
3
+ class MediaAssetsController < ApplicationController
4
+ rescue_from Journeybook::Storage::S3Client::Error, with: :storage_error
5
+
6
+ def index
7
+ @media_asset = MediaAsset.new
8
+ @media_assets = MediaAsset.order(created_at: :desc)
9
+ end
10
+
11
+ def new
12
+ @media_asset = MediaAsset.new
13
+ end
14
+
15
+ def create
16
+ @media_asset = MediaAsset.new(media_asset_params)
17
+ file = params.dig(:media_asset, :file)
18
+
19
+ unless file.respond_to?(:read)
20
+ @media_asset.errors.add(:base, "Image file is required.")
21
+ return render :new, status: :unprocessable_entity
22
+ end
23
+
24
+ unless MediaAsset::IMAGE_CONTENT_TYPES.include?(file.content_type.to_s)
25
+ @media_asset.errors.add(:content_type, "must be PNG, JPEG, WebP, GIF, or SVG")
26
+ return render :new, status: :unprocessable_entity
27
+ end
28
+
29
+ upload = Journeybook::Storage::S3Client.new.upload(file)
30
+ @media_asset.assign_attributes(upload.to_h)
31
+ @media_asset.title = upload.original_filename if @media_asset.title.blank?
32
+
33
+ if @media_asset.save
34
+ redirect_to admin_media_assets_path, notice: "Image uploaded."
35
+ else
36
+ Journeybook::Storage::S3Client.new.delete(upload.storage_key)
37
+ render :new, status: :unprocessable_entity
38
+ end
39
+ end
40
+
41
+ def destroy
42
+ MediaAsset.find(params[:id]).destroy
43
+ redirect_to admin_media_assets_path, notice: "Image deleted."
44
+ end
45
+
46
+ private
47
+
48
+ def media_asset_params
49
+ params.fetch(:media_asset, {}).permit(:title, :alt_text, :description, :tags)
50
+ end
51
+
52
+ def storage_error(error)
53
+ @media_asset ||= MediaAsset.new(media_asset_params)
54
+ @media_asset.errors.add(:base, error.message)
55
+ render :new, status: :unprocessable_entity
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,91 @@
1
+ module Journeybook
2
+ module Admin
3
+ class NavigationItemsController < ApplicationController
4
+ before_action :set_menu
5
+ before_action :set_item, only: %i[edit update destroy move]
6
+ before_action :set_pages, only: %i[new create edit update]
7
+
8
+ def new
9
+ @item = @menu.items.build(position: next_position, visible: true)
10
+ end
11
+
12
+ def create
13
+ @item = @menu.items.build(item_params)
14
+
15
+ if @item.save
16
+ redirect_to edit_admin_navigation_menu_path(@menu), notice: "Navigation item added."
17
+ else
18
+ render :new, status: :unprocessable_entity
19
+ end
20
+ end
21
+
22
+ def edit
23
+ end
24
+
25
+ def update
26
+ if @item.update(item_params)
27
+ redirect_to edit_admin_navigation_menu_path(@menu), notice: "Navigation item updated."
28
+ else
29
+ render :edit, status: :unprocessable_entity
30
+ end
31
+ end
32
+
33
+ def destroy
34
+ @item.destroy
35
+ redirect_to edit_admin_navigation_menu_path(@menu), notice: "Navigation item deleted."
36
+ end
37
+
38
+ def move
39
+ items = @menu.items.ordered.to_a
40
+ index = items.index(@item)
41
+ other_index = params[:direction] == "up" ? index - 1 : index + 1
42
+ other = items[other_index]
43
+
44
+ if other
45
+ NavigationItem.transaction do
46
+ item_position = @item.position
47
+ @item.update!(position: other.position)
48
+ other.update!(position: item_position)
49
+ end
50
+ end
51
+
52
+ redirect_to edit_admin_navigation_menu_path(@menu), notice: "Navigation item moved."
53
+ end
54
+
55
+ def reorder
56
+ ids = Array(params[:item_ids]).map(&:to_i)
57
+ return head :unprocessable_entity unless ids.sort == @menu.items.pluck(:id).sort
58
+
59
+ NavigationItem.transaction do
60
+ ids.each_with_index { |id, position| @menu.items.find(id).update!(position: position) }
61
+ end
62
+
63
+ head :no_content
64
+ end
65
+
66
+ private
67
+
68
+ def set_menu
69
+ @menu = NavigationMenu.find(params[:navigation_menu_id])
70
+ session[:journeybook_site_id] = @menu.site_id
71
+ @current_site = @menu.site
72
+ end
73
+
74
+ def set_item
75
+ @item = @menu.items.find(params[:id])
76
+ end
77
+
78
+ def set_pages
79
+ @pages = current_site.pages.order(:title)
80
+ end
81
+
82
+ def item_params
83
+ params.require(:navigation_item).permit(:page_id, :label, :description, :external_url, :visible, :open_in_new_window)
84
+ end
85
+
86
+ def next_position
87
+ @menu.items.maximum(:position).to_i + 1
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,59 @@
1
+ module Journeybook
2
+ module Admin
3
+ class NavigationMenusController < ApplicationController
4
+ before_action :set_menu, only: %i[edit update destroy]
5
+
6
+ def index
7
+ @menus = current_site ? current_site.navigation_menus.ordered : NavigationMenu.none
8
+ end
9
+
10
+ def new
11
+ @menu = current_site.navigation_menus.build(position: next_position)
12
+ end
13
+
14
+ def create
15
+ @menu = current_site.navigation_menus.build(menu_params)
16
+
17
+ if @menu.save
18
+ redirect_to edit_admin_navigation_menu_path(@menu), notice: "Navigation menu created."
19
+ else
20
+ render :new, status: :unprocessable_entity
21
+ end
22
+ end
23
+
24
+ def edit
25
+ @items = @menu.items.includes(:page).ordered
26
+ end
27
+
28
+ def update
29
+ if @menu.update(menu_params)
30
+ redirect_to edit_admin_navigation_menu_path(@menu), notice: "Navigation menu updated."
31
+ else
32
+ @items = @menu.items.includes(:page).ordered
33
+ render :edit, status: :unprocessable_entity
34
+ end
35
+ end
36
+
37
+ def destroy
38
+ @menu.destroy
39
+ redirect_to admin_navigation_menus_path, notice: "Navigation menu deleted."
40
+ end
41
+
42
+ private
43
+
44
+ def set_menu
45
+ @menu = NavigationMenu.find(params[:id])
46
+ session[:journeybook_site_id] = @menu.site_id
47
+ @current_site = @menu.site
48
+ end
49
+
50
+ def menu_params
51
+ params.require(:navigation_menu).permit(:key, :title, :position)
52
+ end
53
+
54
+ def next_position
55
+ current_site.navigation_menus.maximum(:position).to_i + 1
56
+ end
57
+ end
58
+ end
59
+ end