diff --git a/Gemfile b/Gemfile index 7e6212e..a63c509 100644 --- a/Gemfile +++ b/Gemfile @@ -87,8 +87,11 @@ end #ask gem 'gotcha' -#desktop -gem 'angularjs-rails', '~> 1.2.20' -gem 'angular-ui-bootstrap-rails', '~> 0.11.0' -gem 'jquery_mousewheel_rails', '~> 3.1.11.3' +#caching observers +gem 'mongoid-observers' + +#desktop +# gem 'angularjs-rails', '~> 1.2.20' +# gem 'angular-ui-bootstrap-rails', '~> 0.11.0' +# gem 'jquery_mousewheel_rails', '~> 3.1.11.3' diff --git a/app/assets/images/google-message-background.jpg b/app/assets/images/google-message-background.jpg new file mode 100644 index 0000000..efb11ce Binary files /dev/null and b/app/assets/images/google-message-background.jpg differ diff --git a/app/assets/images/sign-in-with-google.png b/app/assets/images/sign-in-with-google.png deleted file mode 100644 index b20d378..0000000 Binary files a/app/assets/images/sign-in-with-google.png and /dev/null differ diff --git a/app/assets/stylesheets/group_page.css b/app/assets/stylesheets/group_page.css new file mode 100644 index 0000000..358927f --- /dev/null +++ b/app/assets/stylesheets/group_page.css @@ -0,0 +1,249 @@ +@charset "utf-8"; +body { + background: #f3f3f3; +} + +#main-wrap { + padding-top: 0; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +.group-page {} + +.group-page-banner-image-wrap { + position: relative; + max-height: 280px; + overflow: hidden; +} + +.group-page-view-switch { + position: absolute; + right: 2rem; + top: 2rem; +} + +.group-page-banner-image { + max-width: 100%; + width: 100%; + height: auto; +} + +.group-page-bar { + overflow: hidden; + background: #171717; + padding: 20px 2rem; + text-align: center; +} + +.group-page-title { + padding: 6px 0; + color: #fff; + margin-bottom: 12px; +} + +.group-page-setion { + padding: 30px 2rem; + overflow: hidden; + max-width: 1350px; + margin: auto; +} + +.gorup-page-info-wrap { + display: block; + font-size: .9rem; + float: right; + padding: 5px .5rem; + border: 2px solid #cdcdcd; + cursor: pointer; +} + +.group-page-description-wrap { + clear: both; +} + +.gorup-page-info-wrap { + text-align: right; + margin-bottom: 10px; +} + +.group-page-status-wrap { + float: right; + font-size: 0.75rem; + color: #fff; + background: #1d9e20; + padding: 2px .4rem; + border-radius: .2rem; + margin-bottom: 10px; +} +.group-page-status-wrap.close-eye{ + background: #c71932; + margin-left: 5px; +} + +.group-page-description-wrap { + padding: 16px 1.2rem 16px; + margin-bottom: 15px; + box-shadow: inset 0 0 8px #aaa; + border-radius: .2rem; +} + +.group-page-description { + clear: both; + text-align: justify; +} + +.group-page-post { + clear: both; + text-align: center; + overflow: hidden; + margin-bottom: 40px; + background: #fff; + padding: 28px 1.5rem; + border-radius: .2rem; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .2); +} + +.group-page-post-link{ + text-decoration: none; + color: #000; +} + +.group-page-post:hover { + box-shadow: 0 1px 25px 0 rgba(0, 0, 0, .2); +} + +.group-page-post-link:hover { + color: #000; + text-decoration: none; +} + +.group-page-post-image-wrap { + display: inline-block; + margin-bottom: 12px; +} + +.group-page-post-image { + max-width: 100%; + width: 100%; + margin-top: 12px; + /*border-radius: 50%;*/ + height: auto; +} + +.group-page-post-content { + margin-bottom: .5rem; +} + +.group-page-post-title { + font-weight: normal; + font-size: 1.5rem; + line-height: 1; + margin-bottom: 12px; +} + +.group-page-post-meta-wrap { + color: #aaa; +} + +.group-page-post-author, +.group-page-post-postdate { + margin-right: .5rem; +} +/* scroll view */ +.group-page.scroll { + padding-top: 7rem; +} +.group-page.scroll .group-page-banner-image-wrap { + height: 70px; + overflow: hidden; + position: fixed; + top: 0; +} + +.group-page.scroll .group-page-title { + margin-bottom: 0; +} + +.group-page.scroll .group-page-title { + font-size: 18px; +} + +.group-page.scroll .group-page-new-post { + margin-top: 6px; +} + +.group-page.scroll .group-page-new-post { + display: inline-block; + vertical-align: top; +} + +.group-page.scroll .group-page-view-switch { + position: static; + display: inline-block; + margin: 6px .5rem 0 0; +} + +.group-page.scroll .btn { + padding: 2px 5px; +} + +.group-page li a { + text-decoration: none; +} + +.group-page .group-page-feed-view .active { + color: #fff; +} +/* 600px */ + +@media screen and (min-width: 37.5rem) { + .group-page-post { + text-align: left; + margin-bottom: 15px; + } + .group-page-post-image-wrap { + float: left; + width: 12%; + margin-right: 4%; + min-width: 100px; + } + .group-page-post-content-wrap { + float: left; + width: 72%; + } + .group-page.scroll .group-page-view-switch { + margin: 6px .5rem 0 0; + } + .group-page.scroll .group-page-bar { + padding: 0 5rem 0 2rem; + position: fixed; + top: 70px; + width: 100%; + } +} +/* 760px */ + +@media screen and (min-width: 47.5rem) { + .group-page-post-content-wrap { + width: 80%; + } + .group-page-bar { + text-align: left; + } + .group-page-title { + float: left; + margin: 0; + padding: 0; + } + .group-page-new-post { + float: right; + } + .group-page-view-switch { + float: right; + } +} diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index e838d5e..c0b9381 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -1,9 +1,93 @@ class Admin::GroupsController < OrbitMemberController def index + @groups = current_user.groups + end + + def public_groups + @groups = Group.open end def categories + @categories = GroupCategory.all + end + + def newpost + @no_breadcrumb = true + uid = params[:group_id].split("-").last + @group = Group.find_by(:uid => uid) + @grouppost = GroupPost.new + end + + def createpost + uid = params[:group_id].split("-").last + group = Group.find_by(:uid => uid) + gp = GroupPost.new(post_params) + gp.group = group + gp.save + redirect_to admin_group_path(group) + end + + def showpost + + end + + def show + @no_breadcrumb = true + @no_filter = true + uid = params[:id].split("-").last + @group = Group.find_by(:uid => uid) + end + + def create_category + gc = GroupCategory.new(category_params) + gc.save + @categories = GroupCategory.all + render :partial => "group_categories" + end + + def create + group = Group.new(group_params) + group.save + redirect_to admin_groups_path + end + + def new + @group = Group.new + @categories = GroupCategory.all.collect{|gc| [gc.title,gc.id]} + @members = [] + MemberProfile.all.each do |mp| + user = mp.user rescue nil + if !user.nil? && user.id.to_s != current_user.id.to_s && user.user_name != "rulingcom" + avatar = (mp.avatar.thumb.url == "thumb_person.png" ? "/assets/thumb_person.png" : mp.avatar.thumb.url rescue "/assets/thumb_person.png") + @members << { + "id" => user.id.to_s, + "user_name" => (user.user_name rescue ""), + "avatar" => avatar, + "name" => (mp.name_translations rescue {"en" => "","zh_tw" => ""}) + } + end + end + end + + private + + def category_params + params.require(:category).permit! + end + + def post_params + p = params.require(:group_post).permit! + p["author"] = current_user.id + p + end + + def group_params + p = params.require(:group).permit! + p["user_ids"] << current_user.id.to_s + p["admins"] = [] + p["admins"] << current_user.id.to_s + p end end \ No newline at end of file diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 7bda6bf..b379f9b 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -27,7 +27,10 @@ class PagesController < ApplicationController def home @manifest = @key page = Page.first - impressionist(page) + Thread.new do + impressionist(page) + page.inc(view_count: 1) + end OrbitHelper.set_params params,current_user OrbitHelper.set_site_locale locale render :html => render_final_page("home",page,true).html_safe @@ -110,7 +113,10 @@ class PagesController < ApplicationController layout = true end - impressionist(page) + Thread.new do + impressionist(page) + page.inc(view_count: 1) + end render :html => render_final_page("#{module_app}/#{params[:target_action]}",page,layout).html_safe else render :file => "#{Rails.root}/public/404.html", :layout => false, :status => :not_found @@ -301,11 +307,11 @@ class PagesController < ApplicationController def render_final_page(original_view=get_view,page,layout) final_html_for_render = "" + if layout parts = $mobile.blank? ? (page.page_parts rescue []) : (page.mobile_page_parts rescue []) @part_partials = {} - parts.each do |part| subparts = part.sub_parts.asc(:created_at) partials = [] @@ -326,7 +332,10 @@ class PagesController < ApplicationController if @editmode partials << "
" else - partials << render_widget_for_frontend(subpart.module,subpart.widget_method,subpart.widget_type,subpart.id.to_s) + widget_html = Rails.cache.fetch("subpart_#{subpart.module}_#{subpart.id.to_s}_"+I18n.locale.to_s,{ race_condition_ttl: 2, expires_in: 5.minutes}) do + render_widget_for_frontend(subpart.module,subpart.widget_method,subpart.widget_type,subpart.id.to_s) + end + partials << widget_html end elsif subpart.kind == "text" if @editmode diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2c071be..9080750 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -76,94 +76,98 @@ module ApplicationHelper end def render_menu - # json_file = File.read(File.join(Rails.root, 'public', "menu.json")) - # @items = JSON.parse(json_file) - if $mobile.blank? - @pages = Page.root.sorted_published_child_pages - else - @pages = Page.root.sorted_published_child_pages_for_mobile - end + menu_html = Rails.cache.fetch(['main_menu',request.original_fullpath, I18n.locale], race_condition_ttl: 2.seconds) do + # json_file = File.read(File.join(Rails.root, 'public', "menu.json")) + # @items = JSON.parse(json_file) + if $mobile.blank? + @pages = Page.root.sorted_published_child_pages + else + @pages = Page.root.sorted_published_child_pages_for_mobile + end - def create_json(pages) - item = {} - pages.each do |page| - if page.child_page.size > 0 - if page.page_type == "page" - if $mobile.blank? - item["#{page.name}"] = {"url"=> "/#{locale.to_s}" + page.url, "children"=>create_json(page.sorted_published_child_pages), "target" => "_self"} - else - item["#{page.name}"] = {"url"=> "/#{locale.to_s}" + page.url, "children"=>create_json(page.sorted_published_child_pages_for_mobile), "target" => "_self"} + def create_json(pages) + item = {} + pages.each do |page| + if page.child_page.size > 0 + if page.page_type == "page" + if $mobile.blank? + item["#{page.name}"] = {"url"=> "/#{locale.to_s}" + page.url, "children"=>create_json(page.sorted_published_child_pages), "target" => "_self"} + else + item["#{page.name}"] = {"url"=> "/#{locale.to_s}" + page.url, "children"=>create_json(page.sorted_published_child_pages_for_mobile), "target" => "_self"} + end + elsif page.page_type == "link" + target = get_target(page.external_url) + if $mobile.blank? + item["#{page.name}"] = {"url"=> page.external_url, "children"=>create_json(page.sorted_published_child_pages), "target" => target} + else + item["#{page.name}"] = {"url"=> page.external_url, "children"=>create_json(page.sorted_published_child_pages_for_mobile), "target" => target} + end end - elsif page.page_type == "link" - target = get_target(page.external_url) - if $mobile.blank? - item["#{page.name}"] = {"url"=> page.external_url, "children"=>create_json(page.sorted_published_child_pages), "target" => target} - else - item["#{page.name}"] = {"url"=> page.external_url, "children"=>create_json(page.sorted_published_child_pages_for_mobile), "target" => target} + else + if page.page_type == "page" + item["#{page.name}"] = {"url"=> "/#{locale.to_s}" + page.url, "target" => "_self"} + elsif page.page_type == "link" + item["#{page.name}"] = {"url"=> page.external_url, "target" => get_target(page.external_url)} end end - else - if page.page_type == "page" - item["#{page.name}"] = {"url"=> "/#{locale.to_s}" + page.url, "target" => "_self"} - elsif page.page_type == "link" - item["#{page.name}"] = {"url"=> page.external_url, "target" => get_target(page.external_url)} + end + item + end + @items = create_json(@pages) + menu_file = File.open(File.join(Rails.root, 'app', 'templates', "#{@key}", "/home/menu.html.erb")) + doc = Nokogiri::HTML(menu_file, nil, "UTF-8") + menu_file.close + + temp = [] + @menus = [] + @menus_items = [] + + temp << doc.css("*[data-menu-level='0']") + temp << doc.css("*[data-menu-level='1']") + temp << doc.css("*[data-menu-level='2']") + + + temp[0] = temp[0].to_s.gsub(temp[1].to_s,"{{level}}") + temp[1] = temp[1].to_s.gsub(temp[2].to_s,"{{level}}") + temp[2] = temp[2].to_s + + + temp.each_with_index do |menu,i| + t = Nokogiri::HTML(menu, nil, "UTF-8") + a = t.css("*[data-menu-link='true']") + a[0]["href"] = "href_here" + a[0]["target"] = "target_here" + li = t.css("*[data-menu-level='#{i}'] > *") + @menus_items << li.to_html + ul = t.css("*[data-menu-level='#{i}']") + ul[0].inner_html = "{{here}}" + @menus << ul[0].to_html + end + + def create_menu(items,level) + html = "" + items.each do |key,item| + li = @menus_items[level].gsub("href_here",(item["url"] || "")) + li = li.gsub("{{link_name}}",(key || "")) + li = li.gsub("target_here",(item["target"] || "")) + li = request.original_fullpath == item['url'] ? li.gsub("{{active}}","active") : li.gsub("{{active}}","") + + if item["children"] && !item["children"].empty? + li = li.gsub("{{level}}",create_menu(item["children"],level + 1)) + else + li = li.gsub("{{level}}","") end + html = html + li end + html = @menus[level].gsub("{{here}}",html) + html = html.gsub("{{class_level}}",level.to_s) + html end - item - end - @items = create_json(@pages) - menu_file = File.open(File.join(Rails.root, 'app', 'templates', "#{@key}", "/home/menu.html.erb")) - doc = Nokogiri::HTML(menu_file, nil, "UTF-8") - menu_file.close - - temp = [] - @menus = [] - @menus_items = [] - - temp << doc.css("*[data-menu-level='0']") - temp << doc.css("*[data-menu-level='1']") - temp << doc.css("*[data-menu-level='2']") - - - temp[0] = temp[0].to_s.gsub(temp[1].to_s,"{{level}}") - temp[1] = temp[1].to_s.gsub(temp[2].to_s,"{{level}}") - temp[2] = temp[2].to_s - - - temp.each_with_index do |menu,i| - t = Nokogiri::HTML(menu, nil, "UTF-8") - a = t.css("*[data-menu-link='true']") - a[0]["href"] = "href_here" - a[0]["target"] = "target_here" - li = t.css("*[data-menu-level='#{i}'] > *") - @menus_items << li.to_html - ul = t.css("*[data-menu-level='#{i}']") - ul[0].inner_html = "{{here}}" - @menus << ul[0].to_html + h = create_menu(@items,0) + h.html_safe end - def create_menu(items,level) - html = "" - items.each do |key,item| - li = @menus_items[level].gsub("href_here",(item["url"] || "")) - li = li.gsub("{{link_name}}",(key || "")) - li = li.gsub("target_here",(item["target"] || "")) - li = request.original_fullpath == item['url'] ? li.gsub("{{active}}","active") : li.gsub("{{active}}","") - - if item["children"] && !item["children"].empty? - li = li.gsub("{{level}}",create_menu(item["children"],level + 1)) - else - li = li.gsub("{{level}}","") - end - html = html + li - end - html = @menus[level].gsub("{{here}}",html) - html = html.gsub("{{class_level}}",level.to_s) - html - end - h = create_menu(@items,0) - h.html_safe + menu_html end def render_view @@ -269,17 +273,18 @@ module ApplicationHelper doc.to_html.html_safe else unless data['impressionist'].blank? - impression = data['impressionist'].impressions.create - impression.user_id = request.session['user_id'] - impression.controller_name = params[:target_controller] - impression.action_name = params[:target_action] - impression.ip_address = request.remote_ip - impression.session_hash = request.session.id - impression.request_hash = @impressionist_hash - impression.referrer = request.referrer - impression.save - data['impressionist'].view_count = data['impressionist'].impressions.count - data['impressionist'].save + Thread.new do + impression = data['impressionist'].impressions.create + impression.user_id = request.session['user_id'] + impression.controller_name = params[:target_controller] + impression.action_name = params[:target_action] + impression.ip_address = request.remote_ip + impression.session_hash = request.session.id + impression.request_hash = @impressionist_hash + impression.referrer = request.referrer + impression.save + data['impressionist'].inc(view_count: 1) + end end wrap_elements = doc.css("*[data-list][data-level='0']") if wrap_elements.count == 0 diff --git a/app/helpers/orbit_backend_helper.rb b/app/helpers/orbit_backend_helper.rb index dc1e1b1..3ec9675 100644 --- a/app/helpers/orbit_backend_helper.rb +++ b/app/helpers/orbit_backend_helper.rb @@ -102,23 +102,29 @@ module OrbitBackendHelper end def display_visitors(options={}) - Impression.where(options).distinct(:request_hash).count + Impression.where(options).count end def display_visitors_today - display_visitors(created_at: {'$gte' => Time.now.beginning_of_day, '$lte' => Time.now}) + display_visitors(created_at: {'$gte' => Time.now.beginning_of_day}) end def display_visitors_this_week - display_visitors(created_at: {'$gte' => Time.now-7.days, '$lte' => Time.now}) + display_visitors(created_at: {'$gte' => Time.now.beginning_of_week}) end def display_visitors_this_month - display_visitors(created_at: {'$gte' => Time.now-30.days, '$lte' => Time.now}) + visitors_this_month = Rails.cache.fetch("visitors_this_month", expires_in: 1.day) do + display_visitors(created_at: {'$gte' => Time.now.beginning_of_month}) + end + visitors_this_month end def display_visitors_this_year - display_visitors(created_at: {'$gte' => Time.now-365.days, '$lte' => Time.now}) + visitors_this_year = Rails.cache.fetch("visitors_this_year", expires_in: 1.day) do + display_visitors(created_at: {'$gte' => Time.now.beginning_of_year}) + end + visitors_this_year end def get_month_traffic diff --git a/app/models/group.rb b/app/models/group.rb new file mode 100644 index 0000000..426f0ad --- /dev/null +++ b/app/models/group.rb @@ -0,0 +1,20 @@ +class Group + include Mongoid::Document + include Mongoid::Timestamps + include Slug + + field :title, as: :slug_title, type: String, localize: true + field :description, localize: true + field :admins, type: Array, default: [] + field :privacy, default: "closed" + mount_uploader :image, ImageUploader + + belongs_to :group_category + has_and_belongs_to_many :users + has_many :group_posts + + scope :closed, ->{ where(privacy: "closed") } + scope :open, ->{ where(privacy: "open") } + + +end \ No newline at end of file diff --git a/app/models/group_category.rb b/app/models/group_category.rb new file mode 100644 index 0000000..5a3e7a5 --- /dev/null +++ b/app/models/group_category.rb @@ -0,0 +1,8 @@ +class GroupCategory + include Mongoid::Document + include Mongoid::Timestamps + + field :title, localize: true + + has_many :groups +end \ No newline at end of file diff --git a/app/models/group_post.rb b/app/models/group_post.rb new file mode 100644 index 0000000..4177b46 --- /dev/null +++ b/app/models/group_post.rb @@ -0,0 +1,14 @@ +class GroupPost + include Mongoid::Document + include Mongoid::Timestamps + include Slug + + field :title, as: :slug_title, type: String + field :content + field :read_by, type: Array, default: [] + field :author, type: BSON::ObjectId + mount_uploader :image, ImageUploader + + belongs_to :group + +end \ No newline at end of file diff --git a/app/models/member_profile.rb b/app/models/member_profile.rb index 1e3680b..324f19b 100644 --- a/app/models/member_profile.rb +++ b/app/models/member_profile.rb @@ -52,6 +52,13 @@ class MemberProfile end end + def name_translations + { + "en" => "#{self.first_name_translations["en"]} #{self.last_name_translations["zh_tw"]}", + "zh_tw" => "#{self.last_name_translations["zh_tw"]} #{self.first_name_translations["zh_tw"]}" + } + end + def disable_role=(var) var[:id].each do |id,val| if (val=="true") diff --git a/app/models/module_app.rb b/app/models/module_app.rb index efee743..40d8ac0 100644 --- a/app/models/module_app.rb +++ b/app/models/module_app.rb @@ -15,6 +15,7 @@ class ModuleApp field :desktop_enabled, type: Boolean, default: false field :widget_settings field :store_permission_granted, type: Boolean, default: false + field :cache_models, type: Array, default: [] has_many :categories, dependent: :destroy, :autosave => true has_and_belongs_to_many :tags, dependent: :destroy, :autosave => true @@ -34,6 +35,7 @@ class ModuleApp self[:widget_methods] = reg.get_widget_methods self[:widget_settings] = reg.get_widget_settings self[:desktop_enabled] = reg.is_desktop_enabled + self[:cache_models] = reg.get_models_to_cache end def sub_managers diff --git a/app/models/orbit_observer.rb b/app/models/orbit_observer.rb new file mode 100644 index 0000000..92e1279 --- /dev/null +++ b/app/models/orbit_observer.rb @@ -0,0 +1,26 @@ +class OrbitObserver < Mongoid::Observer + models_to_cache = OrbitApp.get_models_for_caching + observe models_to_cache.keys + + def after_save(document) + clear_cache(document) + end + + def after_destroy(document) + clear_cache(document) + end + + def clear_cache(document) + model_module_hash = OrbitApp.get_model_hash_for_caching + case document.class.to_s + when 'Tag' + document.module_app.each do |module_app| + Rails.cache.delete_matched( /#{ module_app.key }/ ) + end + when 'Category' + Rails.cache.delete_matched( /#{ document.module_app.key }/ ) + else + Rails.cache.delete_matched( /#{model_module_hash[document.class.name.underscore.to_sym]}/ ) + end + end +end \ No newline at end of file diff --git a/app/models/page.rb b/app/models/page.rb index 539b2a7..aeea7fe 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -31,6 +31,15 @@ class Page belongs_to :parent_page, :class_name => 'Page', :inverse_of => :child_page before_create :assign_page_number + + after_save :clear_cache + after_destroy :clear_cache + + def clear_cache + I18n.available_locales.each do |locale| + Rails.cache.delete_matched( /main_menu/ ) + end + end def assign_page_number parent_page = self.parent_page diff --git a/app/models/sub_part.rb b/app/models/sub_part.rb index fb2c1e4..c2dd336 100644 --- a/app/models/sub_part.rb +++ b/app/models/sub_part.rb @@ -16,4 +16,11 @@ class SubPart belongs_to :page_part belongs_to :mobile_page_part + + after_save :clear_cache + after_destroy :clear_cache + + def clear_cache + Rails.cache.delete_matched( /#{self.id.to_s}/ ) + end end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index f11c615..fc44c65 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -20,6 +20,7 @@ class User belongs_to :workgroup has_many :authorizations belongs_to :member_profile + has_and_belongs_to_many :groups has_one :facebook, :autosave => true, :dependent => :destroy has_one :google, :autosave => true, :dependent => :destroy # has_one :desktop, :dependent => :destroy diff --git a/app/templates/orbit_bootstrap/assets/images/announcement-default.jpg b/app/templates/orbit_bootstrap/assets/images/announcement-default.jpg index 3307828..b6b82f0 100644 Binary files a/app/templates/orbit_bootstrap/assets/images/announcement-default.jpg and b/app/templates/orbit_bootstrap/assets/images/announcement-default.jpg differ diff --git a/app/templates/orbit_bootstrap/assets/javascripts/app.js b/app/templates/orbit_bootstrap/assets/javascripts/app.js index f806c79..139e974 100644 --- a/app/templates/orbit_bootstrap/assets/javascripts/app.js +++ b/app/templates/orbit_bootstrap/assets/javascripts/app.js @@ -1,108 +1,131 @@ -(function($) { +(function( $ ) { "use strict" function init() { - var doc = document; + var doc = document, + lang = doc.documentElement.lang; var orbit = { - // Cross browser add class function - addClass: function(el, className) { - if (el.classList) { - el.classList.add(className); - } else { - el.className += ' ' + className; - } - }, - // Cross browser has class function - hasClass: function(el, cls) { - return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1; - }, - // Add class name to the menu item when its children items are more than eight - addMegaDropdownClass: function(el, len) { - for (var i = 0; i < el.length; i++) { - if (el[i].children.length > len) { - orbit.addClass(el[i].parentNode, 'mega-dropdown'); + + helpers : { + // Cross-browser class manipulation + addClass: function( el, className ) { + if ( el.classList ) { + el.classList.add( className ); + } else { + el.className += " " + className; + } + }, + hasClass: function( el, cls ) { + return ( " " + el.className + " " ).indexOf( " " + cls + " " ) > -1; } - } - }, - // Append caret to menu item if it has dropdown - addCaret: function() { - var list = doc.querySelectorAll('.page_menu.level_2'); - for (var i = 0, len = list.length; i < len; i++) { - var node = doc.createElement('span'); - node.className = 'caret'; - list[i].parentNode.appendChild(node); - } - }, - // Add link and cursor class name on element that has data-link attribute - addLinkOnADBanner: function(els) { - $.each(els, function(i) { - if ($(this).data('link') !== "" && !$(this).hasClass('youtube')) { - $(this).on('click', function() { - var target = $(this).data('target'), - link = $(this).data('link'); - if (target === '_blank') { - window.open(link, target); - } else { - window.location.href = link; + }, + + plugins : { + // RWD image resize script + bullEye: function() { + $( ".bullseye" ).bullseye({ + fadeEffect: false + }); + } + }, + + nav : { + // Add class name to the menu item when its child items are more than 8 items + addMegaDropdownClass: function( els, len, className ) { + var els = doc.querySelectorAll( els ), + elsLen = els.length, + i = -1; + + if( elsLen > 1 ) { + for ( i = 0; i < elsLen; i++ ) { + if ( els[ i ].children.length > len ) { + this.helpers.addClass( els[ i ].parentNode, className || "mega-dropdown" ); + } } - }).addClass('cursor'); + } + }, + // Append caret to menu item if it has dropdown + addCaret: function() { + var els = doc.querySelectorAll( ".page_menu.level_2" ), + len = els.length, + i = -1; + + for ( i = 0; i < len; i++ ) { + var node = doc.createElement( "span" ); + node.className = "caret"; + els[ i ].parentNode.appendChild( node ); + } } - }); - }, - // Announcement text truncation - truncation: function(el, len) { - for (var i = 0; i < el.length; i++) { - if (el[i].firstChild !== null) { - if (el[i].firstChild.length > len) { - var newStr = el[i].firstChild.nodeValue, - newStr = newStr.substring(0, len) + '...' - el - .eq(i) - .text(newStr); + }, + + announcement: { + // Announcement text truncation + truncateAnnouncement: function( els, maxLen ) { + var els = doc.querySelectorAll( els ), + newTitle = ""; + i = -1, + elsLen = els.length; + + for( i = 0; i < elsLen; i ++ ) { + if ( els[ i ].firstChild !== null ) { + if( els[ i ].firstChild.length > maxLen ) { + newTitle = els[ i ].firstChild.textContent; + els[ i ].textContent = newTitle.substring( 0, maxLen ) + "..."; + } + } + } + } + }, + + // Add link and cursor class name on element that has data-link attribute + addLinkOnADBanner: function( els ) { + $.each( els, function() { + if ( $( this ).data( "link" ) !== "" && !$( this ).hasClass( "youtube" ) ) { + $( this ).on( "click", function() { + var target = $( this ).data( "target" ), + link = $( this ).data( "link" ); + if ( target === "_blank" ) { + window.open( link, target ); + } else { + window.location.href = link; + } + }).addClass( "cursor" ); + } + }); + }, + + // Sitemenu dropdown + sitemenuDropdown: function( els ) { + var els = doc.querySelectorAll(".sitemenu__list.level-2"), + len = els.length, + i = -1; + for ( i = 0; i < len; i++ ) { + if ( els[ i ].children.length ) { + var caret = doc.createElement( "span" ); + caret.className = "sitemenu___dropdown-toggle fa fa-caret-down"; + caret.setAttribute( "data-toggle", "dropdown" ); + + els[ i ].parentNode.insertBefore( caret, els[ i ] ); + this.helpers.addClass( els[ i ], "dropdown-menu" ); } } } - }, - // Sitemenu dropdown - sitemenuDropdown: function() { - var el = doc.querySelectorAll('.sitemenu__list.level-2'); - for (var i = 0, len = el.length; i < len; i++) { - if (el[i].hasChildNodes()) { - var caret = doc.createElement('span'); - caret.className = 'sitemenu___dropdown-toggle fa fa-caret-down'; - caret.setAttribute('data-toggle', 'dropdown'); - - el[i].parentNode.insertBefore(caret, el[i]); - orbit.addClass(el[i], 'dropdown-menu'); - } - } - } - } - - var plugin = { - // RWD image resize script - bullEye: function() { - $(".bullseye").bullseye({ - fadeEffect: false - }); - } } // Specific functions that will be running on homepage - if (doc.body.getAttribute('data-module') === 'home') { + if ( doc.body.getAttribute( "data-module" ) === "home" ) { } // Functions that will be running on every page orbit.sitemenuDropdown(); - plugin.bullEye(); + orbit.plugins.bullEye(); } - - $(document).ready(function($) { + $( document ).ready(function() { init(); }); -}(jQuery)); +}( jQuery )); diff --git a/app/templates/orbit_bootstrap/assets/stylesheets/bootstrap/_mixins.scss b/app/templates/orbit_bootstrap/assets/stylesheets/bootstrap/_mixins.scss new file mode 100644 index 0000000..74ccf9f --- /dev/null +++ b/app/templates/orbit_bootstrap/assets/stylesheets/bootstrap/_mixins.scss @@ -0,0 +1,947 @@ +// +// Mixins +// -------------------------------------------------- + + +// Utilities +// ------------------------- + +// Clearfix +// Source: http://nicolasgallagher.com/micro-clearfix-hack/ +// +// For modern browsers +// 1. The space content is one way to avoid an Opera bug when the +// contenteditable attribute is included anywhere else in the document. +// Otherwise it causes space to appear at the top and bottom of elements +// that are clearfixed. +// 2. The use of `table` rather than `block` is only necessary if using +// `:before` to contain the top-margins of child elements. +@mixin clearfix() { + &:before, + &:after { + content: " "; // 1 + display: table; // 2 + } + &:after { + clear: both; + } +} + +// WebKit-style focus +@mixin tab-focus() { + // Default + outline: thin dotted; + // WebKit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +// Center-align a block level element +@mixin center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} + +// Sizing shortcuts +@mixin size($width, $height) { + width: $width; + height: $height; +} +@mixin square($size) { + @include size($size, $size); +} + +// Placeholder text +@mixin placeholder($color: $input-color-placeholder) { + &::-moz-placeholder { color: $color; // Firefox + opacity: 1; } // See https://github.com/twbs/bootstrap/pull/11526 + &:-ms-input-placeholder { color: $color; } // Internet Explorer 10+ + &::-webkit-input-placeholder { color: $color; } // Safari and Chrome +} + +// Text overflow +// Requires inline-block or block for proper styling +@mixin text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// CSS image replacement +// +// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for +// mixins being reused as classes with the same name, this doesn't hold up. As +// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. Note +// that we cannot chain the mixins together in Less, so they are repeated. +// +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 + +// Deprecated as of v3.0.1 (will be removed in v4) +@mixin hide-text() { + font: #{0/0} a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +// New mixin to use as of v3.0.1 +@mixin text-hide() { + @include hide-text(); +} + + + +// CSS3 PROPERTIES +// -------------------------------------------------- + +// Single side border-radius +@mixin border-top-radius($radius) { + border-top-right-radius: $radius; + border-top-left-radius: $radius; +} +@mixin border-right-radius($radius) { + border-bottom-right-radius: $radius; + border-top-right-radius: $radius; +} +@mixin border-bottom-radius($radius) { + border-bottom-right-radius: $radius; + border-bottom-left-radius: $radius; +} +@mixin border-left-radius($radius) { + border-bottom-left-radius: $radius; + border-top-left-radius: $radius; +} + +// Drop shadows +// +// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's +// supported browsers that have box shadow capabilities now support the +// standard `box-shadow` property. +@mixin box-shadow($shadow...) { + -webkit-box-shadow: $shadow; // iOS <4.3 & Android <4.1 + box-shadow: $shadow; +} + +// Transitions +@mixin transition($transition...) { + -webkit-transition: $transition; + transition: $transition; +} +@mixin transition-property($transition-property...) { + -webkit-transition-property: $transition-property; + transition-property: $transition-property; +} +@mixin transition-delay($transition-delay) { + -webkit-transition-delay: $transition-delay; + transition-delay: $transition-delay; +} +@mixin transition-duration($transition-duration...) { + -webkit-transition-duration: $transition-duration; + transition-duration: $transition-duration; +} +@mixin transition-transform($transition...) { + -webkit-transition: -webkit-transform $transition; + -moz-transition: -moz-transform $transition; + -o-transition: -o-transform $transition; + transition: transform $transition; +} + +// Transformations +@mixin rotate($degrees) { + -webkit-transform: rotate($degrees); + -ms-transform: rotate($degrees); // IE9 only + transform: rotate($degrees); +} +@mixin scale($scale-args...) { + -webkit-transform: scale($scale-args); + -ms-transform: scale($scale-args); // IE9 only + transform: scale($scale-args); +} +@mixin translate($x, $y) { + -webkit-transform: translate($x, $y); + -ms-transform: translate($x, $y); // IE9 only + transform: translate($x, $y); +} +@mixin skew($x, $y) { + -webkit-transform: skew($x, $y); + -ms-transform: skewX($x) skewY($y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+ + transform: skew($x, $y); +} +@mixin translate3d($x, $y, $z) { + -webkit-transform: translate3d($x, $y, $z); + transform: translate3d($x, $y, $z); +} + +@mixin rotateX($degrees) { + -webkit-transform: rotateX($degrees); + -ms-transform: rotateX($degrees); // IE9 only + transform: rotateX($degrees); +} +@mixin rotateY($degrees) { + -webkit-transform: rotateY($degrees); + -ms-transform: rotateY($degrees); // IE9 only + transform: rotateY($degrees); +} +@mixin perspective($perspective) { + -webkit-perspective: $perspective; + -moz-perspective: $perspective; + perspective: $perspective; +} +@mixin perspective-origin($perspective) { + -webkit-perspective-origin: $perspective; + -moz-perspective-origin: $perspective; + perspective-origin: $perspective; +} +@mixin transform-origin($origin) { + -webkit-transform-origin: $origin; + -moz-transform-origin: $origin; + -ms-transform-origin: $origin; // IE9 only + transform-origin: $origin; +} + +// Animations +@mixin animation($animation) { + -webkit-animation: $animation; + animation: $animation; +} +@mixin animation-name($name) { + -webkit-animation-name: $name; + animation-name: $name; +} +@mixin animation-duration($duration) { + -webkit-animation-duration: $duration; + animation-duration: $duration; +} +@mixin animation-timing-function($timing-function) { + -webkit-animation-timing-function: $timing-function; + animation-timing-function: $timing-function; +} +@mixin animation-delay($delay) { + -webkit-animation-delay: $delay; + animation-delay: $delay; +} +@mixin animation-iteration-count($iteration-count) { + -webkit-animation-iteration-count: $iteration-count; + animation-iteration-count: $iteration-count; +} +@mixin animation-direction($direction) { + -webkit-animation-direction: $direction; + animation-direction: $direction; +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden` +@mixin backface-visibility($visibility){ + -webkit-backface-visibility: $visibility; + -moz-backface-visibility: $visibility; + backface-visibility: $visibility; +} + +// Box sizing +@mixin box-sizing($boxmodel) { + -webkit-box-sizing: $boxmodel; + -moz-box-sizing: $boxmodel; + box-sizing: $boxmodel; +} + +// User select +// For selecting text on the page +@mixin user-select($select) { + -webkit-user-select: $select; + -moz-user-select: $select; + -ms-user-select: $select; // IE10+ + user-select: $select; +} + +// Resize anything +@mixin resizable($direction) { + resize: $direction; // Options: horizontal, vertical, both + overflow: auto; // Safari fix +} + +// CSS3 Content Columns +@mixin content-columns($column-count, $column-gap: $grid-gutter-width) { + -webkit-column-count: $column-count; + -moz-column-count: $column-count; + column-count: $column-count; + -webkit-column-gap: $column-gap; + -moz-column-gap: $column-gap; + column-gap: $column-gap; +} + +// Optional hyphenation +@mixin hyphens($mode: auto) { + word-wrap: break-word; + -webkit-hyphens: $mode; + -moz-hyphens: $mode; + -ms-hyphens: $mode; // IE10+ + -o-hyphens: $mode; + hyphens: $mode; +} + +// Opacity +@mixin opacity($opacity) { + opacity: $opacity; + // IE8 filter + $opacity-ie: ($opacity * 100); + filter: #{alpha(opacity=$opacity-ie)}; +} + + + +// GRADIENTS +// -------------------------------------------------- + + + +// Horizontal gradient, from left to right +// +// Creates two color stops, start and end, by specifying a color and position for each color stop. +// Color stops are not available in IE9 and below. +@mixin gradient-horizontal($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) { + background-image: -webkit-linear-gradient(left, color-stop($start-color $start-percent), color-stop($end-color $end-percent)); // Safari 5.1-6, Chrome 10+ + background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=1); // IE9 and down +} + +// Vertical gradient, from top to bottom +// +// Creates two color stops, start and end, by specifying a color and position for each color stop. +// Color stops are not available in IE9 and below. +@mixin gradient-vertical($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) { + background-image: -webkit-linear-gradient(top, $start-color $start-percent, $end-color $end-percent); // Safari 5.1-6, Chrome 10+ + background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=0); // IE9 and down +} + +@mixin gradient-directional($start-color: #555, $end-color: #333, $deg: 45deg) { + background-repeat: repeat-x; + background-image: -webkit-linear-gradient($deg, $start-color, $end-color); // Safari 5.1-6, Chrome 10+ + background-image: linear-gradient($deg, $start-color, $end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ +} +@mixin gradient-horizontal-three-colors($start-color: #00b3ee, $mid-color: #7a43b6, $color-stop: 50%, $end-color: #c3325f) { + background-image: -webkit-linear-gradient(left, $start-color, $mid-color $color-stop, $end-color); + background-image: linear-gradient(to right, $start-color, $mid-color $color-stop, $end-color); + background-repeat: no-repeat; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=1); // IE9 and down, gets no color-stop at all for proper fallback +} +@mixin gradient-vertical-three-colors($start-color: #00b3ee, $mid-color: #7a43b6, $color-stop: 50%, $end-color: #c3325f) { + background-image: -webkit-linear-gradient($start-color, $mid-color $color-stop, $end-color); + background-image: linear-gradient($start-color, $mid-color $color-stop, $end-color); + background-repeat: no-repeat; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{ie-hex-str($start-color)}', endColorstr='#{ie-hex-str($end-color)}', GradientType=0); // IE9 and down, gets no color-stop at all for proper fallback +} +@mixin gradient-radial($inner-color: #555, $outer-color: #333) { + background-image: -webkit-radial-gradient(circle, $inner-color, $outer-color); + background-image: radial-gradient(circle, $inner-color, $outer-color); + background-repeat: no-repeat; +} +@mixin gradient-striped($color: rgba(255,255,255,.15), $angle: 45deg) { + background-image: -webkit-linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent); + background-image: linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent); +} + +// Reset filters for IE +// +// When you need to remove a gradient background, do not forget to use this to reset +// the IE filter for IE9 and below. +@mixin reset-filter() { + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} + + + +// Retina images +// +// Short retina mixin for setting background-image and -size + +@mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) { + background-image: url(if($bootstrap-sass-asset-helper, twbs-image-path("#{$file-1x}"), "#{$file-1x}")); + + @media + only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and ( min--moz-device-pixel-ratio: 2), + only screen and ( -o-min-device-pixel-ratio: 2/1), + only screen and ( min-device-pixel-ratio: 2), + only screen and ( min-resolution: 192dpi), + only screen and ( min-resolution: 2dppx) { + background-image: url(if($bootstrap-sass-asset-helper, twbs-image-path("#{$file-2x}"), "#{$file-2x}")); + background-size: $width-1x $height-1x; + } +} + + +// Responsive image +// +// Keep images from scaling beyond the width of their parents. + +@mixin img-responsive($display: block) { + display: $display; + max-width: 100%; // Part 1: Set a maximum relative to the parent + height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching +} + + +// COMPONENT MIXINS +// -------------------------------------------------- + +// Horizontal dividers +// ------------------------- +// Dividers (basically an hr) within dropdowns and nav lists +@mixin nav-divider($color: #e5e5e5) { + height: 1px; + margin: (($line-height-computed / 2) - 1) 0; + overflow: hidden; + background-color: $color; +} + +// Panels +// ------------------------- +@mixin panel-variant($border, $heading-text-color, $heading-bg-color, $heading-border) { + border-color: $border; + + & > .panel-heading { + color: $heading-text-color; + background-color: $heading-bg-color; + border-color: $heading-border; + + + .panel-collapse .panel-body { + border-top-color: $border; + } + } + & > .panel-footer { + + .panel-collapse .panel-body { + border-bottom-color: $border; + } + } +} + +// Alerts +// ------------------------- +@mixin alert-variant($background, $border, $text-color) { + background-color: $background; + border-color: $border; + color: $text-color; + + hr { + border-top-color: darken($border, 5%); + } + .alert-link { + color: darken($text-color, 10%); + } +} + +// Tables +// ------------------------- +@mixin table-row-variant($state, $background) { + // Exact selectors below required to override `.table-striped` and prevent + // inheritance to nested tables. + .table > thead > tr, + .table > tbody > tr, + .table > tfoot > tr { + > td.#{$state}, + > th.#{$state}, + &.#{$state} > td, + &.#{$state} > th { + background-color: $background; + } + } + + // Hover states for `.table-hover` + // Note: this is not available for cells or rows within `thead` or `tfoot`. + .table-hover > tbody > tr { + > td.#{$state}:hover, + > th.#{$state}:hover, + &.#{$state}:hover > td, + &.#{$state}:hover > th { + background-color: darken($background, 5%); + } + } +} + +// List Groups +// ------------------------- +@mixin list-group-item-variant($state, $background, $color) { + .list-group-item-#{$state} { + color: $color; + background-color: $background; + + // [converter] extracted a& to a.list-group-item-#{$state} + } + + a.list-group-item-#{$state} { + color: $color; + + .list-group-item-heading { color: inherit; } + + &:hover, + &:focus { + color: $color; + background-color: darken($background, 5%); + } + &.active, + &.active:hover, + &.active:focus { + color: #fff; + background-color: $color; + border-color: $color; + } + } +} + +// Button variants +// ------------------------- +// Easily pump out default styles, as well as :hover, :focus, :active, +// and disabled options for all buttons +@mixin button-variant($color, $background, $border) { + color: $color; + background-color: $background; + border-color: $border; + + &:hover, + &:focus, + &:active, + &.active { + color: $color; + background-color: darken($background, 8%); + border-color: darken($border, 12%); + } + .open & { &.dropdown-toggle { + color: $color; + background-color: darken($background, 8%); + border-color: darken($border, 12%); + } } + &:active, + &.active { + background-image: none; + } + .open & { &.dropdown-toggle { + background-image: none; + } } + &.disabled, + &[disabled], + fieldset[disabled] & { + &, + &:hover, + &:focus, + &:active, + &.active { + background-color: $background; + border-color: $border; + } + } + + .badge { + color: $background; + background-color: $color; + } +} + +// Button sizes +// ------------------------- +@mixin button-size($padding-vertical, $padding-horizontal, $font-size, $line-height, $border-radius) { + padding: $padding-vertical $padding-horizontal; + font-size: $font-size; + line-height: $line-height; + border-radius: $border-radius; +} + +// Pagination +// ------------------------- +@mixin pagination-size($padding-vertical, $padding-horizontal, $font-size, $border-radius) { + > li { + > a, + > span { + padding: $padding-vertical $padding-horizontal; + font-size: $font-size; + } + &:first-child { + > a, + > span { + @include border-left-radius($border-radius); + } + } + &:last-child { + > a, + > span { + @include border-right-radius($border-radius); + } + } + } +} + +// Labels +// ------------------------- +@mixin label-variant($color) { + background-color: $color; + &[href] { + &:hover, + &:focus { + background-color: darken($color, 10%); + } + } +} + +// Contextual backgrounds +// ------------------------- +// [converter] $parent hack +@mixin bg-variant($parent, $color) { + #{$parent} { + background-color: $color; + } + a#{$parent}:hover { + background-color: darken($color, 10%); + } +} + +// Typography +// ------------------------- +// [converter] $parent hack +@mixin text-emphasis-variant($parent, $color) { + #{$parent} { + color: $color; + } + a#{$parent}:hover { + color: darken($color, 10%); + } +} + +// Navbar vertical align +// ------------------------- +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin. +@mixin navbar-vertical-align($element-height) { + margin-top: (($navbar-height - $element-height) / 2); + margin-bottom: (($navbar-height - $element-height) / 2); +} + +// Progress bars +// ------------------------- +@mixin progress-bar-variant($color) { + background-color: $color; + .progress-striped & { + @include gradient-striped(); + } +} + +// Responsive utilities +// ------------------------- +// More easily include all the states for responsive-utilities.less. +// [converter] $parent hack +@mixin responsive-visibility($parent) { + #{$parent} { + display: block !important; + } + table#{$parent} { display: table; } + tr#{$parent} { display: table-row !important; } + th#{$parent}, + td#{$parent} { display: table-cell !important; } +} + +// [converter] $parent hack +@mixin responsive-invisibility($parent) { + #{$parent} { + display: none !important; + } +} + + +// Grid System +// ----------- + +// Centered container element +@mixin container-fixed() { + margin-right: auto; + margin-left: auto; + padding-left: ($grid-gutter-width / 2); + padding-right: ($grid-gutter-width / 2); + @include clearfix(); +} + +// Creates a wrapper for a series of columns +@mixin make-row($gutter: $grid-gutter-width) { + margin-left: ($gutter / -2); + margin-right: ($gutter / -2); + @include clearfix(); +} + +// Generate the extra small columns +@mixin make-xs-column($columns, $gutter: $grid-gutter-width) { + position: relative; + float: left; + width: percentage(($columns / $grid-columns)); + min-height: 1px; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); +} +@mixin make-xs-column-offset($columns) { + @media (min-width: $screen-xs-min) { + margin-left: percentage(($columns / $grid-columns)); + } +} +@mixin make-xs-column-push($columns) { + @media (min-width: $screen-xs-min) { + left: percentage(($columns / $grid-columns)); + } +} +@mixin make-xs-column-pull($columns) { + @media (min-width: $screen-xs-min) { + right: percentage(($columns / $grid-columns)); + } +} + + +// Generate the small columns +@mixin make-sm-column($columns, $gutter: $grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); + + @media (min-width: $screen-sm-min) { + float: left; + width: percentage(($columns / $grid-columns)); + } +} +@mixin make-sm-column-offset($columns) { + @media (min-width: $screen-sm-min) { + margin-left: percentage(($columns / $grid-columns)); + } +} +@mixin make-sm-column-push($columns) { + @media (min-width: $screen-sm-min) { + left: percentage(($columns / $grid-columns)); + } +} +@mixin make-sm-column-pull($columns) { + @media (min-width: $screen-sm-min) { + right: percentage(($columns / $grid-columns)); + } +} + + +// Generate the medium columns +@mixin make-md-column($columns, $gutter: $grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); + + @media (min-width: $screen-md-min) { + float: left; + width: percentage(($columns / $grid-columns)); + } +} +@mixin make-md-column-offset($columns) { + @media (min-width: $screen-md-min) { + margin-left: percentage(($columns / $grid-columns)); + } +} +@mixin make-md-column-push($columns) { + @media (min-width: $screen-md-min) { + left: percentage(($columns / $grid-columns)); + } +} +@mixin make-md-column-pull($columns) { + @media (min-width: $screen-md-min) { + right: percentage(($columns / $grid-columns)); + } +} + + +// Generate the large columns +@mixin make-lg-column($columns, $gutter: $grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); + + @media (min-width: $screen-lg-min) { + float: left; + width: percentage(($columns / $grid-columns)); + } +} +@mixin make-lg-column-offset($columns) { + @media (min-width: $screen-lg-min) { + margin-left: percentage(($columns / $grid-columns)); + } +} +@mixin make-lg-column-push($columns) { + @media (min-width: $screen-lg-min) { + left: percentage(($columns / $grid-columns)); + } +} +@mixin make-lg-column-pull($columns) { + @media (min-width: $screen-lg-min) { + right: percentage(($columns / $grid-columns)); + } +} + + +// Framework grid generation +// +// Used only by Bootstrap to generate the correct number of grid classes given +// any value of `$grid-columns`. + +// [converter] This is defined recursively in LESS, but Sass supports real loops +@mixin make-grid-columns() { + $list: ''; + $i: 1; + $list: ".col-xs-#{$i}, .col-sm-#{$i}, .col-md-#{$i}, .col-lg-#{$i}"; + @for $i from (1 + 1) through $grid-columns { + $list: "#{$list}, .col-xs-#{$i}, .col-sm-#{$i}, .col-md-#{$i}, .col-lg-#{$i}"; + } + #{$list} { + position: relative; + // Prevent columns from collapsing when empty + min-height: 1px; + // Inner gutter via padding + padding-left: ($grid-gutter-width / 2); + padding-right: ($grid-gutter-width / 2); + } +} + + +// [converter] This is defined recursively in LESS, but Sass supports real loops +@mixin float-grid-columns($class) { + $list: ''; + $i: 1; + $list: ".col-#{$class}-#{$i}"; + @for $i from (1 + 1) through $grid-columns { + $list: "#{$list}, .col-#{$class}-#{$i}"; + } + #{$list} { + float: left; + } +} + + +@mixin calc-grid-column($index, $class, $type) { + @if ($type == width) and ($index > 0) { + .col-#{$class}-#{$index} { + width: percentage(($index / $grid-columns)); + } + } + @if ($type == push) { + .col-#{$class}-push-#{$index} { + left: percentage(($index / $grid-columns)); + } + } + @if ($type == pull) { + .col-#{$class}-pull-#{$index} { + right: percentage(($index / $grid-columns)); + } + } + @if ($type == offset) { + .col-#{$class}-offset-#{$index} { + margin-left: percentage(($index / $grid-columns)); + } + } +} + +// [converter] This is defined recursively in LESS, but Sass supports real loops +@mixin loop-grid-columns($columns, $class, $type) { + @for $i from 0 through $columns { + @include calc-grid-column($i, $class, $type); + } +} + + +// Create grid for specific class +@mixin make-grid($class) { + @include float-grid-columns($class); + @include loop-grid-columns($grid-columns, $class, width); + @include loop-grid-columns($grid-columns, $class, pull); + @include loop-grid-columns($grid-columns, $class, push); + @include loop-grid-columns($grid-columns, $class, offset); +} + +// Form validation states +// +// Used in forms.less to generate the form validation CSS for warnings, errors, +// and successes. + +@mixin form-control-validation($text-color: #555, $border-color: #ccc, $background-color: #f5f5f5) { + // Color the label and help text + .help-block, + .control-label, + .radio, + .checkbox, + .radio-inline, + .checkbox-inline { + color: $text-color; + } + // Set the border and box shadow on specific inputs to match + .form-control { + border-color: $border-color; + @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work + &:focus { + border-color: darken($border-color, 10%); + $shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten($border-color, 20%); + @include box-shadow($shadow); + } + } + // Set validation states also for addons + .input-group-addon { + color: $text-color; + border-color: $border-color; + background-color: $background-color; + } + // Optional feedback icon + .form-control-feedback { + color: $text-color; + } +} + +// Form control focus state +// +// Generate a customized focus state and for any input with the specified color, +// which defaults to the `$input-focus-border` variable. +// +// We highly encourage you to not customize the default value, but instead use +// this to tweak colors on an as-needed basis. This aesthetic change is based on +// WebKit's default styles, but applicable to a wider range of browsers. Its +// usability and accessibility should be taken into account with any change. +// +// Example usage: change the default blue border and shadow to white for better +// contrast against a dark gray background. + +@mixin form-control-focus($color: $input-border-focus) { + $color-rgba: rgba(red($color), green($color), blue($color), .6); + &:focus { + border-color: $color; + outline: 0; + @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px $color-rgba); + } +} + +// Form control sizing +// +// Relative text size, padding, and border-radii changes for form controls. For +// horizontal sizing, wrap controls in the predefined grid classes. `