raddar 0.0.1.pre

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.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +3 -0
  4. data/Rakefile +27 -0
  5. data/app/assets/images/raddar/fallback/avatar/medium.png +0 -0
  6. data/app/assets/images/raddar/fallback/avatar/thumb.png +0 -0
  7. data/app/assets/javascripts/raddar/admin/dashboard.js.coffee +3 -0
  8. data/app/assets/javascripts/raddar/application.js +16 -0
  9. data/app/assets/javascripts/raddar/followerships.js.coffee +3 -0
  10. data/app/assets/javascripts/raddar/home.js.coffee +3 -0
  11. data/app/assets/javascripts/raddar/notifications.js.coffee +16 -0
  12. data/app/assets/javascripts/raddar/users.js.coffee +3 -0
  13. data/app/assets/javascripts/raddar/users/privacies.js.coffee +3 -0
  14. data/app/assets/stylesheets/raddar/admin/dashboard.css.scss +3 -0
  15. data/app/assets/stylesheets/raddar/application.css +14 -0
  16. data/app/assets/stylesheets/raddar/followerships.css.scss +3 -0
  17. data/app/assets/stylesheets/raddar/home.css.scss +3 -0
  18. data/app/assets/stylesheets/raddar/notifications.css.scss +6 -0
  19. data/app/assets/stylesheets/raddar/users.css.scss +3 -0
  20. data/app/assets/stylesheets/raddar/users/privacies.css.scss +3 -0
  21. data/app/controllers/raddar/admin/dashboard_controller.rb +11 -0
  22. data/app/controllers/raddar/admin/users_controller.rb +34 -0
  23. data/app/controllers/raddar/application_controller.rb +17 -0
  24. data/app/controllers/raddar/followerships_controller.rb +60 -0
  25. data/app/controllers/raddar/home_controller.rb +8 -0
  26. data/app/controllers/raddar/notifications_controller.rb +31 -0
  27. data/app/controllers/raddar/users/email_preferences_controller.rb +24 -0
  28. data/app/controllers/raddar/users/external_accounts_controller.rb +18 -0
  29. data/app/controllers/raddar/users/omniauth_callbacks_controller.rb +30 -0
  30. data/app/controllers/raddar/users/passwords_controller.rb +22 -0
  31. data/app/controllers/raddar/users/privacies_controller.rb +25 -0
  32. data/app/controllers/raddar/users/registrations_controller.rb +51 -0
  33. data/app/controllers/raddar/users/sessions_controller.rb +11 -0
  34. data/app/controllers/raddar/users_controller.rb +8 -0
  35. data/app/helpers/raddar/admin/dashboard_helper.rb +4 -0
  36. data/app/helpers/raddar/application_helper.rb +4 -0
  37. data/app/helpers/raddar/notifications_helper.rb +19 -0
  38. data/app/helpers/raddar/users/privacies_helper.rb +4 -0
  39. data/app/mailers/raddar/notification_mailer.rb +16 -0
  40. data/app/models/raddar/external_account.rb +12 -0
  41. data/app/models/raddar/followership.rb +20 -0
  42. data/app/models/raddar/followership_completion.rb +37 -0
  43. data/app/models/raddar/notification.rb +10 -0
  44. data/app/models/raddar/omniauth_completion.rb +83 -0
  45. data/app/models/raddar/role.rb +7 -0
  46. data/app/models/raddar/user.rb +70 -0
  47. data/app/policies/raddar/admin/dashboard_policy.rb +7 -0
  48. data/app/policies/raddar/admin/user_policy.rb +15 -0
  49. data/app/policies/raddar/application_policy.rb +42 -0
  50. data/app/policies/raddar/followership_policy.rb +25 -0
  51. data/app/policies/raddar/notification_policy.rb +21 -0
  52. data/app/policies/raddar/user_policy.rb +17 -0
  53. data/app/uploaders/raddar/avatar_uploader.rb +56 -0
  54. data/app/views/layouts/raddar/_alerts.html.erb +3 -0
  55. data/app/views/layouts/raddar/_navbar.html.erb +22 -0
  56. data/app/views/layouts/raddar/_notifications.html.erb +20 -0
  57. data/app/views/layouts/raddar/_user_menu.html.erb +21 -0
  58. data/app/views/layouts/raddar/application.html.erb +21 -0
  59. data/app/views/layouts/raddar/notification_mailer.html.erb +12 -0
  60. data/app/views/raddar/admin/dashboard/index.html.erb +6 -0
  61. data/app/views/raddar/admin/users/index.html.erb +11 -0
  62. data/app/views/raddar/admin/users/show.html.erb +12 -0
  63. data/app/views/raddar/followerships/followers.html.erb +9 -0
  64. data/app/views/raddar/followerships/following.html.erb +9 -0
  65. data/app/views/raddar/home/index.html.erb +1 -0
  66. data/app/views/raddar/notification_mailer/new_follower.html.erb +11 -0
  67. data/app/views/raddar/notifications/index.html.erb +12 -0
  68. data/app/views/raddar/notifications/index.json.jbuilder +4 -0
  69. data/app/views/raddar/users/confirmations/new.html.erb +16 -0
  70. data/app/views/raddar/users/email_preferences/edit.html.erb +11 -0
  71. data/app/views/raddar/users/external_accounts/index.html.erb +14 -0
  72. data/app/views/raddar/users/mailer/confirmation_instructions.html.erb +5 -0
  73. data/app/views/raddar/users/mailer/reset_password_instructions.html.erb +8 -0
  74. data/app/views/raddar/users/mailer/unlock_instructions.html.erb +7 -0
  75. data/app/views/raddar/users/passwords/change.html.erb +13 -0
  76. data/app/views/raddar/users/passwords/edit.html.erb +19 -0
  77. data/app/views/raddar/users/passwords/new.html.erb +15 -0
  78. data/app/views/raddar/users/privacies/edit.html.erb +12 -0
  79. data/app/views/raddar/users/registrations/destroy.html.erb +13 -0
  80. data/app/views/raddar/users/registrations/edit.html.erb +30 -0
  81. data/app/views/raddar/users/registrations/new.html.erb +16 -0
  82. data/app/views/raddar/users/sessions/new.html.erb +13 -0
  83. data/app/views/raddar/users/shared/_links.erb +25 -0
  84. data/app/views/raddar/users/show.html.erb +52 -0
  85. data/app/views/raddar/users/unlocks/new.html.erb +16 -0
  86. data/config/cucumber.yml +8 -0
  87. data/config/initializers/carrierwave.rb +10 -0
  88. data/config/initializers/devise.rb +262 -0
  89. data/config/initializers/kaminari_config.rb +10 -0
  90. data/config/initializers/simple_form.rb +142 -0
  91. data/config/initializers/simple_form_bootstrap.rb +17 -0
  92. data/config/locales/devise.en.yml +59 -0
  93. data/config/locales/en.yml +49 -0
  94. data/config/locales/flash.en.yml +20 -0
  95. data/config/locales/mailers.en.yml +5 -0
  96. data/config/locales/simple_form.en.yml +35 -0
  97. data/config/locales/views.en.yml +36 -0
  98. data/config/routes.rb +43 -0
  99. data/db/migrate/20130824222728_devise_create_raddar_users.rb +66 -0
  100. data/db/migrate/20131013222926_create_raddar_followerships.rb +14 -0
  101. data/db/migrate/20131020174318_create_raddar_roles.rb +11 -0
  102. data/db/migrate/20131020175354_create_join_table_raddar_role_user.rb +8 -0
  103. data/db/migrate/20131021134623_create_raddar_notifications.rb +13 -0
  104. data/db/migrate/20131026133924_create_raddar_external_accounts.rb +23 -0
  105. data/lib/raddar.rb +15 -0
  106. data/lib/raddar/engine.rb +5 -0
  107. data/lib/raddar/hstore_serializer.rb +22 -0
  108. data/lib/raddar/version.rb +3 -0
  109. data/lib/tasks/cucumber.rake +65 -0
  110. data/lib/tasks/raddar_tasks.rake +4 -0
  111. metadata +390 -0
@@ -0,0 +1,25 @@
1
+ module Raddar
2
+ class FollowershipPolicy < ApplicationPolicy
3
+ class Scope < Struct.new(:user, :scope)
4
+ def resolve
5
+ scope
6
+ end
7
+ end
8
+
9
+ def create?
10
+ @user.present?
11
+ end
12
+
13
+ def destroy?
14
+ @user.present?
15
+ end
16
+
17
+ def followers?
18
+ @user.present?
19
+ end
20
+
21
+ def following?
22
+ @user.present?
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ module Raddar
2
+ class NotificationPolicy < ApplicationPolicy
3
+ class Scope < Struct.new(:user, :scope)
4
+ def resolve
5
+ scope
6
+ end
7
+ end
8
+
9
+ def index?
10
+ @user.present?
11
+ end
12
+
13
+ def show?
14
+ @user.present? && @record.user == @user
15
+ end
16
+
17
+ def read_all?
18
+ @user.present?
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ module Raddar
2
+ class UserPolicy < ApplicationPolicy
3
+ class Scope < Struct.new(:user, :scope)
4
+ def resolve
5
+ scope
6
+ end
7
+ end
8
+
9
+ def show?
10
+ @user.present?
11
+ end
12
+
13
+ def read_field?(field)
14
+ ((@record.privacy || {})[field] != 'only_me') || (@user == @record)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,56 @@
1
+ module Raddar
2
+ class AvatarUploader < CarrierWave::Uploader::Base
3
+
4
+ # Include RMagick or MiniMagick support:
5
+ # include CarrierWave::RMagick
6
+ include CarrierWave::MiniMagick
7
+
8
+ # Override the directory where uploaded files will be stored.
9
+ # This is a sensible default for uploaders that are meant to be mounted:
10
+ def store_dir
11
+ "uploads/#{model.class.to_s.underscore}/#{model.id}/#{mounted_as}"
12
+ end
13
+
14
+ # Provide a default URL as a default if there hasn't been a file uploaded:
15
+ # def default_url
16
+ # # For Rails 3.1+ asset pipeline compatibility:
17
+ # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
18
+ #
19
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
20
+ # end
21
+ def default_url
22
+ ActionController::Base.helpers.asset_path("fallback/avatar/#{ version_name }.png")
23
+ end
24
+
25
+ # Process files as they are uploaded:
26
+ # process :scale => [200, 300]
27
+ #
28
+ # def scale(width, height)
29
+ # # do something
30
+ # end
31
+
32
+ # Create different versions of your uploaded files:
33
+
34
+ process resize_to_limit: [760, 1160]
35
+
36
+ version :medium do
37
+ process resize_to_fill: [360, 360]
38
+ end
39
+
40
+ version :thumb do
41
+ process resize_to_fill: [60, 60]
42
+ end
43
+
44
+ # Add a white list of extensions which are allowed to be uploaded.
45
+ # For images you might use something like this:
46
+ def extension_white_list
47
+ %w(jpg jpeg gif png)
48
+ end
49
+
50
+ # Override the filename of the uploaded files:
51
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
52
+ # def filename
53
+ # "something.jpg" if original_filename
54
+ # end
55
+ end
56
+ end
@@ -0,0 +1,3 @@
1
+ <% flash.each do |type, msg| %>
2
+ <%= content_tag :div, msg, class: "alert alert-#{ type.to_s == 'alert' ? 'danger' : 'info' }" %>
3
+ <% end %>
@@ -0,0 +1,22 @@
1
+ <header class="navbar navbar-default navbar-static-top" role="navigation">
2
+ <div class='container'>
3
+ <div class="navbar-header">
4
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
5
+ <span class="sr-only">Toggle navigation</span>
6
+ <span class="icon-bar"></span>
7
+ <span class="icon-bar"></span>
8
+ <span class="icon-bar"></span>
9
+ </button>
10
+
11
+ <%= link_to Raddar.app_name, raddar.root_path, class: 'navbar-brand' %>
12
+ </div>
13
+
14
+ <div class="collapse navbar-collapse navbar-ex1-collapse">
15
+ <ul class="nav navbar-nav navbar-right">
16
+ <%= render 'layouts/raddar/notifications' %>
17
+
18
+ <%= render 'layouts/raddar/user_menu' %>
19
+ </ul>
20
+ </div>
21
+ </div>
22
+ </header>
@@ -0,0 +1,20 @@
1
+ <% if user_signed_in? %>
2
+ <li class="dropdown">
3
+ <%= link_to raddar.notifications_path, id: 'notifications-menu', class: 'dropdown-toggle', data: { toggle: 'dropdown' } do %>
4
+ <span class="glyphicon glyphicon-flag"></span>
5
+
6
+ <% if unread_notifications_count(current_user) > 0 %>
7
+ <span class="badge"><%= unread_notifications_count(current_user) %></span>
8
+ <% end %>
9
+ <% end %>
10
+
11
+ <ul class="dropdown-menu">
12
+ <% last_notifications(current_user).each do |notification| %>
13
+ <li><%= link_to_notification notification %></li>
14
+ <li class="divider"></li>
15
+ <% end %>
16
+
17
+ <li><%= link_to t('notifications.see_all'), raddar.notifications_path %></li>
18
+ </ul>
19
+ </li>
20
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <% if user_signed_in? %>
2
+ <li class="dropdown">
3
+ <%= link_to [raddar, current_user], id: 'user-menu', class: 'dropdown-toggle', data: { toggle: 'dropdown' } do %>
4
+ <%= current_user.name %>
5
+ <b class="caret"></b>
6
+ <% end %>
7
+
8
+ <ul class="dropdown-menu">
9
+ <li><%= link_to t('user.show'), [raddar, current_user] %></li>
10
+ <li><%= link_to t('user.registration.edit'), raddar.edit_user_registration_path %></li>
11
+ <li class="divider"></li>
12
+ <% if current_user.admin? %>
13
+ <li><%= link_to t('admin.dashboard'), raddar.admin_root_path %></li>
14
+ <li class="divider"></li>
15
+ <% end %>
16
+ <li><%= link_to t('user.session.destroy'), raddar.destroy_user_session_path, method: :delete %></li>
17
+ </ul>
18
+ </li>
19
+ <% else %>
20
+ <li><%= link_to t('user.session.new'), raddar.new_user_session_path %></li>
21
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= Raddar.app_name %></title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <%= stylesheet_link_tag 'application', media: 'all' %>
7
+
8
+ <%= csrf_meta_tags %>
9
+ </head>
10
+ <body>
11
+ <%= render 'layouts/raddar/navbar' %>
12
+
13
+ <div class='container' id='main-container'>
14
+ <%= render 'layouts/raddar/alerts' %>
15
+
16
+ <%= yield %>
17
+ </div>
18
+
19
+ <%= javascript_include_tag 'application' %>
20
+ </body>
21
+ </html>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
5
+ </head>
6
+
7
+ <body style="background-color: #eee; margin: 0; padding: 20px; font-family: Helvetica, Arial, sans-serif">
8
+ <div style="background-color: #fff; margin: 0 auto; padding: 20px">
9
+ <%= yield %>
10
+ </div>
11
+ </body>
12
+ </html>
@@ -0,0 +1,6 @@
1
+ <div class='page-header'>
2
+ <h1><%= t 'admin.dashboard' %></h1>
3
+ </div>
4
+
5
+
6
+ <%= link_to t('admin.users'), admin_users_path %>
@@ -0,0 +1,11 @@
1
+ <div class="page-header">
2
+ <h1><%= t 'admin.users' %></h1>
3
+ </div>
4
+
5
+ <ul>
6
+ <% @users.each do |user| %>
7
+ <li class="user"><%= link_to user.name, admin_user_path(user) %></li>
8
+ <% end %>
9
+ </ul>
10
+
11
+ <%= paginate @users %>
@@ -0,0 +1,12 @@
1
+ <div class="page-header">
2
+ <h1><%= @user.name %></h1>
3
+ </div>
4
+
5
+ <%= simple_form_for([:admin, @user]) do |f| %>
6
+ <%= f.input :state, collection: [:active, :blocked], as: :radio_buttons %>
7
+
8
+ <%= f.association :roles, as: :check_boxes,
9
+ label_method: ->(r) { t(r.name, scope: 'role.names') } %>
10
+
11
+ <%= f.submit t(:save), class: 'btn btn-default' %>
12
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <div class='page-header'>
2
+ <h1><%= t 'followership.followers' %></h1>
3
+ </div>
4
+
5
+ <ul>
6
+ <% @followable.followers.each do |followership| %>
7
+ <li><%= link_to followership.user.name, followership.user %></li>
8
+ <% end %>
9
+ </ul>
@@ -0,0 +1,9 @@
1
+ <div class='page-header'>
2
+ <h1><%= t 'followership.following' %></h1>
3
+ </div>
4
+
5
+ <ul>
6
+ <% @user.followerships.each do |followership| %>
7
+ <li><%= link_to followership.followable.name, followership.followable %></li>
8
+ <% end %>
9
+ </ul>
@@ -0,0 +1 @@
1
+ <h1><%= Raddar.app_name %></h1>
@@ -0,0 +1,11 @@
1
+ <h1>Hi, <%= @user.name %>!</h1>
2
+
3
+ <p>
4
+ <%= link_to @follower.name, raddar.notification_url(@notification) %>
5
+ is now following you!
6
+ </p>
7
+
8
+ <p>
9
+ To follow back, please go to <%= @follower.name %>'s profile by
10
+ <%= link_to 'clicking here', raddar.notification_url(@notification) %>.
11
+ </p>
@@ -0,0 +1,12 @@
1
+ <div class="page-title">
2
+ <h1><%= t '.title' %></h1>
3
+ </div>
4
+
5
+
6
+ <ul>
7
+ <% @notifications.each do |notification| %>
8
+ <li><%= link_to_notification notification %></li>
9
+ <% end %>
10
+ </ul>
11
+
12
+ <%= paginate @notifications %>
@@ -0,0 +1,4 @@
1
+ json.array!(@notifications) do |notification|
2
+ json.extract! notification, :content, :item_path, :read, :user_id, :notifiable_id
3
+ json.url notification_url(notification, format: :json)
4
+ end
@@ -0,0 +1,16 @@
1
+ <h2>Resend confirmation instructions</h2>
2
+
3
+ <%= simple_form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %>
4
+ <%= f.error_notification %>
5
+ <%= f.full_error :confirmation_token %>
6
+
7
+ <div class="form-inputs">
8
+ <%= f.input :email, :required => true, :autofocus => true %>
9
+ </div>
10
+
11
+ <div class="form-actions">
12
+ <%= f.button :submit, "Resend confirmation instructions" %>
13
+ </div>
14
+ <% end %>
15
+
16
+ <%= render "users/shared/links" %>
@@ -0,0 +1,11 @@
1
+ <h1><%= t '.title' %></h1>
2
+
3
+ <%= simple_form_for(@user, url: user_email_preferences_path, method: :patch) do |f| %>
4
+ <%= f.simple_fields_for(:email_preferences) do |p| %>
5
+ <% Raddar::User.email_preferences_keys.each do |key| %>
6
+ <%= p.input key, as: :boolean, label: false, inline_label: t(".keys.#{ key }"), input_html: { checked: @user.email_preferences[key] } %>
7
+ <% end %>
8
+ <% end %>
9
+
10
+ <%= f.button :submit, t('.submit') %>
11
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <h1><%= t '.title' %></h1>
2
+
3
+ <% Raddar::User.omniauth_providers.each do |provider| %>
4
+ <div class="external-account <%= provider %>">
5
+ <h1><%= provider.to_s.titleize %></h1>
6
+
7
+ <% if account = @user.external_accounts.find_by(provider: provider) %>
8
+ <%= link_to "@#{account.name}", account.url, target: '_blank' %>
9
+ <%= link_to t('.destroy', provider: provider.to_s.titleize), [:user, account], method: :delete, class: 'btn btn-default' %>
10
+ <% else %>
11
+ <%= link_to t('.connect', provider: provider.to_s.titleize), user_omniauth_authorize_path(provider), class: 'btn btn-primary' %>
12
+ <% end %>
13
+ </div>
14
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <p>Welcome <%= @email %>!</p>
2
+
3
+ <p>You can confirm your account email through the link below:</p>
4
+
5
+ <p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>
@@ -0,0 +1,8 @@
1
+ <p>Hello <%= @resource.email %>!</p>
2
+
3
+ <p>Someone has requested a link to change your password. You can do this through the link below.</p>
4
+
5
+ <p><%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @token) %></p>
6
+
7
+ <p>If you didn't request this, please ignore this email.</p>
8
+ <p>Your password won't change until you access the link above and create a new one.</p>
@@ -0,0 +1,7 @@
1
+ <p>Hello <%= @resource.email %>!</p>
2
+
3
+ <p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
4
+
5
+ <p>Click the link below to unlock your account:</p>
6
+
7
+ <p><%= link_to 'Unlock my account', user_unlock_url(@resource, :unlock_token => @token) %></p>
@@ -0,0 +1,13 @@
1
+ <h1><%= t '.title' %></h1>
2
+
3
+ <%= simple_form_for(@user, url: raddar.do_change_user_password_path) do |f| %>
4
+ <%= f.error_notification %>
5
+
6
+ <%= f.input :current_password, autocomplete: 'off', required: true, input_html: { class: 'form-control'} %>
7
+ <%= f.input :password, label: t('.password_label'), autocomplete: 'off', required: true, input_html: { class: 'form-control'} %>
8
+ <%= f.input :password_confirmation, autocomplete: 'off', required: true, input_html: { class: 'form-control'} %>
9
+
10
+ <div class="form-group">
11
+ <%= f.button :submit, t('.submit') %>
12
+ </div>
13
+ <% end %>
@@ -0,0 +1,19 @@
1
+ <h2>Change your password</h2>
2
+
3
+ <%= simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| %>
4
+ <%= f.error_notification %>
5
+
6
+ <%= f.input :reset_password_token, :as => :hidden %>
7
+ <%= f.full_error :reset_password_token %>
8
+
9
+ <div class="form-inputs">
10
+ <%= f.input :password, :label => "New password", :required => true, :autofocus => true %>
11
+ <%= f.input :password_confirmation, :label => "Confirm your new password", :required => true %>
12
+ </div>
13
+
14
+ <div class="form-actions">
15
+ <%= f.button :submit, "Change my password" %>
16
+ </div>
17
+ <% end %>
18
+
19
+ <%= render "raddar/users/shared/links" %>
@@ -0,0 +1,15 @@
1
+ <h2>Forgot your password?</h2>
2
+
3
+ <%= simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %>
4
+ <%= f.error_notification %>
5
+
6
+ <div class="form-inputs">
7
+ <%= f.input :email, :required => true, :autofocus => true %>
8
+ </div>
9
+
10
+ <div class="form-actions">
11
+ <%= f.button :submit, "Send me reset password instructions" %>
12
+ </div>
13
+ <% end %>
14
+
15
+ <%= render "raddar/users/shared/links" %>