Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
did | 8658f4229a | |
did | 5dd9481ec2 | |
did | b89d6f2dbd | |
did | 0b27cd5d1b | |
did | e9c1c1d98f | |
did | e1c2687681 | |
did | ad68826e84 | |
did | c68eb321ff |
1
Gemfile
1
Gemfile
|
@ -11,6 +11,7 @@ gem 'devise', '1.3.4'
|
|||
gem 'devise_bushido_authenticatable', '1.0.0.alpha10', :require => 'devise_cas_authenticatable'
|
||||
|
||||
gem 'mongoid', '~> 2.0.2'
|
||||
gem 'mongoid_i18n', :require => 'mongoid/i18n'
|
||||
gem 'bson_ext', '~> 1.3.0'
|
||||
gem 'locomotive_mongoid_acts_as_tree', '0.1.5.7', :require => 'mongoid_acts_as_tree'
|
||||
gem 'will_paginate', '~> 3.0.0'
|
||||
|
|
|
@ -173,6 +173,9 @@ GEM
|
|||
activemodel (~> 3.0)
|
||||
mongo (~> 1.3)
|
||||
tzinfo (~> 0.3.22)
|
||||
mongoid_i18n (0.3.1)
|
||||
bson_ext
|
||||
mongoid (>= 2.0.0)
|
||||
net-ssh (2.1.4)
|
||||
nokogiri (1.5.0)
|
||||
open4 (1.1.0)
|
||||
|
@ -316,6 +319,7 @@ DEPENDENCIES
|
|||
mimetype-fu
|
||||
mocha (= 0.9.12)
|
||||
mongoid (~> 2.0.2)
|
||||
mongoid_i18n
|
||||
pickle
|
||||
rack-cache
|
||||
rails (= 3.0.10)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
#site-locale-picker
|
||||
%span.hand
|
||||
|
||||
|
||||
%ul
|
||||
- @locales.each_with_index do |locale, index|
|
||||
%li{ :class => "#{'first' if index == 0}" }
|
||||
|
||||
= image_tag "admin/icons/flags/#{locale}.png", :class => 'flag'
|
||||
|
||||
%span.text= locale
|
|
@ -0,0 +1,15 @@
|
|||
class Admin::SiteLocalePickerCell < Cell::Base
|
||||
|
||||
def show(args)
|
||||
site = args[:site]
|
||||
locale = args[:locale].to_s
|
||||
|
||||
if site.locales.empty? || site.locales.size < 2
|
||||
''
|
||||
else
|
||||
@locales = [locale] + (site.locales - [locale])
|
||||
render
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -15,6 +15,8 @@ module Admin
|
|||
|
||||
before_filter :set_locale
|
||||
|
||||
before_filter :set_site_locale
|
||||
|
||||
helper_method :sections, :current_site_url, :site_url, :page_url, :current_ability
|
||||
|
||||
# https://rails.lighthouseapp.com/projects/8994/tickets/1905-apphelpers-within-plugin-not-being-mixed-in
|
||||
|
@ -71,7 +73,25 @@ module Admin
|
|||
end
|
||||
|
||||
def set_locale
|
||||
I18n.locale = current_admin.locale rescue Locomotive.config.default_locale
|
||||
I18n.locale = current_admin.locale rescue Locomotive.config.default_locale # for the back-office
|
||||
end
|
||||
|
||||
def set_site_locale
|
||||
I18n.default_site_locale = current_site.default_locale
|
||||
|
||||
if params[:site_locale].present?
|
||||
session[:site_locale] = params[:site_locale]
|
||||
end
|
||||
|
||||
unless current_site.locales.include?(session[:site_locale])
|
||||
session[:site_locale] = current_site.default_locale
|
||||
end
|
||||
|
||||
I18n.site_locale = session[:site_locale]
|
||||
end
|
||||
|
||||
def back_to_default_site_locale
|
||||
session[:site_locale] = I18n.site_locale = current_site.default_locale
|
||||
end
|
||||
|
||||
# ___ site/page urls builder ___
|
||||
|
|
|
@ -11,6 +11,8 @@ module Admin
|
|||
|
||||
before_filter :authorize_content
|
||||
|
||||
before_filter :back_to_default_site_locale, :only => %w(new create)
|
||||
|
||||
helper_method :breadcrumb_root, :breadcrumb_url, :back_url
|
||||
|
||||
def index
|
||||
|
|
|
@ -5,6 +5,8 @@ module Admin
|
|||
|
||||
respond_to :json, :only => [:update, :sort, :get_path]
|
||||
|
||||
before_filter :back_to_default_site_locale, :only => %w(new create)
|
||||
|
||||
def index
|
||||
@pages = current_site.all_pages_in_once
|
||||
end
|
||||
|
|
|
@ -30,4 +30,7 @@ module Admin::SitesHelper
|
|||
Locomotive.config.multi_sites?
|
||||
end
|
||||
|
||||
def ordered_site_locales(site)
|
||||
site.locales + (Locomotive.config.site_locales - site.locales)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,12 +2,15 @@ class ContentInstance
|
|||
|
||||
include Mongoid::Document
|
||||
include Mongoid::Timestamps
|
||||
include Mongoid::I18n
|
||||
|
||||
## extensions ##
|
||||
include CustomFields::ProxyClassEnabler
|
||||
include Extensions::Shared::Seo
|
||||
|
||||
## fields (dynamic fields) ##
|
||||
localized_fields :_position_in_list, /^custom_field_/
|
||||
|
||||
field :_slug
|
||||
field :_position_in_list, :type => Integer, :default => 0
|
||||
field :_visible, :type => Boolean, :default => true
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
class EditableElement
|
||||
|
||||
include Mongoid::Document
|
||||
include Mongoid::I18n
|
||||
|
||||
## fields ##
|
||||
field :slug
|
||||
field :block
|
||||
field :default_content
|
||||
localized_field :default_content
|
||||
field :default_attribute
|
||||
field :hint
|
||||
field :priority, :type => Integer, :default => 0
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
class EditableFile < EditableElement
|
||||
|
||||
localized_fields :source_filename
|
||||
|
||||
mount_uploader :source, EditableFileUploader
|
||||
|
||||
# localized_field :source
|
||||
|
||||
def content
|
||||
self.source? ? self.source.url : self.default_content
|
||||
end
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
class EditableShortText < EditableElement
|
||||
|
||||
## fields ##
|
||||
field :content
|
||||
localized_field :content
|
||||
|
||||
## methods ##
|
||||
|
||||
def content
|
||||
self.read_attribute(:content).blank? ? self.default_content : self.read_attribute(:content)
|
||||
# def content
|
||||
# self.read_attribute(:content).blank? ? self.default_content : self.read_attribute(:content)
|
||||
# end
|
||||
|
||||
def content_with_localization
|
||||
value = self.content_without_localization
|
||||
value.blank? ? self.default_content : value
|
||||
end
|
||||
|
||||
alias_method_chain :content, :localization
|
||||
|
||||
end
|
|
@ -5,9 +5,9 @@ module Extensions
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
field :serialized_template, :type => Binary
|
||||
field :template_dependencies, :type => Array, :default => []
|
||||
field :snippet_dependencies, :type => Array, :default => []
|
||||
localized_field :serialized_template, :type => Binary
|
||||
localized_field :template_dependencies, :type => Array, :default => []
|
||||
localized_field :snippet_dependencies, :type => Array, :default => []
|
||||
|
||||
attr_reader :template_changed
|
||||
|
||||
|
@ -22,7 +22,7 @@ module Extensions
|
|||
module InstanceMethods
|
||||
|
||||
def template
|
||||
@template ||= Marshal.load(read_attribute(:serialized_template).to_s) rescue nil
|
||||
@template ||= Marshal.load(self.serialized_template.to_s) rescue nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -4,9 +4,9 @@ module Extensions
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
field :seo_title, :type => String
|
||||
field :meta_keywords, :type => String
|
||||
field :meta_description, :type => String
|
||||
localized_field :seo_title
|
||||
localized_field :meta_keywords
|
||||
localized_field :meta_description
|
||||
end
|
||||
|
||||
end # Seo
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
module Extensions
|
||||
module Site
|
||||
module I18n
|
||||
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
|
||||
## fields ##
|
||||
field :locales, :type => Array, :default => []
|
||||
|
||||
## callbacks ##
|
||||
after_validation :add_missing_locales_for_all_pages
|
||||
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
|
||||
def locales=(array)
|
||||
array = [] if array.blank?; super(array)
|
||||
end
|
||||
|
||||
def default_locale
|
||||
self.locales.first || Locomotive.config.site_locales.first
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def add_missing_locales_for_all_pages
|
||||
if self.locales_changed?
|
||||
list = self.pages.to_a
|
||||
|
||||
while !list.empty? do
|
||||
page = list.pop
|
||||
begin
|
||||
page.send(:set_slug_and_fullpath_for_all_locales, self.locales)
|
||||
|
||||
page.save
|
||||
|
||||
rescue TypeError => e
|
||||
list.insert(0, page)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
class Page
|
||||
|
||||
include Locomotive::Mongoid::Document
|
||||
include Mongoid::I18n
|
||||
|
||||
## Extensions ##
|
||||
include Extensions::Page::Tree
|
||||
|
@ -13,10 +14,10 @@ class Page
|
|||
include Extensions::Shared::Seo
|
||||
|
||||
## fields ##
|
||||
field :title
|
||||
field :slug
|
||||
field :fullpath
|
||||
field :raw_template
|
||||
localized_field :title
|
||||
localized_field :slug
|
||||
localized_field :fullpath
|
||||
localized_field :raw_template
|
||||
field :published, :type => Boolean, :default => false
|
||||
field :cache_strategy, :default => 'none'
|
||||
|
||||
|
@ -32,6 +33,7 @@ class Page
|
|||
after_initialize :set_default_raw_template
|
||||
before_validation :normalize_slug
|
||||
before_save { |p| p.fullpath = p.fullpath(true) }
|
||||
before_create :set_slug_and_fullpath_for_all_locales
|
||||
before_destroy :do_not_remove_index_and_404_pages
|
||||
|
||||
## validations ##
|
||||
|
@ -61,9 +63,13 @@ class Page
|
|||
self.index? || self.not_found?
|
||||
end
|
||||
|
||||
def fullpath(force = false)
|
||||
if read_attribute(:fullpath).present? && !force
|
||||
return read_attribute(:fullpath)
|
||||
def default_slug
|
||||
I18n.with_site_locale(I18n.default_site_locale) { self.slug }
|
||||
end
|
||||
|
||||
def fullpath_with_building(force = false)
|
||||
if self.fullpath_without_building.present? && !force
|
||||
return self.fullpath_without_building
|
||||
end
|
||||
|
||||
if self.index? || self.not_found?
|
||||
|
@ -75,6 +81,18 @@ class Page
|
|||
end
|
||||
end
|
||||
|
||||
alias_method_chain :fullpath, :building
|
||||
|
||||
def fullpath_with_locale(locale)
|
||||
url, locale = self.fullpath(true), locale.to_s
|
||||
|
||||
if locale != self.site.default_locale
|
||||
url = File.join(locale, url)
|
||||
end
|
||||
|
||||
url
|
||||
end
|
||||
|
||||
def with_cache?
|
||||
self.cache_strategy != 'none'
|
||||
end
|
||||
|
@ -104,4 +122,14 @@ class Page
|
|||
self.raw_template ||= I18n.t('attributes.defaults.pages.other.body')
|
||||
end
|
||||
|
||||
def set_slug_and_fullpath_for_all_locales(locales = nil)
|
||||
(locales || self.site.locales).each do |locale|
|
||||
I18n.with_site_locale(locale) do
|
||||
%w(slug fullpath).each do |meth|
|
||||
self.send(:"#{meth}=", self.send(meth.to_sym))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
class Site
|
||||
|
||||
include Locomotive::Mongoid::Document
|
||||
include Mongoid::I18n
|
||||
|
||||
## Extensions ##
|
||||
extend Extensions::Site::SubdomainDomains
|
||||
extend Extensions::Site::FirstInstallation
|
||||
include Extensions::Site::I18n
|
||||
include Extensions::Shared::Seo
|
||||
|
||||
## fields ##
|
||||
|
|
|
@ -9,6 +9,17 @@
|
|||
= f.input :meta_keywords
|
||||
= f.input :meta_description
|
||||
|
||||
- if can?(:manage, current_site)
|
||||
= f.foldable_inputs :name => :locales, :class => 'language locales' do
|
||||
= f.custom_input :locales, { :css => 'full', :with_label => false } do
|
||||
#locales
|
||||
- ordered_site_locales(@site).each do |locale|
|
||||
%span
|
||||
= check_box_tag 'site[locales][]', locale, @site.locales.include?(locale), :id => "site-locales-#{locale}"
|
||||
%label{ :for => "site-locales-#{locale}" }
|
||||
= image_tag "admin/icons/flags/#{locale}.png"
|
||||
= t("admin.locales.#{locale}")
|
||||
|
||||
- if can?(:point, Site)
|
||||
- if manage_subdomain_or_domains?
|
||||
= f.foldable_inputs :name => :access_points, :class => 'editable-list off' do
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
- content_for :submenu do
|
||||
= render_cell 'admin/settings_menu', :show
|
||||
|
||||
- content_for :actions do
|
||||
= render_cell 'admin/site_locale_picker', :show, :site => current_site, :locale => I18n.site_locale
|
||||
|
||||
- content_for :buttons do
|
||||
- if can?(:manage, @site)
|
||||
= admin_button_tag :export, new_admin_export_url, :class => 'new'
|
||||
|
|
|
@ -40,8 +40,7 @@
|
|||
= f.radio_button :locale, locale
|
||||
%label{ :for => "my_account_locale_#{locale.downcase}" }
|
||||
= image_tag "admin/icons/flags/#{locale}.png"
|
||||
= t(".#{locale}")
|
||||
/
|
||||
= t("admin.locales.#{locale}")
|
||||
|
||||
|
||||
= render 'admin/shared/form_actions', :button_label => :update
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
- if can? :manage, ContentType
|
||||
= link_to content_tag(:em) + content_tag(:span, t('admin.content_types.index.new')), new_admin_content_type_url
|
||||
|
||||
= render_cell 'admin/site_locale_picker', :show, :site => current_site, :locale => I18n.site_locale
|
|
@ -93,6 +93,7 @@ stylesheets:
|
|||
- public/stylesheets/admin/formtastic_changes.css
|
||||
- public/stylesheets/admin/assets.css
|
||||
- public/stylesheets/admin/sites_picker.css
|
||||
- public/stylesheets/admin/site_locale_picker.css
|
||||
- public/stylesheets/admin/application.css
|
||||
- public/stylesheets/admin/safari.css
|
||||
- public/stylesheets/admin/guiders-1.1.0.css
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
de:
|
||||
admin:
|
||||
locales:
|
||||
en: Englisch
|
||||
de: Deutsch
|
||||
fr: Französisch
|
||||
pt-BR: "Bras. Portugisisch"
|
||||
it: "Italienisch"
|
||||
nl: "Holländer"
|
||||
es: "Spanisch"
|
||||
ru: "Russisch"
|
||||
|
||||
buttons:
|
||||
login: Einloggen
|
||||
send_password: Senden
|
||||
|
@ -157,14 +167,6 @@ de:
|
|||
edit:
|
||||
help: "Deinen Namen kannst du durch darauf klicken ändern."
|
||||
new_site: Neue Webseite
|
||||
en: Englisch
|
||||
de: Deutsch
|
||||
fr: Französisch
|
||||
pt-BR: "Bras. Portugisisch"
|
||||
it: "Italienisch"
|
||||
nl: "Holländer"
|
||||
es: "Spanisch"
|
||||
ru: "Russisch"
|
||||
ask_for_name: "Bitte gib deinen neuen Namen an"
|
||||
|
||||
theme_assets:
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
en:
|
||||
admin:
|
||||
locales:
|
||||
en: English
|
||||
de: German
|
||||
fr: French
|
||||
pt-BR: "Brazilian Portuguese"
|
||||
it: Italian
|
||||
nl: Dutch
|
||||
es: Spanish
|
||||
ru: Russian
|
||||
|
||||
buttons:
|
||||
login: Log in
|
||||
send_password: Send
|
||||
|
@ -168,14 +178,6 @@ en:
|
|||
edit:
|
||||
help: "Your name can be updated by clicking it. To apply your changes, click on the \"Save\" button."
|
||||
new_site: new site
|
||||
en: English
|
||||
de: German
|
||||
fr: French
|
||||
pt-BR: "Brazilian Portuguese"
|
||||
it: Italian
|
||||
nl: Dutch
|
||||
es: Spanish
|
||||
ru: Russian
|
||||
ask_for_name: "Please type your new name"
|
||||
|
||||
theme_assets:
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
fr:
|
||||
admin:
|
||||
locales:
|
||||
en: en Anglais
|
||||
de: en Allemand
|
||||
fr: en Français
|
||||
pt-BR: "en Portugais"
|
||||
it: "en Italien"
|
||||
nl: "en Hollandais"
|
||||
es: en Espagnol
|
||||
ru: en Russe
|
||||
|
||||
errors:
|
||||
"500":
|
||||
title: Erreur applicative
|
||||
|
@ -169,15 +179,6 @@ fr:
|
|||
edit:
|
||||
help: "Votre nom est modifiable en cliquant dessus. Pour appliquer votre modification, cliquez après sur le bouton \"Modifier\""
|
||||
new_site: nouveau site
|
||||
en: en Anglais
|
||||
de: en Allemand
|
||||
fr: en Français
|
||||
pt-BR: "en Portugais"
|
||||
it: "en Italien"
|
||||
nl: "en Hollandais"
|
||||
es: en Espagnol
|
||||
ru: en Russe
|
||||
|
||||
ask_for_name: "Veuillez entrer le nouveau nom"
|
||||
|
||||
theme_assets:
|
||||
|
|
|
@ -10,6 +10,7 @@ en:
|
|||
raw_template: Template
|
||||
credentials: Credentials
|
||||
language: Language
|
||||
locales: Locales
|
||||
sites: Sites
|
||||
access_points: Access points
|
||||
memberships: Accounts
|
||||
|
@ -67,6 +68,7 @@ en:
|
|||
seo_title: "Define a global value here which should be used as the value for the title tag in the head section."
|
||||
meta_keywords: "Meta keywords used within the head tag of the page. They are separated by a comma. Required for SEO."
|
||||
meta_description: "Meta description used within the head tag of the page. Required for SEO."
|
||||
locales: "Drag&drop a flag to the first position to make it the default one."
|
||||
domain_name: "ex: locomotiveapp.org"
|
||||
robots_txt: "Content of the <span class='code'>/robots.txt</span> file. Check the following <a href='http://www.w3.org/TR/html4/appendix/notes.html#h-B.4.1.1'>url</a> for more information."
|
||||
theme_asset:
|
||||
|
|
|
@ -9,11 +9,11 @@ defaults: &defaults
|
|||
|
||||
development:
|
||||
<<: *defaults
|
||||
database: locomotive_dev
|
||||
database: locomotive_i18n_dev
|
||||
|
||||
test:
|
||||
<<: *defaults
|
||||
database: locomotive_test
|
||||
database: locomotive_i18n_test
|
||||
|
||||
# set these environment variables on your prod server
|
||||
production:
|
||||
|
|
37
doc/TODO
37
doc/TODO
|
@ -1,5 +1,42 @@
|
|||
BOARD:
|
||||
|
||||
- i18n (in progress)
|
||||
x site locales
|
||||
x model
|
||||
x ui
|
||||
x locale picker
|
||||
x patches
|
||||
x i18n
|
||||
x mongoid-i18n
|
||||
x rendering engine
|
||||
x get locale
|
||||
x render the right version
|
||||
x contents
|
||||
x store the site locale value
|
||||
x pages
|
||||
x title / slug / fullpath / seo
|
||||
x template
|
||||
x editable contents
|
||||
x custom contents (models)
|
||||
(- snippets)
|
||||
x site
|
||||
x seo
|
||||
x locale picker
|
||||
- liquid tags:
|
||||
x locale switcher
|
||||
x nav
|
||||
(- link_to (new)
|
||||
- others ?) -
|
||||
- theme_assets
|
||||
- other problems to solve:
|
||||
x If you create a new page it shall always be created in the default_language, not depending on the used language in the backend.
|
||||
- set the page slug for all the site locales
|
||||
- default site locale even if the i18n feature is disabled (the disable procedure is missing btw)
|
||||
- redirect for the default site locale if urls like /<default_site_locale>/....
|
||||
- inline editing
|
||||
- with_scope is broken
|
||||
- refactor
|
||||
|
||||
BACKLOG:
|
||||
|
||||
- custom_fields:
|
||||
|
|
|
@ -9,6 +9,7 @@ require 'locomotive/dragonfly'
|
|||
require 'locomotive/liquid'
|
||||
require 'locomotive/mongoid'
|
||||
require 'locomotive/carrierwave'
|
||||
require 'locomotive/i18n'
|
||||
require 'locomotive/custom_fields'
|
||||
require 'locomotive/httparty'
|
||||
require 'locomotive/inherited_resources'
|
||||
|
|
|
@ -8,6 +8,7 @@ module Locomotive
|
|||
# :forbidden_paths => %w{layouts snippets stylesheets javascripts assets admin system api},
|
||||
:reserved_slugs => %w{stylesheets javascripts assets admin images api pages edit},
|
||||
:locales => %w{en de fr pt-BR it nl es ru},
|
||||
:site_locales => %w{en de fr pt-BR it nl es ru},
|
||||
:cookie_key => '_locomotive_session',
|
||||
:enable_logs => false,
|
||||
:hosting => :auto,
|
||||
|
|
|
@ -23,6 +23,13 @@ module CustomFields
|
|||
module Category
|
||||
class Item
|
||||
|
||||
include Mongoid::I18n
|
||||
|
||||
localized_fields :name, :position
|
||||
|
||||
field :name
|
||||
field :position, :type => Integer, :default => 0
|
||||
|
||||
def to_liquid
|
||||
{ 'id' => self._id.to_s, 'name' => self.name }
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ require 'json/pure'
|
|||
require 'devise'
|
||||
require 'mongoid'
|
||||
require 'mongoid_acts_as_tree'
|
||||
require 'mongoid/i18n'
|
||||
require 'will_paginate'
|
||||
require 'haml'
|
||||
require 'liquid'
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
# require 'ruby-debug'
|
||||
## patches for the i18n support (aka multi languages support)
|
||||
require 'i18n'
|
||||
|
||||
module I18n
|
||||
class Config
|
||||
def site_locale
|
||||
@site_locale ||= default_site_locale
|
||||
end
|
||||
|
||||
def site_locale=(site_locale)
|
||||
@site_locale = site_locale.to_sym rescue nil
|
||||
end
|
||||
|
||||
def default_site_locale
|
||||
@@default_site_locale ||= :en
|
||||
end
|
||||
|
||||
def default_site_locale=(site_locale)
|
||||
@@default_site_locale = site_locale.to_sym rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
# Write methods which delegates to the configuration object
|
||||
%w(site_locale default_site_locale).each do |method|
|
||||
module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1
|
||||
def #{method}
|
||||
config.#{method}
|
||||
end
|
||||
|
||||
def #{method}=(value)
|
||||
config.#{method} = (value)
|
||||
end
|
||||
DELEGATORS
|
||||
end
|
||||
|
||||
# Executes block with given I18n.site_locale set.
|
||||
def with_site_locale(tmp_locale = nil)
|
||||
if tmp_locale
|
||||
current_locale = self.site_locale
|
||||
self.site_locale = tmp_locale
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
self.site_locale = current_locale if tmp_locale
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
## CARRIERWAVE ##
|
||||
require 'carrierwave/orm/mongoid'
|
||||
|
||||
module CarrierWave
|
||||
module Mongoid
|
||||
def mount_uploader_with_localization(column, uploader=nil, options={}, &block)
|
||||
mount_uploader_without_localization(column, uploader, options, &block)
|
||||
|
||||
define_method(:read_uploader) { |name| self.send(name.to_sym) }
|
||||
define_method(:write_uploader) { |name, value| self.send(:"#{name.to_sym}=", value) }
|
||||
end
|
||||
|
||||
alias_method_chain :mount_uploader, :localization
|
||||
end
|
||||
end
|
||||
|
||||
## MONGOID-I18n ##
|
||||
|
||||
# TODO: fork https://github.com/Papipo/mongoid_i18n
|
||||
|
||||
module Mongoid
|
||||
module I18n
|
||||
class LocalizedField < Hash
|
||||
|
||||
attr_accessor :type
|
||||
|
||||
def initialize(type = nil)
|
||||
self.type = type || Object
|
||||
super nil
|
||||
end
|
||||
|
||||
def [](key)
|
||||
self.type.set(super(key))
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
super(key, self.type.set(value))
|
||||
end
|
||||
|
||||
def merge(other_hash, &block)
|
||||
# puts "merging #{other_hash.inspect}"
|
||||
converted_hash = {}
|
||||
other_hash.each { |k, v| converted_hash[k] = self.type.set(v) }
|
||||
# puts "converted_hash = #{converted_hash.inspect}"
|
||||
super(converted_hash, &block)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DIRTY MONKEY PATCHING BEFORE THE REFACTORING
|
||||
module Mongoid
|
||||
module Criterion
|
||||
class Selector< Hash
|
||||
def []=(key, value)
|
||||
key = "#{key}.#{::I18n.site_locale}" if fields[key.to_s].try(:type) == Mongoid::I18n::LocalizedField
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module I18n
|
||||
|
||||
included do
|
||||
cattr_accessor :localized_fields_list
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
||||
def localized_fields(*args)
|
||||
self.localized_fields_list = [*args].collect
|
||||
end
|
||||
|
||||
def field(name, options = {})
|
||||
if localized_field?(name)
|
||||
options.merge!(:type => LocalizedField, :default => LocalizedField.new(options[:type]))
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def localized_field?(name)
|
||||
(self.localized_fields_list || []).any? do |rule|
|
||||
case rule
|
||||
when String, Symbol then name.to_s == rule.to_s
|
||||
when Regexp then !(name.to_s =~ rule).nil?
|
||||
else
|
||||
false
|
||||
end
|
||||
end.tap do |result|
|
||||
# options[:type] = LocalizedField if result
|
||||
|
||||
# puts "#{name.inspect}... localized ? #{result.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def create_accessors(name, meth, options = {})
|
||||
if options[:type] == LocalizedField
|
||||
|
||||
if options[:use_default_if_empty] != false # either nil or true
|
||||
define_method(meth) do
|
||||
value = read_attribute(name)
|
||||
if value.is_a?(Hash)
|
||||
converted_value = options[:default].merge(value)
|
||||
|
||||
value = converted_value[::I18n.site_locale.to_s]
|
||||
|
||||
value.to_s.empty? ? converted_value[::I18n.default_site_locale.to_s] : value
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
else
|
||||
define_method(meth) do
|
||||
value = read_attribute(name)
|
||||
if value.is_a?(Hash)
|
||||
converted_value = options[:default].merge(value)
|
||||
options[:default].merge(value)[::I18n.site_locale.to_s]
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
define_method("#{meth}=") do |value|
|
||||
if !@attributes[name].nil? && !@attributes[name].is_a?(Hash)
|
||||
# existing value but not localized yet
|
||||
old_value = @attributes[name]
|
||||
@attributes[name] = options[:default].merge(::I18n.default_site_locale.to_s => old_value)
|
||||
end
|
||||
|
||||
@attributes[name] ||= options[:default]
|
||||
|
||||
@attributes[name] = options[:default].merge(@attributes[name]) unless @attributes[name].is_a?(LocalizedField)
|
||||
|
||||
value = if value.is_a?(Hash)
|
||||
@attributes[name].merge(value)
|
||||
else
|
||||
@attributes[name].merge(::I18n.site_locale.to_s => value)
|
||||
end
|
||||
|
||||
value = value.delete_if { |key, value| value.to_s.empty? } if options[:clear_empty_values] != false
|
||||
|
||||
write_attribute(name, value)
|
||||
end
|
||||
|
||||
define_method("#{meth}_translations") { read_attribute(name) }
|
||||
|
||||
if options[:clear_empty_values] != false
|
||||
define_method("#{meth}_translations=") { |value| write_attribute(name, value.delete_if { |key, value| value.blank? }) }
|
||||
else
|
||||
define_method("#{meth}_translations=") { |value| write_attribute(name, value) }
|
||||
end
|
||||
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -21,6 +21,10 @@ module Locomotive
|
|||
self._source.highlighted_field_value
|
||||
end
|
||||
|
||||
def _permalink
|
||||
self._source._permalink
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
module Locomotive
|
||||
module Liquid
|
||||
module Tags
|
||||
# Display the links to change the locale of the current page
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# {% locale_switcher %} => <div id="locale-switcher"><a href="/features" class="current en">Features</a><a href="/fr/fonctionnalites" class="fr">Fonctionnalités</a></div>
|
||||
#
|
||||
# {% locale_switcher label: locale, sep: ' - ' }
|
||||
#
|
||||
# options:
|
||||
# - label: iso (de, fr, en, ...etc), locale (Deutsch, Français, English, ...etc), title (page title)
|
||||
# - sep: piece of html code seperating 2 locales
|
||||
#
|
||||
# notes:
|
||||
# - "iso" is the default choice for label
|
||||
# - " | " is the default seperating code
|
||||
#
|
||||
class LocaleSwitcher < ::Liquid::Tag
|
||||
|
||||
Syntax = /(#{::Liquid::Expression}+)?/
|
||||
|
||||
def initialize(tag_name, markup, tokens, context)
|
||||
@options = { :label => 'iso', :sep => ' | ' }
|
||||
|
||||
if markup =~ Syntax
|
||||
markup.scan(::Liquid::TagAttributes) { |key, value| @options[key.to_sym] = value.gsub(/"|'/, '') }
|
||||
|
||||
@options[:exclude] = Regexp.new(@options[:exclude]) if @options[:exclude]
|
||||
else
|
||||
raise ::Liquid::SyntaxError.new("Syntax Error in 'locale_switcher' - Valid syntax: locale_switcher <options>")
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def render(context)
|
||||
site = context.registers[:site]
|
||||
current_page = context.registers[:page]
|
||||
|
||||
output = %(<div id="locale-switcher">)
|
||||
|
||||
output += site.locales.collect do |locale|
|
||||
I18n.with_site_locale(locale) do
|
||||
url = current_page.fullpath_with_locale(locale)
|
||||
|
||||
if current_page.templatized?
|
||||
url.gsub!('content_type_template', context['content_instance']._permalink)
|
||||
end
|
||||
|
||||
%(<a href="/#{url}" class="#{locale} #{'current' if locale == context['current_locale']}">#{link_label(current_page)}</a>)
|
||||
end
|
||||
end.join(@options[:sep])
|
||||
|
||||
output += %(</div>)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def link_label(current_page)
|
||||
case @options[:label]
|
||||
when :iso then I18n.site_locale
|
||||
when :locale then I18n.t("admin.locales.#{I18n.site_locale}", :locale => I18n.site_locale)
|
||||
when :title then current_page.title
|
||||
else
|
||||
I18n.site_locale
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
::Liquid::Template.register_tag('locale_switcher', LocaleSwitcher)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -56,14 +56,15 @@ module Locomotive
|
|||
|
||||
# Determines root node for the list
|
||||
def fetch_entries(context)
|
||||
@current_site = context.registers[:site]
|
||||
@current_page = context.registers[:page]
|
||||
|
||||
children = (case @source
|
||||
when 'site' then context.registers[:site].pages.root.minimal_attributes.first # start from home page
|
||||
when 'site' then @current_site.pages.root.minimal_attributes.first # start from home page
|
||||
when 'parent' then @current_page.parent || @current_page
|
||||
when 'page' then @current_page
|
||||
else
|
||||
context.registers[:site].pages.fullpath(@source).minimal_attributes.first
|
||||
@current_site.pages.fullpath(@source).minimal_attributes.first
|
||||
end).children_with_minimal_attributes.to_a
|
||||
|
||||
children.delete_if { |p| !include_page?(p) }
|
||||
|
@ -73,11 +74,13 @@ module Locomotive
|
|||
def render_entry_link(page, css, depth)
|
||||
selected = @current_page.fullpath =~ /^#{page.fullpath}/ ? ' on' : ''
|
||||
|
||||
page.site = @current_site
|
||||
|
||||
icon = @options[:icon] ? '<span></span>' : ''
|
||||
label = %{#{icon if @options[:icon] != 'after' }#{page.title}#{icon if @options[:icon] == 'after' }}
|
||||
|
||||
output = %{<li id="#{page.slug.dasherize}-link" class="link#{selected} #{css}">}
|
||||
output << %{<a href="/#{page.fullpath}">#{label}</a>}
|
||||
output = %{<li id="#{page.default_slug.dasherize}-link" class="link#{selected} #{css}">}
|
||||
output << %{<a href="/#{page.fullpath_with_locale(I18n.site_locale)}">#{label}</a>}
|
||||
output << render_entry_children(page, depth.succ) if (depth.succ <= @options[:depth].to_i)
|
||||
output << %{</li>}
|
||||
|
||||
|
@ -90,7 +93,7 @@ module Locomotive
|
|||
|
||||
children = page.children_with_minimal_attributes.reject { |c| !include_page?(c) }
|
||||
if children.present?
|
||||
output = %{<ul id="#{@options[:id]}-#{page.slug.dasherize}">}
|
||||
output = %{<ul id="#{@options[:id]}-#{page.default_slug.dasherize}">}
|
||||
children.each do |c, page|
|
||||
css = []
|
||||
css << 'first' if children.first == c
|
||||
|
@ -115,8 +118,9 @@ module Locomotive
|
|||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
::Liquid::Template.register_tag('nav', Nav)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,7 +42,7 @@ module Locomotive
|
|||
|
||||
page_count, current_page = pagination['total_pages'], pagination['current_page']
|
||||
|
||||
path = context.registers[:page].fullpath
|
||||
path = context.registers[:page].fullpath_with_locale(I18n.site_locale)
|
||||
|
||||
pagination['previous'] = link(I18n.t('pagination.previous'), current_page - 1, path) if pagination['previous_page']
|
||||
pagination['next'] = link(I18n.t('pagination.next'), current_page + 1, path) if pagination['next_page']
|
||||
|
|
|
@ -17,7 +17,9 @@ module Locomotive
|
|||
|
||||
render_no_page_error and return if @page.nil?
|
||||
|
||||
output = @page.render(locomotive_context)
|
||||
output = I18n.with_locale(I18n.site_locale) do
|
||||
@page.render(locomotive_context)
|
||||
end
|
||||
|
||||
self.prepare_and_set_response(output)
|
||||
end
|
||||
|
@ -33,6 +35,12 @@ module Locomotive
|
|||
path.gsub!(/\.[a-zA-Z][a-zA-Z0-9]{2,}$/, '') # remove the page extension
|
||||
path.gsub!(/^\//, '') # remove the leading slash
|
||||
|
||||
# extract the site locale
|
||||
if path =~ /^(#{current_site.locales.join('|')})+(\/|$)/
|
||||
I18n.site_locale = $1
|
||||
path.gsub!($1 + $2, '')
|
||||
end
|
||||
|
||||
path = 'index' if path.blank?
|
||||
|
||||
if path != 'index'
|
||||
|
@ -67,7 +75,10 @@ module Locomotive
|
|||
'params' => self.params,
|
||||
'url' => request.url,
|
||||
'now' => Time.now.utc,
|
||||
'today' => Date.today
|
||||
'today' => Date.today,
|
||||
'default_locale' => I18n.default_site_locale.to_s,
|
||||
'current_locale' => I18n.site_locale.to_s,
|
||||
'locales' => current_site.locales
|
||||
}
|
||||
|
||||
assigns.merge!(Locomotive.config.context_assign_extensions)
|
||||
|
@ -87,7 +98,7 @@ module Locomotive
|
|||
:current_admin => current_admin
|
||||
}
|
||||
|
||||
::Liquid::Context.new({}, assigns, registers)
|
||||
::Liquid::Context.new({}, assigns, registers, true)
|
||||
end
|
||||
|
||||
def prepare_and_set_response(output)
|
||||
|
|
|
@ -29,6 +29,27 @@ namespace :locomotive do
|
|||
end
|
||||
end
|
||||
|
||||
desc 'Prepare for i18n'
|
||||
task :prepare_for_i18n => :environment do
|
||||
Site.all.each do |site|
|
||||
if site.locales.empty?
|
||||
site.locales = [Locomotive.config.site_locales.first]
|
||||
site.save
|
||||
end
|
||||
end
|
||||
|
||||
Page.skip_callback(:validate, :before)
|
||||
Page.skip_callback(:save, :after)
|
||||
|
||||
I18n.with_site_locale(ENV['LOCALE'] || Locomotive.config.site_locales.first) do
|
||||
Page.all.each do |page|
|
||||
page.template_dependencies = page.template_dependencies
|
||||
page.snippet_dependencies = page.snippet_dependencies
|
||||
page.save(:validate => false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Import a remote template described by its URL -- 2 options: SITE=name or id, RESET=by default false'
|
||||
task :import => :environment do
|
||||
url, site_name_or_id, reset = ENV['URL'], ENV['SITE'], Boolean.set(ENV['RESET']) || false
|
||||
|
|
|
@ -22,6 +22,7 @@ Gem::Specification.new do |s|
|
|||
s.add_dependency 'devise', '1.3.4'
|
||||
s.add_dependency 'devise_bushido_authenticatable', '1.0.0.alpha10'
|
||||
s.add_dependency 'mongoid', '2.0.2'
|
||||
s.add_dependency 'mongoid_i18n'
|
||||
s.add_dependency 'bson_ext', '~> 1.3.0'
|
||||
s.add_dependency 'locomotive_mongoid_acts_as_tree', '0.1.5.7'
|
||||
s.add_dependency 'will_paginate', '~> 3.0.0'
|
||||
|
|
|
@ -165,4 +165,11 @@ $(document).ready(function() {
|
|||
.trigger('refresh');
|
||||
|
||||
$('.formtastic fieldset.inputs ol li:not(.item)').last().addClass('last');
|
||||
|
||||
// site locale picker
|
||||
$('#site-locale-picker').hover(function() { $(this).addClass('open'); }, function() { $(this).removeClass('open'); })
|
||||
.find('li:gt(0)').click(function(e) {
|
||||
var locale = $(this).find('.text').html();
|
||||
addParameterToURL('site_locale', locale);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -70,5 +70,6 @@ $(document).ready(function() {
|
|||
});
|
||||
}).hide();
|
||||
|
||||
|
||||
// locales
|
||||
$('#locales').sortable({ items: 'span', handle: 'label', tolerance: 'pointer', appendTo: 'body' });
|
||||
});
|
||||
|
|
|
@ -13,6 +13,27 @@ function makeSlug(val, sep) { // code largely inspired by http://www.thewebsitet
|
|||
return val.toLowerCase();
|
||||
}
|
||||
|
||||
function addParameterToURL(key, value) { // code from http://stackoverflow.com/questions/486896/adding-a-parameter-to-the-url-with-javascript
|
||||
key = encodeURIComponent(key); value = encodeURIComponent(value);
|
||||
|
||||
var kvp = document.location.search.substr(1).split('&');
|
||||
|
||||
var i = kvp.length; var x; while(i--) {
|
||||
x = kvp[i].split('=');
|
||||
|
||||
if (x[0] == key) {
|
||||
x[1] = value;
|
||||
kvp[i] = x.join('=');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i < 0) { kvp[kvp.length] = [key,value].join('='); }
|
||||
|
||||
//this will reload the page, it's likely better to store this until finished
|
||||
document.location.search = kvp.join('&');
|
||||
}
|
||||
|
||||
(function() {
|
||||
String.prototype.trim = function() {
|
||||
return this.replace(/^\s+/g, '').replace(/\s+$/g, '');
|
||||
|
|
|
@ -508,7 +508,7 @@ form.formtastic fieldset ol li.has-many ul li.template span.actions button span
|
|||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
/* ___ my account ___ */
|
||||
/* ___ language / locales ___ */
|
||||
|
||||
form.formtastic fieldset.language li.full span {
|
||||
display: inline-block;
|
||||
|
@ -535,6 +535,14 @@ form.formtastic fieldset.language li.full span input {
|
|||
margin-right: 2px;
|
||||
}
|
||||
|
||||
form.formtastic fieldset.locales li.full span:first-child label {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
form.formtastic fieldset.locales p.inline-hints {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
/* ___ membership ___ */
|
||||
|
||||
form.formtastic fieldset.email li.full input {
|
||||
|
|
|
@ -107,7 +107,8 @@
|
|||
background: transparent url(/images/admin/menu/submenu/action-border.png) repeat-y left 0; }
|
||||
#submenu > .action a {
|
||||
margin-top: 18px;
|
||||
display: inline-block;
|
||||
display: block;
|
||||
float: left;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 16px;
|
||||
-moz-border-radius: 16px;
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/* ___ AUTOMATICALLY GENERATED: see admin/site_locale_picker.scss for the source file */
|
||||
/* ___ rounded ___ */
|
||||
/* ___ box shadow ___ */
|
||||
/* ___ others ___ */
|
||||
#site-locale-picker {
|
||||
position: relative;
|
||||
float: right;
|
||||
margin: 18px 0 0 10px;
|
||||
padding: 0px 25px 0 10px;
|
||||
border: 1px solid transparent;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 16px;
|
||||
-moz-border-radius: 16px;
|
||||
-webkit-border-radius: 16px; }
|
||||
#site-locale-picker:hover {
|
||||
border-color: black;
|
||||
border-radius: 12px;
|
||||
-moz-border-radius: 12px;
|
||||
-webkit-border-radius: 12px; }
|
||||
#site-locale-picker .hand {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 7px;
|
||||
height: 7px;
|
||||
width: 12px;
|
||||
background: transparent url(/images/admin/menu/icons.png) no-repeat 0px -16px; }
|
||||
#site-locale-picker ul {
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px; }
|
||||
#site-locale-picker ul li {
|
||||
position: relative;
|
||||
display: none;
|
||||
margin: 5px 0;
|
||||
height: 22px;
|
||||
line-height: 20px;
|
||||
cursor: pointer; }
|
||||
#site-locale-picker ul li img.flag {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
width: 20px;
|
||||
height: 20px; }
|
||||
#site-locale-picker ul li span.text {
|
||||
position: relative;
|
||||
position: relative;
|
||||
top: -6px;
|
||||
color: #fff;
|
||||
font-size: 0.7em;
|
||||
text-shadow: 1px 1px 1px #000;
|
||||
text-transform: uppercase; }
|
||||
#site-locale-picker ul li:first-child {
|
||||
display: block;
|
||||
margin: 0px;
|
||||
cursor: default; }
|
||||
#site-locale-picker ul li:last-child {
|
||||
margin-bottom: 0px; }
|
||||
#site-locale-picker ul li:hover span.text {
|
||||
text-decoration: underline; }
|
||||
#site-locale-picker.open ul li {
|
||||
display: block; }
|
||||
#site-locale-picker.open ul li:first-child {
|
||||
margin-bottom: 5px; }
|
|
@ -112,7 +112,8 @@
|
|||
|
||||
a {
|
||||
margin-top: 18px;
|
||||
display: inline-block;
|
||||
display: block;
|
||||
float: left;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
@include full-rounded(16px);
|
||||
padding: 0px 10px 0 15px;
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/* ___ AUTOMATICALLY GENERATED: see admin/site_locale_picker.scss for the source file */
|
||||
|
||||
@import "helpers";
|
||||
|
||||
#site-locale-picker {
|
||||
|
||||
position: relative;
|
||||
|
||||
float: right;
|
||||
|
||||
margin: 18px 0 0 10px;
|
||||
padding: 0px 25px 0 10px;
|
||||
|
||||
border: 1px solid transparent;
|
||||
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
@include full-rounded(16px);
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(0, 0, 0, 1);
|
||||
@include full-rounded(12px);
|
||||
}
|
||||
|
||||
.hand {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 7px;
|
||||
|
||||
height: 7px;
|
||||
width: 12px;
|
||||
|
||||
background: transparent url(/images/admin/menu/icons.png) no-repeat 0px -16px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
|
||||
display: none;
|
||||
|
||||
margin: 5px 0;
|
||||
|
||||
height: 22px;
|
||||
line-height: 20px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
img.flag {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
span.text {
|
||||
position: relative;
|
||||
|
||||
position: relative;
|
||||
top: -6px;
|
||||
|
||||
color: #fff;
|
||||
font-size: 0.7em;
|
||||
text-shadow: 1px 1px 1px #000;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
display: block;
|
||||
margin: 0px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
span.text {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&.open {
|
||||
ul {
|
||||
li {
|
||||
display: block;
|
||||
|
||||
&:first-child {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue