webring-rails 1.7.9 → 1.8.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: 1ea7aa3359bae1b361ffe19ac677b795983c8c9a5f4152b3676ceba5f63f5d01
4
- data.tar.gz: 0fb3e6c8aa1c3f81e61df3c39e3bc19b94bfbb65a081889ef7aa6ee1eaab63f8
3
+ metadata.gz: 7231dcbc5a185d6c345c5d13b387d09aa80dd5acd7962f8c006a9ca7f313a585
4
+ data.tar.gz: 6f377e0a118b9037fcd65f6b015000ff247ca3616e670abc3154dea86389be53
5
5
  SHA512:
6
- metadata.gz: ca8a4215b7baf8888b87a76070feacc381f6f25958a97cea16e833ff6d8fd455346d7676a31d54e7e07ab30474a6ba0931c0001d8cd426b447c427a064f6b0f2
7
- data.tar.gz: 053d4dc8e983ab4d68570c523185bdc078a624e327b69eabce0b34d35d91adc049b0ec67905e5c7aabc4b6b23361c888aae8f642a86be8974afbc4f62e25da76
6
+ metadata.gz: 3ea2a0284502cca42c7f1c9faa6cc7500158fc995e5d7d9f0481ffcc0c674794eff4f58197fa3bc9e24af0732e9fcdaf5bdc9f6c1fc94166ce8d8faa6c34dfa1
7
+ data.tar.gz: 7dbd23f1404bb811c4380fdd37e195fadde5c1212e1261baab4fc94df327350627453d537fa99a1bf452f69598854b3f93f08ff3d4198f9b283ff63d25ab27b3
data/README.md CHANGED
@@ -6,14 +6,15 @@
6
6
 
7
7
  ## Features
8
8
 
9
- - Complete MVC structure for managing webring members
10
- - Circular navigation system between member websites
11
- - UID-based member identification for security
12
- - Embeddable JavaScript widget for easy member site integration
13
- - Customizable widget appearance and behavior
14
- - Optional membership request system for sites to apply to join the webring
15
- - Generators for easy setup and customization
16
- - Extensible architecture for adding custom features
9
+ - **Complete MVC structure** for managing webring members
10
+ - **Circular navigation system** between member websites
11
+ - **One-line route configuration** with `webring_routes` helper (similar to Devise)
12
+ - **UID-based member identification** for security
13
+ - **Embeddable JavaScript widget** for easy member site integration
14
+ - **Customizable widget appearance** and behavior
15
+ - **Optional membership request system** for sites to apply to join the webring
16
+ - **Generators for easy setup** and customization
17
+ - **Extensible architecture** for adding custom features
17
18
 
18
19
  ## Installation
19
20
 
@@ -29,9 +30,6 @@ Run:
29
30
  # Install the gem
30
31
  bundle install
31
32
 
32
- # Run the installation generator
33
- rails generate webring:install
34
-
35
33
  # Create the member model and migrations
36
34
  rails generate webring:member
37
35
 
@@ -39,6 +37,18 @@ rails generate webring:member
39
37
  rails generate webring:navigation_controller
40
38
  ```
41
39
 
40
+ ### Configure Routes
41
+
42
+ Add the `webring_routes` helper to your `config/routes.rb`:
43
+
44
+ ```ruby
45
+ # config/routes.rb
46
+ Rails.application.routes.draw do
47
+ webring_routes
48
+ # ... other routes
49
+ end
50
+ ```
51
+
42
52
  ### Optional Features
43
53
 
44
54
  ```bash
@@ -55,6 +65,33 @@ Finally, run the migrations:
55
65
  rails db:migrate
56
66
  ```
57
67
 
68
+ ## Route Configuration
69
+
70
+ ### Basic Usage
71
+
72
+ ```ruby
73
+ # Default configuration
74
+ webring_routes
75
+ ```
76
+
77
+ ### Custom Controllers
78
+
79
+ ```ruby
80
+ # Use custom controllers
81
+ webring_routes navigation: :custom_navigation, widget: :custom_widget
82
+ ```
83
+
84
+ ### Advanced Configuration
85
+
86
+ ```ruby
87
+ # Full customization
88
+ webring_routes(
89
+ navigation: :my_navigation,
90
+ widget: :my_widget,
91
+ mount_path: '/network'
92
+ )
93
+ ```
94
+
58
95
  ## Documentation
59
96
 
60
97
  For detailed documentation on models, controllers, modules, widget customization, and more advanced installation options, please visit the [Webring Rails Wiki](https://github.com/lstpsche/webring-rails/wiki).
@@ -472,6 +472,21 @@
472
472
 
473
473
  container.innerHTML = '';
474
474
  container.appendChild(fragment);
475
+
476
+ const widgetFetchMetricsPath = "<<REPLACE_ME_WIDGET_FETCH_METRICS_PATH>>";
477
+
478
+ if (widgetFetchMetricsPath && widgetFetchMetricsPath.trim() !== '' && widgetFetchMetricsPath.trim() !== '<<REPLACE_ME_WIDGET_FETCH_METRICS_PATH>>') { // if not empty and not the default value
479
+ // Track widget fetch for metrics
480
+ try {
481
+ fetch(`${baseUrl}${widgetFetchMetricsPath}`, {
482
+ method: 'POST',
483
+ headers: {
484
+ 'Content-Type': 'application/json'
485
+ },
486
+ body: JSON.stringify({ uid: memberUid })
487
+ }).catch(() => {}); // Silently fail if metrics tracking fails - don't break the widget
488
+ } catch (error) {} // Silently fail if fetch is not supported or other errors occur
489
+ }
475
490
  };
476
491
 
477
492
  function applyStyles(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.insertBefore(t,document.head.firstChild)),n){case"none":t.textContent=d.reset;break;case"layout":t.textContent=d.reset+d.layout;break;default:t.textContent=d.reset+d.layout+d.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,f=r.getAttribute("data-target-id")??e.DEFAULT_TARGET_ID,w="false"!==r.getAttribute("data-button-text"),c=r.getAttribute("data-styles")??e.DEFAULT_STYLE_TYPE,m=e.VALID_STYLE_TYPES.includes(c)?c:e.DEFAULT_STYLE_TYPE,x=r.getAttribute("data-theme")??e.DEFAULT_THEME,v=e.VALID_THEMES.includes(x)?x:e.DEFAULT_THEME,p=r.getAttribute("data-prev-text"),h=r.getAttribute("data-random-text"),u=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(f);if(!n)return void console.error(`Webring Widget: No element with id "${f}" 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",v);const d={prev:b.prev.text_enforced?b.prev:p?{...b.prev,text:`\xab ${p}`}:b.prev,random:b.random.text_enforced?b.random:h&&""!==h.trim()?{...b.random,text:`${o(20,20,"margin-right: 4px; margin-top: 1px;")} ${h}`}:b.random,next:b.next.text_enforced?b.next:u?{...b.next,text:`${u} \xbb`}:b.next,logoOnly:b.logoOnly},c=g[s];if(c.showTitle){const n=document.createElement("a");n.href=T,n.className="webring-title",n.innerHTML=a.widgetTitle.enforced?a.widgetTitle.default:E??a.widgetTitle.default,r.appendChild(n)}const x=document.createElement("nav");x.className="webring-buttons",c.actions.forEach((n=>{const e=d[n];if("logoOnly"===n){const n=document.createElement("div");return n.className=e.additionalClass,n.innerHTML=e.symbol,void x.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&&c.showLogoInButton&&"next"===n){const n=b.next.text_enforced?a.next.default:u||d.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"random"!==n||!w||""!==h?.trim()&&void 0!==h?.trim()?t.innerHTML=w?e.text:e.symbol:t.innerHTML=o(23,23,"margin-top: 1px;");x.appendChild(t)})),r.appendChild(x),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: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,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"}}),g=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"]}}),d=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: var(--webring-font-family);\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: 24px;\n text-decoration: none;\n color: inherit;\n cursor: pointer;\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 line-height: 24px;\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-font-family: var(--webring-theme-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif);\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 */\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()}();
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,f="false"!==r.getAttribute("data-button-text"),w=r.getAttribute("data-styles")??e.DEFAULT_STYLE_TYPE,m=e.VALID_STYLE_TYPES.includes(w)?w:e.DEFAULT_STYLE_TYPE,x=r.getAttribute("data-theme")??e.DEFAULT_THEME,p=e.VALID_THEMES.includes(x)?x:e.DEFAULT_THEME,v=r.getAttribute("data-prev-text"),h=r.getAttribute("data-random-text"),u=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 T=r.getAttribute("src"),y=new URL(T,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",f.toString()),r.setAttribute("data-theme",p);const g={prev:b.prev.text_enforced?b.prev:v?{...b.prev,text:`\xab ${v}`}:b.prev,random:b.random.text_enforced?b.random:h&&""!==h.trim()?{...b.random,text:`${o(20,20,"margin-right: 4px; margin-top: 1px;")} ${h}`}:b.random,next:b.next.text_enforced?b.next:u?{...b.next,text:`${u} \xbb`}:b.next,logoOnly:b.logoOnly},w=d[s];if(w.showTitle){const n=document.createElement("a");n.href=y,n.className="webring-title",n.innerHTML=a.widgetTitle.enforced?a.widgetTitle.default:E??a.widgetTitle.default,r.appendChild(n)}const x=document.createElement("nav");x.className="webring-buttons",w.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 x.appendChild(n)}const t=document.createElement("a");if(t.href=`${y}/webring/${e.path}?source_member_uid=${i}`,t.title=e.title,t.className=`webring-btn ${e.additionalClass}`,"one-way"===s&&w.showLogoInButton&&"next"===n){const n=b.next.text_enforced?a.next.default:u||g.next.text.replace(" \xbb","");t.innerHTML=f?`<span class="webring-logo-inline">${o(20,20)}</span> ${n} \xbb`:`<span class="webring-logo-inline">${o(20,20)}</span> ${e.symbol}`}else"random"!==n||!f||""!==h?.trim()&&void 0!==h?.trim()?t.innerHTML=f?e.text:e.symbol:t.innerHTML=o(23,23,"margin-top: 1px;");x.appendChild(t)})),r.appendChild(x),e.appendChild(r),n.innerHTML="",n.appendChild(e);const T="<<REPLACE_ME_WIDGET_FETCH_METRICS_PATH>>";if(T&&""!==T.trim()&&"<<REPLACE_ME_WIDGET_FETCH_METRICS_PATH>>"!==T.trim())try{fetch(`${y}${T}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({uid:i})}).catch((()=>{}))}catch(n){}};"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: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,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: var(--webring-font-family);\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: 24px;\n text-decoration: none;\n color: inherit;\n cursor: pointer;\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 line-height: 24px;\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-font-family: var(--webring-theme-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif);\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 */\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()}();
@@ -38,7 +38,7 @@ module Webring
38
38
 
39
39
  def find_member
40
40
  @member = Webring::Member.active.find_by(uid: permitted_params[:source_member_uid])
41
- return if @member
41
+ return @member if @member
42
42
 
43
43
  render_member_not_found
44
44
  end
@@ -19,6 +19,7 @@ module Webring
19
19
  .read
20
20
  .gsub('"<<REPLACE_ME_LOGO_SVG_FUNCTION>>"', logo_svg_function)
21
21
  .gsub('"<<REPLACE_ME_TEXT_DEFAULTS>>"', JSON.generate(text_defaults))
22
+ .gsub('"<<REPLACE_ME_WIDGET_FETCH_METRICS_PATH>>"', widget_fetch_metrics_path)
22
23
 
23
24
  render js: widget_js
24
25
  end
@@ -50,6 +51,10 @@ module Webring
50
51
  }
51
52
  end
52
53
 
54
+ def widget_fetch_metrics_path
55
+ nil # to be overridden in the custom widget controller
56
+ end
57
+
53
58
  def set_cors_headers
54
59
  response.headers['Access-Control-Allow-Origin'] = '*'
55
60
  response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
@@ -2,6 +2,11 @@ module Webring
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Webring
4
4
 
5
+ # Add routing helpers to Rails router
6
+ initializer 'webring.routing' do
7
+ ActionDispatch::Routing::Mapper.include Webring::Routing
8
+ end
9
+
5
10
  # Add concerns to autoload paths
6
11
  initializer 'webring.autoload_paths' do |app|
7
12
  app.config.autoload_paths += %W[#{config.root}/app/models/concerns]
@@ -0,0 +1,46 @@
1
+ module Webring
2
+ module Routing
3
+ def webring_routes(options = {})
4
+ setup_webring_routes(options)
5
+ setup_webring_metrics(options)
6
+ setup_membership_requests(options)
7
+ end
8
+
9
+ private
10
+
11
+ def setup_webring_routes(options)
12
+ navigation_controller = options[:navigation] || 'navigation'
13
+ widget_controller = options[:widget] || 'widget'
14
+ mount_path = options[:mount_path] || 'webring'
15
+ mount_as = options[:mount_as] || 'webring'
16
+
17
+ # mount Webring::Engine => mount_path, as: mount_as
18
+
19
+ scope module: mount_path, as: mount_as do
20
+ get 'widget.js', to: "#{widget_controller}#show", format: 'js', as: :widget
21
+ get 'webring/next', to: "#{navigation_controller}#next", as: :next
22
+ get 'webring/previous', to: "#{navigation_controller}#previous", as: :previous
23
+ get 'webring/random', to: "#{navigation_controller}#random", as: :random
24
+ end
25
+ end
26
+
27
+ def setup_webring_metrics(options)
28
+ return if options[:skip_metrics]
29
+
30
+ post 'metrics/widget_fetched', to: 'metrics#widget_fetched', as: :widget_fetched_metrics
31
+ options 'metrics/widget_fetched', to: 'metrics#options_widget_fetched'
32
+ end
33
+
34
+ def setup_membership_requests(options)
35
+ return if options[:skip_membership_requests]
36
+
37
+ namespace :webring do
38
+ resources :membership_requests, only: %i[new create] do
39
+ collection do
40
+ get :thank_you
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,3 +1,3 @@
1
1
  module Webring
2
- VERSION = '1.7.9'.freeze
2
+ VERSION = '1.8.0'.freeze
3
3
  end
data/lib/webring-rails.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'webring/version'
2
2
  require 'webring/engine'
3
+ require 'webring/routing'
3
4
 
4
5
  module Webring
5
6
  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.7.9
4
+ version: 1.8.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-13 00:00:00.000000000 Z
11
+ date: 2025-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -90,7 +90,6 @@ files:
90
90
  - app/models/webring/application_record.rb
91
91
  - app/models/webring/member.rb
92
92
  - app/models/webring/membership_request.rb
93
- - config/routes.rb
94
93
  - lib/generators/USAGE
95
94
  - lib/generators/webring/custom_widget_controller/custom_widget_controller_generator.rb
96
95
  - lib/generators/webring/custom_widget_controller/templates/custom_widget_controller.rb
@@ -112,6 +111,7 @@ files:
112
111
  - lib/generators/webring/shared/route_injector.rb
113
112
  - lib/webring-rails.rb
114
113
  - lib/webring/engine.rb
114
+ - lib/webring/routing.rb
115
115
  - lib/webring/version.rb
116
116
  homepage: https://github.com/cybergizer-hq/webring-rails
117
117
  licenses:
data/config/routes.rb DELETED
@@ -1,7 +0,0 @@
1
- Webring::Engine.routes.draw do
2
- get 'next', to: 'navigation#next'
3
- get 'previous', to: 'navigation#previous'
4
- get 'random', to: 'navigation#random'
5
-
6
- root to: 'navigation#random'
7
- end