bigbluebutton_rails 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/.gitignore +2 -1
  2. data/CHANGELOG.rdoc +12 -0
  3. data/Gemfile.lock +53 -14
  4. data/README.rdoc +31 -29
  5. data/Rakefile +63 -11
  6. data/app/controllers/bigbluebutton/rooms_controller.rb +89 -26
  7. data/app/controllers/bigbluebutton/servers_controller.rb +38 -5
  8. data/app/helpers/bigbluebutton_rails_helper.rb +12 -0
  9. data/app/models/bigbluebutton_room.rb +44 -1
  10. data/app/models/bigbluebutton_server.rb +48 -12
  11. data/app/views/bigbluebutton/rooms/_form.html.erb +8 -0
  12. data/app/views/bigbluebutton/rooms/index.html.erb +34 -43
  13. data/app/views/bigbluebutton/rooms/join_mobile.html.erb +9 -0
  14. data/app/views/bigbluebutton/rooms/show.html.erb +8 -0
  15. data/app/views/bigbluebutton/servers/_activity_list.html.erb +48 -0
  16. data/app/views/bigbluebutton/servers/_form.html.erb +4 -0
  17. data/app/views/bigbluebutton/servers/activity.html.erb +51 -0
  18. data/app/views/bigbluebutton/servers/index.html.erb +21 -25
  19. data/app/views/bigbluebutton/servers/show.html.erb +5 -0
  20. data/bigbluebutton_rails.gemspec +9 -6
  21. data/config/locales/en.yml +11 -2
  22. data/lib/bigbluebutton_rails/controller_methods.rb +47 -14
  23. data/lib/bigbluebutton_rails/exceptions.rb +6 -0
  24. data/lib/bigbluebutton_rails/rails/routes.rb +17 -12
  25. data/lib/bigbluebutton_rails/rails.rb +4 -0
  26. data/lib/bigbluebutton_rails/version.rb +1 -1
  27. data/lib/bigbluebutton_rails.rb +1 -0
  28. data/lib/generators/bigbluebutton_rails/install_generator.rb +28 -5
  29. data/lib/generators/bigbluebutton_rails/templates/migration.rb +3 -0
  30. data/lib/generators/bigbluebutton_rails/templates/migration_0_0_4.rb +38 -0
  31. data/lib/generators/bigbluebutton_rails/templates/migration_0_0_5.rb +23 -0
  32. data/{lib/generators/bigbluebutton_rails/templates/public → public}/images/loading.gif +0 -0
  33. data/{lib/generators/bigbluebutton_rails/templates/public → public}/javascripts/jquery.min.js +0 -0
  34. data/public/stylesheets/bigbluebutton_rails.css +138 -0
  35. data/spec/controllers/bigbluebutton/rooms_controller_spec.rb +284 -25
  36. data/spec/controllers/bigbluebutton/servers_controller_spec.rb +93 -1
  37. data/spec/factories/bigbluebutton_room.rb +1 -0
  38. data/spec/factories/bigbluebutton_server.rb +1 -0
  39. data/spec/factories/integration/bigbluebutton_server_integration.rb +8 -0
  40. data/spec/generators/install_generator_spec.rb +53 -10
  41. data/spec/generators/views_generator_spec.rb +6 -2
  42. data/spec/helpers/bigbluebutton_rails_helper_spec.rb +19 -0
  43. data/spec/integration_conf.yml.example +5 -0
  44. data/spec/models/bigbluebutton_room_spec.rb +99 -72
  45. data/spec/models/bigbluebutton_server_spec.rb +81 -20
  46. data/spec/rails_app/.gitignore +2 -1
  47. data/spec/rails_app/app/views/layouts/application.html.erb +1 -1
  48. data/spec/rails_app/config/cucumber.yml +8 -0
  49. data/spec/rails_app/config/database.yml +4 -1
  50. data/spec/rails_app/db/seeds.rb +6 -5
  51. data/spec/rails_app/features/manage_bigbluebutton_rooms.feature +9 -0
  52. data/spec/rails_app/features/manage_bigbluebutton_servers.feature +9 -0
  53. data/spec/rails_app/features/step_definitions/bigbluebutton_room_steps.rb +38 -0
  54. data/spec/rails_app/features/step_definitions/bigbluebutton_server_steps.rb +23 -0
  55. data/spec/rails_app/features/step_definitions/common_steps.rb +3 -0
  56. data/spec/rails_app/features/step_definitions/web_steps.rb +211 -0
  57. data/spec/rails_app/features/support/content.rb +11 -0
  58. data/spec/rails_app/features/support/env.rb +50 -0
  59. data/spec/rails_app/features/support/env_gem.rb +9 -0
  60. data/spec/rails_app/features/support/paths.rb +38 -0
  61. data/spec/rails_app/features/support/selectors.rb +39 -0
  62. data/spec/rails_app/lib/tasks/cucumber.rake +65 -0
  63. data/spec/rails_app/public/images/loading.gif +0 -0
  64. data/spec/rails_app/public/javascripts/jquery.min.js +16 -0
  65. data/spec/rails_app/public/stylesheets/bigbluebutton_rails.css +138 -0
  66. data/spec/rails_app/script/cucumber +10 -0
  67. data/spec/routing/bigbluebutton/rooms_routing_spec.rb +12 -0
  68. data/spec/spec_helper.rb +0 -1
  69. data/spec/support/integration/integration_conf.rb +16 -0
  70. data/spec/support/matchers/shoulda/respond_with_json_matcher.rb +67 -0
  71. data/spec/support/mocked_server.rb +1 -0
  72. metadata +91 -29
  73. data/lib/generators/bigbluebutton_rails/public_generator.rb +0 -16
  74. data/spec/generators/public_generator_spec.rb +0 -31
@@ -7,6 +7,9 @@ en:
7
7
  success: "Your server was successfully created."
8
8
  update:
9
9
  success: "Your server was successfully updated."
10
+ errors:
11
+ param_format: 'you can only use letters, numbers and the symbols "-" or "_" (not at the begin or end)'
12
+ url_format: 'should use the pattern http://<server>/bigbluebutton/api'
10
13
  rooms:
11
14
  notice:
12
15
  create:
@@ -18,21 +21,25 @@ en:
18
21
  end:
19
22
  success: "The meeting was successfully ended."
20
23
  not_running: "The meeting could not be ended because it is not running."
21
- error:
24
+ destroy:
25
+ success: "The meeting was successfully destroyed."
26
+ errors:
22
27
  auth:
23
28
  failure: "Authentication failure."
24
29
  not_running: "The meeting is not running. You must wait for a moderator to join."
30
+ param_format: 'you can only use letters, numbers and the symbols "-" or "_" (not at the begin or end)'
25
31
 
26
32
  activerecord:
27
33
  models:
28
34
  bigbluebutton_server: "BigBlueButton Server"
29
35
  bigbluebutton_room: "BigBlueButton Room"
30
- attributes:
36
+ attributes:
31
37
  bigbluebutton_server:
32
38
  name: "Name"
33
39
  url: "URL"
34
40
  salt: "Security Salt"
35
41
  version: "Version"
42
+ param: "String ID"
36
43
  bigbluebutton_room:
37
44
  server_id: "Server ID"
38
45
  meetingid: "Meeting ID"
@@ -46,4 +53,6 @@ en:
46
53
  dial_number: "Dial Number"
47
54
  voice_bridge: "Voice Bridge"
48
55
  max_participants: "Maximum Number of Participants"
56
+ external: "Externally created"
57
+ param: "String ID"
49
58
 
@@ -26,25 +26,58 @@ module BigbluebuttonRails
26
26
  current_user
27
27
  end
28
28
 
29
- # Returns the role that the current user has in the room 'room'.
30
- # Possibilities:
31
- # :moderator
32
- # :attendee
33
- # You may want to redefine this method in your controllers to define
34
- # real roles to the users. By default, everyone has moderator permissions.
35
- # Redefine it in your ApplicationController to make it available to all
36
- # controllers. For example:
29
+ # Returns the role that the current user has in the room 'room'. Return values:
30
+ # :moderator # the user has attendee permissions
31
+ # :attendee # the user has moderator permissions
32
+ # :password # the user must enter a password that will define his role
33
+ # nil # no role at all (the room is blocked to this user)
34
+ #
35
+ # Returning :moderator or :attendee means that the current user has access
36
+ # to the room and has moderator or attendee privileges, respectively.
37
+ # Returning :password indicates that the user must enter a password to define
38
+ # his role in the room.
39
+ # At last, returning nil means that the user cannot access access this room
40
+ # at all. BigbluebuttonRails::RoomController will thrown an exception of the
41
+ # class BigbluebuttonRails::RoomAccessDenied in this case. By default rails will
42
+ # show an error page whenever an exception is thrown (and not catch). To show
43
+ # a better-looking page, add in your ApplicationController a block to catch the
44
+ # exception, such as:
45
+ #
46
+ # rescue_from BigbluebuttonRails::RoomAccessDenied do |exception|
47
+ # flash[:error] = "You don't have permission to access this room!"
48
+ # redirect_to root_url
49
+ # end
50
+ #
51
+ # You may want to redefine this method in your application to define
52
+ # real roles to the users. By default, if the room is not private and the user
53
+ # is logged, he will have moderator permissions. Otherwise, he must enter
54
+ # a password. Redefine it in your ApplicationController to make it available
55
+ # to all controllers. For example:
37
56
  #
38
57
  # def bigbluebutton_role(@room)
39
- # r = Roles.where(:bigbluebutton_room_id => @room.id).
40
- # where(:user_id => current_user.id).
41
- # first
42
- # r.role
58
+ #
59
+ # # the owner is the moderator
60
+ # if @room.owner == bigbluebutton_user
61
+ # :moderator
62
+ #
63
+ # # only friends are allowed to enter
64
+ # elsif @room.owner.friends.include? bigbluebutton_user
65
+ # if @room.private
66
+ # :password # with password if the room is private
67
+ # else
68
+ # :attendee
69
+ # end
70
+ #
71
+ # # not a friend? you're not allowed in
72
+ # else
73
+ # nil
74
+ # end
75
+ #
43
76
  # end
44
77
  #
45
78
  def bigbluebutton_role(room)
46
- if room.private or current_user.nil?
47
- nil # ask for a password
79
+ if room.private or bigbluebutton_user.nil?
80
+ :password # ask for a password
48
81
  else
49
82
  :moderator
50
83
  end
@@ -0,0 +1,6 @@
1
+ module BigbluebuttonRails
2
+
3
+ # Raised when the user is not authorized to join a room
4
+ class RoomAccessDenied < StandardError; end
5
+
6
+ end
@@ -52,18 +52,20 @@ module ActionDispatch::Routing
52
52
  #
53
53
  # The routes generated are:
54
54
  #
55
- # user_room GET /users/:user_id/room/:id(.:format)
56
- # { :controller=>"bigbluebutton/rooms", :action=>"show" }
57
- # user_join_room GET /users/:user_id/room/:id/join(.:format)
58
- # { :controller=>"bigbluebutton/rooms", :action=>"join" }
59
- # user_auth_room POST /users/:user_id/room/:id/join(.:format)
60
- # { :controller=>"bigbluebutton/rooms", :action=>"auth" }
61
- # user_running_room GET /users/:user_id/room/:id/running(.:format)
62
- # { :controller=>"bigbluebutton/rooms", :action=>"running" }
63
- # user_end_room GET /users/:user_id/room/:id/end(.:format)
64
- # { :controller=>"bigbluebutton/rooms", :action=>"end" }
65
- # user_invite_room GET /users/:user_id/room/:id/invite(.:format)
66
- # { :controller=>"bigbluebutton/rooms", :action=>"invite" }
55
+ # user_room GET /users/:user_id/room/:id(.:format)
56
+ # { :controller=>"bigbluebutton/rooms", :action=>"show" }
57
+ # user_join_room GET /users/:user_id/room/:id/join(.:format)
58
+ # { :controller=>"bigbluebutton/rooms", :action=>"join" }
59
+ # user_join_mobile_room GET /users/:user_id/room/:id/invite(.:format)
60
+ # { :controller=>"bigbluebutton/rooms", :action=>"invite" }
61
+ # user_auth_room POST /users/:user_id/room/:id/join(.:format)
62
+ # { :controller=>"bigbluebutton/rooms", :action=>"auth" }
63
+ # user_running_room GET /users/:user_id/room/:id/running(.:format)
64
+ # { :controller=>"bigbluebutton/rooms", :action=>"running" }
65
+ # user_end_room GET /users/:user_id/room/:id/end(.:format)
66
+ # { :controller=>"bigbluebutton/rooms", :action=>"end" }
67
+ # user_invite_room GET /users/:user_id/room/:id/invite(.:format)
68
+ # { :controller=>"bigbluebutton/rooms", :action=>"invite" }
67
69
  #
68
70
  def bigbluebutton_routes(*params)
69
71
  options = params.extract_options!
@@ -79,11 +81,13 @@ module ActionDispatch::Routing
79
81
 
80
82
  scope options_scope, :as => options_scope do
81
83
  resources :servers, :controller => BigbluebuttonRails.controllers[:servers] do
84
+ get :activity, :on => :member
82
85
  resources :rooms, :controller => BigbluebuttonRails.controllers[:rooms] do
83
86
  get :join, :on => :member
84
87
  get :running, :on => :member
85
88
  get :end, :on => :member
86
89
  get :invite, :on => :member
90
+ get :join_mobile, :on => :member
87
91
  post :join, :on => :member, :action => :auth
88
92
  end
89
93
  end
@@ -94,6 +98,7 @@ module ActionDispatch::Routing
94
98
  # TODO This is generating helpers like "user_running_room" instead of "running_user_room"
95
99
  get 'room/:id' => "#{BigbluebuttonRails.controllers[:rooms]}#show", :as => 'room'
96
100
  get 'room/:id/join' => "#{BigbluebuttonRails.controllers[:rooms]}#join", :as => 'join_room'
101
+ get 'room/:id/join_mobile' => "#{BigbluebuttonRails.controllers[:rooms]}#join_mobile", :as => 'join_mobile_room'
97
102
  post 'room/:id/join' => "#{BigbluebuttonRails.controllers[:rooms]}#auth", :as => 'join_room'
98
103
  get 'room/:id/running' => "#{BigbluebuttonRails.controllers[:rooms]}#running", :as => 'running_room'
99
104
  get 'room/:id/end' => "#{BigbluebuttonRails.controllers[:rooms]}#end", :as => 'end_room'
@@ -1,6 +1,10 @@
1
1
  module BigbluebuttonRails
2
2
  class Engine < ::Rails::Engine
3
3
 
4
+ initializer 'bigbluebutton_rails.helper' do |app|
5
+ ActionView::Base.send :include, BigbluebuttonRailsHelper
6
+ end
7
+
4
8
  initializer "bigbluebutton_rails.controller_methods" do
5
9
  ActiveSupport.on_load(:action_controller) do
6
10
  include BigbluebuttonRails::ControllerMethods
@@ -1,3 +1,3 @@
1
1
  module BigbluebuttonRails
2
- VERSION = "0.0.4".freeze
2
+ VERSION = "0.0.5".freeze
3
3
  end
@@ -6,6 +6,7 @@ module BigbluebuttonRails
6
6
  require 'bigbluebutton_rails/rails'
7
7
  require 'bigbluebutton_rails/controller_methods'
8
8
  require 'bigbluebutton_rails/rails/routes'
9
+ require 'bigbluebutton_rails/exceptions'
9
10
 
10
11
  # Default controllers to generate the routes
11
12
  mattr_accessor :controllers
@@ -4,13 +4,22 @@ module BigbluebuttonRails
4
4
  module Generators
5
5
  class InstallGenerator < Rails::Generators::Base
6
6
  include Rails::Generators::Migration
7
- source_root File.expand_path("../templates", __FILE__)
7
+ argument :migrate_to_version, :type => :string, :default => "", :description => "Generate migration to this version"
8
+ class_option :migration_only, :type => :boolean, :default => false, :description => "Generate only the migration file"
9
+ source_root File.expand_path("../../../../", __FILE__) # set to gem root
8
10
 
9
- desc "Creates the initializer, initial migration and locale files."
11
+ desc "Creates the migrations and locale files. Also used to create migrations when updating the gem version."
10
12
 
11
13
  def copy_locale
12
- # uses bigbluebutton_rails/config to avoid using the local application en.yml
13
- copy_file "../../../../../bigbluebutton_rails/config/locales/en.yml", "config/locales/bigbluebutton_rails.en.yml"
14
+ copy_file "config/locales/en.yml", "config/locales/bigbluebutton_rails.en.yml" unless options.migration_only?
15
+ end
16
+
17
+ def copy_public_files
18
+ unless options.migration_only?
19
+ copy_file "public/javascripts/jquery.min.js", "public/javascripts/jquery.min.js"
20
+ copy_file "public/images/loading.gif", "public/images/loading.gif"
21
+ copy_file "public/stylesheets/bigbluebutton_rails.css", "public/stylesheets/bigbluebutton_rails.css"
22
+ end
14
23
  end
15
24
 
16
25
  def self.next_migration_number(dirname)
@@ -18,7 +27,21 @@ module BigbluebuttonRails
18
27
  end
19
28
 
20
29
  def create_migration_file
21
- migration_template 'migration.rb', 'db/migrate/create_bigbluebutton_rails.rb'
30
+ if migrate_to_version.blank?
31
+ migration_template "#{migration_path}/migration.rb", "db/migrate/create_bigbluebutton_rails.rb"
32
+ else
33
+ migration_template "#{migration_path}/migration_#{version_filename}.rb", "db/migrate/bigbluebutton_rails_to_#{version_filename}.rb"
34
+ end
35
+ end
36
+
37
+ protected
38
+
39
+ def migration_path
40
+ File.join("lib", "generators", "bigbluebutton_rails", "templates")
41
+ end
42
+
43
+ def version_filename
44
+ migrate_to_version.gsub(".", "_")
22
45
  end
23
46
 
24
47
  end
@@ -6,6 +6,7 @@ class CreateBigbluebuttonRails < ActiveRecord::Migration
6
6
  t.string :url
7
7
  t.string :salt
8
8
  t.string :version
9
+ t.string :param
9
10
  t.timestamps
10
11
  end
11
12
  create_table :bigbluebutton_rooms do |t|
@@ -23,6 +24,8 @@ class CreateBigbluebuttonRails < ActiveRecord::Migration
23
24
  t.integer :max_participants
24
25
  t.boolean :private, :default => false
25
26
  t.boolean :randomize_meetingid, :default => true
27
+ t.boolean :external, :default => false
28
+ t.string :param
26
29
  t.timestamps
27
30
  end
28
31
  add_index :bigbluebutton_rooms, :server_id
@@ -0,0 +1,38 @@
1
+ class BigbluebuttonRailsTo004 < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ create_table :bigbluebutton_servers do |t|
5
+ t.string :name
6
+ t.string :url
7
+ t.string :salt
8
+ t.string :version
9
+ t.timestamps
10
+ end
11
+ create_table :bigbluebutton_rooms do |t|
12
+ t.integer :server_id
13
+ t.integer :owner_id
14
+ t.string :owner_type
15
+ t.string :meetingid
16
+ t.string :name
17
+ t.string :attendee_password
18
+ t.string :moderator_password
19
+ t.string :welcome_msg
20
+ t.string :logout_url
21
+ t.string :voice_bridge
22
+ t.string :dial_number
23
+ t.integer :max_participants
24
+ t.boolean :private, :default => false
25
+ t.boolean :randomize_meetingid, :default => true
26
+ t.timestamps
27
+ end
28
+ add_index :bigbluebutton_rooms, :server_id
29
+ add_index :bigbluebutton_rooms, :meetingid, :unique => true
30
+ add_index :bigbluebutton_rooms, :voice_bridge, :unique => true
31
+ end
32
+
33
+ def self.down
34
+ drop_table :bigbluebutton_rooms
35
+ drop_table :bigbluebutton_servers
36
+ end
37
+
38
+ end
@@ -0,0 +1,23 @@
1
+ class BigbluebuttonRailsTo005 < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ add_column :bigbluebutton_rooms, :external, :boolean, :default => false
5
+ add_column :bigbluebutton_rooms, :param, :string
6
+ add_column :bigbluebutton_servers, :param, :string
7
+
8
+ BigbluebuttonRoom.all.each do |r|
9
+ r.update_attributes(:param => r.name.parameterize.downcase) unless r.name.nil?
10
+ end
11
+ BigbluebuttonServer.all.each do |s|
12
+ s.update_attributes(:param => s.name.parameterize.downcase) unless s.name.nil?
13
+ end
14
+
15
+ end
16
+
17
+ def self.down
18
+ remove_column :bigbluebutton_rooms, :external
19
+ remove_column :bigbluebutton_rooms, :param
20
+ remove_column :bigbluebutton_servers, :param
21
+ end
22
+
23
+ end
@@ -0,0 +1,138 @@
1
+ .bbbrails_meeting_description {
2
+ padding: 5px;
3
+ margin-top: 20px;
4
+ }
5
+
6
+ .bbbrails_meeting_description span.running {
7
+ color: green;
8
+ }
9
+
10
+ .bbbrails_meeting_description span.not_running {
11
+ color: #a00000;
12
+ }
13
+
14
+ .bbbrails_meeting_description .button_bar {
15
+ float: right;
16
+ margin-right: 10px;
17
+ border-left: 1px solid #aaa;
18
+ }
19
+
20
+ .bbbrails_meeting_description .button_bar a {
21
+ margin-left: 5px;
22
+ }
23
+
24
+ .bbbrails_meeting_description .title_bar {
25
+ padding: 5px;
26
+ background: #f0f0f0;
27
+ }
28
+
29
+ .bbbrails_meeting_description_changed {
30
+ border: 1px solid #d00000;
31
+ }
32
+
33
+ .bbbrails_countdown {
34
+ float: right;
35
+ padding: 5px;
36
+ margin: 10px;
37
+ }
38
+
39
+ .bbbrails_meeting_attendees {
40
+ margin-top: 10px;
41
+ }
42
+
43
+ .bbbrails_meeting_attendees .header, .bbbrails_meeting_attendees .row {
44
+ clear: both;
45
+ border-top: 1px solid #ddd;
46
+ }
47
+
48
+ .bbbrails_meeting_attendees .header .field {
49
+ float: left;
50
+ margin: 0 5px 0 0;
51
+ width: 180px;
52
+ font-weight: bold;
53
+ padding: 3px;
54
+ }
55
+
56
+ .bbbrails_meeting_attendees .row .field {
57
+ float: left;
58
+ margin: 0 5px 0 0;
59
+ width: 180px;
60
+ padding: 3px;
61
+ }
62
+
63
+
64
+ /*
65
+ * Used in bigbluebutton_servers/index
66
+ */
67
+
68
+ #bbbrails_servers_list {
69
+ list-style-type: none;
70
+ margin: 0;
71
+ padding: 0;
72
+ }
73
+
74
+ #bbbrails_servers_links {
75
+ margin-bottom: 5px;
76
+ padding-bottom: 5px;
77
+ border-bottom: 1px solid #ddd;
78
+ }
79
+
80
+ #bbbrails_servers_list li {
81
+ margin-bottom: 5px;
82
+ padding-bottom: 5px;
83
+ border-bottom: 1px solid #ddd;
84
+ clear: both;
85
+ }
86
+
87
+ #bbbrails_servers_list div.field {
88
+ margin: 0;
89
+ padding: 0;
90
+ clear: both;
91
+ }
92
+
93
+ #bbbrails_servers_list label {
94
+ font-weight: bold;
95
+ width: 150px;
96
+ float: left;
97
+ text-align: right;
98
+ margin-right: 10px;
99
+ clear: both;
100
+ }
101
+
102
+ /*
103
+ * Used in bigbluebutton_rooms/index
104
+ */
105
+
106
+ #bbbrails_rooms_list {
107
+ list-style-type: none;
108
+ margin: 0;
109
+ padding: 0;
110
+ }
111
+
112
+ #bbbrails_rooms_links {
113
+ margin-bottom: 5px;
114
+ padding-bottom: 5px;
115
+ border-bottom: 1px solid #ddd;
116
+ }
117
+
118
+ #bbbrails_rooms_list li {
119
+ margin-bottom: 5px;
120
+ padding-bottom: 5px;
121
+ border-bottom: 1px solid #ddd;
122
+ clear: both;
123
+ }
124
+
125
+ #bbbrails_rooms_list div.field {
126
+ margin: 0;
127
+ padding: 0;
128
+ clear: both;
129
+ }
130
+
131
+ #bbbrails_rooms_list label {
132
+ font-weight: bold;
133
+ width: 300px;
134
+ float: left;
135
+ text-align: right;
136
+ margin-right: 10px;
137
+ clear: both;
138
+ }