railspress-engine 0.1.2 → 1.2.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.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +20 -0
  3. data/README.md +195 -25
  4. data/app/assets/javascripts/railspress/admin.js +39 -0
  5. data/app/assets/javascripts/railspress/markdown_mode.js +343 -0
  6. data/app/assets/stylesheets/application.css +0 -0
  7. data/app/assets/stylesheets/railspress/admin/badges.css +70 -0
  8. data/app/assets/stylesheets/railspress/admin/base.css +25 -0
  9. data/app/assets/stylesheets/railspress/admin/buttons.css +140 -0
  10. data/app/assets/stylesheets/railspress/admin/cards.css +52 -0
  11. data/app/assets/stylesheets/railspress/admin/components/exports.css +55 -0
  12. data/app/assets/stylesheets/railspress/admin/components/focal_point.css +801 -0
  13. data/app/assets/stylesheets/railspress/admin/components/imports.css +144 -0
  14. data/app/assets/stylesheets/railspress/admin/components/lexxy.css +156 -0
  15. data/app/assets/stylesheets/railspress/admin/filters.css +73 -0
  16. data/app/assets/stylesheets/railspress/admin/flash.css +26 -0
  17. data/app/assets/stylesheets/railspress/admin/forms.css +459 -0
  18. data/app/assets/stylesheets/railspress/admin/layout.css +256 -0
  19. data/app/assets/stylesheets/railspress/admin/lists.css +24 -0
  20. data/app/assets/stylesheets/railspress/admin/page.css +111 -0
  21. data/app/assets/stylesheets/railspress/admin/responsive.css +174 -0
  22. data/app/assets/stylesheets/railspress/admin/stats.css +43 -0
  23. data/app/assets/stylesheets/railspress/admin/tables.css +163 -0
  24. data/app/assets/stylesheets/railspress/admin/utilities.css +202 -0
  25. data/app/assets/stylesheets/railspress/admin/variables.css +58 -0
  26. data/app/assets/stylesheets/railspress/application.css +44 -13
  27. data/app/controllers/railspress/admin/base_controller.rb +6 -3
  28. data/app/controllers/railspress/admin/categories_controller.rb +1 -1
  29. data/app/controllers/railspress/admin/cms_transfers_controller.rb +49 -0
  30. data/app/controllers/railspress/admin/content_element_versions_controller.rb +12 -0
  31. data/app/controllers/railspress/admin/content_elements_controller.rb +143 -0
  32. data/app/controllers/railspress/admin/content_groups_controller.rb +69 -0
  33. data/app/controllers/railspress/admin/dashboard_controller.rb +6 -0
  34. data/app/controllers/railspress/admin/entities_controller.rb +157 -0
  35. data/app/controllers/railspress/admin/exports_controller.rb +55 -0
  36. data/app/controllers/railspress/admin/focal_points_controller.rb +100 -0
  37. data/app/controllers/railspress/admin/imports_controller.rb +63 -0
  38. data/app/controllers/railspress/admin/posts_controller.rb +58 -4
  39. data/app/controllers/railspress/admin/prototypes_controller.rb +30 -0
  40. data/app/controllers/railspress/admin/tags_controller.rb +1 -1
  41. data/app/controllers/railspress/application_controller.rb +1 -0
  42. data/app/helpers/railspress/admin_helper.rb +733 -0
  43. data/app/helpers/railspress/application_helper.rb +23 -0
  44. data/app/helpers/railspress/cms_helper.rb +319 -0
  45. data/app/javascript/railspress/controllers/cms_inline_editor_controller.js +147 -0
  46. data/app/javascript/railspress/controllers/content_element_form_controller.js +15 -0
  47. data/app/javascript/railspress/controllers/crop_controller.js +224 -0
  48. data/app/javascript/railspress/controllers/dropzone_controller.js +261 -0
  49. data/app/javascript/railspress/controllers/focal_point_controller.js +124 -0
  50. data/app/javascript/railspress/controllers/image_section_controller.js +94 -0
  51. data/app/javascript/railspress/controllers/index.js +37 -0
  52. data/app/javascript/railspress/index.js +62 -0
  53. data/app/jobs/railspress/export_posts_job.rb +16 -0
  54. data/app/jobs/railspress/import_posts_job.rb +44 -0
  55. data/app/models/concerns/railspress/has_focal_point.rb +242 -0
  56. data/app/models/concerns/railspress/soft_deletable.rb +23 -0
  57. data/app/models/concerns/railspress/taggable.rb +23 -0
  58. data/app/models/railspress/content_element.rb +103 -0
  59. data/app/models/railspress/content_element_version.rb +32 -0
  60. data/app/models/railspress/content_group.rb +39 -0
  61. data/app/models/railspress/export.rb +67 -0
  62. data/app/models/railspress/focal_point.rb +70 -0
  63. data/app/models/railspress/import.rb +65 -0
  64. data/app/models/railspress/post.rb +102 -15
  65. data/app/models/railspress/post_export_processor.rb +162 -0
  66. data/app/models/railspress/post_import_processor.rb +382 -0
  67. data/app/models/railspress/tag.rb +10 -3
  68. data/app/models/railspress/tagging.rb +11 -0
  69. data/app/services/railspress/content_export_service.rb +122 -0
  70. data/app/services/railspress/content_import_service.rb +228 -0
  71. data/app/views/action_text/attachables/_remote_image.html.erb +8 -0
  72. data/app/views/active_storage/blobs/_blob.html.erb +1 -1
  73. data/app/views/layouts/railspress/admin.html.erb +3 -1
  74. data/app/views/railspress/admin/categories/index.html.erb +11 -15
  75. data/app/views/railspress/admin/cms_transfers/show.html.erb +167 -0
  76. data/app/views/railspress/admin/content_element_versions/show.html.erb +42 -0
  77. data/app/views/railspress/admin/content_elements/_form.html.erb +71 -0
  78. data/app/views/railspress/admin/content_elements/_inline_form.html.erb +32 -0
  79. data/app/views/railspress/admin/content_elements/_inline_form_frame.html.erb +6 -0
  80. data/app/views/railspress/admin/content_elements/edit.html.erb +6 -0
  81. data/app/views/railspress/admin/content_elements/index.html.erb +74 -0
  82. data/app/views/railspress/admin/content_elements/new.html.erb +6 -0
  83. data/app/views/railspress/admin/content_elements/show.html.erb +124 -0
  84. data/app/views/railspress/admin/content_groups/_form.html.erb +9 -0
  85. data/app/views/railspress/admin/content_groups/edit.html.erb +6 -0
  86. data/app/views/railspress/admin/content_groups/index.html.erb +42 -0
  87. data/app/views/railspress/admin/content_groups/new.html.erb +6 -0
  88. data/app/views/railspress/admin/content_groups/show.html.erb +92 -0
  89. data/app/views/railspress/admin/dashboard/index.html.erb +36 -1
  90. data/app/views/railspress/admin/entities/_form.html.erb +53 -0
  91. data/app/views/railspress/admin/entities/edit.html.erb +4 -0
  92. data/app/views/railspress/admin/entities/index.html.erb +74 -0
  93. data/app/views/railspress/admin/entities/new.html.erb +4 -0
  94. data/app/views/railspress/admin/entities/show.html.erb +117 -0
  95. data/app/views/railspress/admin/exports/show.html.erb +62 -0
  96. data/app/views/railspress/admin/imports/_instructions.html.erb +56 -0
  97. data/app/views/railspress/admin/imports/show.html.erb +137 -0
  98. data/app/views/railspress/admin/posts/_form.html.erb +102 -28
  99. data/app/views/railspress/admin/posts/_post_row.html.erb +40 -0
  100. data/app/views/railspress/admin/posts/index.html.erb +47 -36
  101. data/app/views/railspress/admin/posts/show.html.erb +55 -19
  102. data/app/views/railspress/admin/prototypes/image_section.html.erb +42 -0
  103. data/app/views/railspress/admin/shared/_dropzone.html.erb +84 -0
  104. data/app/views/railspress/admin/shared/_focal_point_editor.html.erb +102 -0
  105. data/app/views/railspress/admin/shared/_image_section.html.erb +159 -0
  106. data/app/views/railspress/admin/shared/_image_section_compact.html.erb +90 -0
  107. data/app/views/railspress/admin/shared/_image_section_editor.html.erb +171 -0
  108. data/app/views/railspress/admin/shared/_image_section_v2.html.erb +205 -0
  109. data/app/views/railspress/admin/shared/_sidebar.html.erb +73 -5
  110. data/app/views/railspress/admin/tags/index.html.erb +12 -16
  111. data/config/brakeman.ignore +18 -0
  112. data/config/importmap.rb +23 -0
  113. data/config/routes.rb +62 -1
  114. data/db/migrate/20241218000004_create_railspress_post_tags.rb +1 -1
  115. data/db/migrate/20241218000005_create_railspress_imports.rb +21 -0
  116. data/db/migrate/20241218000006_create_railspress_exports.rb +20 -0
  117. data/db/migrate/20241218000007_create_railspress_taggings.rb +20 -0
  118. data/db/migrate/20241218000008_drop_railspress_post_tags.rb +14 -0
  119. data/db/migrate/20241218000010_add_reading_time_to_railspress_posts.rb +5 -0
  120. data/db/migrate/20250105000002_create_railspress_focal_points.rb +20 -0
  121. data/db/migrate/20260206000001_create_railspress_content_groups.rb +18 -0
  122. data/db/migrate/20260206000002_create_railspress_content_elements.rb +21 -0
  123. data/db/migrate/20260206000003_create_railspress_content_element_versions.rb +20 -0
  124. data/db/migrate/20260207000001_add_unique_index_to_content_elements.rb +11 -0
  125. data/db/migrate/20260211112812_add_image_hint_to_railspress_content_elements.rb +7 -0
  126. data/db/migrate/20260211154040_add_required_to_railspress_content_elements.rb +5 -0
  127. data/lib/generators/railspress/entity/entity_generator.rb +89 -0
  128. data/lib/generators/railspress/entity/templates/migration.rb.tt +13 -0
  129. data/lib/generators/railspress/entity/templates/model.rb.tt +21 -0
  130. data/lib/generators/railspress/install/install_generator.rb +51 -40
  131. data/lib/generators/railspress/install/templates/initializer.rb +29 -0
  132. data/lib/railspress/engine.rb +38 -0
  133. data/lib/railspress/entity.rb +239 -0
  134. data/lib/railspress/version.rb +1 -1
  135. data/lib/railspress.rb +198 -8
  136. data/lib/tasks/railspress_tasks.rake +49 -4
  137. metadata +215 -21
  138. data/MIT-LICENSE +0 -20
  139. data/app/assets/stylesheets/railspress/admin.css +0 -1207
  140. data/app/models/railspress/post_tag.rb +0 -8
@@ -0,0 +1,21 @@
1
+ class CreateRailspressImports < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :railspress_imports do |t|
4
+ t.string :import_type, null: false
5
+ t.string :filename
6
+ t.string :content_type
7
+ t.string :status, default: "pending", null: false
8
+ t.integer :total_count, default: 0
9
+ t.integer :success_count, default: 0
10
+ t.integer :error_count, default: 0
11
+ t.text :error_messages
12
+ t.bigint :user_id
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ add_index :railspress_imports, :import_type
18
+ add_index :railspress_imports, :status
19
+ add_index :railspress_imports, :user_id
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ class CreateRailspressExports < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :railspress_exports do |t|
4
+ t.string :export_type, null: false
5
+ t.string :filename
6
+ t.string :status, default: "pending", null: false
7
+ t.integer :total_count, default: 0
8
+ t.integer :success_count, default: 0
9
+ t.integer :error_count, default: 0
10
+ t.text :error_messages
11
+ t.bigint :user_id
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :railspress_exports, :export_type
17
+ add_index :railspress_exports, :status
18
+ add_index :railspress_exports, :user_id
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ class CreateRailspressTaggings < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :railspress_taggings do |t|
4
+ t.references :tag, null: false, foreign_key: { to_table: :railspress_tags }
5
+ t.references :taggable, polymorphic: true, null: false
6
+ t.timestamps
7
+ end
8
+
9
+ # Unique constraint: one tag per taggable
10
+ add_index :railspress_taggings,
11
+ [ :tag_id, :taggable_type, :taggable_id ],
12
+ unique: true,
13
+ name: "index_taggings_unique"
14
+
15
+ # For "find all entities with tag X" queries
16
+ add_index :railspress_taggings,
17
+ [ :taggable_type, :taggable_id ],
18
+ name: "index_taggings_on_taggable"
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ class DropRailspressPostTags < ActiveRecord::Migration[8.0]
2
+ def up
3
+ drop_table :railspress_post_tags
4
+ end
5
+
6
+ def down
7
+ create_table :railspress_post_tags do |t|
8
+ t.references :post, null: false, foreign_key: { to_table: :railspress_posts }
9
+ t.references :tag, null: false, foreign_key: { to_table: :railspress_tags }
10
+ t.timestamps
11
+ end
12
+ add_index :railspress_post_tags, [ :post_id, :tag_id ], unique: true
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ class AddReadingTimeToRailspressPosts < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :railspress_posts, :reading_time, :integer
4
+ end
5
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateRailspressFocalPoints < ActiveRecord::Migration[8.0]
4
+ def change
5
+ create_table :railspress_focal_points do |t|
6
+ t.references :record, polymorphic: true, null: false, index: false
7
+ t.string :attachment_name, null: false
8
+ t.decimal :focal_x, precision: 5, scale: 4, default: 0.5, null: false
9
+ t.decimal :focal_y, precision: 5, scale: 4, default: 0.5, null: false
10
+ t.json :overrides, default: {}
11
+
12
+ t.timestamps
13
+ end
14
+
15
+ add_index :railspress_focal_points,
16
+ [ :record_type, :record_id, :attachment_name ],
17
+ unique: true,
18
+ name: "idx_focal_points_record_attachment"
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateRailspressContentGroups < ActiveRecord::Migration[8.0]
4
+ def change
5
+ create_table :railspress_content_groups do |t|
6
+ t.string :name, null: false
7
+ t.text :description
8
+ t.bigint :author_id
9
+ t.datetime :deleted_at
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :railspress_content_groups, :name, unique: true
15
+ add_index :railspress_content_groups, :deleted_at
16
+ add_index :railspress_content_groups, :author_id
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateRailspressContentElements < ActiveRecord::Migration[8.0]
4
+ def change
5
+ create_table :railspress_content_elements do |t|
6
+ t.string :name, null: false
7
+ t.integer :content_type, default: 0, null: false
8
+ t.text :text_content
9
+ t.references :content_group, null: false, foreign_key: { to_table: :railspress_content_groups }
10
+ t.bigint :author_id
11
+ t.integer :position
12
+ t.datetime :deleted_at
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ add_index :railspress_content_elements, :deleted_at
18
+ add_index :railspress_content_elements, :author_id
19
+ add_index :railspress_content_elements, :content_type
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateRailspressContentElementVersions < ActiveRecord::Migration[8.0]
4
+ def change
5
+ create_table :railspress_content_element_versions do |t|
6
+ t.references :content_element, null: false, foreign_key: { to_table: :railspress_content_elements }
7
+ t.bigint :author_id
8
+ t.text :text_content
9
+ t.integer :version_number, null: false
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :railspress_content_element_versions,
15
+ [ :content_element_id, :version_number ],
16
+ unique: true,
17
+ name: "idx_content_element_versions_unique"
18
+ add_index :railspress_content_element_versions, :author_id
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddUniqueIndexToContentElements < ActiveRecord::Migration[8.1]
4
+ def change
5
+ add_index :railspress_content_elements,
6
+ [ :content_group_id, :name ],
7
+ unique: true,
8
+ where: "deleted_at IS NULL",
9
+ name: "idx_content_elements_unique_name_per_group"
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddImageHintToRailspressContentElements < ActiveRecord::Migration[8.1]
4
+ def change
5
+ add_column :railspress_content_elements, :image_hint, :string
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class AddRequiredToRailspressContentElements < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_column :railspress_content_elements, :required, :boolean, default: false, null: false
4
+ end
5
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module Railspress
7
+ module Generators
8
+ class EntityGenerator < Rails::Generators::NamedBase
9
+ include ActiveRecord::Generators::Migration
10
+
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ argument :attributes, type: :array, default: [], banner: "field:type field:type"
14
+
15
+ desc "Generate a RailsPress-managed entity with model, migration, and registration"
16
+
17
+ def create_model_file
18
+ template "model.rb.tt", File.join("app/models", "#{file_name}.rb")
19
+ end
20
+
21
+ def create_migration
22
+ migration_template "migration.rb.tt", File.join(db_migrate_path, "create_#{table_name}.rb")
23
+ end
24
+
25
+ def add_registration
26
+ initializer_file = "config/initializers/railspress.rb"
27
+
28
+ unless File.exist?(initializer_file)
29
+ create_file initializer_file, <<~RUBY
30
+ # frozen_string_literal: true
31
+
32
+ Railspress.configure do |config|
33
+ # Register your CMS-managed entities here
34
+ # config.register_entity Project
35
+ end
36
+ RUBY
37
+ say_status :created, "RailsPress initializer", :green
38
+ end
39
+
40
+ inject_into_file initializer_file, after: "Railspress.configure do |config|\n" do
41
+ " config.register_entity #{class_name}\n"
42
+ end
43
+
44
+ say_status :registered, "#{class_name} entity", :green
45
+ end
46
+
47
+ def show_next_steps
48
+ say ""
49
+ say "=" * 60, :green
50
+ say " Entity #{class_name} created!", :green
51
+ say "=" * 60, :green
52
+ say ""
53
+ say "Next steps:", :yellow
54
+ say ""
55
+ say " 1. Run the migration:"
56
+ say " $ rails db:migrate", :cyan
57
+ say ""
58
+ say " 2. Restart your Rails server"
59
+ say ""
60
+ say " 3. Access #{class_name} in the admin:"
61
+ say " /railspress/admin/entities/#{table_name}", :cyan
62
+ say ""
63
+ say "=" * 60, :green
64
+ end
65
+
66
+ private
67
+
68
+ def db_migrate_path
69
+ "db/migrate"
70
+ end
71
+
72
+ def rich_text_fields
73
+ attributes.select { |a| a.type == :rich_text }
74
+ end
75
+
76
+ def regular_attributes
77
+ attributes.reject { |a| a.type == :rich_text }
78
+ end
79
+
80
+ def railspress_field_names
81
+ attributes.map { |a| ":#{a.name}" }.join(", ")
82
+ end
83
+
84
+ def migration_version
85
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Create<%= class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ create_table :<%= table_name %> do |t|
6
+ <% regular_attributes.each do |attribute| -%>
7
+ t.<%= attribute.type %> :<%= attribute.name %>
8
+ <% end -%>
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= class_name %> < ApplicationRecord
4
+ include Railspress::Entity
5
+ <% rich_text_fields.each do |attribute| -%>
6
+
7
+ has_rich_text :<%= attribute.name %>
8
+ <% end -%>
9
+
10
+ # Declare which fields appear in the CMS admin
11
+ railspress_fields <%= railspress_field_names %>
12
+ <% rich_text_fields.each do |attribute| -%>
13
+ railspress_fields :<%= attribute.name %>, as: :rich_text
14
+ <% end -%>
15
+
16
+ # Optional: customize sidebar label (defaults to pluralized class name)
17
+ # railspress_label "Custom Label"
18
+
19
+ # Add your validations
20
+ # validates :title, presence: true
21
+ end
@@ -10,6 +10,10 @@ module Railspress
10
10
 
11
11
  desc "Install RailsPress: copy migrations, mount engine, and configure JavaScript"
12
12
 
13
+ def detect_install_mode
14
+ @upgrading_existing_install = existing_railspress_installation?
15
+ end
16
+
13
17
  def copy_railspress_migrations
14
18
  run "bundle exec rake railspress:install:migrations"
15
19
  end
@@ -59,61 +63,58 @@ module Railspress
59
63
  say_status :pinned, "@rails/activestorage in importmap", :green
60
64
  end
61
65
 
62
- # Pin Lexxy editor
63
- if importmap_content.include?('"lexxy"')
64
- say_status :skip, "Lexxy already pinned in importmap", :yellow
65
- else
66
- append_to_file importmap_file, <<~RUBY
67
-
68
- # RailsPress rich text editor
69
- pin "lexxy", to: "lexxy.js"
70
- RUBY
71
- say_status :pinned, "Lexxy in importmap", :green
72
- end
66
+ # Lexxy is auto-pinned by the engine's importmap — no host app pin needed
73
67
  end
74
68
 
75
- def configure_javascript
76
- return unless importmap_available?
77
-
78
- application_js = Rails.root.join("app", "javascript", "application.js")
79
- return unless File.exist?(application_js)
80
-
81
- js_content = File.read(application_js)
69
+ def generate_initializer
70
+ initializer_path = Rails.root.join("config", "initializers", "railspress.rb")
82
71
 
83
- if js_content.include?('import "lexxy"')
84
- say_status :skip, "Lexxy already imported in application.js", :yellow
72
+ if File.exist?(initializer_path)
73
+ say_status :skip, "config/initializers/railspress.rb already exists", :yellow
85
74
  else
86
- append_to_file application_js, <<~JS
87
-
88
- // RailsPress rich text editor
89
- import "lexxy"
90
- JS
91
- say_status :added, "Lexxy import to application.js", :green
75
+ template "initializer.rb", initializer_path
76
+ say_status :created, "config/initializers/railspress.rb", :green
92
77
  end
93
78
  end
94
79
 
95
80
  def show_post_install_message
81
+ upgrading = !!@upgrading_existing_install
82
+
96
83
  say ""
97
84
  say "=" * 60, :green
98
- say " RailsPress installed successfully!", :green
85
+ say(" RailsPress #{upgrading ? "upgrade setup completed!" : "installed successfully!"}", :green)
99
86
  say "=" * 60, :green
100
87
  say ""
101
- say "Next steps:", :yellow
102
- say ""
103
- say " 1. Run migrations:"
104
- say " $ rails db:migrate", :cyan
88
+ say(upgrading ? "Upgrade next steps:" : "Next steps:", :yellow)
105
89
  say ""
106
- say " 2. Access the admin dashboard:"
107
- say " http://localhost:3000/railspress/admin", :cyan
108
- say ""
109
- say " 3. (Optional) Change the mount path in config/routes.rb:"
110
- say " mount Railspress::Engine => \"/blog\"", :cyan
90
+ if upgrading
91
+ say " 1. Review upgrade notes:"
92
+ say " docs/UPGRADING.md", :cyan
93
+ say ""
94
+ say " 2. Run migrations:"
95
+ say " $ rails db:migrate", :cyan
96
+ say ""
97
+ say " 3. Verify your JS integration if using host-page features:"
98
+ say ' app/javascript/application.js includes: import "railspress"', :cyan
99
+ else
100
+ say " 1. Run migrations:"
101
+ say " $ rails db:migrate", :cyan
102
+ say ""
103
+ say " 2. Access the admin dashboard:"
104
+ say " http://localhost:3000/railspress/admin", :cyan
105
+ say ""
106
+ say " 3. (Optional) Change the mount path in config/routes.rb:"
107
+ say ' mount Railspress::Engine => "/blog"', :cyan
108
+ end
111
109
  say ""
112
- say "JavaScript setup:", :yellow
113
- say " The installer added Lexxy (rich text editor) to your importmap"
114
- say " and application.js. If you use a custom JS entry point, add:"
110
+ say "Optional features:", :yellow
111
+ say " Edit config/initializers/railspress.rb to enable:"
112
+ say " - CMS content elements (config.enable_cms)"
113
+ say " - Inline CMS editing (config.inline_editing_check)"
114
+ say " See docs/CONFIGURING.md and docs/INLINE_EDITING.md for details."
115
115
  say ""
116
- say " import \"lexxy\"", :cyan
116
+ say "Full guides & documentation:", :yellow
117
+ say " https://railspress.org", :cyan
117
118
  say ""
118
119
  say "=" * 60, :green
119
120
  end
@@ -139,6 +140,16 @@ module Railspress
139
140
  def importmap_available?
140
141
  defined?(Importmap) && Rails.root.join("config", "importmap.rb").exist?
141
142
  end
143
+
144
+ def existing_railspress_installation?
145
+ routes_mounted = File.read(rails_route_file).include?("Railspress::Engine")
146
+ initializer_exists = Rails.root.join("config", "initializers", "railspress.rb").exist?
147
+ migrations_copied = Dir.glob(migrations_dir.join("*railspress*.rb")).any?
148
+
149
+ routes_mounted || initializer_exists || migrations_copied
150
+ rescue Errno::ENOENT
151
+ false
152
+ end
142
153
  end
143
154
  end
144
155
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ Railspress.configure do |config|
4
+ # === Blog Features (always available) ===
5
+
6
+ # Author tracking for posts and content elements.
7
+ # Uncomment to enable:
8
+ # config.enable_authors
9
+ # config.author_class_name = "User"
10
+ # config.current_author_method = :current_user
11
+
12
+ # === CMS Content Elements (opt-in) ===
13
+ # Adds content groups, content elements, and the cms_element/cms_value
14
+ # view helpers for managing structured content on your site.
15
+ # Image elements support dropzone upload and focal points when
16
+ # enable_focal_points is also active.
17
+ # See docs/CONFIGURING.md for details.
18
+ # Uncomment to enable:
19
+ # config.enable_cms
20
+
21
+ # === Inline CMS Editing (requires enable_cms) ===
22
+ # Right-click editing of CMS content on public pages.
23
+ # Also requires: import "railspress" in your application.js
24
+ # and yield :head in your layout. See docs/INLINE_EDITING.md.
25
+ # Uncomment to enable:
26
+ # config.inline_editing_check = ->(context) {
27
+ # context.controller.current_user&.admin?
28
+ # }
29
+ end
@@ -6,8 +6,46 @@ module Railspress
6
6
  initializer "railspress.assets" do |app|
7
7
  if app.config.respond_to?(:assets)
8
8
  app.config.assets.paths << root.join("app", "assets", "stylesheets").to_s
9
+ app.config.assets.paths << root.join("app", "assets", "stylesheets", "railspress").to_s
9
10
  app.config.assets.paths << root.join("app", "assets", "javascripts").to_s
10
11
  app.config.assets.paths << root.join("app", "assets", "images").to_s
12
+ app.config.assets.paths << root.join("app", "javascript").to_s
13
+ end
14
+ end
15
+
16
+ # Make CMS helper available to host application views (or stub when disabled)
17
+ initializer "railspress.cms_helper" do
18
+ ActiveSupport.on_load(:action_view) do
19
+ if Railspress.cms_enabled?
20
+ include Railspress::CmsHelper
21
+ else
22
+ include Railspress::CmsHelper::DisabledStub
23
+ end
24
+ end
25
+ end
26
+
27
+ # Clear CMS cache on each request in development
28
+ initializer "railspress.cms_cache" do |app|
29
+ if Railspress.cms_enabled?
30
+ app.config.to_prepare do
31
+ Railspress::CmsHelper.clear_cache
32
+ end
33
+ end
34
+ end
35
+
36
+ # Validate configuration after all initializers have run
37
+ initializer "railspress.validate_config" do
38
+ if Railspress.inline_editing_check && !Railspress.cms_enabled?
39
+ raise Railspress::ConfigurationError,
40
+ "Inline editing requires CMS. Add `config.enable_cms` to your initializer."
41
+ end
42
+ end
43
+
44
+ # Configure importmap for Stimulus controllers
45
+ initializer "railspress.importmap", before: "importmap" do |app|
46
+ if app.respond_to?(:importmap)
47
+ app.config.importmap.paths << root.join("config", "importmap.rb")
48
+ app.config.importmap.cache_sweepers << root.join("app", "javascript")
11
49
  end
12
50
  end
13
51
  end