social_construct 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10cb6057e914282f135714277ab5459c5794e1892a4a2a0825c24cc64ec6259c
4
- data.tar.gz: 239310809089b66d572365265074e18a32d1ddc7c693374d60090276a38d793f
3
+ metadata.gz: 9b1b86abf2f0675a09e15f221cbd4cbda23fbd5f5b37d0406130c30808e3f127
4
+ data.tar.gz: 5069906dc7297bddd8600b3bef4b321881c09e475138f63980cea760e0e4ff71
5
5
  SHA512:
6
- metadata.gz: f269fe451a28fae49d20e8c78953b810fe39c7db26559c80d8f555a8cc03ec9f0d80ce811ff6aa94466ba815fd6b4dfe4c19542477878729201b52f70dfbf4aa
7
- data.tar.gz: 868aa2dd22efbae1a7e105c8d10f0cfb2bbeee80f71386acdc44c1205b8672a279eefbca334255255230808863d5bde084de1b3ab5c936da39359d5da5dd4f32
6
+ metadata.gz: a921f74dd5276deef7b2fc446ccfda8adf15d092ff9e2f43fb48c454f7487a4079286b9bf4a3a3346625aaa95716d956229a73f6dce8a4261fa1a0ffbbc7785b
7
+ data.tar.gz: 84b6f50e3d7a6fa73432304be9ded5dfb08f47c72f0c30d72b56e779c9ec0f1c73de02db0799f9582f6e94ecdfc6f28409e48247f3f4e6afd32cf171a7e4a667
data/README.md CHANGED
@@ -1,190 +1,154 @@
1
- # SocialConstruct
1
+ <img src="https://s3.brnbw.com/Stack-C9apVxz8Pg.webp" width="600">
2
2
 
3
- A Rails engine for generating social media preview cards (Open Graph images) with built-in preview functionality.
3
+ - Design using HTML/CSS
4
+ - Supports images, fonts, caching, ...
5
+ - Built-in previews in development (like mailers)
6
+ - Only requirement is Chrome(ium)
4
7
 
5
- ## Installation
8
+ ## Example
6
9
 
7
- Add to your Gemfile:
10
+ Create a card class:
8
11
 
9
12
  ```ruby
10
- gem "social_construct"
11
- ```
12
-
13
- Run the installation generator:
14
-
15
- ```bash
16
- bundle install
17
- bin/rails generate social_construct:install
18
- ```
19
-
20
- This will:
21
-
22
- - Create a configuration initializer
23
- - Set up ApplicationSocialCard base class
24
- - Create an example social card with template
25
- - Add a shared layout for social cards
26
- - Mount the preview interface in development
27
- - Create example preview classes
28
-
29
- ## Usage
30
-
31
- ### 1. Create your base social card class
32
-
33
- ```ruby
34
- # app/social_cards/application_social_card.rb
35
- class ApplicationSocialCard < SocialConstruct::BaseCard
36
- include SocialConstruct::CardConcerns
37
-
38
- # Set the logo path for your application
39
- self.logo_path = Rails.root.join("app/assets/images/logo.png")
40
- end
41
- ```
42
-
43
- ### 2. Create specific social card classes
44
-
45
- ```ruby
46
- # app/social_cards/item_social_card.rb
47
- class ItemSocialCard < ApplicationSocialCard
48
- def initialize(item)
49
- super()
50
- @item = item
13
+ class PostSocialCard < ApplicationSocialCard
14
+ def initialize(post)
15
+ @post = post
51
16
  end
52
17
 
53
- private
54
-
55
18
  def template_assigns
56
19
  {
57
- item: @item,
58
- cover_image_data_url: image_to_data_url(@item.cover_image, resize_to_limit: [480, 630], saver: {quality: 75}),
59
- logo_data_url: logo_data_url
20
+ title: @post.title,
21
+ author: @post.author_name,
22
+ avatar: attachment_data_url(@post.author.avatar)
60
23
  }
61
24
  end
62
25
  end
63
26
  ```
64
27
 
65
- ### 3. Create templates
66
-
67
- Templates go in `app/views/social_cards/` and should match your class names:
28
+ Create a template:
68
29
 
69
30
  ```erb
70
- <!-- app/views/social_cards/item_social_card.html.erb -->
31
+ <!-- app/views/social_cards/post_social_card.html.erb -->
32
+
71
33
  <div class="card">
72
- <h1><%= item.title %></h1>
73
- <!-- Your card HTML -->
34
+ <img src="<%= avatar %>" class="logo">
35
+ <h1><%= title %></h1>
36
+ <p>by <%= author %></p>
74
37
  </div>
75
38
  ```
76
39
 
77
- ### 4. Optional: Use a shared layout
78
-
79
- Create `app/views/layouts/social_cards.html.erb` for shared HTML structure:
40
+ Add a controller action:
80
41
 
81
- ```erb
82
- <!DOCTYPE html>
83
- <html>
84
- <head>
85
- <meta charset="utf-8">
86
- <style>
87
- /* Shared styles */
88
- </style>
89
- <%= yield :head %>
90
- </head>
91
- <body>
92
- <%= yield %>
93
- </body>
94
- </html>
95
- ```
42
+ ```ruby
43
+ class PostsController < ApplicationController
44
+ include SocialConstruct::Controller
96
45
 
97
- ### 5. Create preview classes for development
46
+ # ...
98
47
 
99
- ```ruby
100
- # app/social_cards/previews/item_social_card_preview.rb
101
- class ItemSocialCardPreview
102
- def default
103
- item = Item.first || Item.new(title: "Example Item")
104
- ItemSocialCard.new(item)
105
- end
48
+ def social_image
49
+ @post = Post.find(params[:id])
106
50
 
107
- def with_long_title
108
- item = Item.new(title: "This is a very long title that will test text wrapping")
109
- ItemSocialCard.new(item)
51
+ send_social_card(
52
+ PostSocialCard.new(@post),
53
+ cache_key: [@post.id, @post.updated_at]
54
+ )
110
55
  end
111
56
  end
112
57
  ```
113
58
 
114
- Visit `/rails/social_cards` in development to see all your previews.
59
+ ## Setup
60
+
61
+ ```sh
62
+ $ bundle add social_construct && bundle install
63
+ $ bin/rails generate social_construct:install
64
+ ```
115
65
 
116
- ### 6. Use in your controllers
66
+ ## Images
117
67
 
118
- Include the controller concern:
68
+ Convert ActiveStorage attachments to Base64 `data://` URLs:
119
69
 
120
70
  ```ruby
121
- class ItemsController < ApplicationController
122
- include SocialConstruct::Controller
123
-
124
- def og
125
- @item = Item.find(params[:id])
126
- render ItemSocialCard.new(@item)
127
- end
71
+ def template_assigns
72
+ {
73
+ cover_image: attachment_data_url(@post.cover_image),
74
+ avatar: attachment_data_url(@post.author.avatar, resize_to_limit: [200, 200])
75
+ }
128
76
  end
129
77
  ```
130
78
 
131
- The `render` method automatically handles both formats:
79
+ ## Fonts
132
80
 
133
- - `.png` - Generates the actual PNG image
134
- - `.html` - Shows the HTML preview (useful for debugging)
81
+ ### Remote fonts
135
82
 
136
- Or with caching:
83
+ Just import them normally and they should work.
137
84
 
138
- ```ruby
139
- def og
140
- @item = Item.find(params[:id])
85
+ ```erb
86
+ <style>
87
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
88
+ body { font-family: 'Inter', sans-serif; }
89
+ </style>
90
+ ```
141
91
 
142
- cache_key = [
143
- "social-cards",
144
- "item",
145
- @item.id,
146
- @item.updated_at.to_i
147
- ]
92
+ ### Local fonts
148
93
 
149
- render ItemSocialCard.new(@item), cache_key: cache_key
94
+ Store fonts in `app/assets/fonts/` and embed as `data://` URLs:
95
+
96
+ ```ruby
97
+ class LocalFontsCard < ApplicationSocialCard
98
+ def template_assigns
99
+ {
100
+ custom_font_css: generate_font_face(
101
+ "custom-font-name",
102
+ "Recursive_VF_1.085--subset-GF_latin_basic.woff2",
103
+ weight: "300 1000"
104
+ )
105
+ }
106
+ end
150
107
  end
151
108
  ```
152
109
 
153
- Alternative API:
110
+ And include in template:
154
111
 
155
- ```ruby
156
- def og
157
- @item = Item.find(params[:id])
158
- card = ItemSocialCard.new(@item)
159
-
160
- send_social_card(card,
161
- cache_key: ["social-cards", @item.id, @item.updated_at.to_i],
162
- expires_in: 7.days
163
- )
164
- end
112
+ ```erb
113
+ <style>
114
+ <%= custom_font_css %>
115
+
116
+ body {
117
+ font-family: 'custom-font-name', sans-serif;
118
+ }
119
+ </style>
165
120
  ```
166
121
 
167
- ## Configuration
122
+ ## Previews
168
123
 
169
- Configure in your initializer:
124
+ Mount the preview engine:
170
125
 
171
126
  ```ruby
172
- # config/initializers/social_construct.rb
127
+ Rails.application.routes.draw do
128
+ if Rails.env.development?
129
+ mount(SocialConstruct::Engine => "/rails/social_cards")
130
+ end
131
+ end
132
+ ```
173
133
 
174
- # Template path (default: "social_cards")
175
- Rails.application.config.social_construct.template_path = "custom_path"
134
+ Create preview classes:
176
135
 
177
- # Enable debug logging (default: false)
178
- SocialConstruct::BaseCard.debug = true
179
- ```
136
+ ```ruby
137
+ # test/social_cards/previews/post_social_card_preview.rb
138
+ class PostSocialCardPreview
139
+ def default
140
+ PostSocialCard.new(Post.first)
141
+ end
180
142
 
181
- ## Requirements
143
+ def long_title
144
+ post = Post.new(title: "A very long title that demonstrates text wrapping behavior")
145
+ PostSocialCard.new(post)
146
+ end
147
+ end
148
+ ```
182
149
 
183
- - Rails 7.0+
184
- - Ferrum (headless Chrome driver)
185
- - Marcel (MIME type detection)
150
+ Visit `http://localhost:3000/rails/social_cards` to preview all cards.
186
151
 
187
152
  ## License
188
153
 
189
154
  MIT
190
-
@@ -3,7 +3,6 @@ module SocialConstruct
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- # Register social card mime type if not already registered
7
6
  Mime::Type.register "image/png", :png unless Mime[:png]
8
7
  end
9
8
 
@@ -35,16 +34,14 @@ module SocialConstruct
35
34
 
36
35
  # Allow using render with social cards
37
36
  def render(*args)
38
- if args.first.is_a?(SocialConstruct::BaseCard)
39
- card = args.first
40
- options = args.second || {}
37
+ return super unless args.first.is_a?(SocialConstruct::BaseCard)
41
38
 
42
- respond_to do |format|
43
- format.png { send_social_card(card, **options) }
44
- format.html { render(html: card.render.html_safe, layout: false) }
45
- end
46
- else
47
- super
39
+ card = args.first
40
+ options = args.second || {}
41
+
42
+ respond_to do |format|
43
+ format.png { send_social_card(card, **options) }
44
+ format.html { render(html: card.render.html_safe, layout: false) }
48
45
  end
49
46
  end
50
47
 
@@ -1,5 +1,9 @@
1
1
  module SocialConstruct
2
2
  class PreviewsController < ActionController::Base
3
+ include SocialConstruct::Controller
4
+
5
+ before_action :ensure_previews_enabled
6
+
3
7
  def index
4
8
  @preview_classes = find_preview_classes
5
9
  end
@@ -27,26 +31,29 @@ module SocialConstruct
27
31
 
28
32
  @card = preview_class.new.send(example_name)
29
33
 
30
- respond_to do |format|
31
- format.html { render(html: @card.render.html_safe, layout: false) }
32
- format.png { send_data(@card.to_png, type: "image/png", disposition: "inline") }
33
- end
34
+ render(@card)
34
35
  end
35
36
 
36
37
  private
37
38
 
38
39
  def find_preview_classes
39
- # Look for preview classes in the host app
40
- preview_path = Rails.root.join("app/social_cards/previews")
41
- return [] unless preview_path.exist?
42
-
43
- Dir[preview_path.join("*_preview.rb")]
44
- .map do |file|
45
- require_dependency(file)
46
- class_name = File.basename(file, ".rb").camelize
47
- class_name.constantize if Object.const_defined?(class_name)
40
+ return [] unless (paths = Rails.application.config.social_construct.preview_paths)
41
+
42
+ paths
43
+ .map do |path|
44
+ next [] unless path.exist?
45
+
46
+ glob = path.join("*_preview.rb")
47
+
48
+ Dir[glob]
49
+ .map do |file|
50
+ require_dependency(file)
51
+ class_name = File.basename(file, ".rb").camelize
52
+ class_name.constantize if Object.const_defined?(class_name)
53
+ end
54
+ .compact
48
55
  end
49
- .compact
56
+ .flatten
50
57
  end
51
58
 
52
59
  def find_preview_class(name)
@@ -57,5 +64,11 @@ module SocialConstruct
57
64
  rescue NameError
58
65
  nil
59
66
  end
67
+
68
+ def ensure_previews_enabled
69
+ unless Rails.application.config.social_construct.show_previews
70
+ raise ActionController::RoutingError, "Social card previews are disabled"
71
+ end
72
+ end
60
73
  end
61
74
  end
@@ -6,14 +6,14 @@ module SocialConstruct
6
6
  include ActionView::Helpers
7
7
  include Rails.application.routes.url_helpers
8
8
 
9
- attr_reader :width, :height
10
-
11
- # Class-level debug setting
12
9
  cattr_accessor :debug, default: false
13
10
 
14
- def initialize
15
- @width = 1200
16
- @height = 630
11
+ def width
12
+ @width || 1200
13
+ end
14
+
15
+ def height
16
+ @height || 630
17
17
  end
18
18
 
19
19
  def render
@@ -33,7 +33,7 @@ module SocialConstruct
33
33
  browser_options = {
34
34
  headless: true,
35
35
  timeout: 30,
36
- window_size: [@width, @height]
36
+ window_size: [width, height]
37
37
  }
38
38
 
39
39
  # Add Docker-specific options in production
@@ -81,13 +81,38 @@ module SocialConstruct
81
81
  browser.goto("data:text/html;charset=utf-8,#{encoded_html}")
82
82
  end
83
83
 
84
- browser.set_viewport(width: @width, height: @height)
84
+ browser.set_viewport(width: width, height: height)
85
85
 
86
86
  # Wait for the page to fully load
87
87
  browser.network.wait_for_idle
88
88
 
89
- # Add extra wait for complex pages with large images
90
- sleep(0.5)
89
+ # Wait for all images and fonts to load
90
+ browser.execute(
91
+ <<~JS
92
+ return new Promise((resolve) => {
93
+ // Check if all images are loaded
94
+ const images = Array.from(document.querySelectorAll('img'));
95
+ const imagePromises = images.map(img => {
96
+ if (img.complete) return Promise.resolve();
97
+ return new Promise(res => {
98
+ img.addEventListener('load', res);
99
+ img.addEventListener('error', res);
100
+ });
101
+ });
102
+
103
+ // Check document fonts
104
+ const fontPromise = document.fonts?.ready || Promise.resolve();
105
+
106
+ // Wait for everything
107
+ Promise.all([...imagePromises, fontPromise]).then(() => {
108
+ // Small delay to ensure rendering is complete
109
+ requestAnimationFrame(() => {
110
+ setTimeout(resolve, 50);
111
+ });
112
+ });
113
+ });
114
+ JS
115
+ )
91
116
 
92
117
  # Log page readiness
93
118
  if debug
@@ -108,6 +133,21 @@ module SocialConstruct
108
133
  log_debug("Body HTML length: #{body_html_length}")
109
134
  end
110
135
 
136
+ # Ensure content is painted before screenshot
137
+ browser.execute(
138
+ <<~JS
139
+ // Force layout and paint
140
+ document.body.offsetHeight;
141
+ // Check if we have visible content
142
+ const hasContent = document.body.textContent.trim().length > 0 ||
143
+ document.querySelectorAll('img').length > 0;
144
+ if (!hasContent) {
145
+ console.warn('Page appears to have no visible content');
146
+ }
147
+ return hasContent;
148
+ JS
149
+ )
150
+
111
151
  screenshot = browser.screenshot(
112
152
  encoding: :binary,
113
153
  quality: 100,
@@ -116,27 +156,6 @@ module SocialConstruct
116
156
 
117
157
  log_debug("Screenshot generated, size: #{screenshot.bytesize} bytes")
118
158
 
119
- # Check if screenshot might be blank (very small file size indicates mostly white/single color)
120
- # Less than 10KB usually means it's mostly one color
121
- if screenshot.bytesize < 10_000
122
- log_debug("Screenshot seems too small, might be blank. Retrying with delay...", :warn)
123
-
124
- # Wait a bit more and try again
125
- sleep(1)
126
-
127
- # Force a paint
128
- browser.execute(
129
- "document.body.style.display = 'none'; document.body.offsetHeight; document.body.style.display = 'flex';"
130
- )
131
-
132
- screenshot = browser.screenshot(
133
- encoding: :binary,
134
- quality: 100,
135
- full: false
136
- )
137
- log_debug("Retry screenshot size: #{screenshot.bytesize} bytes")
138
- end
139
-
140
159
  screenshot
141
160
  rescue => e
142
161
  log_debug("Ferrum screenshot failed: #{e.message}", :error)
@@ -177,7 +196,7 @@ module SocialConstruct
177
196
  {}
178
197
  end
179
198
 
180
- def image_to_data_url(attachment, variant_options = {})
199
+ def attachment_data_url(attachment, variant_options = {})
181
200
  return nil unless attachment.attached?
182
201
 
183
202
  begin
@@ -191,14 +210,93 @@ module SocialConstruct
191
210
 
192
211
  content_type = blob.content_type || "image/jpeg"
193
212
  image_data = blob.download
213
+ file_size = image_data.bytesize
214
+
215
+ # Check image size limitations
216
+ # 2MB hard limit
217
+ if file_size > 2_000_000
218
+ log_debug("Image is #{file_size} bytes (#{file_size / 1024 / 1024}MB), exceeds 2MB data URL limit", :error)
219
+ return nil
220
+ # 500KB warning threshold
221
+ elsif file_size > 500_000
222
+ log_debug(
223
+ "Image is #{file_size} bytes (#{file_size / 1024}KB), consider optimizing for better performance",
224
+ :warn
225
+ )
226
+ end
194
227
 
195
- "data:#{content_type};base64,#{Base64.strict_encode64(image_data)}"
228
+ encoded_data = Base64.strict_encode64(image_data)
229
+ log_debug("Image loaded: #{file_size} bytes (#{encoded_data.bytesize} bytes encoded)")
230
+
231
+ "data:#{content_type};base64,#{encoded_data}"
196
232
  rescue => e
197
233
  log_debug("Failed to convert image to data URL: #{e.message}", :error)
198
234
  nil
199
235
  end
200
236
  end
201
237
 
238
+ # Local font helper - converts font file to data URL
239
+ def font_to_data_url(font_path)
240
+ full_path = if font_path.start_with?("/")
241
+ font_path
242
+ else
243
+ Rails.root.join("app", "assets", "fonts", font_path)
244
+ end
245
+
246
+ return nil unless File.exist?(full_path)
247
+
248
+ begin
249
+ font_data = File.read(full_path)
250
+ file_size = font_data.bytesize
251
+
252
+ # Check file size limitations
253
+ # Most browsers have data URL limits around 2MB, but performance degrades after ~500KB
254
+ # 2MB hard limit
255
+ if file_size > 2_000_000
256
+ log_debug(
257
+ "Font file #{font_path} is #{file_size} bytes (#{file_size / 1024 / 1024}MB), exceeds 2MB data URL limit",
258
+ :error
259
+ )
260
+ return nil
261
+ # 500KB warning threshold
262
+ elsif file_size > 500_000
263
+ log_debug(
264
+ "Font file #{font_path} is #{file_size} bytes (#{file_size / 1024}KB), consider optimizing for better performance",
265
+ :warn
266
+ )
267
+ end
268
+
269
+ content_type = font_content_type(full_path)
270
+ encoded_data = Base64.strict_encode64(font_data)
271
+
272
+ # Base64 encoding increases size by ~33%
273
+ encoded_size = encoded_data.bytesize
274
+ log_debug("Font #{font_path} loaded: #{file_size} bytes (#{encoded_size} bytes encoded)")
275
+
276
+ "data:#{content_type};base64,#{encoded_data}"
277
+ rescue => e
278
+ log_debug("Failed to convert font to data URL: #{e.message}", :error)
279
+ nil
280
+ end
281
+ end
282
+
283
+ # Generate @font-face declaration for local fonts
284
+ def generate_font_face(family_name, font_path, weight: "normal", style: "normal", display: "swap")
285
+ data_url = font_to_data_url(font_path)
286
+ return "" unless data_url
287
+
288
+ <<~CSS
289
+ @font-face {
290
+ font-family: '#{family_name}';
291
+ src: url('#{data_url}');
292
+ font-weight: #{weight};
293
+ font-style: #{style};
294
+ font-display: #{display};
295
+ }
296
+ CSS
297
+ .html_safe
298
+ end
299
+
202
300
  def template_path
203
301
  Rails.application.config.social_construct.template_path
204
302
  end
@@ -207,5 +305,84 @@ module SocialConstruct
207
305
  return unless debug
208
306
  Rails.logger.send(level, "[SocialCard] #{message}")
209
307
  end
308
+
309
+ # Local image helper - converts image file to data URL
310
+ def image_data_url(image_path)
311
+ full_path = if image_path.start_with?("/")
312
+ image_path
313
+ else
314
+ Rails.root.join("app", "assets", "images", image_path)
315
+ end
316
+
317
+ return nil unless File.exist?(full_path)
318
+
319
+ begin
320
+ image_data = File.read(full_path)
321
+ file_size = image_data.bytesize
322
+
323
+ # Check file size limitations
324
+ if file_size > 2_000_000
325
+ log_debug(
326
+ "Image file #{image_path} is #{file_size} bytes (#{file_size / 1024 / 1024}MB), exceeds 2MB data URL limit",
327
+ :error
328
+ )
329
+ return nil
330
+ elsif file_size > 500_000
331
+ log_debug(
332
+ "Image file #{image_path} is #{file_size} bytes (#{file_size / 1024}KB), consider optimizing for better performance",
333
+ :warn
334
+ )
335
+ end
336
+
337
+ content_type = image_content_type(full_path)
338
+ encoded_data = Base64.strict_encode64(image_data)
339
+ log_debug("Image #{image_path} loaded: #{file_size} bytes (#{encoded_data.bytesize} bytes encoded)")
340
+
341
+ "data:#{content_type};base64,#{encoded_data}"
342
+ rescue => e
343
+ log_debug("Failed to convert image to data URL: #{e.message}", :error)
344
+ nil
345
+ end
346
+ end
347
+
348
+ # Determine MIME type for image files
349
+ def image_content_type(image_path)
350
+ extension = File.extname(image_path).downcase
351
+ case extension
352
+ when ".png"
353
+ "image/png"
354
+ when ".jpg", ".jpeg"
355
+ "image/jpeg"
356
+ when ".gif"
357
+ "image/gif"
358
+ when ".svg"
359
+ "image/svg+xml"
360
+ when ".webp"
361
+ "image/webp"
362
+ else
363
+ # fallback
364
+ "image/png"
365
+ end
366
+ end
367
+
368
+ # Determine MIME type for font files
369
+ def font_content_type(font_path)
370
+ extension = File.extname(font_path).downcase
371
+ case extension
372
+ when ".woff2"
373
+ "font/woff2"
374
+ when ".woff"
375
+ "font/woff"
376
+ when ".ttf"
377
+ "font/truetype"
378
+ when ".otf"
379
+ "font/opentype"
380
+ when ".eot"
381
+ "application/vnd.ms-fontobject"
382
+ else
383
+ # fallback
384
+ "font/truetype"
385
+ end
386
+ end
210
387
  end
211
388
  end
data/config/routes.rb CHANGED
@@ -4,4 +4,6 @@ SocialConstruct::Engine.routes.draw do
4
4
  get(":example_name", action: :preview, as: :example)
5
5
  end
6
6
  end
7
+
8
+ root(to: redirect("previews"))
7
9
  end
@@ -28,7 +28,7 @@ module SocialConstruct
28
28
  end
29
29
 
30
30
  def create_example_preview
31
- template("example_social_card_preview.rb", "app/social_cards/previews/example_social_card_preview.rb")
31
+ template("example_social_card_preview.rb", "test/social_cards/previews/example_social_card_preview.rb")
32
32
  end
33
33
 
34
34
  def add_route
@@ -42,10 +42,6 @@ module SocialConstruct
42
42
 
43
43
  route(route_string)
44
44
  end
45
-
46
- def display_post_install
47
- readme("POST_INSTALL") if behavior == :invoke
48
- end
49
45
  end
50
46
  end
51
47
  end
@@ -1,7 +1,7 @@
1
- <% content_for :head do %>
1
+ <%% content_for :head do %>
2
2
  <style>
3
3
  body {
4
- background-color: <%= background_color %>;
4
+ background-color: <%%= background_color %>;
5
5
  display: flex;
6
6
  align-items: center;
7
7
  justify-content: center;
@@ -9,11 +9,11 @@
9
9
  text-align: center;
10
10
  padding: 60px;
11
11
  }
12
-
12
+
13
13
  .content {
14
14
  max-width: 900px;
15
15
  }
16
-
16
+
17
17
  .title {
18
18
  font-size: 72px;
19
19
  font-weight: 800;
@@ -21,26 +21,26 @@
21
21
  margin-bottom: 24px;
22
22
  letter-spacing: -2px;
23
23
  }
24
-
24
+
25
25
  .subtitle {
26
26
  font-size: 32px;
27
27
  font-weight: 400;
28
28
  opacity: 0.8;
29
29
  line-height: 1.3;
30
30
  }
31
-
31
+
32
32
  .logo {
33
33
  position: absolute;
34
34
  bottom: 60px;
35
35
  right: 60px;
36
36
  }
37
-
37
+
38
38
  .logo img {
39
39
  height: 40px;
40
40
  width: auto;
41
41
  opacity: 0.9;
42
42
  }
43
-
43
+
44
44
  .decoration {
45
45
  position: absolute;
46
46
  top: 0;
@@ -51,19 +51,13 @@
51
51
  pointer-events: none;
52
52
  }
53
53
  </style>
54
- <% end %>
54
+ <%% end %>
55
55
 
56
56
  <div class="decoration"></div>
57
57
 
58
58
  <div class="content">
59
- <h1 class="title"><%= title %></h1>
60
- <% if subtitle.present? %>
61
- <p class="subtitle"><%= subtitle %></p>
62
- <% end %>
59
+ <h1 class="title"><%%= title %></h1>
60
+ <%% if subtitle.present? %>
61
+ <p class="subtitle"><%%= subtitle %></p>
62
+ <%% end %>
63
63
  </div>
64
-
65
- <% if logo_data_url %>
66
- <div class="logo">
67
- <img src="<%= logo_data_url %>" alt="Logo">
68
- </div>
69
- <% end %>
@@ -1,6 +1,5 @@
1
1
  class ExampleSocialCard < ApplicationSocialCard
2
2
  def initialize(title: "Hello World", subtitle: nil, background_color: "#1a1a1a")
3
- super()
4
3
  @title = title
5
4
  @subtitle = subtitle
6
5
  @background_color = background_color
@@ -12,8 +11,7 @@ class ExampleSocialCard < ApplicationSocialCard
12
11
  {
13
12
  title: @title,
14
13
  subtitle: @subtitle,
15
- background_color: @background_color,
16
- logo_data_url: logo_data_url
14
+ background_color: @background_color
17
15
  }
18
16
  end
19
17
  end
@@ -5,33 +5,4 @@ class ExampleSocialCardPreview
5
5
  subtitle: "Beautiful social cards for your Rails app"
6
6
  )
7
7
  end
8
-
9
- def dark_theme
10
- ExampleSocialCard.new(
11
- title: "Dark Theme Example",
12
- subtitle: "Perfect for modern applications",
13
- background_color: "#0a0a0a"
14
- )
15
- end
16
-
17
- def colorful
18
- ExampleSocialCard.new(
19
- title: "Colorful Background",
20
- subtitle: "Make your cards stand out",
21
- background_color: "#6366f1"
22
- )
23
- end
24
-
25
- def long_title
26
- ExampleSocialCard.new(
27
- title: "This is a very long title that demonstrates how text wrapping works in social cards",
28
- subtitle: "Subtitle remains readable"
29
- )
30
- end
31
-
32
- def no_subtitle
33
- ExampleSocialCard.new(
34
- title: "Simple and Clean"
35
- )
36
- end
37
8
  end
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="<%= I18n.locale %>">
2
+ <html lang="<%%= I18n.locale %>">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
@@ -10,13 +10,13 @@
10
10
  padding: 0;
11
11
  box-sizing: border-box;
12
12
  }
13
-
13
+
14
14
  html {
15
15
  width: 1200px;
16
16
  height: 630px;
17
17
  background: white;
18
18
  }
19
-
19
+
20
20
  body {
21
21
  width: 1200px;
22
22
  height: 630px;
@@ -28,22 +28,23 @@
28
28
  -webkit-font-smoothing: antialiased;
29
29
  -moz-osx-font-smoothing: grayscale;
30
30
  }
31
-
31
+
32
32
  /* Ensure images don't have borders */
33
33
  img {
34
34
  border: 0;
35
35
  max-width: 100%;
36
36
  }
37
-
37
+
38
38
  /* Default text rendering */
39
39
  h1, h2, h3, h4, h5, h6, p {
40
40
  font-weight: normal;
41
41
  margin: 0;
42
42
  }
43
43
  </style>
44
- <%= yield :head %>
44
+ <%%= yield :head %>
45
45
  </head>
46
46
  <body>
47
- <%= yield %>
47
+ <%%= yield %>
48
48
  </body>
49
- </html>
49
+ </html>
50
+
@@ -1,9 +1,16 @@
1
- # SocialConstruct configuration
2
1
  Rails.application.configure do
3
2
  # Configure the template path for social card views
4
3
  # Default: "social_cards"
5
4
  # config.social_construct.template_path = "social_cards"
6
5
 
6
+ # Configure paths for social card previews
7
+ # Default: ["test/social_cards/previews"]
8
+ # config.social_construct.preview_paths = ["test/social_cards/previews"]
9
+
10
+ # Enable social card previews
11
+ # Default: true in development, false otherwise
12
+ # config.social_construct.show_previews = Rails.env.development?
13
+
7
14
  # Enable debug logging for social card generation
8
15
  # Default: false
9
16
  # SocialConstruct::BaseCard.debug = true
@@ -4,11 +4,14 @@ module SocialConstruct
4
4
 
5
5
  config.social_construct = ActiveSupport::OrderedOptions.new
6
6
  config.social_construct.template_path = "social_cards"
7
+ config.social_construct.preview_paths = []
8
+ config.social_construct.show_previews = Rails.env.development?
7
9
 
8
- # Ensure dependencies are loaded
9
- config.before_initialize do
10
- require "ferrum"
11
- require "marcel"
10
+ # Set default preview path after application initializes
11
+ config.after_initialize do |app|
12
+ if app.config.social_construct.preview_paths.empty?
13
+ app.config.social_construct.preview_paths = [Rails.root.join("test/social_cards/previews")]
14
+ end
12
15
  end
13
16
  end
14
17
  end
@@ -1,3 +1,3 @@
1
1
  module SocialConstruct
2
- VERSION = "0.1.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -6,7 +6,4 @@ require "ferrum"
6
6
  require "marcel"
7
7
 
8
8
  module SocialConstruct
9
- # Configuration for template paths and other settings
10
- mattr_accessor :template_path
11
- @@template_path = "social_cards"
12
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: social_construct
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Malmberg
@@ -68,7 +68,6 @@ files:
68
68
  - app/views/social_construct/previews/show.html.erb
69
69
  - config/routes.rb
70
70
  - lib/generators/social_construct/install/install_generator.rb
71
- - lib/generators/social_construct/install/templates/POST_INSTALL
72
71
  - lib/generators/social_construct/install/templates/application_social_card.rb
73
72
  - lib/generators/social_construct/install/templates/example_social_card.html.erb
74
73
  - lib/generators/social_construct/install/templates/example_social_card.rb
@@ -78,12 +77,12 @@ files:
78
77
  - lib/social_construct.rb
79
78
  - lib/social_construct/engine.rb
80
79
  - lib/social_construct/version.rb
81
- homepage: https://github.com/brnbw/social_construct
80
+ homepage: https://github.com/mikker/social_construct
82
81
  licenses: []
83
82
  metadata:
84
- homepage_uri: https://github.com/brnbw/social_construct
85
- source_code_uri: https://github.com/brnbw/social_construct
86
- changelog_uri: https://github.com/brnbw/social_construct/blob/main/CHANGELOG.md
83
+ homepage_uri: https://github.com/mikker/social_construct
84
+ source_code_uri: https://github.com/mikker/social_construct
85
+ changelog_uri: https://github.com/mikker/social_construct/blob/main/CHANGELOG.md
87
86
  rdoc_options: []
88
87
  require_paths:
89
88
  - lib
@@ -1,34 +0,0 @@
1
- ===============================================================================
2
-
3
- SocialConstruct has been successfully installed! 🎉
4
-
5
- Next steps:
6
-
7
- 1. Update the logo path in app/social_cards/application_social_card.rb
8
- to point to your actual logo file.
9
-
10
- 2. Run your Rails server and visit:
11
- http://localhost:3000/rails/social_cards
12
-
13
- You should see the example social card previews.
14
-
15
- 3. Create your own social card classes:
16
- - Inherit from ApplicationSocialCard
17
- - Create matching templates in app/views/social_cards/
18
- - Add preview classes in app/social_cards/previews/
19
-
20
- 4. Use in your controllers:
21
-
22
- class YourController < ApplicationController
23
- include SocialConstruct::Controller
24
-
25
- def og
26
- @model = Model.find(params[:id])
27
- render YourSocialCard.new(@model)
28
- end
29
- end
30
-
31
- For more information, see:
32
- https://github.com/brnbw/social_construct
33
-
34
- ===============================================================================