webring-rails 1.2.1 → 1.3.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: 9b272723dd4b551c5d77e195df9a4144b260c92b9a0f284d7bf2935e0b0e3a49
4
- data.tar.gz: 110b576926898690377edd1c617351b588ce0bb0804db33a51584e0abe2cf9da
3
+ metadata.gz: 919f405578a35bb65238570b3e7bdc650e2685ac435dde5f40a814828dfbc491
4
+ data.tar.gz: db6dd54f0c3138e534c4dfe7ec3bce2075e1d06e978ca21865ef099ac253256f
5
5
  SHA512:
6
- metadata.gz: b60a09f995bcdeb4880a7f06fdded5dca653a034161f1da82a03bf98e960b3203158c63519808296a91ed10717cd1a502471f9f9d615f6b591a5501654dae276
7
- data.tar.gz: 15b9c7a4e3d31462a779303269c313c5bffe6605ae94216ff3b3e118d0616abef0518883ac69b149b4c6a3de9aa210071f64054916260e60be5886e0bca40d00
6
+ metadata.gz: 7b89d911a5b51898e9a2ac0f8c157fd5d1aac5e54a885918a6bdf76a3758721501d6040c97fe4df93a692d373806df21dcff26456d7546ceb23b935c77b723cc
7
+ data.tar.gz: 6072b3a3573f4c9433f11472eba97590dc7cb4af7c53db2b5b476d3590787d99d9a98b64f1fee64787bdc6c6190b4308741637ce4f8af1fff901e653e241a75b
@@ -2,11 +2,11 @@
2
2
  * Webring Navigation Widget
3
3
  *
4
4
  * Usage:
5
- * <script src="https://yourhub.com/webring/widget.js" data-member-uid="YOUR_MEMBER_UID" data-widget-type="full"></script>
5
+ * <script src="https://yourhub.com/widget.js" data-member-uid="YOUR_MEMBER_UID" data-widget-type="full"></script>
6
6
  * <div id="webring-widget"></div>
7
7
  *
8
8
  * For multiple widgets on same page:
9
- * <script src="https://yourhub.com/webring/widget.js" data-member-uid="YOUR_MEMBER_UID" data-widget-type="full" data-target-id="custom-widget-id"></script>
9
+ * <script src="https://yourhub.com/widget.js" data-member-uid="YOUR_MEMBER_UID" data-widget-type="full" data-target-id="custom-widget-id"></script>
10
10
  * <div id="custom-widget-id"></div>
11
11
  *
12
12
  * Widget Types:
@@ -14,6 +14,7 @@
14
14
  * - no-text: back btn, random btn, forward btn (no text)
15
15
  * - two-way: back btn, forward btn (no random)
16
16
  * - one-way: forward btn only
17
+ * - random-only: random btn only
17
18
  *
18
19
  * Additional Options:
19
20
  * - data-button-text="true|false": If true, buttons will show text labels. If false, only symbols are shown. Default: true
@@ -26,7 +27,7 @@
26
27
  (function() {
27
28
  // Configuration constants
28
29
  const WIDGET_CONFIG = {
29
- VALID_TYPES: ['full', 'no-text', 'two-way', 'one-way'],
30
+ VALID_TYPES: ['full', 'no-text', 'two-way', 'one-way', 'random-only'],
30
31
  DEFAULT_TYPE: 'full',
31
32
  DEFAULT_TARGET_ID: 'webring-widget',
32
33
  STYLE_ID: 'webring-widget-styles',
@@ -34,6 +35,8 @@
34
35
  DEFAULT_STYLE_TYPE: 'full'
35
36
  };
36
37
 
38
+ const logoSvg = (width = 20, height = 20, style = "") => `<<REPLACE_ME_LOGO_SVG>>`;
39
+
37
40
  const NAVIGATION_ACTIONS = {
38
41
  prev: {
39
42
  symbol: '«',
@@ -42,24 +45,32 @@
42
45
  path: 'previous'
43
46
  },
44
47
  random: {
45
- symbol: '⚡',
46
- text: 'Random',
48
+ symbol: logoSvg(22, 22),
49
+ text: `${logoSvg(20, 20, "margin-right: 4px; margin-top: 1px;")} Random`,
47
50
  title: 'Random site',
48
- path: 'random'
51
+ path: 'random',
52
+ additionalClass: 'random-btn'
49
53
  },
50
54
  next: {
51
55
  symbol: '»',
52
56
  text: 'Next »',
53
57
  title: 'Next site',
54
58
  path: 'next'
59
+ },
60
+ logoOnly: {
61
+ symbol: logoSvg(22, 22),
62
+ text: `${logoSvg(20, 20)} Random`,
63
+ title: 'Ruby Webring',
64
+ path: ''
55
65
  }
56
66
  };
57
67
 
58
68
  const WIDGET_TYPE_CONFIG = {
59
69
  'full': { showTitle: true, actions: ['prev', 'random', 'next'] },
60
70
  'no-text': { showTitle: false, actions: ['prev', 'random', 'next'] },
61
- 'two-way': { showTitle: false, actions: ['prev', 'next'] },
62
- 'one-way': { showTitle: false, actions: ['next'] }
71
+ 'two-way': { showTitle: false, actions: ['prev', 'logoOnly', 'next'], showLogoInMiddle: true },
72
+ 'one-way': { showTitle: false, actions: ['next'], showLogoInButton: true },
73
+ 'random-only': { showTitle: false, actions: ['random'] }
63
74
  };
64
75
 
65
76
  // Define styles outside the function to avoid duplication
@@ -83,6 +94,7 @@
83
94
  gap: 10px;
84
95
  width: 100%;
85
96
  justify-content: center;
97
+ align-items: center;
86
98
  }
87
99
  .webring-nav a.webring-btn {
88
100
  display: flex;
@@ -91,14 +103,23 @@
91
103
  padding: 6px 12px;
92
104
  text-decoration: none;
93
105
  }
106
+ .webring-nav a.webring-btn.random-btn {
107
+ padding: 6px 9px 6px 9px;
108
+ }
109
+ .webring-nav .logo-only {
110
+ padding: 8px 3px 6px 3px;
111
+ }
94
112
  .webring-nav[data-widget-type="no-text"] {
95
113
  padding: 8px 10px;
96
114
  }
97
115
  .webring-nav[data-widget-type="one-way"] {
98
116
  max-width: 200px;
99
117
  }
100
- .webring-nav[data-widget-type="one-way"] nav {
101
- justify-content: center;
118
+ .webring-logo-inline {
119
+ display: inline-block;
120
+ vertical-align: middle;
121
+ margin-right: 6px;
122
+ margin-top: 1px;
102
123
  }
103
124
  `,
104
125
  design: `
@@ -175,13 +196,28 @@
175
196
  const config = WIDGET_TYPE_CONFIG[widgetType];
176
197
  const linkElements = config.actions.map(action => {
177
198
  const actionConfig = NAVIGATION_ACTIONS[action];
199
+
200
+ // Logo-only block
201
+ if (action === 'logoOnly') {
202
+ return `<div class="logo-only">${logoSvg(22, 22)}</div>`;
203
+ }
204
+
178
205
  const url = `${baseUrl}/webring/${actionConfig.path}?source_member_uid=${memberUid}`;
179
- const label = buttonText ? actionConfig.text : actionConfig.symbol;
180
- return `<a href="${url}" title="${actionConfig.title}" class="webring-btn">${label}</a>`;
206
+ let label = buttonText ? actionConfig.text : actionConfig.symbol;
207
+ const btnClass = `webring-btn${actionConfig.additionalClass ? ` ${actionConfig.additionalClass}` : ''}`;
208
+
209
+ // One-way type case
210
+ if (widgetType === 'one-way' && config.showLogoInButton && action === 'next') {
211
+ label = buttonText
212
+ ? `<span class="webring-logo-inline">${logoSvg(20, 20)}</span> ${actionConfig.text}`
213
+ : `<span class="webring-logo-inline">${logoSvg(20, 20)}</span> ${actionConfig.symbol}`;
214
+ }
215
+
216
+ return `<a href="${url}" title="${actionConfig.title}" class="${btnClass}">${label}</a>`;
181
217
  }).join('\n ');
182
218
 
183
219
  // Create widget HTML
184
- const title = config.showTitle ? '<span class="webring-title">Webring</span>' : '';
220
+ const title = config.showTitle ? '<span class="webring-title">Ruby Webring</span>' : '';
185
221
  container.innerHTML = `
186
222
  <div class="webring-nav" data-widget-type="${widgetType}">
187
223
  ${title}
@@ -1,20 +1,26 @@
1
1
  module Webring
2
2
  class WidgetController < ::ApplicationController
3
3
  # Disable CSRF protection for widget.js as it needs to be loaded from other domains
4
- skip_forgery_protection only: :show
4
+ skip_forgery_protection
5
5
 
6
6
  # Set CORS headers for the widget
7
- before_action :set_cors_headers, only: :show
7
+ before_action :set_cors_headers
8
8
 
9
9
  # Serve the webring navigation widget JavaScript
10
- # GET /webring/widget.js
10
+ # GET /widget.js
11
11
  def show
12
12
  respond_to do |format|
13
13
  format.js do
14
14
  response.headers['Content-Type'] = 'application/javascript'
15
15
 
16
- # Serve the JavaScript file from the engine's assets
17
- render file: Webring::Engine.root.join('app/assets/javascripts/webring/widget.js')
16
+ # Take the JavaScript file from the engine's assets and replace the customizable Logo SVG
17
+ widget_js =
18
+ Webring::Engine
19
+ .root.join('app/assets/javascripts/webring/widget.js')
20
+ .read
21
+ .gsub('<<REPLACE_ME_LOGO_SVG>>', logo_svg)
22
+
23
+ render js: widget_js
18
24
  end
19
25
  end
20
26
  end
@@ -27,5 +33,14 @@ module Webring
27
33
  response.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept'
28
34
  response.headers['Access-Control-Max-Age'] = '86400'
29
35
  end
36
+
37
+ # should include `${width}`, `${height}`, `${style}` in order to be customizable
38
+ def logo_svg
39
+ <<~SVG
40
+ <svg width="${width}" height="${height}" style="${style}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
41
+ <path d="M13 3L6 14H12L11 21L18 10H12L13 3Z" fill="currentColor"/>
42
+ </svg>
43
+ SVG
44
+ end
30
45
  end
31
46
  end
@@ -4,6 +4,8 @@ module Webring
4
4
 
5
5
  UID_LENGTH = 16 # 32-character hex string
6
6
 
7
+ self.filter_attributes += %i[uid]
8
+
7
9
  validates :url, presence: true, uniqueness: true
8
10
  validates :name, presence: true, uniqueness: true
9
11
  validates :uid, presence: true, uniqueness: true, length: { is: UID_LENGTH * 2 }
data/config/routes.rb CHANGED
@@ -2,7 +2,6 @@ Webring::Engine.routes.draw do
2
2
  get 'next', to: 'navigation#next'
3
3
  get 'previous', to: 'navigation#previous'
4
4
  get 'random', to: 'navigation#random'
5
- get 'widget.js', to: 'widget#show', format: 'js', as: :widget
6
5
 
7
6
  root to: 'navigation#random'
8
7
  end
@@ -0,0 +1,47 @@
1
+ require_relative '../shared/route_injector'
2
+
3
+ module Webring
4
+ module Generators
5
+ # @description The CustomWidgetControllerGenerator creates a controller to handle webring custom widgets
6
+ # This generator creates both the controller file and adds the required routes
7
+ #
8
+ # @usage Run: rails generate webring:custom_widget_controller
9
+ #
10
+ # @example The generated controller provides an endpoint for embedding the widget via the <script> tag:
11
+ # # GET /widget.js - Get the widget's code for a script tag
12
+ #
13
+ # @note This generator should be run after installing the Webring engine
14
+ class CustomWidgetControllerGenerator < Rails::Generators::Base
15
+ include Shared::RouteInjector
16
+
17
+ source_root File.expand_path('templates', __dir__)
18
+
19
+ desc 'Creates a Webring::CustomWidgetController and necessary routes for custom widget'
20
+
21
+ # Creates the CustomWidgetController file based on the template
22
+ def create_controller_file
23
+ template 'custom_widget_controller.rb', 'app/controllers/webring/custom_widget_controller.rb'
24
+ end
25
+
26
+ # Adds custom widget routes to the application's routes.rb file
27
+ # These routes are used to create new custom widgets
28
+ def create_custom_widget_routes
29
+ routes_file = 'config/routes.rb'
30
+ widget_route = "get 'widget.js', to: 'widget#show', format: 'js', as: :widget"
31
+ new_widget_route = "get 'widget.js', to: 'custom_widget#show', format: 'js', as: :widget"
32
+
33
+ if File.read(routes_file).include?(widget_route)
34
+ gsub_file routes_file, widget_route, new_widget_route
35
+ else
36
+ route_content = <<~ROUTE
37
+ scope module: 'webring' do
38
+ get 'widget.js', to: 'custom_widget#show', format: 'js', as: :widget
39
+ end
40
+ ROUTE
41
+
42
+ inject_webring_routes(route_content)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,13 @@
1
+ module Webring
2
+ class CustomWidgetController < Webring::WidgetController
3
+ private
4
+
5
+ # should include `${width}`, `${height}`, `${style}` in order to be customizable
6
+ # remove the method or call `super` to use the default logo
7
+ def logo_svg
8
+ <<~SVG
9
+ Add your custom logo SVG here
10
+ SVG
11
+ end
12
+ end
13
+ end
@@ -15,14 +15,13 @@ module Webring
15
15
 
16
16
  desc 'Creates Webring routes and mounts the engine in your application.'
17
17
 
18
- # Adds the engine mount point to the application's routes.rb file
19
- # @return [void]
18
+ # Adds the engine mount point and widget route to the application's routes.rb file
20
19
  def add_webring_routes
20
+ route "scope module: 'webring' do\n get 'widget.js', to: 'widget#show', format: 'js', as: :widget\nend\n\n"
21
21
  route "mount Webring::Engine => '/webring', as: 'webring'\n\n"
22
22
  end
23
23
 
24
24
  # Displays the README with next steps after installation
25
- # @return [void]
26
25
  def show_readme
27
26
  readme 'AFTER_INSTALL' if behavior == :invoke
28
27
  end
@@ -2,7 +2,7 @@
2
2
  Webring Engine Has Been Successfully Installed!
3
3
  ==============================================
4
4
 
5
- The Webring engine has been mounted at '/webring' in your application. Below are
5
+ The Webring engine has been mounted at '/webring' in your application as well as the widget route. Below are
6
6
  the next steps to complete the webring setup and customization options.
7
7
 
8
8
  1. Configure Your Members Model
@@ -24,7 +24,7 @@ After generating the model, run the migration:
24
24
  --------------------------------------------
25
25
  Generate a navigation controller to handle navigation between webring members:
26
26
 
27
- $ rails generate webring:controller
27
+ $ rails generate webring:navigation_controller
28
28
 
29
29
  This will:
30
30
  - Create the Webring::NavigationController in app/controllers/webring/navigation_controller.rb
@@ -50,7 +50,7 @@ After generating the model, run the migration:
50
50
 
51
51
  Then, optionally generate a controller to handle membership requests:
52
52
 
53
- $ rails generate webring:controller:membership_requests
53
+ $ rails generate webring:membership_requests_controller
54
54
 
55
55
  4. Customize Your Routes [Optional]
56
56
  ----------------------------------
@@ -80,3 +80,13 @@ You can add members to your webring using the Webring::Member model:
80
80
  Webring::Member.create(name: 'Example Site', url: 'https://example.com')
81
81
 
82
82
  Consider building an admin interface to manage your webring members.
83
+
84
+ 7. Customize the Widget
85
+ -----------------------
86
+ You can customize the widget's logo or the widget's server controller by adding a customizable widget controller.
87
+
88
+ $ rails generate webring:custom_widget_controller
89
+
90
+ This will:
91
+ - Create a Webring::CustomWidgetController in app/controllers/webring/custom_widget_controller.rb
92
+ - Replace the default widget route with the customizable one or add the customizable one if the default one is not present
@@ -26,13 +26,11 @@ module Webring
26
26
  desc 'Creates a Webring::Member model and necessary migration for storing webring members'
27
27
 
28
28
  # Creates a migration file to create the webring_members table
29
- # @return [void]
30
29
  def create_migration_file
31
30
  migration_template 'migration.rb', 'db/migrate/create_webring_members.rb'
32
31
  end
33
32
 
34
33
  # Creates the Member model file based on the template
35
- # @return [void]
36
34
  def create_model_file
37
35
  template 'model.rb', 'app/models/webring/member.rb'
38
36
  end
@@ -47,7 +45,6 @@ module Webring
47
45
  end
48
46
 
49
47
  # Displays the README with next steps after installation
50
- # @return [void]
51
48
  def show_readme
52
49
  readme 'AFTER_INSTALL' if behavior == :invoke
53
50
  end
@@ -4,6 +4,8 @@ module Webring
4
4
 
5
5
  UID_LENGTH = 16 # 32-character hex string
6
6
 
7
+ self.filter_attributes += %i[uid]
8
+
7
9
  validates :url, presence: true, uniqueness: true
8
10
  validates :name, presence: true, uniqueness: true
9
11
  validates :uid, presence: true, uniqueness: true, length: { is: UID_LENGTH * 2 }
@@ -26,30 +26,38 @@ module Webring
26
26
  desc 'Creates a Webring::MembershipRequest model and necessary migration for storing webring membership requests'
27
27
 
28
28
  # Creates a migration file to create the webring_membership_requests table
29
- # @return [void]
30
29
  def create_migration_file
31
30
  migration_template 'migration.rb', 'db/migrate/create_webring_membership_requests.rb'
32
31
  migration_template 'relations_migration.rb', 'db/migrate/add_membership_request_to_webring_members.rb'
33
32
  end
34
33
 
35
34
  # Creates the MembershipRequest model file based on the template
36
- # @return [void]
37
35
  def create_model_file
38
36
  template 'model.rb', 'app/models/webring/membership_request.rb'
39
37
  end
40
38
 
41
39
  # Injects the belongs_to relationship into the Member model
42
- # @return [void]
43
40
  def inject_into_member_model
44
41
  member_model_path = 'app/models/webring/member.rb'
45
42
 
43
+ inject_conditions = [
44
+ { line: 'UID_LENGTH =', inject_options: { after: /UID_LENGTH =.*\n\n/ } },
45
+ { line: 'extend Webring::Navigation', inject_options: { after: "extend Webring::Navigation\n\n" } },
46
+ { line: 'class Member < ApplicationRecord', inject_options: { after: "class Member < ApplicationRecord\n" } }
47
+ ]
48
+
46
49
  if File.exist?(member_model_path)
47
- inject_into_file member_model_path, after: "class Member\n" do
48
- " belongs_to :membership_request,\n" \
49
- " class_name: 'Webring::MembershipRequest',\n" \
50
- " foreign_key: :webring_membership_request_id,\n" \
51
- " optional: true,\n" \
52
- " inverse_of: :member\n"
50
+ content = File.read(member_model_path)
51
+ options = inject_conditions.find { |condition| content.include?(condition[:line]) }&.fetch(:inject_options)
52
+ return if options.nil? || options.empty?
53
+
54
+ inject_into_file member_model_path, **options do
55
+ " belongs_to :membership_request,\n" \
56
+ " class_name: 'Webring::MembershipRequest',\n" \
57
+ " foreign_key: :webring_membership_request_id,\n" \
58
+ " optional: true,\n" \
59
+ " inverse_of: :member\n" \
60
+ "\n"
53
61
  end
54
62
  end
55
63
  end
@@ -64,7 +72,6 @@ module Webring
64
72
  end
65
73
 
66
74
  # Displays the README with next steps after installation
67
- # @return [void]
68
75
  def show_readme
69
76
  readme 'AFTER_INSTALL' if behavior == :invoke
70
77
  end
@@ -21,14 +21,12 @@ module Webring
21
21
  desc 'Creates a Webring::MembershipRequestsController and necessary routes for membership requests'
22
22
 
23
23
  # Creates the MembershipRequestsController file based on the template
24
- # @return [void]
25
24
  def create_controller_file
26
25
  template 'membership_requests_controller.rb', 'app/controllers/webring/membership_requests_controller.rb'
27
26
  end
28
27
 
29
28
  # Adds membership request routes to the application's routes.rb file
30
29
  # These routes are used to create new membership requests
31
- # @return [void]
32
30
  def create_membership_request_routes
33
31
  route_content = <<~ROUTE
34
32
  # Webring membership request routes
@@ -22,14 +22,12 @@ module Webring
22
22
  desc 'Creates a Webring::NavigationController and necessary routes for webring navigation'
23
23
 
24
24
  # Creates the NavigationController file based on the template
25
- # @return [void]
26
25
  def create_controller_file
27
26
  template 'navigation_controller.rb', 'app/controllers/webring/navigation_controller.rb'
28
27
  end
29
28
 
30
29
  # Adds navigation routes to the application's routes.rb file
31
30
  # These routes are used to navigate between webring members
32
- # @return [void]
33
31
  def create_navigation_routes
34
32
  route_content = <<~ROUTE
35
33
  # Webring navigation routes
@@ -20,7 +20,6 @@ module Webring
20
20
  # to the end of the routes file.
21
21
  #
22
22
  # @param route_content [String] The routes to be added
23
- # @return [void]
24
23
  def inject_webring_routes(route_content)
25
24
  cleared_route_content = route_content.gsub(/^/, ' ')
26
25
  routes_file = 'config/routes.rb'
@@ -1,3 +1,3 @@
1
1
  module Webring
2
- VERSION = '1.2.1'.freeze
2
+ VERSION = '1.3.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.2.1
4
+ version: 1.3.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-07 00:00:00.000000000 Z
11
+ date: 2025-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -76,6 +76,8 @@ files:
76
76
  - app/models/webring/membership_request.rb
77
77
  - config/routes.rb
78
78
  - lib/generators/USAGE
79
+ - lib/generators/webring/custom_widget_controller/custom_widget_controller_generator.rb
80
+ - lib/generators/webring/custom_widget_controller/templates/custom_widget_controller.rb
79
81
  - lib/generators/webring/install/install_generator.rb
80
82
  - lib/generators/webring/install/templates/AFTER_INSTALL
81
83
  - lib/generators/webring/member/member_generator.rb