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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb7c45a7911e93d8ba099c52ab12aa3552007ade35aee285cadb4adfa2eb0fb2
4
- data.tar.gz: 87c041e80ac5729486e4a1d479401255b4def0995e96e0f0e0d4773d8109f6ad
3
+ metadata.gz: 754be274910dadf9dc06187305d7482287140f2d2e8c18f4eb949c9343ca66ee
4
+ data.tar.gz: '0449f04934aa33b9cdcc306890d27d1bd34a149c577996221a41904b02774215'
5
5
  SHA512:
6
- metadata.gz: a106e56ff61948867ec07e2cb29e3d7c547ee83b5974b9f7823c229f8d220bafe44d9f8e45de1c7b0fa6d44ed226730c22e54c10ceddb57fd700a7f0dc2b2707
7
- data.tar.gz: c5084e377d1a15bbcbe3a3fd72ab421b8aadb7f8fcfa23ddd99f3150a747563e2ef61f59dc77fc598b02cb009ff295e730e113c87e5758a7ab91e4a9f0d23ec6
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 (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.
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 'webring_rails'
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:controller:navigation
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:controller:membership_requests
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
- const logoSvg = (width = 20, height = 20, style = "") => `<<REPLACE_ME_LOGO_SVG>>`;
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
- const NAVIGATION_ACTIONS = {
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: Prev',
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;")} Random`,
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: 'Next »',
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
- // Define styles outside the function to avoid duplication
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-nav a.webring-btn {
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
- .webring-nav a.webring-btn.random-btn {
109
- padding: 6px 8px 6px 8px;
143
+ .random-btn {
144
+ padding: 6px 8px;
110
145
  }
111
- .webring-nav .logo-only {
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
- /* no-text prev button */
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
- /* no-text next button */
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-nav a.webring-btn {
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-nav a.webring-btn:hover {
185
+ .webring-btn:hover {
152
186
  background-color: #000000;
153
187
  color: #ffffff;
154
188
  }
155
- .webring-nav a.webring-btn:focus {
189
+ .webring-btn:focus {
156
190
  outline: none;
157
191
  background-color: transparent;
158
192
  color: #000000;
159
193
  }
160
- .webring-nav a.webring-btn:active {
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
- const script = scriptElement || document.currentScript || (function() {
173
- const scripts = document.getElementsByTagName('script');
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') || WIDGET_CONFIG.DEFAULT_TYPE;
182
- const targetId = script.getAttribute('data-target-id') || WIDGET_CONFIG.DEFAULT_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') || WIDGET_CONFIG.DEFAULT_STYLE_TYPE;
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
- // Navigation links
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
- const linkElements = config.actions.map(action => {
210
- const actionConfig = NAVIGATION_ACTIONS[action];
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
- return `<div class="${actionConfig.additionalClass}">${actionConfig.symbol}</div>`;
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 url = `${baseUrl}/webring/${actionConfig.path}?source_member_uid=${memberUid}`;
218
- let label = buttonText ? actionConfig.text : actionConfig.symbol;
219
- const btnClass = `webring-btn${actionConfig.additionalClass ? ` ${actionConfig.additionalClass}` : ''}`;
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
- label = buttonText
224
- ? `<span class="webring-logo-inline">${logoSvg(20, 20)}</span> ${actionConfig.text}`
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
- return `<a href="${url}" title="${actionConfig.title}" class="${btnClass}">${label}</a>`;
229
- }).join('\n ');
230
-
231
- // Create widget HTML
232
- const title = config.showTitle ? '<span class="webring-title">Ruby Webring</span>' : '';
233
- container.innerHTML = `
234
- <div class="webring-nav" data-widget-type="${widgetType}" data-button-text="${buttonText}">
235
- ${title}
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('<<REPLACE_ME_LOGO_SVG>>', logo_svg)
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
- <<~SVG
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
- SVG
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
@@ -6,7 +6,7 @@ Example:
6
6
  rails generate webring:install
7
7
 
8
8
  This will create:
9
- # Mount the Webring engine in your routes.rb file
9
+ # Mount the Webring engine and widget.js path in your routes.rb file
10
10
 
11
11
  Example:
12
12
  rails generate webring:member
@@ -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
- <<~SVG
8
+ <<~HTML
9
9
  Add your custom logo SVG here
10
- SVG
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
 
@@ -1,3 +1,3 @@
1
1
  module Webring
2
- VERSION = '1.3.1'.freeze
2
+ VERSION = '1.4.2'.freeze
3
3
  end
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.3.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-08 00:00:00.000000000 Z
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,4 +0,0 @@
1
- module Webring
2
- module ApplicationHelper
3
- end
4
- end
@@ -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