helios-press 0.1.0 → 0.3
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 +4 -4
- data/README.md +83 -18
- data/app/controllers/helios/press/admin/base_controller.rb +1 -2
- data/app/controllers/helios/press/admin/blocks_controller.rb +1 -1
- data/app/controllers/helios/press/admin/posts_controller.rb +5 -5
- data/app/controllers/helios/press/api/posts_controller.rb +19 -13
- data/app/controllers/helios/press/posts_controller.rb +5 -1
- data/app/models/helios/press/block_image.rb +23 -1
- data/app/services/helios/press/image_ingestor.rb +167 -0
- data/app/views/helios/press/admin/blocks/_image_block.html.erb +2 -2
- data/app/views/helios/press/admin/blocks/_image_item.html.erb +14 -2
- data/app/views/helios/press/admin/blocks/_text_block.html.erb +2 -2
- data/app/views/helios/press/admin/blocks/_video_block.html.erb +11 -3
- data/app/views/helios/press/admin/posts/_form.html.erb +2 -2
- data/app/views/helios/press/admin/posts/edit.html.erb +1 -1
- data/app/views/helios/press/admin/posts/index.html.erb +32 -21
- data/app/views/helios/press/posts/blocks/_image_container.html.erb +20 -3
- data/app/views/helios/press/posts/index.html.erb +21 -0
- data/config/routes/admin.rb +17 -0
- data/config/routes/api.rb +5 -0
- data/config/routes/public.rb +6 -0
- data/config/routes.rb +0 -19
- data/db/migrate/20250526000001_add_image_ingestion_to_helios_press.rb +6 -0
- data/lib/helios/press/configuration.rb +2 -0
- data/lib/helios/press/engine.rb +42 -0
- data/lib/helios/press/version.rb +1 -1
- data/lib/helios/press.rb +1 -0
- data/lib/helios-press.rb +1 -0
- metadata +27 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1d3b3438e1adcc7a8776aab2c1504eca5768bdb82529e1a8013b5c52786b1322
|
|
4
|
+
data.tar.gz: 0caad891b10dc08f98f43ded048b20abfeec168829642cdcc26505f0360dc862
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f42ac281d93c06b6bbdd98b869f9c6a63f68309f4036a9cefd85f32e87770ae18f62a2fbce701b8940fcc6ba3d3d8569aee9175f72585ebb5ff80cb8c05001a
|
|
7
|
+
data.tar.gz: c999f3186d81ee01f529de8170b3b32677c93939ca3a6af02979a670554b19dbce6e2a10d0f87d8222a70481147d4b3a56214bee24202acdaa8beb94b48949c6
|
data/README.md
CHANGED
|
@@ -32,6 +32,9 @@ Helios::Press.configure do |config|
|
|
|
32
32
|
# Parent controller for admin views (must provide authentication)
|
|
33
33
|
config.admin_parent_controller = "Admin::BaseController"
|
|
34
34
|
|
|
35
|
+
# Parent controller for public views (blog index/show)
|
|
36
|
+
config.public_parent_controller = "ApplicationController"
|
|
37
|
+
|
|
35
38
|
# Optional slug prefix for posts
|
|
36
39
|
config.post_slug_prefix = nil # e.g., "blog/" for /blog/my-post
|
|
37
40
|
|
|
@@ -49,16 +52,40 @@ end
|
|
|
49
52
|
|
|
50
53
|
## Routes
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
Helios::Press provides three independent engines that you can mount wherever you want:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# Admin block editor — mount behind your auth
|
|
59
|
+
mount Helios::Press::Admin::Engine, at: "/admin/press"
|
|
60
|
+
|
|
61
|
+
# Public blog index/show — mount at your preferred public path
|
|
62
|
+
mount Helios::Press::Public::Engine, at: "/blog"
|
|
63
|
+
|
|
64
|
+
# API for external post ingestion
|
|
65
|
+
mount Helios::Press::Api::Engine, at: "/api/press"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Mount only the engines you need. For example, if you only want the admin editor and will build your own public views:
|
|
53
69
|
|
|
54
70
|
```ruby
|
|
55
|
-
mount Helios::Press::Engine, at: "/
|
|
71
|
+
mount Helios::Press::Admin::Engine, at: "/admin/press"
|
|
56
72
|
```
|
|
57
73
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
-
|
|
74
|
+
### Routes provided
|
|
75
|
+
|
|
76
|
+
**Admin Engine:**
|
|
77
|
+
- `GET /` — Posts list
|
|
78
|
+
- `GET /posts/new` — New post form
|
|
79
|
+
- `GET /posts/:id/edit` — Block editor
|
|
80
|
+
- `POST/PATCH/DELETE /posts/:id` — CRUD
|
|
81
|
+
- Block and image sub-resources
|
|
82
|
+
|
|
83
|
+
**Public Engine:**
|
|
84
|
+
- `GET /` — Published posts index
|
|
85
|
+
- `GET /:slug` — Single post view
|
|
86
|
+
|
|
87
|
+
**API Engine:**
|
|
88
|
+
- `POST /posts` — Upsert a post by `external_id`
|
|
62
89
|
|
|
63
90
|
## JavaScript Setup
|
|
64
91
|
|
|
@@ -69,15 +96,11 @@ import {
|
|
|
69
96
|
HeliosPressBlocksController,
|
|
70
97
|
HeliosPressTextBlockController,
|
|
71
98
|
HeliosPressImageBlockController
|
|
72
|
-
} from "helios
|
|
99
|
+
} from "helios/press"
|
|
73
100
|
|
|
74
101
|
application.register("helios-press-blocks", HeliosPressBlocksController)
|
|
75
102
|
application.register("helios-press-text-block", HeliosPressTextBlockController)
|
|
76
103
|
application.register("helios-press-image-block", HeliosPressImageBlockController)
|
|
77
|
-
|
|
78
|
-
// If using helios-videos:
|
|
79
|
-
import { HeliosVideoBlockController } from "helios-videos"
|
|
80
|
-
application.register("helios-video-block", HeliosVideoBlockController)
|
|
81
104
|
```
|
|
82
105
|
|
|
83
106
|
**npm dependencies** (add to your host app's package.json):
|
|
@@ -86,13 +109,35 @@ application.register("helios-video-block", HeliosVideoBlockController)
|
|
|
86
109
|
- `@rails/activestorage`
|
|
87
110
|
- `trix` and `@rails/actiontext`
|
|
88
111
|
|
|
112
|
+
### Vite
|
|
113
|
+
|
|
114
|
+
If your host app uses Vite, add an alias so Vite can resolve the gem's JavaScript:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// vite.config.mts
|
|
118
|
+
resolve: {
|
|
119
|
+
alias: {
|
|
120
|
+
'helios/press': resolve(__dirname, '/path/to/helios-press/app/javascript/helios/press'),
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
When using a local path gem, point to the local checkout. When using the published gem, point to the installed gem path (e.g., via `bundle show helios-press`).
|
|
126
|
+
|
|
89
127
|
## CSS
|
|
90
128
|
|
|
91
|
-
Include the block editor styles in your
|
|
129
|
+
Include the block editor styles in your stylesheet:
|
|
92
130
|
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
|
|
131
|
+
```scss
|
|
132
|
+
@import 'helios_press_blocks';
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
You can either symlink or copy the file from the gem:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Symlink (local development)
|
|
139
|
+
ln -s /path/to/helios-press/app/assets/stylesheets/helios/press/blocks.css \
|
|
140
|
+
app/assets/stylesheets/_helios_press_blocks.scss
|
|
96
141
|
```
|
|
97
142
|
|
|
98
143
|
## Block Types
|
|
@@ -103,7 +148,7 @@ Include the block editor styles in your asset pipeline:
|
|
|
103
148
|
|
|
104
149
|
## API Ingestion
|
|
105
150
|
|
|
106
|
-
POST to
|
|
151
|
+
POST to your mounted API path with `X-API-Key` header:
|
|
107
152
|
|
|
108
153
|
```json
|
|
109
154
|
{
|
|
@@ -112,11 +157,31 @@ POST to `/helios_press/api/posts` with `X-API-Key` header:
|
|
|
112
157
|
"slug": "my-post-title",
|
|
113
158
|
"description": "Meta description",
|
|
114
159
|
"keywords": "keyword1, keyword2",
|
|
115
|
-
"body_html": "<p>Post content...</p>",
|
|
116
|
-
"published": true
|
|
160
|
+
"body_html": "<p>Post content with <img src=\"https://example.com/photo.jpg\"> tags...</p>",
|
|
161
|
+
"published": true,
|
|
162
|
+
"images": [
|
|
163
|
+
{
|
|
164
|
+
"reference_key": "hero1",
|
|
165
|
+
"url": "https://example.com/hero.jpg",
|
|
166
|
+
"alt": "Hero image",
|
|
167
|
+
"caption": "Photo credit: Example"
|
|
168
|
+
}
|
|
169
|
+
]
|
|
117
170
|
}
|
|
118
171
|
```
|
|
119
172
|
|
|
173
|
+
### Image Ingestion
|
|
174
|
+
|
|
175
|
+
When a post is ingested via the API, all images in `body_html` are automatically downloaded, stored via ActiveStorage (e.g., to S3), and the `<img>` src attributes are rewritten to local proxy URLs. This ensures no external image hotlinking in published posts.
|
|
176
|
+
|
|
177
|
+
Two modes are supported:
|
|
178
|
+
|
|
179
|
+
1. **Explicit references**: Use `src="helios://image/<reference_key>"` in your `body_html` and provide the actual download URL in the `images` array. The ingestor matches them by `reference_key`.
|
|
180
|
+
|
|
181
|
+
2. **Auto-import**: Any `src="https://..."` URL in `body_html` is automatically downloaded. Images are deduplicated by a SHA256 hash of the URL path (ignoring query strings/signatures), so re-ingesting the same image with different signed URLs won't create duplicates.
|
|
182
|
+
|
|
183
|
+
Images are stored as `BlockImage` records with ActiveStorage attachments and served via `rails_storage_proxy_path`. Failed downloads are logged and skipped (soft failure — the original src is preserved).
|
|
184
|
+
|
|
120
185
|
## License
|
|
121
186
|
|
|
122
187
|
Proprietary. All rights reserved.
|
|
@@ -30,7 +30,7 @@ module Helios
|
|
|
30
30
|
|
|
31
31
|
# Handle video upload for video_container blocks (when helios-videos is present)
|
|
32
32
|
if @block.video_container? && params[:video_signed_id].present? && Helios::Press.videos_enabled?
|
|
33
|
-
video = Helios::Videos
|
|
33
|
+
video = Helios::Videos.video_class.new(name: params[:video_name], block: @block)
|
|
34
34
|
video.video_file.attach(params[:video_signed_id])
|
|
35
35
|
video.save!
|
|
36
36
|
end
|
|
@@ -23,7 +23,7 @@ module Helios
|
|
|
23
23
|
@post = Post.new(post_params)
|
|
24
24
|
|
|
25
25
|
if @post.save
|
|
26
|
-
redirect_to
|
|
26
|
+
redirect_to helios_press_admin.edit_post_path(@post), notice: "Post was successfully created."
|
|
27
27
|
else
|
|
28
28
|
render :new, status: :unprocessable_entity
|
|
29
29
|
end
|
|
@@ -42,7 +42,7 @@ module Helios
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
if @post.update(update_params)
|
|
45
|
-
redirect_to
|
|
45
|
+
redirect_to helios_press_admin.edit_post_path(@post), notice: success_message
|
|
46
46
|
else
|
|
47
47
|
render :edit, status: :unprocessable_entity
|
|
48
48
|
end
|
|
@@ -50,7 +50,7 @@ module Helios
|
|
|
50
50
|
|
|
51
51
|
def destroy
|
|
52
52
|
@post.destroy
|
|
53
|
-
redirect_to
|
|
53
|
+
redirect_to helios_press_admin.posts_path, notice: "Post was successfully deleted."
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
private
|
|
@@ -63,8 +63,8 @@ module Helios
|
|
|
63
63
|
params.require(:post).permit(:name, :published, :slug, :keywords, :description)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
def
|
|
67
|
-
Helios::Press::Engine.routes.url_helpers
|
|
66
|
+
def helios_press_admin
|
|
67
|
+
Helios::Press::Admin::Engine.routes.url_helpers
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
end
|
|
@@ -2,7 +2,7 @@ module Helios
|
|
|
2
2
|
module Press
|
|
3
3
|
module Api
|
|
4
4
|
class PostsController < Helios::Press::Api::BaseController
|
|
5
|
-
# POST /api/
|
|
5
|
+
# POST /api/press/posts
|
|
6
6
|
# Upsert a post by external_id. Payload shape:
|
|
7
7
|
# {
|
|
8
8
|
# "external_id": "helios-blog-post-draft-4821",
|
|
@@ -10,9 +10,18 @@ module Helios
|
|
|
10
10
|
# "slug": "...",
|
|
11
11
|
# "keywords": "...",
|
|
12
12
|
# "description": "...",
|
|
13
|
-
# "body_html": "
|
|
14
|
-
# "published": true
|
|
13
|
+
# "body_html": "<p>Content with <img src=\"https://...\"> tags...</p>",
|
|
14
|
+
# "published": true,
|
|
15
|
+
# "images": [
|
|
16
|
+
# { "reference_key": "hero1", "url": "https://...", "alt": "...", "caption": "..." }
|
|
17
|
+
# ]
|
|
15
18
|
# }
|
|
19
|
+
#
|
|
20
|
+
# Images in body_html are automatically downloaded, stored via ActiveStorage,
|
|
21
|
+
# and their src attributes rewritten to local proxy URLs. Two modes:
|
|
22
|
+
# 1. Explicit: use src="helios://image/<reference_key>" in body_html and
|
|
23
|
+
# provide the download URL in the images array.
|
|
24
|
+
# 2. Auto: any src="https://..." in body_html is downloaded automatically.
|
|
16
25
|
def create
|
|
17
26
|
external_id = params[:external_id].to_s
|
|
18
27
|
return render json: { error: "external_id is required" }, status: :bad_request if external_id.blank?
|
|
@@ -27,19 +36,16 @@ module Helios
|
|
|
27
36
|
published: ActiveModel::Type::Boolean.new.cast(params[:published])
|
|
28
37
|
)
|
|
29
38
|
|
|
30
|
-
|
|
39
|
+
post.save!
|
|
40
|
+
|
|
31
41
|
if params[:body_html].present?
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
post.blocks.build(block_type: "text", position: 0, content: params[:body_html])
|
|
38
|
-
end
|
|
42
|
+
ingestor = ImageIngestor.new(post)
|
|
43
|
+
ingestor.call(
|
|
44
|
+
body_html: params[:body_html],
|
|
45
|
+
images_payload: params[:images] || []
|
|
46
|
+
)
|
|
39
47
|
end
|
|
40
48
|
|
|
41
|
-
post.save!
|
|
42
|
-
|
|
43
49
|
render json: {
|
|
44
50
|
ok: true,
|
|
45
51
|
id: post.id,
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
module Helios
|
|
2
2
|
module Press
|
|
3
|
-
class PostsController < ::
|
|
3
|
+
class PostsController < Helios::Press.configuration.public_parent_controller.constantize
|
|
4
|
+
def index
|
|
5
|
+
@posts = Post.published.reverse_sorted
|
|
6
|
+
end
|
|
7
|
+
|
|
4
8
|
def show
|
|
5
9
|
@post = Post.published.find_by!(slug: params[:slug])
|
|
6
10
|
end
|
|
@@ -8,9 +8,31 @@ module Helios
|
|
|
8
8
|
has_one_attached :file
|
|
9
9
|
|
|
10
10
|
validates :position, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
|
11
|
-
validates :file, presence: true
|
|
11
|
+
validates :file, presence: true, unless: :reference_key?
|
|
12
|
+
|
|
13
|
+
def reference_key?
|
|
14
|
+
reference_key.present?
|
|
15
|
+
end
|
|
12
16
|
|
|
13
17
|
acts_as_list scope: :block
|
|
18
|
+
|
|
19
|
+
DOCUMENT_CONTENT_TYPES = %w[
|
|
20
|
+
application/pdf
|
|
21
|
+
application/msword
|
|
22
|
+
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
def svg?
|
|
26
|
+
file.attached? && file.content_type == "image/svg+xml"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def document?
|
|
30
|
+
file.attached? && DOCUMENT_CONTENT_TYPES.include?(file.content_type)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def variable?
|
|
34
|
+
file.attached? && file.variable?
|
|
35
|
+
end
|
|
14
36
|
end
|
|
15
37
|
end
|
|
16
38
|
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "nokogiri"
|
|
5
|
+
require "uri"
|
|
6
|
+
require "digest"
|
|
7
|
+
|
|
8
|
+
module Helios
|
|
9
|
+
module Press
|
|
10
|
+
# Downloads remote images referenced in ingested post HTML, stores them via
|
|
11
|
+
# ActiveStorage, and rewrites the body HTML so <img> src attributes point at
|
|
12
|
+
# locally-hosted ActiveStorage proxy URLs.
|
|
13
|
+
#
|
|
14
|
+
# Supports two modes:
|
|
15
|
+
# 1. Explicit references: src="helios://image/<reference_key>" paired with
|
|
16
|
+
# an images_payload array containing download URLs.
|
|
17
|
+
# 2. Auto-import: any remaining src="https://..." URLs are downloaded,
|
|
18
|
+
# deduplicated by a SHA256 of the URL path.
|
|
19
|
+
class ImageIngestor
|
|
20
|
+
class FetchError < StandardError; end
|
|
21
|
+
|
|
22
|
+
MAX_BYTES = 15.megabytes
|
|
23
|
+
|
|
24
|
+
def initialize(post)
|
|
25
|
+
@post = post
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# images_payload: Array of Hashes
|
|
29
|
+
# [{ "reference_key" => "abc123", "url" => "https://...", "alt" => "...", "caption" => "..." }]
|
|
30
|
+
def call(body_html:, images_payload: [])
|
|
31
|
+
ensure_text_block!
|
|
32
|
+
upsert_inline_images(images_payload)
|
|
33
|
+
rewritten = rewrite_body_html(body_html)
|
|
34
|
+
text_block.content = rewritten
|
|
35
|
+
text_block.save!
|
|
36
|
+
rewritten
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
attr_reader :post
|
|
42
|
+
|
|
43
|
+
def text_block
|
|
44
|
+
@text_block ||= post.blocks.find_by(block_type: "text") ||
|
|
45
|
+
post.blocks.build(block_type: "text", position: 0)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def ensure_text_block!
|
|
49
|
+
text_block # trigger lazy init
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def image_block
|
|
53
|
+
@image_block ||= post.blocks.find_by(block_type: "image_container") ||
|
|
54
|
+
post.blocks.create!(block_type: "image_container", position: 1)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def upsert_inline_images(payload)
|
|
58
|
+
return if payload.blank?
|
|
59
|
+
|
|
60
|
+
payload.each do |entry|
|
|
61
|
+
key = entry["reference_key"].presence
|
|
62
|
+
next unless key
|
|
63
|
+
|
|
64
|
+
bi = image_block.block_images.find_or_initialize_by(reference_key: key)
|
|
65
|
+
bi.caption = entry["caption"].to_s if entry["caption"].present?
|
|
66
|
+
bi.position ||= (image_block.block_images.maximum(:position) || 0) + 1
|
|
67
|
+
|
|
68
|
+
if bi.file.attached?
|
|
69
|
+
bi.save! if bi.changed?
|
|
70
|
+
next
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
blob = fetch_as_blob(entry["url"])
|
|
74
|
+
unless blob
|
|
75
|
+
Rails.logger.warn("Helios::Press::ImageIngestor: skipping #{key} — blob fetch failed")
|
|
76
|
+
next
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
bi.file.attach(blob)
|
|
80
|
+
bi.save!
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def rewrite_body_html(html)
|
|
85
|
+
return html if html.blank?
|
|
86
|
+
|
|
87
|
+
doc = Nokogiri::HTML.fragment(html)
|
|
88
|
+
doc.css("img").each do |img|
|
|
89
|
+
src = img["src"].to_s
|
|
90
|
+
|
|
91
|
+
if src.start_with?("helios://image/")
|
|
92
|
+
key = src.delete_prefix("helios://image/")
|
|
93
|
+
bi = image_block.block_images.find_by(reference_key: key)
|
|
94
|
+
next unless bi&.file&.attached?
|
|
95
|
+
|
|
96
|
+
img["src"] = proxy_path(bi.file)
|
|
97
|
+
img["alt"] = bi.caption if bi.caption.present? && img["alt"].blank?
|
|
98
|
+
elsif src.match?(%r{\Ahttps?://}i)
|
|
99
|
+
bi = import_remote_image(src, alt: img["alt"].to_s)
|
|
100
|
+
next unless bi&.file&.attached?
|
|
101
|
+
|
|
102
|
+
img["src"] = proxy_path(bi.file)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
doc.to_html
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def import_remote_image(url, alt: "")
|
|
110
|
+
uri = URI.parse(url)
|
|
111
|
+
key = "auto_#{Digest::SHA256.hexdigest(uri.path.to_s)[0, 16]}"
|
|
112
|
+
|
|
113
|
+
bi = image_block.block_images.find_or_initialize_by(reference_key: key)
|
|
114
|
+
bi.caption = alt if alt.present? && bi.caption.blank?
|
|
115
|
+
bi.position ||= (image_block.block_images.maximum(:position) || 0) + 1
|
|
116
|
+
|
|
117
|
+
if bi.file.attached?
|
|
118
|
+
bi.save! if bi.changed?
|
|
119
|
+
return bi
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
blob = fetch_as_blob(url)
|
|
123
|
+
unless blob
|
|
124
|
+
Rails.logger.warn("Helios::Press::ImageIngestor: skipping remote image #{url} — blob fetch failed")
|
|
125
|
+
return nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
bi.file.attach(blob)
|
|
129
|
+
bi.save!
|
|
130
|
+
bi
|
|
131
|
+
rescue StandardError => e
|
|
132
|
+
Rails.logger.error("Helios::Press::ImageIngestor: failed to import #{url} — #{e.class}: #{e.message}")
|
|
133
|
+
nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def proxy_path(attachment)
|
|
137
|
+
Rails.application.routes.url_helpers.rails_storage_proxy_path(attachment, only_path: true)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def fetch_as_blob(url)
|
|
141
|
+
uri = URI.parse(url)
|
|
142
|
+
return nil unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
143
|
+
|
|
144
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
145
|
+
http.open_timeout = 10
|
|
146
|
+
http.read_timeout = 30
|
|
147
|
+
http.request(Net::HTTP::Get.new(uri))
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
raise FetchError, "HTTP #{response.code} fetching #{url}" unless response.is_a?(Net::HTTPSuccess)
|
|
151
|
+
raise FetchError, "Image too large: #{response.body.bytesize}" if response.body.bytesize > MAX_BYTES
|
|
152
|
+
|
|
153
|
+
filename = File.basename(uri.path.presence || "image")
|
|
154
|
+
filename = "image.jpg" if filename.blank? || filename == "/"
|
|
155
|
+
|
|
156
|
+
ActiveStorage::Blob.create_and_upload!(
|
|
157
|
+
io: StringIO.new(response.body),
|
|
158
|
+
filename: filename,
|
|
159
|
+
content_type: response["content-type"] || "application/octet-stream"
|
|
160
|
+
)
|
|
161
|
+
rescue StandardError => e
|
|
162
|
+
Rails.logger.error("Helios::Press::ImageIngestor: failed to fetch #{url} — #{e.class}: #{e.message}")
|
|
163
|
+
nil
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<div data-controller="helios-press-image-block"
|
|
2
2
|
data-helios-press-image-block-id-value="<%= block.id %>"
|
|
3
3
|
data-helios-press-image-block-post-id-value="<%= post.id %>"
|
|
4
|
-
data-helios-press-image-block-base-url-value="<%=
|
|
5
|
-
data-helios-press-image-block-block-url-value="<%=
|
|
4
|
+
data-helios-press-image-block-base-url-value="<%= helios_press_admin.post_block_images_path(post, block) %>"
|
|
5
|
+
data-helios-press-image-block-block-url-value="<%= helios_press_admin.post_block_path(post, block) %>"
|
|
6
6
|
class="image-block-container">
|
|
7
7
|
|
|
8
8
|
<!-- Block Controls -->
|
|
@@ -2,10 +2,22 @@
|
|
|
2
2
|
data-helios-press-image-block-target="image"
|
|
3
3
|
data-image-id="<%= block_image.id %>">
|
|
4
4
|
|
|
5
|
-
<!-- Image -->
|
|
5
|
+
<!-- Image / Document -->
|
|
6
6
|
<div class="image-wrapper">
|
|
7
7
|
<% if block_image.file.attached? %>
|
|
8
|
-
|
|
8
|
+
<% if block_image.svg? %>
|
|
9
|
+
<%= image_tag url_for(block_image.file), class: "block-image" %>
|
|
10
|
+
<% elsif block_image.document? %>
|
|
11
|
+
<div class="document-preview d-flex flex-column align-items-center justify-content-center h-100">
|
|
12
|
+
<i class="bi <%= block_image.file.content_type.include?('pdf') ? 'bi-file-earmark-pdf' : 'bi-file-earmark-word' %>" style="font-size: 3rem; color: #6c757d;"></i>
|
|
13
|
+
<small class="text-muted mt-1"><%= block_image.file.filename %></small>
|
|
14
|
+
<%= link_to "Download", url_for(block_image.file), class: "btn btn-sm btn-outline-secondary mt-1", download: true %>
|
|
15
|
+
</div>
|
|
16
|
+
<% elsif block_image.variable? %>
|
|
17
|
+
<%= image_tag block_image.file.variant(resize_to_limit: [800, 800]), class: "block-image" %>
|
|
18
|
+
<% else %>
|
|
19
|
+
<%= image_tag url_for(block_image.file), class: "block-image" %>
|
|
20
|
+
<% end %>
|
|
9
21
|
<% end %>
|
|
10
22
|
|
|
11
23
|
<!-- Delete button -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div data-controller="helios-press-text-block"
|
|
2
2
|
data-helios-press-text-block-id-value="<%= block.id %>"
|
|
3
3
|
data-helios-press-text-block-post-id-value="<%= post.id %>"
|
|
4
|
-
data-helios-press-text-block-base-url-value="<%=
|
|
4
|
+
data-helios-press-text-block-base-url-value="<%= helios_press_admin.post_block_path(post, block) %>"
|
|
5
5
|
data-helios-press-text-block-mode-value="view">
|
|
6
6
|
|
|
7
7
|
<!-- View Mode -->
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<!-- Edit Mode -->
|
|
19
19
|
<div data-helios-press-text-block-target="editMode" class="block-edit-mode d-none">
|
|
20
20
|
<%= form_with(model: block,
|
|
21
|
-
url:
|
|
21
|
+
url: helios_press_admin.post_block_path(post, block),
|
|
22
22
|
data: { helios_press_text_block_target: "form" }) do |f| %>
|
|
23
23
|
<%= f.rich_text_area :content,
|
|
24
24
|
data: { helios_press_text_block_target: "editor" },
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<% if block.respond_to?(:video) && block.video.present? %>
|
|
2
2
|
<% video = block.video %>
|
|
3
|
+
<% video_ready = video.key.present? && video.playback_urls.present? %>
|
|
3
4
|
<div data-controller="helios-video-block"
|
|
4
5
|
data-helios-video-block-video-id-value="<%= video.id %>"
|
|
6
|
+
data-helios-video-block-ready-value="<%= video_ready %>"
|
|
7
|
+
data-helios-video-block-status-url-value="<%= helios_videos.admin_video_path(video) %>"
|
|
5
8
|
class="video-block-container">
|
|
6
9
|
<!-- Block Controls -->
|
|
7
10
|
<div class="d-flex justify-content-end mb-2">
|
|
@@ -32,12 +35,17 @@
|
|
|
32
35
|
</div>
|
|
33
36
|
|
|
34
37
|
<!-- Video Player -->
|
|
35
|
-
<div class="video-player-wrapper">
|
|
38
|
+
<div class="video-player-wrapper" data-helios-video-block-target="player">
|
|
36
39
|
<%= video.player_component.html_safe %>
|
|
37
40
|
</div>
|
|
38
41
|
</div>
|
|
39
42
|
<% else %>
|
|
40
|
-
<div class="alert alert-warning mb-0">
|
|
41
|
-
No video found for this block
|
|
43
|
+
<div class="alert alert-warning mb-0 d-flex justify-content-between align-items-center">
|
|
44
|
+
<span>No video found for this block.</span>
|
|
45
|
+
<button type="button"
|
|
46
|
+
class="btn btn-sm btn-danger"
|
|
47
|
+
data-action="click->helios-press-blocks#deleteBlock">
|
|
48
|
+
Delete Block
|
|
49
|
+
</button>
|
|
42
50
|
</div>
|
|
43
51
|
<% end %>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= form_with(model: post, url: post.persisted? ?
|
|
1
|
+
<%= form_with(model: post, url: post.persisted? ? helios_press_admin.post_path(post) : helios_press_admin.posts_path, local: true) do |form| %>
|
|
2
2
|
<% if post.errors.any? %>
|
|
3
3
|
<div class="alert alert-danger">
|
|
4
4
|
<h4><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h4>
|
|
@@ -43,6 +43,6 @@
|
|
|
43
43
|
<% if post.persisted? && !post.published? %>
|
|
44
44
|
<%= form.submit "Publish Now", name: "publish", class: "btn btn-success", data: { turbo_confirm: "Publish this post now?" } %>
|
|
45
45
|
<% end %>
|
|
46
|
-
<%= link_to "Cancel",
|
|
46
|
+
<%= link_to "Cancel", helios_press_admin.posts_path, class: "btn btn-secondary" %>
|
|
47
47
|
</div>
|
|
48
48
|
<% end %>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<h5 class="mb-0">Content Blocks</h5>
|
|
17
17
|
</div>
|
|
18
18
|
<div class="card-body">
|
|
19
|
-
<div data-controller="helios-press-blocks" data-helios-press-blocks-post-id-value="<%= @post.id %>" data-helios-press-blocks-base-url-value="<%=
|
|
19
|
+
<div data-controller="helios-press-blocks" data-helios-press-blocks-post-id-value="<%= @post.id %>" data-helios-press-blocks-base-url-value="<%= helios_press_admin.post_blocks_path(@post) %>">
|
|
20
20
|
<%= render "blocks_container", post: @post %>
|
|
21
21
|
</div>
|
|
22
22
|
</div>
|
|
@@ -1,30 +1,41 @@
|
|
|
1
1
|
<div class="container py-5">
|
|
2
2
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
3
3
|
<h1>Posts</h1>
|
|
4
|
-
<%= link_to "New Post",
|
|
4
|
+
<%= link_to "New Post", helios_press_admin.new_post_path, class: "btn btn-primary" %>
|
|
5
5
|
</div>
|
|
6
6
|
|
|
7
7
|
<% if @posts.any? %>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
8
|
+
<table class="table">
|
|
9
|
+
<thead>
|
|
10
|
+
<tr>
|
|
11
|
+
<th>Title</th>
|
|
12
|
+
<th>Slug</th>
|
|
13
|
+
<th>Status</th>
|
|
14
|
+
<th>Last Updated</th>
|
|
15
|
+
<th>Actions</th>
|
|
16
|
+
</tr>
|
|
17
|
+
</thead>
|
|
18
|
+
<tbody>
|
|
19
|
+
<% @posts.each do |post| %>
|
|
20
|
+
<tr>
|
|
21
|
+
<td><%= link_to post.name, helios_press_admin.edit_post_path(post) %></td>
|
|
22
|
+
<td><small class="text-muted">/<%= post.slug %></small></td>
|
|
23
|
+
<td>
|
|
24
|
+
<% if post.published? %>
|
|
25
|
+
<span class="badge bg-success">Published</span>
|
|
26
|
+
<% else %>
|
|
27
|
+
<span class="badge bg-secondary">Draft</span>
|
|
28
|
+
<% end %>
|
|
29
|
+
</td>
|
|
30
|
+
<td><%= post.updated_at.strftime("%m/%d/%y") %></td>
|
|
31
|
+
<td>
|
|
32
|
+
<%= link_to "Edit", helios_press_admin.edit_post_path(post), class: "btn btn-sm btn-outline-primary" %>
|
|
33
|
+
<%= link_to "Delete", helios_press_admin.post_path(post), data: { turbo_method: :delete, turbo_confirm: "Are you sure?" }, class: "btn btn-sm btn-outline-danger" %>
|
|
34
|
+
</td>
|
|
35
|
+
</tr>
|
|
36
|
+
<% end %>
|
|
37
|
+
</tbody>
|
|
38
|
+
</table>
|
|
28
39
|
<% else %>
|
|
29
40
|
<p class="text-muted">No posts yet. Create your first post!</p>
|
|
30
41
|
<% end %>
|
|
@@ -3,9 +3,26 @@
|
|
|
3
3
|
<% block.block_images.each do |block_image| %>
|
|
4
4
|
<div class="image-item">
|
|
5
5
|
<% if block_image.file.attached? %>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
<% if block_image.svg? %>
|
|
7
|
+
<%= image_tag url_for(block_image.file),
|
|
8
|
+
class: "img-fluid rounded",
|
|
9
|
+
alt: block_image.caption.presence || "" %>
|
|
10
|
+
<% elsif block_image.document? %>
|
|
11
|
+
<div class="document-download text-center p-3">
|
|
12
|
+
<i class="bi <%= block_image.file.content_type.include?('pdf') ? 'bi-file-earmark-pdf' : 'bi-file-earmark-word' %>" style="font-size: 3rem;"></i>
|
|
13
|
+
<p class="mt-2 mb-0">
|
|
14
|
+
<%= link_to block_image.file.filename, url_for(block_image.file), download: true %>
|
|
15
|
+
</p>
|
|
16
|
+
</div>
|
|
17
|
+
<% elsif block_image.variable? %>
|
|
18
|
+
<%= image_tag block_image.file.variant(resize_to_limit: [1200, 1200]),
|
|
19
|
+
class: "img-fluid rounded",
|
|
20
|
+
alt: block_image.caption.presence || "" %>
|
|
21
|
+
<% else %>
|
|
22
|
+
<%= image_tag url_for(block_image.file),
|
|
23
|
+
class: "img-fluid rounded",
|
|
24
|
+
alt: block_image.caption.presence || "" %>
|
|
25
|
+
<% end %>
|
|
9
26
|
<% end %>
|
|
10
27
|
<% if block_image.caption.present? %>
|
|
11
28
|
<p class="image-caption text-muted mt-2 small">
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<div class="container my-5">
|
|
2
|
+
<h1>Blog</h1>
|
|
3
|
+
|
|
4
|
+
<% if @posts.any? %>
|
|
5
|
+
<% @posts.each do |post| %>
|
|
6
|
+
<article class="mb-4">
|
|
7
|
+
<h2><%= link_to post.name, helios_press_public.post_path(post.slug) %></h2>
|
|
8
|
+
<p class="text-muted">
|
|
9
|
+
<time datetime="<%= post.updated_at.iso8601 %>">
|
|
10
|
+
<%= post.updated_at.strftime("%m/%d/%y") %>
|
|
11
|
+
</time>
|
|
12
|
+
</p>
|
|
13
|
+
<% if post.description.present? %>
|
|
14
|
+
<p><%= post.description %></p>
|
|
15
|
+
<% end %>
|
|
16
|
+
</article>
|
|
17
|
+
<% end %>
|
|
18
|
+
<% else %>
|
|
19
|
+
<p>No posts yet.</p>
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Helios::Press::Admin::Engine.routes.draw do
|
|
2
|
+
scope module: "helios/press/admin" do
|
|
3
|
+
root to: "posts#index"
|
|
4
|
+
resources :posts do
|
|
5
|
+
resources :blocks, only: [:create, :update, :destroy] do
|
|
6
|
+
collection do
|
|
7
|
+
patch :reorder
|
|
8
|
+
end
|
|
9
|
+
resources :images, controller: "block_images", only: [:create, :update, :destroy] do
|
|
10
|
+
collection do
|
|
11
|
+
patch :reorder
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/config/routes.rb
CHANGED
|
@@ -1,21 +1,2 @@
|
|
|
1
1
|
Helios::Press::Engine.routes.draw do
|
|
2
|
-
namespace :admin do
|
|
3
|
-
root to: "posts#index"
|
|
4
|
-
resources :posts do
|
|
5
|
-
resources :blocks, only: [:create, :update, :destroy] do
|
|
6
|
-
collection do
|
|
7
|
-
patch :reorder
|
|
8
|
-
end
|
|
9
|
-
resources :images, controller: "block_images", only: [:create, :update, :destroy] do
|
|
10
|
-
collection do
|
|
11
|
-
patch :reorder
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
namespace :api do
|
|
19
|
-
resources :posts, only: [:create]
|
|
20
|
-
end
|
|
21
2
|
end
|
|
@@ -2,12 +2,14 @@ module Helios
|
|
|
2
2
|
module Press
|
|
3
3
|
class Configuration
|
|
4
4
|
attr_accessor :admin_parent_controller,
|
|
5
|
+
:public_parent_controller,
|
|
5
6
|
:api_parent_controller,
|
|
6
7
|
:api_authentication,
|
|
7
8
|
:post_slug_prefix
|
|
8
9
|
|
|
9
10
|
def initialize
|
|
10
11
|
@admin_parent_controller = "ApplicationController"
|
|
12
|
+
@public_parent_controller = "ApplicationController"
|
|
11
13
|
@api_parent_controller = "ActionController::API"
|
|
12
14
|
@api_authentication = nil
|
|
13
15
|
@post_slug_prefix = nil
|
data/lib/helios/press/engine.rb
CHANGED
|
@@ -6,6 +6,48 @@ module Helios
|
|
|
6
6
|
initializer "helios_press.assets" do |app|
|
|
7
7
|
app.config.assets.precompile += %w[helios/press/blocks.css] if app.config.respond_to?(:assets)
|
|
8
8
|
end
|
|
9
|
+
|
|
10
|
+
initializer "helios_press.helpers" do
|
|
11
|
+
ActiveSupport.on_load(:action_controller) do
|
|
12
|
+
helper Rails.application.routes.url_helpers
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module Admin
|
|
18
|
+
class Engine < ::Rails::Engine
|
|
19
|
+
engine_name "helios_press_admin"
|
|
20
|
+
|
|
21
|
+
self.paths["config/routes.rb"] = "config/routes/admin.rb"
|
|
22
|
+
|
|
23
|
+
initializer "helios_press_admin.helpers" do
|
|
24
|
+
ActiveSupport.on_load(:action_controller) do
|
|
25
|
+
helper Rails.application.routes.url_helpers
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module Public
|
|
32
|
+
class Engine < ::Rails::Engine
|
|
33
|
+
engine_name "helios_press_public"
|
|
34
|
+
|
|
35
|
+
self.paths["config/routes.rb"] = "config/routes/public.rb"
|
|
36
|
+
|
|
37
|
+
initializer "helios_press_public.helpers" do
|
|
38
|
+
ActiveSupport.on_load(:action_controller) do
|
|
39
|
+
helper Rails.application.routes.url_helpers
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module Api
|
|
46
|
+
class Engine < ::Rails::Engine
|
|
47
|
+
engine_name "helios_press_api"
|
|
48
|
+
|
|
49
|
+
self.paths["config/routes.rb"] = "config/routes/api.rb"
|
|
50
|
+
end
|
|
9
51
|
end
|
|
10
52
|
end
|
|
11
53
|
end
|
data/lib/helios/press/version.rb
CHANGED
data/lib/helios/press.rb
CHANGED
data/lib/helios-press.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "helios/press"
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: helios-press
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: '0.3'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jason Fleetwood-Boldt
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-05-26 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: rails
|
|
@@ -37,6 +38,20 @@ dependencies:
|
|
|
37
38
|
- - ">="
|
|
38
39
|
- !ruby/object:Gem::Version
|
|
39
40
|
version: '1.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: nokogiri
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.13'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.13'
|
|
40
55
|
- !ruby/object:Gem::Dependency
|
|
41
56
|
name: helios-videos
|
|
42
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -97,6 +112,7 @@ files:
|
|
|
97
112
|
- app/models/helios/press/block.rb
|
|
98
113
|
- app/models/helios/press/block_image.rb
|
|
99
114
|
- app/models/helios/press/post.rb
|
|
115
|
+
- app/services/helios/press/image_ingestor.rb
|
|
100
116
|
- app/views/helios/press/admin/blocks/_block.html.erb
|
|
101
117
|
- app/views/helios/press/admin/blocks/_image_block.html.erb
|
|
102
118
|
- app/views/helios/press/admin/blocks/_image_item.html.erb
|
|
@@ -110,13 +126,19 @@ files:
|
|
|
110
126
|
- app/views/helios/press/posts/blocks/_image_container.html.erb
|
|
111
127
|
- app/views/helios/press/posts/blocks/_text.html.erb
|
|
112
128
|
- app/views/helios/press/posts/blocks/_video_container.html.erb
|
|
129
|
+
- app/views/helios/press/posts/index.html.erb
|
|
113
130
|
- app/views/helios/press/posts/show.html.erb
|
|
114
131
|
- app/views/layouts/helios/press/admin.html.erb
|
|
115
132
|
- app/views/layouts/helios/press/application.html.erb
|
|
116
133
|
- config/routes.rb
|
|
134
|
+
- config/routes/admin.rb
|
|
135
|
+
- config/routes/api.rb
|
|
136
|
+
- config/routes/public.rb
|
|
117
137
|
- db/migrate/20250510000001_create_helios_press_posts.rb
|
|
118
138
|
- db/migrate/20250510000002_create_helios_press_blocks.rb
|
|
119
139
|
- db/migrate/20250510000003_create_helios_press_block_images.rb
|
|
140
|
+
- db/migrate/20250526000001_add_image_ingestion_to_helios_press.rb
|
|
141
|
+
- lib/helios-press.rb
|
|
120
142
|
- lib/helios/press.rb
|
|
121
143
|
- lib/helios/press/configuration.rb
|
|
122
144
|
- lib/helios/press/engine.rb
|
|
@@ -128,6 +150,7 @@ licenses:
|
|
|
128
150
|
metadata:
|
|
129
151
|
homepage_uri: https://github.com/heliosdev-shop/helios-press
|
|
130
152
|
source_code_uri: https://github.com/heliosdev-shop/helios-press
|
|
153
|
+
post_install_message:
|
|
131
154
|
rdoc_options: []
|
|
132
155
|
require_paths:
|
|
133
156
|
- lib
|
|
@@ -142,7 +165,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
142
165
|
- !ruby/object:Gem::Version
|
|
143
166
|
version: '0'
|
|
144
167
|
requirements: []
|
|
145
|
-
rubygems_version: 3.
|
|
168
|
+
rubygems_version: 3.4.19
|
|
169
|
+
signing_key:
|
|
146
170
|
specification_version: 4
|
|
147
171
|
summary: Multi-block blog post editor for Rails with drag-to-reorder
|
|
148
172
|
test_files: []
|