diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..49c9f84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +settings_local.py +*.py[co] +*.sw[po] +*.pot +*.mo +pip-log.txt +build.py +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..1bab793 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +Markup +=== + +Firefox MarkUp is a [Django][Django]-based web application... +[Django]: http://www.djangoproject.com/ + +Getting Started +--- +### Python +You need Python 2.6. Also, you probably want to run this application in a +[virtualenv][virtualenv] environment + +[virtualenv]: http://pypi.python.org/pypi/virtualenv + +### Dependencies + +run + + easy_install pip + +followed by + + ./bootsrap.sh + + + +### Django + +South is used for migrations, so to sync the db, run + + manage.py syncdb + +and to run the migs + + manage.py migrate + + +Then run the invite-generation script. generate_invites.py takes two vars: number of invites to generate, and type of invites to generate. invite-types are 't' for translator, or 'c' for contributor. + +for 5 translator invites, run + manage.py generate_invites 5 t +for 10 contributor invites, run + manage.py generate_invites 10 c + + +For production environments, uncomment the following in settings: + # CACHE_BACKEND = 'caching.backends.memcached://localhost:11211?timeout=500' + + # SESSION_COOKIE_SECURE = True + # SESSION_COOKIE_HTTPONLY = True + +and set + REDIRECT_TO_SSL = True + diff --git a/ffdemo/markup/__init__.py b/ffdemo/markup/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ffdemo/markup/common.py b/ffdemo/markup/common.py new file mode 100644 index 0000000..9b1fe00 --- /dev/null +++ b/ffdemo/markup/common.py @@ -0,0 +1,82 @@ +from ffdemo.markup.models import Mark +from ffdemo.markup.models import Invitation +from ffdemo.utils import short_url +from datetime import datetime +import re + +def get_invite_from_code(c): + invite = None + try: + invite = Invitation.objects.get(invite_code=c) + except Invitation.DoesNotExist: + return None + return invite +def get_translated_marks(): + return + +# convenience method to unpack marks for request return +def decode_mark_objects(data): + if data: + all_marks = [] + for m in data: + # We need to decode the points obj simplified + decoded_points_obj = decode_points_obj(m.points_obj_simplified) + # Append to all marks + all_marks.append({'date_drawn': m.date_drawn.strftime("%a, %d %b %Y %I:%M:%S"), 'reference': m.reference, 'id': m.id, 'points_obj_simplified': decoded_points_obj, 'country_code': m.country_code, 'contributor': m.contributor, 'is_approved': m.is_approved}) + return all_marks + else: + return + return + +def save_new_mark_with_data(data): + # Remove whitespace from raw full points obj + stripped_points_obj_full = re.sub(r'\s', '', data['points_obj']) + # remove whitespace where not in extra_info (the contributor quote) + j = re.compile('^.*\"extra\_info"\:\"') + k = re.compile('\"extra\_info"\:\".*\"\,*.*$') + sec1 = j.search(data['points_obj_simplified']) + sec2 = k.search(data['points_obj_simplified']) + if sec1 and sec2: + stripped_sec1 = re.sub(r'\s','',sec1.group()) + stripped_sec2 = re.sub('"extra_info":"','',sec2.group()) + stripped_points_obj_simplified = stripped_sec1 + stripped_sec2 + else: + stripped_points_obj_simplified = re.sub(r'\s','',data['points_obj_simplified']) + # stripped_points_obj_simplified = re.sub(r'\s', '', data['points_obj_simplified']) + # Encode both + encoded_points_obj_full = stripped_points_obj_full.encode('base64', 'strict') + encoded_points_obj_simplified = stripped_points_obj_simplified.encode('base64', 'strict') + # New mark + new_mark = Mark.objects.create() + new_mark.points_obj = encoded_points_obj_full + new_mark.points_obj_simplified = encoded_points_obj_simplified + new_mark.reference = short_url.encode_url(new_mark.id) + if 'country_code' in data: + new_mark.country_code = data['country_code'] + invite = None + if 'invite' in data: + invite = get_invite_from_code(data['invite']) + if invite and 'contributor_locale' in data and len(data['contributor_locale'])>0: + new_mark.contributor_locale = data['contributor_locale'] + else: + pass + if invite and 'contributor' in data and len(data['contributor'])>0: + new_mark.contributor = data['contributor'] + else: + pass + else: + pass + + new_mark.save() + if invite: + invite.used_at = datetime.now() + invite.save() + # Catch errors + return new_mark.reference + +def decode_points_obj(obj): + returned_str = str(obj) + decoded_data = returned_str.decode('base64', 'strict') + return decoded_data + + diff --git a/ffdemo/markup/forms.py b/ffdemo/markup/forms.py new file mode 100644 index 0000000..4f5f6d5 --- /dev/null +++ b/ffdemo/markup/forms.py @@ -0,0 +1,12 @@ +from django.forms import ModelForm +from ffdemo.markup.models import Mark + +class MarkForm(ModelForm): + class Meta: + model = Mark + fields = ('points_obj', 'country_code') + + +class LoginForm(ModelForm): + class Meta: + model = Mark diff --git a/ffdemo/markup/management/__init__.py b/ffdemo/markup/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ffdemo/markup/management/commands/__init__.py b/ffdemo/markup/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ffdemo/markup/management/commands/generate_invites.py b/ffdemo/markup/management/commands/generate_invites.py new file mode 100644 index 0000000..c04d37f --- /dev/null +++ b/ffdemo/markup/management/commands/generate_invites.py @@ -0,0 +1,25 @@ +import os, sys +from django.db import models +from django.core.management.base import BaseCommand, CommandError +from django.conf import settings +from ffdemo.markup.models import Invitation + +class Command(BaseCommand): + args = '' + def handle(self, *args, **options): + if len(args) < 2: + raise CommandError('Requires number of invites to generate and type of invite') + num_invites = int(args[0]) + invite_type = args[1] + valid_values = [] + for choice_id, choice_label in settings.CONTRIBUTOR_TYPE_CHOICES: + valid_values += choice_id + if invite_type not in valid_values: + raise CommandError('Invite type must be in ', valid_values) + else: + print "generating ", num_invites, " ", invite_type, "invites" + for i in range(num_invites): + invite = Invitation(contributor_type=invite_type) + invite.save() + print "finished generating invites" + diff --git a/ffdemo/markup/migrations/0001_initial.py b/ffdemo/markup/migrations/0001_initial.py new file mode 100644 index 0000000..ea1953a --- /dev/null +++ b/ffdemo/markup/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Mark' + db.create_table('markup_mark', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('date_drawn', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('reference', self.gf('django.db.models.fields.CharField')(max_length=50, blank=True, unique=True)), + ('points_obj', self.gf('django.db.models.fields.TextField')(blank=True)), + ('points_obj_simplified', self.gf('django.db.models.fields.TextField')(blank=True)), + ('country_code', self.gf('django.db.models.fields.CharField')(max_length=2, blank=True)), + ('contributor_locale', self.gf('django.db.models.fields.CharField')(max_length=5, null=True, blank=True)), + ('contributor', self.gf('django.db.models.fields.CharField')(max_length=75, null=True, blank=True)), + ('flaggings', self.gf('django.db.models.fields.IntegerField')(default=0)), + ('is_approved', self.gf('django.db.models.fields.BooleanField')(default=False)) + )) + db.send_create_signal('markup', ['Mark']) + + # Adding model 'Invitation' + db.create_table('markup_invitation', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('invite_code', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=50, blank=True)), + ('contributor_type', self.gf('django.db.models.fields.CharField')(max_length=1)), + ('used_at', self.gf('django.db.models.fields.DateTimeField')(blank=True)) + )) + db.send_create_signal('markup', ['Invitation']) + + def backwards(self, orm): + + # Deleting model 'Mark' + db.delete_table('markup_mark') + # Deleting model 'Invitation' + db.delete_table('markup_invitation') + + models = { + 'markup.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'contributor_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invite_code': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'used_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'markup.mark': { + 'Meta': {'object_name': 'Mark'}, + 'contributor': ('django.db.models.fields.CharField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'contributor_locale': ('django.db.models.fields.CharField', [], {'max_length': '5', 'null': 'True', 'blank': 'True'}), + 'country_code': ('django.db.models.fields.CharField', [], {'max_length': '2', 'blank': 'True'}), + 'date_drawn': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'flaggings': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'points_obj': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'points_obj_simplified': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}) + } + } + + complete_apps = ['markup'] diff --git a/ffdemo/markup/migrations/0002_auto__add_unique_invitation_invite_code.py b/ffdemo/markup/migrations/0002_auto__add_unique_invitation_invite_code.py new file mode 100644 index 0000000..004be1f --- /dev/null +++ b/ffdemo/markup/migrations/0002_auto__add_unique_invitation_invite_code.py @@ -0,0 +1,50 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding unique constraint on 'Invitation', fields ['invite_code'] + db.create_unique('markup_invitation', ['invite_code']) + + # Adding index on 'Mark', fields ['reference'] + db.create_index('markup_mark', ['reference']) + + + def backwards(self, orm): + + # Removing index on 'Mark', fields ['reference'] + db.delete_index('markup_mark', ['reference']) + + # Removing unique constraint on 'Invitation', fields ['invite_code'] + db.delete_unique('markup_invitation', ['invite_code']) + + + models = { + 'markup.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'contributor_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invite_code': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), + 'used_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'markup.mark': { + 'Meta': {'object_name': 'Mark'}, + 'contributor': ('django.db.models.fields.CharField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'contributor_locale': ('django.db.models.fields.CharField', [], {'max_length': '5', 'null': 'True', 'blank': 'True'}), + 'country_code': ('django.db.models.fields.CharField', [], {'max_length': '2', 'blank': 'True'}), + 'date_drawn': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'flaggings': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'points_obj': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'points_obj_simplified': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'unique': 'True', 'max_length': '50', 'blank': 'True'}) + } + } + + complete_apps = ['markup'] diff --git a/ffdemo/markup/migrations/__init__.py b/ffdemo/markup/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ffdemo/markup/models.py b/ffdemo/markup/models.py new file mode 100644 index 0000000..91d050a --- /dev/null +++ b/ffdemo/markup/models.py @@ -0,0 +1,34 @@ +from django.db import models +from django.template.defaultfilters import slugify +import hashlib +import uuid +from datetime import datetime +from django.conf import settings + +class Mark(models.Model): + date_drawn = models.DateTimeField(auto_now_add=True) + reference = models.CharField(max_length=50, blank=True, db_index=True, unique=True) + points_obj = models.TextField(blank=True) + points_obj_simplified = models.TextField(blank=True) + country_code = models.CharField(max_length=2, blank=True) + flaggings = models.IntegerField(default=0) + is_approved = models.BooleanField(default=False) + # contributor attrs + contributor_locale = models.CharField(max_length=5, blank=True, null=True) + contributor = models.CharField(max_length=75, blank=True, null=True) + + def __unicode__(self): + return unicode(self.date_drawn) + +class Invitation(models.Model): + invite_code = models.SlugField(max_length=50, unique=True, db_index=True) + contributor_type = models.CharField(max_length=1, choices=settings.CONTRIBUTOR_TYPE_CHOICES) + used_at = models.DateTimeField(blank=True, null=True) + + def save(self): + myuuid = uuid.uuid1().hex + self.invite_code = slugify( hashlib.md5(myuuid + datetime.now().strftime("%Y%m%d%H%m%s") ).hexdigest()[:12] ) + super( Invitation, self ).save() + + def __unicode__(self): + return self.invite_code diff --git a/ffdemo/markup/requests.py b/ffdemo/markup/requests.py new file mode 100644 index 0000000..8ebddfc --- /dev/null +++ b/ffdemo/markup/requests.py @@ -0,0 +1,459 @@ +from django.http import HttpResponse +from ffdemo.markup.models import Mark +from ffdemo.markup import common +from django.utils import simplejson +from django.core import serializers +import datetime +from django.db.models import Q + +def get_translated_marks(request): + marks_to_be_dumped = None + dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None + response = {'success': False} + marks_to_be_dumped = Mark.objects.exclude(contributor_locale__isnull=True).order_by('id') + if marks_to_be_dumped: + all_marks = [] + for m in marks_to_be_dumped: + # We need to decode the points obj simplified + decoded_points_obj = common.decode_points_obj(m.points_obj_simplified) + # Append to all marks + all_marks.append({'date_drawn': m.date_drawn.strftime("%a, %d %b %Y %I:%M:%S"), 'reference': m.reference, 'id': m.id, 'points_obj_simplified': decoded_points_obj, 'country_code': m.country_code, 'is_approved': m.is_approved, 'contributor_locale': m.contributor_locale}) + response['success'] = True + response['marks'] = all_marks + else: + response['success'] = False + response['error'] = "No marks to be parsed" + + json_response = simplejson.dumps(response) + return HttpResponse(json_response,'application/javascript') + +def flag_mark(request): + response = {'success': False} + if 'reference' in request.POST and len(request.POST['reference']) > 0: + try: + mark = Mark.objects.get(reference=request.POST['reference']) + if mark.is_approved: + pass + else: + mark.flaggings += 1 + mark.save() + response['success'] = True + except Mark.DoesNotExist: + response['error'] = "Mark does not exist" + except Mark.MultipleObjectsReturned: + # should never [ever] happen, purely CYA + response['error'] = "Multiple marks returned" + else: + response['error'] = "No mark specified" + json_response = simplejson.dumps(response) + return HttpResponse(json_response, 'application/javascript') + +def init_viz_data(request): + # grab the last mark + # grab the first mark + response = {} + response['country_total_marks'] = '' + response['country_first_mark'] = '' + response['country_last_mark'] = '' + response['country_first_mark_at'] = '' + # for contributed marks + response['contributor_marks'] = [] + contributor_marks = common.decode_mark_objects(Mark.objects.exclude(contributor__isnull=True).order_by('id')) + response['contributor_marks'] = contributor_marks + all_marks = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True).order_by('id') + if 'country_code' in request.GET and len(request.GET['country_code']) > 0: + country_marks = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True,country_code=request.GET['country_code']).order_by('id') + if len(country_marks) > 0: + response['country_total_marks'] = country_marks.count() + response['country_first_mark'] = country_marks[0].reference + response['country_last_mark'] = country_marks[response['country_total_marks']-1].reference + response['country_first_mark_at'] = country_marks[0].date_drawn.strftime("%a, %d %b %Y %I:%M:%S") + else: + pass + else: + pass + + response['total_marks'] = all_marks.count() + response['last_mark'] = all_marks[response['total_marks']-1].reference + response['first_mark'] = all_marks[0].reference + response['first_mark_at'] = all_marks[0].date_drawn.strftime("%a, %d %b %Y %I:%M:%S") + response['total_countries'] = Mark.objects.values('country_code').distinct().count() + json_response = simplejson.dumps(response) + return HttpResponse(json_response, 'application/javascript') + + +def save_mark(request): + # Default response + response = {'success': False} + # Check for mandatory POST data + if 'points_obj' in request.POST and 'points_obj_simplified' in request.POST: + # Cosntruct mark data + mark_data = { 'points_obj': request.POST['points_obj'], 'points_obj_simplified': request.POST['points_obj_simplified'] } + if 'country_code' in request.POST: + mark_data['country_code'] = request.POST['country_code'] + if 'invite' in request.POST: + mark_data['invite'] = request.POST['invite'] + if 'contributor_locale' in request.POST: + mark_data['contributor_locale'] = request.POST['contributor_locale'] + else: + pass + if 'contributor' in request.POST: + mark_data['contributor'] = request.POST['contributor'] + else: + pass + else: + pass + + # Save new mark, handled by common.py + new_mark_reference = common.save_new_mark_with_data(mark_data) + # Successful response, returning new mark reference + response['success'] = True + response['mark_reference'] = new_mark_reference + else: + # Error response + response['success'] = False + response['error'] = 'missing data in POST request' + # Return response as json + json_response = simplejson.dumps(response) + return HttpResponse(json_response, 'application/javascript') + + +def delete_mark(request): + # Completely remove the mark + response = {'success': False} + if 'reference' in request.POST and len(request.POST['reference']) > 0: + try: + m = Mark.objects.get(reference=request.POST['reference']) + m.delete() + response['success'] = True + except Mark.DoesNotExist: + response['error'] = 'Mark does not exist' + else: + response['error'] = "No mark specified" + json_response = simplejson.dumps(response) + return HttpResponse(json_response, 'application/javascript') + + + +def approve_mark(request): + # Approve the mark // CHECK + response = {'success': False} + if 'reference' in request.POST and len(request.POST['reference']) > 0: + try: + m = Mark.objects.get(reference=request.POST['reference']) + should_approve = False + if request.POST['should_approve'] == "true": + should_approve = True + m.is_approved = should_approve + m.save() + response['success'] = True + except Mark.DoesNotExist: + response['error'] = 'Mark does not exist' + else: + response['error'] = "No mark specified" + json_response = simplejson.dumps(response) + return HttpResponse(json_response, 'application/javascript') + +def get_mark(request): + # Get mark by ID + mark = None + try: + mark = Mark.objects.get(reference=request.GET['mark_id']) + except Mark.DoesNotExist: + pass + except Mark.MultipleObjectsReturned: + pass + previous_marks = "" + next_marks = "" + # Decode simplified points data + decoded_points_obj = common.decode_points_obj(mark.points_obj_simplified) + # Return raw + return HttpResponse(decoded_points_obj, 'application/javascript') + + +def marks_by_offset(request): + # Parameters: + # offset: Integer - + # max: Integer - (defaults 15) + # country_code: String - filter by country-code + # + # returns json object including relevant marks with their attributes: id, reference string, points_obj, points_obj_simplified + dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None + response = {'success': False} + + marks_to_be_dumped = None + did_fail_get_marks = False + + # We've got an offset to play with + if 'offset' in request.GET: + # An offset requires a max value to be returned from this offset + if 'max' in request.GET: + offset = request.GET['offset'] + max = request.GET['max'] + # We can also filter by country code if need be + if 'country_code' in request.GET: + marks_to_be_dumped = Mark.objects.exclude.exclude(contributor_locale__isnull=False).filter(country_code=request.GET['country_code'])[offset:max] + else: + marks_to_be_dumped = Mark.objects.all()[offset:max] + else: + response['success'] = False + response['error'] = "Querying by offset also requires a 'max' POST var" + did_fail_get_marks = True + else: + # No special query parameters, query for all marks + marks_to_be_dumped = Mark.objects.exclude(contributor_locale__isnull=False) + # Check that we've got marks to dump + if not did_fail_get_marks: + if marks_to_be_dumped: + # Dump out + all_marks = [] + for m in marks_to_be_dumped: + # We need to decode the points obj simplified + decoded_points_obj = common.decode_points_obj(m.points_obj_simplified) + # Append to all marks + all_marks.append({'date_drawn': m.date_drawn.strftime("%a, %d %b %Y %I:%M:%S"), 'reference': m.reference, 'points_obj_simplified': decoded_points_obj, 'contributor': m.contributor, 'country_code': m.country_code}) + response['success'] = True + response['marks'] = all_marks + else: + # No marks to dump + response['success'] = False + response['error'] = "No marks to be parsed" + # Dump and return + json_response = simplejson.dumps(response, default=dthandler) + return HttpResponse(json_response, 'application/javascript') + + + +def marks_by_locale(request): + # Parameters: + # country_code: String - filter by country-code + # max: Integer - (defaults 15) + # + # returns json object including relevant marks with their attributes: id, reference string, points_obj, points_obj_simplified + dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None + response = {'success': False} + + max_returned = 15 + + marks_to_be_dumped = None + did_fail_get_marks = False + + +def marks_by_reference(request): + # Parameters: + # reference_mark: String - slug of reference mark + # include_back: Integer - number of returned marks before the reference mark (defaults to 0) + # include_forward: Integer - number of returned marks after the reference mark (defaults to 15) + # include_mark: Boolean - include the reference mark (defaults true) + # country_code: String - filter by country-code + # returns json object including relevant marks with their attributes: id, reference string, points_obj, points_obj_simplified + dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None + response = {'success': False} + reference_mark = None + include_back = 0 + include_forward = 15 + include_mark = True + country_code = None + marks_to_be_dumped = None + did_fail_get_marks = False + m_offset = None + # default limit returns 19 marks + m_limit = include_back + 1 + include_forward + all_marks = None + offset_index = 0 + total_marks = 0 + + if 'reference' in request.GET: + reference_mark = request.GET['reference'] + try: + m_offset = Mark.objects.get(reference=reference_mark) + except Mark.DoesNotExist: + response['success'] = False + response['error'] = "Reference mark doesn't exist" + did_fail_get_marks = True + except Mark.MultipleObjectsReturned: + response['success'] = False + response['error'] = "Multiple marks found for reference" + did_fail_get_marks = True + if 'include_mark' in request.GET: + if int(request.GET['include_mark']) == 0: + include_mark = False + + + if 'include_forward' in request.GET: + include_forward = int(request.GET['include_forward']) + if 'include_back' in request.GET: + include_back = int(request.GET['include_back']) + + + + if 'country_code' in request.GET: + kountry_code = request.GET['country_code'] + all_marks = Mark.objects.exclude(flaggings__gte=1).filter(country_code=kountry_code,contributor_locale__isnull=True).order_by('id') + total_marks = all_marks.count() + for i, item in enumerate(all_marks): + if item.reference == reference_mark: + offset_index = i + break + relative_include_back = offset_index - include_back + if relative_include_back < 0: relative_include_back = 0 + unflagged_marks = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True) + if len(unflagged_marks) > 0: + marks_to_be_dumped = unflagged_marks.exclude(flaggings__gte=1,contributor_locale__isnull=False).filter(country_code=kountry_code,contributor_locale__isnull=True).order_by('id')[relative_include_back:offset_index+include_forward] + else: + response['success'] = False + response['error'] = "No marks to be dumped" + did_fail_get_marks = True + else: + all_marks = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True).order_by('id') + total_marks = all_marks.count() + for i, item in enumerate(all_marks): + if item.reference == reference_mark: + offset_index = i + break + relative_include_back = offset_index - include_back + if relative_include_back < 0: relative_include_back = 0 + try: + marks_to_be_dumped = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True).order_by('id')[relative_include_back:offset_index+include_forward] + except Mark.DoesNotExist: + response['success'] = False + response['error'] = "No marks to be dumped" + did_fail_get_marks = True + + else: + # required param + response['success'] = False + response['error'] = "Querying by reference requires a reference string" + did_fail_get_marks = True + + # Check that we've got marks to dump + if not did_fail_get_marks: + if marks_to_be_dumped: + # Dump out + all_marks = [] + + for m in marks_to_be_dumped: + is_reference_mark = False + if m.reference == reference_mark: + is_reference_mark = True + if include_mark == False and is_reference_mark: + pass + else: + # We need to decode the points obj simplified + decoded_points_obj = common.decode_points_obj(m.points_obj_simplified) + # Append to all marks + all_marks.append({'is_reference_mark': is_reference_mark,'date_drawn': m.date_drawn.strftime("%a, %d %b %Y %I:%M:%S"), 'reference': m.reference, 'id': m.id, 'points_obj_simplified': decoded_points_obj, 'contributor': m.contributor, 'country_code': m.country_code}) + response['success'] = True + response['marks'] = all_marks + else: + # No marks to dump + response['success'] = False + response['error'] = "No marks to be parsed" + + # Dump and return + json_response = simplejson.dumps(response, default=dthandler) + return HttpResponse(json_response, 'application/javascript') + + +def all_marks(request): + # Get all marks, all data per mark excluding full points object + # This method can be queried via POST depending what the frontend requires + # Handler for dumping datetime field as JSON + # + # offset: Integer - + # max: Integer - (defaults 15) + # country_code: String - filter by country-code + # + # returns json object including relevant marks with their attributes: id, reference string, points_obj, points_obj_simplified + + dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None + response = {'success': False} + + include_back = 3 + include_forward = 15 + include_mark = True + max_returned = 15 + + marks_to_be_dumped = None + did_fail_get_marks = False + + # We've got an offset to play with + if 'offset' in request.GET: + # An offset requires a max value to be returned from this offset + if 'max' in request.GET: + offset = request.GET['offset'] + max = request.GET['max'] + # We can also filter by country code if need be + if 'country_code' in request.GET: + marks_to_be_dumped = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True).order_by('id').filter(country_code=request.GET['country_code'])[offset:max] + else: + marks_to_be_dumped = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True).order_by('id')[offset:max] + else: + response['success'] = False + response['error'] = "Querying by offset also requires a 'max' POST var" + did_fail_get_marks = True + else: + # We can also filter by country code here as well if need be + if 'country_code' in request.GET: + marks_to_be_dumped = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True,country_code=request.GET['country_code']) + else: + # No special query parameters, query for all marks + marks_to_be_dumped = Mark.objects.exclude(flaggings__gte=1).filter(contributor_locale__isnull=True) + # Check that we've got marks to dump + if not did_fail_get_marks: + if marks_to_be_dumped: + # Dump out + all_marks = [] + for m in marks_to_be_dumped: + # We need to decode the points obj simplified + decoded_points_obj = common.decode_points_obj(m.points_obj_simplified) + # Append to all marks + all_marks.append({'date_drawn': m.date_drawn.strftime("%a, %d %b %Y %I:%M:%S"), 'reference': m.reference, 'id': m.id, 'points_obj_simplified': decoded_points_obj, 'contributor': m.contributor, 'country_code': m.country_code, 'flaggings': m.flaggings}) + response['success'] = True + response['marks'] = all_marks + else: + # No marks to dump + response['success'] = False + response['error'] = "No marks to be parsed" + # Dump and return + json_response = simplejson.dumps(response, default=dthandler) + return HttpResponse(json_response, 'application/javascript') + + +def marks_by_flagged(request): + dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.datetime) else None + response = {'success': False} + + include_back = 3 + include_forward = 15 + include_mark = True + max_returned = 15 + + marks_to_be_dumped = Mark.objects.filter(flaggings__gte=1).order_by('id') # CHECK offset? + + # Check that we've got marks to dump + if marks_to_be_dumped: + # Dump out + all_marks = [] + for m in marks_to_be_dumped: + # We need to decode the points obj simplified + decoded_points_obj = common.decode_points_obj(m.points_obj_simplified) + # Append to all marks + all_marks.append({'date_drawn': m.date_drawn, 'reference': m.reference, 'id': m.id, 'points_obj_simplified': decoded_points_obj, 'contributor': m.contributor, 'country_code': m.country_code, 'is_approved': m.is_approved}) + response['success'] = True + response['marks'] = all_marks + else: + # No marks to dump + response['success'] = False + response['error'] = "No marks to be parsed" + # Dump and return + json_response = simplejson.dumps(response, default=dthandler) + return HttpResponse(json_response, 'application/javascript') + +def update_language(request): + response = {} + if 'language_code' in request.GET: + set_language(request) + else: + pass + return response diff --git a/ffdemo/markup/templatetags/__init__.py b/ffdemo/markup/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ffdemo/markup/templatetags/gml_extras.py b/ffdemo/markup/templatetags/gml_extras.py new file mode 100644 index 0000000..975c9ed --- /dev/null +++ b/ffdemo/markup/templatetags/gml_extras.py @@ -0,0 +1,8 @@ +from django import template + +register = template.Library() + +@register.filter(name='normalize_point') +def normalize_point(value,arg): + ret_val = float(value)/arg + return str(round(ret_val, 2)).replace(',','.') diff --git a/ffdemo/markup/views.py b/ffdemo/markup/views.py new file mode 100644 index 0000000..34994d0 --- /dev/null +++ b/ffdemo/markup/views.py @@ -0,0 +1,119 @@ +import json +import string +from django.http import Http404, HttpResponse, HttpResponseRedirect +from django.utils.encoding import force_unicode +from django.utils import translation +from django.utils.translation import ugettext_lazy as _lazy +from django.utils.translation import ugettext as _ +from django.utils import simplejson +from django.conf import settings +from django.contrib.sites.models import Site +from django.template.loader import get_template +from django.contrib.auth.decorators import login_required +from django.contrib.auth import authenticate, login, logout +from django.views.decorators.cache import cache_page +from django.core.context_processors import csrf +from ffdemo.utils.render import render_response +from ffdemo.markup.models import Mark +from ffdemo.markup.forms import MarkForm +from django.shortcuts import get_object_or_404 +from ffdemo.markup import common + +@cache_page(15) # cache for 15 seconds +def home(request): + mr = settings.MEDIA_ROOT + return render_response(request, 'home.html', {'mr':mr}) + +@cache_page(60 * 30) # cache for 30 minutes +def about(request): + return render_response(request, 'about.html') + +@cache_page(60 * 30) +def about_gml(request): + return render_response(request, 'gml.html') + +@cache_page(60 * 30) +def credits(request): + return render_response(request, 'credits.html') + +@cache_page(60 * 30) +def code(request): + return render_response(request, 'code.html') + +@cache_page(60 * 30) +def mozilla(request): + return render_response(request, 'mozilla.html') + +@cache_page(60 * 30) +def evan(request): + return render_response(request, 'evan-roth.html') + +def gml(request): + mark = Mark.objects.all()[10] + obj_decoded = simplejson.loads(common.decode_points_obj(mark.points_obj_simplified)) + context = { 'mark': mark, 'obj_decoded': obj_decoded } + return render_response(request, 'gml.xml', context, mimetype='application/xml') + +@cache_page(60 * 30) +def manifesto(request): + return render_response(request, 'manifesto.html') + +def mark(request, mark_reference): + mark = get_object_or_404(Mark, reference=mark_reference) + return render_response(request, 'mark.html', {'mark': mark}) + +def makemark(request): + if request.method == "POST": + mark_form = MarkForm(request.POST) + if mark_form.is_valid(): + mark_data = { 'points_obj': mark_form.cleaned_data['points_obj'], 'country_code': mark_form.cleaned_data['country_code'] } + common.save_new_mark_with_data(mark_data) + else: + mark_form = MarkForm() + return render_response(request, 'makemark.html', {'form': mark_form}) + +@cache_page(30) +def community(request): + if 'offset' in request.GET: + offset = int(request.GET['offset']) + per_page = int(request.GET['per_page']) + top_limit = offset + per_page; + all_marks = Mark.objects.exclude(flaggings__gte=1)[offset:top_limit] + else: + all_marks = Mark.objects.exclude(flaggings__gte=1) + return render_response(request, 'community.html', {'all_marks': all_marks}) + +@cache_page(60 * 30) +def newsletter(request): + return render_response(request, 'newsletter.html', {}) + +@cache_page(60 * 30) +def home_sammy(request): + mr = settings.MEDIA_ROOT + return render_response(request,'sammy/home.html',{'mr':mr}) + +@cache_page(15) +def linear_sammy(request): + return render_response(request, 'sammy/linear.html') + +def mark_sammy(request): + mark = get_object_or_404(Mark, reference=mark_reference) + return render_response(request, 'sammy/mark.html', {'mark':mark}) + +def makemark_sammy(request): + mark_form = MarkForm() + return render_response(request, 'sammy/makemark.html', {'form': mark_form}) + + +### MODERATION VIEWS +def account_locked(request): + return render_response(request, 'registration/locked.html') + + + + +def moderate_sammy(request): + if not request.user.is_authenticated(): + return HttpResponseRedirect('/accounts/login/') + else: + return render_response(request, 'sammy/moderate.html') diff --git a/ffdemo/middleware.py b/ffdemo/middleware.py new file mode 100644 index 0000000..82e7cb0 --- /dev/null +++ b/ffdemo/middleware.py @@ -0,0 +1,72 @@ +from django.db import connection +from django.conf import settings +from django.utils.encoding import * +from urlparse import urlparse + +import re +from django import http +import django.core.exceptions +from django.core import urlresolvers +from django.http import HttpResponseRedirect +from django.utils import translation +import localeurl +from localeurl import utils + +from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect, get_host + +class SQLLogMiddleware: + def process_response(self, request, response): + if settings.DEV and connection.queries: + time = sum([float(q['time']) for q in connection.queries]) + return response + +class DetectReferrer: + def process_request(self, request): + if not request.session.get('HTTP_REFERER', False): + if(request.META.get('HTTP_REFERER')): + ref = urlparse(request.META.get('HTTP_REFERER')) + request.session['HTTP_REFERER'] = ref.hostname + + + +# SSL Middleware +# via: http://djangosnippets.org/snippets/85/ + +# __license__ = "Python" +# __copyright__ = "Copyright (C) 2007, Stephen Zabel" +# __author__ = "Stephen Zabel - sjzabel@gmail.com" +# __contributors__ = "Jay Parlar - parlar@gmail.com" + +SSL = 'SSL' + +class SSLRedirect: + + def process_view(self, request, view_func, view_args, view_kwargs): + if SSL in view_kwargs: + secure = view_kwargs[SSL] + del view_kwargs[SSL] + else: + secure = False + + if not secure == self._is_secure(request): + return self._redirect(request, secure) + + def _is_secure(self, request): + if request.is_secure(): + return True + + #Handle the Webfaction case until this gets resolved in the request.is_secure() + if 'HTTP_X_FORWARDED_SSL' in request.META: + return request.META['HTTP_X_FORWARDED_SSL'] == 'on' + + return False + + def _redirect(self, request, secure): + protocol = secure and "https" or "http" + newurl = "%s://%s%s" % (protocol,get_host(request),request.get_full_path()) + if settings.DEBUG and request.method == 'POST': + raise RuntimeError, \ + """Django can't perform a SSL redirect while maintaining POST data. + Please structure your views so that redirects only occur during GETs.""" + + return HttpResponsePermanentRedirect(newurl) diff --git a/ffdemo/settings.py b/ffdemo/settings.py new file mode 100644 index 0000000..3734e4c --- /dev/null +++ b/ffdemo/settings.py @@ -0,0 +1,207 @@ +import settings_local +import os +import re + +# Django settings for ff4 project. + +DEBUG = settings_local.DEBUG +DEV = settings_local.DEV +TEMPLATE_DEBUG = DEBUG + +PROJECT_PATH = os.path.realpath(os.path.dirname(__file__)) +PROJECT_DOMAIN = '' +PROJECT_DIR = os.path.realpath(os.path.dirname(__file__)) + + + +# UNCOMMENT TO ENABLE SECURE SESSIONS +# SESSION_COOKIE_SECURE = True +# SESSION_COOKIE_HTTPONLY = True +# SESSION_COOKIE_DOMAIN = None + +# global for enabling SSL redirection of admin views +# set True for properly configured production +REDIRECT_TO_SSL = False + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +# Memcached! +# CACHE_BACKEND = 'caching.backends.memcached://localhost:11211?timeout=500' + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': settings_local.DB_NAME, # Or path to database file if using sqlite3. + 'USER': settings_local.DB_USER, # Not used with sqlite3. + 'PASSWORD': settings_local.DB_PASSWORD, # Not used with sqlite3. + 'HOST': settings_local.DB_HOST, # Set to empty string for localhost. Not used with sqlite3. + 'PORT': settings_local.DB_PORT, # Set to empty string for default. Not used with sqlite3. + } +} + + + + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/New_York' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-US' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale +USE_L10N = True + +# Accepted locales +#INPUT_LANGUAGES = ('ar', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'en-US', 'es', +# 'fr', 'fy-NL', 'gl', 'he', 'hu', 'id', 'it', 'ko', 'nb-NO', +# 'nl', 'pl', 'pt-PT', 'ro', 'ru', 'sk', 'sq', 'uk', 'vi', +# 'zh-CN', 'zh-TW') + +gettext = lambda s: s +LANGUAGES = ( + ('de', gettext('German')), + ('en', gettext('English')), + ('fr', gettext('French')), + ('ru', gettext('Russian')), +) +# default to accept-language header, per localeurl's settings +LOCALEURL_USE_ACCEPT_LANGUAGE = True + +# don't url-localize requests + +LOCALE_INDEPENDENT_PATHS = ( + re.compile('requests/'), + re.compile('/accounts/login/$'), + re.compile('/accounts/logout/$'), + re.compile('/i18n/'), +) + +#RTL_LANGUAGES = ('ar', 'he',) # ('fa', 'fa-IR') +# Fallbacks for locales that are not recognized by Babel. Bug 596981. +BABEL_FALLBACK = {'fy-nl': 'nl'} + + + + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = PROJECT_PATH+'/static/' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = '/media/' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/media/admin/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = 'priU+iaciut#uV&aphlADo#zlep?i!rlethiu-wOuslapr2eSp' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + #'ffdemo.jinja.Loader', + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'localeurl.middleware.LocaleURLMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'axes.middleware.FailedLoginMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + #'django.middleware.locale.LocaleMiddleware', + 'ffdemo.middleware.SQLLogMiddleware', + 'ffdemo.middleware.SSLRedirect', +) + +# ADMIN +CONTRIBUTOR_TYPE_CHOICES = ( + ('c','contributor'), + ('t','translator'), +) +LOGIN_REDIRECT_URL = "/#/moderate" + +# AXES SEC. CONFIG +AXES_LOGIN_FAILURE_LIMIT = 5 +AXES_LOCK_OUT_AT_FAILURE = True +AXES_COOLOFF_TIME = 2 +AXES_LOCKOUT_URL = "/auth/locked/" + +ROOT_URLCONF = 'ffdemo.urls' + +TEMPLATE_DIRS = ( + PROJECT_PATH+'/templates_orig', + PROJECT_PATH+'/templates_orig/sammy', +) + +# JINJA_TEMPLATE_DIRS = ( +# PROJECT_PATH+'/templates', +# ) + +def JINJA_CONFIG(): + import jinja2 + config = {'extensions': ['jinja2.ext.loopcontrols', + 'jinja2.ext.with_', 'caching.ext.cache'], + 'finalize': lambda x: x if x is not None else ''} + return config + + +TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.core.context_processors.request', + 'django.core.context_processors.i18n', + 'django.contrib.auth.context_processors.auth', +) + +SERIALIZATION_MODULES = { + 'yml': "django.core.serializers.pyyaml" +} + +INSTALLED_APPS = ( + 'localeurl', + 'ffdemo.markup', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'south', + 'axes', +) + +FIXTURE_DIRS = ( + PROJECT_PATH+'/fixtures/', +) +SOUTH_TESTS_MIGRATE = False + +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = settings_local.EMAIL_HOST +EMAIL_PORT = settings_local.EMAIL_PORT +EMAIL_HOST_USER = settings_local.EMAIL_HOST_USER +EMAIL_HOST_PASSWORD = settings_local.EMAIL_HOST_PASSWORD +EMAIL_USE_TLS = settings_local.EMAIL_USE_TLS + diff --git a/ffdemo/static/Jimfile b/ffdemo/static/Jimfile new file mode 100644 index 0000000..bfcf580 --- /dev/null +++ b/ffdemo/static/Jimfile @@ -0,0 +1,34 @@ +{ + "vendor_dir": "assets/js/vendor", + "compressed_path": "assets/js/compressed.js", + "bundle_dir": "assets/js", + "bundles": { + "markApp": [ + "jquery.markApp", + "jquery.markApp.linear", + "jquery.markApp.capture", + "jquery.markApp.intro", + "jquery.delayedBind", + "jquery.collapsibleMod", + "jquery.socialShare", + "jquery.ui-selectBox", + "json2", + "canvas_extensions", + "mark.layer", + "mark.layerManager", + "mark.gmlPoint", + "mark.simplifyPath", + "mark.brushes", + "mark.gmlMark", + "mark.scene", + "mark.renderer", + "mark.camera", + "mark.base", + "tween", + "sammy-0.6.3", + "sammy.hash_push_proxy", + "sammy.template" + ] + }, + "compressed_suffix": ".min" +} \ No newline at end of file diff --git a/ffdemo/static/assets/css/style.css b/ffdemo/static/assets/css/style.css new file mode 100644 index 0000000..72a4f9e --- /dev/null +++ b/ffdemo/static/assets/css/style.css @@ -0,0 +1,1212 @@ +html, +body { + margin: 0; + padding: 0; + background-color: #333; + font-family: Helvetica, Arial, sans; + font-size: 67.5%; + color: #222222; + background-color: #2f2f30; + background-image: url("/media/assets/images/footer_bg.gif"); + background-position: 50% 0; + background-repeat: repeat; +} +body { + overflow-x: hidden; +} +section, +header, +footer, +canvas, +nav { + display: block; +} +a { + text-decoration: none; + outline: none; +} + +a img +{ + border: none; +} +/* SelectBox styles */ +.ui-selectBox { + position: relative; + width: 188px; /* 188px + 35px (padding-right) + 2px (for borders) == 225px wide */ + border: solid 1px #BBB; + text-decoration: none; + color: #000; + outline: none; + vertical-align: middle; + background: #F2F2F2; + background: -moz-linear-gradient(top, #F8F8F8 1%, #E1E1E1 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(1%, #F8F8F8), color-stop(100%, #E1E1E1)); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#F8F8F8', endColorstr='#E1E1E1', GradientType=0); + -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, .75); + -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, .75); + box-shadow: 0 1px 0 rgba(255, 255, 255, .75); + padding-right: 35px; + display: inline-block; + cursor: default; +} + +.ui-selectBox-disabled, +.ui-selectBox-disabled .ui-selectBox-arrow { + color: #888 !important; + border-color: #BBB !important; + font-style: italic !important; +} + +.ui-selectBox:hover, +.ui-selectBox-focus, +.ui-selectBox:hover .ui-selectBox-arrow, +.ui-selectBox-focus .ui-selectBox-arrow { + border-color: #666; +} + +.ui-selectBox-label { + width: 100%; + padding: .2em .3em; + display: inline-block; + white-space: nowrap; + overflow: hidden; +} + +.ui-selectBox-arrow { + position: absolute; + top: 0; + right: 0; + width: 23px; + height: 100%; + background: url(jquery.ui-selectBox-arrow.gif) 50% center no-repeat; + border-left: solid 1px #BBB; +} + +/* Dropdown styles */ +#ui-selectBox-dropdown { + max-height: 200px; + border: solid 1px #BBB; + background: #FFF; + margin-top: -1px; + overflow: auto; + -moz-box-shadow: 0 2px 6px rgba(0, 0, 0, .2); + -webkit-box-shadow: 0 2px 6px rgba(0, 0, 0, .2); + box-shadow: 0 2px 6px rgba(0, 0, 0, .2); +} + +#ui-selectBox-dropdown UL, +#ui-selectBox-dropdown UL LI { + list-style: none; + padding: 0; + margin: 0; +} + +#ui-selectBox-dropdown UL LI { + white-space: nowrap; + overflow: hidden; + padding: .2em .6em .2em 20px; + display: block; + cursor: default; +} + +#ui-selectBox-dropdown .ui-selectBox-optgroup { + background: #F2F2F2; + color: #888; + padding-left: .6em; +} + +#ui-selectBox-dropdown.ui-selectBox-hasOptgroups .ui-selectBox-option { + padding-left: 20px; + background-position: 14px center; +} + +#ui-selectBox-dropdown .ui-selectBox-option.ui-selectBox-initial { + background-image: url(jquery.ui-selectBox-tick.gif); + background-position: 4px center; + background-repeat: no-repeat; +} + +#ui-selectBox-dropdown .ui-selectBox-current { + background-color: #C8DEF4; +} + +#ui-selectBox-dropdown .ui-selectBox-disabled { + color: #888; + font-style: italic; +} + + +/* The following can be removed if you already have a jQuery UI theme loaded */ +.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; } +.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } +.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } +.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } +.ui-selectBox +{ + width: 200px; + background: #fff; + border: #bbb 1px solid; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + -moz-box-shadow: none; + -webkit-box-shadow: none; + padding: 3px 0 3px 7px; + line-height: 10px; + background: url('/media/assets/images/location_selector_dropdown_bg.gif') 100% 50% no-repeat #ffffff; +} +.ui-selectBox:hover, +.ui-selectBox-focus, +.ui-selectBox:hover .ui-selectBox-arrow, +.ui-selectBox-focus .ui-selectBox-arrow +{ + border-color: #bbb; +} +.ui-selectBox .ui-selectBox-label +{ + font-size: 10px; + line-height: 10px; + color: #6e6e6e; + padding: 4px 0 0 5px; +} +.ui-selectBox-arrow +{ + display: none; + border-color: #bbb; +} +#ui-selectBox-dropdown ul li +{ + font-size: 10px; + margin: 0; + padding: 2px 0 2px 9px; + color: #6e6e6e; +} + +#mark-browsing-options .ui-selectBox +{ + width: 137px; + padding: 0; + border: #e4e4e4 1px solid; +} + +#mark-browsing-options .ui-selectBox:hover, +#mark-browsing-options .ui-selectBox-focus, +#mark-browsing-options .ui-selectBox:hover .ui-selectBox-arrow, +#mark-browsing-options .ui-selectBox-focus .ui-selectBox-arrow +{ + border-color: #e4e4e4; +} + +#mark-browsing-options .ui-selectBox-arrow +{ + border-color: #e4e4e4; +} + +#mark-browsing-options .ui-selectBox-label +{ + padding: 8px 0 4px 6px; + display: block; + font-size: 10px; + line-height: 10px; +} + +/* End Select Box Styles */ + +#main-links, +#main-navigation, +#legal, +#callout-boxes { + +} +header { + background-color: #000000; + border-top: #717171 2px solid; +} +#main-links { + width: 970px; + margin: 0 auto; + position: relative; +} +#main-links #mozilla-logo { + position: absolute; + top: 0; + right: 0; + width: 118px; + height: 47px; + background: url('/media/assets/images/mozilla_tab.png') top right no-repeat; + padding: 0; + margin: 0; + text-indent: -9000em; +} +header h1 { + margin: 0; + float: left; + font-size: 3em; +} +header h1 a { + color: #F66C1E; + background-image: url(/media/assets/images/mark_up_logo.jpg); + background-position: 0 0; + background-repeat: no-repeat; + text-transform: uppercase; + text-indent: -99999px; + display: block; + width: 425px; + height: 62px; +} +header h2 +{ + margin: 0; + padding: 0; +} +#main-navigation { + position: absolute; + z-index: 1000; + width: 100%; +} +#main-navigation nav { + position: relative; + width: 970px; + margin: 0 auto; + color: #000000; +} + +#main-navigation nav ul { + list-style: none; + margin: 0; + padding: 0; +} +#main-navigation nav ul li { + margin: 0; + float: left; + background-color: #fff; + opacity: 0.9; + filter:alpha(opacity=90) +} +#main-navigation nav ul li.disabled { + color: #cccccc; + font-weight: bold; + text-transform: uppercase; + font-size: 1.4em; + line-height: 1.4em; + padding: 5px 14px 6px 14px; +} +#main-navigation nav ul li#community +{ + cursor: pointer; +} +#main-navigation nav ul li a { + display: block; + color: #000000; + font-weight: bold; + text-transform: uppercase; + font-size: 1.4em; + line-height: 1.4em; + padding: 5px 14px 6px 14px; +} +#main-navigation nav ul li.download-link, +#main-navigation nav ul li#locale-options { + float: left; +} +#main-navigation nav ul li.download-link a, +#main-navigation nav ul li#locale-options a { + color: #ff5400; +} + +#coming-soon-tip +{ + display: none; + z-index: 1000; + position: absolute; + top: 22px; + left: 344px; + width: 150px; + height: 40px; + background: url('/media/assets/images/coming-soon-tip-bg.png') top left no-repeat; + color: white; + padding: 21px 0 0 9px; + font-size: 1.5em; +} + +body.behind-the-code nav ul #behind-the-code a, +body.manifesto nav ul #manifesto a, +body.make-your-mark ul #make-your-mark a, +body.community ul #community a { + background: #ff5400; + color: #000; +} + +#content { + width: 100%; + background-color: #ffffff; + overflow: hidden; + position: relative; + font-size: 2em; + min-height: 500px; +} +#sammy, +#markapp { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: auto; + height: auto; +} +#content canvas { + background: transparent; + position: absolute; + top: 0; + width: 100%; +} + +#site-info { + height: 30px; + background: #333; +} +#sammy { + padding: 20px; + color: #333; +} +#sammy a { + color: #F66C1E; +} +#markmaker { + height: 100%; + margin: -20px; + background-image: url(/media/assets/images/large_dash.gif); + background-repeat: repeat-x; + background-position: 0 70%; +} + +#intro-content { + width: 600px; + margin: 0 auto; + padding-left: 370px; +} +#browse-marks, +#intro-main-copy, +#click-anywhere { + position: absolute; +} +#click-anywhere { + font-size: 0.9em; + margin: 10px 0 0 0; +} +#browse-marks { + z-index: 300; +} +#browse-marks a { + float: right; + display: block; + background-image: url("/media/assets/images/magnifying.png"); + background-position: 0 0; + font-size: 0.7em; + text-shadow: 0 0 1px rgba(0,0,0,.25); + height: 63px; + line-height: 1.3em; + padding-right: 13px; + padding-top: 24px; + text-align: center; + width: 76px; + color: #ffffff; +} +#markmaker #intro-main-copy { + width: 600px; +} +#markmaker #intro-main-copy h1 { + margin: 0; +} +#markmaker #intro-main-copy p { + line-height: 26px; + font-size: 0.9em; +} + +#markmaker #drawing { + background: url(/media/assets/images/large_dash.gif ) 0 80% repeat-x; + height: 100%; + margin: -20px; +} +#markmaker-controls { + width: 400px; + position: absolute; + bottom: 0; + left: 50%; + margin-left: -200px; + list-style: none; + z-index: 200; + text-align: center; + padding: 0; +} +#markmaker-controls li { +display: inline-block; +} + +#markmaker-controls li a { + color: #939393; + cursor: pointer; + display: inline-block; + padding: 30px 10px 4px; + margin: 0 10px; + background-repeat: no-repeat; + background-position: 50% 0; + text-transform: lowercase; + font-weight: bold; + font-size: 0.9em; +} +#markmaker-controls li a.disabled { + opacity: 0.5; + cursor: default; + cursor: not-allowed; +} +#markmaker-controls li#markmaker-submit a { + background-image: url(/media/assets/images/submit.png); + background-position: 50% 3px ; +} +#markmaker-controls li#markmaker-reset a { + background-image: url(/media/assets/images/reset.png); +} +#markmaker-controls li#markmaker-cancel a { + background-image: url(/media/assets/images/cancel.png); + background-position: 50% 5px; +} +#markmaker-controls li#markmaker-location a { + background-image: url(/media/assets/images/location.png); +} +#markmaker-information { + position: absolute; + right: 35px; + z-index: 200; + bottom: 25px; + display: block; + width: 30px; + height: 30px; + text-indent: -9999px; + background: url(/media/assets/images/i.png) 50% 50% no-repeat; + cursor: pointer; +} + +#contributor-fields, +#translator-fields { + width: 300px; + position: absolute; + z-index: 200; + bottom: 25px; + left: 25px; + padding: 7px; + background: #f7f7f7; + opacity: 0.9; +} +#translator-fields { + font-size: 12px; +} +#contributor-fields .field-wrapper { + margin: 10px 0; +} +#contributor-fields input, +#contributor-fields textarea { + width: 250px; + resize: none; + padding: 4px 2px; + font-size: 0.8em; + font-family: Helvetica, Arial, sans; + border: 1px solid #999999; +} +#contributor-fields label { + font-size: 0.7em; + display: inline; + width: 40px; + margin: 0px 5px 0px 0; + padding: 4px 0; + vertical-align: top; +} +#view-source { + position: absolute; + left: 12px; + bottom: 12px; + font-size: 0.7em; + font-weight: bold; + z-index: 1000; + padding: 5px; +} +.collapsible { + overflow: hidden; +} +.collapsible h3 { + cursor: pointer; + background-color: #F2F2F2; + background-image: url("/media/assets/images/collapsible.png"); + background-position: 97% 50%; + background-repeat: no-repeat; + color: #6E6E6E; + font-size: 10px; + margin: -5px; + padding: 5px; +} +.collapsibleMod-expanded h3 { + background-image: url("/media/assets/images/collapsed.png"); +} +#stats { + position: absolute; + right: 12px; + bottom: 12px; + z-index: 300; +} +#stats, +#mark-browsing { + padding: 7px; + width: 166px; + background: #f7f7f7; + z-index: 300; + opacity: 0.9; +} +#mark-browsing-options { + margin-bottom: 12px; +} + +#stats ul { + list-style:none; + margin: 0; + padding: 10px 0 0; + font-size: 10px; + color: #6e6e6e; +} +#stats ul li { + margin: 0; + padding: 3px 0; +} +#mark-browsing { + position: absolute; + right: 12px; + top: 20px; +} + +#mark-browsing-controls { + list-style: none; + background-image: url(/media/assets/images/controls.png); + background-repeat: no-repeat; + background-position: 50% 36px; + width: 166px; + height: 112px; + padding: 0; + margin: 10px 0; + position: relative; +} +#mark-browsing-controls li { + position: absolute; + +} +#mark-browsing-controls li a { + display: block; + color: #999999; + opacity: 1; + background-repeat: no-repeat; + font-size: 0px; + font-weight: bold; + text-indent: -9000em; +} +#mark-browsing-controls li a:hover { + opacity: 0.8; +} +#mark-browsing-zoom-in { + left: 48px; + top: 5px; +} +#mark-browsing-zoom-out { + left: 48px; + bottom: 8px; +} +#mark-browsing-next { + right: 21px; + top: 30px; +} +#mark-browsing-prev { + left: 24px; + top: 30px; +} +#mark-browsing-zoom-in a { + background-image: url(/media/assets/images/zoomIn.png); + background-position: 50% 100%; + padding: 0 0 20px 0; + text-align: center; + width: 70px; +} +#mark-browsing-zoom-out a { + background-image: url(/media/assets/images/zoomOut.png); + background-position: 50% 0%; + padding: 40px 0 0 0; + text-align: center; + width: 70px; +} +#mark-browsing-next a { + background-image: url(/media/assets/images/nextMark.png); + background-position: 0% 50%; + padding: 10px 0 10px 36px; + text-align: right; + height: 11px; +} +#mark-browsing-prev a { + background-image: url("/media/assets/images/prevMark.png"); + background-position: 100% 50%; + height: 11px; + padding: 10px 34px 10px 0; + text-align: left; +} +.select-wrapper { + margin-bottom: 8px; +} +#country-select-label, +#contributor-select-label { + display: inline-block; + background-image: url(/media/assets/images/location_small.png); + background-position: 0 0; + background-repeat: no-repeat; + text-indent:-9999px; + width: 14px; + height: 14px; +} +#contributor-select-label { + background-image: url(/media/assets/images/contributor_label.png); +} +#country-select { + margin-bottom: 8px; +} +#country-select, +#contributor-select { + width: 140px; +} +#mark-browsing-shortcuts { + list-style: none; + padding: 0; + margin: 10px 0 0; +} +#mark-browsing-shortcuts li { + text-align: center; +} +#mark-browsing-shortcuts li a { + display: block; + font-size: 10px; + background-color: #f2f2f2; + margin: 3px; + padding: 5px; + color: #6e6e6e; + font-weight: bold; +} +#mark-browsing-shortcuts li a:hover { + background-color: #ededed; + color: #555; +} +#mark-information { + position: absolute; + bottom: 12px; + left: 50%; + width: 206px; + padding: 10px 14px 10px 43px; + margin: 0 0 0 -80px; + background: #f7f7f7; + z-index: 300; + color: #f26522; + opacity: 0.9; +} +#mark-playback { + position: absolute; + top: 9px; + left: 10px; + width: 25px; + height: 25px; + text-indent: -9999px; + display: block; + background-image: url(/media/assets/images/playback.png); + background-repeat: no-repeat; + background-position: 0 0; +} +#mark-flag { + position: absolute; + top: 10px; + right: 12px; + width: 16px; + height: 10px; + text-indent: -9999px; + display: block; + background-image: url(/media/assets/images/flag.png); + background-repeat: no-repeat; + background-position: 0 0; +} +#mark-flag.disabled { + background-position: 0 -10px; + cursor: default; +} +#facebook-share, +#twitter-share { + width: 24px; + height: 23px; + display: block; + float: left; + background-position: 0 0; + background-repeat: no-repeat; + text-indent: -9999px; + margin-right: 2px; +} +#facebook-share { + background-image: url(/media/assets/images/facebook.png); +} +#twitter-share { + background-image: url(/media/assets/images/twitter.png); +} + +#delete-mark +{ + float: left; + margin: 0 20px 0 0; +} +#approve-mark-copy +{ + float: left; + padding: 4px 6px 0 0; +} +#approve-mark-checkbox +{ + float: left; +} + +#mark-information h3, +#mark-information h4, +#url-share label { + font-size: 11px; + margin: 0; + font-weight: normal; +} +#mark-information h4 { + margin-bottom: 5px; +} +#mark-contributor-name { + font-weight: bold; +} +#url-share label { +} +#url-share input { + width: 110px; + height: 20px; + padding: 0 3px; + border: 1px solid #f9a57d; + font-size: 10px; +} +footer { + padding-bottom: 20px; +} +footer #callout-boxes { + background-color: #000000; +} +footer #callout-boxes-wrapper { + width: 970px; + margin: 0 auto; + padding: 18px 0; +} +.callout-box { + width: 227px; + float: left; + color: #a8a8a8; + padding: 0 10px 0 0; + border-right: 1px solid #424242; + margin: 0 9px 10px 0; + font-size: 1.4em; +} +footer #callout-boxes-wrapper .last { + border-right: none; + padding: 0; + margin: 0; +} +.callout-box img { + float: left; + margin: 0 10px 10px 0; +} +.callout-box h3 { + margin: 0; +} +.callout-box h3 { + margin: 0; +} +.callout-box h3 a { + color: #A8A8A8; +} +.callout-box p { + margin: 4px 0; + line-height: 1.6em; +} + + +footer #language +{ + width: 968px; + margin: 20px auto 0; + position: relative; +} + +footer #language #current-locale +{ + float: right; + display: block; + color: #999; + font-size: 1.4em; + text-transform: uppercase; + padding: 5px 7px 7px 20px; + background: #2F2F30 url('/media/assets/images/locale_selector_arrow.png') 0 0 no-repeat; +} + +footer #language #current-locale.selected +{ + background-position: 0 -15px; +} + +footer #language #language-selector +{ + display: none; + position: absolute; + top: 15px; + right: 0; + width: 120px; + background: #4c4c4c; + z-index: 1000; + color: white; + text-shadow: 1px 1px 1px #e14f00; + padding: 0; +} + +footer #language #language-selector li +{ + list-style: none; + text-shadow: none; + font-size: 1.4em; + text-transform: uppercase; +} + +footer #language #language-selector li.selected +{ + padding: 5px 0 5px 10px; + border-left: #2b2b2b 10px solid; +} + +footer #language #language-selector li a +{ + display: block; + color: white; + padding: 5px 0 5px 10px; + border-left: #3d3d3d 10px solid; +} + +footer #language #language-selector li a:hover +{ + border-left: #2b2b2b 10px solid; +} + + + + + + + + + +footer #legal { + background-color: #2f2f30; + color: #727272; + width: 938px; + padding: 15px; + margin: 20px auto 0; +} +footer #legal p { + font-size: 1.4em; + margin: 0; + padding: 0; + line-height: 1.7em; +} +footer #legal p.legal-blurb { + width: 460px; + float: left; + margin-right: 10px; +} +footer #legal p.last { + margin-right: 0; +} +footer #legal a { + color: #727272; + text-decoration: underline; +} +footer #legal a:hover { + background-color: #272727; + margin-bottom: 1px; +} +#legal-links { + clear: both; +} +#legal-links ul { + list-style: none; + margin: 0; + padding: 20px 0 4px; +} +#legal-links ul li { + float: left; + border-right: 1px solid #616161; + margin-right: 10px; + padding-right: 10px; + font-size: 1.4em; +} +#legal-links ul li.last { + border-right: none; + margin-right: 0; + padding-right: 0; +} +.infobox-inner { + background-color: #e4e4e4; +} +.infobox-arrow { + background-image: url("/media/assets/images/tri.png"); + background-position: 100% 100%; + background-repeat: no-repeat; + height: 14px; + width: 30px; +} +.infobox-inner p { + margin: 0; + padding: 5px; + line-height: 1.4em; +} +#contributor-quote-box { + width: 350px; + font-size: 0.7em; + z-index: 201; + position: absolute; + opacity: 0.8; +} +#contributor-quote-box .infobox-inner { + padding: 10px; +} +#contributor-quote-box p { + margin: 0 0 10px; + padding: 0; +} +#contributor-quote-box #contributor-name { + text-align: right; + font-size: 10px; + margin-bottom: 0; +} +#contributor-quote-box #contributor-quote { + font-style: italic; + font-size: 11px; +} +#location-dialog { + width: 350px; + font-size: 0.7em; + z-index: 201; + position: absolute; +} +#location-dialog .infobox-inner { + padding: 14px 10px 6px; +} +#location-dialog select { + +} +#information-dialog { + font-size: 0.7em; + z-index: 201; + position: absolute; +} +#information-dialog p { + margin: 0; + padding: 0; + max-width: 300px; +} +#information-dialog .infobox-inner { + padding: 6px 10px; +} +#information-dialog .infobox-arrow { + background-image: url("/media/assets/images/tri_rtl.png"); + height: 14px; + width: 30px; + float: right; + margin-right: 14px; +} +#markmaker-legal-line { + font-size: 0.75em; + font-style: italic; + color: #bbbbbb; + width: 400px; + margin: 0 0 0 -200px; + position: absolute; + left: 50%; + text-align: center; + bottom: 102px; +} +.overlay-wrapper { + background: url(/media/assets/images/overlay.png) 0 0 repeat; +} +.overlay-light { + background: url(/media/assets/images/light_overlay.png) 0 0 repeat; + opacity: 0.9; +} +#markapp-loader { + padding: 100px 0; + z-index: 300; + position: relative; +} +#markapp-loader div { + background: url("/media/assets/images/loading.gif") no-repeat 50% 31px; + color: #FFFFFF; + font-size: 0.9em; + font-weight: bold; + line-height: 1.8em; + margin: 0 auto; + padding-bottom: 20px; + text-align: center; + width: 420px; +} +#markapp-loader.overlay-light div { + color: #ff5400; + background: url("/media/assets/images/loader_light.gif") no-repeat 50% 31px; +} +#fallback, +#markapp-error { + padding: 160px 0 0; + z-index: 300; + position: relative; +} +#fallback-content, +#markapp-error-content { + margin: 0 auto; + width: 420px; + font-size: 0.9em; + background: #ff5400; + border: 2px solid #ffffff; + color: #ffffff; + line-height: 1.8em; +} +#fallback-content p, +#markapp-error-content p { + background-image: url(/media/assets/images/arrow_orange.png); + background-position: 10px 100%; + background-repeat: no-repeat; + margin: 0 0 -18px; + padding: 30px 50px 50px; + font-size: 13px; +} +#fallback-content p a, +#markapp-error-content p a { + color: #7F3100; +} +#fallback-content p a:hover, +#markapp-error-content p a:hover { + color: #FFA680; +} +/* Static content styles */ +#static-content { + width: 970px; + margin: 60px auto 120px; +} +#static-content h1 { + font-size: 2.3em; + margin-top: 0; + margin-bottom: 0.3em; +} +#static-content h2 { + font-size: 1.3em; + margin: 2em 0 0.3em; +} +#static-content p { + line-height: 1.6em ; + font-size: 1em; + margin: 0.4em 0 1em 0; +} +#static-content blockquote { + margin: 0.4em 0.3em 0.4em 0.3em; + line-height: 1.6em; + font-style: italic; +} +#static-content cite { + display: block; + margin: 0.7em 0.3em 1.2em 0.6em; +} +#static-content a { + color: #FF5400; +} +#static-content .col-secondary, +#static-content .col-main { + float: left; + padding-right: 50px; +} +#static-content .col-last { + padding-right: 0; +} +#static-content .col-secondary { + width: 220px; +} +#static-content .col-main { + width: 700px; +} +#about-nav ul { + list-style: none; + padding: 88px 0 10px 0; + margin: 0; +} +#about-nav ul li { + text-align: right; + padding-bottom: 1em; +} +#about-nav ul li a { + color: #000000; + font-weight: bold; + font-size: 1.3em; +} +#newsletter-form +{ + margin: 30px 0 30px 30px; +} + +#newsletter-form label, +#newsletter-form input, +#newsletter-form select +{ + float: left; +} +#newsletter-form label +{ + margin: 0 10px 0 0; +} +#newsletter-form input[type="radio"] +{ + margin: 5px 15px 0 0; +} +#newsletter-form input[type="checkbox"] +{ + margin: 7px 10px 0 0; +} +#newsletter-form cite +{ + margin-left: 0; +} + + +#main-links:after, +.ui-selectBox:after, +#main-navigation:after, +#main-navigation nav ul:after, +#legal:after, +#legal-links ul:after, +#callout-boxes:after, +#static-content:after, +header:after, +.group:after, +footer #language:after, +#newsletter-form p:after { + clear: both; + content: "."; + display: block; + height: 0; + visibility: hidden; +} \ No newline at end of file diff --git a/ffdemo/static/assets/images/arrow.png b/ffdemo/static/assets/images/arrow.png new file mode 100644 index 0000000..004e312 Binary files /dev/null and b/ffdemo/static/assets/images/arrow.png differ diff --git a/ffdemo/static/assets/images/arrow_orange.png b/ffdemo/static/assets/images/arrow_orange.png new file mode 100644 index 0000000..6e8fcde Binary files /dev/null and b/ffdemo/static/assets/images/arrow_orange.png differ diff --git a/ffdemo/static/assets/images/bg.jpg b/ffdemo/static/assets/images/bg.jpg new file mode 100644 index 0000000..151fcce Binary files /dev/null and b/ffdemo/static/assets/images/bg.jpg differ diff --git a/ffdemo/static/assets/images/cancel.png b/ffdemo/static/assets/images/cancel.png new file mode 100644 index 0000000..1a9562b Binary files /dev/null and b/ffdemo/static/assets/images/cancel.png differ diff --git a/ffdemo/static/assets/images/collapsed.png b/ffdemo/static/assets/images/collapsed.png new file mode 100644 index 0000000..2d3ce9b Binary files /dev/null and b/ffdemo/static/assets/images/collapsed.png differ diff --git a/ffdemo/static/assets/images/collapsible.png b/ffdemo/static/assets/images/collapsible.png new file mode 100644 index 0000000..c0c2698 Binary files /dev/null and b/ffdemo/static/assets/images/collapsible.png differ diff --git a/ffdemo/static/assets/images/coming-soon-tip-bg.png b/ffdemo/static/assets/images/coming-soon-tip-bg.png new file mode 100644 index 0000000..c668d7b Binary files /dev/null and b/ffdemo/static/assets/images/coming-soon-tip-bg.png differ diff --git a/ffdemo/static/assets/images/contributor_icon.png b/ffdemo/static/assets/images/contributor_icon.png new file mode 100644 index 0000000..dc2e46f Binary files /dev/null and b/ffdemo/static/assets/images/contributor_icon.png differ diff --git a/ffdemo/static/assets/images/contributor_label.png b/ffdemo/static/assets/images/contributor_label.png new file mode 100644 index 0000000..8458886 Binary files /dev/null and b/ffdemo/static/assets/images/contributor_label.png differ diff --git a/ffdemo/static/assets/images/controls.png b/ffdemo/static/assets/images/controls.png new file mode 100644 index 0000000..f64289c Binary files /dev/null and b/ffdemo/static/assets/images/controls.png differ diff --git a/ffdemo/static/assets/images/evan_roth.jpg b/ffdemo/static/assets/images/evan_roth.jpg new file mode 100644 index 0000000..54d736d Binary files /dev/null and b/ffdemo/static/assets/images/evan_roth.jpg differ diff --git a/ffdemo/static/assets/images/evan_roth_bio.jpg b/ffdemo/static/assets/images/evan_roth_bio.jpg new file mode 100644 index 0000000..9a5f221 Binary files /dev/null and b/ffdemo/static/assets/images/evan_roth_bio.jpg differ diff --git a/ffdemo/static/assets/images/facebook.png b/ffdemo/static/assets/images/facebook.png new file mode 100644 index 0000000..9d75439 Binary files /dev/null and b/ffdemo/static/assets/images/facebook.png differ diff --git a/ffdemo/static/assets/images/favicon.png b/ffdemo/static/assets/images/favicon.png new file mode 100644 index 0000000..ad65cde Binary files /dev/null and b/ffdemo/static/assets/images/favicon.png differ diff --git a/ffdemo/static/assets/images/firefox_logo.jpg b/ffdemo/static/assets/images/firefox_logo.jpg new file mode 100644 index 0000000..5fef452 Binary files /dev/null and b/ffdemo/static/assets/images/firefox_logo.jpg differ diff --git a/ffdemo/static/assets/images/flag.png b/ffdemo/static/assets/images/flag.png new file mode 100644 index 0000000..5d33f93 Binary files /dev/null and b/ffdemo/static/assets/images/flag.png differ diff --git a/ffdemo/static/assets/images/footer_bg.gif b/ffdemo/static/assets/images/footer_bg.gif new file mode 100644 index 0000000..fe93de9 Binary files /dev/null and b/ffdemo/static/assets/images/footer_bg.gif differ diff --git a/ffdemo/static/assets/images/i.png b/ffdemo/static/assets/images/i.png new file mode 100644 index 0000000..a584845 Binary files /dev/null and b/ffdemo/static/assets/images/i.png differ diff --git a/ffdemo/static/assets/images/large_dash.gif b/ffdemo/static/assets/images/large_dash.gif new file mode 100644 index 0000000..ec58e78 Binary files /dev/null and b/ffdemo/static/assets/images/large_dash.gif differ diff --git a/ffdemo/static/assets/images/light_overlay.png b/ffdemo/static/assets/images/light_overlay.png new file mode 100644 index 0000000..c9e9099 Binary files /dev/null and b/ffdemo/static/assets/images/light_overlay.png differ diff --git a/ffdemo/static/assets/images/loader_light.gif b/ffdemo/static/assets/images/loader_light.gif new file mode 100644 index 0000000..aa9d091 Binary files /dev/null and b/ffdemo/static/assets/images/loader_light.gif differ diff --git a/ffdemo/static/assets/images/loading.gif b/ffdemo/static/assets/images/loading.gif new file mode 100644 index 0000000..3ec2f62 Binary files /dev/null and b/ffdemo/static/assets/images/loading.gif differ diff --git a/ffdemo/static/assets/images/locale_selector_arrow.png b/ffdemo/static/assets/images/locale_selector_arrow.png new file mode 100644 index 0000000..8fa37ae Binary files /dev/null and b/ffdemo/static/assets/images/locale_selector_arrow.png differ diff --git a/ffdemo/static/assets/images/location.png b/ffdemo/static/assets/images/location.png new file mode 100644 index 0000000..db7dc25 Binary files /dev/null and b/ffdemo/static/assets/images/location.png differ diff --git a/ffdemo/static/assets/images/location_selector_dropdown_bg.gif b/ffdemo/static/assets/images/location_selector_dropdown_bg.gif new file mode 100644 index 0000000..051a0a9 Binary files /dev/null and b/ffdemo/static/assets/images/location_selector_dropdown_bg.gif differ diff --git a/ffdemo/static/assets/images/location_small.png b/ffdemo/static/assets/images/location_small.png new file mode 100644 index 0000000..b94060f Binary files /dev/null and b/ffdemo/static/assets/images/location_small.png differ diff --git a/ffdemo/static/assets/images/magnifying.png b/ffdemo/static/assets/images/magnifying.png new file mode 100644 index 0000000..78f28f5 Binary files /dev/null and b/ffdemo/static/assets/images/magnifying.png differ diff --git a/ffdemo/static/assets/images/mark_up_logo.jpg b/ffdemo/static/assets/images/mark_up_logo.jpg new file mode 100644 index 0000000..0e2a32f Binary files /dev/null and b/ffdemo/static/assets/images/mark_up_logo.jpg differ diff --git a/ffdemo/static/assets/images/mozilla_tab.png b/ffdemo/static/assets/images/mozilla_tab.png new file mode 100644 index 0000000..819ba57 Binary files /dev/null and b/ffdemo/static/assets/images/mozilla_tab.png differ diff --git a/ffdemo/static/assets/images/nextMark.png b/ffdemo/static/assets/images/nextMark.png new file mode 100644 index 0000000..3b3cd69 Binary files /dev/null and b/ffdemo/static/assets/images/nextMark.png differ diff --git a/ffdemo/static/assets/images/overlay.png b/ffdemo/static/assets/images/overlay.png new file mode 100644 index 0000000..027d6db Binary files /dev/null and b/ffdemo/static/assets/images/overlay.png differ diff --git a/ffdemo/static/assets/images/playback.png b/ffdemo/static/assets/images/playback.png new file mode 100644 index 0000000..41d34e9 Binary files /dev/null and b/ffdemo/static/assets/images/playback.png differ diff --git a/ffdemo/static/assets/images/prevMark.png b/ffdemo/static/assets/images/prevMark.png new file mode 100644 index 0000000..9e84ff5 Binary files /dev/null and b/ffdemo/static/assets/images/prevMark.png differ diff --git a/ffdemo/static/assets/images/reset.png b/ffdemo/static/assets/images/reset.png new file mode 100644 index 0000000..1737948 Binary files /dev/null and b/ffdemo/static/assets/images/reset.png differ diff --git a/ffdemo/static/assets/images/site_thumb.jpg b/ffdemo/static/assets/images/site_thumb.jpg new file mode 100644 index 0000000..f02fbf1 Binary files /dev/null and b/ffdemo/static/assets/images/site_thumb.jpg differ diff --git a/ffdemo/static/assets/images/submit.png b/ffdemo/static/assets/images/submit.png new file mode 100644 index 0000000..2b795d1 Binary files /dev/null and b/ffdemo/static/assets/images/submit.png differ diff --git a/ffdemo/static/assets/images/tri.png b/ffdemo/static/assets/images/tri.png new file mode 100644 index 0000000..374f61b Binary files /dev/null and b/ffdemo/static/assets/images/tri.png differ diff --git a/ffdemo/static/assets/images/tri_rtl.png b/ffdemo/static/assets/images/tri_rtl.png new file mode 100644 index 0000000..cccacae Binary files /dev/null and b/ffdemo/static/assets/images/tri_rtl.png differ diff --git a/ffdemo/static/assets/images/twitter.png b/ffdemo/static/assets/images/twitter.png new file mode 100644 index 0000000..c0106dd Binary files /dev/null and b/ffdemo/static/assets/images/twitter.png differ diff --git a/ffdemo/static/assets/images/web_of_wonders.jpg b/ffdemo/static/assets/images/web_of_wonders.jpg new file mode 100644 index 0000000..a714aa1 Binary files /dev/null and b/ffdemo/static/assets/images/web_of_wonders.jpg differ diff --git a/ffdemo/static/assets/images/world_eater.jpg b/ffdemo/static/assets/images/world_eater.jpg new file mode 100644 index 0000000..bf406ef Binary files /dev/null and b/ffdemo/static/assets/images/world_eater.jpg differ diff --git a/ffdemo/static/assets/images/zoomIn.png b/ffdemo/static/assets/images/zoomIn.png new file mode 100644 index 0000000..6ba3cd7 Binary files /dev/null and b/ffdemo/static/assets/images/zoomIn.png differ diff --git a/ffdemo/static/assets/images/zoomOut.png b/ffdemo/static/assets/images/zoomOut.png new file mode 100644 index 0000000..6fab609 Binary files /dev/null and b/ffdemo/static/assets/images/zoomOut.png differ diff --git a/ffdemo/static/assets/js/app.js b/ffdemo/static/assets/js/app.js new file mode 100644 index 0000000..bbd070c --- /dev/null +++ b/ffdemo/static/assets/js/app.js @@ -0,0 +1,161 @@ +( function( $ ) { + var app = $.sammy( '#sammy', function() { + // ROUTES + this.get( '#/', function( context ) { + // making our mark + // unload the visualization if it's loaded + $( '#markapp' ).markApp( 'unloadModule', 'linear' ); + // load the template + this.partial( 'makemark_sammy.html' ) + .then( function() { + // init the intro + // init the capture interface, specifying that we're playing the intro first, and it should be ready to go when it's done + $( '#markapp' ).markApp( 'addModule', { 'capture': { 'state': 'intro' } } ); + $( '#markapp' ).markApp( 'addModule', { 'intro': { } } ); + + $( '#sammy' ).css( 'zIndex', '' ); + } ); + } ); + + // MARK CREATION + this.get( '#/mark/new', function( context ) { + // unload the visualization if it's loaded + $( '#markapp' ).markApp( 'unloadModule', 'linear' ); + + var modOptions = { + 'state': 'drawing', + 'invite_code': context.params['invite'], + 'contributor_type': context.params['contributor_type'] + } + // if we already have the content loaded, just update the state of the interface + if ( $( '#markmaker' ).size() > 0 ) { + $( '#markapp' ).markApp( 'unloadModule', 'intro' ); + + $( '#markapp' ).markApp( 'addModule', { 'capture': modOptions } ); + } else { + // template is not yet loaded, so lets start fresh + // load the template + this.partial( 'makemark_sammy.html' ) + .then( function() { + // init the capture interface, specifying that the intro should not be shown + $( '#markapp' ).markApp( 'addModule', { 'capture': modOptions } ); + $( '#sammy' ).css( 'zIndex', '' ); + } ); + } + } ); + + // MODERATION PAGE + this.get( '#/moderate', function( context ) { + // unload the other modules if they're loaded + $( '#markapp' ).markApp( 'unloadModule', 'intro' ); + $( '#markapp' ).markApp( 'unloadModule', 'capture' ); + this.partial( '/en/moderate_sammy.html' ) + .then( function() { + // load the linear module + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'is_flagged': true, 'linear_root': 'moderate' } } ); + } ); + } ); + // Moderation via direct reference + this.get( '#\/moderate\/(.*)', function( context ) { + // unload the other modules if they're loaded + $( '#markapp' ).markApp( 'unloadModule', 'intro' ); + $( '#markapp' ).markApp( 'unloadModule', 'capture' ); + if ( $( '#linear' ).size() > 0 ) { + // already setup, just load the new reference mark into the module + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'is_flagged': true, 'linear_root': 'moderate', 'reference_mark': context.params['splat'][0], 'playback': context.params['playback'] } } ); + } else { + // show all the signatures + this.partial( '/en/moderate_sammy.html' ) + .then( function() { + $( '#sammy' ).css( 'zIndex', '' ); + $( '#markapp' ).css( { 'zIndex': 100, 'cursor': 'default' } ); + // load up the visualization + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'is_flagged': true, 'linear_root': 'moderate', 'reference_mark': context.params['splat'][0], 'playback': context.params['playback'] } } ); + } ); + } + } ); + + // visualization + // visualization with country filtering + this.get( '#/linear/country/:country_code\/(.*)', function( context ) { + // unload the other modules if they're loaded + $( '#markapp' ).markApp( 'unloadModule', 'intro' ); + $( '#markapp' ).markApp( 'unloadModule', 'capture' ); + if ( $( '#linear' ).size() > 0 ) { + // already setup, just load the new reference mark into the module + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'country_code': context.params['country_code'], 'reference_mark': context.params['splat'][0] } } ); + } else { + this.partial( 'linear_sammy.html' ) + .then( function() { + $( '#sammy' ).css( 'zIndex', '' ); + $( '#markapp' ).css( { 'zIndex': 100, 'cursor': 'default' } ); + // load up the visualization + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'country_code': context.params['country_code'], 'reference_mark': context.params['splat'][0] } } ); + } ); + } + } ); + // visualization without country filtering + this.get( '#\/linear\/(.*)', function( context ) { + // unload the other modules if they're loaded + $( '#markapp' ).markApp( 'unloadModule', 'intro' ); + $( '#markapp' ).markApp( 'unloadModule', 'capture' ); + if ( $( '#linear' ).size() > 0 ) { + // already setup, just load the new reference mark into the module + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'reference_mark': context.params['splat'][0], 'playback': context.params['playback'] } } ); + } else { + // show all the signatures + this.partial( 'linear_sammy.html' ) + .then( function() { + $( '#sammy' ).css( 'zIndex', '' ); + $( '#markapp' ).css( { 'zIndex': 100, 'cursor': 'default' } ); + // load up the visualization + $( '#markapp' ).markApp( 'addModule', { 'linear': { 'reference_mark': context.params['splat'][0], 'playback': context.params['playback'] } } ); + } ); + } + } ); + + // event handlers + this.bind( 'run', function() { + // add an instance of markApp + $( '#markapp' ) + .markApp() + // and privide it a way of accessing the sammy app + .data( 'markApp-context' )['app'] = app; + } ); + + // other stuff + this.swap = function( content ) { + this.$element().fadeOut( 'fast', function() { + $( this ).html( content ).fadeIn( 'normal' ); + $( '#markapp' ).trigger( 'ready' ); + } ); + $( '#markapp' ).trigger( 'swap' ); + + }; + + this.use( 'Template' ); + + } ); + + $( document ).ready( function () { + function browserSupportsRequiredFeatures() { + // detect canvas support + return !!document.createElement('canvas').getContext; + } + if ( browserSupportsRequiredFeatures ) { + // remove the placeholder content + $( '#fallback' ).remove(); + // run the app + app.run( '#/' ); + } + + // Try binding click event to locale here + $("#current-locale").click(function () + { + $(this).parent().find("ul").toggle(); + $(this).toggleClass("selected"); + return false; + }); + + } ); //document ready +} )( jQuery ); \ No newline at end of file diff --git a/ffdemo/static/assets/js/bundled.js b/ffdemo/static/assets/js/bundled.js new file mode 100644 index 0000000..38b940d --- /dev/null +++ b/ffdemo/static/assets/js/bundled.js @@ -0,0 +1,4419 @@ +( function( $ ) { + + // TODO -- support loose augmentation of $.markApp, in case module code is ever loaded before this. + + // The markApp namespace is used for storing available module code, instances of markApp, and any context free functions + $.markApp = { + // holds all available modules + modules: {}, + // we keep track of all instances of markApp in here + instances: [], + // helper functions go here + fn: {} + }; + + // Creates a markApp instances with an element, and handles function calls on the instance + $.fn.markApp = function( options ) { + var $this = $( this ); + // The context each markApp instance is stored as data on that element + var context = $this.data( 'markApp-context' ); + // On first call, we need to set things up, but on all following calls we can skip right to the API handling + if ( !context || typeof context == 'undefined' ) { + context = { + // useful variables -- jquery objects get prefixed with $ + app: null, + $container: $this, + frameCount: 0, + width: 0, + height: 0, + minWidth: 300, + minHeight: 400, + countries: [], + mouseX: null, + mouseY: null, + mouseDown: false, + mouseIn: false, + modules: {}, + usersMark: null, + defaultErrorMsg: $( '#default-error-msg' ).text(), + defaultLoadingMsg: $( '#default-loading-msg' ).text(), + locale: ( window.location.pathname.split("/").length > 1 ) ? window.location.pathname.split("/")[1] : "en", // locale -- set by URL, default to 'en' + instance: $.markApp.instances.push( $this ) - 1, // store this instances index in the global instace array + // events + evt: { + resize: function( e ) { + var availableWidth = $( window ).width(); + var availableHeight = $( window ).height() - ( $( 'header' ).height() + $( '#callout-boxes' ).height() ); + if ( availableWidth < context.minWidth ) availableWidth = context.minWidth; + if ( availableHeight < context.minHeight ) availableHeight = context.minHeight; + context.$container.parent().width( availableWidth ); + context.$container.parent().height( availableHeight ); + context.width = availableWidth; + context.height = availableHeight; + // resize any elements with the autoResize class + $( '.autoResize' ) + .height( availableHeight ) + .width( availableWidth ) + .trigger( 'resize.markApp', [availableWidth, availableHeight] ); + }, + mousemove: function( e ) { + context.mouseX = e.layerX; + context.mouseY = e.layerY; + }, + mousedown: function( e ) { + if( 'preventDefault' in e ) e.preventDefault(); + context.mouseDown = true; + }, + mouseup: function( e ) { + if( 'preventDefault' in e ) e.preventDefault(); + context.mouseDown = false; + }, + mouseover: function( e ) { + if( 'preventDefault' in e ) e.preventDefault(); + context.mouseIn = true; + }, + mouseout: function( e ) { + if( 'preventDefault' in e ) e.preventDefault(); + context.mouseX = null; + context.mouseY = null; + context.mouseIn = false; + } + }, + // publicly accessible functions + public_fn: { + addModule: function( context, data ) { + var moduleName, + moduleOptions = {}; + + if( typeof data == "string" ) { + moduleName = data + } else if( typeof data == "object" ) { + for ( var moduleData in data ) { + moduleName = moduleData + moduleOptions = data[moduleData]; + break; + } + } + + if( $.markApp.modules[moduleName] ) { + // give this module it's own space for storing stuff if it doesn't already have it + context.modules[moduleName] = context.modules[moduleName] || {}; + // if it has an init function, run it + if( 'init' in $.markApp.modules[moduleName].fn ) + $.markApp.modules[moduleName].fn.init( context, moduleOptions ); + } + }, + unloadModule: function( context, moduleName ) { + if( moduleName == "all" ) { + // unload all the currently loaded modules + for ( moduleName in context.modules ) { + // if it has a deinit function, run it + if( 'deinit' in $.markApp.modules[moduleName].fn ) + $.markApp.modules[moduleName].fn.deinit( context ); + // remove it from our modules + delete context.modules[moduleName]; + } + } else if ( moduleName in context.modules ) { + // if it has a deinit function, run it + if( 'deinit' in $.markApp.modules[moduleName].fn ) + $.markApp.modules[moduleName].fn.deinit( context ); + // remove it from our modules + delete context.modules[moduleName]; + } + } + }, + // internal functions + fn: { + // trigger event handlers on modules + trigger: function( eventName, eventObj, args ) { + + // Add some assurances to our eventObj + if( typeof eventObj == "undefined" ) + eventObj = { 'type': 'custom' }; + + // trigger the global handlers first + if ( eventName in context.evt ) { + // if it returns false, stop the train + if ( context.evt[eventName]( eventObj ) == false ) { + return false; + } + } + + // run the event handler on each module that's got it + for( var module in context.modules ) { + if( module in $.markApp.modules && + 'evt' in $.markApp.modules[module] && + eventName in $.markApp.modules[module].evt ) { + $.markApp.modules[module].evt[eventName]( context, eventObj, args ); + } + } + }, + loop: function( e ) { + // reset the delay + setTimeout( function() { context.fn.loop( ); }, 42 ); + // incremenet the counter + context.frameCount++; + // dispatch the event + context.fn.trigger( 'loop', {}, [] ); + }, + // useful for delayed loading of the country data + withCountryCodes: function ( callback ) { + if ( 'US' in context.countries ) { + callback( context.countries ); + } else { + $.ajax( { + 'url': '/media/assets/js/vendor/country_codes.json', + 'dataType': 'JSON', + 'success': function ( data ) { + context.countries = data; + for( var i = 0; i < data.length; i++ ) { + context.countries[ data[i].code ] = data[i].name; + } + callback( context.countries ); + }, + 'error': function () { + // handle error loading countries + } + } ); + } + }, + showLoader: function( msg, custom_class ) { + var custom_class = custom_class || ''; + var msg = typeof msg === "string" ? msg : context.defaultLoadingMsg; + // append our loader + var $loader = $( '
' ) + .width( context.width ) + .height( context.height ) + .hide() + .addClass( 'overlay-wrapper autoResize' ) + .addClass( custom_class ) + .attr( 'id', 'markapp-loader' ) + .append( $( '
' ) + .text( msg ) ); + context.$container + .append( $loader ); + $loader.fadeIn( 'fast' ); + }, + hideLoader: function( ) { + $( '#markapp-loader' ).fadeOut( 'fast', function() { + $( this ).remove(); + } ); + }, + showError: function ( msg ) { + // TODO -- translate this default string + var msg = typeof msg === "string" ? msg : context.defaultErrorMsg; + var $error = $( '
' ) + .width( context.width ) + .height( context.height ) + .hide() + .click( function ( e ) { + e.preventDefault(); + context.fn.hideError(); + } ) + .addClass( 'overlay-wrapper autoResize' ) + .attr( 'id', 'markapp-error' ) + .append( $( '
' ) + .attr( 'id', 'markapp-error-content' ) + .append( $( '

' ).text( msg ) ) ); + context.$container + .append( $error ); + $error.fadeIn( 'fast' ); + }, + hideError: function ( ) { + $( '#markapp-error' ).fadeOut( 'fast', function() { + $( this ).remove(); + } ); + }, + storeData: function( key, value ) { + if ( typeof localStorage != 'undefined' ) { + // use localStorage if it exists + try { + if( typeof value === "object" ) { + value = JSON.stringify( value ); + } + localStorage.setItem( key, value ); + } catch (e) { + if ( e == QUOTA_EXCEEDED_ERR ) { /* data wasn't successfully saved due to quota exceed */ } + } + } else { + // use cookies + // todo -- impliment cookie fallback....maybe + } + }, + getData: function( key ) { + if ( typeof localStorage != 'undefined' ) { + var item = localStorage.getItem( key ); + if( item ) { + if ( item[0]=="{" ) item = JSON.parse( item ); + return item; + } else { + return false; + } + } + } + } + }; + // bindings + $( window ) + .delayedBind( 300, 'resize', function( e ) { + return context.fn.trigger( 'resize', e ); + } ) + .bind( 'keydown keypress keyup', function( e ) { + return context.fn.trigger( e.type, e ); + } ) + .trigger( 'resize' ); + context.$container + .bind( 'mousemove mousedown mouseup mouseover mouseout ready swap', function( e ) { + return context.fn.trigger( e.type, e ); + } ); + // start the loop + context.fn.loop(); + } + + // Convert the arguments to an array to make this easier + var args = $.makeArray( arguments ); + + // handle public function calls + if ( args.length > 0 ) { + var call = args.shift(); + if ( call in context.public_fn ) { + context.public_fn[call]( context, typeof args[0] == 'undefined' ? {} : args[0] ); + } + } + + return $this.data( 'markApp-context', context ); + }; + +}( jQuery ) ); +( function( $ ) { + + // support loose augmentation + markApp = $.markApp = $.markApp || {}; + modules = $.markApp.modules = $.markApp.modules || {}; + + modules.linear = { + defaults: { + reference_mark: null, // optional reference mark -- will init the visualization on this mark if passed + country_code: null, // optional country code -- only loads marks with this county code if present + playback: false, // set this to true to immediately play back the initial mark + is_flagged: false, // optional flagged flag -- only loads marks that are flagged (for use within moderation) + linear_root: 'linear' + }, + config: { + marks: {}, // all marks held here + flaggings: {}, // loaded from local storage, and used for some local checking that users are flagging things multiple times + orderedMarks: [], // array of mark references, arranged by id + leftBuffer: [], // mark buffers for the left and right of the marks currently in the scene + rightBuffer: [], + bufferSize: 20, // amount of marks to keep in a buffer + bufferMinSize: 5, // min amount of marks to keep in a buffer before forcing a refill + scene: null, + // handles camera changes -- could probably be shifted into the camera object + cameraChange: {}, + tweens: {}, + layerManager: null, // layer manager + initialized: false, // boolean flag to indicate if this module is fully setup + requestingMarks: false, // prevents more mark request from being sent when set to true + moreLeft: true, // Flags for the begining and end of the line. Assume we're not there at the start + moreRight: true, + hoverMark: null, // holds the mark currently being hovered over + currentMark: null, + + playbackTimes: {}, // used for storing mark playback times by reference + eventChange: false // flag to tell the render loop if the mouse is causing changes that need rendered + }, + evt: { + ready: function ( context, e ) { + modules.linear.fn.initInterface( context ); + }, + resize: function( context, e ) { + context.modules.linear.eventChange = true; + context.modules.linear.layerManager.resizeAll( context.width, context.height ); + }, + keydown: function ( context, e ) { + var lC = context.modules.linear; + switch( e.keyCode ) { + case 38: + // arrow up + e.preventDefault(); + lC.cameraChange.aZ = 10; + break; + case 40: + // arrow down + e.preventDefault(); + lC.cameraChange.aZ = -10; + break; + case 39: + // arrow right -- pan the camera to the right + e.preventDefault(); + // next mark + lC.cameraChange.aX = 10; + // hide mark info + modules.linear.fn.hideMarkInformation( context ); + break; + case 37: + // arrow left -- pan the camera to the left + e.preventDefault(); + // prev mark + lC.cameraChange.aX = -10; + // hide mark info + modules.linear.fn.hideMarkInformation( context ); + break; + } + }, + keyup: function( context, e ) { + var lC = context.modules.linear; + switch( e.keyCode ) { + case 38: + case 40: + // arrow up + e.preventDefault(); + lC.cameraChange.aZ = 0; + break; + case 39: + case 37: + e.preventDefault(); + lC.cameraChange.aX = 0; + break; + } + }, + // prevent our bound keys from firing native events + keypress: function( context, e ) { + switch( e.keyCode ) { + case 38: + case 40: + case 39: + case 37: + e.preventDefault(); + break; + } + }, + mousedown: function( context, e ) { + var lC = context.modules.linear; + if( mark = modules.linear.fn.hitTest( context, context.mouseX, context.mouseY ) ) { + // if the mark hasn't changed from what's in our URL, we just need to show the details again + if ( mark == lC.currentMark ) { + modules.linear.fn.centerCurrentMark( context, function() { + modules.linear.fn.showMarkInformation( context ); + } ); + // otherwise update the URL, and the module will jump to the referenced mark + } else if ( lC.country_code ) { + context.app.setLocation( '#/' + lC.linear_root + '/country/' + lC.country_code + '/' + mark.reference ); + } else { + context.app.setLocation( '#/' + lC.linear_root + '/' + mark.reference ); + } + } + }, + mousemove: function( context, e ) { + var lC = context.modules.linear; + lC.eventChange = true; + // hover test + if( mark = modules.linear.fn.hoverTest( context, context.mouseX, context.mouseY, lC.hoverMark ) ) { + // reset the old hovered mark + if( lC.hoverMark ) lC.hoverMark.color = lC.hoverMark.contributor_name ? '171,73,27' : '0,0,0'; + // store this hover mark + lC.hoverMark = mark; + if ( lC.hoverMark.reference == lC.currentMark.reference && lC.hoverMark.contributor_name && $( '#mark-information' ).is( ':visible' ) ) { + $( '#contributor-quote-box' ) + .fadeIn( 'fast' ) + .css( { left: context.mouseX - 15, top: context.mouseY - $( '#contributor-quote-box' ).height() - 15 } ); + } else { + lC.hoverMark.color = '0,139,211'; + } + } else if ( lC.hoverMark ) { + lC.hoverMark.color = lC.hoverMark.contributor_name ? '171,73,27' : '0,0,0'; + lC.hoverMark = null; + // fade out any contributor quotes we might happen to be showing + $( '#contributor-quote-box:visible' ).fadeOut( 'fast' ); + } + + }, + loop: function ( context ) { + var lC = context.modules.linear, + dLayer = lC.layerManager.layers['drawingLayer']; + + // update the position of the camera + var lastCameraPosition = {x: lC.scene.camera.position.x, y: lC.scene.camera.position.y, z: lC.scene.camera.position.z }; + // two ways this can happen + if( 'cameraEase' in lC.tweens ) { + // with a tween + TWEEN.update(); + } else { + // or with physics + lC.cameraChange.vZ += lC.cameraChange.aZ; + lC.cameraChange.vX += lC.cameraChange.aX; + lC.cameraChange.vX *= .93; + lC.cameraChange.vZ *= .93; + lC.scene.camera.position.x += lC.cameraChange.vX; + lC.scene.camera.position.z += lC.cameraChange.vZ; + // bring the Y and Z positions close to the current mark + var mark = modules.linear.fn.closestMark( context ); + if ( mark ) { + var dY = lC.scene.camera.position.y - ( mark.position.y + ( mark.bHeight / 2 ) ); + // if Z is not changing, try to maintain + // if( ) + // var dZ = lC.scene.camera.position.z - + if ( dY != 0 && Math.abs(dY) >= 10) lC.scene.camera.position.y += ( dY > 0 ? -10 : 10 ); + } + // TODO: fix the Z index while navigating the line + } + + // if nothing has changed, return RIGHT NOW + if( !lC.eventChange && + lastCameraPosition.x == lC.scene.camera.position.x && + lastCameraPosition.y == lC.scene.camera.position.y && + lastCameraPosition.z == lC.scene.camera.position.z ) { + return false; + } + + // modified hover test -- only removes hover states + if( lC.hoverMark && ! modules.linear.fn.hoverTest( context, context.mouseX, context.mouseY, lC.hoverMark ) ) { + lC.hoverMark.color = lC.hoverMark.contributor_name ? '171,73,27' : '0,0,0'; + lC.hoverMark = null; + // fade out any contributor quotes we might happen to be showing + $( '#contributor-quote-box:visible' ).fadeOut( 'fast' ); + } + + dLayer.clean(); + // update scene marks && add more as needed + modules.linear.fn.updateScene( context, lC.scene.camera.position.x - 10000, lC.scene.camera.position.x + 10000 ); + // cleanup the scene and tend to empty buffers + modules.linear.fn.updateBuffers( context, lC.scene.camera.position.x - 10000, lC.scene.camera.position.x + 10000 ); + + // render the scene + Mark.renderer.renderScene( lC.scene, { 'cursor': {x: context.mouseX, y: context.mouseY}, width: context.width, height: context.height } ); + // ark.renderer.renderScene( lC.scene, dLayer.context, { x: context.mouseX, y: context.mouseY }, context.width, context.height, lC.playbackTimes ); + + // set the eventChange flag back to false -- a mouse/keyboard or playback event will need to set it back to true again before we'll render because of it + lC.eventChange = false + + // cleanup playback times if necissary + for( mark in lC.scene.timers ) { + var now = ( new Date() ).getTime(); + if( lC.scene.timers[mark].end < now ) { + delete lC.scene.timers[mark]; + } + lC.eventChange = true; + } + + } + }, + fn: { + init: function( context, options ) { + var lC = context.modules.linear; + // if this modules has already been initialized, update the options + if ( '$linear' in context.modules.linear ) { + // before we merge options, check if we need to dump our current data + if ( options['country_code'] != lC.country_code ) { + modules.linear.fn.dumpAllMarks( context ); + } + // now our options into our context + $.extend( lC, lC, options ); + // since merging won't replace null or undefined values, make sure we clean up after it + for( option in modules.linear.defaults ) { + if ( options[option] == null ) lC[option] = modules.linear.defaults[option]; + } + // update the interface + modules.linear.fn.updateInterface( context ); + // load our marks + modules.linear.fn.initMarks( context ); + } else { + // allow defaults to be overriden + $.extend( lC, modules.linear.defaults, options ); + // but not the cofig + $.extend( lC, lC, modules.linear.config ); + + // DOM setup + lC.$linear = $( '

' ) + .addClass( "linear-container" ); + context.$container + .append( lC.$linear ); + + // scene setup + lC.scene = new Mark.scene(); + lC.cameraChange = { aX: 0, aY: 0, aZ: 0, vX: 0, vY: 0, vZ: 0 }; + // layer setup + lC.layerManager = new Mark.layerManager( lC.$linear.get( 0 ) ); + lC.layerManager.addLayer( 'drawingLayer' ); + lC.scene.canvasContext = lC.layerManager.layers['drawingLayer'].context; + // trigger resize so our new layers are sized to fit + context.fn.trigger( 'resize' ); + + lC.initialized = true; + } + // load flaggings + lC.flaggings = context.fn.getData( 'markFlaggings' ) || {}; + }, + deinit: function( context ) { + var lC = context.modules.linear; + lC.$linear.fadeOut( 'fast', function () { + // remove all our layers + lC.layerManager.removeAll(); + lC.$linear.remove(); + lC.initialized = false; + } ); + }, + initInterface: function( context ) { + var lC = context.modules.linear; + // hide anything that needs data loaded into it + $( '#stats' ) + .hide( ); + // enable the controls + $( '#mark-browsing-zoom-in a, #mark-browsing-zoom-out a, #mark-browsing-next a, #mark-browsing-prev a' ) + .click( function( e ){ e.preventDefault(); } ) + .bind( 'mouseup mouseout', function( e ) { + e.preventDefault(); + if ( $( this ).data( 'mouseDown' ) ) { + if( $( this ).is( '#mark-browsing-zoom-in a, #mark-browsing-zoom-out a' ) ) { + context.modules.linear.cameraChange.aZ = 0; + } else if( $( this ).is( '#mark-browsing-next a, #mark-browsing-prev a' ) ) { + context.modules.linear.cameraChange.aX = 0; + } + $( this ).data( 'mouseDown', false ); + } + } ) + .bind( 'mousedown', function( e ) { + e.preventDefault(); + $( this ).data( 'mouseDown', true ); + if( $( this ).is( '#mark-browsing-zoom-in a, #mark-browsing-zoom-out a' ) ) { + context.modules.linear.cameraChange.aZ = $( this ).is( '#mark-browsing-zoom-in a' ) ? 10 : -10; + } else if ( $( this ).is( '#mark-browsing-next a, #mark-browsing-prev a' ) ) { + context.modules.linear.cameraChange.aX = $( this ).is( '#mark-browsing-next a' ) ? 10 : -10; + // hide the mark information + modules.linear.fn.hideMarkInformation( context ); + } + } ); + // populate our country filter select box + context.fn.withCountryCodes( function ( countryCodes ) { + var $select = $( '#country-select' ); + for( var i = 0; i < countryCodes.length; i++ ) { + var $option = $( '