webring-rails 1.3.1 → 1.4.2
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 +4 -4
- data/app/assets/javascripts/webring/widget.js +146 -58
- data/app/assets/javascripts/webring/widget.min.js +1 -0
- data/app/controllers/webring/widget_controller.rb +24 -5
- data/app/models/webring/membership_request.rb +1 -0
- data/lib/generators/USAGE +1 -1
- data/lib/generators/webring/custom_widget_controller/templates/custom_widget_controller.rb +13 -2
- data/lib/generators/webring/membership_request/templates/model.rb +1 -0
- data/lib/webring/version.rb +1 -1
- metadata +17 -4
- data/app/helpers/webring/application_helper.rb +0 -4
- data/lib/generators/webring_generator.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 754be274910dadf9dc06187305d7482287140f2d2e8c18f4eb949c9343ca66ee
|
4
|
+
data.tar.gz: '0449f04934aa33b9cdcc306890d27d1bd34a149c577996221a41904b02774215'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6cefdc9a28d9549e5778a436a962f49ebe8b367a43ab9fc7774f307cdd3f497735bb4bcea4fdd9b40f2fbcb8409737294b8103f667f811a83630fb2ae4150b3
|
7
|
+
data.tar.gz: 945aa0fbc5e07f6aef49bf03079967640ccd7d2c697d4acfd5e9921f594ce89208f6d05f8ef92ac426dc4ca726131e26f958c5add6fb1878044114ce59b4edfc
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# Webring for Rails
|
4
4
|
|
5
|
-
Webring for Rails (
|
5
|
+
[Webring for Rails (webring-rails)](https://github.com/lstpsche/webring-rails) is a flexible engine for creating and managing a webring system in your Ruby on Rails application. A webring is a collection of websites linked together in a circular structure, allowing visitors to navigate from one site to another.
|
6
6
|
|
7
7
|
## Features
|
8
8
|
|
@@ -20,7 +20,7 @@ Webring for Rails (webring_rails) is a flexible engine for creating and managing
|
|
20
20
|
Add this line to your application's Gemfile:
|
21
21
|
|
22
22
|
```ruby
|
23
|
-
gem '
|
23
|
+
gem 'webring-rails'
|
24
24
|
```
|
25
25
|
|
26
26
|
Run:
|
@@ -36,7 +36,7 @@ rails generate webring:install
|
|
36
36
|
rails generate webring:member
|
37
37
|
|
38
38
|
# Create the navigation controller
|
39
|
-
rails generate webring:
|
39
|
+
rails generate webring:navigation_controller
|
40
40
|
```
|
41
41
|
|
42
42
|
### Optional Features
|
@@ -46,7 +46,7 @@ rails generate webring:controller:navigation
|
|
46
46
|
rails generate webring:membership_request
|
47
47
|
|
48
48
|
# Add membership request controller and routes
|
49
|
-
rails generate webring:
|
49
|
+
rails generate webring:membership_requests_controller
|
50
50
|
```
|
51
51
|
|
52
52
|
Finally, run the migrations:
|
@@ -22,39 +22,75 @@
|
|
22
22
|
* - full: Apply all styles (default)
|
23
23
|
* - layout: Only layout styles, no visual design
|
24
24
|
* - none: No styles applied
|
25
|
+
* - data-prev-text="Custom Text": Sets custom text for the "previous" button. Default: "« Prev"
|
26
|
+
* - data-random-text="Custom Text": Sets custom text for the "random" button (keeps the logo). Default: "Random"
|
27
|
+
* - data-next-text="Custom Text": Sets custom text for the "next" button. Default: "Next »"
|
28
|
+
* - data-widget-text="Custom Text": Sets custom text for the widget title. Default: "Webring"
|
25
29
|
*/
|
26
30
|
|
27
31
|
(function() {
|
28
32
|
// Configuration constants
|
29
|
-
const WIDGET_CONFIG = {
|
30
|
-
VALID_TYPES: ['full', 'no-text', 'two-way', 'one-way', 'random-only'],
|
33
|
+
const WIDGET_CONFIG = Object.freeze({
|
34
|
+
VALID_TYPES: Object.freeze(['full', 'no-text', 'two-way', 'one-way', 'random-only']),
|
31
35
|
DEFAULT_TYPE: 'full',
|
32
36
|
DEFAULT_TARGET_ID: 'webring-widget',
|
33
37
|
STYLE_ID: 'webring-widget-styles',
|
34
|
-
VALID_STYLE_TYPES: ['full', 'layout', 'none'],
|
38
|
+
VALID_STYLE_TYPES: Object.freeze(['full', 'layout', 'none']),
|
35
39
|
DEFAULT_STYLE_TYPE: 'full'
|
36
|
-
};
|
40
|
+
});
|
37
41
|
|
38
|
-
|
42
|
+
// Default text configurations
|
43
|
+
const TEXT_DEFAULTS = Object.freeze({
|
44
|
+
prev: { default: '« Prev', enforced: false },
|
45
|
+
random: { default: 'Random', enforced: false },
|
46
|
+
next: { default: 'Next »', enforced: false },
|
47
|
+
widgetTitle: { default: 'Webring', enforced: false }
|
48
|
+
});
|
39
49
|
|
40
|
-
|
50
|
+
// These defaults will be provided by the widget_controller
|
51
|
+
const PROVIDED_TEXT_DEFAULTS = "<<REPLACE_ME_TEXT_DEFAULTS>>";
|
52
|
+
|
53
|
+
// Parse the provided defaults JSON string
|
54
|
+
const parsedProvidedDefaults =
|
55
|
+
PROVIDED_TEXT_DEFAULTS !== "<<REPLACE_ME_TEXT_DEFAULTS>>"
|
56
|
+
? (typeof PROVIDED_TEXT_DEFAULTS === 'string' ? JSON.parse(PROVIDED_TEXT_DEFAULTS) : PROVIDED_TEXT_DEFAULTS)
|
57
|
+
: {};
|
58
|
+
|
59
|
+
// Merge defaults with provided defaults
|
60
|
+
const FULL_TEXT_DEFAULTS = Object.freeze(
|
61
|
+
Object.keys(TEXT_DEFAULTS).reduce((acc, key) => {
|
62
|
+
const providedValue = parsedProvidedDefaults[key];
|
63
|
+
acc[key] = {
|
64
|
+
default: providedValue?.default ?? TEXT_DEFAULTS[key].default,
|
65
|
+
enforced: providedValue?.enforced ?? TEXT_DEFAULTS[key].enforced
|
66
|
+
};
|
67
|
+
return acc;
|
68
|
+
}, {})
|
69
|
+
);
|
70
|
+
|
71
|
+
const logoSvg = "<<REPLACE_ME_LOGO_SVG_FUNCTION>>";
|
72
|
+
|
73
|
+
const NAVIGATION_ACTIONS = Object.freeze({
|
41
74
|
prev: {
|
42
75
|
symbol: '«',
|
43
|
-
text:
|
76
|
+
text: `« ${FULL_TEXT_DEFAULTS.prev.default}`,
|
77
|
+
text_enforced: FULL_TEXT_DEFAULTS.prev.enforced,
|
44
78
|
title: 'Previous site',
|
45
79
|
path: 'previous',
|
46
80
|
additionalClass: 'prev-btn'
|
47
81
|
},
|
48
82
|
random: {
|
49
83
|
symbol: logoSvg(23, 23),
|
50
|
-
text: `${logoSvg(20, 20, "margin-right: 4px; margin-top: 1px;")}
|
84
|
+
text: `${logoSvg(20, 20, "margin-right: 4px; margin-top: 1px;")} ${FULL_TEXT_DEFAULTS.random.default}`,
|
85
|
+
text_enforced: FULL_TEXT_DEFAULTS.random.enforced,
|
51
86
|
title: 'Random site',
|
52
87
|
path: 'random',
|
53
88
|
additionalClass: 'random-btn'
|
54
89
|
},
|
55
90
|
next: {
|
56
91
|
symbol: '»',
|
57
|
-
text:
|
92
|
+
text: `${FULL_TEXT_DEFAULTS.next.default} »`,
|
93
|
+
text_enforced: FULL_TEXT_DEFAULTS.next.enforced,
|
58
94
|
title: 'Next site',
|
59
95
|
path: 'next',
|
60
96
|
additionalClass: 'next-btn'
|
@@ -65,18 +101,17 @@
|
|
65
101
|
path: '',
|
66
102
|
additionalClass: 'logo-only'
|
67
103
|
}
|
68
|
-
};
|
104
|
+
});
|
69
105
|
|
70
|
-
const WIDGET_TYPE_CONFIG = {
|
106
|
+
const WIDGET_TYPE_CONFIG = Object.freeze({
|
71
107
|
'full': { showTitle: true, actions: ['prev', 'random', 'next'] },
|
72
108
|
'no-text': { showTitle: false, actions: ['prev', 'random', 'next'] },
|
73
109
|
'two-way': { showTitle: false, actions: ['prev', 'logoOnly', 'next'], showLogoInMiddle: true },
|
74
110
|
'one-way': { showTitle: false, actions: ['next'], showLogoInButton: true },
|
75
111
|
'random-only': { showTitle: false, actions: ['random'] }
|
76
|
-
};
|
112
|
+
});
|
77
113
|
|
78
|
-
|
79
|
-
const STYLES = {
|
114
|
+
const STYLES = Object.freeze({
|
80
115
|
layout: `
|
81
116
|
.webring-nav {
|
82
117
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
@@ -98,17 +133,17 @@
|
|
98
133
|
justify-content: center;
|
99
134
|
align-items: center;
|
100
135
|
}
|
101
|
-
.webring-
|
136
|
+
.webring-btn {
|
102
137
|
display: flex;
|
103
138
|
align-items: center;
|
104
139
|
justify-content: center;
|
105
140
|
padding: 6px 12px;
|
106
141
|
text-decoration: none;
|
107
142
|
}
|
108
|
-
.
|
109
|
-
padding: 6px 8px
|
143
|
+
.random-btn {
|
144
|
+
padding: 6px 8px;
|
110
145
|
}
|
111
|
-
.
|
146
|
+
.logo-only {
|
112
147
|
padding: 8px 3px 6px 3px;
|
113
148
|
}
|
114
149
|
.webring-nav[data-widget-type="no-text"] {
|
@@ -123,13 +158,11 @@
|
|
123
158
|
margin-right: 6px;
|
124
159
|
margin-top: 1px;
|
125
160
|
}
|
126
|
-
|
127
|
-
.webring-nav[data-button-text="false"] a.webring-btn.prev-btn {
|
161
|
+
[data-button-text="false"] .prev-btn {
|
128
162
|
padding-top: 5px;
|
129
163
|
padding-right: 12.5px;
|
130
164
|
}
|
131
|
-
|
132
|
-
.webring-nav[data-button-text="false"] a.webring-btn.next-btn {
|
165
|
+
[data-button-text="false"] .next-btn {
|
133
166
|
padding-top: 5px;
|
134
167
|
padding-left: 12.5px;
|
135
168
|
}
|
@@ -141,49 +174,57 @@
|
|
141
174
|
.webring-title {
|
142
175
|
font-weight: 600;
|
143
176
|
}
|
144
|
-
.webring-
|
177
|
+
.webring-btn {
|
178
|
+
text-wrap: nowrap;
|
145
179
|
color: #000000;
|
146
180
|
font-weight: 600;
|
147
181
|
background-color: #ffffff;
|
148
182
|
border: 2.5px solid #000000;
|
149
183
|
transition: background-color 0.2s ease, color 0.2s ease;
|
150
184
|
}
|
151
|
-
.webring-
|
185
|
+
.webring-btn:hover {
|
152
186
|
background-color: #000000;
|
153
187
|
color: #ffffff;
|
154
188
|
}
|
155
|
-
.webring-
|
189
|
+
.webring-btn:focus {
|
156
190
|
outline: none;
|
157
191
|
background-color: transparent;
|
158
192
|
color: #000000;
|
159
193
|
}
|
160
|
-
.webring-
|
194
|
+
.webring-btn:active {
|
161
195
|
border-color: #000000;
|
162
196
|
background-color: #000000;
|
163
197
|
color: #ffffff;
|
164
198
|
}
|
165
199
|
`
|
166
|
-
};
|
200
|
+
});
|
201
|
+
|
202
|
+
// Track if styles have been applied
|
203
|
+
let stylesApplied = false;
|
167
204
|
|
168
205
|
// Run immediately for widget initialization
|
169
206
|
createWidget();
|
170
207
|
|
171
208
|
function createWidget(scriptElement) {
|
172
|
-
|
173
|
-
|
174
|
-
return scripts[scripts.length - 1];
|
175
|
-
})();
|
209
|
+
// Find the script element, using a passed element, current script, or last script
|
210
|
+
const script = scriptElement || document.currentScript || document.getElementsByTagName('script')[document.getElementsByTagName('script').length - 1];
|
176
211
|
|
177
212
|
if (!script) return;
|
178
213
|
|
179
214
|
// Config from data attributes
|
180
215
|
const memberUid = script.getAttribute('data-member-uid');
|
181
|
-
const widgetType = script.getAttribute('data-widget-type')
|
182
|
-
const targetId = script.getAttribute('data-target-id')
|
216
|
+
const widgetType = script.getAttribute('data-widget-type') ?? WIDGET_CONFIG.DEFAULT_TYPE;
|
217
|
+
const targetId = script.getAttribute('data-target-id') ?? WIDGET_CONFIG.DEFAULT_TARGET_ID;
|
183
218
|
const buttonText = script.getAttribute('data-button-text') !== 'false';
|
184
|
-
const stylesType = script.getAttribute('data-styles')
|
219
|
+
const stylesType = script.getAttribute('data-styles') ?? WIDGET_CONFIG.DEFAULT_STYLE_TYPE;
|
185
220
|
const stylesOption = WIDGET_CONFIG.VALID_STYLE_TYPES.includes(stylesType) ? stylesType : WIDGET_CONFIG.DEFAULT_STYLE_TYPE;
|
186
221
|
|
222
|
+
// Custom text data attributes
|
223
|
+
const prevText = script.getAttribute('data-prev-text');
|
224
|
+
const randomText = script.getAttribute('data-random-text');
|
225
|
+
const nextText = script.getAttribute('data-next-text');
|
226
|
+
const widgetText = script.getAttribute('data-widget-text');
|
227
|
+
|
187
228
|
if (!memberUid) {
|
188
229
|
console.error('Webring Widget: Missing data-member-uid attribute on script tag.');
|
189
230
|
return;
|
@@ -204,42 +245,89 @@
|
|
204
245
|
return;
|
205
246
|
}
|
206
247
|
|
207
|
-
//
|
248
|
+
// Apply styles only once per page
|
249
|
+
if (!stylesApplied) {
|
250
|
+
applyStyles(stylesOption);
|
251
|
+
stylesApplied = true;
|
252
|
+
}
|
253
|
+
|
254
|
+
// Use DocumentFragment for DOM operations
|
255
|
+
const fragment = document.createDocumentFragment();
|
256
|
+
|
257
|
+
// Create main wrapper div
|
258
|
+
const wrapperDiv = document.createElement('div');
|
259
|
+
wrapperDiv.className = 'webring-nav';
|
260
|
+
wrapperDiv.setAttribute('data-widget-type', widgetType);
|
261
|
+
wrapperDiv.setAttribute('data-button-text', buttonText.toString());
|
262
|
+
|
263
|
+
// Apply custom texts efficiently
|
264
|
+
const customTexts = {
|
265
|
+
prev: NAVIGATION_ACTIONS.prev.text_enforced
|
266
|
+
? NAVIGATION_ACTIONS.prev
|
267
|
+
: (prevText ? {...NAVIGATION_ACTIONS.prev, text: `« ${prevText}`} : NAVIGATION_ACTIONS.prev),
|
268
|
+
random: NAVIGATION_ACTIONS.random.text_enforced
|
269
|
+
? NAVIGATION_ACTIONS.random
|
270
|
+
: (randomText ? {...NAVIGATION_ACTIONS.random, text: `${logoSvg(20, 20, "margin-right: 4px; margin-top: 1px;")} ${randomText}`} : NAVIGATION_ACTIONS.random),
|
271
|
+
next: NAVIGATION_ACTIONS.next.text_enforced
|
272
|
+
? NAVIGATION_ACTIONS.next
|
273
|
+
: (nextText ? {...NAVIGATION_ACTIONS.next, text: `${nextText} »`} : NAVIGATION_ACTIONS.next),
|
274
|
+
logoOnly: NAVIGATION_ACTIONS.logoOnly
|
275
|
+
};
|
276
|
+
|
277
|
+
// Add title if needed
|
208
278
|
const config = WIDGET_TYPE_CONFIG[widgetType];
|
209
|
-
|
210
|
-
const
|
279
|
+
if (config.showTitle) {
|
280
|
+
const titleElem = document.createElement('span');
|
281
|
+
titleElem.className = 'webring-title';
|
282
|
+
titleElem.innerHTML = FULL_TEXT_DEFAULTS.widgetTitle.enforced ?
|
283
|
+
FULL_TEXT_DEFAULTS.widgetTitle.default :
|
284
|
+
(widgetText ?? FULL_TEXT_DEFAULTS.widgetTitle.default);
|
285
|
+
wrapperDiv.appendChild(titleElem);
|
286
|
+
}
|
287
|
+
|
288
|
+
// Create navigation container
|
289
|
+
const navElem = document.createElement('nav');
|
290
|
+
navElem.className = 'webring-buttons';
|
291
|
+
|
292
|
+
// Create buttons
|
293
|
+
config.actions.forEach(action => {
|
294
|
+
const actionConfig = customTexts[action];
|
211
295
|
|
212
296
|
// Logo-only block
|
213
297
|
if (action === 'logoOnly') {
|
214
|
-
|
298
|
+
const logoDiv = document.createElement('div');
|
299
|
+
logoDiv.className = actionConfig.additionalClass;
|
300
|
+
logoDiv.innerHTML = actionConfig.symbol;
|
301
|
+
navElem.appendChild(logoDiv);
|
302
|
+
return;
|
215
303
|
}
|
216
304
|
|
217
|
-
const
|
218
|
-
|
219
|
-
|
305
|
+
const linkElem = document.createElement('a');
|
306
|
+
linkElem.href = `${baseUrl}/webring/${actionConfig.path}?source_member_uid=${memberUid}`;
|
307
|
+
linkElem.title = actionConfig.title;
|
308
|
+
linkElem.className = `webring-btn ${actionConfig.additionalClass}`;
|
220
309
|
|
221
310
|
// One-way type case
|
222
311
|
if (widgetType === 'one-way' && config.showLogoInButton && action === 'next') {
|
223
|
-
|
224
|
-
|
312
|
+
const buttonTextContent = NAVIGATION_ACTIONS.next.text_enforced ?
|
313
|
+
FULL_TEXT_DEFAULTS.next.default :
|
314
|
+
(nextText || customTexts.next.text.replace(' »', ''));
|
315
|
+
|
316
|
+
linkElem.innerHTML = buttonText
|
317
|
+
? `<span class="webring-logo-inline">${logoSvg(20, 20)}</span> ${buttonTextContent} »`
|
225
318
|
: `<span class="webring-logo-inline">${logoSvg(20, 20)}</span> ${actionConfig.symbol}`;
|
319
|
+
} else {
|
320
|
+
linkElem.innerHTML = buttonText ? actionConfig.text : actionConfig.symbol;
|
226
321
|
}
|
227
322
|
|
228
|
-
|
229
|
-
})
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
<nav class="webring-buttons">
|
237
|
-
${linkElements}
|
238
|
-
</nav>
|
239
|
-
</div>
|
240
|
-
`;
|
241
|
-
|
242
|
-
applyStyles(stylesOption);
|
323
|
+
navElem.appendChild(linkElem);
|
324
|
+
});
|
325
|
+
|
326
|
+
wrapperDiv.appendChild(navElem);
|
327
|
+
fragment.appendChild(wrapperDiv);
|
328
|
+
|
329
|
+
container.innerHTML = '';
|
330
|
+
container.appendChild(fragment);
|
243
331
|
};
|
244
332
|
|
245
333
|
function applyStyles(styleOption) {
|
@@ -0,0 +1 @@
|
|
1
|
+
!function(){function e(e){function n(e){let n=document.getElementById(t.STYLE_ID);switch(n||(n=document.createElement("style"),n.id=t.STYLE_ID,document.head.appendChild(n)),e){case"none":n.textContent="";break;case"layout":n.textContent=s.layout;break;default:n.textContent=s.layout+s.design}}const o=e||document.currentScript||document.getElementsByTagName("script")[document.getElementsByTagName("script").length-1];if(!o)return;const a=o.getAttribute("data-member-uid"),g=o.getAttribute("data-widget-type")??t.DEFAULT_TYPE,p=o.getAttribute("data-target-id")??t.DEFAULT_TARGET_ID,f="false"!==o.getAttribute("data-button-text"),u=o.getAttribute("data-styles")??t.DEFAULT_STYLE_TYPE,x=t.VALID_STYLE_TYPES.includes(u)?u:t.DEFAULT_STYLE_TYPE,b=o.getAttribute("data-prev-text"),m=o.getAttribute("data-random-text"),w=o.getAttribute("data-next-text"),y=o.getAttribute("data-widget-text");if(!a)return void console.error("Webring Widget: Missing data-member-uid attribute on script tag.");if(!t.VALID_TYPES.includes(g))return void console.error(`Webring Widget: Invalid widget type "${g}". Valid types: ${t.VALID_TYPES.join(", ")}`);const T=o.getAttribute("src"),E=new URL(T,window.location.href).origin,_=function(){const e=document.getElementById(p);if(!e)return void console.error(`Webring Widget: No element with id "${p}" found.`);c||(n(x),c=!0);const t=document.createDocumentFragment(),o=document.createElement("div");o.className="webring-nav",o.setAttribute("data-widget-type",g),o.setAttribute("data-button-text",f.toString());const s={prev:d.prev.text_enforced?d.prev:b?{...d.prev,text:`\xab ${b}`}:d.prev,random:d.random.text_enforced?d.random:m?{...d.random,text:`${r(20,20,"margin-right: 4px; margin-top: 1px;")} ${m}`}:d.random,next:d.next.text_enforced?d.next:w?{...d.next,text:`${w} \xbb`}:d.next,logoOnly:d.logoOnly},u=l[g];if(u.showTitle){const e=document.createElement("span");e.className="webring-title",e.innerHTML=i.widgetTitle.enforced?i.widgetTitle.default:y??i.widgetTitle.default,o.appendChild(e)}const T=document.createElement("nav");T.className="webring-buttons",u.actions.forEach((e=>{const t=s[e];if("logoOnly"===e){const e=document.createElement("div");return e.className=t.additionalClass,e.innerHTML=t.symbol,void T.appendChild(e)}const n=document.createElement("a");if(n.href=`${E}/webring/${t.path}?source_member_uid=${a}`,n.title=t.title,n.className=`webring-btn ${t.additionalClass}`,"one-way"===g&&u.showLogoInButton&&"next"===e){const e=d.next.text_enforced?i.next.default:w||s.next.text.replace(" \xbb","");n.innerHTML=f?`<span class="webring-logo-inline">${r(20,20)}</span> ${e} \xbb`:`<span class="webring-logo-inline">${r(20,20)}</span> ${t.symbol}`}else n.innerHTML=f?t.text:t.symbol;T.appendChild(n)})),o.appendChild(T),t.appendChild(o),e.innerHTML="",e.appendChild(t)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",_):_()}const t=Object.freeze({VALID_TYPES:Object.freeze(["full","no-text","two-way","one-way","random-only"]),DEFAULT_TYPE:"full",DEFAULT_TARGET_ID:"webring-widget",STYLE_ID:"webring-widget-styles",VALID_STYLE_TYPES:Object.freeze(["full","layout","none"]),DEFAULT_STYLE_TYPE:"full"}),n=Object.freeze({prev:{default:"\xab Prev",enforced:!1},random:{default:"Random",enforced:!1},next:{default:"Next \xbb",enforced:!1},widgetTitle:{default:"Webring",enforced:!1}}),o="<<REPLACE_ME_TEXT_DEFAULTS>>",a="<<REPLACE_ME_TEXT_DEFAULTS>>"!==o?"string"==typeof o?JSON.parse(o):o:{},i=Object.freeze(Object.keys(n).reduce(((e,t)=>{const o=a[t];return e[t]={default:o?.default??n[t].default,enforced:o?.enforced??n[t].enforced},e}),{})),r="<<REPLACE_ME_LOGO_SVG_FUNCTION>>",d=Object.freeze({prev:{symbol:"\xab",text:`\xab ${i.prev.default}`,text_enforced:i.prev.enforced,title:"Previous site",path:"previous",additionalClass:"prev-btn"},random:{symbol:r(23,23),text:`${r(20,20,"margin-right: 4px; margin-top: 1px;")} ${i.random.default}`,text_enforced:i.random.enforced,title:"Random site",path:"random",additionalClass:"random-btn"},next:{symbol:"\xbb",text:`${i.next.default} \xbb`,text_enforced:i.next.enforced,title:"Next site",path:"next",additionalClass:"next-btn"},logoOnly:{symbol:r(23,23),text:r(23,23),path:"",additionalClass:"logo-only"}}),l=Object.freeze({full:{showTitle:!0,actions:["prev","random","next"]},"no-text":{showTitle:!1,actions:["prev","random","next"]},"two-way":{showTitle:!1,actions:["prev","logoOnly","next"],showLogoInMiddle:!0},"one-way":{showTitle:!1,actions:["next"],showLogoInButton:!0},"random-only":{showTitle:!1,actions:["random"]}}),s=Object.freeze({layout:'\n .webring-nav {\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;\n font-size: 16px;\n display: flex;\n flex-direction: column;\n align-items: center;\n max-width: 350px;\n margin: 0 auto;\n padding: 10px;\n }\n .webring-title {\n margin-bottom: 8px;\n }\n .webring-nav nav {\n display: flex;\n gap: 10px;\n width: 100%;\n justify-content: center;\n align-items: center;\n }\n .webring-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 6px 12px;\n text-decoration: none;\n }\n .random-btn {\n padding: 6px 8px;\n }\n .logo-only {\n padding: 8px 3px 6px 3px;\n }\n .webring-nav[data-widget-type="no-text"] {\n padding: 8px 10px;\n }\n .webring-nav[data-widget-type="one-way"] {\n max-width: 200px;\n }\n .webring-logo-inline {\n display: inline-block;\n vertical-align: middle;\n margin-right: 6px;\n margin-top: 1px;\n }\n [data-button-text="false"] .prev-btn {\n padding-top: 5px;\n padding-right: 12.5px;\n }\n [data-button-text="false"] .next-btn {\n padding-top: 5px;\n padding-left: 12.5px;\n }\n ',design:'\n .webring-nav[data-widget-type="full"] {\n border: 2.5px solid #000000;\n }\n .webring-title {\n font-weight: 600;\n }\n .webring-btn {\n text-wrap: nowrap;\n color: #000000;\n font-weight: 600;\n background-color: #ffffff;\n border: 2.5px solid #000000;\n transition: background-color 0.2s ease, color 0.2s ease;\n }\n .webring-btn:hover {\n background-color: #000000;\n color: #ffffff;\n }\n .webring-btn:focus {\n outline: none;\n background-color: transparent;\n color: #000000;\n }\n .webring-btn:active {\n border-color: #000000;\n background-color: #000000;\n color: #ffffff;\n }\n '});let c=!1;e()}();
|
@@ -13,12 +13,13 @@ module Webring
|
|
13
13
|
format.js do
|
14
14
|
response.headers['Content-Type'] = 'application/javascript'
|
15
15
|
|
16
|
-
# Take the JavaScript file from the engine's assets and replace the customizable Logo SVG
|
16
|
+
# Take the minified JavaScript file from the engine's assets and replace the customizable Logo SVG
|
17
17
|
widget_js =
|
18
18
|
Webring::Engine
|
19
|
-
.root.join('app/assets/javascripts/webring/widget.js')
|
19
|
+
.root.join('app/assets/javascripts/webring/widget.min.js')
|
20
20
|
.read
|
21
|
-
.gsub('<<
|
21
|
+
.gsub('"<<REPLACE_ME_LOGO_SVG_FUNCTION>>"', logo_svg_function)
|
22
|
+
.gsub('"<<REPLACE_ME_TEXT_DEFAULTS>>"', JSON.generate(text_defaults))
|
22
23
|
|
23
24
|
render js: widget_js
|
24
25
|
end
|
@@ -34,13 +35,31 @@ module Webring
|
|
34
35
|
response.headers['Access-Control-Max-Age'] = '86400'
|
35
36
|
end
|
36
37
|
|
38
|
+
# This function is used to generate the logo SVG function for the compiled widget.js file to overcome args names compression issue
|
39
|
+
def logo_svg_function
|
40
|
+
<<~JS
|
41
|
+
(width = 20, height = 20, style = "") => `#{logo_svg}`
|
42
|
+
JS
|
43
|
+
end
|
44
|
+
|
37
45
|
# should include `${width}`, `${height}`, `${style}` in order to be customizable
|
38
46
|
def logo_svg
|
39
|
-
<<~
|
47
|
+
<<~HTML
|
40
48
|
<svg width="${width}" height="${height}" style="${style}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
41
49
|
<path d="M13 3L6 14H12L11 21L18 10H12L13 3Z" fill="currentColor"/>
|
42
50
|
</svg>
|
43
|
-
|
51
|
+
HTML
|
52
|
+
end
|
53
|
+
|
54
|
+
# Provide default texts for the widget
|
55
|
+
# Override this method to customize the default texts
|
56
|
+
def text_defaults
|
57
|
+
{
|
58
|
+
prev: { default: 'Prev', enforced: false },
|
59
|
+
random: { default: 'Random', enforced: false },
|
60
|
+
next: { default: 'Next', enforced: false },
|
61
|
+
widgetTitle: { default: 'Webring', enforced: false }
|
62
|
+
}
|
44
63
|
end
|
45
64
|
end
|
46
65
|
end
|
@@ -10,6 +10,7 @@ module Webring
|
|
10
10
|
validates :url, presence: true, uniqueness: true
|
11
11
|
validates :name, presence: true
|
12
12
|
validates :description, presence: true
|
13
|
+
validates :callback_email, presence: true
|
13
14
|
|
14
15
|
enum :status, { pending: 0, approved: 1, rejected: 2 }, default: :pending
|
15
16
|
end
|
data/lib/generators/USAGE
CHANGED
@@ -5,9 +5,20 @@ module Webring
|
|
5
5
|
# should include `${width}`, `${height}`, `${style}` in order to be customizable
|
6
6
|
# remove the method or call `super` to use the default logo
|
7
7
|
def logo_svg
|
8
|
-
<<~
|
8
|
+
<<~HTML
|
9
9
|
Add your custom logo SVG here
|
10
|
-
|
10
|
+
HTML
|
11
|
+
end
|
12
|
+
|
13
|
+
# Override default texts for the widget
|
14
|
+
# Remove the method or call `super` to use the gem's default texts
|
15
|
+
def text_defaults
|
16
|
+
{
|
17
|
+
prev: { default: 'Prev', enforced: false },
|
18
|
+
random: { default: 'Random', enforced: false },
|
19
|
+
next: { default: 'Next', enforced: false },
|
20
|
+
widgetTitle: { default: 'Webring', enforced: false }
|
21
|
+
}
|
11
22
|
end
|
12
23
|
end
|
13
24
|
end
|
@@ -8,6 +8,7 @@ module Webring
|
|
8
8
|
validates :url, presence: true, uniqueness: true
|
9
9
|
validates :name, presence: true
|
10
10
|
validates :description, presence: true
|
11
|
+
validates :callback_email, presence: true
|
11
12
|
|
12
13
|
enum :status, { pending: 0, approved: 1, rejected: 2 }, default: :pending
|
13
14
|
|
data/lib/webring/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webring-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nikita Shkoda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-06-
|
11
|
+
date: 2025-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '2.31'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: terser
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.2'
|
55
69
|
description: Mountable Rails Engine for webring implementation with an embeddable
|
56
70
|
styled widget
|
57
71
|
email:
|
@@ -63,12 +77,12 @@ files:
|
|
63
77
|
- MIT-LICENSE
|
64
78
|
- README.md
|
65
79
|
- app/assets/javascripts/webring/widget.js
|
80
|
+
- app/assets/javascripts/webring/widget.min.js
|
66
81
|
- app/controllers/webring/application_controller.rb
|
67
82
|
- app/controllers/webring/members_controller.rb
|
68
83
|
- app/controllers/webring/membership_requests_controller.rb
|
69
84
|
- app/controllers/webring/navigation_controller.rb
|
70
85
|
- app/controllers/webring/widget_controller.rb
|
71
|
-
- app/helpers/webring/application_helper.rb
|
72
86
|
- app/models/concerns/webring/membership_request_actions.rb
|
73
87
|
- app/models/concerns/webring/navigation.rb
|
74
88
|
- app/models/webring/application_record.rb
|
@@ -94,7 +108,6 @@ files:
|
|
94
108
|
- lib/generators/webring/navigation_controller/navigation_controller_generator.rb
|
95
109
|
- lib/generators/webring/navigation_controller/templates/navigation_controller.rb
|
96
110
|
- lib/generators/webring/shared/route_injector.rb
|
97
|
-
- lib/generators/webring_generator.rb
|
98
111
|
- lib/webring-rails.rb
|
99
112
|
- lib/webring/engine.rb
|
100
113
|
- lib/webring/version.rb
|
@@ -1,17 +0,0 @@
|
|
1
|
-
module Webring
|
2
|
-
module Generators
|
3
|
-
class WebringGenerator < Rails::Generators::NamedBase
|
4
|
-
namespace 'webring'
|
5
|
-
|
6
|
-
desc 'Creates a Webring configuration for the given model name'
|
7
|
-
|
8
|
-
def invoke_webring_member
|
9
|
-
invoke 'webring:member'
|
10
|
-
end
|
11
|
-
|
12
|
-
def invoke_webring_controller
|
13
|
-
invoke 'webring:member_controller'
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|