social_construct 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a6da2405e4a0117c652f0273ba675242053926527418d9fdcb3acf01df55342
|
4
|
+
data.tar.gz: bb626f6d95fac62a0b0696b8c6ceb083f3109d03b74ad670f5496640f44eacb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29c1411422336bffdfb0fda48294395b8043dddab4ba44dd20b7bc8f3a367311a33dc1a484e79fb30403a5cfdf7c5b17d90512ac594151b7324d189a5be1a013
|
7
|
+
data.tar.gz: ca8995422185f360f50feeb4f0d0860f592e7d821b389305e8199936f388ff7b0d53672fcccc50772f34ba686b49eaa9efade2e864ab3057c2af6836eb8a24e3
|
data/README.md
CHANGED
@@ -1,188 +1,153 @@
|
|
1
|
-
|
1
|
+
<img src="https://s3.brnbw.com/Stack-C9apVxz8Pg.webp" width="600">
|
2
2
|
|
3
|
-
|
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
|
-
##
|
8
|
+
## Example
|
6
9
|
|
7
|
-
|
10
|
+
Create a card class:
|
8
11
|
|
9
12
|
```ruby
|
10
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
20
|
+
title: @post.title,
|
21
|
+
author: @post.author_name,
|
22
|
+
avatar: image_to_data_url(@post.author.avatar)
|
60
23
|
}
|
61
24
|
end
|
62
25
|
end
|
63
26
|
```
|
64
27
|
|
65
|
-
|
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/
|
31
|
+
<!-- app/views/social_cards/post_social_card.html.erb -->
|
32
|
+
|
71
33
|
<div class="card">
|
72
|
-
<
|
73
|
-
|
34
|
+
<img src="<%= avatar %>" class="logo">
|
35
|
+
<h1><%= title %></h1>
|
36
|
+
<p>by <%= author %></p>
|
74
37
|
</div>
|
75
38
|
```
|
76
39
|
|
77
|
-
|
78
|
-
|
79
|
-
Create `app/views/layouts/social_cards.html.erb` for shared HTML structure:
|
40
|
+
Add a controller action:
|
80
41
|
|
81
|
-
```
|
82
|
-
|
83
|
-
|
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
|
-
|
46
|
+
# ...
|
98
47
|
|
99
|
-
|
100
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
59
|
+
## Setup
|
115
60
|
|
116
|
-
|
61
|
+
```sh
|
62
|
+
$ bundle add social_construct && bundle install
|
63
|
+
$ bin/rails generate social_construct:install
|
64
|
+
```
|
117
65
|
|
118
|
-
|
66
|
+
## Images
|
119
67
|
|
120
|
-
|
121
|
-
class ItemsController < ApplicationController
|
122
|
-
include SocialConstruct::Controller
|
68
|
+
Convert ActiveStorage attachments to Base64 `data://` URLs:
|
123
69
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
70
|
+
```ruby
|
71
|
+
def template_assigns
|
72
|
+
{
|
73
|
+
cover_image: image_to_data_url(@post.cover_image),
|
74
|
+
avatar: image_to_data_url(@post.author.avatar, resize_to_limit: [200, 200])
|
75
|
+
}
|
128
76
|
end
|
129
77
|
```
|
130
78
|
|
131
|
-
|
79
|
+
## Fonts
|
132
80
|
|
133
|
-
|
134
|
-
- `.html` - Shows the HTML preview (useful for debugging)
|
81
|
+
### Remote fonts
|
135
82
|
|
136
|
-
|
83
|
+
Just import them normally and they should work.
|
137
84
|
|
138
|
-
```
|
139
|
-
|
140
|
-
@
|
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
|
-
|
143
|
-
"social-cards",
|
144
|
-
"item",
|
145
|
-
@item.id,
|
146
|
-
@item.updated_at.to_i
|
147
|
-
]
|
92
|
+
### Local fonts
|
148
93
|
|
149
|
-
|
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
|
-
|
110
|
+
And include in template:
|
154
111
|
|
155
|
-
```
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
-
##
|
122
|
+
## Previews
|
168
123
|
|
169
|
-
|
124
|
+
Mount the preview engine:
|
170
125
|
|
171
126
|
```ruby
|
172
|
-
|
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
|
-
|
175
|
-
Rails.application.config.social_construct.template_path = "custom_path"
|
134
|
+
Create preview classes:
|
176
135
|
|
177
|
-
|
178
|
-
|
179
|
-
|
136
|
+
```ruby
|
137
|
+
# app/social_cards/previews/post_social_card_preview.rb
|
138
|
+
class PostSocialCardPreview
|
139
|
+
def default
|
140
|
+
PostSocialCard.new(Post.first)
|
141
|
+
end
|
180
142
|
|
181
|
-
|
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
|
-
|
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
|
|
@@ -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
|
15
|
-
@width
|
16
|
-
|
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: [
|
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:
|
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
|
-
#
|
90
|
-
|
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)
|
@@ -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
|
-
|
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
|
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.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mikkel Malmberg
|
@@ -78,12 +78,12 @@ files:
|
|
78
78
|
- lib/social_construct.rb
|
79
79
|
- lib/social_construct/engine.rb
|
80
80
|
- lib/social_construct/version.rb
|
81
|
-
homepage: https://github.com/
|
81
|
+
homepage: https://github.com/mikker/social_construct
|
82
82
|
licenses: []
|
83
83
|
metadata:
|
84
|
-
homepage_uri: https://github.com/
|
85
|
-
source_code_uri: https://github.com/
|
86
|
-
changelog_uri: https://github.com/
|
84
|
+
homepage_uri: https://github.com/mikker/social_construct
|
85
|
+
source_code_uri: https://github.com/mikker/social_construct
|
86
|
+
changelog_uri: https://github.com/mikker/social_construct/blob/main/CHANGELOG.md
|
87
87
|
rdoc_options: []
|
88
88
|
require_paths:
|
89
89
|
- lib
|