webring-rails 1.4.4 → 1.6.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: 447748a2ca051ec5cfdbdb2939c09a30c8b57e2a47fff7aabd8f32cea100cc0c
4
- data.tar.gz: 6644675f77cd307363543cc56b183152e4a42adb1121844834da943e430ba993
3
+ metadata.gz: 82bc3c0e725233f97c81c7f5d93300155cf4a926618b9f0cfc0b34728411ce90
4
+ data.tar.gz: 81704a8acd70cf5f060f6208eed0512ace47504e28ddf4071caee9d04e7f4589
5
5
  SHA512:
6
- metadata.gz: a2b3855e306305947af1fc114b30005e8ed551f070f7cacd67d5719f9517b915fcbe6a3bf266f4acf187c6345f636f513a6ee0a6ebaea5ea8d1b4d8a20c40ac5
7
- data.tar.gz: 771bdf593b8310ae9a0c18b957ed3f6f896e26b7277b85da26e36bd03c7f86bcb736430a73df8c45f039150eb8dfd7f3616b8b0c1427706fd7ebfd50238c216c
6
+ metadata.gz: b83576e412c50d3e060bdfc056d2695da79f576aae7c398d85e4a0a8efe77c68d3dd57bdcef5cdbb5c1654dc7e7331f4894817cf05b0c6c853060bd36fcab269
7
+ data.tar.gz: 1a717972d57d3361d51d6f250b1ada90ea38d7d5142dba6bbf573ab6fd57a39d9f5431dd835f2ede7be9969f166805f1fd8d1541620254e2e4eaffa6c2cb96ba
@@ -22,6 +22,7 @@
22
22
  * - full: Apply all styles (default)
23
23
  * - layout: Only layout styles, no visual design
24
24
  * - none: No styles applied
25
+ * - data-theme="light|dark": Sets the theme for the widget. Default: light
25
26
  * - data-prev-text="Custom Text": Sets custom text for the "previous" button. Default: "« Prev"
26
27
  * - data-random-text="Custom Text": Sets custom text for the "random" button (keeps the logo). Default: "Random"
27
28
  * - data-next-text="Custom Text": Sets custom text for the "next" button. Default: "Next »"
@@ -36,7 +37,9 @@
36
37
  DEFAULT_TARGET_ID: 'webring-widget',
37
38
  STYLE_ID: 'webring-widget-styles',
38
39
  VALID_STYLE_TYPES: Object.freeze(['full', 'layout', 'none']),
39
- DEFAULT_STYLE_TYPE: 'full'
40
+ DEFAULT_STYLE_TYPE: 'full',
41
+ VALID_THEMES: Object.freeze(['light', 'dark']),
42
+ DEFAULT_THEME: 'light'
40
43
  });
41
44
 
42
45
  // Default text configurations
@@ -204,8 +207,7 @@
204
207
  box-sizing: border-box;
205
208
  }
206
209
  .webring-nav .webring-title {
207
- margin-bottom: 8px;
208
- margin-top: 0;
210
+ margin: 0 0 8px 0;
209
211
  padding: 0;
210
212
  font-size: inherit;
211
213
  line-height: normal;
@@ -245,48 +247,84 @@
245
247
  .webring-nav .webring-logo-inline {
246
248
  display: inline-block;
247
249
  vertical-align: middle;
248
- margin-right: 6px;
249
- margin-top: 1px;
250
- margin-bottom: 0;
251
- margin-left: 0;
250
+ margin: 1px 6px 0 0;
252
251
  }
253
252
  .webring-nav[data-button-text="false"] .prev-btn {
254
- padding-top: 5px;
255
- padding-right: 12.5px;
253
+ padding: 5px 12.5px 6px 12px;
256
254
  }
257
255
  .webring-nav[data-button-text="false"] .next-btn {
258
- padding-top: 5px;
259
- padding-left: 12.5px;
256
+ padding: 5px 12px 6px 12.5px;
260
257
  }
261
258
  `,
262
259
  design: `
260
+ /* CSS Custom Properties for easy theming */
261
+ .webring-nav {
262
+ /* Internal variables that use public API with fallbacks */
263
+ --webring-bg-color: var(--webring-theme-bg);
264
+ --webring-border-color: var(--webring-theme-border);
265
+ --webring-text-color: var(--webring-theme-text);
266
+ --webring-btn-bg: var(--webring-theme-btn-bg);
267
+ --webring-btn-border: var(--webring-theme-btn-border);
268
+ --webring-btn-text: var(--webring-theme-btn-text);
269
+ --webring-btn-hover-bg: var(--webring-theme-btn-hover-bg);
270
+ --webring-btn-hover-text: var(--webring-theme-btn-hover-text);
271
+ }
272
+
273
+ /* Common theme styles using CSS variables */
274
+ .webring-nav {
275
+ background-color: var(--webring-bg-color);
276
+ }
263
277
  .webring-nav[data-widget-type="full"] {
264
- border: 2.5px solid #000000;
278
+ border: 2.5px solid var(--webring-border-color);
265
279
  }
266
280
  .webring-nav .webring-title {
267
281
  font-weight: 600;
282
+ color: var(--webring-text-color);
268
283
  }
269
284
  .webring-nav .webring-btn {
270
285
  text-wrap: nowrap;
271
- color: #000000;
272
286
  font-weight: 600;
273
- background-color: #ffffff;
274
- border: 2.5px solid #000000;
287
+ color: var(--webring-btn-text);
288
+ background-color: var(--webring-btn-bg);
289
+ border: 2.5px solid var(--webring-btn-border);
275
290
  transition: background-color 0.2s ease, color 0.2s ease;
276
291
  }
277
- .webring-nav .webring-btn:hover {
278
- background-color: #000000;
279
- color: #ffffff;
280
- }
281
292
  .webring-nav .webring-btn:focus {
282
293
  outline: none;
283
294
  background-color: transparent;
284
- color: #000000;
295
+ color: var(--webring-btn-text);
285
296
  }
297
+ .webring-nav .webring-btn:hover,
286
298
  .webring-nav .webring-btn:active {
287
- border-color: #000000;
288
- background-color: #000000;
289
- color: #ffffff;
299
+ background-color: var(--webring-btn-hover-bg);
300
+ color: var(--webring-btn-hover-text);
301
+ }
302
+ .webring-nav svg {
303
+ fill: currentColor;
304
+ }
305
+
306
+ /* Light theme defaults - these have lower specificity than member's rules */
307
+ .webring-nav {
308
+ --webring-theme-bg: #ffffff;
309
+ --webring-theme-border: #000000;
310
+ --webring-theme-text: #000000;
311
+ --webring-theme-btn-bg: #ffffff;
312
+ --webring-theme-btn-border: #000000;
313
+ --webring-theme-btn-text: #000000;
314
+ --webring-theme-btn-hover-bg: #000000;
315
+ --webring-theme-btn-hover-text: #ffffff;
316
+ }
317
+
318
+ /* Dark theme overrides */
319
+ .webring-nav[data-theme="dark"] {
320
+ --webring-theme-bg: #000000;
321
+ --webring-theme-border: #ffffff;
322
+ --webring-theme-text: #ffffff;
323
+ --webring-theme-btn-bg: #000000;
324
+ --webring-theme-btn-border: #ffffff;
325
+ --webring-theme-btn-text: #ffffff;
326
+ --webring-theme-btn-hover-bg: #ffffff;
327
+ --webring-theme-btn-hover-text: #000000;
290
328
  }
291
329
  `
292
330
  });
@@ -310,6 +348,8 @@
310
348
  const buttonText = script.getAttribute('data-button-text') !== 'false';
311
349
  const stylesType = script.getAttribute('data-styles') ?? WIDGET_CONFIG.DEFAULT_STYLE_TYPE;
312
350
  const stylesOption = WIDGET_CONFIG.VALID_STYLE_TYPES.includes(stylesType) ? stylesType : WIDGET_CONFIG.DEFAULT_STYLE_TYPE;
351
+ const themeType = script.getAttribute('data-theme') ?? WIDGET_CONFIG.DEFAULT_THEME;
352
+ const theme = WIDGET_CONFIG.VALID_THEMES.includes(themeType) ? themeType : WIDGET_CONFIG.DEFAULT_THEME;
313
353
 
314
354
  // Custom text data attributes
315
355
  const prevText = script.getAttribute('data-prev-text');
@@ -351,6 +391,7 @@
351
391
  wrapperDiv.className = 'webring-nav';
352
392
  wrapperDiv.setAttribute('data-widget-type', widgetType);
353
393
  wrapperDiv.setAttribute('data-button-text', buttonText.toString());
394
+ wrapperDiv.setAttribute('data-theme', theme);
354
395
 
355
396
  // Apply custom texts efficiently
356
397
  const customTexts = {
@@ -427,7 +468,8 @@
427
468
  if (!styleElement) {
428
469
  styleElement = document.createElement('style');
429
470
  styleElement.id = WIDGET_CONFIG.STYLE_ID;
430
- document.head.appendChild(styleElement);
471
+ // Insert at the beginning of head so user styles can override
472
+ document.head.insertBefore(styleElement, document.head.firstChild);
431
473
  }
432
474
 
433
475
  switch (styleOption) {
@@ -1 +1 @@
1
- !function(){function n(n){function t(n){let t=document.getElementById(e.STYLE_ID);switch(t||(t=document.createElement("style"),t.id=e.STYLE_ID,document.head.appendChild(t)),n){case"none":t.textContent=g.reset;break;case"layout":t.textContent=g.reset+g.layout;break;default:t.textContent=g.reset+g.layout+g.design}}const i=n||document.currentScript||document.getElementsByTagName("script")[document.getElementsByTagName("script").length-1];if(!i)return;const a=i.getAttribute("data-member-uid"),b=i.getAttribute("data-widget-type")??e.DEFAULT_TYPE,c=i.getAttribute("data-target-id")??e.DEFAULT_TARGET_ID,p="false"!==i.getAttribute("data-button-text"),f=i.getAttribute("data-styles")??e.DEFAULT_STYLE_TYPE,w=e.VALID_STYLE_TYPES.includes(f)?f:e.DEFAULT_STYLE_TYPE,m=i.getAttribute("data-prev-text"),u=i.getAttribute("data-random-text"),x=i.getAttribute("data-next-text"),v=i.getAttribute("data-widget-text");if(!a)return void console.error("Webring Widget: Missing data-member-uid attribute on script tag.");if(!e.VALID_TYPES.includes(b))return void console.error(`Webring Widget: Invalid widget type "${b}". Valid types: ${e.VALID_TYPES.join(", ")}`);const h=i.getAttribute("src"),y=new URL(h,window.location.href).origin,T=function(){const n=document.getElementById(c);if(!n)return void console.error(`Webring Widget: No element with id "${c}" found.`);s||(t(w),s=!0);const e=document.createDocumentFragment(),i=document.createElement("div");i.className="webring-nav",i.setAttribute("data-widget-type",b),i.setAttribute("data-button-text",p.toString());const g={prev:d.prev.text_enforced?d.prev:m?{...d.prev,text:`\xab ${m}`}:d.prev,random:d.random.text_enforced?d.random:u?{...d.random,text:`${r(20,20,"margin-right: 4px; margin-top: 1px;")} ${u}`}:d.random,next:d.next.text_enforced?d.next:x?{...d.next,text:`${x} \xbb`}:d.next,logoOnly:d.logoOnly},f=l[b];if(f.showTitle){const n=document.createElement("span");n.className="webring-title",n.innerHTML=o.widgetTitle.enforced?o.widgetTitle.default:v??o.widgetTitle.default,i.appendChild(n)}const h=document.createElement("nav");h.className="webring-buttons",f.actions.forEach((n=>{const e=g[n];if("logoOnly"===n){const n=document.createElement("div");return n.className=e.additionalClass,n.innerHTML=e.symbol,void h.appendChild(n)}const t=document.createElement("a");if(t.href=`${y}/webring/${e.path}?source_member_uid=${a}`,t.title=e.title,t.className=`webring-btn ${e.additionalClass}`,"one-way"===b&&f.showLogoInButton&&"next"===n){const n=d.next.text_enforced?o.next.default:x||g.next.text.replace(" \xbb","");t.innerHTML=p?`<span class="webring-logo-inline">${r(20,20)}</span> ${n} \xbb`:`<span class="webring-logo-inline">${r(20,20)}</span> ${e.symbol}`}else t.innerHTML=p?e.text:e.symbol;h.appendChild(t)})),i.appendChild(h),e.appendChild(i),n.innerHTML="",n.appendChild(e)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",T):T()}const e=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"}),t=Object.freeze({prev:{default:"\xab Prev",enforced:!1},random:{default:"Random",enforced:!1},next:{default:"Next \xbb",enforced:!1},widgetTitle:{default:"Webring",enforced:!1}}),i="<<REPLACE_ME_TEXT_DEFAULTS>>",a="<<REPLACE_ME_TEXT_DEFAULTS>>"!==i?"string"==typeof i?JSON.parse(i):i:{},o=Object.freeze(Object.keys(t).reduce(((n,e)=>{const i=a[e];return n[e]={default:i?.default??t[e].default,enforced:i?.enforced??t[e].enforced},n}),{})),r="<<REPLACE_ME_LOGO_SVG_FUNCTION>>",d=Object.freeze({prev:{symbol:"\xab",text:`\xab ${o.prev.default}`,text_enforced:o.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;")} ${o.random.default}`,text_enforced:o.random.enforced,title:"Random site",path:"random",additionalClass:"random-btn"},next:{symbol:"\xbb",text:`${o.next.default} \xbb`,text_enforced:o.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"]}}),g=Object.freeze({reset:"\n .webring-nav,\n .webring-nav div,\n .webring-nav span,\n .webring-nav nav,\n .webring-nav a,\n .webring-nav div::before,\n .webring-nav span::before,\n .webring-nav nav::before,\n .webring-nav a::before,\n .webring-nav div::after,\n .webring-nav span::after,\n .webring-nav nav::after,\n .webring-nav a::after {\n all: initial;\n box-sizing: border-box;\n }\n\n // .webring-nav svg,\n // .webring-nav svg * {\n // all: revert;\n // fill: currentColor;\n // }\n\n .webring-nav,\n .webring-nav div,\n .webring-nav span,\n .webring-nav nav,\n .webring-nav a {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n color: inherit;\n }\n\n .webring-nav div,\n .webring-nav span,\n .webring-nav nav,\n .webring-nav a {\n display: initial;\n margin: 0;\n padding: 0;\n border: none;\n background: none;\n text-decoration: none;\n color: inherit;\n font-weight: inherit;\n font-size: inherit;\n line-height: inherit;\n vertical-align: baseline;\n box-shadow: none;\n outline: none;\n transform: none;\n opacity: 1;\n visibility: visible;\n position: static;\n float: none;\n clear: none;\n overflow: visible;\n white-space: normal;\n text-align: left;\n letter-spacing: normal;\n word-spacing: normal;\n text-transform: none;\n text-shadow: none;\n list-style: none;\n border-radius: 0;\n min-width: auto;\n max-width: none;\n min-height: auto;\n max-height: none;\n width: auto;\n height: auto;\n top: auto;\n right: auto;\n bottom: auto;\n left: auto;\n z-index: auto;\n }\n\n .webring-nav a {\n cursor: pointer;\n }\n ",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 box-sizing: border-box;\n }\n .webring-nav .webring-title {\n margin-bottom: 8px;\n margin-top: 0;\n padding: 0;\n font-size: inherit;\n line-height: normal;\n }\n .webring-nav nav {\n display: flex;\n gap: 10px;\n width: 100%;\n justify-content: center;\n align-items: center;\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n .webring-nav .webring-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 6px 12px;\n text-decoration: none;\n box-sizing: border-box;\n margin: 0;\n }\n .webring-nav .webring-btn.random-btn {\n padding: 6px 8px;\n }\n .webring-nav nav .logo-only {\n padding: 8px 3px 6px 3px;\n margin: 0;\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-nav .webring-logo-inline {\n display: inline-block;\n vertical-align: middle;\n margin-right: 6px;\n margin-top: 1px;\n margin-bottom: 0;\n margin-left: 0;\n }\n .webring-nav[data-button-text="false"] .prev-btn {\n padding-top: 5px;\n padding-right: 12.5px;\n }\n .webring-nav[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-nav .webring-title {\n font-weight: 600;\n }\n .webring-nav .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-nav .webring-btn:hover {\n background-color: #000000;\n color: #ffffff;\n }\n .webring-nav .webring-btn:focus {\n outline: none;\n background-color: transparent;\n color: #000000;\n }\n .webring-nav .webring-btn:active {\n border-color: #000000;\n background-color: #000000;\n color: #ffffff;\n }\n '});let s=!1;n()}();
1
+ !function(){function n(n){function t(n){let t=document.getElementById(e.STYLE_ID);switch(t||(t=document.createElement("style"),t.id=e.STYLE_ID,document.head.insertBefore(t,document.head.firstChild)),n){case"none":t.textContent=g.reset;break;case"layout":t.textContent=g.reset+g.layout;break;default:t.textContent=g.reset+g.layout+g.design}}const r=n||document.currentScript||document.getElementsByTagName("script")[document.getElementsByTagName("script").length-1];if(!r)return;const i=r.getAttribute("data-member-uid"),s=r.getAttribute("data-widget-type")??e.DEFAULT_TYPE,c=r.getAttribute("data-target-id")??e.DEFAULT_TARGET_ID,w="false"!==r.getAttribute("data-button-text"),f=r.getAttribute("data-styles")??e.DEFAULT_STYLE_TYPE,m=e.VALID_STYLE_TYPES.includes(f)?f:e.DEFAULT_STYLE_TYPE,v=r.getAttribute("data-theme")??e.DEFAULT_THEME,x=e.VALID_THEMES.includes(v)?v:e.DEFAULT_THEME,p=r.getAttribute("data-prev-text"),u=r.getAttribute("data-random-text"),h=r.getAttribute("data-next-text"),E=r.getAttribute("data-widget-text");if(!i)return void console.error("Webring Widget: Missing data-member-uid attribute on script tag.");if(!e.VALID_TYPES.includes(s))return void console.error(`Webring Widget: Invalid widget type "${s}". Valid types: ${e.VALID_TYPES.join(", ")}`);const y=r.getAttribute("src"),T=new URL(y,window.location.href).origin,_=function(){const n=document.getElementById(c);if(!n)return void console.error(`Webring Widget: No element with id "${c}" found.`);l||(t(m),l=!0);const e=document.createDocumentFragment(),r=document.createElement("div");r.className="webring-nav",r.setAttribute("data-widget-type",s),r.setAttribute("data-button-text",w.toString()),r.setAttribute("data-theme",x);const g={prev:b.prev.text_enforced?b.prev:p?{...b.prev,text:`\xab ${p}`}:b.prev,random:b.random.text_enforced?b.random:u?{...b.random,text:`${o(20,20,"margin-right: 4px; margin-top: 1px;")} ${u}`}:b.random,next:b.next.text_enforced?b.next:h?{...b.next,text:`${h} \xbb`}:b.next,logoOnly:b.logoOnly},f=d[s];if(f.showTitle){const n=document.createElement("span");n.className="webring-title",n.innerHTML=a.widgetTitle.enforced?a.widgetTitle.default:E??a.widgetTitle.default,r.appendChild(n)}const v=document.createElement("nav");v.className="webring-buttons",f.actions.forEach((n=>{const e=g[n];if("logoOnly"===n){const n=document.createElement("div");return n.className=e.additionalClass,n.innerHTML=e.symbol,void v.appendChild(n)}const t=document.createElement("a");if(t.href=`${T}/webring/${e.path}?source_member_uid=${i}`,t.title=e.title,t.className=`webring-btn ${e.additionalClass}`,"one-way"===s&&f.showLogoInButton&&"next"===n){const n=b.next.text_enforced?a.next.default:h||g.next.text.replace(" \xbb","");t.innerHTML=w?`<span class="webring-logo-inline">${o(20,20)}</span> ${n} \xbb`:`<span class="webring-logo-inline">${o(20,20)}</span> ${e.symbol}`}else t.innerHTML=w?e.text:e.symbol;v.appendChild(t)})),r.appendChild(v),e.appendChild(r),n.innerHTML="",n.appendChild(e)};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",_):_()}const e=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",VALID_THEMES:Object.freeze(["light","dark"]),DEFAULT_THEME:"light"}),t=Object.freeze({prev:{default:"\xab Prev",enforced:!1},random:{default:"Random",enforced:!1},next:{default:"Next \xbb",enforced:!1},widgetTitle:{default:"Webring",enforced:!1}}),r="<<REPLACE_ME_TEXT_DEFAULTS>>",i="<<REPLACE_ME_TEXT_DEFAULTS>>"!==r?"string"==typeof r?JSON.parse(r):r:{},a=Object.freeze(Object.keys(t).reduce(((n,e)=>{const r=i[e];return n[e]={default:r?.default??t[e].default,enforced:r?.enforced??t[e].enforced},n}),{})),o="<<REPLACE_ME_LOGO_SVG_FUNCTION>>",b=Object.freeze({prev:{symbol:"\xab",text:`\xab ${a.prev.default}`,text_enforced:a.prev.enforced,title:"Previous site",path:"previous",additionalClass:"prev-btn"},random:{symbol:o(23,23),text:`${o(20,20,"margin-right: 4px; margin-top: 1px;")} ${a.random.default}`,text_enforced:a.random.enforced,title:"Random site",path:"random",additionalClass:"random-btn"},next:{symbol:"\xbb",text:`${a.next.default} \xbb`,text_enforced:a.next.enforced,title:"Next site",path:"next",additionalClass:"next-btn"},logoOnly:{symbol:o(23,23),text:o(23,23),path:"",additionalClass:"logo-only"}}),d=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"]}}),g=Object.freeze({reset:"\n .webring-nav,\n .webring-nav div,\n .webring-nav span,\n .webring-nav nav,\n .webring-nav a,\n .webring-nav div::before,\n .webring-nav span::before,\n .webring-nav nav::before,\n .webring-nav a::before,\n .webring-nav div::after,\n .webring-nav span::after,\n .webring-nav nav::after,\n .webring-nav a::after {\n all: initial;\n box-sizing: border-box;\n }\n\n .webring-nav,\n .webring-nav div,\n .webring-nav span,\n .webring-nav nav,\n .webring-nav a {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n color: inherit;\n }\n\n .webring-nav div,\n .webring-nav span,\n .webring-nav nav,\n .webring-nav a {\n display: initial;\n margin: 0;\n padding: 0;\n border: none;\n background: none;\n text-decoration: none;\n color: inherit;\n font-weight: inherit;\n font-size: inherit;\n line-height: inherit;\n vertical-align: baseline;\n box-shadow: none;\n outline: none;\n transform: none;\n opacity: 1;\n visibility: visible;\n position: static;\n float: none;\n clear: none;\n overflow: visible;\n white-space: normal;\n text-align: left;\n letter-spacing: normal;\n word-spacing: normal;\n text-transform: none;\n text-shadow: none;\n list-style: none;\n border-radius: 0;\n min-width: auto;\n max-width: none;\n min-height: auto;\n max-height: none;\n width: auto;\n height: auto;\n top: auto;\n right: auto;\n bottom: auto;\n left: auto;\n z-index: auto;\n }\n\n .webring-nav a {\n cursor: pointer;\n }\n ",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 box-sizing: border-box;\n }\n .webring-nav .webring-title {\n margin: 0 0 8px 0;\n padding: 0;\n font-size: inherit;\n line-height: normal;\n }\n .webring-nav nav {\n display: flex;\n gap: 10px;\n width: 100%;\n justify-content: center;\n align-items: center;\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n .webring-nav .webring-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 6px 12px;\n text-decoration: none;\n box-sizing: border-box;\n margin: 0;\n }\n .webring-nav .webring-btn.random-btn {\n padding: 6px 8px;\n }\n .webring-nav nav .logo-only {\n padding: 8px 3px 6px 3px;\n margin: 0;\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-nav .webring-logo-inline {\n display: inline-block;\n vertical-align: middle;\n margin: 1px 6px 0 0;\n }\n .webring-nav[data-button-text="false"] .prev-btn {\n padding: 5px 12.5px 6px 12px;\n }\n .webring-nav[data-button-text="false"] .next-btn {\n padding: 5px 12px 6px 12.5px;\n }\n ',design:'\n /* CSS Custom Properties for easy theming */\n .webring-nav {\n /* Internal variables that use public API with fallbacks */\n --webring-bg-color: var(--webring-theme-bg);\n --webring-border-color: var(--webring-theme-border);\n --webring-text-color: var(--webring-theme-text);\n --webring-btn-bg: var(--webring-theme-btn-bg);\n --webring-btn-border: var(--webring-theme-btn-border);\n --webring-btn-text: var(--webring-theme-btn-text);\n --webring-btn-hover-bg: var(--webring-theme-btn-hover-bg);\n --webring-btn-hover-text: var(--webring-theme-btn-hover-text);\n }\n\n /* Common theme styles using CSS variables */\n .webring-nav {\n background-color: var(--webring-bg-color);\n }\n .webring-nav[data-widget-type="full"] {\n border: 2.5px solid var(--webring-border-color);\n }\n .webring-nav .webring-title {\n font-weight: 600;\n color: var(--webring-text-color);\n }\n .webring-nav .webring-btn {\n text-wrap: nowrap;\n font-weight: 600;\n color: var(--webring-btn-text);\n background-color: var(--webring-btn-bg);\n border: 2.5px solid var(--webring-btn-border);\n transition: background-color 0.2s ease, color 0.2s ease;\n }\n .webring-nav .webring-btn:focus {\n outline: none;\n background-color: transparent;\n color: var(--webring-btn-text);\n }\n .webring-nav .webring-btn:hover,\n .webring-nav .webring-btn:active {\n background-color: var(--webring-btn-hover-bg);\n color: var(--webring-btn-hover-text);\n }\n .webring-nav svg {\n fill: currentColor;\n }\n\n /* Light theme defaults - these have lower specificity than member\'s rules */\n .webring-nav {\n --webring-theme-bg: #ffffff;\n --webring-theme-border: #000000;\n --webring-theme-text: #000000;\n --webring-theme-btn-bg: #ffffff;\n --webring-theme-btn-border: #000000;\n --webring-theme-btn-text: #000000;\n --webring-theme-btn-hover-bg: #000000;\n --webring-theme-btn-hover-text: #ffffff;\n }\n\n /* Dark theme overrides */\n .webring-nav[data-theme="dark"] {\n --webring-theme-bg: #000000;\n --webring-theme-border: #ffffff;\n --webring-theme-text: #ffffff;\n --webring-theme-btn-bg: #000000;\n --webring-theme-btn-border: #ffffff;\n --webring-theme-btn-text: #ffffff;\n --webring-theme-btn-hover-bg: #ffffff;\n --webring-theme-btn-hover-text: #000000;\n }\n '});let l=!1;n()}();
@@ -31,9 +31,9 @@ module Webring
31
31
  # PATCH/PUT /webring/members/1
32
32
  def update
33
33
  if member.update(member_params)
34
- redirect_to admin_panel_member_path(member), notice: 'Member was successfully updated.'
34
+ update_success_response
35
35
  else
36
- render :edit, status: :unprocessable_entity
36
+ update_error_response
37
37
  end
38
38
  end
39
39
 
@@ -46,6 +46,20 @@ module Webring
46
46
 
47
47
  private
48
48
 
49
+ def update_success_response
50
+ respond_to do |format|
51
+ format.html { redirect_to admin_panel_member_path(member), notice: 'Member was successfully updated.' }
52
+ format.json { head :ok }
53
+ end
54
+ end
55
+
56
+ def update_error_response
57
+ respond_to do |format|
58
+ format.html { render :edit, status: :unprocessable_entity }
59
+ format.json { render json: { status: 'error', errors: member.errors }, status: :unprocessable_entity }
60
+ end
61
+ end
62
+
49
63
  def members
50
64
  @members ||= Member.all.order(created_at: :desc)
51
65
  end
@@ -55,7 +69,7 @@ module Webring
55
69
  end
56
70
 
57
71
  def member_params
58
- params.require(:member).permit(:name, :url, :description)
72
+ params.require(:member).permit(:name, :url, :description, :status)
59
73
  end
60
74
  end
61
75
  end
@@ -35,7 +35,7 @@ module Webring
35
35
  end
36
36
 
37
37
  def find_member
38
- @member = Webring::Member.find_by(uid: permitted_params[:source_member_uid])
38
+ @member = Webring::Member.active.find_by(uid: permitted_params[:source_member_uid])
39
39
  return if @member
40
40
 
41
41
  render_member_not_found
@@ -43,13 +43,13 @@ module Webring
43
43
 
44
44
  def check_member_exists
45
45
  member_uid = permitted_params[:source_member_uid]
46
- return unless member_uid.present? && !Webring::Member.exists?(uid: member_uid)
46
+ return unless member_uid.present? && !Webring::Member.active.exists?(uid: member_uid)
47
47
 
48
48
  render_member_not_found
49
49
  end
50
50
 
51
51
  def ensure_members_exist
52
- return if Webring::Member.exists?
52
+ return if Webring::Member.active.exists?
53
53
 
54
54
  render plain: 'No members in the webring', status: :not_found
55
55
  end
@@ -4,55 +4,57 @@ module Webring
4
4
  # Find the next member in the webring after the current one
5
5
  # If current member is the last, return the first member (ring concept)
6
6
  def find_next(source_member_uid)
7
- source_member = find_by(uid: source_member_uid)
8
- return first_member_by_creation unless source_member
7
+ source_member = active.find_by(uid: source_member_uid)
8
+ return first_active_member_by_creation unless source_member
9
9
 
10
- find_next_member(source_member) || first_member_by_creation
10
+ find_next_active_member(source_member) || first_active_member_by_creation
11
11
  end
12
12
 
13
13
  # Find the previous member in the webring before the current one
14
14
  # If current member is the first, return the last member (ring concept)
15
15
  def find_previous(source_member_uid)
16
- source_member = find_by(uid: source_member_uid)
17
- return last_member_by_creation unless source_member
16
+ source_member = active.find_by(uid: source_member_uid)
17
+ return last_active_member_by_creation unless source_member
18
18
 
19
- find_previous_member(source_member) || last_member_by_creation
19
+ find_previous_active_member(source_member) || last_active_member_by_creation
20
20
  end
21
21
 
22
22
  # Find a random member, excluding the current one if provided
23
23
  # If current member is the only one, return it
24
24
  def find_random(source_member_uid: nil)
25
- return order('RANDOM()').first if source_member_uid.blank?
25
+ return active.order('RANDOM()').first if source_member_uid.blank?
26
26
 
27
27
  # Use exists? check to avoid loading records when not needed
28
- excluded_scope = where.not(uid: source_member_uid)
28
+ excluded_scope = active.where.not(uid: source_member_uid)
29
29
 
30
30
  if excluded_scope.exists?
31
31
  excluded_scope.order('RANDOM()').first
32
32
  else
33
- # if only one member exists (the current one), return it
34
- find_by(uid: source_member_uid)
33
+ # if only one active member exists (the current one), return it
34
+ active.find_by(uid: source_member_uid)
35
35
  end
36
36
  end
37
37
 
38
38
  private
39
39
 
40
- def first_member_by_creation
41
- order(created_at: :asc).first
40
+ def first_active_member_by_creation
41
+ active.order(created_at: :asc).first
42
42
  end
43
43
 
44
- def last_member_by_creation
45
- order(created_at: :desc).first
44
+ def last_active_member_by_creation
45
+ active.order(created_at: :desc).first
46
46
  end
47
47
 
48
- def find_next_member(source_member)
49
- where('created_at > ?', source_member.created_at)
48
+ def find_next_active_member(source_member)
49
+ active
50
+ .where('created_at > ?', source_member.created_at)
50
51
  .order(created_at: :asc)
51
52
  .first
52
53
  end
53
54
 
54
- def find_previous_member(source_member)
55
- where('created_at < ?', source_member.created_at)
55
+ def find_previous_active_member(source_member)
56
+ active
57
+ .where('created_at < ?', source_member.created_at)
56
58
  .order(created_at: :desc)
57
59
  .first
58
60
  end
@@ -4,6 +4,9 @@ module Webring
4
4
 
5
5
  UID_LENGTH = 16 # 32-character hex string
6
6
 
7
+ # :active?, :disabled? -- :active!, :disabled!
8
+ enum :status, { active: 0, disabled: 1 }
9
+
7
10
  self.filter_attributes += %i[uid]
8
11
 
9
12
  validates :url, presence: true, uniqueness: true
@@ -5,6 +5,7 @@ class CreateWebringMembers < ActiveRecord::Migration<%= "[#{Rails::VERSION::MAJO
5
5
  t.string :name, null: false
6
6
  t.string :url, null: false
7
7
  t.text :description, null: false
8
+ t.integer :status, null: false, default: 0
8
9
 
9
10
  t.index :uid, unique: true
10
11
  t.index :name, unique: true
@@ -4,6 +4,9 @@ module Webring
4
4
 
5
5
  UID_LENGTH = 16 # 32-character hex string
6
6
 
7
+ # :active?, :disabled? -- :active!, :disabled!
8
+ enum :status, { active: 0, disabled: 1 }
9
+
7
10
  self.filter_attributes += %i[uid]
8
11
 
9
12
  validates :url, presence: true, uniqueness: true
@@ -35,7 +35,7 @@ module Webring
35
35
  end
36
36
 
37
37
  def find_member
38
- @member = Webring::Member.find_by(uid: permitted_params[:source_member_uid])
38
+ @member = Webring::Member.active.find_by(uid: permitted_params[:source_member_uid])
39
39
  return if @member
40
40
 
41
41
  render_member_not_found
@@ -43,13 +43,13 @@ module Webring
43
43
 
44
44
  def check_member_exists
45
45
  member_uid = permitted_params[:source_member_uid]
46
- return unless member_uid.present? && !Webring::Member.exists?(uid: member_uid)
46
+ return unless member_uid.present? && !Webring::Member.active.exists?(uid: member_uid)
47
47
 
48
48
  render_member_not_found
49
49
  end
50
50
 
51
51
  def ensure_members_exist
52
- return if Webring::Member.exists?
52
+ return if Webring::Member.active.exists?
53
53
 
54
54
  render plain: 'No members in the webring', status: :not_found
55
55
  end
@@ -57,5 +57,12 @@ module Webring
57
57
  def render_member_not_found
58
58
  render plain: 'Member not found', status: :not_found
59
59
  end
60
+
61
+ def set_cors_headers
62
+ headers['Access-Control-Allow-Origin'] = '*'
63
+ headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
64
+ headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept'
65
+ headers['Access-Control-Max-Age'] = '86400'
66
+ end
60
67
  end
61
68
  end
@@ -1,3 +1,3 @@
1
1
  module Webring
2
- VERSION = '1.4.4'.freeze
2
+ VERSION = '1.6.0'.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.4.4
4
+ version: 1.6.0
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-10 00:00:00.000000000 Z
11
+ date: 2025-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails