diff --git a/README.rst b/README.rst index e1abe7f3fc3..8cc867f4844 100644 --- a/README.rst +++ b/README.rst @@ -9,8 +9,8 @@ http://ckan.org/. * Developer mailing list: ckan-dev@lists.okfn.org * Issue tracker: http://trac.ckan.org/ - Building Documentation - ====================== +Building Documentation +====================== 1. Install python-sphinx diff --git a/ckan/config/deployment.ini_tmpl b/ckan/config/deployment.ini_tmpl index f9d25c4eed9..0cad20aaac2 100644 --- a/ckan/config/deployment.ini_tmpl +++ b/ckan/config/deployment.ini_tmpl @@ -111,6 +111,11 @@ ckan.site_url = ## Favicon (default is the CKAN software favicon) ckan.favicon = /images/icons/ckan.ico +## The gravatar default to use. This can be any of the pre-defined strings +## as defined on http://en.gravatar.com/site/implement/images/ (e.g. "identicon" +## or "mm"). Or it can be a url, e.g. "http://example.com/images/avatar.jpg" +ckan.gravatar_default = identicon + ## Solr support #solr_url = http://127.0.0.1:8983/solr diff --git a/ckan/config/routing.py b/ckan/config/routing.py index 2ed0d3f4939..0c5944bfdd6 100644 --- a/ckan/config/routing.py +++ b/ckan/config/routing.py @@ -115,6 +115,8 @@ def make_map(): conditions=GET) m.connect('/util/resource/format_autocomplete', action='format_autocomplete', conditions=GET) + m.connect('/util/resource/format_icon', + action='format_icon', conditions=GET) m.connect('/util/authorizationgroup/autocomplete', action='authorizationgroup_autocomplete') m.connect('/util/group/autocomplete', action='group_autocomplete') @@ -175,6 +177,7 @@ def make_map(): m.connect('/dataset/{action}/{id}', requirements=dict(action='|'.join([ 'edit', + 'editresources', 'authz', 'history', 'read_ajax', diff --git a/ckan/controllers/api.py b/ckan/controllers/api.py index 5d3f97c9520..bbafe897689 100644 --- a/ckan/controllers/api.py +++ b/ckan/controllers/api.py @@ -5,7 +5,7 @@ from webob.multidict import UnicodeMultiDict from ckan.lib.base import BaseController, response, c, _, gettext, request -from ckan.lib.helpers import json, date_str_to_datetime +from ckan.lib.helpers import json, date_str_to_datetime, format_icon, icon_url import ckan.model as model import ckan.rating from ckan.lib.search import (query_for, QueryOptions, SearchIndexError, SearchError, @@ -692,6 +692,14 @@ def munge_tag(self): munged_tag = munge_tag(tag) return self._finish_ok(munged_tag) + def format_icon(self): + f = request.params.get('format') + out = { + 'format' : f, + 'icon' : icon_url(format_icon(f)) + } + return self._finish_ok(out) + def status(self): context = {'model': model, 'session': model.Session} data_dict = {} diff --git a/ckan/controllers/datastore.py b/ckan/controllers/datastore.py index f115e86d4a0..bd5e2d124e2 100644 --- a/ckan/controllers/datastore.py +++ b/ckan/controllers/datastore.py @@ -1,5 +1,7 @@ from ckan.lib.base import BaseController, abort, _, c, response, request, g import ckan.model as model +from ckan.lib.helpers import json +from ckan.lib.jsonp import jsonpify from ckan.logic import get_action, check_access from ckan.logic import NotFound, NotAuthorized, ValidationError @@ -11,12 +13,17 @@ def _make_redirect(self, id, url=''): # headers must be ascii strings response.headers['X-Accel-Redirect'] = str(redirect) + @jsonpify def read(self, id, url=''): context = {'model': model, 'session': model.Session, 'user': c.user or c.author} try: resource = get_action('resource_show')(context, {'id': id}) + if not resource.get('webstore_url', ''): + return { + 'error': 'DataStore is disabled for this resource' + } self._make_redirect(id, url) return '' except NotFound: @@ -24,6 +31,7 @@ def read(self, id, url=''): except NotAuthorized: abort(401, _('Unauthorized to read resource %s') % id) + @jsonpify def write(self, id, url): context = {'model': model, 'session': model.Session, 'user': c.user or c.author} @@ -31,6 +39,10 @@ def write(self, id, url): resource = model.Resource.get(id) if not resource: abort(404, _('Resource not found')) + if not resource.webstore_url: + return { + 'error': 'DataStore is disabled for this resource' + } context["resource"] = resource check_access('resource_update', context, {'id': id}) self._make_redirect(id, url) diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py index bcf064c2d40..b7f8efe2413 100644 --- a/ckan/controllers/group.py +++ b/ckan/controllers/group.py @@ -18,7 +18,6 @@ log = logging.getLogger(__name__) - class GroupController(BaseController): ## hooks for subclasses @@ -244,28 +243,14 @@ def edit(self, id, data=None, errors=None, error_summary=None): def _get_group_type(self, id): """ - Given the id of a group it determines the plugin to load - based on the group's type name (type). The plugin found - will be returned, or None if there is no plugin associated with - the type. - - Uses a minimal context to do so. The main use of this method - is for figuring out which plugin to delegate to. - - aborts if an exception is raised. + Given the id of a group it determines the type of a group given + a valid id/name for the group. """ - global _controller_behaviour_for - - context = {'model': model, 'session': model.Session, - 'user': c.user or c.author} - try: - data = get_action('group_show')(context, {'id': id}) - except NotFound: - abort(404, _('Group not found')) - except NotAuthorized: - abort(401, _('Unauthorized to read group %s') % id) - return data['type'] + group = model.Group.get( id ) + if not group: + return None + return group.type def _save_new(self, context, group_type=None): try: diff --git a/ckan/controllers/package.py b/ckan/controllers/package.py index 9504f765d90..ec055e6d64f 100644 --- a/ckan/controllers/package.py +++ b/ckan/controllers/package.py @@ -321,10 +321,12 @@ def new(self, data=None, errors=None, error_summary=None): data = data or clean_dict(unflatten(tuplize_dict(parse_params( request.params, ignore_keys=[CACHE_PARAMETER])))) + c.pkg_json = json.dumps(data) errors = errors or {} error_summary = error_summary or {} vars = {'data': data, 'errors': errors, 'error_summary': error_summary} + c.errors_json = json.dumps(errors) self._setup_template_variables(context, {'id': id}) @@ -370,6 +372,7 @@ def edit(self, id, data=None, errors=None, error_summary=None): errors = errors or {} vars = {'data': data, 'errors': errors, 'error_summary': error_summary} + c.errors_json = json.dumps(errors) self._setup_template_variables(context, {'id': id}, package_type=package_type) @@ -379,7 +382,15 @@ def edit(self, id, data=None, errors=None, error_summary=None): c.form = render(self.package_form, extra_vars=vars) else: c.form = render(self._package_form(package_type=package_type), extra_vars=vars) - return render('package/edit.html') + + if (c.action == u'editresources'): + return render('package/editresources.html') + else: + return render('package/edit.html') + + def editresources(self, id, data=None, errors=None, error_summary=None): + '''Hook method made available for routing purposes.''' + return self.edit(id,data,errors,error_summary) def read_ajax(self, id, revision=None): package_type=self._get_package_type(id) @@ -450,8 +461,6 @@ def _get_package_type(self, id): aborts if an exception is raised. """ - global _controller_behaviour_for - context = {'model': model, 'session': model.Session, 'user': c.user or c.author} try: @@ -523,13 +532,6 @@ def _form_save_redirect(self, pkgname, action): @param action - What the action of the edit was ''' assert action in ('new', 'edit') - if action == 'new': - msg = _('Congratulations, your dataset has been created. ' \ - 'Upload or link ' \ - 'some data now »') - msg = msg % h.url_for(controller='package', action='edit', - id=pkgname, anchor='section-resources') - h.flash_success(msg,allow_html=True) url = request.params.get('return_to') or \ config.get('package_%s_return_url' % action) if url: @@ -660,6 +662,7 @@ def resource_read(self, id, resource_id): c.package['isopen'] = model.Package.get_license_register()[license_id].isopen() except KeyError: c.package['isopen'] = False - + c.datastore_api = h.url_for('datastore_read', id=c.resource.get('id'), + qualified=True) return render('package/resource_read.html') diff --git a/ckan/controllers/user.py b/ckan/controllers/user.py index b7f74533e7b..f446c21c790 100644 --- a/ckan/controllers/user.py +++ b/ckan/controllers/user.py @@ -11,7 +11,7 @@ from ckan.logic import NotFound, NotAuthorized, ValidationError from ckan.logic import check_access, get_action from ckan.logic import tuplize_dict, clean_dict, parse_params -from ckan.logic.schema import user_new_form_schema, user_edit_form_schema +from ckan.logic.schema import user_new_form_schema, user_edit_form_schema from ckan.logic.action.get import user_activity_list_html from ckan.lib.captcha import check_recaptcha, CaptchaError @@ -28,7 +28,7 @@ def __before__(self, action, **env): if c.action not in ('login','request_reset','perform_reset',): abort(401, _('Not authorized to see this page')) - ## hooks for subclasses + ## hooks for subclasses new_user_form = 'user/new_user_form.html' edit_user_form = 'user/edit_user_form.html' @@ -207,10 +207,10 @@ def edit(self, id=None, data=None, errors=None, error_summary=None): abort(404, _('User not found')) user_obj = context.get('user_obj') - + if not (ckan.authz.Authorizer().is_sysadmin(unicode(c.user)) or c.user == user_obj.name): abort(401, _('User %s not authorized to edit %s') % (str(c.user), id)) - + errors = errors or {} vars = {'data': data, 'errors': errors, 'error_summary': error_summary} @@ -255,7 +255,7 @@ def login(self): return render('user/login.html') else: return render('user/logout_first.html') - + def logged_in(self): if c.user: context = {'model': model, @@ -277,14 +277,14 @@ def logged_in(self): h.flash_error('Login failed. Bad username or password.' + \ ' (Or if using OpenID, it hasn\'t been associated with a user account.)') h.redirect_to(controller='user', action='login') - + def logged_out(self): c.user = None response.delete_cookie("ckan_user") response.delete_cookie("ckan_display_name") response.delete_cookie("ckan_apikey") return render('user/logout.html') - + def request_reset(self): if request.method == 'POST': id = request.params.get('user') @@ -346,7 +346,7 @@ def perform_reset(self, id): if request.method == 'POST': try: - context['reset_password'] = True + context['reset_password'] = True new_password = self._get_form_password() user_dict['password'] = new_password user_dict['reset_key'] = c.reset_key @@ -374,7 +374,7 @@ def _format_about(self, about): log.error('Could not print "about" field Field: %r Error: %r', about, e) html = _('Error: Could not parse About text') return html - + def _get_form_password(self): password1 = request.params.getone('password1') password2 = request.params.getone('password2') @@ -384,4 +384,4 @@ def _get_form_password(self): elif not password1 == password2: raise ValueError(_("The passwords you entered do not match.")) return password1 - + diff --git a/ckan/lib/alphabet_paginate.py b/ckan/lib/alphabet_paginate.py index f30f5f9c78d..0afa88f3ff1 100644 --- a/ckan/lib/alphabet_paginate.py +++ b/ckan/lib/alphabet_paginate.py @@ -45,11 +45,14 @@ def __init__(self, collection, alpha_attribute, page, other_text, paging_thresho self.controller_name = controller_name self.available = dict( (c,0,) for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ) for c in self.collection: - x = c[0] if isinstance( c, unicode ) else getattr(c, self.alpha_attribute)[0] + if isinstance(c, unicode): + x = c[0] + elif isinstance(c, dict): + x = c[self.alpha_attribute][0] + else: + x = getattr(c, self.alpha_attribute)[0] self.available[x] = self.available.get(x, 0) + 1 - - def pager(self, q=None): '''Returns pager html - for navigating between the pages. e.g. Something like this: diff --git a/ckan/lib/authenticator.py b/ckan/lib/authenticator.py index 8eea54ae347..b56711a3427 100644 --- a/ckan/lib/authenticator.py +++ b/ckan/lib/authenticator.py @@ -5,7 +5,7 @@ class OpenIDAuthenticator(object): implements(IAuthenticator) - + def authenticate(self, environ, identity): if 'repoze.who.plugins.openid.userid' in identity: openid = identity.get('repoze.who.plugins.openid.userid') @@ -15,16 +15,16 @@ def authenticate(self, environ, identity): else: return user.name return None - + class UsernamePasswordAuthenticator(object): implements(IAuthenticator) - + def authenticate(self, environ, identity): if not 'login' in identity or not 'password' in identity: return None user = User.by_name(identity.get('login')) - if user is None: + if user is None: return None if user.validate_password(identity.get('password')): return user.name diff --git a/ckan/lib/create_test_data.py b/ckan/lib/create_test_data.py index b90fbc3bd3e..be6cb04f446 100644 --- a/ckan/lib/create_test_data.py +++ b/ckan/lib/create_test_data.py @@ -22,7 +22,7 @@ class CreateTestData(cli.CkanCommand): tag_names = [] group_names = set() user_refs = [] - + pkg_core_fields = ['name', 'title', 'version', 'url', 'notes', 'author', 'author_email', 'maintainer', 'maintainer_email', @@ -89,7 +89,7 @@ def create_test_user(cls): @classmethod def create_arbitrary(cls, package_dicts, relationships=[], - extra_user_names=[], extra_group_names=[], + extra_user_names=[], extra_group_names=[], admins=[]): '''Creates packages and a few extra objects as well at the same time if required. @@ -101,7 +101,7 @@ def create_arbitrary(cls, package_dicts, relationships=[], @param extra_group_names - a list of group names to create. No properties get set though. @param admins - a list of user names to make admins of all the - packages created. + packages created. ''' assert isinstance(relationships, (list, tuple)) assert isinstance(extra_user_names, (list, tuple)) @@ -111,11 +111,11 @@ def create_arbitrary(cls, package_dicts, relationships=[], new_user_names = extra_user_names new_group_names = set() new_groups = {} - - rev = model.repo.new_revision() + + rev = model.repo.new_revision() rev.author = cls.author rev.message = u'Creating test packages.' - + admins_list = defaultdict(list) # package_name: admin_names if package_dicts: if isinstance(package_dicts, dict): @@ -131,7 +131,7 @@ def create_arbitrary(cls, package_dicts, relationships=[], if isinstance(val, str): val = unicode(val) if attr=='name': - continue + continue if attr in cls.pkg_core_fields: pass elif attr == 'download_url': @@ -160,7 +160,7 @@ def create_arbitrary(cls, package_dicts, relationships=[], if not tag: tag = model.Tag(name=tag_name) cls.tag_names.append(tag_name) - model.Session.add(tag) + model.Session.add(tag) pkg.add_tag(tag) model.Session.flush() elif attr == 'groups': @@ -208,8 +208,8 @@ def create_arbitrary(cls, package_dicts, relationships=[], model.repo.commit_and_remove() needs_commit = False - - rev = model.repo.new_revision() + + rev = model.repo.new_revision() for group_name in extra_group_names: group = model.Group(name=unicode(group_name)) model.Session.add(group) @@ -258,7 +258,7 @@ def create_arbitrary(cls, package_dicts, relationships=[], needs_commit = False if relationships: - rev = model.repo.new_revision() + rev = model.repo.new_revision() rev.author = cls.author rev.message = u'Creating package relationships.' @@ -270,7 +270,7 @@ def pkg(pkg_name): needs_commit = True model.repo.commit_and_remove() - + @classmethod def create_groups(cls, group_dicts, admin_user_name=None, auth_profile=""): @@ -324,7 +324,7 @@ def create(cls, auth_profile="", package_type=None): cls.pkg_names = [u'annakarenina', u'warandpeace'] pkg1 = model.Package(name=cls.pkg_names[0], type=package_type) - if auth_profile == "publisher": + if auth_profile == "publisher": pkg1.group = publisher_group model.Session.add(pkg1) pkg1.title = u'A Novel By Tolstoy' @@ -368,7 +368,7 @@ def create(cls, auth_profile="", package_type=None): u with umlaut \xfc 66-style quote \u201c foreign word: th\xfcmb - + Needs escaping: left arrow < @@ -379,7 +379,7 @@ def create(cls, auth_profile="", package_type=None): tag1 = model.Tag(name=u'russian') tag2 = model.Tag(name=u'tolstoy') - if auth_profile == "publisher": + if auth_profile == "publisher": pkg2.group = publisher_group # Flexible tag, allows spaces, upper-case, @@ -407,12 +407,12 @@ def create(cls, auth_profile="", package_type=None): type=auth_profile or 'group') for obj in [david, roger]: model.Session.add(obj) - + cls.group_names.add(u'david') cls.group_names.add(u'roger') model.Session.flush() - + model.Session.add(model.Member(table_id=pkg1.id, table_name='package', group=david)) model.Session.add(model.Member(table_id=pkg2.id, table_name='package', group=david)) model.Session.add(model.Member(table_id=pkg1.id, table_name='package', group=roger)) @@ -447,7 +447,7 @@ def create(cls, auth_profile="", package_type=None): # Create a couple of authorization groups for ag_name in [u'anauthzgroup', u'anotherauthzgroup']: - ag=model.AuthorizationGroup.by_name(ag_name) + ag=model.AuthorizationGroup.by_name(ag_name) if not ag: #may already exist, if not create ag=model.AuthorizationGroup(name=ag_name) model.Session.add(ag) @@ -559,7 +559,7 @@ def get_all_data(cls): 'groups':'ukgov test1 test2 penguin', 'license':'odc-by', 'notes':u'''From - + > The Government Information Locator Service (GILS) is an effort to identify, locate, and describe publicly available Federal > Because this collection is decentralized, the GPO @@ -604,7 +604,7 @@ def get_all_data(cls): {'name':'uk-government-expenditure', 'title':'UK Government Expenditure', 'tags':'workshop-20081101,uk,gov,expenditure,finance,public,funding,penguin'.split(','), - 'groups':'ukgov penguin', + 'groups':'ukgov penguin', 'notes':'''Discussed at [Workshop on Public Information, 2008-11-02](http://okfn.org/wiki/PublicInformation). Overview is available in Red Book, or Financial Statement and Budget Report (FSBR), [published by the Treasury](http://www.hm-treasury.gov.uk/budget.htm).''', @@ -613,7 +613,7 @@ def get_all_data(cls): {'name':'se-publications', 'title':'Sweden - Government Offices of Sweden - Publications', 'url':'http://www.sweden.gov.se/sb/d/574', - 'groups':'penguin', + 'groups':'penguin', 'tags':u'country-sweden,format-pdf,access-www,documents,publications,government,eutransparency,penguin,CAPITALS,surprise.,greek omega \u03a9,japanese katakana \u30a1'.split(','), 'license':'', 'notes':'''### About @@ -627,7 +627,7 @@ def get_all_data(cls): }, {'name':'se-opengov', 'title':'Opengov.se', - 'groups':'penguin', + 'groups':'penguin', 'url':'http://www.opengov.se/', 'download_url':'http://www.opengov.se/data/open/', 'tags':'country-sweden,government,data,penguin'.split(','), diff --git a/ckan/lib/dictization/__init__.py b/ckan/lib/dictization/__init__.py index 66d4abf6dec..42f748b07d6 100644 --- a/ckan/lib/dictization/__init__.py +++ b/ckan/lib/dictization/__init__.py @@ -3,7 +3,7 @@ import sqlalchemy from pylons import config -# NOTE +# NOTE # The functions in this file contain very generic methods for dictizing objects # and saving dictized objects. If a specialised use is needed please do NOT extend # these functions. Copy code from here as needed. @@ -68,7 +68,7 @@ def obj_list_dictize(obj_list, context, sort_key=lambda x:x): return sorted(result_list, key=sort_key) def obj_dict_dictize(obj_dict, context, sort_key=lambda x:x): - '''Get a dict whose values are model objects + '''Get a dict whose values are model objects and represent it as a list of dicts''' result_list = [] @@ -93,7 +93,7 @@ def get_unique_constraints(table, context): def table_dict_save(table_dict, ModelClass, context): '''Given a dict and a model class, update or create a sqlalchemy object. - This will use an existing object if "id" is supplied OR if any unique + This will use an existing object if "id" is supplied OR if any unique constraints are met. e.g supplying just a tag name will get out that tag obj. ''' @@ -107,7 +107,7 @@ def table_dict_save(table_dict, ModelClass, context): unique_constriants = get_unique_constraints(table, context) id = table_dict.get("id") - + if id: obj = session.query(ModelClass).get(id) diff --git a/ckan/lib/dictization/model_dictize.py b/ckan/lib/dictization/model_dictize.py index ee3d268250c..1b4deb6f38a 100644 --- a/ckan/lib/dictization/model_dictize.py +++ b/ckan/lib/dictization/model_dictize.py @@ -12,7 +12,7 @@ ## package save -def group_list_dictize(obj_list, context, +def group_list_dictize(obj_list, context, sort_key=lambda x:x['display_name'], reverse=False): active = context.get('active', True) @@ -78,6 +78,7 @@ def extras_list_dictize(extras_list, context): def resource_dictize(res, context): resource = table_dictize(res, context) + resource['format'] = resource.get('format','').lower() extras = resource.pop("extras", None) if extras: resource.update(extras) @@ -93,10 +94,10 @@ def _execute_with_revision(q, rev_table, context): But you can provide revision_id, revision_date or pending in the context and it will filter to an earlier time or the latest unmoderated object revision. - + Raises NotFound if context['revision_id'] is provided, but the revision ID does not exist. - + Returns [] if there are no results. ''' @@ -113,7 +114,7 @@ def _execute_with_revision(q, rev_table, context): if not revision: raise NotFound revision_date = revision.timestamp - + if revision_date: q = q.where(rev_table.c.revision_timestamp <= revision_date) q = q.where(rev_table.c.expired_timestamp > revision_date) @@ -133,7 +134,7 @@ def package_dictize(pkg, context): but you can provide revision_id, revision_date or pending in the context and it will filter to an earlier time or the latest unmoderated object revision. - + May raise NotFound. TODO: understand what the specific set of circumstances are that cause this. ''' @@ -148,7 +149,7 @@ def package_dictize(pkg, context): #resources res_rev = model.resource_revision_table resource_group = model.resource_group_table - q = select([res_rev], from_obj = res_rev.join(resource_group, + q = select([res_rev], from_obj = res_rev.join(resource_group, resource_group.c.id == res_rev.c.resource_group_id)) q = q.where(resource_group.c.package_id == pkg.id) result = _execute_with_revision(q, res_rev, context) @@ -156,7 +157,7 @@ def package_dictize(pkg, context): #tags tag_rev = model.package_tag_revision_table tag = model.tag_table - q = select([tag, tag_rev.c.state, tag_rev.c.revision_timestamp], + q = select([tag, tag_rev.c.state, tag_rev.c.revision_timestamp], from_obj=tag_rev.join(tag, tag.c.id == tag_rev.c.tag_id) ).where(tag_rev.c.package_id == pkg.id) result = _execute_with_revision(q, tag_rev, context) @@ -171,7 +172,8 @@ def package_dictize(pkg, context): group = model.group_table q = select([group], from_obj=member_rev.join(group, group.c.id == member_rev.c.group_id) - ).where(member_rev.c.table_id == pkg.id) + ).where(member_rev.c.table_id == pkg.id)\ + .where(member_rev.c.state == 'active') result = _execute_with_revision(q, member_rev, context) result_dict["groups"] = obj_list_dictize(result, context) #relations @@ -182,7 +184,7 @@ def package_dictize(pkg, context): q = select([rel_rev]).where(rel_rev.c.object_package_id == pkg.id) result = _execute_with_revision(q, rel_rev, context) result_dict["relationships_as_object"] = obj_list_dictize(result, context) - + # Extra properties from the domain object # We need an actual Package object for this, not a PackageRevision if isinstance(pkg,PackageRevision): @@ -279,9 +281,10 @@ def tag_dictize(tag, context): result_dict = table_dictize(tag, context) result_dict["packages"] = obj_list_dictize(tag.packages, context) + return result_dict -def user_list_dictize(obj_list, context, +def user_list_dictize(obj_list, context, sort_key=lambda x:x['name'], reverse=False): result_list = [] @@ -302,13 +305,13 @@ def user_dictize(user, context): result_dict = table_dictize(user, context) del result_dict['password'] - + result_dict['display_name'] = user.display_name result_dict['email_hash'] = user.email_hash result_dict['number_of_edits'] = user.number_of_edits() result_dict['number_administered_packages'] = user.number_administered_packages() - return result_dict + return result_dict def task_status_dictize(task_status, context): return table_dictize(task_status, context) @@ -316,23 +319,23 @@ def task_status_dictize(task_status, context): ## conversion to api def group_to_api1(group, context): - + dictized = group_dictize(group, context) - dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) + dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) for extra in dictized["extras"]) dictized["packages"] = sorted([package["name"] for package in dictized["packages"]]) return dictized def group_to_api2(group, context): - + dictized = group_dictize(group, context) - dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) + dictized["extras"] = dict((extra["key"], json.loads(extra["value"])) for extra in dictized["extras"]) dictized["packages"] = sorted([package["id"] for package in dictized["packages"]]) return dictized def tag_to_api1(tag, context): - + dictized = tag_dictize(tag, context) return sorted([package["name"] for package in dictized["packages"]]) diff --git a/ckan/lib/dictization/model_save.py b/ckan/lib/dictization/model_save.py index f4ee8a0c9e1..d918532dafd 100644 --- a/ckan/lib/dictization/model_save.py +++ b/ckan/lib/dictization/model_save.py @@ -8,7 +8,7 @@ def resource_dict_save(res_dict, context): model = context["model"] session = context["session"] trigger_url_change = False - + id = res_dict.get("id") obj = None if id: @@ -21,7 +21,7 @@ def resource_dict_save(res_dict, context): table = class_mapper(model.Resource).mapped_table fields = [field.name for field in table.c] - + for key, value in res_dict.iteritems(): if isinstance(value, list): continue @@ -36,6 +36,11 @@ def resource_dict_save(res_dict, context): # of in a separate extras field like packages and groups obj.extras[key] = value + # Resource extras not submitted should be removed from the extras dict + extras_to_delete = set(obj.extras.keys()) - set(res_dict.keys()) + for delete_me in extras_to_delete: + del obj.extras[delete_me] + if context.get('pending'): if session.is_modified(obj, include_collections=False): obj.state = u'pending' @@ -68,7 +73,7 @@ def package_resource_list_save(res_dicts, package, context): else: resource.state = 'deleted' resource_list.append(resource) - tag_package_tag = dict((package_tag.tag, package_tag) + tag_package_tag = dict((package_tag.tag, package_tag) for package_tag in package.package_tag_all) @@ -89,7 +94,7 @@ def package_extras_save(extra_dicts, obj, context): for extra_dict in extra_dicts: if extra_dict.get("deleted"): continue - + if extra_dict['value'] is None: pass elif extras_as_string: @@ -146,10 +151,10 @@ def package_tag_list_save(tag_dicts, package, context): session = context["session"] pending = context.get('pending') - tag_package_tag = dict((package_tag.tag, package_tag) + tag_package_tag = dict((package_tag.tag, package_tag) for package_tag in package.package_tag_all) - + tag_package_tag_inactive = dict( [ (tag,pt) for tag,pt in tag_package_tag.items() if pt.state in ['deleted', 'pending-deleted'] ] @@ -199,7 +204,7 @@ def package_membership_list_save(group_dicts, package, context): members = session.query(model.Member).filter_by(table_id = package.id) - group_member = dict((member.group, member) + group_member = dict((member.group, member) for member in members) groups = set() @@ -233,7 +238,7 @@ def package_membership_list_save(group_dicts, package, context): member_obj.state = 'active' session.add(member_obj) - + def relationship_list_save(relationship_dicts, package, attr, context): allow_partial_update = context.get("allow_partial_update", False) @@ -248,7 +253,7 @@ def relationship_list_save(relationship_dicts, package, attr, context): relationships = [] for relationship_dict in relationship_dicts: - obj = table_dict_save(relationship_dict, + obj = table_dict_save(relationship_dict, model.PackageRelationship, context) relationships.append(obj) @@ -263,7 +268,7 @@ def relationship_list_save(relationship_dicts, package, attr, context): def package_dict_save(pkg_dict, context): import uuid - + model = context["model"] package = context.get("package") allow_partial_update = context.get("allow_partial_update", False) @@ -319,13 +324,21 @@ def group_member_save(context, group_dict, member_table_name): group_id=group.id, ).all() - entity_member = dict(((member.table_id, member.capacity), member) for member in members) + processed = { + 'added': [], + 'removed': [] + } + entity_member = dict(((member.table_id, member.capacity), member) for member in members) for entity_id in set(entity_member.keys()) - set(entities.keys()): + if entity_member[entity_id].state != 'deleted': + processed['removed'].append(entity_id[0]) entity_member[entity_id].state = 'deleted' session.add(entity_member[entity_id]) for entity_id in set(entity_member.keys()) & set(entities.keys()): + if entity_member[entity_id].state != 'active': + processed['added'].append(entity_id[0]) entity_member[entity_id].state = 'active' session.add(entity_member[entity_id]) @@ -333,12 +346,16 @@ def group_member_save(context, group_dict, member_table_name): member = Member(group=group, group_id=group.id, table_id=entity_id[0], table_name=member_table_name[:-1], capacity=entity_id[1]) + processed['added'].append(entity_id[0]) session.add(member) + return processed + def group_dict_save(group_dict, context): - import uuid - + from ckan.lib.search import rebuild + import uuid + model = context["model"] session = context["session"] group = context.get("group") @@ -346,19 +363,27 @@ def group_dict_save(group_dict, context): Group = model.Group if group: - group_dict["id"] = group.id + group_dict["id"] = group.id group = table_dict_save(group_dict, Group, context) if not group.id: group.id = str(uuid.uuid4()) - + context['group'] = group - group_member_save(context, group_dict, 'packages') + pkgs_edited = group_member_save(context, group_dict, 'packages') group_member_save(context, group_dict, 'users') group_member_save(context, group_dict, 'groups') group_member_save(context, group_dict, 'tags') + # We will get a list of packages that we have either added or + # removed from the group, and trigger a re-index. + package_ids = pkgs_edited['removed'] + package_ids.extend( pkgs_edited['added'] ) + if package_ids: + session.commit() + map( rebuild, package_ids ) + extras = group_extras_save(group_dict.get("extras", {}), context) if extras or not allow_partial_update: old_extras = set(group.extras.keys()) @@ -366,8 +391,7 @@ def group_dict_save(group_dict, context): for key in old_extras - new_extras: del group.extras[key] for key in new_extras: - group.extras[key] = extras[key] - + group.extras[key] = extras[key] return group @@ -377,11 +401,11 @@ def user_dict_save(user_dict, context): model = context['model'] session = context['session'] user = context.get('user_obj') - + User = model.User if user: user_dict['id'] = user.id - + if 'password' in user_dict and not len(user_dict['password']): del user_dict['password'] @@ -409,7 +433,7 @@ def package_api_to_dict(api1_dict, context): updated_extras.update(value) new_value = [] - + for extras_key, extras_value in updated_extras.iteritems(): if extras_value is not None: new_value.append({"key": extras_key, @@ -430,7 +454,7 @@ def package_api_to_dict(api1_dict, context): dictized["resources"] = [{'url': download_url}] download_url = dictized.pop('download_url', None) - + return dictized def group_api_to_dict(api1_dict, context): @@ -442,7 +466,7 @@ def group_api_to_dict(api1_dict, context): if key == 'packages': new_value = [{"id": item} for item in value] if key == 'extras': - new_value = [{"key": extra_key, "value": value[extra_key]} + new_value = [{"key": extra_key, "value": value[extra_key]} for extra_key in value] dictized[key] = new_value @@ -453,7 +477,7 @@ def task_status_dict_save(task_status_dict, context): task_status = context.get("task_status") allow_partial_update = context.get("allow_partial_update", False) if task_status: - task_status_dict["id"] = task_status.id + task_status_dict["id"] = task_status.id task_status = table_dict_save(task_status_dict, model.TaskStatus, context) return task_status diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index 1e7004f8521..972b8f180de 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -5,8 +5,10 @@ Consists of functions to typically be used within templates, but also available to Controllers. This module is available to templates as 'h'. """ +import email.utils import datetime import re +import urllib from webhelpers.html import escape, HTML, literal, url_escape from webhelpers.html.tools import mail_to @@ -323,20 +325,52 @@ def markdown_extract(text, extract_length=190): def icon_url(name): return url_for_static('/images/icons/%s.png' % name) -def icon_html(url, alt=None): - return literal('%s ' % (url, alt)) - -def icon(name, alt=None): - return icon_html(icon_url(name),alt) - -def linked_gravatar(email_hash, size=100, default="identicon"): +def icon_html(url, alt=None, inline=True): + classes = '' + if inline: classes += 'inline-icon ' + return literal('%s ' % (url, alt, classes)) + +def icon(name, alt=None, inline=True): + return icon_html(icon_url(name),alt,inline) + +def resource_icon(res): + if False: + icon_name = 'page_white' + # if (res.is_404?): icon_name = 'page_white_error' + # also: 'page_white_gear' + # also: 'page_white_link' + return icon(icon_name) + else: + return icon(format_icon(res.get('format',''))) + +def format_icon(_format): + _format = _format.lower() + if ('json' in _format): return 'page_white_cup' + if ('csv' in _format): return 'page_white_gear' + if ('xls' in _format): return 'page_white_excel' + if ('zip' in _format): return 'page_white_compressed' + if ('api' in _format): return 'page_white_database' + if ('plain text' in _format): return 'page_white_text' + if ('xml' in _format): return 'page_white_code' + return 'page_white' + +def linked_gravatar(email_hash, size=100, default=None): return literal(''' %s''' % gravatar(email_hash,size,default) ) -def gravatar(email_hash, size=100, default="identicon"): +_VALID_GRAVATAR_DEFAULTS = ['404', 'mm', 'identicon', 'monsterid', 'wavatar', 'retro'] +def gravatar(email_hash, size=100, default=None): + if default is None: + from pylons import config + default = config.get('ckan.gravatar_default', 'identicon') + + if not default in _VALID_GRAVATAR_DEFAULTS: + # treat the default as a url + default = urllib.quote(default, safe='') + return literal('''''' % (email_hash, size, default) @@ -397,6 +431,27 @@ def date_str_to_datetime(date_str): # a strptime. Also avoids problem with Python 2.5 not having %f. return datetime.datetime(*map(int, re.split('[^\d]', date_str))) +def parse_rfc_2822_date(date_str, tz_aware=True): + """ + Parse a date string of the form specified in RFC 2822, and return a datetime. + + RFC 2822 is the date format used in HTTP headers. + + If the date string contains a timezone indication, and tz_aware is True, + then the associated tzinfo is attached to the returned datetime object. + + Returns None if the string cannot be parse as a valid datetime. + """ + time_tuple = email.utils.parsedate_tz(date_str) + + if not time_tuple: + return None + + if not tz_aware: + time_tuple = time_tuple[:-1] + (None,) + + return datetime.datetime.fromtimestamp(email.utils.mktime_tz(time_tuple)) + def time_ago_in_words_from_str(date_str, granularity='month'): if date_str: return date.time_ago_in_words(date_str_to_datetime(date_str), granularity=granularity) @@ -454,3 +509,17 @@ def tag_link(tag): def group_link(group): url = url_for(controller='group', action='read', id=group['name']) return link_to(group['name'], url) + +def dump_json(obj): + import json + return json.dumps(obj) + +def auto_log_message(context): + from pylons.i18n import _ + if (context.action=='new') : + return _('Created new dataset.') + elif (context.action=='editresources'): + return _('Edited resources.') + elif (context.action=='edit'): + return _('Edited settings.') + return '' diff --git a/ckan/lib/plugins.py b/ckan/lib/plugins.py index 619fa02f4eb..1e20a8c0aac 100644 --- a/ckan/lib/plugins.py +++ b/ckan/lib/plugins.py @@ -54,8 +54,11 @@ def register_package_plugins(map): exception will be raised. """ global _default_package_plugin - if _default_package_plugin: - # we've already set things up + + # This function should have not effect if called more than once. + # This should not occur in normal deployment, but it may happen when + # running unit tests. + if _default_package_plugin is not None: return # Create the mappings and register the fallback behaviour if one is found. @@ -101,6 +104,12 @@ def register_group_plugins(map): """ global _default_group_plugin + # This function should have not effect if called more than once. + # This should not occur in normal deployment, but it may happen when + # running unit tests. + if _default_group_plugin is not None: + return + # Create the mappings and register the fallback behaviour if one is found. for plugin in plugins.PluginImplementations(plugins.IGroupForm): if plugin.is_fallback(): diff --git a/ckan/lib/search/index.py b/ckan/lib/search/index.py index d3e74aef537..b6c376c94d6 100644 --- a/ckan/lib/search/index.py +++ b/ckan/lib/search/index.py @@ -16,7 +16,7 @@ PACKAGE_TYPE = "package" KEY_CHARS = string.digits + string.letters + "_-" SOLR_FIELDS = [TYPE_FIELD, "res_url", "text", "urls", "indexed_ts", "site_id"] -RESERVED_FIELDS = SOLR_FIELDS + ["tags", "groups", "res_description", +RESERVED_FIELDS = SOLR_FIELDS + ["tags", "groups", "res_description", "res_format", "res_url"] RELATIONSHIP_TYPES = PackageRelationship.types @@ -39,28 +39,28 @@ def clear_index(): conn.close() class SearchIndex(object): - """ - A search index handles the management of documents of a specific type in the - index, but no queries. - The default implementation maps many of the methods, so most subclasses will - only have to implement ``update_dict`` and ``remove_dict``. - """ - + """ + A search index handles the management of documents of a specific type in the + index, but no queries. + The default implementation maps many of the methods, so most subclasses will + only have to implement ``update_dict`` and ``remove_dict``. + """ + def __init__(self): pass - + def insert_dict(self, data): """ Insert new data from a dictionary. """ return self.update_dict(data) - + def update_dict(self, data): """ Update data from a dictionary. """ log.debug("NOOP Index: %s" % ",".join(data.keys())) - + def remove_dict(self, data): """ Delete an index entry uniquely identified by ``data``. """ log.debug("NOOP Delete: %s" % ",".join(data.keys())) - + def clear(self): """ Delete the complete index. """ clear_index() @@ -68,26 +68,26 @@ def clear(self): def get_all_entity_ids(self): """ Return a list of entity IDs in the index. """ raise NotImplemented - + class NoopSearchIndex(SearchIndex): pass class PackageSearchIndex(SearchIndex): def remove_dict(self, pkg_dict): self.delete_package(pkg_dict) - + def update_dict(self, pkg_dict): self.index_package(pkg_dict) def index_package(self, pkg_dict): - if pkg_dict is None: - return + if pkg_dict is None: + return if (not pkg_dict.get('state')) or ('active' not in pkg_dict.get('state')): return self.delete_package(pkg_dict) conn = make_connection() index_fields = RESERVED_FIELDS + pkg_dict.keys() - + # include the extras in the main namespace extras = pkg_dict.get('extras', {}) for (key, value) in extras.items(): @@ -100,7 +100,7 @@ def index_package(self, pkg_dict): if 'extras' in pkg_dict: del pkg_dict['extras'] - # flatten the structure for indexing: + # flatten the structure for indexing: for resource in pkg_dict.get('resources', []): for (okey, nkey) in [('description', 'res_description'), ('format', 'res_format'), @@ -108,18 +108,18 @@ def index_package(self, pkg_dict): pkg_dict[nkey] = pkg_dict.get(nkey, []) + [resource.get(okey, u'')] if 'resources' in pkg_dict: del pkg_dict['resources'] - + # index relationships as : rel_dict = {} rel_types = list(itertools.chain(RELATIONSHIP_TYPES)) for rel in pkg_dict.get('relationships', []): _type = rel.get('type', 'rel') - if (_type in pkg_dict.keys()) or (_type not in rel_types): + if (_type in pkg_dict.keys()) or (_type not in rel_types): continue rel_dict[_type] = rel_dict.get(_type, []) + [rel.get('object')] - + pkg_dict.update(rel_dict) - + if 'relationships' in pkg_dict: del pkg_dict['relationships'] @@ -134,7 +134,7 @@ def index_package(self, pkg_dict): # mark this CKAN instance as data source: pkg_dict['site_id'] = config.get('ckan.site_id') - + # add a unique index_id to avoid conflicts import hashlib pkg_dict['index_id'] = hashlib.md5('%s%s' % (pkg_dict['id'],config.get('ckan.site_id'))).hexdigest() @@ -144,7 +144,7 @@ def index_package(self, pkg_dict): assert pkg_dict, 'Plugin must return non empty package dict on index' - # send to solr: + # send to solr: try: conn.add_many([pkg_dict]) conn.commit(wait_flush=False, wait_searcher=False) @@ -152,8 +152,8 @@ def index_package(self, pkg_dict): log.exception(e) raise SearchIndexError(e) finally: - conn.close() - + conn.close() + log.debug("Updated index for %s" % pkg_dict.get('name')) def delete_package(self, pkg_dict): diff --git a/ckan/logic/__init__.py b/ckan/logic/__init__.py index 250d93d4cde..513f2918ab3 100644 --- a/ckan/logic/__init__.py +++ b/ckan/logic/__init__.py @@ -25,6 +25,11 @@ class ActionError(Exception): def __init__(self, extra_msg=None): self.extra_msg = extra_msg + def __str__(self): + err_msgs = (super(ActionError, self).__str__(), + self.extra_msg) + return ' - '.join([str(err_msg) for err_msg in err_msgs if err_msg]) + class NotFound(ActionError): pass @@ -40,6 +45,11 @@ def __init__(self, error_dict, error_summary=None, extra_msg=None): self.error_summary = error_summary self.extra_msg = extra_msg + def __str__(self): + err_msgs = (super(ValidationError, self).__str__(), + self.error_summary) + return ' - '.join([str(err_msg) for err_msg in err_msgs if err_msg]) + log = logging.getLogger(__name__) def parse_params(params, ignore_keys=None): diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index a148b6a8071..b63b2dc92d7 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -371,7 +371,6 @@ def revision_show(context, data_dict): def group_show(context, data_dict): '''Shows group details''' - model = context['model'] id = data_dict['id'] api = context.get('api_version') or '1' @@ -610,7 +609,7 @@ def format_autocomplete(context, data_dict): .order_by('total DESC')\ .limit(limit) - return [resource.format for resource in query] + return [resource.format.lower() for resource in query] def user_autocomplete(context, data_dict): '''Returns users containing the provided string''' @@ -910,7 +909,7 @@ def get_site_user(context, data_dict): def roles_show(context, data_dict): '''Returns the roles that users (and authorization groups) have on a particular domain_object. - + If you specify a user (or authorization group) then the resulting roles will be filtered by those of that user (or authorization group). diff --git a/ckan/logic/auth/publisher/get.py b/ckan/logic/auth/publisher/get.py index 7766b1ae1fa..27515958d15 100644 --- a/ckan/logic/auth/publisher/get.py +++ b/ckan/logic/auth/publisher/get.py @@ -65,40 +65,44 @@ def package_show(context, data_dict): """ Package show permission checks the user group if the state is deleted """ model = context['model'] package = get_package_object(context, data_dict) - + if package.state == 'deleted': if 'ignore_auth' in context and context['ignore_auth']: - return {'success': True} - + return {'success': True} + user = context.get('user') if not user: return {'success': False, 'msg': _('User not authorized to read package %s') % (package.id)} userobj = model.User.get( user ) + + if Authorizer().is_sysadmin(unicode(user)): + return {'success': True} + if not userobj: return {'success': False, 'msg': _('User %s not authorized to read package %s') % (str(user),package.id)} if not _groups_intersect( userobj.get_groups('publisher'), package.get_groups('publisher') ): return {'success': False, 'msg': _('User %s not authorized to read package %s') % (str(user),package.id)} - + return {'success': True} def resource_show(context, data_dict): - """ Resource show permission checks the user group if the package state is deleted """ + """ Resource show permission checks the user group if the package state is deleted """ model = context['model'] user = context.get('user') resource = get_resource_object(context, data_dict) - package = resource.revision_group.package + package = resource.resource_group.package if package.state == 'deleted': userobj = model.User.get( user ) if not userobj: - return {'success': False, 'msg': _('User %s not authorized to read resource %s') % (str(user),package.id)} + return {'success': False, 'msg': _('User %s not authorized to read resource %s') % (str(user),package.id)} if not _groups_intersect( userobj.get_groups('publisher'), package.get_groups('publisher') ): return {'success': False, 'msg': _('User %s not authorized to read package %s') % (str(user),package.id)} - - pkg_dict = {'id': pkg.id} + + pkg_dict = {'id': package.id} return package_show(context, pkg_dict) @@ -112,12 +116,12 @@ def group_show(context, data_dict): user = context.get('user') group = get_group_object(context, data_dict) userobj = model.User.get( user ) - + if group.state == 'deleted': if not user or \ not _groups_intersect( userobj.get_groups('publisher'), group.get_groups('publisher') ): - return {'success': False, 'msg': _('User %s not authorized to show group %s') % (str(user),group.id)} - + return {'success': False, 'msg': _('User %s not authorized to show group %s') % (str(user),group.id)} + return {'success': True} def tag_show(context, data_dict): diff --git a/ckan/logic/schema.py b/ckan/logic/schema.py index 164343a4eac..aa0d84ea0c4 100644 --- a/ckan/logic/schema.py +++ b/ckan/logic/schema.py @@ -186,6 +186,13 @@ def default_group_schema(): '__extras': [ignore], 'packages': { "id": [not_empty, unicode, package_id_or_name_exists], + "title":[ignore_missing, unicode], + "name":[ignore_missing, unicode], + "__extras": [ignore] + }, + 'groups': { + "name": [not_empty, unicode], + "capacity": [ignore_missing], "__extras": [ignore] }, 'users': { @@ -206,13 +213,14 @@ def group_form_schema(): #schema['extras_validation'] = [duplicate_extras_key, ignore] schema['packages'] = { "name": [not_empty, unicode], + "title": [ignore_missing], "__extras": [ignore] } schema['users'] = { "name": [not_empty, unicode], - "capacity": [ignore_missing], + "capacity": [ignore_missing], "__extras": [ignore] - } + } return schema diff --git a/ckan/logic/validators.py b/ckan/logic/validators.py index ec635c296cd..290a3c9600f 100644 --- a/ckan/logic/validators.py +++ b/ckan/logic/validators.py @@ -114,6 +114,16 @@ def group_id_exists(group_id, context): raise Invalid('%s: %s' % (_('Not found'), _('Group'))) return group_id +def group_id_or_name_exists(reference, context): + """ + Raises Invalid if a group identified by the name or id cannot be found. + """ + model = context['model'] + result = model.Group.get(reference) + if not result: + raise Invalid(_('That group name or ID does not exist.')) + return reference + def activity_type_exists(activity_type): """Raises Invalid if there is no registered activity renderer for the given activity_type. Otherwise returns the given activity_type. diff --git a/ckan/model/group.py b/ckan/model/group.py index e90b35a4c35..70925563811 100644 --- a/ckan/model/group.py +++ b/ckan/model/group.py @@ -103,6 +103,20 @@ def get(cls, reference): return group # Todo: Make sure group names can't be changed to look like group IDs? + @classmethod + def all(cls, group_type=None, state=('active',)): + """ + Returns all groups. + """ + q = Session.query(cls) + if state: + q = q.filter(cls.state.in_(state)) + + if group_type: + q = q.filter(cls.type==group_type) + + return q.order_by(cls.title) + def set_approval_status(self, status): """ Aproval status can be set on a group, where currently it does @@ -116,17 +130,21 @@ def set_approval_status(self, status): pass def members_of_type(self, object_type, capacity=None): + from ckan import model object_type_string = object_type.__name__.lower() query = Session.query(object_type).\ - filter(group_table.c.id == self.id).\ - filter(member_table.c.state == 'active').\ - filter(member_table.c.table_name == object_type_string) + filter(model.Group.id == self.id).\ + filter(model.Member.state == 'active').\ + filter(model.Member.table_name == object_type_string) + + if hasattr(object_type,'state'): + query = query.filter(object_type.state == 'active' ) if capacity: - query = query.filter(member_table.c.capacity == capacity) + query = query.filter(model.Member.capacity == capacity) - query = query.join(member_table, member_table.c.table_id == getattr(object_type,'id') ).\ - join(group_table, group_table.c.id == member_table.c.group_id) + query = query.join(model.Member, member_table.c.table_id == getattr(object_type,'id') ).\ + join(model.Group, group_table.c.id == member_table.c.group_id) return query @@ -136,6 +154,15 @@ def add_child(self, object_instance): member = Member(group=self, table_id=getattr(object_instance,'id'), table_name=object_type_string) Session.add(member) + def get_children_groups(self, type='group'): + # Returns a list of dicts where each dict contains "id", "name", and "title" + # When querying with a CTE specifying a model in the query parameter causes + # problems as it returns only the first level deep apparently not recursing + # any deeper than that. If we simplify and request only specific fields then + # if returns the full depth of the hierarchy. + results = Session.query("id","name", "title").\ + from_statement(HIERARCHY_CTE).params(id=self.id, type=type).all() + return [ { "id":idf, "name": name, "title": title } for idf,name,title in results ] def active_packages(self, load_eager=True): query = Session.query(Package).\ @@ -242,3 +269,15 @@ def __repr__(self): #TODO MemberRevision.related_packages = lambda self: [self.continuity.package] +HIERARCHY_CTE = """ + WITH RECURSIVE subtree(id) AS ( + SELECT M.* FROM public.member AS M + WHERE M.table_name = 'group' AND M.state = 'active' + UNION + SELECT M.* FROM public.member M, subtree SG + WHERE M.table_id = SG.group_id AND M.table_name = 'group' AND M.state = 'active') + + SELECT G.* FROM subtree AS ST + INNER JOIN public.group G ON G.id = ST.table_id + WHERE group_id = :id AND G.type = :type and table_name='group' +""" diff --git a/ckan/model/user.py b/ckan/model/user.py index ed07c0c5e37..780cb94ef00 100644 --- a/ckan/model/user.py +++ b/ckan/model/user.py @@ -23,15 +23,15 @@ ) class User(DomainObject): - + VALID_NAME = re.compile(r"^[a-zA-Z0-9_\-]{3,255}$") DOUBLE_SLASH = re.compile(':\/([^/])') - + @classmethod def by_openid(cls, openid): obj = Session.query(cls).autoflush(False) return obj.filter_by(openid=openid).first() - + @classmethod def get(cls, user_reference): # double slashes in an openid often get turned into single slashes @@ -57,7 +57,7 @@ def email_hash(self): if self.email: e = self.email.strip().lower().encode('utf8') return hashlib.md5(e).hexdigest() - + def get_reference_preferred_for_uri(self): '''Returns a reference (e.g. name, id, openid) for this user suitable for the user\'s URI. @@ -73,7 +73,7 @@ def get_reference_preferred_for_uri(self): else: ref = self.id return ref - + def _set_password(self, password): """Hash password on the fly.""" if isinstance(password, unicode): @@ -104,7 +104,7 @@ def validate_password(self, password): :return: Whether the password is valid. :rtype: bool """ - if not password or not self.password: + if not password or not self.password: return False if isinstance(password, unicode): password_8bit = password.encode('ascii', 'ignore') @@ -114,7 +114,7 @@ def validate_password(self, password): return self.password[40:] == hashed_pass.hexdigest() password = property(_get_password, _set_password) - + @classmethod def check_name_valid(cls, name): if not name \ @@ -147,10 +147,23 @@ def number_administered_packages(self): def is_in_group(self, group): return group in self.get_groups() - + + def is_in_groups(self, groupids): + """ Given a list of group ids, returns True if this user is in any of + those groups """ + guser = set( self.get_group_ids() ) + gids = set( groupids ) + + return len( guser.intersection( gids ) ) > 0 + + + def get_group_ids(self, group_type=None): + """ Returns a list of group ids that the current user belongs to """ + return [ g.id for g in self.get_groups( group_type=group_type ) ] + def get_groups(self, group_type=None, capacity=None): import ckan.model as model - + q = model.Session.query(model.Group)\ .join(model.Member, model.Member.group_id == model.Group.id and \ model.Member.table_name == 'user' ).\ @@ -160,12 +173,12 @@ def get_groups(self, group_type=None, capacity=None): if capacity: q = q.filter( model.Member.capacity == capacity ) return q.all() - + if '_groups' not in self.__dict__: self._groups = q.all() - + groups = self._groups - if group_type: + if group_type: groups = [g for g in groups if g.type == group_type] return groups @@ -173,7 +186,7 @@ def get_groups(self, group_type=None, capacity=None): @classmethod def search(cls, querystr, sqlalchemy_query=None): '''Search name, fullname, email and openid. - + ''' import ckan.model as model if sqlalchemy_query is None: diff --git a/ckan/public/css/bootstrap.css b/ckan/public/css/bootstrap.css index 708a79e9043..d27dfed199b 100644 --- a/ckan/public/css/bootstrap.css +++ b/ckan/public/css/bootstrap.css @@ -124,481 +124,2649 @@ button.pretty-button::-moz-focus-inner, input.pretty-button::-moz-focus-inner { /* :: Bootstrap v2.0 :: */ /* ================== */ -/* ================================== */ -/* = Twitter.Bootstrap: Buttons etc = */ -/* ================================== */ +/*! + * Bootstrap v2.0.0 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section { + display: block; +} +audio, canvas, video { + display: inline-block; + *display: inline; + *zoom: 1; +} +audio:not([controls]) { + display: none; +} +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +a:hover, a:active { + outline: 0; +} +sub, sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} +img { + max-width: 100%; + height: auto; + border: 0; + -ms-interpolation-mode: bicubic; +} +button, +input, +select, +textarea { + margin: 0; + font-size: 100%; + vertical-align: middle; +} +button, input { + *overflow: visible; + line-height: normal; +} +button::-moz-focus-inner, input::-moz-focus-inner { + padding: 0; + border: 0; +} +button, +input[type="button"], +input[type="reset"], +input[type="submit"] { + cursor: pointer; + -webkit-appearance: button; +} +input[type="search"] { + -webkit-appearance: textfield; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; +} +textarea { + overflow: auto; + vertical-align: top; +} +body { + margin: 0; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 13px; + line-height: 18px; + color: #333333; + background-color: #ffffff; +} +a { + color: #0088cc; + text-decoration: none; +} +a:hover { + color: #005580; + text-decoration: underline; +} +.row { + margin-left: -20px; + *zoom: 1; +} +.row:before, .row:after { + display: table; + content: ""; +} +.row:after { + clear: both; +} +[class*="span"] { + float: left; + margin-left: 20px; +} +.span1 { + width: 60px; +} +.span2 { + width: 140px; +} +.span3 { + width: 220px; +} +.span4 { + width: 300px; +} +.span5 { + width: 380px; +} +.span6 { + width: 460px; +} +.span7 { + width: 540px; +} +.span8 { + width: 620px; +} +.span9 { + width: 700px; +} +.span10 { + width: 780px; +} +.span11 { + width: 860px; +} +.span12, .container { + width: 940px; +} +.offset1 { + margin-left: 100px; +} +.offset2 { + margin-left: 180px; +} +.offset3 { + margin-left: 260px; +} +.offset4 { + margin-left: 340px; +} +.offset5 { + margin-left: 420px; +} +.offset6 { + margin-left: 500px; +} +.offset7 { + margin-left: 580px; +} +.offset8 { + margin-left: 660px; +} +.offset9 { + margin-left: 740px; +} +.offset10 { + margin-left: 820px; +} +.offset11 { + margin-left: 900px; +} +.row-fluid { + width: 100%; + *zoom: 1; +} +.row-fluid:before, .row-fluid:after { + display: table; + content: ""; +} +.row-fluid:after { + clear: both; +} +.row-fluid > [class*="span"] { + float: left; + margin-left: 2.127659574%; +} +.row-fluid > [class*="span"]:first-child { + margin-left: 0; +} +.row-fluid .span1 { + width: 6.382978723%; +} +.row-fluid .span2 { + width: 14.89361702%; +} +.row-fluid .span3 { + width: 23.404255317%; +} +.row-fluid .span4 { + width: 31.914893614%; +} +.row-fluid .span5 { + width: 40.425531911%; +} +.row-fluid .span6 { + width: 48.93617020799999%; +} +.row-fluid .span7 { + width: 57.446808505%; +} +.row-fluid .span8 { + width: 65.95744680199999%; +} +.row-fluid .span9 { + width: 74.468085099%; +} +.row-fluid .span10 { + width: 82.97872339599999%; +} +.row-fluid .span11 { + width: 91.489361693%; +} +.row-fluid .span12 { + width: 99.99999998999999%; +} +.container { + width: 940px; + margin-left: auto; + margin-right: auto; + *zoom: 1; +} +.container:before, .container:after { + display: table; + content: ""; +} +.container:after { + clear: both; +} +.container-fluid { + padding-left: 20px; + padding-right: 20px; + *zoom: 1; +} +.container-fluid:before, .container-fluid:after { + display: table; + content: ""; +} +.container-fluid:after { + clear: both; +} +p { + margin: 0 0 9px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 13px; + line-height: 18px; +} +p small { + font-size: 11px; + color: #999999; +} +.lead { + margin-bottom: 18px; + font-size: 20px; + font-weight: 200; + line-height: 27px; +} +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; + font-weight: bold; + color: #333333; + text-rendering: optimizelegibility; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small { + font-weight: normal; + color: #999999; +} +h1 { + font-size: 30px; + line-height: 36px; +} +h1 small { + font-size: 18px; +} +h2 { + font-size: 24px; + line-height: 36px; +} +h2 small { + font-size: 18px; +} +h3 { + line-height: 27px; + font-size: 18px; +} +h3 small { + font-size: 14px; +} +h4, h5, h6 { + line-height: 18px; +} +h4 { + font-size: 14px; +} +h4 small { + font-size: 12px; +} +h5 { + font-size: 12px; +} +h6 { + font-size: 11px; + color: #999999; + text-transform: uppercase; +} +.page-header { + padding-bottom: 17px; + margin: 18px 0; + border-bottom: 1px solid #eeeeee; +} +.page-header h1 { + line-height: 1; +} +ul, ol { + padding: 0; + margin: 0 0 9px 25px; +} +ul ul, +ul ol, +ol ol, +ol ul { + margin-bottom: 0; +} +ul { + list-style: disc; +} +ol { + list-style: decimal; +} +li { + line-height: 18px; +} +ul.unstyled { + margin-left: 0; + list-style: none; +} +dl { + margin-bottom: 18px; +} +dt, dd { + line-height: 18px; +} +dt { + font-weight: bold; +} +dd { + margin-left: 9px; +} +hr { + margin: 18px 0; + border: 0; + border-top: 1px solid #e5e5e5; + border-bottom: 1px solid #ffffff; +} +strong { + font-weight: bold; +} +em { + font-style: italic; +} +.muted { + color: #999999; +} +abbr { + font-size: 90%; + text-transform: uppercase; + border-bottom: 1px dotted #ddd; + cursor: help; +} +blockquote { + padding: 0 0 0 15px; + margin: 0 0 18px; + border-left: 5px solid #eeeeee; +} +blockquote p { + margin-bottom: 0; + font-size: 16px; + font-weight: 300; + line-height: 22.5px; +} +blockquote small { + display: block; + line-height: 18px; + color: #999999; +} +blockquote small:before { + content: '\2014 \00A0'; +} +blockquote.pull-right { + float: right; + padding-left: 0; + padding-right: 15px; + border-left: 0; + border-right: 5px solid #eeeeee; +} +blockquote.pull-right p, blockquote.pull-right small { + text-align: right; +} +q:before, +q:after, +blockquote:before, +blockquote:after { + content: ""; +} +address { + display: block; + margin-bottom: 18px; + line-height: 18px; + font-style: normal; +} +small { + font-size: 100%; +} +cite { + font-style: normal; +} +code, pre { + padding: 0 3px 2px; + font-family: Menlo, Monaco, "Courier New", monospace; + font-size: 12px; + color: #333333; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +code { + padding: 3px 4px; + color: #d14; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; +} +pre { + display: block; + padding: 8.5px; + margin: 0 0 9px; + font-size: 12px; + line-height: 18px; + background-color: #f5f5f5; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.15); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + white-space: pre; + white-space: pre-wrap; + word-break: break-all; +} +pre.prettyprint { + margin-bottom: 18px; +} +pre code { + padding: 0; + background-color: transparent; +} +form { + margin: 0 0 18px; +} +fieldset { + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 27px; + font-size: 19.5px; + line-height: 36px; + color: #333333; + border: 0; + border-bottom: 1px solid #eee; +} +label, +input, +button, +select, +textarea { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 13px; + font-weight: normal; + line-height: 18px; +} +label { + display: block; + margin-bottom: 5px; + color: #333333; +} +input, +textarea, +select, +.uneditable-input { + display: inline-block; + width: 210px; + height: 18px; + padding: 4px; + margin-bottom: 9px; + font-size: 13px; + line-height: 18px; + color: #555555; + border: 1px solid #ccc; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.uneditable-textarea { + width: auto; + height: auto; +} +label input, label textarea, label select { + display: block; +} +input[type="image"], input[type="checkbox"], input[type="radio"] { + width: auto; + height: auto; + padding: 0; + margin: 3px 0; + *margin-top: 0; + /* IE7 */ + line-height: normal; + border: 0; + cursor: pointer; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +input[type="file"] { + padding: initial; + line-height: initial; + border: initial; + background-color: #ffffff; + background-color: initial; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} +input[type="button"], input[type="reset"], input[type="submit"] { + width: auto; + height: auto; +} +select, input[type="file"] { + height: 28px; + /* In IE7, the height of the select element cannot be changed by height, only font-size */ -.fade { - -webkit-transition: opacity 0.15s linear; - -moz-transition: opacity 0.15s linear; - -ms-transition: opacity 0.15s linear; - -o-transition: opacity 0.15s linear; + *margin-top: 4px; + /* For IE7, add top margin to align select with labels */ + + line-height: 28px; +} +select { + width: 220px; + background-color: #ffffff; +} +select[multiple], select[size] { + height: auto; +} +input[type="image"] { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} +textarea { + height: auto; +} +input[type="hidden"] { + display: none; +} +.radio, .checkbox { + padding-left: 18px; +} +.radio input[type="radio"], .checkbox input[type="checkbox"] { + float: left; + margin-left: -18px; +} +.controls > .radio:first-child, .controls > .checkbox:first-child { + padding-top: 5px; +} +.radio.inline, .checkbox.inline { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; +} +.radio.inline + .radio.inline, .checkbox.inline + .checkbox.inline { + margin-left: 10px; +} +.controls > .radio.inline:first-child, .controls > .checkbox.inline:first-child { + padding-top: 0; +} +input, textarea { + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; + -moz-transition: border linear 0.2s, box-shadow linear 0.2s; + -ms-transition: border linear 0.2s, box-shadow linear 0.2s; + -o-transition: border linear 0.2s, box-shadow linear 0.2s; + transition: border linear 0.2s, box-shadow linear 0.2s; +} +input:focus, textarea:focus { + border-color: rgba(82, 168, 236, 0.8); + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); + outline: 0; + outline: thin dotted \9; + /* IE6-8 */ + +} +input[type="file"]:focus, input[type="checkbox"]:focus, select:focus { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.input-mini { + width: 60px; +} +.input-small { + width: 90px; +} +.input-medium { + width: 150px; +} +.input-large { + width: 210px; +} +.input-xlarge { + width: 270px; +} +.input-xxlarge { + width: 530px; +} +input[class*="span"], +select[class*="span"], +textarea[class*="span"], +.uneditable-input { + float: none; + margin-left: 0; +} +input.span1, textarea.span1, .uneditable-input.span1 { + width: 50px; +} +input.span2, textarea.span2, .uneditable-input.span2 { + width: 130px; +} +input.span3, textarea.span3, .uneditable-input.span3 { + width: 210px; +} +input.span4, textarea.span4, .uneditable-input.span4 { + width: 290px; +} +input.span5, textarea.span5, .uneditable-input.span5 { + width: 370px; +} +input.span6, textarea.span6, .uneditable-input.span6 { + width: 450px; +} +input.span7, textarea.span7, .uneditable-input.span7 { + width: 530px; +} +input.span8, textarea.span8, .uneditable-input.span8 { + width: 610px; +} +input.span9, textarea.span9, .uneditable-input.span9 { + width: 690px; +} +input.span10, textarea.span10, .uneditable-input.span10 { + width: 770px; +} +input.span11, textarea.span11, .uneditable-input.span11 { + width: 850px; +} +input.span12, textarea.span12, .uneditable-input.span12 { + width: 930px; +} +input[disabled], +select[disabled], +textarea[disabled], +input[readonly], +select[readonly], +textarea[readonly] { + background-color: #f5f5f5; + border-color: #ddd; + cursor: not-allowed; +} +.control-group.warning > label, .control-group.warning .help-block, .control-group.warning .help-inline { + color: #c09853; +} +.control-group.warning input, .control-group.warning select, .control-group.warning textarea { + color: #c09853; + border-color: #c09853; +} +.control-group.warning input:focus, .control-group.warning select:focus, .control-group.warning textarea:focus { + border-color: #a47e3c; + -webkit-box-shadow: 0 0 6px #dbc59e; + -moz-box-shadow: 0 0 6px #dbc59e; + box-shadow: 0 0 6px #dbc59e; +} +.control-group.warning .input-prepend .add-on, .control-group.warning .input-append .add-on { + color: #c09853; + background-color: #fcf8e3; + border-color: #c09853; +} +.control-group.error > label, .control-group.error .help-block, .control-group.error .help-inline { + color: #b94a48; +} +.control-group.error input, .control-group.error select, .control-group.error textarea { + color: #b94a48; + border-color: #b94a48; +} +.control-group.error input:focus, .control-group.error select:focus, .control-group.error textarea:focus { + border-color: #953b39; + -webkit-box-shadow: 0 0 6px #d59392; + -moz-box-shadow: 0 0 6px #d59392; + box-shadow: 0 0 6px #d59392; +} +.control-group.error .input-prepend .add-on, .control-group.error .input-append .add-on { + color: #b94a48; + background-color: #f2dede; + border-color: #b94a48; +} +.control-group.success > label, .control-group.success .help-block, .control-group.success .help-inline { + color: #468847; +} +.control-group.success input, .control-group.success select, .control-group.success textarea { + color: #468847; + border-color: #468847; +} +.control-group.success input:focus, .control-group.success select:focus, .control-group.success textarea:focus { + border-color: #356635; + -webkit-box-shadow: 0 0 6px #7aba7b; + -moz-box-shadow: 0 0 6px #7aba7b; + box-shadow: 0 0 6px #7aba7b; +} +.control-group.success .input-prepend .add-on, .control-group.success .input-append .add-on { + color: #468847; + background-color: #dff0d8; + border-color: #468847; +} +input:focus:required:invalid, textarea:focus:required:invalid, select:focus:required:invalid { + color: #b94a48; + border-color: #ee5f5b; +} +input:focus:required:invalid:focus, textarea:focus:required:invalid:focus, select:focus:required:invalid:focus { + border-color: #e9322d; + -webkit-box-shadow: 0 0 6px #f8b9b7; + -moz-box-shadow: 0 0 6px #f8b9b7; + box-shadow: 0 0 6px #f8b9b7; +} +.form-actions { + padding: 17px 20px 18px; + margin-top: 18px; + margin-bottom: 18px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; +} +.uneditable-input { + display: block; + background-color: #ffffff; + border-color: #eee; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); + cursor: not-allowed; +} +:-moz-placeholder { + color: #999999; +} +::-webkit-input-placeholder { + color: #999999; +} +.help-block { + margin-top: 5px; + margin-bottom: 0; + color: #999999; +} +.help-inline { + display: inline-block; + *display: inline; + /* IE7 inline-block hack */ + + *zoom: 1; + margin-bottom: 9px; + vertical-align: middle; + padding-left: 5px; +} +.input-prepend, .input-append { + margin-bottom: 5px; + *zoom: 1; +} +.input-prepend:before, +.input-append:before, +.input-prepend:after, +.input-append:after { + display: table; + content: ""; +} +.input-prepend:after, .input-append:after { + clear: both; +} +.input-prepend input, +.input-append input, +.input-prepend .uneditable-input, +.input-append .uneditable-input { + -webkit-border-radius: 0 3px 3px 0; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; +} +.input-prepend input:focus, +.input-append input:focus, +.input-prepend .uneditable-input:focus, +.input-append .uneditable-input:focus { + position: relative; + z-index: 2; +} +.input-prepend .uneditable-input, .input-append .uneditable-input { + border-left-color: #ccc; +} +.input-prepend .add-on, .input-append .add-on { + float: left; + display: block; + width: auto; + min-width: 16px; + height: 18px; + margin-right: -1px; + padding: 4px 5px; + font-weight: normal; + line-height: 18px; + color: #999999; + text-align: center; + text-shadow: 0 1px 0 #ffffff; + background-color: #f5f5f5; + border: 1px solid #ccc; + -webkit-border-radius: 3px 0 0 3px; + -moz-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; +} +.input-prepend .active, .input-append .active { + background-color: #a9dba9; + border-color: #46a546; +} +.input-prepend .add-on { + *margin-top: 1px; + /* IE6-7 */ + +} +.input-append input, .input-append .uneditable-input { + float: left; + -webkit-border-radius: 3px 0 0 3px; + -moz-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; +} +.input-append .uneditable-input { + border-right-color: #ccc; +} +.input-append .add-on { + margin-right: 0; + margin-left: -1px; + -webkit-border-radius: 0 3px 3px 0; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; +} +.input-append input:first-child { + *margin-left: -160px; +} +.input-append input:first-child + .add-on { + *margin-left: -21px; +} +.search-query { + padding-left: 14px; + padding-right: 14px; + margin-bottom: 0; + -webkit-border-radius: 14px; + -moz-border-radius: 14px; + border-radius: 14px; +} +.form-search input, +.form-inline input, +.form-horizontal input, +.form-search textarea, +.form-inline textarea, +.form-horizontal textarea, +.form-search select, +.form-inline select, +.form-horizontal select, +.form-search .help-inline, +.form-inline .help-inline, +.form-horizontal .help-inline, +.form-search .uneditable-input, +.form-inline .uneditable-input, +.form-horizontal .uneditable-input { + display: inline-block; + margin-bottom: 0; +} +.form-search label, +.form-inline label, +.form-search .input-append, +.form-inline .input-append, +.form-search .input-prepend, +.form-inline .input-prepend { + display: inline-block; +} +.form-search .input-append .add-on, +.form-inline .input-prepend .add-on, +.form-search .input-append .add-on, +.form-inline .input-prepend .add-on { + vertical-align: middle; +} +.control-group { + margin-bottom: 9px; +} +.form-horizontal legend + .control-group { + margin-top: 18px; + -webkit-margin-top-collapse: separate; +} +.form-horizontal .control-group { + margin-bottom: 18px; + *zoom: 1; +} +.form-horizontal .control-group:before, .form-horizontal .control-group:after { + display: table; + content: ""; +} +.form-horizontal .control-group:after { + clear: both; +} +.form-horizontal .control-group > label { + float: left; + width: 140px; + padding-top: 5px; + text-align: right; +} +.form-horizontal .controls { + margin-left: 160px; +} +.form-horizontal .form-actions { + padding-left: 160px; +} +table { + max-width: 100%; + border-collapse: collapse; + border-spacing: 0; +} +.table { + width: 100%; + margin-bottom: 18px; +} +.table th, .table td { + padding: 8px; + line-height: 18px; + text-align: left; + border-top: 1px solid #ddd; +} +.table th { + font-weight: bold; + vertical-align: bottom; +} +.table td { + vertical-align: top; +} +.table thead:first-child tr th, .table thead:first-child tr td { + border-top: 0; +} +.table tbody + tbody { + border-top: 2px solid #ddd; +} +.table-condensed th, .table-condensed td { + padding: 4px 5px; +} +.table-bordered { + border: 1px solid #ddd; + border-collapse: separate; + *border-collapse: collapsed; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.table-bordered th + th, +.table-bordered td + td, +.table-bordered th + td, +.table-bordered td + th { + border-left: 1px solid #ddd; +} +.table-bordered thead:first-child tr:first-child th, .table-bordered tbody:first-child tr:first-child th, .table-bordered tbody:first-child tr:first-child td { + border-top: 0; +} +.table-bordered thead:first-child tr:first-child th:first-child, .table-bordered tbody:first-child tr:first-child td:first-child { + -webkit-border-radius: 4px 0 0 0; + -moz-border-radius: 4px 0 0 0; + border-radius: 4px 0 0 0; +} +.table-bordered thead:first-child tr:first-child th:last-child, .table-bordered tbody:first-child tr:first-child td:last-child { + -webkit-border-radius: 0 4px 0 0; + -moz-border-radius: 0 4px 0 0; + border-radius: 0 4px 0 0; +} +.table-bordered thead:last-child tr:last-child th:first-child, .table-bordered tbody:last-child tr:last-child td:first-child { + -webkit-border-radius: 0 0 0 4px; + -moz-border-radius: 0 0 0 4px; + border-radius: 0 0 0 4px; +} +.table-bordered thead:last-child tr:last-child th:last-child, .table-bordered tbody:last-child tr:last-child td:last-child { + -webkit-border-radius: 0 0 4px 0; + -moz-border-radius: 0 0 4px 0; + border-radius: 0 0 4px 0; +} +.table-striped tbody tr:nth-child(odd) td, .table-striped tbody tr:nth-child(odd) th { + background-color: #f9f9f9; +} +table .span1 { + float: none; + width: 44px; + margin-left: 0; +} +table .span2 { + float: none; + width: 124px; + margin-left: 0; +} +table .span3 { + float: none; + width: 204px; + margin-left: 0; +} +table .span4 { + float: none; + width: 284px; + margin-left: 0; +} +table .span5 { + float: none; + width: 364px; + margin-left: 0; +} +table .span6 { + float: none; + width: 444px; + margin-left: 0; +} +table .span7 { + float: none; + width: 524px; + margin-left: 0; +} +table .span8 { + float: none; + width: 604px; + margin-left: 0; +} +table .span9 { + float: none; + width: 684px; + margin-left: 0; +} +table .span10 { + float: none; + width: 764px; + margin-left: 0; +} +table .span11 { + float: none; + width: 844px; + margin-left: 0; +} +table .span12 { + float: none; + width: 924px; + margin-left: 0; +} +[class^="icon-"] { + display: inline-block; + width: 14px; + height: 14px; + vertical-align: text-top; + background-image: url(../img/glyphicons-halflings.png); + background-position: 14px 14px; + background-repeat: no-repeat; + *margin-right: .3em; +} +[class^="icon-"]:last-child { + *margin-left: 0; +} +.icon-white { + background-image: url(../img/glyphicons-halflings-white.png); +} +.icon-glass { + background-position: 0 0; +} +.icon-music { + background-position: -24px 0; +} +.icon-search { + background-position: -48px 0; +} +.icon-envelope { + background-position: -72px 0; +} +.icon-heart { + background-position: -96px 0; +} +.icon-star { + background-position: -120px 0; +} +.icon-star-empty { + background-position: -144px 0; +} +.icon-user { + background-position: -168px 0; +} +.icon-film { + background-position: -192px 0; +} +.icon-th-large { + background-position: -216px 0; +} +.icon-th { + background-position: -240px 0; +} +.icon-th-list { + background-position: -264px 0; +} +.icon-ok { + background-position: -288px 0; +} +.icon-remove { + background-position: -312px 0; +} +.icon-zoom-in { + background-position: -336px 0; +} +.icon-zoom-out { + background-position: -360px 0; +} +.icon-off { + background-position: -384px 0; +} +.icon-signal { + background-position: -408px 0; +} +.icon-cog { + background-position: -432px 0; +} +.icon-trash { + background-position: -456px 0; +} +.icon-home { + background-position: 0 -24px; +} +.icon-file { + background-position: -24px -24px; +} +.icon-time { + background-position: -48px -24px; +} +.icon-road { + background-position: -72px -24px; +} +.icon-download-alt { + background-position: -96px -24px; +} +.icon-download { + background-position: -120px -24px; +} +.icon-upload { + background-position: -144px -24px; +} +.icon-inbox { + background-position: -168px -24px; +} +.icon-play-circle { + background-position: -192px -24px; +} +.icon-repeat { + background-position: -216px -24px; +} +.icon-refresh { + background-position: -240px -24px; +} +.icon-list-alt { + background-position: -264px -24px; +} +.icon-lock { + background-position: -287px -24px; +} +.icon-flag { + background-position: -312px -24px; +} +.icon-headphones { + background-position: -336px -24px; +} +.icon-volume-off { + background-position: -360px -24px; +} +.icon-volume-down { + background-position: -384px -24px; +} +.icon-volume-up { + background-position: -408px -24px; +} +.icon-qrcode { + background-position: -432px -24px; +} +.icon-barcode { + background-position: -456px -24px; +} +.icon-tag { + background-position: 0 -48px; +} +.icon-tags { + background-position: -25px -48px; +} +.icon-book { + background-position: -48px -48px; +} +.icon-bookmark { + background-position: -72px -48px; +} +.icon-print { + background-position: -96px -48px; +} +.icon-camera { + background-position: -120px -48px; +} +.icon-font { + background-position: -144px -48px; +} +.icon-bold { + background-position: -167px -48px; +} +.icon-italic { + background-position: -192px -48px; +} +.icon-text-height { + background-position: -216px -48px; +} +.icon-text-width { + background-position: -240px -48px; +} +.icon-align-left { + background-position: -264px -48px; +} +.icon-align-center { + background-position: -288px -48px; +} +.icon-align-right { + background-position: -312px -48px; +} +.icon-align-justify { + background-position: -336px -48px; +} +.icon-list { + background-position: -360px -48px; +} +.icon-indent-left { + background-position: -384px -48px; +} +.icon-indent-right { + background-position: -408px -48px; +} +.icon-facetime-video { + background-position: -432px -48px; +} +.icon-picture { + background-position: -456px -48px; +} +.icon-pencil { + background-position: 0 -72px; +} +.icon-map-marker { + background-position: -24px -72px; +} +.icon-adjust { + background-position: -48px -72px; +} +.icon-tint { + background-position: -72px -72px; +} +.icon-edit { + background-position: -96px -72px; +} +.icon-share { + background-position: -120px -72px; +} +.icon-check { + background-position: -144px -72px; +} +.icon-move { + background-position: -168px -72px; +} +.icon-step-backward { + background-position: -192px -72px; +} +.icon-fast-backward { + background-position: -216px -72px; +} +.icon-backward { + background-position: -240px -72px; +} +.icon-play { + background-position: -264px -72px; +} +.icon-pause { + background-position: -288px -72px; +} +.icon-stop { + background-position: -312px -72px; +} +.icon-forward { + background-position: -336px -72px; +} +.icon-fast-forward { + background-position: -360px -72px; +} +.icon-step-forward { + background-position: -384px -72px; +} +.icon-eject { + background-position: -408px -72px; +} +.icon-chevron-left { + background-position: -432px -72px; +} +.icon-chevron-right { + background-position: -456px -72px; +} +.icon-plus-sign { + background-position: 0 -96px; +} +.icon-minus-sign { + background-position: -24px -96px; +} +.icon-remove-sign { + background-position: -48px -96px; +} +.icon-ok-sign { + background-position: -72px -96px; +} +.icon-question-sign { + background-position: -96px -96px; +} +.icon-info-sign { + background-position: -120px -96px; +} +.icon-screenshot { + background-position: -144px -96px; +} +.icon-remove-circle { + background-position: -168px -96px; +} +.icon-ok-circle { + background-position: -192px -96px; +} +.icon-ban-circle { + background-position: -216px -96px; +} +.icon-arrow-left { + background-position: -240px -96px; +} +.icon-arrow-right { + background-position: -264px -96px; +} +.icon-arrow-up { + background-position: -289px -96px; +} +.icon-arrow-down { + background-position: -312px -96px; +} +.icon-share-alt { + background-position: -336px -96px; +} +.icon-resize-full { + background-position: -360px -96px; +} +.icon-resize-small { + background-position: -384px -96px; +} +.icon-plus { + background-position: -408px -96px; +} +.icon-minus { + background-position: -433px -96px; +} +.icon-asterisk { + background-position: -456px -96px; +} +.icon-exclamation-sign { + background-position: 0 -120px; +} +.icon-gift { + background-position: -24px -120px; +} +.icon-leaf { + background-position: -48px -120px; +} +.icon-fire { + background-position: -72px -120px; +} +.icon-eye-open { + background-position: -96px -120px; +} +.icon-eye-close { + background-position: -120px -120px; +} +.icon-warning-sign { + background-position: -144px -120px; +} +.icon-plane { + background-position: -168px -120px; +} +.icon-calendar { + background-position: -192px -120px; +} +.icon-random { + background-position: -216px -120px; +} +.icon-comment { + background-position: -240px -120px; +} +.icon-magnet { + background-position: -264px -120px; +} +.icon-chevron-up { + background-position: -288px -120px; +} +.icon-chevron-down { + background-position: -313px -119px; +} +.icon-retweet { + background-position: -336px -120px; +} +.icon-shopping-cart { + background-position: -360px -120px; +} +.icon-folder-close { + background-position: -384px -120px; +} +.icon-folder-open { + background-position: -408px -120px; +} +.icon-resize-vertical { + background-position: -432px -119px; +} +.icon-resize-horizontal { + background-position: -456px -118px; +} +.dropdown { + position: relative; +} +.dropdown-toggle { + *margin-bottom: -3px; +} +.dropdown-toggle:active, .open .dropdown-toggle { + outline: 0; +} +.caret { + display: inline-block; + width: 0; + height: 0; + text-indent: -99999px; + *text-indent: 0; + vertical-align: top; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid #000000; + opacity: 0.3; + filter: alpha(opacity=30); + content: "\2193"; +} +.dropdown .caret { + margin-top: 8px; + margin-left: 2px; +} +.dropdown:hover .caret, .open.dropdown .caret { + opacity: 1; + filter: alpha(opacity=100); +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + float: left; + display: none; + min-width: 160px; + max-width: 220px; + _width: 160px; + padding: 4px 0; + margin: 0; + list-style: none; + background-color: #ffffff; + border-color: #ccc; + border-color: rgba(0, 0, 0, 0.2); + border-style: solid; + border-width: 1px; + -webkit-border-radius: 0 0 5px 5px; + -moz-border-radius: 0 0 5px 5px; + border-radius: 0 0 5px 5px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + *border-right-width: 2px; + *border-bottom-width: 2px; +} +.dropdown-menu.bottom-up { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +.dropdown-menu .divider { + height: 1px; + margin: 5px 1px; + overflow: hidden; + background-color: #e5e5e5; + border-bottom: 1px solid #ffffff; + *width: 100%; + *margin: -5px 0 5px; +} +.dropdown-menu a { + display: block; + padding: 3px 15px; + clear: both; + font-weight: normal; + line-height: 18px; + color: #555555; + white-space: nowrap; +} +.dropdown-menu li > a:hover, .dropdown-menu .active > a, .dropdown-menu .active > a:hover { + color: #ffffff; + text-decoration: none; + background-color: #0088cc; +} +.dropdown.open { + *z-index: 1000; +} +.dropdown.open .dropdown-toggle { + color: #ffffff; + background: #ccc; + background: rgba(0, 0, 0, 0.3); +} +.dropdown.open .dropdown-menu { + display: block; +} +.typeahead { + margin-top: 2px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #eee; + border: 1px solid rgba(0, 0, 0, 0.05); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} +.fade { + -webkit-transition: opacity 0.15s linear; + -moz-transition: opacity 0.15s linear; + -ms-transition: opacity 0.15s linear; + -o-transition: opacity 0.15s linear; transition: opacity 0.15s linear; opacity: 0; } -.fade.in { - opacity: 1; +.fade.in { + opacity: 1; +} +.collapse { + -webkit-transition: height 0.35s ease; + -moz-transition: height 0.35s ease; + -ms-transition: height 0.35s ease; + -o-transition: height 0.35s ease; + transition: height 0.35s ease; + position: relative; + overflow: hidden; + height: 0; +} +.collapse.in { + height: auto; +} +.close { + float: right; + font-size: 20px; + font-weight: bold; + line-height: 18px; + color: #000000; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; + filter: alpha(opacity=20); +} +.close:hover { + color: #000000; + text-decoration: none; + opacity: 0.4; + filter: alpha(opacity=40); + cursor: pointer; +} +.btn { + display: inline-block; + padding: 4px 10px 4px; + font-size: 13px; + line-height: 18px; + color: #333333; + text-align: center; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + background-color: #fafafa; + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); + background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6); + background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); + background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); + background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); + background-repeat: no-repeat; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); + border: 1px solid #ccc; + border-bottom-color: #bbb; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + cursor: pointer; + *margin-left: .3em; +} +.btn:first-child { + *margin-left: 0; +} +.btn:hover { + color: #333333; + text-decoration: none; + background-color: #e6e6e6; + background-position: 0 -15px; + -webkit-transition: background-position 0.1s linear; + -moz-transition: background-position 0.1s linear; + -ms-transition: background-position 0.1s linear; + -o-transition: background-position 0.1s linear; + transition: background-position 0.1s linear; +} +.btn:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn.active, .btn:active { + background-image: none; + -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + background-color: #e6e6e6; + background-color: #d9d9d9 \9; + color: rgba(0, 0, 0, 0.5); + outline: 0; +} +.btn.disabled, .btn[disabled] { + cursor: default; + background-image: none; + background-color: #e6e6e6; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} +.btn-large { + padding: 9px 14px; + font-size: 15px; + line-height: normal; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} +.btn-large .icon { + margin-top: 1px; +} +.btn-small { + padding: 5px 9px; + font-size: 11px; + line-height: 16px; +} +.btn-small .icon { + margin-top: -1px; +} +.btn-primary, +.btn-primary:hover, +.btn-warning, +.btn-warning:hover, +.btn-danger, +.btn-danger:hover, +.btn-success, +.btn-success:hover, +.btn-info, +.btn-info:hover { + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + color: #ffffff; +} +.btn-primary.active, +.btn-warning.active, +.btn-danger.active, +.btn-success.active, +.btn-info.active { + color: rgba(255, 255, 255, 0.75); +} +.btn-primary { + background-color: #006dcc; + background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -ms-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); + background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: linear-gradient(top, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.btn-primary:hover, +.btn-primary:active, +.btn-primary.active, +.btn-primary.disabled, +.btn-primary[disabled] { + background-color: #0044cc; +} +.btn-primary:active, .btn-primary.active { + background-color: #003399 \9; +} +.btn-warning { + background-color: #faa732; + background-image: -moz-linear-gradient(top, #fbb450, #f89406); + background-image: -ms-linear-gradient(top, #fbb450, #f89406); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); + background-image: -webkit-linear-gradient(top, #fbb450, #f89406); + background-image: -o-linear-gradient(top, #fbb450, #f89406); + background-image: linear-gradient(top, #fbb450, #f89406); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0); + border-color: #f89406 #f89406 #ad6704; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.btn-warning:hover, +.btn-warning:active, +.btn-warning.active, +.btn-warning.disabled, +.btn-warning[disabled] { + background-color: #f89406; +} +.btn-warning:active, .btn-warning.active { + background-color: #c67605 \9; +} +.btn-danger { + background-color: #da4f49; + background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f); + background-image: -ms-linear-gradient(top, #ee5f5b, #bd362f); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f)); + background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f); + background-image: -o-linear-gradient(top, #ee5f5b, #bd362f); + background-image: linear-gradient(top, #ee5f5b, #bd362f); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0); + border-color: #bd362f #bd362f #802420; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.btn-danger:hover, +.btn-danger:active, +.btn-danger.active, +.btn-danger.disabled, +.btn-danger[disabled] { + background-color: #bd362f; +} +.btn-danger:active, .btn-danger.active { + background-color: #942a25 \9; +} +.btn-success { + background-color: #5bb75b; + background-image: -moz-linear-gradient(top, #62c462, #51a351); + background-image: -ms-linear-gradient(top, #62c462, #51a351); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351)); + background-image: -webkit-linear-gradient(top, #62c462, #51a351); + background-image: -o-linear-gradient(top, #62c462, #51a351); + background-image: linear-gradient(top, #62c462, #51a351); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0); + border-color: #51a351 #51a351 #387038; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.btn-success:hover, +.btn-success:active, +.btn-success.active, +.btn-success.disabled, +.btn-success[disabled] { + background-color: #51a351; +} +.btn-success:active, .btn-success.active { + background-color: #408140 \9; +} +.btn-info { + background-color: #49afcd; + background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4); + background-image: -ms-linear-gradient(top, #5bc0de, #2f96b4); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4)); + background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4); + background-image: -o-linear-gradient(top, #5bc0de, #2f96b4); + background-image: linear-gradient(top, #5bc0de, #2f96b4); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0); + border-color: #2f96b4 #2f96b4 #1f6377; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.btn-info:hover, +.btn-info:active, +.btn-info.active, +.btn-info.disabled, +.btn-info[disabled] { + background-color: #2f96b4; +} +.btn-info:active, .btn-info.active { + background-color: #24748c \9; +} +button.btn, input[type="submit"].btn { + *padding-top: 2px; + *padding-bottom: 2px; +} +button.btn::-moz-focus-inner, input[type="submit"].btn::-moz-focus-inner { + padding: 0; + border: 0; +} +button.btn.large, input[type="submit"].btn.large { + *padding-top: 7px; + *padding-bottom: 7px; +} +button.btn.small, input[type="submit"].btn.small { + *padding-top: 3px; + *padding-bottom: 3px; +} +.btn-group { + position: relative; + *zoom: 1; + *margin-left: .3em; +} +.btn-group:before, .btn-group:after { + display: table; + content: ""; +} +.btn-group:after { + clear: both; +} +.btn-group:first-child { + *margin-left: 0; +} +.btn-group + .btn-group { + margin-left: 5px; +} +.btn-toolbar { + margin-top: 9px; + margin-bottom: 9px; +} +.btn-toolbar .btn-group { + display: inline-block; + *display: inline; + /* IE7 inline-block hack */ + + *zoom: 1; +} +.btn-group .btn { + position: relative; + float: left; + margin-left: -1px; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.btn-group .btn:first-child { + margin-left: 0; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + border-top-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + border-bottom-left-radius: 4px; +} +.btn-group .btn:last-child, .btn-group .dropdown-toggle { + -webkit-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -moz-border-radius-bottomright: 4px; + border-bottom-right-radius: 4px; +} +.btn-group .btn.large:first-child { + margin-left: 0; + -webkit-border-top-left-radius: 6px; + -moz-border-radius-topleft: 6px; + border-top-left-radius: 6px; + -webkit-border-bottom-left-radius: 6px; + -moz-border-radius-bottomleft: 6px; + border-bottom-left-radius: 6px; +} +.btn-group .btn.large:last-child, .btn-group .large.dropdown-toggle { + -webkit-border-top-right-radius: 6px; + -moz-border-radius-topright: 6px; + border-top-right-radius: 6px; + -webkit-border-bottom-right-radius: 6px; + -moz-border-radius-bottomright: 6px; + border-bottom-right-radius: 6px; +} +.btn-group .btn:hover, +.btn-group .btn:focus, +.btn-group .btn:active, +.btn-group .btn.active { + z-index: 2; +} +.btn-group .dropdown-toggle:active, .btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; + -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + *padding-top: 5px; + *padding-bottom: 5px; +} +.btn-group.open { + *z-index: 1000; +} +.btn-group.open .dropdown-menu { + display: block; + margin-top: 1px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} +.btn-group.open .dropdown-toggle { + background-image: none; + -webkit-box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); } -.collapse { - -webkit-transition: height 0.35s ease; - -moz-transition: height 0.35s ease; - -ms-transition: height 0.35s ease; - -o-transition: height 0.35s ease; - transition: height 0.35s ease; - position: relative; - overflow: hidden; - height: 0; +.btn .caret { + margin-top: 7px; + margin-left: 0; } -.collapse.in { - height: auto; +.btn:hover .caret, .open.btn-group .caret { + opacity: 1; + filter: alpha(opacity=100); } -.close { - float: right; - font-size: 20px; - font-weight: bold; - line-height: 18px; - color: #000000; - text-shadow: 0 1px 0 #ffffff; - opacity: 0.2; - filter: alpha(opacity=20); +.btn-primary .caret, +.btn-danger .caret, +.btn-info .caret, +.btn-success .caret { + border-top-color: #ffffff; + opacity: 0.75; + filter: alpha(opacity=75); } -.close:hover { - color: #000000; - text-decoration: none; - opacity: 0.4; - filter: alpha(opacity=40); - cursor: pointer; +.btn-small .caret { + margin-top: 4px; } -.btn { - display: inline-block; - padding: 4px 10px 4px; - font-size: 13px; - line-height: 18px; - color: #333333; - text-align: center; - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - background-color: #fafafa; - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6); - background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); - background-repeat: no-repeat; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); - border: 1px solid #ccc; - border-bottom-color: #bbb; +.alert { + padding: 8px 35px 8px 14px; + margin-bottom: 18px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + background-color: #fcf8e3; + border: 1px solid #fbeed5; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - cursor: pointer; - *margin-left: .3em; } -.btn:first-child { - *margin-left: 0; +.alert, .alert-heading { + color: #c09853; } -.btn:hover { - color: #333333; +.alert .close { + position: relative; + top: -2px; + right: -21px; + line-height: 18px; +} +.alert-success { + background-color: #dff0d8; + border-color: #d6e9c6; +} +.alert-success, .alert-success .alert-heading { + color: #468847; +} +.alert-danger, .alert-error { + background-color: #f2dede; + border-color: #eed3d7; +} +.alert-danger, +.alert-error, +.alert-danger .alert-heading, +.alert-error .alert-heading { + color: #b94a48; +} +.alert-info { + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-info, .alert-info .alert-heading { + color: #3a87ad; +} +.alert-block { + padding-top: 14px; + padding-bottom: 14px; +} +.alert-block > p, .alert-block > ul { + margin-bottom: 0; +} +.alert-block p + p { + margin-top: 5px; +} +.nav { + margin-left: 0; + margin-bottom: 18px; + list-style: none; +} +.nav > li > a { + display: block; +} +.nav > li > a:hover { text-decoration: none; - background-color: #e6e6e6; - background-position: 0 -15px; - -webkit-transition: background-position 0.1s linear; - -moz-transition: background-position 0.1s linear; - -ms-transition: background-position 0.1s linear; - -o-transition: background-position 0.1s linear; - transition: background-position 0.1s linear; + background-color: #eeeeee; } -.btn:focus { - outline: thin dotted; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; +.nav-list { + padding-left: 14px; + padding-right: 14px; + margin-bottom: 0; } -.btn.active, .btn:active { - background-image: none; - -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - background-color: #e6e6e6; - background-color: #d9d9d9 \9; - color: rgba(0, 0, 0, 0.5); - outline: 0; +.nav-list > li > a, .nav-list .nav-header { + display: block; + padding: 3px 15px; + margin-left: -15px; + margin-right: -15px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); } -.btn.disabled, .btn[disabled] { +.nav-list .nav-header { + font-size: 11px; + font-weight: bold; + line-height: 18px; + color: #999999; + text-transform: uppercase; +} +.nav-list > li + .nav-header { + margin-top: 9px; +} +.nav-list .active > a, .nav-list .active > a:hover { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); + background-color: #0088cc; +} +.nav-list [class^="icon-"] { + margin-right: 2px; +} +.nav-tabs, .nav-pills { + *zoom: 1; +} +.nav-tabs:before, +.nav-pills:before, +.nav-tabs:after, +.nav-pills:after { + display: table; + content: ""; +} +.nav-tabs:after, .nav-pills:after { + clear: both; +} +.nav-tabs > li, .nav-pills > li { + float: left; +} +.nav-tabs > li > a, .nav-pills > li > a { + padding-right: 12px; + padding-left: 12px; + margin-right: 2px; + line-height: 14px; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + margin-bottom: -1px; +} +.nav-tabs > li > a { + padding-top: 9px; + padding-bottom: 9px; + border: 1px solid transparent; + -webkit-border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eeeeee #eeeeee #dddddd; +} +.nav-tabs > .active > a, .nav-tabs > .active > a:hover { + color: #555555; + background-color: #ffffff; + border: 1px solid #ddd; + border-bottom-color: transparent; cursor: default; - background-image: none; - background-color: #e6e6e6; - opacity: 0.65; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; } -.btn-large { - padding: 9px 14px; - font-size: 15px; - line-height: normal; +.nav-pills > li > a { + padding-top: 8px; + padding-bottom: 8px; + margin-top: 2px; + margin-bottom: 2px; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } -.btn-large .icon { +.nav-pills .active > a, .nav-pills .active > a:hover { + color: #ffffff; + background-color: #0088cc; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li > a { + margin-right: 0; +} +.nav-tabs.nav-stacked { + border-bottom: 0; +} +.nav-tabs.nav-stacked > li > a { + border: 1px solid #ddd; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.nav-tabs.nav-stacked > li:first-child > a { + -webkit-border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} +.nav-tabs.nav-stacked > li:last-child > a { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} +.nav-tabs.nav-stacked > li > a:hover { + border-color: #ddd; + z-index: 2; +} +.nav-pills.nav-stacked > li > a { + margin-bottom: 3px; +} +.nav-pills.nav-stacked > li:last-child > a { + margin-bottom: 1px; +} +.nav-tabs .dropdown-menu, .nav-pills .dropdown-menu { margin-top: 1px; + border-width: 1px; } -.btn-small { - padding: 5px 9px; - font-size: 11px; - line-height: 16px; +.nav-pills .dropdown-menu { + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; } -.btn-small .icon { - margin-top: -1px; +.nav-tabs .dropdown-toggle .caret, .nav-pills .dropdown-toggle .caret { + border-top-color: #0088cc; + margin-top: 6px; } -.btn-primary, -.btn-primary:hover, -.btn-warning, -.btn-warning:hover, -.btn-danger, -.btn-danger:hover, -.btn-success, -.btn-success:hover, -.btn-info, -.btn-info:hover { - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +.nav-tabs .dropdown-toggle:hover .caret, .nav-pills .dropdown-toggle:hover .caret { + border-top-color: #005580; +} +.nav-tabs .active .dropdown-toggle .caret, .nav-pills .active .dropdown-toggle .caret { + border-top-color: #333333; +} +.nav > .dropdown.active > a:hover { + color: #000000; + cursor: pointer; +} +.nav-tabs .open .dropdown-toggle, .nav-pills .open .dropdown-toggle, .nav > .open.active > a:hover { color: #ffffff; + background-color: #999999; + border-color: #999999; +} +.nav .open .caret, .nav .open.active .caret, .nav .open a:hover .caret { + border-top-color: #ffffff; + opacity: 1; + filter: alpha(opacity=100); +} +.tabs-stacked .open > a:hover { + border-color: #999999; +} +.tabbable { + *zoom: 1; +} +.tabbable:before, .tabbable:after { + display: table; + content: ""; +} +.tabbable:after { + clear: both; +} +.tabs-below .nav-tabs, .tabs-right .nav-tabs, .tabs-left .nav-tabs { + border-bottom: 0; +} +.tab-content > .tab-pane, .pill-content > .pill-pane { + display: none; +} +.tab-content > .active, .pill-content > .active { + display: block; +} +.tabs-below .nav-tabs { + border-top: 1px solid #ddd; +} +.tabs-below .nav-tabs > li { + margin-top: -1px; + margin-bottom: 0; +} +.tabs-below .nav-tabs > li > a { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} +.tabs-below .nav-tabs > li > a:hover { + border-bottom-color: transparent; + border-top-color: #ddd; +} +.tabs-below .nav-tabs .active > a, .tabs-below .nav-tabs .active > a:hover { + border-color: transparent #ddd #ddd #ddd; } -.btn-primary.active, -.btn-warning.active, -.btn-danger.active, -.btn-success.active, -.btn-info.active { - color: rgba(255, 255, 255, 0.75); +.tabs-left .nav-tabs > li, .tabs-right .nav-tabs > li { + float: none; } -.btn-primary { - background-color: #006dcc; - background-image: -moz-linear-gradient(top, #0088cc, #0044cc); - background-image: -ms-linear-gradient(top, #0088cc, #0044cc); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); - background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); - background-image: -o-linear-gradient(top, #0088cc, #0044cc); - background-image: linear-gradient(top, #0088cc, #0044cc); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); - border-color: #0044cc #0044cc #002a80; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +.tabs-left .nav-tabs > li > a, .tabs-right .nav-tabs > li > a { + min-width: 74px; + margin-right: 0; + margin-bottom: 3px; } -.btn-primary:hover, -.btn-primary:active, -.btn-primary.active, -.btn-primary.disabled, -.btn-primary[disabled] { - background-color: #0044cc; +.tabs-left .nav-tabs { + float: left; + margin-right: 19px; + border-right: 1px solid #ddd; } -.btn-primary:active, .btn-primary.active { - background-color: #003399 \9; +.tabs-left .nav-tabs > li > a { + margin-right: -1px; + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; } -.btn-warning { - background-color: #faa732; - background-image: -moz-linear-gradient(top, #fbb450, #f89406); - background-image: -ms-linear-gradient(top, #fbb450, #f89406); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); - background-image: -webkit-linear-gradient(top, #fbb450, #f89406); - background-image: -o-linear-gradient(top, #fbb450, #f89406); - background-image: linear-gradient(top, #fbb450, #f89406); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0); - border-color: #f89406 #f89406 #ad6704; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +.tabs-left .nav-tabs > li > a:hover { + border-color: #eeeeee #dddddd #eeeeee #eeeeee; } -.btn-warning:hover, -.btn-warning:active, -.btn-warning.active, -.btn-warning.disabled, -.btn-warning[disabled] { - background-color: #f89406; +.tabs-left .nav-tabs .active > a, .tabs-left .nav-tabs .active > a:hover { + border-color: #ddd transparent #ddd #ddd; + *border-right-color: #ffffff; } -.btn-warning:active, .btn-warning.active { - background-color: #c67605 \9; +.tabs-right .nav-tabs { + float: right; + margin-left: 19px; + border-left: 1px solid #ddd; } -.btn-danger { - background-color: #da4f49; - background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f); - background-image: -ms-linear-gradient(top, #ee5f5b, #bd362f); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f)); - background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f); - background-image: -o-linear-gradient(top, #ee5f5b, #bd362f); - background-image: linear-gradient(top, #ee5f5b, #bd362f); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0); - border-color: #bd362f #bd362f #802420; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +.tabs-right .nav-tabs > li > a { + margin-left: -1px; + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; } -.btn-danger:hover, -.btn-danger:active, -.btn-danger.active, -.btn-danger.disabled, -.btn-danger[disabled] { - background-color: #bd362f; +.tabs-right .nav-tabs > li > a:hover { + border-color: #eeeeee #eeeeee #eeeeee #dddddd; } -.btn-danger:active, .btn-danger.active { - background-color: #942a25 \9; +.tabs-right .nav-tabs .active > a, .tabs-right .nav-tabs .active > a:hover { + border-color: #ddd #ddd #ddd transparent; + *border-left-color: #ffffff; } -.btn-success { - background-color: #5bb75b; - background-image: -moz-linear-gradient(top, #62c462, #51a351); - background-image: -ms-linear-gradient(top, #62c462, #51a351); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351)); - background-image: -webkit-linear-gradient(top, #62c462, #51a351); - background-image: -o-linear-gradient(top, #62c462, #51a351); - background-image: linear-gradient(top, #62c462, #51a351); +.navbar { + overflow: visible; + margin-bottom: 18px; +} +.navbar-inner { + padding-left: 20px; + padding-right: 20px; + background-color: #2c2c2c; + background-image: -moz-linear-gradient(top, #333333, #222222); + background-image: -ms-linear-gradient(top, #333333, #222222); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222)); + background-image: -webkit-linear-gradient(top, #333333, #222222); + background-image: -o-linear-gradient(top, #333333, #222222); + background-image: linear-gradient(top, #333333, #222222); background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0); - border-color: #51a351 #51a351 #387038; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); +} +.btn-navbar { + display: none; + float: right; + padding: 7px 10px; + margin-left: 5px; + margin-right: 5px; + background-color: #2c2c2c; + background-image: -moz-linear-gradient(top, #333333, #222222); + background-image: -ms-linear-gradient(top, #333333, #222222); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222)); + background-image: -webkit-linear-gradient(top, #333333, #222222); + background-image: -o-linear-gradient(top, #333333, #222222); + background-image: linear-gradient(top, #333333, #222222); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0); + border-color: #222222 #222222 #000000; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); } -.btn-success:hover, -.btn-success:active, -.btn-success.active, -.btn-success.disabled, -.btn-success[disabled] { - background-color: #51a351; +.btn-navbar:hover, +.btn-navbar:active, +.btn-navbar.active, +.btn-navbar.disabled, +.btn-navbar[disabled] { + background-color: #222222; } -.btn-success:active, .btn-success.active { - background-color: #408140 \9; +.btn-navbar:active, .btn-navbar.active { + background-color: #080808 \9; } -.btn-info { - background-color: #49afcd; - background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4); - background-image: -ms-linear-gradient(top, #5bc0de, #2f96b4); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4)); - background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4); - background-image: -o-linear-gradient(top, #5bc0de, #2f96b4); - background-image: linear-gradient(top, #5bc0de, #2f96b4); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0); - border-color: #2f96b4 #2f96b4 #1f6377; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +.btn-navbar .icon-bar { + display: block; + width: 18px; + height: 2px; + background-color: #f5f5f5; + -webkit-border-radius: 1px; + -moz-border-radius: 1px; + border-radius: 1px; + -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); + -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); } -.btn-info:hover, -.btn-info:active, -.btn-info.active, -.btn-info.disabled, -.btn-info[disabled] { - background-color: #2f96b4; +.btn-navbar .icon-bar + .icon-bar { + margin-top: 3px; } -.btn-info:active, .btn-info.active { - background-color: #24748c \9; +.nav-collapse.collapse { + height: auto; } -button.btn, input[type="submit"].btn { - *padding-top: 2px; - *padding-bottom: 2px; +.navbar .brand:hover { + text-decoration: none; } -button.btn::-moz-focus-inner, input[type="submit"].btn::-moz-focus-inner { - padding: 0; - border: 0; +.navbar .brand { + float: left; + display: block; + padding: 8px 20px 12px; + margin-left: -20px; + font-size: 20px; + font-weight: 200; + line-height: 1; + color: #ffffff; } -button.btn.large, input[type="submit"].btn.large { - *padding-top: 7px; - *padding-bottom: 7px; +.navbar .navbar-text { + margin-bottom: 0; + line-height: 40px; + color: #999999; } -button.btn.small, input[type="submit"].btn.small { - *padding-top: 3px; - *padding-bottom: 3px; +.navbar .navbar-text a:hover { + color: #ffffff; + background-color: transparent; } -.btn-group { - position: relative; +.navbar .btn, .navbar .btn-group { + margin-top: 5px; +} +.navbar .btn-group .btn { + margin-top: 0; +} +.navbar-form { + margin-bottom: 0; *zoom: 1; - *margin-left: .3em; } -.btn-group:before, .btn-group:after { +.navbar-form:before, .navbar-form:after { display: table; content: ""; } -.btn-group:after { +.navbar-form:after { clear: both; } -.btn-group:first-child { - *margin-left: 0; -} -.btn-group + .btn-group { - margin-left: 5px; +.navbar-form input, .navbar-form select { + display: inline-block; + margin-top: 5px; + margin-bottom: 0; } -.btn-toolbar { - margin-top: 9px; - margin-bottom: 9px; +.navbar-form .radio, .navbar-form .checkbox { + margin-top: 5px; } -.btn-toolbar .btn-group { - display: inline-block; - *display: inline; - /* IE7 inline-block hack */ - - *zoom: 1; +.navbar-form input[type="image"], .navbar-form input[type="checkbox"], .navbar-form input[type="radio"] { + margin-top: 3px; } -.btn-group .btn { +.navbar-search { position: relative; float: left; - margin-left: -1px; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} -.btn-group .btn:first-child { - margin-left: 0; - -webkit-border-top-left-radius: 4px; - -moz-border-radius-topleft: 4px; - border-top-left-radius: 4px; - -webkit-border-bottom-left-radius: 4px; - -moz-border-radius-bottomleft: 4px; - border-bottom-left-radius: 4px; -} -.btn-group .btn:last-child, .btn-group .dropdown-toggle { - -webkit-border-top-right-radius: 4px; - -moz-border-radius-topright: 4px; - border-top-right-radius: 4px; - -webkit-border-bottom-right-radius: 4px; - -moz-border-radius-bottomright: 4px; - border-bottom-right-radius: 4px; + margin-top: 6px; + margin-bottom: 0; } -.btn-group .btn.large:first-child { - margin-left: 0; - -webkit-border-top-left-radius: 6px; - -moz-border-radius-topleft: 6px; - border-top-left-radius: 6px; - -webkit-border-bottom-left-radius: 6px; - -moz-border-radius-bottomleft: 6px; - border-bottom-left-radius: 6px; +.navbar-search .search-query { + padding: 4px 9px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 13px; + font-weight: normal; + line-height: 1; + color: #ffffff; + color: rgba(255, 255, 255, 0.75); + background: #666; + background: rgba(255, 255, 255, 0.3); + border: 1px solid #111; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0px rgba(255, 255, 255, 0.15); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0px rgba(255, 255, 255, 0.15); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0px rgba(255, 255, 255, 0.15); + -webkit-transition: none; + -moz-transition: none; + -ms-transition: none; + -o-transition: none; + transition: none; +} +.navbar-search .search-query :-moz-placeholder { + color: #eeeeee; +} +.navbar-search .search-query::-webkit-input-placeholder { + color: #eeeeee; +} +.navbar-search .search-query:hover { + color: #ffffff; + background-color: #999999; + background-color: rgba(255, 255, 255, 0.5); } -.btn-group .btn.large:last-child, .btn-group .large.dropdown-toggle { - -webkit-border-top-right-radius: 6px; - -moz-border-radius-topright: 6px; - border-top-right-radius: 6px; - -webkit-border-bottom-right-radius: 6px; - -moz-border-radius-bottomright: 6px; - border-bottom-right-radius: 6px; +.navbar-search .search-query:focus, .navbar-search .search-query.focused { + padding: 5px 10px; + color: #333333; + text-shadow: 0 1px 0 #ffffff; + background-color: #ffffff; + border: 0; + -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); + -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); + box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); + outline: 0; } -.btn-group .btn:hover, -.btn-group .btn:focus, -.btn-group .btn:active, -.btn-group .btn.active { - z-index: 2; +.navbar-fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; } -.btn-group .dropdown-toggle:active, .btn-group.open .dropdown-toggle { - outline: 0; +.navbar-fixed-top .navbar-inner { + padding-left: 0; + padding-right: 0; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; } -.btn-group .dropdown-toggle { - padding-left: 8px; - padding-right: 8px; - -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - *padding-top: 5px; - *padding-bottom: 5px; +.navbar .nav { + position: relative; + left: 0; + display: block; + float: left; + margin: 0 10px 0 0; } -.btn-group.open { - *z-index: 1000; +.navbar .nav.pull-right { + float: right; } -.btn-group.open .dropdown-menu { +.navbar .nav > li { display: block; - margin-top: 1px; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; + float: left; } -.btn-group.open .dropdown-toggle { - background-image: none; - -webkit-box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); +.navbar .nav > li > a { + float: none; + padding: 10px 10px 11px; + line-height: 19px; + color: #999999; + text-decoration: none; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } -.btn .caret { - margin-top: 7px; - margin-left: 0; +.navbar .nav > li > a:hover { + background-color: transparent; + color: #ffffff; + text-decoration: none; } -.btn:hover .caret, .open.btn-group .caret { - opacity: 1; - filter: alpha(opacity=100); +.navbar .nav .active > a, .navbar .nav .active > a:hover { + color: #ffffff; + text-decoration: none; + background-color: #222222; + background-color: rgba(0, 0, 0, 0.5); } -.btn-primary .caret, -.btn-danger .caret, -.btn-info .caret, -.btn-success .caret { - border-top-color: #ffffff; - opacity: 0.75; - filter: alpha(opacity=75); +.navbar .divider-vertical { + height: 40px; + width: 1px; + margin: 0 9px; + overflow: hidden; + background-color: #222222; + border-right: 1px solid #333333; } -.btn-small .caret { - margin-top: 4px; +.navbar .nav.pull-right { + margin-left: 10px; + margin-right: 0; } - -/* ================================== */ -/* = Twitter.Bootstrap Alerts = */ -/* ================================== */ - -.alert { - padding: 8px 35px 8px 14px; - margin-bottom: 18px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - background-color: #fcf8e3; - border: 1px solid #fbeed5; +.navbar .dropdown-menu { + margin-top: 1px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } -.alert, .alert-heading { - color: #c09853; -} -.alert .close { - position: relative; - top: -2px; - right: -21px; - line-height: 18px; -} -.alert-success { - background-color: #dff0d8; - border-color: #d6e9c6; -} -.alert-success, .alert-success .alert-heading { - color: #468847; +.navbar .dropdown-menu:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; + top: -7px; + left: 9px; +} +.navbar .dropdown-menu:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + position: absolute; + top: -6px; + left: 10px; +} +.navbar .nav .dropdown-toggle .caret, .navbar .nav .open.dropdown .caret { + border-top-color: #ffffff; } -.alert-danger, .alert-error { - background-color: #f2dede; - border-color: #eed3d7; +.navbar .nav .active .caret { + opacity: 1; + filter: alpha(opacity=100); } -.alert-danger, -.alert-error, -.alert-danger .alert-heading, -.alert-error .alert-heading { - color: #b94a48; +.navbar .nav .open > .dropdown-toggle, .navbar .nav .active > .dropdown-toggle, .navbar .nav .open.active > .dropdown-toggle { + background-color: transparent; } -.alert-info { - background-color: #d9edf7; - border-color: #bce8f1; +.navbar .nav .active > .dropdown-toggle:hover { + color: #ffffff; } -.alert-info, .alert-info .alert-heading { - color: #3a87ad; +.navbar .nav.pull-right .dropdown-menu { + left: auto; + right: 0; +} +.navbar .nav.pull-right .dropdown-menu:before { + left: auto; + right: 12px; +} +.navbar .nav.pull-right .dropdown-menu:after { + left: auto; + right: 13px; +} +.breadcrumb { + padding: 7px 14px; + margin: 0 0 18px; + background-color: #fbfbfb; + background-image: -moz-linear-gradient(top, #ffffff, #f5f5f5); + background-image: -ms-linear-gradient(top, #ffffff, #f5f5f5); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5)); + background-image: -webkit-linear-gradient(top, #ffffff, #f5f5f5); + background-image: -o-linear-gradient(top, #ffffff, #f5f5f5); + background-image: linear-gradient(top, #ffffff, #f5f5f5); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0); + border: 1px solid #ddd; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: inset 0 1px 0 #ffffff; + -moz-box-shadow: inset 0 1px 0 #ffffff; + box-shadow: inset 0 1px 0 #ffffff; } -.alert-block { - padding-top: 14px; - padding-bottom: 14px; +.breadcrumb li { + display: inline; + text-shadow: 0 1px 0 #ffffff; } -.alert-block > p, .alert-block > ul { - margin-bottom: 0; +.breadcrumb .divider { + padding: 0 5px; + color: #999999; } -.alert-block p + p { - margin-top: 5px; +.breadcrumb .active a { + color: #333333; } - -/* ================================== */ -/* = Twitter.Bootstrap Pagination = */ -/* ================================== */ - .pagination { height: 36px; margin: 18px 0; @@ -658,5 +2826,675 @@ button.btn.small, input[type="submit"].btn.small { .pagination-right { text-align: right; } +.pager { + margin-left: 0; + margin-bottom: 18px; + list-style: none; + text-align: center; + *zoom: 1; +} +.pager:before, .pager:after { + display: table; + content: ""; +} +.pager:after { + clear: both; +} +.pager li { + display: inline; +} +.pager a { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; +} +.pager a:hover { + text-decoration: none; + background-color: #f5f5f5; +} +.pager .next a { + float: right; +} +.pager .previous a { + float: left; +} +.modal-open .dropdown-menu { + z-index: 2050; +} +.modal-open .dropdown.open { + *z-index: 2050; +} +.modal-open .popover { + z-index: 2060; +} +.modal-open .tooltip { + z-index: 2070; +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000000; +} +.modal-backdrop.fade { + opacity: 0; +} +.modal-backdrop, .modal-backdrop.fade.in { + opacity: 0.8; + filter: alpha(opacity=80); +} +.modal { + position: fixed; + top: 50%; + left: 50%; + z-index: 1050; + max-height: 500px; + overflow: auto; + width: 560px; + margin: -250px 0 0 -280px; + background-color: #ffffff; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, 0.3); + *border: 1px solid #999; + /* IE6-7 */ + + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + -webkit-background-clip: padding-box; + -moz-background-clip: padding-box; + background-clip: padding-box; +} +.modal.fade { + -webkit-transition: opacity .3s linear, top .3s ease-out; + -moz-transition: opacity .3s linear, top .3s ease-out; + -ms-transition: opacity .3s linear, top .3s ease-out; + -o-transition: opacity .3s linear, top .3s ease-out; + transition: opacity .3s linear, top .3s ease-out; + top: -25%; +} +.modal.fade.in { + top: 50%; +} +.modal-header { + padding: 9px 15px; + border-bottom: 1px solid #eee; +} +.modal-header .close { + margin-top: 2px; +} +.modal-body { + padding: 15px; +} +.modal-footer { + padding: 14px 15px 15px; + margin-bottom: 0; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; + -webkit-box-shadow: inset 0 1px 0 #ffffff; + -moz-box-shadow: inset 0 1px 0 #ffffff; + box-shadow: inset 0 1px 0 #ffffff; + *zoom: 1; +} +.modal-footer:before, .modal-footer:after { + display: table; + content: ""; +} +.modal-footer:after { + clear: both; +} +.modal-footer .btn { + float: right; + margin-left: 5px; + margin-bottom: 0; +} +.tooltip { + position: absolute; + z-index: 1020; + display: block; + visibility: visible; + padding: 5px; + font-size: 11px; + opacity: 0; + filter: alpha(opacity=0); +} +.tooltip.in { + opacity: 0.8; + filter: alpha(opacity=80); +} +.tooltip.top { + margin-top: -2px; +} +.tooltip.right { + margin-left: 2px; +} +.tooltip.bottom { + margin-top: 2px; +} +.tooltip.left { + margin-left: -2px; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #000000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 5px solid #000000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-bottom: 5px solid #000000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-right: 5px solid #000000; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #ffffff; + text-align: center; + text-decoration: none; + background-color: #000000; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1010; + display: none; + padding: 5px; +} +.popover.top { + margin-top: -5px; +} +.popover.right { + margin-left: 5px; +} +.popover.bottom { + margin-top: 5px; +} +.popover.left { + margin-left: -5px; +} +.popover.top .arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #000000; +} +.popover.right .arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-right: 5px solid #000000; +} +.popover.bottom .arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-bottom: 5px solid #000000; +} +.popover.left .arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 5px solid #000000; +} +.popover .arrow { + position: absolute; + width: 0; + height: 0; +} +.popover-inner { + padding: 3px; + width: 280px; + overflow: hidden; + background: #000000; + background: rgba(0, 0, 0, 0.8); + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); +} +.popover-title { + padding: 9px 15px; + line-height: 1; + background-color: #f5f5f5; + border-bottom: 1px solid #eee; + -webkit-border-radius: 3px 3px 0 0; + -moz-border-radius: 3px 3px 0 0; + border-radius: 3px 3px 0 0; +} +.popover-content { + padding: 14px; + background-color: #ffffff; + -webkit-border-radius: 0 0 3px 3px; + -moz-border-radius: 0 0 3px 3px; + border-radius: 0 0 3px 3px; + -webkit-background-clip: padding-box; + -moz-background-clip: padding-box; + background-clip: padding-box; +} +.popover-content p, .popover-content ul, .popover-content ol { + margin-bottom: 0; +} +.thumbnails { + margin-left: -20px; + list-style: none; + *zoom: 1; +} +.thumbnails:before, .thumbnails:after { + display: table; + content: ""; +} +.thumbnails:after { + clear: both; +} +.thumbnails > li { + float: left; + margin: 0 0 18px 20px; +} +.thumbnail { + display: block; + padding: 4px; + line-height: 1; + border: 1px solid #ddd; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075); +} +a.thumbnail:hover { + border-color: #0088cc; + -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); + -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); + box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); +} +.thumbnail > img { + display: block; + max-width: 100%; + margin-left: auto; + margin-right: auto; +} +.thumbnail .caption { + padding: 9px; +} +.label { + padding: 1px 3px 2px; + font-size: 9.75px; + font-weight: bold; + color: #ffffff; + text-transform: uppercase; + background-color: #999999; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.label-important { + background-color: #b94a48; +} +.label-warning { + background-color: #f89406; +} +.label-success { + background-color: #468847; +} +.label-info { + background-color: #3a87ad; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 0 0; + } + to { + background-position: 40px 0; + } +} +@-moz-keyframes progress-bar-stripes { + from { + background-position: 0 0; + } + to { + background-position: 40px 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 0 0; + } + to { + background-position: 40px 0; + } +} +.progress { + overflow: hidden; + height: 18px; + margin-bottom: 18px; + background-color: #f7f7f7; + background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9); + background-image: -ms-linear-gradient(top, #f5f5f5, #f9f9f9); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9)); + background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9); + background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9); + background-image: linear-gradient(top, #f5f5f5, #f9f9f9); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0); + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.progress .bar { + width: 0%; + height: 18px; + color: #ffffff; + font-size: 12px; + text-align: center; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #0e90d2; + background-image: -moz-linear-gradient(top, #149bdf, #0480be); + background-image: -ms-linear-gradient(top, #149bdf, #0480be); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be)); + background-image: -webkit-linear-gradient(top, #149bdf, #0480be); + background-image: -o-linear-gradient(top, #149bdf, #0480be); + background-image: linear-gradient(top, #149bdf, #0480be); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0); + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-transition: width 0.6s ease; + -moz-transition: width 0.6s ease; + -ms-transition: width 0.6s ease; + -o-transition: width 0.6s ease; + transition: width 0.6s ease; +} +.progress-striped .bar { + background-color: #62c462; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + -webkit-background-size: 40px 40px; + -moz-background-size: 40px 40px; + -o-background-size: 40px 40px; + background-size: 40px 40px; +} +.progress.active .bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -moz-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-danger .bar { + background-color: #dd514c; + background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35); + background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35)); + background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35); + background-image: -o-linear-gradient(top, #ee5f5b, #c43c35); + background-image: linear-gradient(top, #ee5f5b, #c43c35); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0); +} +.progress-danger.progress-striped .bar { + background-color: #ee5f5b; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-success .bar { + background-color: #5eb95e; + background-image: -moz-linear-gradient(top, #62c462, #57a957); + background-image: -ms-linear-gradient(top, #62c462, #57a957); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957)); + background-image: -webkit-linear-gradient(top, #62c462, #57a957); + background-image: -o-linear-gradient(top, #62c462, #57a957); + background-image: linear-gradient(top, #62c462, #57a957); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0); +} +.progress-success.progress-striped .bar { + background-color: #62c462; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-info .bar { + background-color: #4bb1cf; + background-image: -moz-linear-gradient(top, #5bc0de, #339bb9); + background-image: -ms-linear-gradient(top, #5bc0de, #339bb9); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9)); + background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9); + background-image: -o-linear-gradient(top, #5bc0de, #339bb9); + background-image: linear-gradient(top, #5bc0de, #339bb9); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0); +} +.progress-info.progress-striped .bar { + background-color: #5bc0de; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.accordion { + margin-bottom: 18px; +} +.accordion-group { + margin-bottom: 2px; + border: 1px solid #e5e5e5; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.accordion-heading { + border-bottom: 0; +} +.accordion-heading .accordion-toggle { + display: block; + padding: 8px 15px; +} +.accordion-inner { + padding: 9px 15px; + border-top: 1px solid #e5e5e5; +} +.carousel { + position: relative; + margin-bottom: 18px; + line-height: 1; +} +.carousel-inner { + overflow: hidden; + width: 100%; + position: relative; +} +.carousel .item { + display: none; + position: relative; + -webkit-transition: 0.6s ease-in-out left; + -moz-transition: 0.6s ease-in-out left; + -ms-transition: 0.6s ease-in-out left; + -o-transition: 0.6s ease-in-out left; + transition: 0.6s ease-in-out left; +} +.carousel .item > img { + display: block; + line-height: 1; +} +.carousel .active, .carousel .next, .carousel .prev { + display: block; +} +.carousel .active { + left: 0; +} +.carousel .next, .carousel .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel .next { + left: 100%; +} +.carousel .prev { + left: -100%; +} +.carousel .next.left, .carousel .prev.right { + left: 0; +} +.carousel .active.left { + left: -100%; +} +.carousel .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 40%; + left: 15px; + width: 40px; + height: 40px; + margin-top: -20px; + font-size: 60px; + font-weight: 100; + line-height: 30px; + color: #ffffff; + text-align: center; + background: #222222; + border: 3px solid #ffffff; + -webkit-border-radius: 23px; + -moz-border-radius: 23px; + border-radius: 23px; + opacity: 0.5; + filter: alpha(opacity=50); +} +.carousel-control.right { + left: auto; + right: 15px; +} +.carousel-control:hover { + color: #ffffff; + text-decoration: none; + opacity: 0.9; + filter: alpha(opacity=90); +} +.carousel-caption { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 10px 15px 5px; + background: #333333; + background: rgba(0, 0, 0, 0.75); +} +.carousel-caption h4, .carousel-caption p { + color: #ffffff; +} +.hero-unit { + padding: 60px; + margin-bottom: 30px; + background-color: #f5f5f5; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} +.hero-unit h1 { + margin-bottom: 0; + font-size: 60px; + line-height: 1; + letter-spacing: -1px; +} +.hero-unit p { + font-size: 18px; + font-weight: 200; + line-height: 27px; +} +.pull-right { + float: right; +} +.pull-left { + float: left; +} +.hide { + display: none; +} +.show { + display: block; +} +.invisible { + visibility: hidden; +} +/* ======================= */ +/* = Mods (Temporary = */ +/* ======================= */ +[class*="span"] { + float: auto; + margin-left: auto; +} diff --git a/ckan/public/css/forms.css b/ckan/public/css/forms.css index 97406579d9f..e1a6a46de91 100644 --- a/ckan/public/css/forms.css +++ b/ckan/public/css/forms.css @@ -139,6 +139,12 @@ form.has-errors .field_error, form.has-errors .error-explanation { position: relative; background: transparent url(../images/icons/error.png) left 3px no-repeat; } +td.field_warning { + color: #d12f19; } + +.fieldset_button_error { + background: transparent url(../images/icons/error.png) left center no-repeat; } + .error-explanation, #errorExplanation { background: #fff; diff --git a/ckan/public/css/style.css b/ckan/public/css/style.css index 0143eb36e1a..bb38318b232 100644 --- a/ckan/public/css/style.css +++ b/ckan/public/css/style.css @@ -1,5 +1,4 @@ @import url('forms.css'); -@import url('bootstrap.css'); body.no-sidebar #sidebar { display: none; } body.no-sidebar #content { @@ -17,7 +16,7 @@ body.no-sidebar #content { background-image: linear-gradient(top, #e2e2e2, #cccccc); filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#e2e2e2', EndColorStr='#cccccc'); - margin-bottom: 20px; + margin-bottom: 0px; -moz-box-shadow: 0px 2px 15px #dddddd; -webkit-box-shadow: 0px 2px 15px #dddddd; box-shadow: 0px 2px 15px #dddddd; @@ -54,6 +53,7 @@ header .menu form { header .menu input { width: 200px; margin-top: -1px; + height: 16px; } header .menu a { @@ -150,7 +150,6 @@ a:hover { fieldset { border: 0px; - border-bottom: 1px solid #e0e0e0; } @@ -180,13 +179,6 @@ tbody tr:nth-child(even) td, tbody tr.even td { tbody tr:nth-child(odd) td, tbody tr.odd td { background-color: #F2F2F2; } -tbody tr.table-empty td { - background: #f8f8f8; - border: 1px solid #eee; - font-style: italic; - color: #333; - font-size: 90%; -} /* ==================== */ @@ -236,6 +228,36 @@ img.gravatar { border-radius: 3px; } +.inline-icon { + margin-bottom: -4px; + padding-right: 5px; +} + +.drag-drop-list { + list-style-type: none; + margin: 0; + padding: 0; +} +.drag-drop-list .drag-bars { + font-size: 12px; + color: #ccc; + display: inline-block; + text-align: center; + vertical-align: middle; + padding-left: 18px; + padding-right: 12px; + cursor: move; +} +.drag-drop-list li { + margin-bottom: 3px; + cursor: move; +} + +ul.no-break li { + white-space: nowrap; + overflow: hidden; +} + /* =============== */ /* MinorNavigation */ /* =============== */ @@ -290,11 +312,6 @@ img.gravatar { text-decoration: none; font-weight: bold; } -#minornavigation li a img, -#minornavigation li.disabled img:first-child { - margin-bottom: -4px; - padding-right: 5px; -} #minornavigation li.current-tab a, #minornavigation li.current-tab a:hover, #minornavigation li.current-tab a:visited { @@ -312,6 +329,9 @@ img.gravatar { /* ======================================== */ /* MinorNavigation extension: Dropdown Menu */ /* ======================================== */ +.dropdown-appears hr { + margin: 5px 0; +} #minornavigation .dropdown { position: relative; display: inline-block; @@ -908,6 +928,7 @@ input.url-input { /* = Mini-Tabs = */ /* ============= */ ul.button-row { + margin-top: 5px; margin-bottom: 5px; } ul.button-row li { @@ -973,6 +994,7 @@ ul.dataset-edit-nav li a { display: block; padding: 7px 0 7px 10px; margin-bottom: 7px; + margin-left: 20px; border: 1px transparent solid; } ul.dataset-edit-nav li a.active, @@ -1000,116 +1022,46 @@ ul.dataset-edit-nav li a:hover { /* ===================== */ /* = Edit Dataset Page = */ /* ===================== */ -.dataset-edit-form .edit-summary { - /* Needs refactoring. Clutters the page too much. */ +.dataset-edit-form fieldset#resources { display: none; } -.dataset-edit-form input#Resource--url { - width: 60%; -} -.dataset-edit-form .resource-add { - background: #eee; - padding-top: 10px; - padding-bottom: 5px; - border: 1px solid #e0e0e0; - border-left: none; - border-right: none; -} -.dataset-edit-form .resource-add li h4 { - display: inline; - padding-right: 20px; -} -.dataset-edit-form .resource-add .subpane { - margin-top: 10px; -} -.dataset-edit-form .resource-add .fileinfo { - margin: 7px 0; -} .dataset-edit-form button.dataset-delete { - vertical-align: top; + vertical-align: middle; } - -/* ================================ */ -/* = Edit Dataset Page: Resources = */ -/* ================================ */ -.resource-table-edit tbody tr td, -.resource-table-view tbody tr td { - /* Constrain structure against overflow */ - max-width: 200px; - overflow: hidden; - white-space: nowrap; -} -.resource-table-edit tr { - overflow: hidden; -} -th.resource-edit-delete, -td.resource-edit-delete { - /* Override screen.css */ - padding: 0; - width: 40px; -} -td.resource-edit-delete img { - padding: 8px; -} -a.resource-edit-expand { - background-image: url('/images/icons/arrow-closed.gif'); - padding-left: 13px; - background-position: left center; - background-repeat: no-repeat; -} -td.resource-edit { - padding: 10px; - vertical-align: top; -} -.resource-edit-expanded { - overflow: hidden; - margin: 0; -} -.resource-edit-expanded table { - /* Override screen.css */ - margin: 10px 0; -} -table.resource-table-edit td.resource-edit tbody td { - /* Override alternating background */ - background: transparent; -} -td.resource-edit-delete { - vertical-align: top; - text-align: center; -} -td.resource-edit input { - /* Override forms.css */ +/* ======================= */ +/* = Edit Resources Page = */ +/* ======================= */ +body.editresources #content { width: 100%; + padding-right: 0; + margin-right: 0; + border-right: 0; } -th.resource-edit-label { - width: 23%; - display: none; +body.editresources #sidebar { + display: none; } -th.resource-edit-value { - width: 27%; - display: none; +.dataset-editresources-form fieldset#basic-information, +.dataset-editresources-form fieldset#groups, +.dataset-editresources-form fieldset#further-information, +.dataset-editresources-form fieldset#extras, +.dataset-editresources-form fieldset#delete { + display: none; } -td.resource-edit-label, -td.resource-edit-value { - font-size: 0.92em; - line-height: 1.5em; - padding-top: 0; - padding-bottom: 4px; - background: inherit; + +.dataset-editresources-form .resource-add li h4 { + display: inline; + padding-right: 20px; } -td.resource-edit-label { - text-align: right; - font-weight: bold; - padding-right: 4px; - vertical-align: middle; +.dataset-editresources-form .resource-add .subpane { + margin-top: 10px; } -td.resource-edit-value { - padding-left: 4px; - border-left: 1px dashed #aaa; +.dataset-editresources-form .resource-add .fileinfo { + margin: 7px 0; } + /* ==================== */ /* = Add Dataset Page = */ /* ==================== */ @@ -1117,11 +1069,11 @@ td.resource-edit-value { /* Show only one field */ display: none; } -.dataset-create-form fieldset#basic-information { +.dataset-create-form fieldset#basic-information, +.dataset-create-form fieldset#resources { display: block; -} -.dataset-create-form .edit-summary { - display: none; + padding-bottom: 0; + margin-bottom: 0; } .dataset-create-form dt.homepage-label, .dataset-create-form dd.homepage-field, @@ -1135,17 +1087,8 @@ td.resource-edit-value { { display: none; } -.dataset-create-form dt.description-label, -.dataset-create-form dd.description-field, -.dataset-create-form dd.description-instructions -{ - display: none; -} -.dataset-create-form dt.license-label, -.dataset-create-form dd.license-field, -.dataset-create-form dd.license-instructions -{ - display: none; +dd.license-instructions { + font-size: 11px; } .dataset-create-form dd.name-field { padding-top: 0.2em; @@ -1153,6 +1096,9 @@ td.resource-edit-value { .dataset-create-form dd.name-field p { margin-bottom: 4px; } +.dataset-create-form .description-field textarea { + height: 70px; +} .group-create-form dd.name-field { padding-top: 0.2em; @@ -1165,6 +1111,25 @@ a.url-edit { font-weight: normal; margin-left: 10px; } +p.url-is-long { + color: #600; + display: none; + font-size: 11px; + font-weight: bold; +} +div.author-box, +label.edit-summary { + font-size: 11px; + color: #666; +} +#log_message { + height: 40px; + color: #666; + font-size: 11px; +} +#log_message:focus { + color: #000; +} /* ===================== */ @@ -1286,31 +1251,35 @@ body.package.read #sidebar li.widget-container { border: 0 } .notes { - padding: 8px; - border-left: 2px solid #eee; background: url('../images/ldquo.png') no-repeat top left #f7f7f7; + border: 1px solid #eee; + border-radius: 5px; } -.notes #notes-toggle a { - cursor: pointer; +.notes > div { + padding: 8px; } -.notes #notes-toggle a.more:after { - content: ' »'; - font-size: 150%; - position: relative; - bottom: -1px; +#notes-toggle { + padding: 0; + height: 23px; } -.notes #notes-toggle a.less:before { - content: '« '; - font-size: 150%; - position: relative; - bottom: -1px; +.notes #notes-toggle button { + cursor: pointer; + width: 100%; + height: 23px; + padding: 4px; + border-radius: 0; + border: 0; + border-top: 1px solid #eee; } - #notes-extract p { margin-bottom: 0; } #notes-remainder { - margin-top: 1em; + overflow: hidden; + padding: 0 8px; +} +#notes-remainder p:last-child { + margin-bottom: 0; } .dataset-label { font-weight: bold; @@ -1448,3 +1417,185 @@ body.authz form button { color:#999; } + + + + + + +/* Dev */ +fieldset#resources { + min-height: 430px; + margin-bottom: 40px; + position: relative; + padding: 0; +} +body.editresources fieldset#resources > .instructions { + display: none; +} +fieldset#resources > .instructions { + width: 300px; + padding: 12px 12px 2px 12px; +} +.resource-list { + list-style-type: none; + padding: 0; + margin: 0; +} +.resource-list li { + background: #fff; + border: 0; + border: 1px solid #eee; + border-top-color: transparent; + border-left-color: transparent; + border-right: 0; + position: relative; + margin: 0 20px 0 0; + z-index: 1; + white-space: nowrap; + overflow: hidden; + font-weight: bold; +} +.resource-list li:last-child { + border-bottom-color: transparent; +} +.resource-list li a { + display: block; + padding: 5px 10px; + color: #333; +} +.resource-list li:hover a{ + color: #B22; +} +.resource-list li:hover { + background: #f7f7f7; +} +.resource-list li.active { + border-color: #888; + background: #f9f9f9; + margin-right: 0; +} + + +/* HasErrors */ +.resource-list li.hasErrors { + border-color: #c00; + border-right: 1px solid #c00; +} +.resource-list li.active.hasErrors { + border-right: 0; +} +.resource-list li.hasErrors .drag-bars, +.resource-list li.hasErrors a { + color: #c00; +} +.resource-errors { + display: none; +} +.resource-errors dl { + margin-bottom: 0; +} +body.editresources .error-explanation { + /* Let JS render the resource errors inline */ + display: none; +} + + +/* While dragging.... */ +.resource-list-edit li.ui-sortable-helper { + box-shadow: 2px 2px 8px rgba(0,0,0,0.1); +} +.resource-list-edit li.ui-sortable-helper.active { + margin-right: 20px; + border-right: 1px solid #888; +} +.resource-list-add, +.resource-list-edit { + width: 400px; +} +.resource-list-add { + margin-top: 20px; +} +.resource-list-add li { + border-top: 1px solid #eee; +} +.resource-list-add li.active { + border-bottom-color: #888; +} +.resource-list-add li a { + padding-left: 43px; +} +.resource-list-edit li a { + padding-left: 0; +} +.resource-list-edit li:hover .drag-bars { + color: #999; +} + +button.resource-edit-delete { + float: right; +} + + + +/* Right-hand-side edit resource panel */ +/* ----------------------------------- */ +.resource-panel { + background: #f9f9f9; + border: 1px solid #888; + box-shadow: 2px 2px 4px #888; + margin-bottom: 20px; + position: absolute; + left: 399px; + top: 0px; + padding: 10px; + min-height: 100px; + min-width: 530px; +} +.resource-panel .resource-panel-close { + position: absolute; + right: -8px; + top: -12px; + width: 20px; + padding: 0; + text-align: center; +} +.resource-panel .hint { + font-size: 11px; +} +.resource-details input[type="text"] { + width: 350px; +} +.resource-details .markdown-editor { + width: 30em; +} +.resource-details textarea { + width: 340px; + height: 90px; +} +.resource-details .dataset-label { + vertical-align: top; +} +.resource-details.resource-add { + margin-bottom: 30px; +} + +/* Resource extra fields */ +/* --------------------- */ +.dynamic-extras .add-resource-extra { + margin-left: 320px; +} +.dynamic-extras .dynamic-extra { + margin: 1px 15px; +} +.dynamic-extras .remove-resource-extra { + padding: 0 7px; + font-size: 8px; +} +.dynamic-extras input.strikethrough { + text-decoration: line-through; +} +.dynamic-extras input[type="text"] { + width: 200px; +} + diff --git a/ckan/public/images/chevron-down.png b/ckan/public/images/chevron-down.png new file mode 100644 index 00000000000..8d6c121eb9b Binary files /dev/null and b/ckan/public/images/chevron-down.png differ diff --git a/ckan/public/images/chevron-up.png b/ckan/public/images/chevron-up.png new file mode 100644 index 00000000000..78b0290d06d Binary files /dev/null and b/ckan/public/images/chevron-up.png differ diff --git a/ckan/public/images/icons/package_add.png b/ckan/public/images/icons/package_add.png new file mode 100755 index 00000000000..9c8a9da4ae4 Binary files /dev/null and b/ckan/public/images/icons/package_add.png differ diff --git a/ckan/public/images/icons/page_white.png b/ckan/public/images/icons/page_white.png new file mode 100755 index 00000000000..8b8b1ca0000 Binary files /dev/null and b/ckan/public/images/icons/page_white.png differ diff --git a/ckan/public/images/icons/page_white_add.png b/ckan/public/images/icons/page_white_add.png new file mode 100755 index 00000000000..aa23dde3746 Binary files /dev/null and b/ckan/public/images/icons/page_white_add.png differ diff --git a/ckan/public/images/icons/page_white_code.png b/ckan/public/images/icons/page_white_code.png new file mode 100755 index 00000000000..0c76bd12977 Binary files /dev/null and b/ckan/public/images/icons/page_white_code.png differ diff --git a/ckan/public/images/icons/page_white_compressed.png b/ckan/public/images/icons/page_white_compressed.png new file mode 100755 index 00000000000..2b6b1007f33 Binary files /dev/null and b/ckan/public/images/icons/page_white_compressed.png differ diff --git a/ckan/public/images/icons/page_white_cup.png b/ckan/public/images/icons/page_white_cup.png new file mode 100755 index 00000000000..0a7d6f4a6f6 Binary files /dev/null and b/ckan/public/images/icons/page_white_cup.png differ diff --git a/ckan/public/images/icons/page_white_database.png b/ckan/public/images/icons/page_white_database.png new file mode 100755 index 00000000000..bddba1f98ca Binary files /dev/null and b/ckan/public/images/icons/page_white_database.png differ diff --git a/ckan/public/images/icons/page_white_error.png b/ckan/public/images/icons/page_white_error.png new file mode 100755 index 00000000000..9fc5a0a103d Binary files /dev/null and b/ckan/public/images/icons/page_white_error.png differ diff --git a/ckan/public/images/icons/page_white_excel.png b/ckan/public/images/icons/page_white_excel.png new file mode 100755 index 00000000000..b977d7e52e2 Binary files /dev/null and b/ckan/public/images/icons/page_white_excel.png differ diff --git a/ckan/public/images/icons/page_white_gear.png b/ckan/public/images/icons/page_white_gear.png new file mode 100755 index 00000000000..106f5aa3611 Binary files /dev/null and b/ckan/public/images/icons/page_white_gear.png differ diff --git a/ckan/public/images/icons/page_white_link.png b/ckan/public/images/icons/page_white_link.png new file mode 100755 index 00000000000..bf7bd1c9bfd Binary files /dev/null and b/ckan/public/images/icons/page_white_link.png differ diff --git a/ckan/public/images/icons/page_white_text.png b/ckan/public/images/icons/page_white_text.png new file mode 100755 index 00000000000..813f712f726 Binary files /dev/null and b/ckan/public/images/icons/page_white_text.png differ diff --git a/ckan/public/scripts/application.js b/ckan/public/scripts/application.js index e8e40859cfe..dc9e5e9d3e2 100644 --- a/ckan/public/scripts/application.js +++ b/ckan/public/scripts/application.js @@ -1,3 +1,8 @@ +var CKAN = CKAN || {}; + +/* ================================= */ +/* == Initialise CKAN Application == */ +/* ================================= */ (function ($) { $(document).ready(function () { CKAN.Utils.setupUserAutocomplete($('input.autocomplete-user')); @@ -10,6 +15,8 @@ CKAN.Utils.setupFormatAutocomplete($(this)); }); CKAN.Utils.setupMarkdownEditor($('.markdown-editor')); + // bootstrap collapse + $('.collapse').collapse(); // set up ckan js var config = { endpoint: CKAN.SITE_URL + '/' @@ -20,6 +27,12 @@ client: client }; + // Buttons with href-action should navigate when clicked + $('input.href-action').click(function(e) { + e.preventDefault(); + window.location = ($(e.target).attr('action')); + }); + var isFrontPage = $('body.index.home').length > 0; if (isFrontPage) { CKAN.Utils.setupWelcomeBanner($('.js-welcome-banner')); @@ -35,86 +48,736 @@ if (isDatasetView) { // Show extract of notes field CKAN.Utils.setupNotesExtract(); + $('.js-scroll-resources').click(function() { + var header = $('#dataset-resources > h3:first-child'); + $("html,body").animate({ scrollTop: header.offset().top }, 500); + }); } var isResourceView = $('body.package.resource_read').length > 0; if (isResourceView) { - CKANEXT.DATAPREVIEW.loadPreviewDialog(preload_resource); + CKAN.DataPreview.loadPreviewDialog(preload_resource); } - var isDatasetNew = $('body.package.new').length > 0; if (isDatasetNew) { // Set up magic URL slug editor - CKAN.Utils.setupUrlEditor('package'); + var urlEditor = new CKAN.View.UrlEditor({ + slugType: 'package' + }); $('#save').val(CKAN.Strings.addDataset); $("#title").focus(); } var isGroupNew = $('body.group.new').length > 0; if (isGroupNew) { // Set up magic URL slug editor - CKAN.Utils.setupUrlEditor('group'); + var urlEditor = new CKAN.View.UrlEditor({ + slugType: 'group' + }); $('#save').val(CKAN.Strings.addGroup); $("#title").focus(); } - // Buttons with href-action should navigate when clicked - $('input.href-action').click(function(e) { - e.preventDefault(); - window.location = ($(e.target).attr('action')); - }); - var isDatasetEdit = $('body.package.edit').length > 0; if (isDatasetEdit) { - CKAN.Utils.setupUrlEditor('package',readOnly=true); + CKAN.Utils.warnOnFormChanges($('form#dataset-edit')); + var urlEditor = new CKAN.View.UrlEditor({ + slugType: 'package', + editMode: true + }); + + // Set up hashtag nagivigation + CKAN.Utils.setupDatasetEditNavigation(); + + // Set up dataset delete button + CKAN.Utils.setupDatasetDeleteButton(); + } + var isDatasetResourceEdit = $('body.package.editresources').length > 0; + if (isDatasetNew || isDatasetResourceEdit) { // Selectively enable the upload button var storageEnabled = $.inArray('storage',CKAN.plugins)>=0; if (storageEnabled) { $('li.js-upload-file').show(); } - - // Set up hashtag nagivigation - CKAN.Utils.setupDatasetEditNavigation(); - + // Backbone model/view var _dataset = new CKAN.Model.Dataset(preload_dataset); var $el=$('form#dataset-edit'); - var view=new CKAN.View.DatasetEditForm({ - model: _dataset, + var view=new CKAN.View.ResourceEditor({ + collection: _dataset.get('resources'), el: $el }); view.render(); - // Set up dataset delete button - var select = $('select.dataset-delete'); - select.attr('disabled','disabled'); - select.css({opacity: 0.3}); - $('button.dataset-delete').click(function(e) { - select.removeAttr('disabled'); - select.fadeTo('fast',1.0); - $(e.target).css({opacity:0}); - $(e.target).attr('disabled','disabled'); - return false; + $( ".drag-drop-list" ).sortable({ + distance: 10 }); + $( ".drag-drop-list" ).disableSelection(); } + var isGroupEdit = $('body.group.edit').length > 0; if (isGroupEdit) { - CKAN.Utils.setupUrlEditor('group',readOnly=true); + var urlEditor = new CKAN.View.UrlEditor({ + slugType: 'group', + editMode: true + }); } }); }(jQuery)); -var CKAN = CKAN || {}; -CKAN.Utils = function($, my) { - my.flashMessage = function(msg, category) { - if (!category) { - category = 'info'; +/* ============================== */ +/* == Backbone View: UrlEditor == */ +/* ============================== */ +CKAN.View.UrlEditor = Backbone.View.extend({ + initialize: function() { + _.bindAll(this,'titleToSlug','titleChanged','urlChanged','checkSlugIsValid','toEditMode','apiCallback'); + + // Initial state + this.updateTimer = null; + this.titleInput = $('.js-title'); + this.urlSuffix = $('.js-url-suffix'); + this.urlInput = $('.js-url-input'); + this.validMsg = $('.js-url-is-valid'); + this.lengthMsg = $('.url-is-long'); + this.lastTitle = ""; + this.disableTitleChanged = false; + + // Settings + this.regexToHyphen = [ new RegExp('[ .:/_]', 'g'), + new RegExp('[^a-zA-Z0-9-_]', 'g'), + new RegExp('-+', 'g')]; + this.regexToDelete = [ new RegExp('^-*', 'g'), + new RegExp('-*$', 'g')]; + + // Default options + if (!this.options.apiUrl) + this.options.apiUrl = CKAN.SITE_URL + '/api/2/util/is_slug_valid'; + if (!this.options.MAX_SLUG_LENGTH) + this.options.MAX_SLUG_LENGTH = 90; + if (!this.options.editMode) + this.options.editMode = false; + + if (this.options.editMode) { + this.originalUrl = this.urlInput.val(); } - var messageDiv = $('
').html(msg).addClass(category).hide(); - $('.flash-messages').append(messageDiv); - messageDiv.show(1200); - }; + // Hook title changes to the input box + CKAN.Utils.bindInputChanges(this.titleInput, this.titleChanged); + CKAN.Utils.bindInputChanges(this.urlInput, this.urlChanged); + $('.url-edit').click(this.toEditMode); + + // Set up the form + this.urlChanged(); + if (this.options.editMode) { + this.toEditMode(); + this.validMsg.html(''); + } + }, + + toEditMode: function(event) { + $('.js-url-viewmode').hide(); + $('.js-url-editmode').show(); + if (event) { + // If we clicked a link, highlight the input + event.preventDefault(); + this.urlInput.select(); + this.urlInput.focus(); + } + // Disable automatic slug generation + this.disableTitleChanged = true; + }, + + titleToSlug: function(title) { + var slug = title; + $.each(this.regexToHyphen, function(idx,regex) { slug = slug.replace(regex, '-'); }); + $.each(this.regexToDelete, function(idx,regex) { slug = slug.replace(regex, ''); }); + slug = slug.toLowerCase(); + + if (slug.length...'); + } + else { + this.urlSuffix.html(''+slug+''); + } + if (slug.length<2) { + this.validMsg.html(''+CKAN.Strings.urlIsTooShort+''); + } + else if (this.options.editMode && slug==this.originalUrl) { + this.validMsg.html(''+CKAN.Strings.urlIsUnchanged+''); + } + else { + this.validMsg.html(''+CKAN.Strings.checking+''); + var self = this; + this.updateTimer = setTimeout(function () { + self.checkSlugIsValid(slug); + }, 200); + } + if (slug.length>20) { + this.lengthMsg.show(); + } + else { + this.lengthMsg.hide(); + } + }, + + checkSlugIsValid: function(slug) { + $.ajax({ + url: this.options.apiUrl, + data: 'type='+this.options.slugType+'&slug=' + slug, + dataType: 'jsonp', + type: 'get', + jsonpCallback: 'callback', + success: this.apiCallback + }); + }, + + /* Called when the slug-validator gets back to us */ + apiCallback: function(data) { + if (data.valid) { + this.validMsg.html(''+CKAN.Strings.urlIsAvailable+''); + } else { + this.validMsg.html(''+CKAN.Strings.urlIsNotAvailable+''); + } + }, +}); + + +/* =================================== */ +/* == Backbone View: ResourceEditor == */ +/* =================================== */ +CKAN.View.ResourceEditor = Backbone.View.extend({ + initialize: function() { + // Init bindings + _.bindAll(this, 'resourceAdded', 'resourceRemoved', 'sortStop', 'openFirstPanel', 'closePanel', 'openAddPanel'); + this.collection.bind('add', this.resourceAdded); + this.collection.bind('remove', this.resourceRemoved); + this.collection.each(this.resourceAdded); + this.el.find('.resource-list-edit').bind("sortstop", this.sortStop); + + // Delete the barebones editor. We will populate our own form. + this.el.find('.js-resource-edit-barebones').remove(); + + // Warn on form changes + var flashWarning = CKAN.Utils.warnOnFormChanges(this.el); + this.collection.bind('add', flashWarning); + this.collection.bind('remove', flashWarning); + + // Trigger the Add Resource pane + this.el.find('.js-resource-add').click(this.openAddPanel); + + // Tabbed view for adding resources + var $resourceAdd = this.el.find('.resource-add'); + this.addView=new CKAN.View.ResourceAddTabs({ + collection: this.collection, + el: $resourceAdd + }); + + // Close details button + this.el.find('.resource-panel-close').click(this.closePanel); + + // Did we embed some form errors? + if (typeof global_form_errors == 'object') { + if (global_form_errors.resources) { + var openedOne = false; + for (i in global_form_errors.resources) { + var resource_errors = global_form_errors.resources[i]; + if (CKAN.Utils.countObject(resource_errors) > 0) { + var resource = this.collection.at(i); + resource.view.setErrors(resource_errors); + if (!openedOne) { + resource.view.openMyPanel(); + openedOne = true; + } + } + } + } + } + else { + // Initial state + this.openFirstPanel(); + } + }, + /* + * Called when the page loads or the current resource is deleted. + * Reset page state to the first available edit panel. + */ + openFirstPanel: function() { + if (this.collection.length>0) { + this.collection.at(0).view.openMyPanel(); + } + else { + this.openAddPanel(); + } + }, + /* + * Open the 'Add New Resource' special-case panel on the right. + */ + openAddPanel: function(e) { + if (e) e.preventDefault(); + var panel = this.el.find('.resource-panel'); + var addLi = this.el.find('.resource-list-add li'); + this.el.find('.resource-list li').removeClass('active'); + this.el.find('.resource-details').hide(); + this.el.find('.resource-details.resource-add').show(); + addLi.addClass('active'); + panel.show(); + panel.css('top', Math.max(0, addLi.position().top + addLi.height() - panel.height())); + }, + /* + * Close the panel on the right. + */ + closePanel: function(e) { + if (e) e.preventDefault(); + this.el.find('.resource-list li').removeClass('active'); + this.el.find('.resource-panel').hide(); + }, + /* + * Update the resource__N__field names to match + * new sort order. + */ + sortStop: function(e,ui) { + this.collection.each(function(resource) { + // Ask the DOM for the new sort order + var index = resource.view.li.index(); + resource.view.options.position = index; + // Update the form element names + var table = resource.view.table; + $.each(table.find('input,textarea,select'), function(input_index, input) { + var name = $(input).attr('name'); + if (name) { + name = name.replace(/(resources__)\d+(.*)/, '$1'+index+'$2') + $(input).attr('name',name); + } + }); + }); + }, + /* + * Calculate id of the next resource to create + */ + nextIndex: function() { + var maxId=-1; + var root = this.el.find('.resource-panel'); + root.find('input').each(function(idx,input) { + var name = $(input).attr('name') || ''; + var splitName=name.split('__'); + if (splitName.length>1) { + var myId = parseInt(splitName[1]) + maxId = Math.max(myId, maxId); + } + }); + return maxId+1; + }, + /* + * Create DOM elements for new resource. + */ + resourceAdded: function(resource) { + var self = this; + resource.view = new CKAN.View.Resource({ + position: this.nextIndex(), + model: resource, + callback_deleteMe: function() { self.collection.remove(resource); } + }); + this.el.find('.resource-list-edit').append(resource.view.li); + this.el.find('.resource-panel').append(resource.view.table); + if (resource.isNew()) { + resource.view.openMyPanel(); + } + }, + /* + * Destroy DOM elements for deleted resource. + */ + resourceRemoved: function(resource) { + resource.view.removeFromDom(); + delete resource.view; + this.openFirstPanel(); + } +}); + + +/* ============================== */ +/* == Backbone View: Resource == */ +/* ============================== */ + +CKAN.View.Resource = Backbone.View.extend({ + initialize: function() { + this.el = $(this.el); + _.bindAll(this,'updateName','updateIcon','name','askToDelete','openMyPanel','setErrors','setupDynamicExtras','addDynamicExtra', 'onDatastoreEnabledChange'); + this.render(); + }, + render: function() { + this.raw_resource = this.model.toTemplateJSON(); + var resource_object = { + resource: this.raw_resource, + num: this.options.position, + resource_icon: '/images/icons/page_white.png', + resourceTypeOptions: [ + ['file', 'Data File'] + , ['api', 'API'] + , ['visualization', 'Visualization'] + , ['image', 'Image'] + , ['metadata', 'Metadata'] + , ['documentation', 'Documentation'] + , ['code', 'Code'] + , ['example', 'Example'] + ] + }; + // Generate DOM elements + this.li = $($.tmpl(CKAN.Templates.resourceEntry, resource_object)); + this.table = $($.tmpl(CKAN.Templates.resourceDetails, resource_object)); + + // Hook to changes in name + this.nameBox = this.table.find('input.js-resource-edit-name'); + this.descriptionBox = this.table.find('textarea.js-resource-edit-description'); + CKAN.Utils.bindInputChanges(this.nameBox,this.updateName); + CKAN.Utils.bindInputChanges(this.descriptionBox,this.updateName); + // Hook to changes in format + this.formatBox = this.table.find('input.js-resource-edit-format'); + CKAN.Utils.bindInputChanges(this.formatBox,this.updateIcon); + // Hook to open panel link + this.li.find('.resource-open-my-panel').click(this.openMyPanel); + this.table.find('.js-resource-edit-delete').click(this.askToDelete); + this.table.find('.js-datastore-enabled-checkbox').change(this.onDatastoreEnabledChange); + // Hook to markdown editor + CKAN.Utils.setupMarkdownEditor(this.table.find('.markdown-editor')); + if (resource_object.resource.webstore_url) { + this.table.find('.js-datastore-enabled-checkbox').prop('checked', true); + } + + // Set initial state + this.updateName(); + this.updateIcon(); + this.setupDynamicExtras(); + this.hasErrors = false; + }, + /* + * Process a JSON object of errors attached to this resource + */ + setErrors: function(obj) { + if (CKAN.Utils.countObject(obj) > 0) { + this.hasErrors = true; + this.errors = obj; + this.li.addClass('hasErrors'); + var errorList = $('
').addClass('errorList'); + $.each(obj,function(k,v) { + var errorText = ''; + var newLine = false; + $.each(v,function(index,value) { + if (newLine) errorText += '
'; + errorText += value; + newLine = true; + }); + errorList.append($('
').html(k)); + errorList.append($('
').html(errorText)); + }); + this.table.find('.resource-errors').append(errorList).show(); + } + }, + /* + * Work out what I should be called. Rough-match + * of helpers.py:resource_display_name. + */ + name: function() { + var name = this.nameBox.val(); + if (!name) { + name = this.descriptionBox.val(); + if (!name) { + if (this.model.isNew()) { + name = '[new resource]'; + } + else { + name = '[no name] ' + this.model.id; + } + } + } + if (name.length>45) { + name = name.substring(0,45)+'...'; + } + return name; + }, + /* + * Called when the user types to update the name in + * my
  • to match the values. + */ + updateName: function() { + // Need to structurally modify the DOM to force a re-render of text + var $link = this.li.find('.js-resource-edit-name'); + $link.html(''+this.name()+''); + }, + /* + * Called when the user types to update the icon + * tags. Uses server API to select icon. + */ + updateIcon: function() { + var self = this; + if (self.updateIconTimer) { + clearTimeout(self.updateIconTimer); + } + self.updateIconTimer = setTimeout(function() { + // AJAX to server API + $.getJSON('/api/2/util/resource/format_icon?format='+encodeURIComponent(self.formatBox.val()), function(data) { + if (data && data.icon && data.format==self.formatBox.val()) { + self.li.find('.js-resource-icon').attr('src',data.icon); + self.table.find('.js-resource-icon').attr('src',data.icon); + } + }); + delete self.updateIconTimer; + }, + 100); + }, + /* + * Closes all other panels on the right and opens my editor panel. + */ + openMyPanel: function(e) { + if (e) e.preventDefault(); + // Close all tables + var panel = this.table.parents('.resource-panel'); + panel.find('.resource-details').hide(); + this.li.parents('fieldset#resources').find('li').removeClass('active'); + panel.show(); + this.table.show(); + this.table.find('.js-resource-edit-name').focus(); + this.li.addClass('active'); + panel.css('top', Math.max(0, this.li.position().top+this.li.height() - panel.height())); + }, + /* + * Called when my delete button is clicked. Calls back to the parent + * resource editor. + */ + askToDelete: function(e) { + e.preventDefault(); + var confirmMessage = CKAN.Strings.deleteThisResourceQuestion.replace('%name%', this.name()); + if (confirm(confirmMessage)) { + this.options.callback_deleteMe(); + } + }, + /* + * Set up the dynamic-extras section of the table. + */ + setupDynamicExtras: function() { + var self = this; + $.each(this.raw_resource, function(key,value) { + // Skip the known keys + if (self.reservedWord(key)) return; + self.addDynamicExtra(key,value); + }); + this.table.find('.add-resource-extra').click(function(e) { + e.preventDefault(); + self.addDynamicExtra('',''); + }); + }, + addDynamicExtra: function(key,value) { + // Create elements + var dynamicExtra = $($.tmpl(CKAN.Templates.resourceExtra, { + num: this.options.position, + key: key, + value: value})); + this.table.find('.dynamic-extras').append(dynamicExtra); + // Captured values + var inputKey = dynamicExtra.find('.extra-key'); + var inputValue = dynamicExtra.find('.extra-value'); + // Callback function + var self = this; + var setExtraName = function() { + var _key = inputKey.val(); + var key = _key.trim().replace(/\s+/g,''); + // Don't allow you to create an extra called mimetype (etc) + if (self.reservedWord(key)) key=''; + // Set or unset the field's name + if (key.length) { + var newName = 'resources__'+self.options.position+'__'+key; + inputValue.attr('name',newName); + inputValue.removeClass('strikethrough'); + } + else { + inputValue.removeAttr('name'); + inputValue.addClass('strikethrough'); + } + }; + // Callback function + var clickRemove = function(e) { + e.preventDefault(); + dynamicExtra.remove(); + }; + // Init with bindings + CKAN.Utils.bindInputChanges(dynamicExtra.find('.extra-key'), setExtraName); + dynamicExtra.find('.remove-resource-extra').click(clickRemove); + setExtraName(); + }, + reservedWord: function(word) { + return word=='cache_last_updated' + || word=='cache_url' + || word=='dataset' + || word=='description' + || word=='displaytitle' + || word=='extras' + || word=='format' + || word=='hash' + || word=='id' + || word=='last_modified' + || word=='mimetype' + || word=='mimetype_inner' + || word=='name' + || word=='package_id' + || word=='position' + || word=='resource_group_id' + || word=='resource_type' + || word=='revision_id' + || word=='revision_timestamp' + || word=='size' + || word=='size_extra' + || word=='state' + || word=='url' + || word=='webstore_last_updated' + || word=='webstore_url' + ; + }, + /* + * Called when my model is destroyed. Remove me from the page. + */ + removeFromDom: function() { + this.li.remove(); + this.table.remove(); + }, + onDatastoreEnabledChange: function(e) { + var isChecked = this.table.find('.js-datastore-enabled-checkbox').prop('checked'); + var webstore_url = isChecked ? 'enabled' : null; + this.model.set({webstore_url: webstore_url});; + this.table.find('.js-datastore-enabled-text').val(webstore_url); + } +}); + +/* ===================================== */ +/* == Backbone View: ResourceAdd Tabs == */ +/* ===================================== */ +CKAN.View.ResourceAddTabs = Backbone.View.extend({ + initialize: function() { + _.bindAll(this, 'render', 'addNewResource', 'reset'); + }, + + render: function() { + }, + + events: { + 'click button': 'clickButton', + 'click input[name=reset]': 'reset' + }, + + reset: function() { + this.el.find('button').removeClass('depressed'); + if (this.subView != null) { + this.subView.remove(); + this.subView = null; + } + return false; + }, + + clickButton: function(e) { + e.preventDefault(); + var $target = $(e.target); + + if ($target.is('.depressed')) { + this.reset(); + } + else { + this.reset(); + $target.addClass('depressed'); + + var $subPane = $('
    ').addClass('subpane'); + this.el.append($subPane); + + var tempResource = new CKAN.Model.Resource({}); + + tempResource.bind('change', this.addNewResource); + // Open sub-pane + if ($target.is('.js-upload-file')) { + this.subView = new CKAN.View.ResourceUpload({ + el: $subPane, + model: tempResource, + // TODO: horrible reverse depedency ... + client: CKAN.UI.workspace.client + }); + } + else if ($target.is('.js-link-file') || $target.is('.js-link-api')) { + this.subView = new CKAN.View.ResourceAddLink({ + el: $subPane, + model: tempResource, + mode: ($target.is('.js-link-file'))? 'file' : 'api', + // TODO: horrible reverse depedency ... + client: CKAN.UI.workspace.client + }); + } + this.subView.render(); + } + }, + + addNewResource: function(tempResource) { + // Deep-copy the tempResource we had bound to + var resource=new CKAN.Model.Resource(tempResource.toJSON()); + + this.collection.add(resource); + this.reset(); + } +}); + +/* ================================================= */ +/* == Backbone View: ResourceAdd Link-To-Resource == */ +/* ================================================= */ +CKAN.View.ResourceAddLink = Backbone.View.extend({ + initialize: function(options) { + _.bindAll(this, 'render'); + this.mode = options.mode; + }, + + render: function() { + if (this.mode=='file') { + var tmpl = $.tmpl(CKAN.Templates.resourceAddLinkFile); + } + else if (this.mode=='api') { + var tmpl = $.tmpl(CKAN.Templates.resourceAddLinkApi); + } + $(this.el).html(tmpl); + return this; + }, + + events: { + 'submit form': 'setResourceInfo', + }, + + setResourceInfo: function(e) { + e.preventDefault(); + var urlVal=this.el.find('input[name=url]').val(); + this.model.set({url: urlVal, resource_type: this.mode}) + } +}); + + + +/* ================ */ +/* == CKAN.Utils == */ +/* ================ */ +CKAN.Utils = function($, my) { // Animate the appearance of an element by expanding its height my.animateHeight = function(element, animTime) { if (!animTime) animTime = 350; @@ -145,114 +808,18 @@ CKAN.Utils = function($, my) { } }; - my.setupUrlEditor = function(slugType,readOnly) { - // Page elements to hook onto - var titleInput = $('.js-title'); - var urlText = $('.js-url-text'); - var urlSuffix = $('.js-url-suffix'); - var urlInput = $('.js-url-input'); - var validMsg = $('.js-url-is-valid'); - - if (titleInput.length==0) throw "No titleInput found."; - if (urlText.length==0) throw "No urlText found."; - if (urlSuffix.length==0) throw "No urlSuffix found."; - if (urlInput.length==0) throw "No urlInput found."; - if (validMsg.length==0) throw "No validMsg found."; - - var api_url = CKAN.SITE_URL + '/api/2/util/is_slug_valid'; - // (make length less than max, in case we need a few for '_' chars to de-clash slugs.) - var MAX_SLUG_LENGTH = 90; - - var titleChanged = function() { - var lastTitle = ""; - var regexToHyphen = [ new RegExp('[ .:/_]', 'g'), - new RegExp('[^a-zA-Z0-9-_]', 'g'), - new RegExp('-+', 'g')]; - var regexToDelete = [ new RegExp('^-*', 'g'), - new RegExp('-*$', 'g')]; - - var titleToSlug = function(title) { - var slug = title; - $.each(regexToHyphen, function(idx,regex) { slug = slug.replace(regex, '-'); }); - $.each(regexToDelete, function(idx,regex) { slug = slug.replace(regex, ''); }); - slug = slug.toLowerCase(); - - if (slug.length'+CKAN.Strings.urlIsAvailable+''); - } else { - validMsg.html(''+CKAN.Strings.urlIsNotAvailable+''); - } - } - }); - } - - return function() { - slug = urlInput.val(); - urlSuffix.html(''+slug+''); - if (timer) clearTimeout(timer); - if (slug.length<2) { - validMsg.html(''+CKAN.Strings.urlIsTooShort+''); - } - else { - validMsg.html(''+CKAN.Strings.checking+''); - timer = setTimeout(function () { - checkSlugValid(slug); - }, 200); - } - }; - }(); - - if (readOnly) { - slug = urlInput.val(); - urlSuffix.html(''+slug+''); - } - else { - var editLink = $('.js-url-editlink'); - editLink.show(); - // Hook title changes to the input box - my.bindInputChanges(titleInput, titleChanged); - my.bindInputChanges(urlInput, urlChanged); - // Set up the form - urlChanged(); - - editLink.live('click',function(e) { - e.preventDefault(); - $('.js-url-viewmode').hide(); - $('.js-url-editmode').show(); - urlInput.select(); - urlInput.focus(); - }); - } - } + my.setupDatasetDeleteButton = function() { + var select = $('select.dataset-delete'); + select.attr('disabled','disabled'); + select.css({opacity: 0.3}); + $('button.dataset-delete').click(function(e) { + select.removeAttr('disabled'); + select.fadeTo('fast',1.0); + $(e.target).css({opacity:0}); + $(e.target).attr('disabled','disabled'); + return false; + }); + }; // Attach dataset autocompletion to provided elements // @@ -395,10 +962,9 @@ CKAN.Utils = function($, my) { input_box.attr('name', new_name) input_box.attr('id', new_name) - var capacity = $("input:radio[name=add-user-capacity]:checked").val(); parent_dd.before( '' + - '' + + '' + '
    ' + ui.item.label + '
    ' ); @@ -505,27 +1071,59 @@ CKAN.Utils = function($, my) { // the div if clicked my.setupNotesExtract = function() { var notes = $('#content div.notes'); - if(notes.find('p').length > 1){ - var extract = notes.children(':eq(0)'); - var remainder = notes.children(':gt(0)'); - notes.html($.tmpl(CKAN.Templates.notesField)); - notes.find('#notes-extract').html(extract); - notes.find('#notes-remainder').html(remainder); - notes.find('#notes-remainder').hide(); - notes.find('#notes-toggle a').click(function(event){ - notes.find('#notes-toggle a').toggle(); - var remainder = notes.find('#notes-remainder') - if ($(event.target).hasClass('more')) { - remainder.slideDown(); - } - else { - remainder.slideUp(); + var paragraphs = notes.find('#notes-extract > *'); + if (paragraphs.length==0) { + notes.hide(); + } + else if (paragraphs.length > 1) { + var remainder = notes.find('#notes-remainder'); + $.each(paragraphs,function(i,para) { + if (i > 0) remainder.append($(para).remove()); + }); + var finalHeight = remainder.height(); + remainder.height(0); + notes.find('#notes-toggle').show(); + notes.find('#notes-toggle button').click( + function(event){ + notes.find('#notes-toggle button').toggle(); + if ($(event.target).hasClass('more')) + remainder.animate({'height':finalHeight}); + else + remainder.animate({'height':0}); + return false; } - return false; - }) + ); } }; + my.warnOnFormChanges = function() { + var boundToUnload = false; + return function($form) { + var flashWarning = function() { + if (boundToUnload) return; + boundToUnload = true; + // Bind to the window departure event + window.onbeforeunload = function () { + return CKAN.Strings.youHaveUnsavedChanges; + }; + } + // Hook form modifications to flashWarning + $form.find('input,select').live('change', function(e) { + $target = $(e.target); + // Entering text in the 'add' box does not represent a change + if ($target.closest('.resource-add').length==0) { + flashWarning(); + } + }); + // Don't stop us leaving + $form.submit(function() { + window.onbeforeunload = null; + }); + // Calling functions might hook to flashWarning + return flashWarning; + }; + }(); + // Show/hide fieldset sections from the edit dataset form. my.setupDatasetEditNavigation = function() { @@ -544,6 +1142,7 @@ CKAN.Utils = function($, my) { // Adjust form state on click $('.dataset-edit-nav li a').live('click', function(e) { + e.preventDefault(); var $el = $(e.target); // Prefix="#section-" var showMe = $el.attr('href').slice(9); @@ -552,286 +1151,22 @@ CKAN.Utils = function($, my) { }); }; + my.countObject = function(obj) { + var count=0; + $.each(obj, function() { + count++; + }); + return count; + }; return my; }(jQuery, CKAN.Utils || {}); -CKAN.View.DatasetEditForm = Backbone.View.extend({ - initialize: function() { - var resources = this.model.get('resources'); - var $form = this.el; - - var changesMade = function() { - var boundToUnload = false; - return function() { - if (!boundToUnload) { - var parentDiv = $('
    ').addClass('flash-messages'); - var messageDiv = $('
    ').html(CKAN.Strings.youHaveUnsavedChanges).addClass('notice').hide(); - parentDiv.append(messageDiv); - $('#unsaved-warning').append(parentDiv); - messageDiv.show(200); - - boundToUnload = true; - window.onbeforeunload = function () { - return CKAN.Strings.youHaveUnsavedChanges; - }; - } - } - }(); - - $form.find('input,select').live('change', function(e) { - $target = $(e.target); - // Entering text in the 'add' box does not represent a change - if ($target.closest('.resource-add').length==0) { - changesMade(); - } - }); - resources.bind('add', changesMade); - resources.bind('remove', changesMade); - - $form.submit(function() { - // Don't stop us leaving - window.onbeforeunload = null; - }); - - // Table for editing resources - var $el = this.el.find('.js-resource-editor'); - this.resourceList=new CKAN.View.ResourceEditList({ - collection: resources, - el: $el - }); - - // Tabbed view for adding resources - var $el = this.el.find('.resource-add'); - this.addView=new CKAN.View.ResourceAddTabs({ - collection: resources, - el: $el - }); - - this.addView.render(); - this.resourceList.render(); - } -}); - - -CKAN.View.ResourceEditList = Backbone.View.extend({ - initialize: function() { - _.bindAll(this, 'addResource', 'removeResource'); - this.collection.bind('add', this.addResource); - this.collection.bind('remove', this.removeResource); - this.collection.each(this.addResource); - }, - - nextIndex: function() { - var maxId=-1; - this.el.find('input').each(function(idx,input) { - var splitName=$(input).attr('name').split('__'); - if (splitName.length>1) { - var myId = parseInt(splitName[1]) - maxId = Math.max(myId, maxId); - } - }); - return maxId+1; - }, - - addResource: function(resource) { - var position = this.nextIndex(); - // Create a row from the template - var $tr = $(''); - $tr.html($.tmpl( - CKAN.Templates.resourceEntry, - { resource: resource.toTemplateJSON(), - num: position, - resourceTypeOptions: [ - ['file', 'Data File'] - , ['api', 'API'] - , ['image', 'Image'] - , ['metadata', 'Metadata'] - , ['documentation', 'Documentation'] - , ['code', 'Code'] - , ['example', 'Example'] - ] - } - )); - $tr.find('.js-resource-edit-expanded').hide(); - this.el.append($tr); - resource.view_tr = $tr; - - // == Inner Function: Toggle the expanded options set == // - var toggleOpen = function(triggerEvent) { - if (triggerEvent) triggerEvent.preventDefault(); - var animTime = 350; - var expandedTable = $tr.find('.js-resource-edit-expanded'); - var finalHeight = expandedTable.height(); - var icon = 'closed'; - - if (expandedTable.is(':visible')) { - expandedTable.animate( - {height:0}, - animTime, - function() { - expandedTable.height(finalHeight); - expandedTable.hide(); - } - ); - } - else { - expandedTable.show(); - expandedTable.height(0); - // Transition to its true height - expandedTable.animate({height:finalHeight}, animTime); - $tr.find('.js-resource-edit-name').focus(); - icon = 'open'; - } - $tr.find('.js-resource-edit-toggle').css("background-image", "url('/images/icons/arrow-"+icon+".gif')"); - }; - - // == Inner Function: Delete the row == // - var collection = this.collection; - var deleteResource = function(triggerEvent) { - if (triggerEvent) triggerEvent.preventDefault(); - confirmMessage = CKAN.Strings.deleteThisResourceQuestion; - resourceName = resource.attributes.name || CKAN.Strings.noNameBrackets; - confirmMessage = confirmMessage.replace('%name%', resourceName); - if (confirm(confirmMessage)) { - collection.remove(resource); - } - }; - - // == Inner Functions: Update the name as you type == // - var setName = function(newName) { - $link = $tr.find('.js-resource-edit-toggle'); - newName = newName || (''+CKAN.Strings.noNameBrackets+''); - // Need to structurally modify the DOM to force a re-render of text - $link.html(''+newName+''); - }; - var nameBoxChanged = function(e) { - setName($(e.target).val()); - } - - // Trigger animation - if (resource.isNew()) { - toggleOpen(); - } - - var nameBox = $tr.find('input.js-resource-edit-name'); - CKAN.Utils.bindInputChanges(nameBox,nameBoxChanged); - - $tr.find('.js-resource-edit-toggle').click(toggleOpen); - $tr.find('.js-resource-edit-delete').click(deleteResource); - // Initialise name - setName(resource.attributes.name); - }, - - removeResource: function(resource) { - if (resource.view_tr) { - resource.view_tr.remove(); - delete resource.view_tr; - } - } -}); - - -CKAN.View.ResourceAddTabs = Backbone.View.extend({ - initialize: function() { - _.bindAll(this, 'render', 'addNewResource', 'reset'); - }, - - render: function() { - }, - - events: { - 'click button': 'clickButton', - 'click input[name=reset]': 'reset' - }, - - reset: function() { - this.el.find('button').removeClass('depressed'); - if (this.subView != null) { - this.subView.remove(); - this.subView = null; - } - return false; - }, - - clickButton: function(e) { - e.preventDefault(); - var $target = $(e.target); - - if ($target.is('.depressed')) { - this.reset(); - } - else { - this.reset(); - $target.addClass('depressed'); - - var $subPane = $('
    ').addClass('subpane'); - this.el.append($subPane); - - var tempResource = new CKAN.Model.Resource({}); - - tempResource.bind('change', this.addNewResource); - // Open sub-pane - if ($target.is('.js-upload-file')) { - this.subView = new CKAN.View.ResourceUpload({ - el: $subPane, - model: tempResource, - // TODO: horrible reverse depedency ... - client: CKAN.UI.workspace.client - }); - } - else if ($target.is('.js-link-file') || $target.is('.js-link-api')) { - this.subView = new CKAN.View.ResourceAddLink({ - el: $subPane, - model: tempResource, - mode: ($target.is('.js-link-file'))? 'file' : 'api', - // TODO: horrible reverse depedency ... - client: CKAN.UI.workspace.client - }); - } - this.subView.render(); - } - }, - - addNewResource: function(tempResource) { - // Deep-copy the tempResource we had bound to - var resource=new CKAN.Model.Resource(tempResource.toJSON()); - - this.collection.add(resource); - this.reset(); - } -}); -CKAN.View.ResourceAddLink = Backbone.View.extend({ - initialize: function(options) { - _.bindAll(this, 'render'); - this.mode = options.mode; - }, - - render: function() { - if (this.mode=='file') { - var tmpl = $.tmpl(CKAN.Templates.resourceAddLinkFile); - } - else if (this.mode=='api') { - var tmpl = $.tmpl(CKAN.Templates.resourceAddLinkApi); - } - $(this.el).html(tmpl); - return this; - }, - - events: { - 'submit form': 'setResourceInfo' - }, - - setResourceInfo: function(e) { - e.preventDefault(); - var urlVal=this.el.find('input[name=url]').val(); - this.model.set({url: urlVal, resource_type: this.mode}) - } -}); - -(function ($) { - var my = {}; +/* ==================== */ +/* == Data Previewer == */ +/* ==================== */ +CKAN.DataPreview = function ($, my) { my.jsonpdataproxyUrl = 'http://jsonpdataproxy.appspot.com/'; my.dialogId = 'ckanext-datapreview'; my.$dialog = $('#' + my.dialogId); @@ -1061,5 +1396,6 @@ CKAN.View.ResourceAddLink = Backbone.View.extend({ // Export the CKANEXT object onto the window. $.extend(true, window, {CKANEXT: {}}); CKANEXT.DATAPREVIEW = my; -}(jQuery)); + return my; +}(jQuery, CKAN.DataPreview || {}); diff --git a/ckan/public/scripts/templates.js b/ckan/public/scripts/templates.js index a4c33ce93a5..42d9ceb1ba5 100644 --- a/ckan/public/scripts/templates.js +++ b/ckan/public/scripts/templates.js @@ -65,103 +65,135 @@ CKAN.Templates.resourceUpload = ' \ CKAN.Templates.resourceEntry = ' \ - \ - ${resource.name}\ -
    \ +
  • \ + \ +
    |||
    \ + \ + ${resource.name}\ +
    \ +
  • '; + +CKAN.Templates.resourceDetails = ' \ + \ - \ - \ - \ - \ -'; - -CKAN.Templates.notesField = ' \ -
    \ -
    \ -
    \ + \ + \
    \ - \ '; +CKAN.Templates.resourceExtra = ' \ +
    \ + \ + \ + \ +
    \ + '; diff --git a/ckan/public/scripts/vendor/bootstrap/2.0.0/bootstrap.js b/ckan/public/scripts/vendor/bootstrap/2.0.0/bootstrap.js new file mode 100644 index 00000000000..c832ccb2e64 --- /dev/null +++ b/ckan/public/scripts/vendor/bootstrap/2.0.0/bootstrap.js @@ -0,0 +1,1722 @@ +/* =================================================== + * bootstrap-transition.js v2.0.0 + * http://twitter.github.com/bootstrap/javascript.html#transitions + * =================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + +!function( $ ) { + + $(function () { + + "use strict" + + /* CSS TRANSITION SUPPORT (https://gist.github.com/373874) + * ======================================================= */ + + $.support.transition = (function () { + var thisBody = document.body || document.documentElement + , thisStyle = thisBody.style + , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined + + return support && { + end: (function () { + var transitionEnd = "TransitionEnd" + if ( $.browser.webkit ) { + transitionEnd = "webkitTransitionEnd" + } else if ( $.browser.mozilla ) { + transitionEnd = "transitionend" + } else if ( $.browser.opera ) { + transitionEnd = "oTransitionEnd" + } + return transitionEnd + }()) + } + })() + + }) + +}( window.jQuery ) +/* ========================================================== + * bootstrap-alert.js v2.0.0 + * http://twitter.github.com/bootstrap/javascript.html#alerts + * ========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function( $ ){ + + "use strict" + + /* ALERT CLASS DEFINITION + * ====================== */ + + var dismiss = '[data-dismiss="alert"]' + , Alert = function ( el ) { + $(el).on('click', dismiss, this.close) + } + + Alert.prototype = { + + constructor: Alert + + , close: function ( e ) { + var $this = $(this) + , selector = $this.attr('data-target') + , $parent + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + $parent = $(selector) + $parent.trigger('close') + + e && e.preventDefault() + + $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) + + $parent.removeClass('in') + + function removeElement() { + $parent.remove() + $parent.trigger('closed') + } + + $.support.transition && $parent.hasClass('fade') ? + $parent.on($.support.transition.end, removeElement) : + removeElement() + } + + } + + + /* ALERT PLUGIN DEFINITION + * ======================= */ + + $.fn.alert = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('alert') + if (!data) $this.data('alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.alert.Constructor = Alert + + + /* ALERT DATA-API + * ============== */ + + $(function () { + $('body').on('click.alert.data-api', dismiss, Alert.prototype.close) + }) + +}( window.jQuery ) +/* ============================================================ + * bootstrap-button.js v2.0.0 + * http://twitter.github.com/bootstrap/javascript.html#buttons + * ============================================================ + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + +!function( $ ){ + + "use strict" + + /* BUTTON PUBLIC CLASS DEFINITION + * ============================== */ + + var Button = function ( element, options ) { + this.$element = $(element) + this.options = $.extend({}, $.fn.button.defaults, options) + } + + Button.prototype = { + + constructor: Button + + , setState: function ( state ) { + var d = 'disabled' + , $el = this.$element + , data = $el.data() + , val = $el.is('input') ? 'val' : 'html' + + state = state + 'Text' + data.resetText || $el.data('resetText', $el[val]()) + + $el[val](data[state] || this.options[state]) + + // push to event loop to allow forms to submit + setTimeout(function () { + state == 'loadingText' ? + $el.addClass(d).attr(d, d) : + $el.removeClass(d).removeAttr(d) + }, 0) + } + + , toggle: function () { + var $parent = this.$element.parent('[data-toggle="buttons-radio"]') + + $parent && $parent + .find('.active') + .removeClass('active') + + this.$element.toggleClass('active') + } + + } + + + /* BUTTON PLUGIN DEFINITION + * ======================== */ + + $.fn.button = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('button') + , options = typeof option == 'object' && option + if (!data) $this.data('button', (data = new Button(this, options))) + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + $.fn.button.defaults = { + loadingText: 'loading...' + } + + $.fn.button.Constructor = Button + + + /* BUTTON DATA-API + * =============== */ + + $(function () { + $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) { + $(e.target).button('toggle') + }) + }) + +}( window.jQuery ) +/* ========================================================== + * bootstrap-carousel.js v2.0.0 + * http://twitter.github.com/bootstrap/javascript.html#carousel + * ========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function( $ ){ + + "use strict" + + /* CAROUSEL CLASS DEFINITION + * ========================= */ + + var Carousel = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.carousel.defaults, options) + this.options.slide && this.slide(this.options.slide) + } + + Carousel.prototype = { + + cycle: function () { + this.interval = setInterval($.proxy(this.next, this), this.options.interval) + return this + } + + , to: function (pos) { + var $active = this.$element.find('.active') + , children = $active.parent().children() + , activePos = children.index($active) + , that = this + + if (pos > (children.length - 1) || pos < 0) return + + if (this.sliding) { + return this.$element.one('slid', function () { + that.to(pos) + }) + } + + if (activePos == pos) { + return this.pause().cycle() + } + + return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos])) + } + + , pause: function () { + clearInterval(this.interval) + return this + } + + , next: function () { + if (this.sliding) return + return this.slide('next') + } + + , prev: function () { + if (this.sliding) return + return this.slide('prev') + } + + , slide: function (type, next) { + var $active = this.$element.find('.active') + , $next = next || $active[type]() + , isCycling = this.interval + , direction = type == 'next' ? 'left' : 'right' + , fallback = type == 'next' ? 'first' : 'last' + , that = this + + this.sliding = true + + isCycling && this.pause() + + $next = $next.length ? $next : this.$element.find('.item')[fallback]() + + if (!$.support.transition && this.$element.hasClass('slide')) { + this.$element.trigger('slide') + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger('slid') + } else { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + this.$element.trigger('slide') + this.$element.one($.support.transition.end, function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { that.$element.trigger('slid') }, 0) + }) + } + + isCycling && this.cycle() + + return this + } + + } + + + /* CAROUSEL PLUGIN DEFINITION + * ========================== */ + + $.fn.carousel = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('carousel') + , options = typeof option == 'object' && option + if (!data) $this.data('carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (typeof option == 'string' || (option = options.slide)) data[option]() + else data.cycle() + }) + } + + $.fn.carousel.defaults = { + interval: 5000 + } + + $.fn.carousel.Constructor = Carousel + + + /* CAROUSEL DATA-API + * ================= */ + + $(function () { + $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) { + var $this = $(this), href + , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 + , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data()) + $target.carousel(options) + e.preventDefault() + }) + }) + +}( window.jQuery ) +/* ============================================================= + * bootstrap-collapse.js v2.0.0 + * http://twitter.github.com/bootstrap/javascript.html#collapse + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + +!function( $ ){ + + "use strict" + + var Collapse = function ( element, options ) { + this.$element = $(element) + this.options = $.extend({}, $.fn.collapse.defaults, options) + + if (this.options["parent"]) { + this.$parent = $(this.options["parent"]) + } + + this.options.toggle && this.toggle() + } + + Collapse.prototype = { + + constructor: Collapse + + , dimension: function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + , show: function () { + var dimension = this.dimension() + , scroll = $.camelCase(['scroll', dimension].join('-')) + , actives = this.$parent && this.$parent.find('.in') + , hasData + + if (actives && actives.length) { + hasData = actives.data('collapse') + actives.collapse('hide') + hasData || actives.data('collapse', null) + } + + this.$element[dimension](0) + this.transition('addClass', 'show', 'shown') + this.$element[dimension](this.$element[0][scroll]) + + } + + , hide: function () { + var dimension = this.dimension() + this.reset(this.$element[dimension]()) + this.transition('removeClass', 'hide', 'hidden') + this.$element[dimension](0) + } + + , reset: function ( size ) { + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + [dimension](size || 'auto') + [0].offsetWidth + + this.$element.addClass('collapse') + } + + , transition: function ( method, startEvent, completeEvent ) { + var that = this + , complete = function () { + if (startEvent == 'show') that.reset() + that.$element.trigger(completeEvent) + } + + this.$element + .trigger(startEvent) + [method]('in') + + $.support.transition && this.$element.hasClass('collapse') ? + this.$element.one($.support.transition.end, complete) : + complete() + } + + , toggle: function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + } + + /* COLLAPSIBLE PLUGIN DEFINITION + * ============================== */ + + $.fn.collapse = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('collapse') + , options = typeof option == 'object' && option + if (!data) $this.data('collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.collapse.defaults = { + toggle: true + } + + $.fn.collapse.Constructor = Collapse + + + /* COLLAPSIBLE DATA-API + * ==================== */ + + $(function () { + $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) { + var $this = $(this), href + , target = $this.attr('data-target') + || e.preventDefault() + || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 + , option = $(target).data('collapse') ? 'toggle' : $this.data() + $(target).collapse(option) + }) + }) + +}( window.jQuery ) +/* ============================================================ + * bootstrap-dropdown.js v2.0.0 + * http://twitter.github.com/bootstrap/javascript.html#dropdowns + * ============================================================ + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function( $ ){ + + "use strict" + + /* DROPDOWN CLASS DEFINITION + * ========================= */ + + var toggle = '[data-toggle="dropdown"]' + , Dropdown = function ( element ) { + var $el = $(element).on('click.dropdown.data-api', this.toggle) + $('html').on('click.dropdown.data-api', function () { + $el.parent().removeClass('open') + }) + } + + Dropdown.prototype = { + + constructor: Dropdown + + , toggle: function ( e ) { + var $this = $(this) + , selector = $this.attr('data-target') + , $parent + , isActive + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + $parent = $(selector) + $parent.length || ($parent = $this.parent()) + + isActive = $parent.hasClass('open') + + clearMenus() + !isActive && $parent.toggleClass('open') + + return false + } + + } + + function clearMenus() { + $(toggle).parent().removeClass('open') + } + + + /* DROPDOWN PLUGIN DEFINITION + * ========================== */ + + $.fn.dropdown = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('dropdown') + if (!data) $this.data('dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.dropdown.Constructor = Dropdown + + + /* APPLY TO STANDARD DROPDOWN ELEMENTS + * =================================== */ + + $(function () { + $('html').on('click.dropdown.data-api', clearMenus) + $('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) + }) + +}( window.jQuery ) +/* ========================================================= + * bootstrap-modal.js v2.0.0 + * http://twitter.github.com/bootstrap/javascript.html#modals + * ========================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + + +!function( $ ){ + + "use strict" + + /* MODAL CLASS DEFINITION + * ====================== */ + + var Modal = function ( content, options ) { + this.options = $.extend({}, $.fn.modal.defaults, options) + this.$element = $(content) + .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) + } + + Modal.prototype = { + + constructor: Modal + + , toggle: function () { + return this[!this.isShown ? 'show' : 'hide']() + } + + , show: function () { + var that = this + + if (this.isShown) return + + $('body').addClass('modal-open') + + this.isShown = true + this.$element.trigger('show') + + escape.call(this) + backdrop.call(this, function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + !that.$element.parent().length && that.$element.appendTo(document.body) //don't move modals dom position + + that.$element + .show() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + transition ? + that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : + that.$element.trigger('shown') + + }) + } + + , hide: function ( e ) { + e && e.preventDefault() + + if (!this.isShown) return + + var that = this + this.isShown = false + + $('body').removeClass('modal-open') + + escape.call(this) + + this.$element + .trigger('hide') + .removeClass('in') + + $.support.transition && this.$element.hasClass('fade') ? + hideWithTransition.call(this) : + hideModal.call(this) + } + + } + + + /* MODAL PRIVATE METHODS + * ===================== */ + + function hideWithTransition() { + var that = this + , timeout = setTimeout(function () { + that.$element.off($.support.transition.end) + hideModal.call(that) + }, 500) + + this.$element.one($.support.transition.end, function () { + clearTimeout(timeout) + hideModal.call(that) + }) + } + + function hideModal( that ) { + this.$element + .hide() + .trigger('hidden') + + backdrop.call(this) + } + + function backdrop( callback ) { + var that = this + , animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $('