railspress-engine 1.0.0 → 1.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00fa3ef64f615a9f1a7bb10c79085c50657dfa462e3f61e57002dfdc9e181d5e
4
- data.tar.gz: c241000c9554ac492a6fed87f1d1bded59ea3257541d14e6a855c3102463cf03
3
+ metadata.gz: 620cf1a81acefe35aedfda0894686721073bdad83c405be7b9fcb967df04829d
4
+ data.tar.gz: 6ffab1d40c3dade6d4fed057174754d1caecc854e0415d634d49e160ab8bcc3f
5
5
  SHA512:
6
- metadata.gz: aa8e9ad5cf137778317c7c94bf9c75fdffe5a32356a3a2681ef6278868e16d16d9b9686edab3bcd4c190136f8fcfc0cefab0799d50fed20c37a74978cfea7e7b
7
- data.tar.gz: 2fe456078ff4a52b014a43b56f3ad8e434b62f001ac42b07dd5cc5eb335af88a5ae428c13863cf467df69e0c749bd74792b7590d94ccacc6e63b2aa528fc9089
6
+ metadata.gz: dfe7e288c00fefbfbb8d23999bdff5211154ec68e00b79a3e6d7269b957fd99a2d784ea414243399fdfff43a684f635dd3d3be99e05fa31d628341c480e9a3aa
7
+ data.tar.gz: 3ff12ea74a82a33ebb1fe504944540e8ddb72dd599f7e85042c27ea144764ab6ba7116aba693cbabb037ebe474d52b23529f8509dd8db04a155289f37701a232
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  </p>
5
5
 
6
6
  <p align="center">
7
- <a href="https://rubygems.org/gems/railspress-engine"><img src="https://img.shields.io/gem/v/railspress-engine.svg?style=flat" alt="Gem Version"></a>
7
+ <a href="https://rubygems.org/gems/railspress-engine"><img src="https://img.shields.io/gem/v/railspress-engine.svg?style=flat&bump=true" alt="Gem Version"></a>
8
8
  <img src="https://img.shields.io/badge/Rails-8.1%2B-red.svg?style=flat" alt="Rails 8.1+">
9
9
  <img src="https://img.shields.io/badge/Ruby-3.3%2B-red.svg?style=flat" alt="Ruby 3.3+">
10
10
  <a href="https://osaasy.dev/"><img src="https://img.shields.io/badge/License-O'Saasy-blue.svg?style=flat" alt="License"></a>
@@ -14,6 +14,8 @@
14
14
 
15
15
  RailsPress is a mountable Rails engine that gives your app a complete content management system — namespaced and isolated so it stays out of your way.
16
16
 
17
+ You can read more at [Railspress.org](https://railspress.org).
18
+
17
19
  It manages three kinds of content:
18
20
 
19
21
  ## Posts, Entities, and Blocks
@@ -22,7 +24,7 @@ It manages three kinds of content:
22
24
 
23
25
  Your blog. Chronological content published over time — articles, news, announcements. Categories, tags, draft/published workflow.
24
26
 
25
- - Rich text editing with [Lexxy](https://github.com/aviflombaum/lexxy) + markdown mode toggle
27
+ - Rich text editing with [Lexxy](https://github.com/basecamp/lexxy) + markdown mode toggle
26
28
  - Categories, tags, draft/published workflow
27
29
  - SEO metadata (meta title, meta description)
28
30
  - Reading time auto-calculation
@@ -4,11 +4,21 @@
4
4
  */
5
5
 
6
6
  :where(lexxy-toolbar){
7
+ position: sticky;
8
+ top: 0;
9
+ z-index: 10;
10
+ background: var(--rp-bg-elevated, #fff);
7
11
  margin-bottom: 20px;
8
12
  padding-bottom: 10px;
13
+ border-bottom: 1px solid var(--rp-border);
9
14
  }
10
15
 
11
- /* Fix toolbar dropdown alignment (color/link buttons) */
16
+ /*
17
+ * Disabled custom Lexxy dropdown sizing override.
18
+ * Lexxy 0.9.8.beta manages dropdown layout internally; forcing fixed 34x34
19
+ * sizing can break toolbar dropdown/picker rendering.
20
+ */
21
+ /*
12
22
  .lexxy-editor__toolbar-dropdown {
13
23
  width: 34px !important;
14
24
  height: 34px !important;
@@ -30,6 +40,7 @@
30
40
  .lexxy-editor__toolbar-dropdown > summary::-webkit-details-marker {
31
41
  display: none;
32
42
  }
43
+ */
33
44
 
34
45
  /* Rich text content spacing */
35
46
  .rp-rich-text p,
@@ -26,6 +26,9 @@
26
26
 
27
27
  .rp-sidebar-content {
28
28
  padding: var(--rp-space-lg) var(--rp-space-md);
29
+ min-height: 100%;
30
+ display: flex;
31
+ flex-direction: column;
29
32
  }
30
33
 
31
34
  .rp-sidebar-header {
@@ -130,6 +133,75 @@
130
133
  margin: var(--rp-space-md) 0;
131
134
  }
132
135
 
136
+ .rp-sidebar-footer {
137
+ margin-top: auto;
138
+ padding-top: var(--rp-space-md);
139
+ border-top: 1px solid var(--rp-sidebar-hover);
140
+ display: flex;
141
+ flex-direction: column;
142
+ gap: var(--rp-space-xs);
143
+ }
144
+
145
+ .rp-sidebar-version {
146
+ font-size: 0.75rem;
147
+ font-weight: 600;
148
+ letter-spacing: 0.02em;
149
+ text-transform: uppercase;
150
+ color: var(--rp-sidebar-text);
151
+ margin-bottom: var(--rp-space-xs);
152
+ }
153
+
154
+ .rp-sidebar-footer-link {
155
+ display: flex;
156
+ align-items: center;
157
+ gap: var(--rp-space-sm);
158
+ padding: 8px 10px;
159
+ border-radius: var(--rp-radius);
160
+ color: var(--rp-sidebar-text);
161
+ font-size: 0.8125rem;
162
+ font-weight: 500;
163
+ text-decoration: none;
164
+ transition: all 0.15s ease;
165
+ }
166
+
167
+ .rp-sidebar-footer-link:hover {
168
+ background: var(--rp-sidebar-hover);
169
+ color: var(--rp-sidebar-text-active);
170
+ }
171
+
172
+ .rp-sidebar-footer-icon {
173
+ width: 16px;
174
+ height: 16px;
175
+ opacity: 0.8;
176
+ flex-shrink: 0;
177
+ }
178
+
179
+ .rp-sidebar-footer-link:hover .rp-sidebar-footer-icon {
180
+ opacity: 1;
181
+ }
182
+
183
+ .rp-sidebar-footer-text {
184
+ white-space: nowrap;
185
+ overflow: hidden;
186
+ }
187
+
188
+ .rp-sidebar-credit {
189
+ margin: var(--rp-space-sm) 0 0;
190
+ color: var(--rp-sidebar-text);
191
+ font-size: 0.75rem;
192
+ line-height: 1.45;
193
+ }
194
+
195
+ .rp-sidebar-credit-link {
196
+ color: var(--rp-sidebar-text-active);
197
+ text-decoration: none;
198
+ border-bottom: 1px solid transparent;
199
+ }
200
+
201
+ .rp-sidebar-credit-link:hover {
202
+ border-bottom-color: currentColor;
203
+ }
204
+
133
205
  /* Main Content Area */
134
206
  .rp-main {
135
207
  flex: 1;
@@ -195,6 +267,22 @@
195
267
  display: block;
196
268
  }
197
269
 
270
+ .rp-sidebar--collapsed .rp-sidebar-footer {
271
+ align-items: center;
272
+ }
273
+
274
+ .rp-sidebar--collapsed .rp-sidebar-version,
275
+ .rp-sidebar--collapsed .rp-sidebar-footer-text,
276
+ .rp-sidebar--collapsed .rp-sidebar-credit {
277
+ display: none;
278
+ }
279
+
280
+ .rp-sidebar--collapsed .rp-sidebar-footer-link {
281
+ justify-content: center;
282
+ width: 100%;
283
+ padding: 8px;
284
+ }
285
+
198
286
  /* Main content adjustment when sidebar collapsed */
199
287
  .rp-admin-layout--sidebar-collapsed .rp-main {
200
288
  margin-left: var(--rp-sidebar-width-collapsed);
@@ -56,6 +56,21 @@
56
56
  clip: auto;
57
57
  }
58
58
 
59
+ .rp-sidebar--collapsed .rp-sidebar-footer {
60
+ align-items: stretch;
61
+ }
62
+
63
+ .rp-sidebar--collapsed .rp-sidebar-version,
64
+ .rp-sidebar--collapsed .rp-sidebar-footer-text,
65
+ .rp-sidebar--collapsed .rp-sidebar-credit {
66
+ display: block;
67
+ }
68
+
69
+ .rp-sidebar--collapsed .rp-sidebar-footer-link {
70
+ justify-content: flex-start;
71
+ padding: 8px 10px;
72
+ }
73
+
59
74
  .rp-mobile-header {
60
75
  display: flex;
61
76
  }
@@ -153,8 +153,9 @@ module Railspress
153
153
  private
154
154
 
155
155
  # Wrap content in a Stimulus-controlled <span> for inline editing.
156
- # Includes a display Turbo Frame, hidden context menu with form Turbo Frame,
157
- # and hidden backdrop.
156
+ # Only the display Turbo Frame lives inside the span the menu and
157
+ # backdrop are created dynamically on document.body by the JS controller
158
+ # to avoid inheriting ancestor stacking contexts (opacity, transforms).
158
159
  def inline_wrapper_for(content_element, rendered_content)
159
160
  suffix = SecureRandom.hex(4)
160
161
  display_frame_id = "cms_display_#{content_element.id}_#{suffix}"
@@ -177,27 +178,7 @@ module Railspress
177
178
  },
178
179
  style: "display:contents"
179
180
  ) do
180
- # Display frame (replaced after save)
181
- display = content_tag("turbo-frame", rendered_content, id: display_frame_id)
182
-
183
- # Context menu panel (hidden by default)
184
- menu = content_tag(:div, class: "rp-inline-menu rp-inline-hidden",
185
- data: { "rp--cms-inline-editor-target": "menu" }
186
- ) do
187
- # Form Turbo Frame (lazy-loaded on first open)
188
- content_tag("turbo-frame", "", id: form_frame_id,
189
- src: nil,
190
- data: { "rp--cms-inline-editor-target": "frame" })
191
- end
192
-
193
- # Backdrop (hidden by default)
194
- backdrop = content_tag(:div, "", class: "rp-inline-backdrop rp-inline-hidden",
195
- data: {
196
- "rp--cms-inline-editor-target": "backdrop",
197
- action: "click->rp--cms-inline-editor#close"
198
- })
199
-
200
- safe_join([ display, menu, backdrop ])
181
+ content_tag("turbo-frame", rendered_content, id: display_frame_id)
201
182
  end
202
183
  end
203
184
 
@@ -7,6 +7,9 @@ import { Controller } from "@hotwired/stimulus"
7
7
  * Opens a positioned context menu with a Turbo Frame that lazy-loads an
8
8
  * inline edit form from the admin controller.
9
9
  *
10
+ * The menu and backdrop are created dynamically on document.body to avoid
11
+ * inheriting ancestor stacking contexts (opacity, transforms, etc.).
12
+ *
10
13
  * Usage (generated by CmsHelper#inline_wrapper_for):
11
14
  * <span data-controller="rp--cms-inline-editor"
12
15
  * data-rp--cms-inline-editor-inline-path-value="/railspress/admin/content_elements/1/inline"
@@ -15,17 +18,10 @@ import { Controller } from "@hotwired/stimulus"
15
18
  * data-rp--cms-inline-editor-element-id-value="1"
16
19
  * data-action="contextmenu->rp--cms-inline-editor#open"
17
20
  * style="display:contents">
18
- *
19
21
  * <turbo-frame id="cms_display_1_abc123">Content here</turbo-frame>
20
- * <div class="rp-inline-menu rp-inline-hidden" data-rp--cms-inline-editor-target="menu">
21
- * <turbo-frame id="cms_form_1_abc123" data-rp--cms-inline-editor-target="frame"></turbo-frame>
22
- * </div>
23
- * <div class="rp-inline-backdrop rp-inline-hidden" data-rp--cms-inline-editor-target="backdrop"></div>
24
22
  * </span>
25
23
  */
26
24
  export default class extends Controller {
27
- static targets = ["menu", "frame", "backdrop"]
28
-
29
25
  static values = {
30
26
  inlinePath: String,
31
27
  frameId: String,
@@ -37,32 +33,53 @@ export default class extends Controller {
37
33
  connect() {
38
34
  this._handleKeydown = this.handleKeydown.bind(this)
39
35
  this._handleSubmitEnd = this.handleSubmitEnd.bind(this)
36
+ this._handleBackdropClick = () => this.close()
40
37
  }
41
38
 
42
39
  disconnect() {
43
40
  document.removeEventListener("keydown", this._handleKeydown)
44
- this.element.removeEventListener("turbo:submit-end", this._handleSubmitEnd)
41
+ if (this._menu) this._menu.remove()
42
+ if (this._backdrop) this._backdrop.remove()
43
+ }
44
+
45
+ // Lazily create the menu and backdrop on document.body (once per controller)
46
+ _ensureMenuExists() {
47
+ if (this._menu) return
48
+
49
+ this._backdrop = document.createElement("div")
50
+ this._backdrop.className = "rp-inline-backdrop rp-inline-hidden"
51
+ this._backdrop.addEventListener("click", this._handleBackdropClick)
52
+ document.body.appendChild(this._backdrop)
53
+
54
+ this._menu = document.createElement("div")
55
+ this._menu.className = "rp-inline-menu rp-inline-hidden"
56
+ document.body.appendChild(this._menu)
57
+
58
+ this._frame = document.createElement("turbo-frame")
59
+ this._frame.id = this.formFrameIdValue
60
+ this._menu.appendChild(this._frame)
45
61
  }
46
62
 
47
63
  open(event) {
48
64
  event.preventDefault()
49
65
  event.stopPropagation()
50
66
 
67
+ this._ensureMenuExists()
68
+
51
69
  // Close any other open inline editors (one-at-a-time)
52
70
  document.querySelectorAll(".rp-inline-menu:not(.rp-inline-hidden)").forEach(menu => {
53
- const controller = this.application.getControllerForElementAndIdentifier(
54
- menu.closest("[data-controller*='rp--cms-inline-editor']"),
55
- "rp--cms-inline-editor"
56
- )
57
- if (controller && controller !== this) controller.close()
71
+ if (menu !== this._menu) menu.classList.add("rp-inline-hidden")
72
+ })
73
+ document.querySelectorAll(".rp-inline-backdrop:not(.rp-inline-hidden)").forEach(backdrop => {
74
+ if (backdrop !== this._backdrop) backdrop.classList.add("rp-inline-hidden")
58
75
  })
59
76
 
60
77
  // Lazy-load the form Turbo Frame on first open
61
- if (!this.loadedValue && this.hasFrameTarget) {
78
+ if (!this.loadedValue) {
62
79
  const url = new URL(this.inlinePathValue, window.location.origin)
63
80
  url.searchParams.set("form_frame_id", this.formFrameIdValue)
64
81
  url.searchParams.set("display_frame_id", this.frameIdValue)
65
- this.frameTarget.setAttribute("src", url.toString())
82
+ this._frame.setAttribute("src", url.toString())
66
83
  this.loadedValue = true
67
84
  }
68
85
 
@@ -70,19 +87,19 @@ export default class extends Controller {
70
87
  this.positionMenu(event.clientX, event.clientY)
71
88
 
72
89
  // Show menu and backdrop
73
- this.menuTarget.classList.remove("rp-inline-hidden")
74
- this.backdropTarget.classList.remove("rp-inline-hidden")
90
+ this._menu.classList.remove("rp-inline-hidden")
91
+ this._backdrop.classList.remove("rp-inline-hidden")
75
92
 
76
93
  // Listen for escape and form submission
77
94
  document.addEventListener("keydown", this._handleKeydown)
78
- this.element.addEventListener("turbo:submit-end", this._handleSubmitEnd)
95
+ this._menu.addEventListener("turbo:submit-end", this._handleSubmitEnd)
79
96
  }
80
97
 
81
98
  close() {
82
- this.menuTarget.classList.add("rp-inline-hidden")
83
- this.backdropTarget.classList.add("rp-inline-hidden")
99
+ if (this._menu) this._menu.classList.add("rp-inline-hidden")
100
+ if (this._backdrop) this._backdrop.classList.add("rp-inline-hidden")
84
101
  document.removeEventListener("keydown", this._handleKeydown)
85
- this.element.removeEventListener("turbo:submit-end", this._handleSubmitEnd)
102
+ if (this._menu) this._menu.removeEventListener("turbo:submit-end", this._handleSubmitEnd)
86
103
  }
87
104
 
88
105
  handleKeydown(event) {
@@ -101,7 +118,7 @@ export default class extends Controller {
101
118
  }
102
119
 
103
120
  positionMenu(x, y) {
104
- const menu = this.menuTarget
121
+ const menu = this._menu
105
122
  // Reset position to measure natural dimensions
106
123
  menu.style.left = "0px"
107
124
  menu.style.top = "0px"
@@ -16,6 +16,9 @@
16
16
  * register(application)
17
17
  */
18
18
 
19
+ // Lexxy rich text editor
20
+ import "lexxy"
21
+
19
22
  // Import Turbo for Turbo Frames support
20
23
  import "@hotwired/turbo-rails"
21
24
 
@@ -30,7 +30,7 @@ module Railspress
30
30
 
31
31
  # Get override for specific context
32
32
  def override_for(context)
33
- overrides[context.to_s]&.with_indifferent_access
33
+ overrides&.dig(context.to_s)&.with_indifferent_access
34
34
  end
35
35
 
36
36
  # Check if context has custom override (not using focal point)
@@ -132,7 +132,7 @@ module Railspress
132
132
  zip_filename = "posts_export_#{timestamp}.zip"
133
133
  zip_path = Rails.root.join("tmp", zip_filename)
134
134
 
135
- Zip::File.open(zip_path, Zip::File::CREATE) do |zipfile|
135
+ Zip::File.open(zip_path, create: true) do |zipfile|
136
136
  add_directory_to_zip(zipfile, @export_dir, "")
137
137
  end
138
138
 
@@ -60,9 +60,9 @@ module Railspress
60
60
  zip_file.each do |entry|
61
61
  next if entry.name.start_with?("__MACOSX", ".")
62
62
 
63
- destination = File.join(@extract_dir, entry.name)
64
- FileUtils.mkdir_p(File.dirname(destination))
65
- entry.extract(destination) unless File.exist?(destination)
63
+ # rubyzip 3+ expects extraction with a destination directory.
64
+ destination = File.join(@extract_dir.to_s, entry.name)
65
+ entry.extract(entry.name, destination_directory: @extract_dir.to_s) unless File.exist?(destination)
66
66
  end
67
67
  end
68
68
 
@@ -21,7 +21,9 @@ module Railspress
21
21
  return [] if csv_string.blank?
22
22
 
23
23
  tag_names = csv_string.split(",").map { |t| t.strip.downcase }.reject(&:blank?).uniq
24
- tag_names.map { |name| find_or_create_by(name: name) }
24
+ tag_names.map do |name|
25
+ find_by(name: name) || find_by(slug: name.parameterize) || create!(name: name)
26
+ end
25
27
  end
26
28
 
27
29
  private
@@ -73,9 +73,9 @@ module Railspress
73
73
  break
74
74
  end
75
75
 
76
- destination = File.join(@extract_dir, entry.name)
77
- FileUtils.mkdir_p(File.dirname(destination))
78
- entry.extract(destination) unless File.exist?(destination)
76
+ # rubyzip 3+ expects extraction with a destination directory.
77
+ destination = File.join(@extract_dir.to_s, entry.name)
78
+ entry.extract(entry.name, destination_directory: @extract_dir.to_s) unless File.exist?(destination)
79
79
  end
80
80
  end
81
81
  rescue Zip::Error => e
@@ -103,4 +103,36 @@
103
103
  <span class="rp-nav-text">Main Site</span>
104
104
  <% end %>
105
105
  </nav>
106
+
107
+ <div class="rp-sidebar-footer">
108
+ <div class="rp-sidebar-version">v<%= Railspress::VERSION %></div>
109
+
110
+ <%= link_to "https://railspress.org/guide/", class: "rp-sidebar-footer-link", target: "_blank", rel: "noopener noreferrer", title: "Guide" do %>
111
+ <svg class="rp-sidebar-footer-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
112
+ <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/>
113
+ <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5V4.5A2.5 2.5 0 0 1 6.5 2z"/>
114
+ </svg>
115
+ <span class="rp-sidebar-footer-text">Guide</span>
116
+ <% end %>
117
+
118
+ <%= link_to "https://railspress.org/docs/", class: "rp-sidebar-footer-link", target: "_blank", rel: "noopener noreferrer", title: "Docs" do %>
119
+ <svg class="rp-sidebar-footer-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
120
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
121
+ <polyline points="14 2 14 8 20 8"/>
122
+ </svg>
123
+ <span class="rp-sidebar-footer-text">Docs</span>
124
+ <% end %>
125
+
126
+ <%= link_to "https://github.com/aviflombaum/railspress-engine", class: "rp-sidebar-footer-link", target: "_blank", rel: "noopener noreferrer", title: "GitHub" do %>
127
+ <svg class="rp-sidebar-footer-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
128
+ <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/>
129
+ </svg>
130
+ <span class="rp-sidebar-footer-text">GitHub</span>
131
+ <% end %>
132
+
133
+ <p class="rp-sidebar-credit">
134
+ Made with ❤️ by
135
+ <%= link_to "Avi.nyc", "https://avi.nyc", target: "_blank", rel: "noopener noreferrer", class: "rp-sidebar-credit-link" %>
136
+ </p>
137
+ </div>
106
138
  </div>
data/config/importmap.rb CHANGED
@@ -8,6 +8,9 @@
8
8
  #
9
9
  # This auto-registers all RailsPress Stimulus controllers.
10
10
 
11
+ # Lexxy rich text editor (hard dependency — auto-pinned so host apps don't need to)
12
+ pin "lexxy", to: "lexxy.js"
13
+
11
14
  # Turbo for Turbo Frames support
12
15
  pin "@hotwired/turbo-rails", to: "turbo.min.js"
13
16
 
@@ -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,17 +63,7 @@ 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
69
  def generate_initializer
@@ -84,21 +78,34 @@ module Railspress
84
78
  end
85
79
 
86
80
  def show_post_install_message
81
+ upgrading = !!@upgrading_existing_install
82
+
87
83
  say ""
88
84
  say "=" * 60, :green
89
- say " RailsPress installed successfully!", :green
85
+ say(" RailsPress #{upgrading ? "upgrade setup completed!" : "installed successfully!"}", :green)
90
86
  say "=" * 60, :green
91
87
  say ""
92
- say "Next steps:", :yellow
88
+ say(upgrading ? "Upgrade next steps:" : "Next steps:", :yellow)
93
89
  say ""
94
- say " 1. Run migrations:"
95
- say " $ rails db:migrate", :cyan
96
- say ""
97
- say " 2. Access the admin dashboard:"
98
- say " http://localhost:3000/railspress/admin", :cyan
99
- say ""
100
- say " 3. (Optional) Change the mount path in config/routes.rb:"
101
- 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
102
109
  say ""
103
110
  say "Optional features:", :yellow
104
111
  say " Edit config/initializers/railspress.rb to enable:"
@@ -106,6 +113,9 @@ module Railspress
106
113
  say " - Inline CMS editing (config.inline_editing_check)"
107
114
  say " See docs/CONFIGURING.md and docs/INLINE_EDITING.md for details."
108
115
  say ""
116
+ say "Full guides & documentation:", :yellow
117
+ say " https://railspress.org", :cyan
118
+ say ""
109
119
  say "=" * 60, :green
110
120
  end
111
121
 
@@ -130,6 +140,16 @@ module Railspress
130
140
  def importmap_available?
131
141
  defined?(Importmap) && Rails.root.join("config", "importmap.rb").exist?
132
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
133
153
  end
134
154
  end
135
155
  end
@@ -1,3 +1,3 @@
1
1
  module Railspress
2
- VERSION = "1.0.0"
2
+ VERSION = "1.2.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: railspress-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Avi Flombaum
@@ -27,42 +27,42 @@ dependencies:
27
27
  name: lexxy
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - "~>"
30
+ - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 0.7.6.beta
32
+ version: 0.9.0.beta
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
37
+ - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 0.7.6.beta
39
+ version: 0.9.0.beta
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: rubyzip
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - "~>"
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: '2.3'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: '2.3'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: redcarpet
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - "~>"
58
+ - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: '3.6'
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - "~>"
65
+ - - ">="
66
66
  - !ruby/object:Gem::Version
67
67
  version: '3.6'
68
68
  - !ruby/object:Gem::Dependency
@@ -297,14 +297,19 @@ metadata:
297
297
  post_install_message: |2+
298
298
 
299
299
  ============================================================
300
- RailsPress installed! Run the generator to complete setup:
300
+ RailsPress installed/updated.
301
301
 
302
+ New install:
302
303
  $ rails generate railspress:install
303
304
 
304
- This will:
305
- - Copy database migrations
306
- - Mount the engine in your routes
307
- - Install ActionText (if needed)
305
+ Upgrading existing app:
306
+ $ rails railspress:install:migrations
307
+ $ rails db:migrate
308
+
309
+ Guides:
310
+ - Install/setup: README.md + docs/CONFIGURING.md
311
+ - Upgrading: docs/UPGRADING.md
312
+ - Full docs: https://railspress.org
308
313
  ============================================================
309
314
 
310
315
  rdoc_options: []