From b1cfee4592df436a52103ef4b3b9a94826eeccf2 Mon Sep 17 00:00:00 2001 From: Louis Sobel Date: Tue, 23 Oct 2012 14:50:17 -0400 Subject: [PATCH 001/228] Add support for subtitle download link Add a download link to the video player. The link is defined in the content xml using the following structure: --- common/lib/xmodule/xmodule/video_module.py | 22 ++++++++++++++++++---- lms/templates/video.html | 6 ++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/video_module.py b/common/lib/xmodule/xmodule/video_module.py index 11c90f45ef..9a22950ca8 100644 --- a/common/lib/xmodule/xmodule/video_module.py +++ b/common/lib/xmodule/xmodule/video_module.py @@ -32,6 +32,7 @@ class VideoModule(XModule): self.position = 0 self.show_captions = xmltree.get('show_captions', 'true') self.source = self._get_source(xmltree) + self.track = self._get_track(xmltree) if instance_state is not None: state = json.loads(instance_state) @@ -40,13 +41,25 @@ class VideoModule(XModule): def _get_source(self, xmltree): # find the first valid source - source = None - for element in xmltree.findall('source'): + return self._get_first_external(xmltree, 'source') + + def _get_track(self, xmltree): + # find the first valid track + return self._get_first_external(xmltree, 'track') + + def _get_first_external(self, xmltree, tag): + """ + Will return the first valid element + of the given tag. + 'valid' means has a non-empty 'src' attribute + """ + result = None + for element in xmltree.findall(tag): src = element.get('src') if src: - source = src + result = src break - return source + return result def handle_ajax(self, dispatch, get): ''' @@ -85,6 +98,7 @@ class VideoModule(XModule): 'id': self.location.html_id(), 'position': self.position, 'source': self.source, + 'track' : self.track, 'display_name': self.display_name, # TODO (cpennington): This won't work when we move to data that isn't on the filesystem 'data_dir': self.metadata['data_dir'], diff --git a/lms/templates/video.html b/lms/templates/video.html index 47556095cb..5c041d5c70 100644 --- a/lms/templates/video.html +++ b/lms/templates/video.html @@ -18,3 +18,9 @@

Download video here.

% endif + +% if track: +
+

Download subtitles here.

+
+% endif From e4ffb2a514c608db8bb07c1694f8be34bf897212 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Wed, 24 Oct 2012 11:15:14 -0400 Subject: [PATCH 002/228] Add TestCenterUser model and export for Pearson --- .../management/commands/pearson_export_cdd.py | 60 ++++++ .../migrations/0020_add_test_center_user.py | 186 ++++++++++++++++++ common/djangoapps/student/models.py | 66 +++++++ 3 files changed, 312 insertions(+) create mode 100644 common/djangoapps/student/management/commands/pearson_export_cdd.py create mode 100644 common/djangoapps/student/migrations/0020_add_test_center_user.py diff --git a/common/djangoapps/student/management/commands/pearson_export_cdd.py b/common/djangoapps/student/management/commands/pearson_export_cdd.py new file mode 100644 index 0000000000..ce90531941 --- /dev/null +++ b/common/djangoapps/student/management/commands/pearson_export_cdd.py @@ -0,0 +1,60 @@ +import csv +from collections import OrderedDict +from datetime import datetime + +from django.core.management.base import BaseCommand, CommandError + +from student.models import TestCenterUser + +class Command(BaseCommand): + CSV_TO_MODEL_FIELDS = OrderedDict([ + ("ClientCandidateID", "client_candidate_id"), + ("FirstName", "first_name"), + ("LastName", "last_name"), + ("MiddleName", "middle_name"), + ("Suffix", "suffix"), + ("Salutation", "salutation"), + ("Email", "email"), + # Skipping optional fields Username and Password + ("Address1", "address_1"), + ("Address2", "address_2"), + ("Address3", "address_3"), + ("City", "city"), + ("State", "state"), + ("PostalCode", "postal_code"), + ("Country", "country"), + ("Phone", "phone"), + ("Extension", "extension"), + ("PhoneCountryCode", "phone_country_code"), + ("FAX", "fax"), + ("FAXCountryCode", "fax_country_code"), + ("CompanyName", "company_name"), + # Skipping optional field CustomQuestion + ("LastUpdate", "user_updated_at"), # in UTC, so same as what we store + ]) + + args = '' + help = """ + Export user information from TestCenterUser model into a tab delimited + text file with a format that Pearson expects. + """ + def handle(self, *args, **kwargs): + if len(args) < 1: + print Command.help + return + + + with open(args[0], "wb") as outfile: + writer = csv.DictWriter(outfile, + Command.CSV_TO_MODEL_FIELDS, + delimiter="\t", + quoting=csv.QUOTE_MINIMAL, + extrasaction='ignore') + writer.writeheader() + for tcu in TestCenterUser.objects.order_by('id'): + record = dict((csv_field, getattr(tcu, model_field)) + for csv_field, model_field + in Command.CSV_TO_MODEL_FIELDS.items()) + record["LastUpdate"] = record["LastUpdate"].strftime("%Y/%m/%d %H:%M:%S") + writer.writerow(record) + diff --git a/common/djangoapps/student/migrations/0020_add_test_center_user.py b/common/djangoapps/student/migrations/0020_add_test_center_user.py new file mode 100644 index 0000000000..f3b729afdc --- /dev/null +++ b/common/djangoapps/student/migrations/0020_add_test_center_user.py @@ -0,0 +1,186 @@ +# -*- coding: 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 'TestCenterUser' + db.create_table('student_testcenteruser', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, db_index=True, blank=True)), + ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, db_index=True, blank=True)), + ('user_updated_at', self.gf('django.db.models.fields.DateTimeField')(db_index=True)), + ('candidate_id', self.gf('django.db.models.fields.IntegerField')(null=True, db_index=True)), + ('client_candidate_id', self.gf('django.db.models.fields.CharField')(max_length=50, db_index=True)), + ('first_name', self.gf('django.db.models.fields.CharField')(max_length=30, db_index=True)), + ('last_name', self.gf('django.db.models.fields.CharField')(max_length=50, db_index=True)), + ('middle_name', self.gf('django.db.models.fields.CharField')(max_length=30, blank=True)), + ('suffix', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('salutation', self.gf('django.db.models.fields.CharField')(max_length=50, blank=True)), + ('address_1', self.gf('django.db.models.fields.CharField')(max_length=40)), + ('address_2', self.gf('django.db.models.fields.CharField')(max_length=40, blank=True)), + ('address_3', self.gf('django.db.models.fields.CharField')(max_length=40, blank=True)), + ('city', self.gf('django.db.models.fields.CharField')(max_length=32, db_index=True)), + ('state', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=20, blank=True)), + ('postal_code', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=16, blank=True)), + ('country', self.gf('django.db.models.fields.CharField')(max_length=3, db_index=True)), + ('phone', self.gf('django.db.models.fields.CharField')(max_length=35)), + ('extension', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=8, blank=True)), + ('phone_country_code', self.gf('django.db.models.fields.CharField')(max_length=3, db_index=True)), + ('fax', self.gf('django.db.models.fields.CharField')(max_length=35, blank=True)), + ('fax_country_code', self.gf('django.db.models.fields.CharField')(max_length=3, blank=True)), + ('company_name', self.gf('django.db.models.fields.CharField')(max_length=50, blank=True)), + )) + db.send_create_signal('student', ['TestCenterUser']) + + + def backwards(self, orm): + # Deleting model 'TestCenterUser' + db.delete_table('student_testcenteruser') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'student.courseenrollment': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'student.pendingemailchange': { + 'Meta': {'object_name': 'PendingEmailChange'}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.pendingnamechange': { + 'Meta': {'object_name': 'PendingNameChange'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.registration': { + 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.testcenteruser': { + 'Meta': {'object_name': 'TestCenterUser'}, + 'address_1': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'address_2': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}), + 'address_3': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}), + 'candidate_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_index': 'True'}), + 'city': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'client_candidate_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'company_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'extension': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '8', 'blank': 'True'}), + 'fax': ('django.db.models.fields.CharField', [], {'max_length': '35', 'blank': 'True'}), + 'fax_country_code': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'middle_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'phone': ('django.db.models.fields.CharField', [], {'max_length': '35'}), + 'phone_country_code': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}), + 'postal_code': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '16', 'blank': 'True'}), + 'salutation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'state': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}), + 'suffix': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'user_updated_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}) + }, + 'student.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, + 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), + 'gender': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}), + 'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'student.usertestgroup': { + 'Meta': {'object_name': 'UserTestGroup'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}) + } + } + + complete_apps = ['student'] \ No newline at end of file diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 7e8658045e..d379689639 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -133,6 +133,72 @@ class UserProfile(models.Model): def set_meta(self, js): self.meta = json.dumps(js) +class TestCenterUser(models.Model): + """This is our representation of the User for in-person testing, and + specifically for Pearson at this point. A few things to note: + + * Pearson only supports Latin-1, so we have to make sure that the data we + capture here will work with that encoding. + * While we have a lot of this demographic data in UserProfile, it's much + more free-structured there. We'll try to pre-pop the form with data from + UserProfile, but we'll need to have a step where people who are signing + up re-enter their demographic data into the fields we specify. + * Users are only created here if they register to take an exam in person. + + The field names and lengths are modeled on the conventions and constraints + of Pearson's data import system, including oddities such as suffix having + a limit of 255 while last_name only gets 50. + """ + # Our own record keeping... + # user = models.ForeignKey(User, unique=True) + created_at = models.DateTimeField(auto_now_add=True, db_index=True) + updated_at = models.DateTimeField(auto_now=True, db_index=True) + # user_updated_at happens only when the user makes a change to their data, + # and is something Pearson needs to know to manage updates. Unlike + # updated_at, this will not get incremented when we do a batch data import. + user_updated_at = models.DateTimeField(db_index=True) + + # Unique ID given to us for this User by the Testing Center. It's null when + # we first create the User entry, and is assigned by Pearson later. + candidate_id = models.IntegerField(null=True, db_index=True) + + # Unique ID we assign our user for a the Test Center. + client_candidate_id = models.CharField(max_length=50, db_index=True) + + # Name + first_name = models.CharField(max_length=30, db_index=True) + last_name = models.CharField(max_length=50, db_index=True) + middle_name = models.CharField(max_length=30, blank=True) + suffix = models.CharField(max_length=255, blank=True) + salutation = models.CharField(max_length=50, blank=True) + + # Address + address_1 = models.CharField(max_length=40) + address_2 = models.CharField(max_length=40, blank=True) + address_3 = models.CharField(max_length=40, blank=True) + city = models.CharField(max_length=32, db_index=True) + # state example: HI -- they have an acceptable list that we'll just plug in + # state is required if you're in the US or Canada, but otherwise not. + state = models.CharField(max_length=20, blank=True, db_index=True) + # postal_code required if you're in the US or Canada + postal_code = models.CharField(max_length=16, blank=True, db_index=True) + # country is a ISO 3166-1 alpha-3 country code (e.g. "USA", "CAN", "MNG") + country = models.CharField(max_length=3, db_index=True) + + # Phone + phone = models.CharField(max_length=35) + extension = models.CharField(max_length=8, blank=True, db_index=True) + phone_country_code = models.CharField(max_length=3, db_index=True) + fax = models.CharField(max_length=35, blank=True) + # fax_country_code required *if* fax is present. + fax_country_code = models.CharField(max_length=3, blank=True) + + # Company + company_name = models.CharField(max_length=50, blank=True) + + @property + def email(self): + return "" # should return user.email, but stub for now ## TODO: Should be renamed to generic UserGroup, and possibly # Given an optional field for type of group From bbbd976671d616c1941d56bca3ea48522fb6d408 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Wed, 24 Oct 2012 15:02:05 -0400 Subject: [PATCH 003/228] Added some more sample data. The command to export to Pearson reads from the table, but currently just adds the entries it needs at runtime. --- .../management/commands/pearson_export_cdd.py | 99 ++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/common/djangoapps/student/management/commands/pearson_export_cdd.py b/common/djangoapps/student/management/commands/pearson_export_cdd.py index ce90531941..e9433f15d5 100644 --- a/common/djangoapps/student/management/commands/pearson_export_cdd.py +++ b/common/djangoapps/student/management/commands/pearson_export_cdd.py @@ -1,5 +1,6 @@ import csv -from collections import OrderedDict +import uuid +from collections import defaultdict, OrderedDict from datetime import datetime from django.core.management.base import BaseCommand, CommandError @@ -43,6 +44,7 @@ class Command(BaseCommand): print Command.help return + self.reset_sample_data() with open(args[0], "wb") as outfile: writer = csv.DictWriter(outfile, @@ -58,3 +60,98 @@ class Command(BaseCommand): record["LastUpdate"] = record["LastUpdate"].strftime("%Y/%m/%d %H:%M:%S") writer.writerow(record) + def reset_sample_data(self): + def make_sample(**kwargs): + data = dict((model_field, kwargs.get(model_field, "")) + for model_field in Command.CSV_TO_MODEL_FIELDS.values()) + return TestCenterUser(**data) + + def generate_id(): + sub_uuid = str(uuid.uuid4()).upper() + return sub_uuid[-12:-8] + '-' + sub_uuid[-8:-4] + '-' + sub_uuid[-4:] + + TestCenterUser.objects.all().delete() + + samples = [ + make_sample( + client_candidate_id=generate_id(), + first_name="Jack", + last_name="Doe", + middle_name="C", + address_1="11 Cambridge Center", + address_2="Suite 101", + city="Cambridge", + state="MA", + postal_code="02140", + country="USA", + phone="(617)555-5555", + phone_country_code="1", + user_updated_at=datetime.utcnow() + ), + make_sample( + client_candidate_id=generate_id(), + first_name="Clyde", + last_name="Smith", + middle_name="J", + suffix="Jr.", + salutation="Mr.", + address_1="1 Penny Lane", + city="Honolulu", + state="HI", + postal_code="96792", + country="USA", + phone="555-555-5555", + phone_country_code="1", + user_updated_at=datetime.utcnow() + ), + make_sample( + client_candidate_id=generate_id(), + first_name="Patty", + last_name="Lee", + salutation="Dr.", + address_1="P.O. Box 555", + city="Honolulu", + state="HI", + postal_code="96792", + country="USA", + phone="808-555-5555", + phone_country_code="1", + user_updated_at=datetime.utcnow() + ), + make_sample( + client_candidate_id=generate_id(), + first_name="Jimmy", + last_name="James", + address_1="2020 Palmer Blvd.", + city="Springfield", + state="MA", + postal_code="96792", + country="USA", + phone="917-555-5555", + phone_country_code="1", + extension="2039", + fax="917-555-5556", + fax_country_code="1", + company_name="ACME Traps", + user_updated_at=datetime.utcnow() + ), + make_sample( + client_candidate_id=generate_id(), + first_name="Yeong-Un", + last_name="Seo", + address_1="Duryu, Lotte 101", + address_2="Apt 55", + city="Daegu", + country="KOR", + phone="917-555-5555", + phone_country_code="011", + user_updated_at=datetime.utcnow() + ), + + ] + + for tcu in samples: + tcu.save() + + + \ No newline at end of file From e4daad432714a59dc334d2656540e7431a3e90c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20Rocha?= Date: Wed, 24 Oct 2012 20:30:54 -0400 Subject: [PATCH 004/228] Update Job Page with new positions --- lms/static/sass/multicourse/_jobs.scss | 8 +++ lms/templates/static_templates/jobs.html | 67 +++++++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/lms/static/sass/multicourse/_jobs.scss b/lms/static/sass/multicourse/_jobs.scss index d2f86e93a6..f9629232d6 100644 --- a/lms/static/sass/multicourse/_jobs.scss +++ b/lms/static/sass/multicourse/_jobs.scss @@ -136,6 +136,14 @@ margin-bottom: 15px; } + h4 { + font-size: 1.0em; + font-family: $sans-serif; + font-weight: 700; + margin-top: 25px; + margin-bottom: 10px; + } + ul { padding-left: 50px; } diff --git a/lms/templates/static_templates/jobs.html b/lms/templates/static_templates/jobs.html index f2b3b69cab..77f292b80f 100644 --- a/lms/templates/static_templates/jobs.html +++ b/lms/templates/static_templates/jobs.html @@ -1,5 +1,4 @@ <%namespace name='static' file='../static_content.html'/> - <%inherit file="../main.html" /> <%block name="title">Jobs @@ -50,12 +49,78 @@

If you are interested in this position, please send an email to jobs@edx.org

+
+
+

Learning Designer/Interaction Learning Designer

+

The Learning Designer will work as part of the content and development team to plan, develop and deliver highly engaging and media rich online courses. The learning designer will be a flexible thinker, able to determine and apply sound pedagogical strategies to unique situations and a diverse set of academic disciplines.

+

Specific Responsibilities include:

+
    +
  • Work with producers, product developers and course staff on implementing instructional design approaches in the development of media and other course materials.
  • +
  • Articulate learning objectives and align them to content design strategy and assessments.
  • +
  • Write effective instructional text, and audio and video scripts.
  • +
  • Coordinate workflows with video and content development team
  • +
  • Identify best practices and share these with the course staff and faculty as needed.
  • +
  • Create course communication style guides. Train and coach teaching staff on best practices for communication and discussion management.
  • +
  • Develop use case guides as needed on the use of edX courseware and new technologies.
  • +
  • Serve as a liaison to instructional design teams located at X universities.
  • +
  • Design peer review processes to be used by learners in selected courses.
  • +
  • Ability to apply game-based learning theory and design into selected courses as appropriate.
  • +
  • Use learning analytics and metrics to inform course design and revision process.
  • +
  • Work closely with the Content Research Director on articulating best practices for MOOC teaching and learning and course design.
  • +
  • Assist in the development of pilot courses used for sponsored research initiatives.
  • +
+

Qualifications:

+

Master's Degree in Educational Technology, Instructional Design or related field. Experience in higher education with additional experience in a start-up or research environment desirable. Excellent interpersonal and communication (written and verbal), project management, problem-solving and time management skills. The ability to be flexible with projects and to work on multiple courses essential. Ability to meet deadlines and manage expectations of constituents. Capacity to develop new and relevant technology skills.  Experience using game theory design and learning analytics to inform instructional design decisions and strategy.

+

Technical Skills:

+

Video and screencasting experience. LMS Platform experience, xml, HTML, CSS, Adobe Design Suite, Camtasia or Captivate experience. Experience with web 2.0 collaboration tools.

+

If you are interested in this position, please send an email to jobs@edx.org

+
+
+
+
+

Production Coordinator

+

The Production Coordinator supports video editors and course staff in all video related tasks, such as ingesting footage, transcoding, tracking live dates, transcriptions, organizing project deliverables and archiving completed projects.

+

Primary responsibilities:

+
    +
  • organize, track, and manage video and associated assets across the video workflow
  • +
  • manage project data and spreadsheets
  • +
  • route incoming source footage, and apply metadata tags
  • +
  • run encoding/transcoding jobs
  • +
  • prepare and process associated video assets, such as slides and image files
  • +
  • manage the transcription process
  • +
      +
    • traffic files among project staff and video transcription services
    • +
    • coordinate transcript reviews with course staff
    • +
    • integrate transcripts in course pages
    • +
    +
  • other video-related tasks as assigned.
  • +
+
+

Qualifications

+

The ideal candidate for the Production Coordinator position will have

+
    +
  • relentless attention to detailability to communicate and collaborate effectively across the organization
  • +
  • knowledge and understanding of digital media production tools and processes
  • +
  • experience with compression techniques, image processing, and presentation software preferred
  • +
  • proficiency with standard office applications
  • +
      +
    • spreadsheets
    • +
    • word processing
    • +
    • presentation
    • +
    +
  • experience with web publishing, e.g., HTML, XML, CSS, a plus
  • +
+

If you are interested in this position, please send an email to jobs@edx.org

+
+

Positions

How to Apply

E-mail your resume, coverletter and any other materials to jobs@edx.org

From 4a0e5f66d600b8811d4acb35ed7d0bae6e1d5859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20Rocha?= Date: Thu, 25 Oct 2012 10:06:47 -0400 Subject: [PATCH 005/228] Fix link in jobs page --- lms/templates/static_templates/jobs.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/templates/static_templates/jobs.html b/lms/templates/static_templates/jobs.html index 77f292b80f..6bebd7ace4 100644 --- a/lms/templates/static_templates/jobs.html +++ b/lms/templates/static_templates/jobs.html @@ -120,7 +120,7 @@ EdX Content Engineer Platform Developer Learning Designer - Product Coordinator + Production Coordinator

How to Apply

E-mail your resume, coverletter and any other materials to jobs@edx.org

From 2db84a757eac0fb679b0c3713d0bbd0587011a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20Rocha?= Date: Thu, 25 Oct 2012 12:43:57 -0400 Subject: [PATCH 006/228] Update to learning designer job description --- lms/templates/static_templates/jobs.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/templates/static_templates/jobs.html b/lms/templates/static_templates/jobs.html index 6bebd7ace4..a8afdb7712 100644 --- a/lms/templates/static_templates/jobs.html +++ b/lms/templates/static_templates/jobs.html @@ -52,7 +52,7 @@

Learning Designer/Interaction Learning Designer

-

The Learning Designer will work as part of the content and development team to plan, develop and deliver highly engaging and media rich online courses. The learning designer will be a flexible thinker, able to determine and apply sound pedagogical strategies to unique situations and a diverse set of academic disciplines.

+

The Learning Designer will work as part of the content and development team to plan, develop and deliver highly engaging and media rich online courses. The learning designer will be a flexible thinker, able to determine and apply sound pedagogical strategies to unique situations and a diverse set of academic disciplines. This is a 6-12 months contract opportunity.

Specific Responsibilities include:

  • Work with producers, product developers and course staff on implementing instructional design approaches in the development of media and other course materials.
  • From 7a84bed916b035f11a4923031c6fb920cf698406 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Fri, 26 Oct 2012 15:50:32 -0400 Subject: [PATCH 007/228] hackish EAD export support --- .../management/commands/pearson_export_cdd.py | 5 +- .../management/commands/pearson_export_ead.py | 162 ++++++++++++++++++ 2 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 common/djangoapps/student/management/commands/pearson_export_ead.py diff --git a/common/djangoapps/student/management/commands/pearson_export_cdd.py b/common/djangoapps/student/management/commands/pearson_export_cdd.py index e9433f15d5..b10e92d92d 100644 --- a/common/djangoapps/student/management/commands/pearson_export_cdd.py +++ b/common/djangoapps/student/management/commands/pearson_export_cdd.py @@ -67,10 +67,9 @@ class Command(BaseCommand): return TestCenterUser(**data) def generate_id(): - sub_uuid = str(uuid.uuid4()).upper() - return sub_uuid[-12:-8] + '-' + sub_uuid[-8:-4] + '-' + sub_uuid[-4:] + return "edX{:012}".format(uuid.uuid4().int % (10**12)) - TestCenterUser.objects.all().delete() + # TestCenterUser.objects.all().delete() samples = [ make_sample( diff --git a/common/djangoapps/student/management/commands/pearson_export_ead.py b/common/djangoapps/student/management/commands/pearson_export_ead.py new file mode 100644 index 0000000000..33ab837d1b --- /dev/null +++ b/common/djangoapps/student/management/commands/pearson_export_ead.py @@ -0,0 +1,162 @@ +import csv +import uuid +from collections import defaultdict, OrderedDict +from datetime import datetime + +from django.core.management.base import BaseCommand, CommandError + +from student.models import TestCenterUser + +def generate_id(): + return "{:012}".format(uuid.uuid4().int % (10**12)) + +class Command(BaseCommand): + args = '' + help = """ + Export user information from TestCenterUser model into a tab delimited + text file with a format that Pearson expects. + """ + FIELDS = [ + 'AuthorizationTransactionType', + 'AuthorizationID', + 'ClientAuthorizationID', + 'ClientCandidateID', + 'ExamAuthorizationCount', + 'ExamSeriesCode', + 'EligibilityApptDateFirst', + 'EligibilityApptDateLast', + 'LastUpdate', + ] + + def handle(self, *args, **kwargs): + if len(args) < 1: + print Command.help + return + + # self.reset_sample_data() + + with open(args[0], "wb") as outfile: + writer = csv.DictWriter(outfile, + Command.FIELDS, + delimiter="\t", + quoting=csv.QUOTE_MINIMAL, + extrasaction='ignore') + writer.writeheader() + for tcu in TestCenterUser.objects.order_by('id')[:5]: + record = defaultdict( + lambda: "", + AuthorizationTransactionType="Add", + ClientAuthorizationID=generate_id(), + ClientCandidateID=tcu.client_candidate_id, + ExamAuthorizationCount="1", + ExamSeriesCode="MIT 6.002-001", + EligibilityApptDateFirst="2012/12/15", + EligibilityApptDateLast="2012/12/30", + LastUpdate=datetime.utcnow().strftime("%Y/%m/%d %H:%M:%S") + ) + writer.writerow(record) + + + + + + + + + + + + + + + def reset_sample_data(self): + def make_sample(**kwargs): + data = dict((model_field, kwargs.get(model_field, "")) + for model_field in Command.CSV_TO_MODEL_FIELDS.values()) + return TestCenterUser(**data) + + # TestCenterUser.objects.all().delete() + + samples = [ + make_sample( + client_candidate_id=generate_id(), + first_name="Jack", + last_name="Doe", + middle_name="C", + address_1="11 Cambridge Center", + address_2="Suite 101", + city="Cambridge", + state="MA", + postal_code="02140", + country="USA", + phone="(617)555-5555", + phone_country_code="1", + user_updated_at=datetime.utcnow() + ), + make_sample( + client_candidate_id=generate_id(), + first_name="Clyde", + last_name="Smith", + middle_name="J", + suffix="Jr.", + salutation="Mr.", + address_1="1 Penny Lane", + city="Honolulu", + state="HI", + postal_code="96792", + country="USA", + phone="555-555-5555", + phone_country_code="1", + user_updated_at=datetime.utcnow() + ), + make_sample( + client_candidate_id=generate_id(), + first_name="Patty", + last_name="Lee", + salutation="Dr.", + address_1="P.O. Box 555", + city="Honolulu", + state="HI", + postal_code="96792", + country="USA", + phone="808-555-5555", + phone_country_code="1", + user_updated_at=datetime.utcnow() + ), + make_sample( + client_candidate_id=generate_id(), + first_name="Jimmy", + last_name="James", + address_1="2020 Palmer Blvd.", + city="Springfield", + state="MA", + postal_code="96792", + country="USA", + phone="917-555-5555", + phone_country_code="1", + extension="2039", + fax="917-555-5556", + fax_country_code="1", + company_name="ACME Traps", + user_updated_at=datetime.utcnow() + ), + make_sample( + client_candidate_id=generate_id(), + first_name="Yeong-Un", + last_name="Seo", + address_1="Duryu, Lotte 101", + address_2="Apt 55", + city="Daegu", + country="KOR", + phone="917-555-5555", + phone_country_code="011", + user_updated_at=datetime.utcnow() + ), + + ] + + for tcu in samples: + tcu.save() + + + \ No newline at end of file From 6d9fe76dbb637cfca407bde24abe78329459b720 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 23 Oct 2012 17:35:19 -0400 Subject: [PATCH 008/228] wip. adding tests, basic refactor --- common/lib/capa/capa/inputtypes.py | 70 ++++++++----------- common/lib/capa/capa/tests/test_inputtypes.py | 60 +++++++++++----- 2 files changed, 71 insertions(+), 59 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 40fe24434e..55115e66d8 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -195,48 +195,37 @@ class OptionInput(InputTypeBase): template = "optioninput.html" tags = ['optioninput'] + def __init__(self, system, xml, state): + super(OptionInput, self).__init__(system, xml, state) + + # Extract the options... + options = self.xml.get('options') + if not options: + raise Exception( + "[courseware.capa.inputtypes.optioninput] Missing options specification in " + + etree.tostring(self.xml)) + + # parse the set of possible options + oset = shlex.shlex(options[1:-1]) + oset.quotes = "'" + oset.whitespace = "," + oset = [x[1:-1] for x in list(oset)] + + # make ordered list with (key, value) same + self.osetdict = [(oset[x], oset[x]) for x in range(len(oset))] + # TODO: allow ordering to be randomized + def _get_render_context(self): - return _optioninput(self.xml, self.value, self.status, self.system.render_template, self.msg) - -def optioninput(element, value, status, render_template, msg=''): - context = _optioninput(element, value, status, render_template, msg) - html = render_template("optioninput.html", context) - return etree.XML(html) - -def _optioninput(element, value, status, render_template, msg=''): - """ - Select option input type. - - Example: - - The location of the sky - """ - eid = element.get('id') - options = element.get('options') - if not options: - raise Exception( - "[courseware.capa.inputtypes.optioninput] Missing options specification in " - + etree.tostring(element)) - - # parse the set of possible options - oset = shlex.shlex(options[1:-1]) - oset.quotes = "'" - oset.whitespace = "," - oset = [x[1:-1] for x in list(oset)] - - # make ordered list with (key, value) same - osetdict = [(oset[x], oset[x]) for x in range(len(oset))] - # TODO: allow ordering to be randomized - - context = {'id': eid, - 'value': value, - 'state': status, - 'msg': msg, - 'options': osetdict, - 'inline': element.get('inline',''), - } - return context + context = { + 'id': self.id, + 'value': self.value, + 'state': self.status, + 'msg': self.msg, + 'options': self.osetdict, + 'inline': self.xml.get('inline',''), + } + return context register_input_class(OptionInput) @@ -245,7 +234,6 @@ register_input_class(OptionInput) # TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of # desired semantics. -# @register_render_function def choicegroup(element, value, status, render_template, msg=''): ''' Radio button inputs: multiple choice or true/false diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 9ef642d468..79cd9b6c98 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -27,24 +27,6 @@ class OptionInputTest(unittest.TestCase): ''' Make sure option inputs work ''' - def test_rendering_new(self): - xml = """""" - element = etree.fromstring(xml) - - value = 'Down' - status = 'answered' - context = inputtypes._optioninput(element, value, status, test_system.render_template) - print 'context: ', context - - expected = {'value': 'Down', - 'options': [('Up', 'Up'), ('Down', 'Down')], - 'state': 'answered', - 'msg': '', - 'inline': '', - 'id': 'sky_input'} - - self.assertEqual(context, expected) - def test_rendering(self): xml_str = """""" @@ -66,3 +48,45 @@ class OptionInputTest(unittest.TestCase): self.assertEqual(context, expected) +class ChoiceGroupTest(unittest.TestCase): + ''' + Test choice groups. + ''' + def test_mult_choice(self): + xml_str = """ + + + This is foil One. + + + This is foil Two. + + + This is foil Three. + + + This is foil Four. + + + This is foil Five. + + + """ + element = etree.fromstring(xml_str) + + state = {'value': 'Down', + 'id': 'sky_input', + 'status': 'answered'} + option_input = inputtypes.OptionInput(system, element, state) + + context = option_input._get_render_context() + + expected = {'value': 'Down', + 'options': [('Up', 'Up'), ('Down', 'Down')], + 'state': 'answered', + 'msg': '', + 'inline': '', + 'id': 'sky_input'} + + self.assertEqual(context, expected) + From 009d6c2e019f9f8344bcb0673d5f3bbd15d98683 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 24 Oct 2012 14:23:02 -0400 Subject: [PATCH 009/228] ChoiceGroup refactor - Make it into a class. - Combine ChoiceGroup, RadioGroup, CheckboxGroup implementation. (All three tags still work--this just unifies the code) - add tests --- common/lib/capa/capa/inputtypes.py | 151 ++++++++---------- common/lib/capa/capa/tests/test_inputtypes.py | 93 ++++++++--- 2 files changed, 134 insertions(+), 110 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 55115e66d8..acff3abf6a 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -97,8 +97,8 @@ class InputTypeBase(object): have a render_template function. - xml : Element tree of this Input element - state : a dictionary with optional keys: - * 'value' - * 'id' + * 'value' -- the current value of this input (what the student entered last time) + * 'id' -- the id of this input, typically "{problem-location}_{response-num}_{input-num}" * 'status' (answered, unanswered, unsubmitted) * 'feedback' (dictionary containing keys for hints, errors, or other feedback from previous attempt. Specifically 'message', 'hint', 'hintmode'. If 'hintmode' @@ -234,49 +234,70 @@ register_input_class(OptionInput) # TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of # desired semantics. -def choicegroup(element, value, status, render_template, msg=''): - ''' - Radio button inputs: multiple choice or true/false + +class ChoiceGroup(InputTypeBase): + """ + Radio button or checkbox inputs: multiple choice or true/false TODO: allow order of choices to be randomized, following lon-capa spec. Use "location" attribute, ie random, top, bottom. - ''' - eid = element.get('id') - if element.get('type') == "MultipleChoice": - element_type = "radio" - elif element.get('type') == "TrueFalse": - element_type = "checkbox" - else: - element_type = "radio" - choices = [] - for choice in element: - if not choice.tag == 'choice': - raise Exception("[courseware.capa.inputtypes.choicegroup] " - "Error: only tags should be immediate children " - "of a , found %s instead" % choice.tag) - ctext = "" - # TODO: what if choice[0] has math tags in it? - ctext += ''.join([etree.tostring(x) for x in choice]) - if choice.text is not None: - # TODO: fix order? - ctext += choice.text - choices.append((choice.get("name"), ctext)) - context = {'id': eid, - 'value': value, - 'state': status, - 'input_type': element_type, - 'choices': choices, - 'name_array_suffix': ''} - html = render_template("choicegroup.html", context) - return etree.XML(html) -_reg(choicegroup) + Example: + + + + This is foil One. + + + This is foil Two. + + + This is foil Three. + + + """ + template = "choicegroup.html" + tags = ['choicegroup', 'radiogroup', 'checkboxgroup'] + + def __init__(self, system, xml, state): + super(ChoiceGroup, self).__init__(system, xml, state) + + if self.tag == 'choicegroup': + self.suffix = '' + if self.xml.get('type') == "MultipleChoice": + self.element_type = "radio" + elif self.xml.get('type') == "TrueFalse": + # Huh? Why TrueFalse->checkbox? Each input can be true / false separately? + self.element_type = "checkbox" + else: + self.element_type = "radio" + + elif self.tag == 'radiogroup': + self.element_type = "radio" + self.suffix = '[]' + elif self.tag == 'checkboxgroup': + self.element_type = "checkbox" + self.suffix = '[]' + else: + raise Exception("ChoiceGroup: unexpected tag {0}".format(self.tag)) + + self.choices = extract_choices(self.xml) + + def _get_render_context(self): + context = {'id': self.id, + 'value': self.value, + 'state': self.status, + 'input_type': self.element_type, + 'choices': self.choices, + 'name_array_suffix': self.suffix} + return context -#----------------------------------------------------------------------------- def extract_choices(element): ''' - Extracts choices for a few input types, such as radiogroup and - checkboxgroup. + Extracts choices for a few input types, such as ChoiceGroup, RadioGroup and + CheckboxGroup. + + returns list of (choice_name, choice_text) tuples TODO: allow order of choices to be randomized, following lon-capa spec. Use "location" attribute, ie random, top, bottom. @@ -285,63 +306,25 @@ def extract_choices(element): choices = [] for choice in element: - if not choice.tag == 'choice': + if choice.tag != 'choice': raise Exception("[courseware.capa.inputtypes.extract_choices] \ Expected a tag; got %s instead" % choice.tag) choice_text = ''.join([etree.tostring(x) for x in choice]) + if choice.text is not None: + # TODO: fix order? + choice_text += choice.text choices.append((choice.get("name"), choice_text)) return choices + + +register_input_class(ChoiceGroup) -# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of -# desired semantics. -def radiogroup(element, value, status, render_template, msg=''): - ''' - Radio button inputs: (multiple choice) - ''' +#----------------------------------------------------------------------------- - eid = element.get('id') - - choices = extract_choices(element) - - context = {'id': eid, - 'value': value, - 'state': status, - 'input_type': 'radio', - 'choices': choices, - 'name_array_suffix': '[]'} - - html = render_template("choicegroup.html", context) - return etree.XML(html) - - -_reg(radiogroup) - -# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of -# desired semantics. -def checkboxgroup(element, value, status, render_template, msg=''): - ''' - Checkbox inputs: (select one or more choices) - ''' - - eid = element.get('id') - - choices = extract_choices(element) - - context = {'id': eid, - 'value': value, - 'state': status, - 'input_type': 'checkbox', - 'choices': choices, - 'name_array_suffix': '[]'} - - html = render_template("choicegroup.html", context) - return etree.XML(html) - -_reg(checkboxgroup) def javascriptinput(element, value, status, render_template, msg='null'): ''' diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 79cd9b6c98..833cc396c2 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -35,7 +35,7 @@ class OptionInputTest(unittest.TestCase): state = {'value': 'Down', 'id': 'sky_input', 'status': 'answered'} - option_input = inputtypes.OptionInput(system, element, state) + option_input = inputtypes.get_class_for_tag('optioninput')(system, element, state) context = option_input._get_render_context() @@ -53,40 +53,81 @@ class ChoiceGroupTest(unittest.TestCase): Test choice groups. ''' def test_mult_choice(self): - xml_str = """ - - - This is foil One. - - - This is foil Two. - - - This is foil Three. - - - This is foil Four. - - - This is foil Five. - + xml_template = """ + + This is foil One. + This is foil Two. + This is foil Three. """ + + def check_type(type_str, expected_input_type): + print "checking for type_str='{0}'".format(type_str) + xml_str = xml_template.format(type_str) + + element = etree.fromstring(xml_str) + + state = {'value': 'foil3', + 'id': 'sky_input', + 'status': 'answered'} + + option_input = inputtypes.get_class_for_tag('choicegroup')(system, element, state) + + context = option_input._get_render_context() + + expected = {'id': 'sky_input', + 'value': 'foil3', + 'state': 'answered', + 'input_type': expected_input_type, + 'choices': [('foil1', 'This is foil One.'), + ('foil2', 'This is foil Two.'), + ('foil3', 'This is foil Three.'),], + 'name_array_suffix': '', # what is this for?? + } + + self.assertEqual(context, expected) + + check_type('', 'radio') + check_type('type=""', 'radio') + check_type('type="MultipleChoice"', 'radio') + check_type('type="TrueFalse"', 'checkbox') + # fallback. + check_type('type="StrangeUnknown"', 'radio') + + + def check_group(self, tag, expected_input_type, expected_suffix): + xml_str = """ + <{tag}> + This is foil One. + This is foil Two. + This is foil Three. + + """.format(tag=tag) + element = etree.fromstring(xml_str) - state = {'value': 'Down', + state = {'value': 'foil3', 'id': 'sky_input', 'status': 'answered'} - option_input = inputtypes.OptionInput(system, element, state) - context = option_input._get_render_context() + the_input = inputtypes.get_class_for_tag(tag)(system, element, state) - expected = {'value': 'Down', - 'options': [('Up', 'Up'), ('Down', 'Down')], + context = the_input._get_render_context() + + expected = {'id': 'sky_input', + 'value': 'foil3', 'state': 'answered', - 'msg': '', - 'inline': '', - 'id': 'sky_input'} + 'input_type': expected_input_type, + 'choices': [('foil1', 'This is foil One.'), + ('foil2', 'This is foil Two.'), + ('foil3', 'This is foil Three.'),], + 'name_array_suffix': expected_suffix, # what is this for?? + } self.assertEqual(context, expected) + def test_radiogroup(self): + self.check_group('radiogroup', 'radio', '[]') + + def test_checkboxgroup(self): + self.check_group('checkboxgroup', 'checkbox', '[]') From d0a9b231a49a101dbf6b209979590dbe5023758b Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 24 Oct 2012 15:27:45 -0400 Subject: [PATCH 010/228] Turn javascriptinput into a class, add tests --- common/lib/capa/capa/inputtypes.py | 55 +++++++++++-------- common/lib/capa/capa/tests/test_inputtypes.py | 40 +++++++++++++- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index acff3abf6a..43259b3f0b 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -318,7 +318,7 @@ def extract_choices(element): choices.append((choice.get("name"), choice_text)) return choices - + register_input_class(ChoiceGroup) @@ -326,37 +326,44 @@ register_input_class(ChoiceGroup) #----------------------------------------------------------------------------- -def javascriptinput(element, value, status, render_template, msg='null'): - ''' +class JavascriptInput(InputTypeBase): + """ Hidden field for javascript to communicate via; also loads the required scripts for rendering the problem and passes data to the problem. - ''' - eid = element.get('id') - params = element.get('params') - problem_state = element.get('problem_state') - display_class = element.get('display_class') - display_file = element.get('display_file') + """ - # Need to provide a value that JSON can parse if there is no - # student-supplied value yet. - if value == "": - value = 'null' + template = "javascriptinput.html" + tags = ['javascriptinput'] - escapedict = {'"': '"'} - value = saxutils.escape(value, escapedict) - msg = saxutils.escape(msg, escapedict) - context = {'id': eid, - 'params': params, - 'display_file': display_file, - 'display_class': display_class, - 'problem_state': problem_state, + def __init__(self, system, xml, state): + super(JavascriptInput, self).__init__(system, xml, state) + # Need to provide a value that JSON can parse if there is no + # student-supplied value yet. + if self.value == "": + self.value = 'null' + + self.params = self.xml.get('params') + self.problem_state = self.xml.get('problem_state') + self.display_class = self.xml.get('display_class') + self.display_file = self.xml.get('display_file') + + + def _get_render_context(self): + escapedict = {'"': '"'} + value = saxutils.escape(self.value, escapedict) + msg = saxutils.escape(self.msg, escapedict) + + context = {'id': self.id, + 'params': self.params, + 'display_file': self.display_file, + 'display_class': self.display_class, + 'problem_state': self.problem_state, 'value': value, 'evaluation': msg, } - html = render_template("javascriptinput.html", context) - return etree.XML(html) + return context -_reg(javascriptinput) +register_input_class(JavascriptInput) def textline(element, value, status, render_template, msg=""): diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 833cc396c2..fa3f7d0595 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -8,6 +8,7 @@ from mock import Mock from nose.plugins.skip import SkipTest import os import unittest +import xml.sax.saxutils as saxutils from . import test_system from capa import inputtypes @@ -128,6 +129,43 @@ class ChoiceGroupTest(unittest.TestCase): def test_radiogroup(self): self.check_group('radiogroup', 'radio', '[]') - + def test_checkboxgroup(self): self.check_group('checkboxgroup', 'checkbox', '[]') + + + +class JavascriptInputTest(unittest.TestCase): + ''' + The javascript input is a pretty straightforward pass-thru, but test it anyway + ''' + + def test_rendering(self): + params = "(1,2,3)" + + problem_state = "abc12',12&hi" + display_class = "a_class" + display_file = "my_files/hi.js" + + xml_str = """""".format( + params=params, + ps=saxutils.quoteattr(problem_state)[1:-1], # don't want the outer quotes + dc=display_class, df=display_file) + + element = etree.fromstring(xml_str) + + state = {'value': '3',} + the_input = inputtypes.get_class_for_tag('javascriptinput')(system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'params': params, + 'display_file': display_file, + 'display_class': display_class, + 'problem_state': problem_state, + 'value': '3', + 'evaluation': '',} + + self.assertEqual(context, expected) From 0c6f6f873b470eec42d13b9fad0e56545c6b197c Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 24 Oct 2012 17:14:57 -0400 Subject: [PATCH 011/228] add a template for a new input type class --- common/lib/capa/capa/inputtypes.py | 32 ++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 43259b3f0b..ed7125859b 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -1,3 +1,29 @@ + + +# template: +''' +class ClassName(InputTypeBase): + """ + """ + + template = "tagname.html" + tags = ['tagname'] + + def __init__(self, system, xml, state): + super(ClassName, self).__init__(system, xml, state) + + + def _get_render_context(self): + + context = {'id': self.id, + + } + return context + +register_input_class(ClassName) +''' + + # # File: courseware/capa/inputtypes.py # @@ -366,6 +392,8 @@ class JavascriptInput(InputTypeBase): register_input_class(JavascriptInput) +#----------------------------------------------------------------------------- + def textline(element, value, status, render_template, msg=""): ''' Simple text line input, with optional size specification. @@ -677,8 +705,8 @@ def imageinput(element, value, status, render_template, msg=''): 'src': src, 'gx': gx, 'gy': gy, - 'state': status, # to change - 'msg': msg, # to change + 'state': status, # to change + 'msg': msg, # to change } html = render_template("imageinput.html", context) return etree.XML(html) From c2682273a81a1713617460ad9f480190e248ced2 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 24 Oct 2012 18:55:13 -0400 Subject: [PATCH 012/228] Convert textline input into class. Unify math and non-math code and templates. Add tests. --- common/lib/capa/capa/inputtypes.py | 130 +++++++----------- common/lib/capa/capa/templates/textinput.html | 43 ++++-- .../capa/templates/textinput_dynamath.html | 50 ------- common/lib/capa/capa/tests/test_inputtypes.py | 57 ++++++++ 4 files changed, 137 insertions(+), 143 deletions(-) delete mode 100644 common/lib/capa/capa/templates/textinput_dynamath.html diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index ed7125859b..82b5e04843 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -16,7 +16,7 @@ class ClassName(InputTypeBase): def _get_render_context(self): context = {'id': self.id, - + } return context @@ -394,92 +394,58 @@ register_input_class(JavascriptInput) #----------------------------------------------------------------------------- -def textline(element, value, status, render_template, msg=""): - ''' - Simple text line input, with optional size specification. - ''' - # TODO: 'dojs' flag is temporary, for backwards compatibility with 8.02x - if element.get('math') or element.get('dojs'): - return textline_dynamath(element, value, status, render_template, msg) - eid = element.get('id') - if eid is None: - msg = 'textline has no id: it probably appears outside of a known response type' - msg += "\nSee problem XML source line %s" % getattr(element, 'sourceline', '') - raise Exception(msg) +class TextLine(InputTypeBase): + """ - count = int(eid.split('_')[-2]) - 1 # HACK - size = element.get('size') - # if specified, then textline is hidden and id is stored in div of name given by hidden - hidden = element.get('hidden', '') + """ - # Escape answers with quotes, so they don't crash the system! - escapedict = {'"': '"'} - value = saxutils.escape(value, escapedict) + template = "textinput.html" + tags = ['textline'] - context = {'id': eid, - 'value': value, - 'state': status, - 'count': count, - 'size': size, - 'msg': msg, - 'hidden': hidden, - 'inline': element.get('inline',''), + def __init__(self, system, xml, state): + super(TextLine, self).__init__(system, xml, state) + self.size = self.xml.get('size') + + # if specified, then textline is hidden and input id is stored + # in div with name=self.hidden. + self.hidden = self.xml.get('hidden', False) + + # TODO (vshnayder): can we get rid of inline? Was it one of + # the styling hacks early this semester? + self.inline = self.xml.get('inline', False) + + # TODO: 'dojs' flag is temporary, for backwards compatibility with 8.02x + self.do_math = bool(self.xml.get('math') or self.xml.get('dojs')) + # TODO: do math checking using ajax instead of using js, so + # that we only have one math parser. + self.preprocessor = None + if self.do_math: + # Preprocessor to insert between raw input and Mathjax + self.preprocessor = {'class_name': self.xml.get('preprocessorClassName',''), + 'script_src': self.xml.get('preprocessorSrc','')} + if '' in self.preprocessor.values(): + self.preprocessor = None + + + + def _get_render_context(self): + # Escape answers with quotes, so they don't crash the system! + escapedict = {'"': '"'} + value = saxutils.escape(self.value, escapedict) + + context = {'id': self.id, + 'value': value, + 'state': self.status, + 'size': self.size, + 'msg': self.msg, + 'hidden': self.hidden, + 'inline': self.inline, + 'do_math': self.do_math, + 'preprocessor': self.preprocessor, } + return context - html = render_template("textinput.html", context) - try: - xhtml = etree.XML(html) - except Exception as err: - # TODO: needs to be self.system.DEBUG - but can't access system - if True: - log.debug('[inputtypes.textline] failed to parse XML for:\n%s' % html) - raise - return xhtml - -_reg(textline) - -#----------------------------------------------------------------------------- - - -def textline_dynamath(element, value, status, render_template, msg=''): - ''' - Text line input with dynamic math display (equation rendered on client in real time - during input). - ''' - # TODO: Make a wrapper for - # TODO: Make an AJAX loop to confirm equation is okay in real-time as user types - ''' - textline is used for simple one-line inputs, like formularesponse and symbolicresponse. - uses a `{::}` - and a hidden textarea with id=input_eid_fromjs for the mathjax rendering and return. - ''' - eid = element.get('id') - count = int(eid.split('_')[-2]) - 1 # HACK - size = element.get('size') - # if specified, then textline is hidden and id is stored in div of name given by hidden - hidden = element.get('hidden', '') - - # Preprocessor to insert between raw input and Mathjax - preprocessor = {'class_name': element.get('preprocessorClassName',''), - 'script_src': element.get('preprocessorSrc','')} - if '' in preprocessor.values(): - preprocessor = None - - # Escape characters in student input for safe XML parsing - escapedict = {'"': '"'} - value = saxutils.escape(value, escapedict) - - context = {'id': eid, - 'value': value, - 'state': status, - 'count': count, - 'size': size, - 'msg': msg, - 'hidden': hidden, - 'preprocessor': preprocessor,} - html = render_template("textinput_dynamath.html", context) - return etree.XML(html) - +register_input_class(TextLine) #----------------------------------------------------------------------------- def filesubmission(element, value, status, render_template, msg=''): diff --git a/common/lib/capa/capa/templates/textinput.html b/common/lib/capa/capa/templates/textinput.html index 9b66654117..14d54d4cde 100644 --- a/common/lib/capa/capa/templates/textinput.html +++ b/common/lib/capa/capa/templates/textinput.html @@ -1,6 +1,18 @@ <% doinline = "inline" if inline else "" %> -
    +<% +# TODO: +# Is id inputtype_${id} vs textinput_${id} important? +# Is class capa_inputtype vs textinput important? +# should really just use one. +%> +
    + + % if preprocessor is not None: +
    +
    + % endif + % if state == 'unsubmitted':
    % elif state == 'correct': @@ -15,12 +27,15 @@ % endif

    @@ -35,12 +50,18 @@ % endif

    -

    +

    + + % if do_math: +
    `{::}`
    + % endif + +% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: +
    +% endif % if msg: ${msg|n} % endif -% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete'] or hidden: -
    -% endif +
    diff --git a/common/lib/capa/capa/templates/textinput_dynamath.html b/common/lib/capa/capa/templates/textinput_dynamath.html deleted file mode 100644 index d1de22ef27..0000000000 --- a/common/lib/capa/capa/templates/textinput_dynamath.html +++ /dev/null @@ -1,50 +0,0 @@ -### -### version of textline.html which does dynamic math -### -
    - - % if preprocessor is not None: -
    -
    - % endif - - % if state == 'unsubmitted': -
    - % elif state == 'correct': -
    - % elif state == 'incorrect': -
    - % elif state == 'incomplete': -
    - % endif - % if hidden: -
    - % endif - - -

    - % if state == 'unsubmitted': - unanswered - % elif state == 'correct': - correct - % elif state == 'incorrect': - incorrect - % elif state == 'incomplete': - incomplete - % endif -

    - -

    - -
    `{::}`
    - -
    - - % if msg: - ${msg|n} - % endif -
    diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index fa3f7d0595..30a0ded1ee 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -169,3 +169,60 @@ class JavascriptInputTest(unittest.TestCase): 'evaluation': '',} self.assertEqual(context, expected) + + +class TextLineTest(unittest.TestCase): + ''' + Check that textline inputs work, with and without math. + ''' + + def test_rendering(self): + size = "42" + xml_str = """""".format(size=size) + + element = etree.fromstring(xml_str) + + state = {'value': 'BumbleBee',} + the_input = inputtypes.get_class_for_tag('textline')(system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'value': 'BumbleBee', + 'state': 'unanswered', + 'size': size, + 'msg': '', + 'hidden': False, + 'inline': False, + 'do_math': False, + 'preprocessor': None} + self.assertEqual(context, expected) + + + def test_math_rendering(self): + size = "42" + preprocessorClass = "preParty" + script = "foo/party.js" + + xml_str = """""".format(size=size, pp=preprocessorClass, sc=script) + + element = etree.fromstring(xml_str) + + state = {'value': 'BumbleBee',} + the_input = inputtypes.get_class_for_tag('textline')(system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'value': 'BumbleBee', + 'state': 'unanswered', + 'size': size, + 'msg': '', + 'hidden': False, + 'inline': False, + 'do_math': True, + 'preprocessor': {'class_name': preprocessorClass, + 'script_src': script}} + self.assertEqual(context, expected) From c8bc46b6faa6d4b24354e045912257ac19b3a15b Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 24 Oct 2012 19:14:29 -0400 Subject: [PATCH 013/228] Convert FileSubmission input to be a class. - add test --- common/lib/capa/capa/inputtypes.py | 62 +++++++++++-------- common/lib/capa/capa/tests/test_inputtypes.py | 45 +++++++++++++- 2 files changed, 79 insertions(+), 28 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 82b5e04843..2eb5bf45bc 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -448,36 +448,44 @@ class TextLine(InputTypeBase): register_input_class(TextLine) #----------------------------------------------------------------------------- -def filesubmission(element, value, status, render_template, msg=''): - ''' - Upload a single file (e.g. for programming assignments) - ''' - eid = element.get('id') - escapedict = {'"': '"'} - allowed_files = json.dumps(element.get('allowed_files', '').split()) - allowed_files = saxutils.escape(allowed_files, escapedict) - required_files = json.dumps(element.get('required_files', '').split()) - required_files = saxutils.escape(required_files, escapedict) - # Check if problem has been queued - queue_len = 0 - # Flag indicating that the problem has been queued, 'msg' is length of queue - if status == 'incomplete': - status = 'queued' - queue_len = msg - msg = 'Submitted to grader.' +class FileSubmission(InputTypeBase): + """ + Upload some files (e.g. for programming assignments) + """ - context = { 'id': eid, - 'state': status, - 'msg': msg, - 'value': value, - 'queue_len': queue_len, - 'allowed_files': allowed_files, - 'required_files': required_files,} - html = render_template("filesubmission.html", context) - return etree.XML(html) + template = "filesubmission.html" + tags = ['filesubmission'] -_reg(filesubmission) + def __init__(self, system, xml, state): + super(FileSubmission, self).__init__(system, xml, state) + escapedict = {'"': '"'} + self.allowed_files = json.dumps(xml.get('allowed_files', '').split()) + self.allowed_files = saxutils.escape(self.allowed_files, escapedict) + self.required_files = json.dumps(xml.get('required_files', '').split()) + self.required_files = saxutils.escape(self.required_files, escapedict) + + # Check if problem has been queued + queue_len = 0 + # Flag indicating that the problem has been queued, 'msg' is length of queue + if self.status == 'incomplete': + self.status = 'queued' + self.queue_len = self.msg + self.msg = 'Submitted to grader.' + + + def _get_render_context(self): + + context = {'id': self.id, + 'state': self.status, + 'msg': self.msg, + 'value': self.value, + 'queue_len': self.queue_len, + 'allowed_files': self.allowed_files, + 'required_files': self.required_files,} + return context + +register_input_class(FileSubmission) #----------------------------------------------------------------------------- diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 30a0ded1ee..759ec8bdfa 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -24,6 +24,9 @@ def tst_render_template(template, context): system = Mock(render_template=tst_render_template) +def quote_attr(s): + return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes + class OptionInputTest(unittest.TestCase): ''' Make sure option inputs work @@ -150,7 +153,7 @@ class JavascriptInputTest(unittest.TestCase): xml_str = """""".format( params=params, - ps=saxutils.quoteattr(problem_state)[1:-1], # don't want the outer quotes + ps=quote_attr(problem_state), dc=display_class, df=display_file) element = etree.fromstring(xml_str) @@ -226,3 +229,43 @@ class TextLineTest(unittest.TestCase): 'preprocessor': {'class_name': preprocessorClass, 'script_src': script}} self.assertEqual(context, expected) + + +class FileSubmissionTest(unittest.TestCase): + ''' + Check that file submission inputs work + ''' + + def test_rendering(self): + allowed_files = "runme.py nooooo.rb ohai.java" + required_files = "cookies.py" + + xml_str = """""".format(af=allowed_files, + rf=required_files,) + + + element = etree.fromstring(xml_str) + + escapedict = {'"': '"'} + esc = lambda s: saxutils.escape(s, escapedict) + + state = {'value': 'BumbleBee.py', + 'status': 'incomplete', + 'feedback' : {'message': '3'}, } + the_input = inputtypes.get_class_for_tag('filesubmission')(system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'state': 'queued', + 'msg': 'Submitted to grader.', + 'value': 'BumbleBee.py', + 'queue_len': '3', + 'allowed_files': esc('["runme.py", "nooooo.rb", "ohai.java"]'), + 'required_files': esc('["cookies.py"]')} + + self.assertEqual(context, expected) + From 935e370184e0c99510f3702af060561ff34dcf86 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 25 Oct 2012 14:56:16 -0400 Subject: [PATCH 014/228] Refactor textbox. - tests - rename it codeinput, with textbox still supported too --- common/lib/capa/capa/inputtypes.py | 99 +++++++++---------- .../{textbox.html => codeinput.html} | 0 common/lib/capa/capa/tests/test_inputtypes.py | 51 +++++++++- 3 files changed, 97 insertions(+), 53 deletions(-) rename common/lib/capa/capa/templates/{textbox.html => codeinput.html} (100%) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 2eb5bf45bc..bbbddc0fb6 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -489,66 +489,61 @@ register_input_class(FileSubmission) #----------------------------------------------------------------------------- -## TODO: Make a wrapper for -def textbox(element, value, status, render_template, msg=''): - ''' - The textbox is used for code input. The message is the return HTML string from - evaluating the code, eg error messages, and output from the code tests. - ''' - eid = element.get('id') - count = int(eid.split('_')[-2]) - 1 # HACK - size = element.get('size') - rows = element.get('rows') or '30' - cols = element.get('cols') or '80' - # if specified, then textline is hidden and id is stored in div of name given by hidden - hidden = element.get('hidden', '') +class CodeInput(InputTypeBase): + """ + A text area input for code--uses codemirror, does syntax highlighting, special tab handling, + etc. + """ - # if no student input yet, then use the default input given by the problem - if not value: - value = element.text + template = "codeinput.html" + tags = ['codeinput', + 'textbox', # Old name for this. Still supported, but deprecated. + ] - # Check if problem has been queued - queue_len = 0 - # Flag indicating that the problem has been queued, 'msg' is length of queue - if status == 'incomplete': - status = 'queued' - queue_len = msg - msg = 'Submitted to grader.' + def __init__(self, system, xml, state): + super(CodeInput, self).__init__(system, xml, state) - # For CodeMirror - mode = element.get('mode','python') - linenumbers = element.get('linenumbers','true') - tabsize = element.get('tabsize','4') - tabsize = int(tabsize) + self.rows = xml.get('rows') or '30' + self.cols = xml.get('cols') or '80' + # if specified, then textline is hidden and id is stored in div of name given by hidden + self.hidden = xml.get('hidden', '') - context = {'id': eid, - 'value': value, - 'state': status, - 'count': count, - 'size': size, - 'msg': msg, - 'mode': mode, - 'linenumbers': linenumbers, - 'rows': rows, - 'cols': cols, - 'hidden': hidden, - 'tabsize': tabsize, - 'queue_len': queue_len, + # if no student input yet, then use the default input given by the problem + if not self.value: + self.value = xml.text + + # Check if problem has been queued + self.queue_len = 0 + # Flag indicating that the problem has been queued, 'msg' is length of queue + if self.status == 'incomplete': + self.status = 'queued' + self.queue_len = self.msg + self.msg = 'Submitted to grader.' + + # For CodeMirror + self.mode = xml.get('mode', 'python') + self.linenumbers = xml.get('linenumbers', 'true') + self.tabsize = int(xml.get('tabsize', '4')) + + def _get_render_context(self): + + context = {'id': self.id, + 'value': self.value, + 'state': self.status, + 'msg': self.msg, + 'mode': self.mode, + 'linenumbers': self.linenumbers, + 'rows': self.rows, + 'cols': self.cols, + 'hidden': self.hidden, + 'tabsize': self.tabsize, + 'queue_len': self.queue_len, } - html = render_template("textbox.html", context) - try: - xhtml = etree.XML(html) - except Exception as err: - newmsg = 'error %s in rendering message' % (str(err).replace('<', '<')) - newmsg += '
    Original message: %s' % msg.replace('<', '<') - context['msg'] = newmsg - html = render_template("textbox.html", context) - xhtml = etree.XML(html) - return xhtml + return context +register_input_class(CodeInput) -_reg(textbox) #----------------------------------------------------------------------------- def schematic(element, value, status, render_template, msg=''): diff --git a/common/lib/capa/capa/templates/textbox.html b/common/lib/capa/capa/templates/codeinput.html similarity index 100% rename from common/lib/capa/capa/templates/textbox.html rename to common/lib/capa/capa/templates/codeinput.html diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 759ec8bdfa..8cfbbc54ef 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -251,7 +251,7 @@ class FileSubmissionTest(unittest.TestCase): escapedict = {'"': '"'} esc = lambda s: saxutils.escape(s, escapedict) - + state = {'value': 'BumbleBee.py', 'status': 'incomplete', 'feedback' : {'message': '3'}, } @@ -269,3 +269,52 @@ class FileSubmissionTest(unittest.TestCase): self.assertEqual(context, expected) + +class CodeInputTest(unittest.TestCase): + ''' + Check that codeinput inputs work + ''' + + def test_rendering(self): + mode = "parrot" + linenumbers = 'false' + rows = '37' + cols = '11' + tabsize = '7' + + xml_str = """""".format(m=mode, c=cols, r=rows, ln=linenumbers, ts=tabsize) + + element = etree.fromstring(xml_str) + + escapedict = {'"': '"'} + esc = lambda s: saxutils.escape(s, escapedict) + + state = {'value': 'print "good evening"', + 'status': 'incomplete', + 'feedback' : {'message': '3'}, } + + the_input = inputtypes.get_class_for_tag('codeinput')(system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'value': 'print "good evening"', + 'state': 'queued', + 'msg': 'Submitted to grader.', + 'mode': mode, + 'linenumbers': linenumbers, + 'rows': rows, + 'cols': cols, + 'hidden': '', + 'tabsize': int(tabsize), + 'queue_len': '3', + } + + self.assertEqual(context, expected) + From 80d0952d161a6ef4e795336bd8109e155807ed46 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 26 Oct 2012 18:29:46 -0400 Subject: [PATCH 015/228] Refactor schematic input --- common/lib/capa/capa/inputtypes.py | 52 ++++++++++-------- common/lib/capa/capa/tests/test_inputtypes.py | 55 ++++++++++++++++++- 2 files changed, 84 insertions(+), 23 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index bbbddc0fb6..cbd5d1d02e 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -546,29 +546,37 @@ register_input_class(CodeInput) #----------------------------------------------------------------------------- -def schematic(element, value, status, render_template, msg=''): - eid = element.get('id') - height = element.get('height') - width = element.get('width') - parts = element.get('parts') - analyses = element.get('analyses') - initial_value = element.get('initial_value') - submit_analyses = element.get('submit_analyses') - context = { - 'id': eid, - 'value': value, - 'initial_value': initial_value, - 'state': status, - 'width': width, - 'height': height, - 'parts': parts, - 'analyses': analyses, - 'submit_analyses': submit_analyses, - } - html = render_template("schematicinput.html", context) - return etree.XML(html) +class Schematic(InputTypeBase): + """ + """ -_reg(schematic) + template = "schematicinput.html" + tags = ['schematic'] + + def __init__(self, system, xml, state): + super(Schematic, self).__init__(system, xml, state) + self.height = xml.get('height') + self.width = xml.get('width') + self.parts = xml.get('parts') + self.analyses = xml.get('analyses') + self.initial_value = xml.get('initial_value') + self.submit_analyses = xml.get('submit_analyses') + + + def _get_render_context(self): + + context = {'id': self.id, + 'value': self.value, + 'initial_value': self.initial_value, + 'state': self.status, + 'width': self.width, + 'height': self.height, + 'parts': self.parts, + 'analyses': self.analyses, + 'submit_analyses': self.submit_analyses, } + return context + +register_input_class(Schematic) #----------------------------------------------------------------------------- ### TODO: Move out of inputtypes diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 8cfbbc54ef..573f52a01a 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -1,5 +1,9 @@ """ -Tests of input types (and actually responsetypes too) +Tests of input types (and actually responsetypes too). + +TODO: +- test unicode in values, parameters, etc. +- test various html escapes """ from datetime import datetime @@ -318,3 +322,52 @@ class CodeInputTest(unittest.TestCase): self.assertEqual(context, expected) + +class SchematicTest(unittest.TestCase): + ''' + Check that schematic inputs work + ''' + + def test_rendering(self): + height = '12' + width = '33' + parts = 'resistors, capacitors, and flowers' + analyses = 'fast, slow, and pink' + initial_value = 'two large batteries' + submit_analyses = 'maybe' + + + xml_str = """""".format(h=height, w=width, p=parts, a=analyses, + iv=initial_value, sa=submit_analyses) + + element = etree.fromstring(xml_str) + + value = 'three resistors and an oscilating pendulum' + state = {'value': value, + 'status': 'unsubmitted', + 'feedback' : {'message': '3'}, } + + the_input = inputtypes.get_class_for_tag('schematic')(system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'value': value, + 'initial_value': initial_value, + 'state': 'unsubmitted', + 'width': width, + 'height': height, + 'parts': parts, + 'analyses': analyses, + 'submit_analyses': submit_analyses, + } + + self.assertEqual(context, expected) + From 9135d7b2db316b6ad31857ea9d88310ffd83666e Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 26 Oct 2012 19:05:56 -0400 Subject: [PATCH 016/228] minor cleanup in math function --- common/lib/capa/capa/inputtypes.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index cbd5d1d02e..5d271f257e 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -596,18 +596,14 @@ def math(element, value, status, render_template, msg=''): ''' mathstr = re.sub('\$(.*)\$', '[mathjaxinline]\\1[/mathjaxinline]', element.text) mtag = 'mathjax' - if not '\\displaystyle' in mathstr: mtag += 'inline' - else: mathstr = mathstr.replace('\\displaystyle', '') + if not '\\displaystyle' in mathstr: + mtag += 'inline' + else: + mathstr = mathstr.replace('\\displaystyle', '') mathstr = mathstr.replace('mathjaxinline]', '%s]' % mtag) - #if '\\displaystyle' in mathstr: - # isinline = False - # mathstr = mathstr.replace('\\displaystyle','') - #else: - # isinline = True - # html = render_template("mathstring.html", {'mathstr':mathstr, - # 'isinline':isinline,'tail':element.tail}) + # TODO: why are there nested html tags here?? Why are there html tags at all, in fact? html = '%s%s' % (mathstr, saxutils.escape(element.tail)) try: xhtml = etree.XML(html) From 2fab97c1660a65cc741b296f4a028067e28f71e5 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 26 Oct 2012 19:06:34 -0400 Subject: [PATCH 017/228] Refactor imageinput and crystallography. - also add a test for chemicalequationinput --- common/lib/capa/capa/inputtypes.py | 203 +++++++++--------- .../capa/capa/templates/crystallography.html | 18 +- .../lib/capa/capa/templates/vsepr_input.html | 24 +-- common/lib/capa/capa/tests/test_inputtypes.py | 121 ++++++++++- 4 files changed, 231 insertions(+), 135 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 5d271f257e..145eabd953 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -647,9 +647,8 @@ _reg(solution) #----------------------------------------------------------------------------- - -def imageinput(element, value, status, render_template, msg=''): - ''' +class ImageInput(InputTypeBase): + """ Clickable image as an input field. Element should specify the image source, height, and width, e.g. @@ -657,130 +656,120 @@ def imageinput(element, value, status, render_template, msg=''): TODO: showanswer for imageimput does not work yet - need javascript to put rectangle over acceptable area of image. - ''' - eid = element.get('id') - src = element.get('src') - height = element.get('height') - width = element.get('width') + """ - # if value is of the form [x,y] then parse it and send along coordinates of previous answer - m = re.match('\[([0-9]+),([0-9]+)]', value.strip().replace(' ', '')) - if m: - (gx, gy) = [int(x) - 15 for x in m.groups()] - else: - (gx, gy) = (0, 0) + template = "imageinput.html" + tags = ['imageinput'] - context = { - 'id': eid, - 'value': value, - 'height': height, - 'width': width, - 'src': src, - 'gx': gx, - 'gy': gy, - 'state': status, # to change - 'msg': msg, # to change - } - html = render_template("imageinput.html", context) - return etree.XML(html) + def __init__(self, system, xml, state): + super(ImageInput, self).__init__(system, xml, state) + self.src = xml.get('src') + self.height = xml.get('height') + self.width = xml.get('width') -_reg(imageinput) + # if value is of the form [x,y] then parse it and send along coordinates of previous answer + m = re.match('\[([0-9]+),([0-9]+)]', self.value.strip().replace(' ', '')) + if m: + # TODO (vshnayder): why is there a "-15" here?? + (self.gx, self.gy) = [int(x) - 15 for x in m.groups()] + else: + (self.gx, self.gy) = (0, 0) -def crystallography(element, value, status, render_template, msg=''): - eid = element.get('id') - if eid is None: - msg = 'cryst has no id: it probably appears outside of a known response type' - msg += "\nSee problem XML source line %s" % getattr(element, 'sourceline', '') - raise Exception(msg) - height = element.get('height') - width = element.get('width') - display_file = element.get('display_file') + def _get_render_context(self): - count = int(eid.split('_')[-2]) - 1 # HACK - size = element.get('size') - # if specified, then textline is hidden and id is stored in div of name given by hidden - hidden = element.get('hidden', '') - # Escape answers with quotes, so they don't crash the system! - escapedict = {'"': '"'} - value = saxutils.escape(value, escapedict) - - context = {'id': eid, - 'value': value, - 'state': status, - 'count': count, - 'size': size, - 'msg': msg, - 'hidden': hidden, - 'inline': element.get('inline', ''), - 'width': width, - 'height': height, - 'display_file': display_file, + context = {'id': self.id, + 'value': self.value, + 'height': self.height, + 'width': self.width, + 'src': self.src, + 'gx': self.gx, + 'gy': self.gy, + 'state': self.status, # to change (VS: to what??) + 'msg': self.msg, # to change } + return context - html = render_template("crystallography.html", context) +register_input_class(ImageInput) - try: - xhtml = etree.XML(html) - except Exception as err: - # TODO: needs to be self.system.DEBUG - but can't access system - if True: - log.debug('[inputtypes.crystallography] failed to parse XML for:\n%s' % html) - raise - return xhtml +#----------------------------------------------------------------------------- -_reg(crystallography) +class Crystallography(InputTypeBase): + """ + An input for crystallography -- user selects 3 points on the axes, and we get a plane. + TODO: what's the actual value format? + """ -def vsepr_input(element, value, status, render_template, msg=''): - eid = element.get('id') - if eid is None: - msg = 'cryst has no id: it probably appears outside of a known response type' - msg += "\nSee problem XML source line %s" % getattr(element, 'sourceline', '') - raise Exception(msg) - height = element.get('height') - width = element.get('width') - display_file = element.get('display_file') + template = "crystallography.html" + tags = ['crystallography'] - count = int(eid.split('_')[-2]) - 1 # HACK - size = element.get('size') - # if specified, then textline is hidden and id is stored in div of name given by hidden - hidden = element.get('hidden', '') - # Escape answers with quotes, so they don't crash the system! - escapedict = {'"': '"'} - value = saxutils.escape(value, escapedict) + def __init__(self, system, xml, state): + super(Crystallography, self).__init__(system, xml, state) - molecules = element.get('molecules') - geometries = element.get('geometries') + self.height = xml.get('height') + self.width = xml.get('width') + self.size = xml.get('size') - context = {'id': eid, - 'value': value, - 'state': status, - 'count': count, - 'size': size, - 'msg': msg, - 'hidden': hidden, - 'inline': element.get('inline', ''), - 'width': width, - 'height': height, - 'display_file': display_file, - 'molecules': molecules, - 'geometries': geometries, + # if specified, then textline is hidden and id is stored in div of name given by hidden + self.hidden = xml.get('hidden', '') + + # Escape answers with quotes, so they don't crash the system! + escapedict = {'"': '"'} + self.value = saxutils.escape(self.value, escapedict) + + def _get_render_context(self): + context = {'id': self.id, + 'value': self.value, + 'state': self.status, + 'size': self.size, + 'msg': self.msg, + 'hidden': self.hidden, + 'width': self.width, + 'height': self.height, } + return context - html = render_template("vsepr_input.html", context) +register_input_class(Crystallography) - try: - xhtml = etree.XML(html) - except Exception as err: - # TODO: needs to be self.system.DEBUG - but can't access system - if True: - log.debug('[inputtypes.vsepr_input] failed to parse XML for:\n%s' % html) - raise - return xhtml +# ------------------------------------------------------------------------- -_reg(vsepr_input) +class VseprInput(InputTypeBase): + """ + Input for molecular geometry--show possible structures, let student + pick structure and label positions with atoms or electron pairs. + """ + template = 'vsepr_input.html' + tags = ['vsepr_input'] + + def __init__(self, system, xml, state): + super(ImageInput, self).__init__(system, xml, state) + + self.height = xml.get('height') + self.width = xml.get('width') + + # Escape answers with quotes, so they don't crash the system! + escapedict = {'"': '"'} + self.value = saxutils.escape(self.value, escapedict) + + self.molecules = xml.get('molecules') + self.geometries = xml.get('geometries') + + def _get_render_context(self): + + context = {'id': self.id, + 'value': self.value, + 'state': self.status, + 'msg': self.msg, + 'width': self.width, + 'height': self.height, + 'molecules': self.molecules, + 'geometries': self.geometries, + } + return context + +register_input_class(VseprInput) #-------------------------------------------------------------------------------- diff --git a/common/lib/capa/capa/templates/crystallography.html b/common/lib/capa/capa/templates/crystallography.html index 1fc638b356..71578f1fa0 100644 --- a/common/lib/capa/capa/templates/crystallography.html +++ b/common/lib/capa/capa/templates/crystallography.html @@ -1,19 +1,19 @@ -<% doinline = "inline" if inline else "" %> - -
    +
    -
    +
    +
    +
    % if state == 'unsubmitted': -
    +
    % elif state == 'correct': -
    +
    % elif state == 'incorrect': -
    +
    % elif state == 'incomplete': -
    +
    % endif % if hidden:
    @@ -45,7 +45,7 @@ % if msg: ${msg|n} % endif -% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete'] or hidden: +% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
    % endif
    diff --git a/common/lib/capa/capa/templates/vsepr_input.html b/common/lib/capa/capa/templates/vsepr_input.html index 588e53c914..5194551d50 100644 --- a/common/lib/capa/capa/templates/vsepr_input.html +++ b/common/lib/capa/capa/templates/vsepr_input.html @@ -1,6 +1,4 @@ -<% doinline = "inline" if inline else "" %> - -
    +
    @@ -14,25 +12,17 @@
    % if state == 'unsubmitted': -
    +
    % elif state == 'correct': -
    +
    % elif state == 'incorrect': -
    +
    % elif state == 'incomplete': -
    - % endif - % if hidden: -
    +
    % endif

    @@ -52,7 +42,7 @@ % if msg: ${msg|n} % endif -% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete'] or hidden: +% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:

    % endif diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 573f52a01a..773f2b3519 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -4,6 +4,7 @@ Tests of input types (and actually responsetypes too). TODO: - test unicode in values, parameters, etc. - test various html escapes +- test funny xml chars -- should never get xml parse error if things are escaped properly. """ from datetime import datetime @@ -351,8 +352,7 @@ class SchematicTest(unittest.TestCase): value = 'three resistors and an oscilating pendulum' state = {'value': value, - 'status': 'unsubmitted', - 'feedback' : {'message': '3'}, } + 'status': 'unsubmitted'} the_input = inputtypes.get_class_for_tag('schematic')(system, element, state) @@ -371,3 +371,120 @@ class SchematicTest(unittest.TestCase): self.assertEqual(context, expected) + +class ImageInputTest(unittest.TestCase): + ''' + Check that image inputs work + ''' + + def check(self, value, egx, egy): + height = '78' + width = '427' + src = 'http://www.edx.org/cowclicker.jpg' + + xml_str = """""".format(s=src, h=height, w=width) + + element = etree.fromstring(xml_str) + + state = {'value': value, + 'status': 'unsubmitted'} + + the_input = inputtypes.get_class_for_tag('imageinput')(system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'value': value, + 'state': 'unsubmitted', + 'width': width, + 'height': height, + 'src': src, + 'gx': egx, + 'gy': egy, + 'state': 'unsubmitted', + 'msg': ''} + + self.assertEqual(context, expected) + + def test_with_value(self): + self.check('[50,40]', 35, 25) + + def test_without_value(self): + self.check('', 0, 0) + + def test_corrupt_values(self): + self.check('[12', 0, 0) + self.check('[12, a]', 0, 0) + self.check('[12 10]', 0, 0) + self.check('[12]', 0, 0) + self.check('[12 13 14]', 0, 0) + + + +class CrystallographyTest(unittest.TestCase): + ''' + Check that crystallography inputs work + ''' + + def test_rendering(self): + height = '12' + width = '33' + size = '10' + + xml_str = """""".format(h=height, w=width, s=size) + + element = etree.fromstring(xml_str) + + value = 'abc' + state = {'value': value, + 'status': 'unsubmitted'} + + the_input = inputtypes.get_class_for_tag('crystallography')(system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'value': value, + 'state': 'unsubmitted', + 'size': size, + 'msg': '', + 'hidden': '', + 'width': width, + 'height': height, + } + + self.assertEqual(context, expected) + + +class ChemicalEquationTest(unittest.TestCase): + ''' + Check that chemical equation inputs work. + ''' + + def test_rendering(self): + size = "42" + xml_str = """""".format(size=size) + + element = etree.fromstring(xml_str) + + state = {'value': 'H2OYeah',} + the_input = inputtypes.get_class_for_tag('chemicalequationinput')(system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'value': 'H2OYeah', + 'status': 'unanswered', + 'size': size, + 'previewer': '/static/js/capa/chemical_equation_preview.js', + } + self.assertEqual(context, expected) + From f6637b7fe03e9d72b8053972c444840921b84d6e Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sat, 27 Oct 2012 19:29:24 -0400 Subject: [PATCH 018/228] Add a registry that keeps track of tag->renderer/input mappings --- common/lib/capa/capa/registry.py | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 common/lib/capa/capa/registry.py diff --git a/common/lib/capa/capa/registry.py b/common/lib/capa/capa/registry.py new file mode 100644 index 0000000000..94a2853dec --- /dev/null +++ b/common/lib/capa/capa/registry.py @@ -0,0 +1,49 @@ +class TagRegistry(object): + """ + A registry mapping tags to handlers. + + (A dictionary with some extra error checking.) + """ + def __init__(self): + self._mapping = {} + + def register(self, cls): + """ + Register cls as a supported tag type. It is expected to define cls.tags as a list of tags + that it implements. + + If an already-registered type has registered one of those tags, will raise ValueError. + + If there are no tags in cls.tags, will also raise ValueError. + """ + + # Do all checks and complain before changing any state. + if len(cls.tags) == 0: + raise ValueError("No tags specified for class {0}".format(cls.__name__)) + + for t in cls.tags: + if t in self._mapping: + other_cls = self._mapping[t] + if cls == other_cls: + # registering the same class multiple times seems silly, but ok + continue + raise ValueError("Tag {0} already registered by class {1}." + " Can't register for class {2}" + .format(t, other_cls.__name__, cls.__name__)) + + # Ok, should be good to change state now. + for t in cls.tags: + self._mapping[t] = cls + + def registered_tags(self): + """ + Get a list of all the tags that have been registered. + """ + return self._mapping.keys() + + def get_class_for_tag(self, tag): + """ + For any tag in registered_tags(), returns the corresponding class. Otherwise, will raise + KeyError. + """ + return self._mapping[tag] From d36d8cba6b1ce624f8202c366b20ed1b113f0dcf Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sat, 27 Oct 2012 19:31:17 -0400 Subject: [PATCH 019/228] Cleanups: - switch to using registry - pull math and solution "input types" into a separate customrender.py file - make capa_problem use new custom renderers and input types registries - remove unused imports, methods, etc - add tests for math and solution tags. --- common/lib/capa/capa/capa_problem.py | 37 ++- common/lib/capa/capa/customrender.py | 100 +++++++++ common/lib/capa/capa/inputtypes.py | 210 +++--------------- common/lib/capa/capa/tests/__init__.py | 12 +- .../lib/capa/capa/tests/test_customrender.py | 76 +++++++ common/lib/capa/capa/tests/test_inputtypes.py | 43 ++-- 6 files changed, 243 insertions(+), 235 deletions(-) create mode 100644 common/lib/capa/capa/customrender.py create mode 100644 common/lib/capa/capa/tests/test_customrender.py diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index 1c31725e4b..451891d067 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -38,6 +38,7 @@ import calc from correctmap import CorrectMap import eia import inputtypes +import customrender from util import contextualize_text, convert_files_to_filenames import xqueue_interface @@ -47,23 +48,8 @@ import responsetypes # dict of tagname, Response Class -- this should come from auto-registering response_tag_dict = dict([(x.response_tag, x) for x in responsetypes.__all__]) -# Different ways students can input code -entry_types = ['textline', - 'schematic', - 'textbox', - 'imageinput', - 'optioninput', - 'choicegroup', - 'radiogroup', - 'checkboxgroup', - 'filesubmission', - 'javascriptinput', - 'crystallography', - 'chemicalequationinput', - 'vsepr_input'] - # extra things displayed after "show answers" is pressed -solution_types = ['solution'] +solution_tags = ['solution'] # these get captured as student responses response_properties = ["codeparam", "responseparam", "answer"] @@ -309,7 +295,7 @@ class LoncapaProblem(object): answer_map.update(results) # include solutions from ... stanzas - for entry in self.tree.xpath("//" + "|//".join(solution_types)): + for entry in self.tree.xpath("//" + "|//".join(solution_tags)): answer = etree.tostring(entry) if answer: answer_map[entry.get('id')] = contextualize_text(answer, self.context) @@ -487,7 +473,7 @@ class LoncapaProblem(object): problemid = problemtree.get('id') # my ID - if problemtree.tag in inputtypes.registered_input_tags(): + if problemtree.tag in inputtypes.registry.registered_tags(): # If this is an inputtype subtree, let it render itself. status = "unsubmitted" msg = '' @@ -513,7 +499,7 @@ class LoncapaProblem(object): 'hint': hint, 'hintmode': hintmode,}} - input_type_cls = inputtypes.get_class_for_tag(problemtree.tag) + input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag) the_input = input_type_cls(self.system, problemtree, state) return the_input.get_html() @@ -521,9 +507,15 @@ class LoncapaProblem(object): if problemtree in self.responders: return self.responders[problemtree].render_html(self._extract_html) + # let each custom renderer render itself: + if problemtree.tag in customrender.registry.registered_tags(): + renderer_class = customrender.registry.get_class_for_tag(problemtree.tag) + renderer = renderer_class(self.system, problemtree) + return renderer.get_html() + + # otherwise, render children recursively, and copy over attributes tree = etree.Element(problemtree.tag) for item in problemtree: - # render child recursively item_xhtml = self._extract_html(item) if item_xhtml is not None: tree.append(item_xhtml) @@ -560,11 +552,12 @@ class LoncapaProblem(object): response_id += 1 answer_id = 1 + input_tags = inputtypes.registry.registered_tags() inputfields = tree.xpath("|".join(['//' + response.tag + '[@id=$id]//' + x - for x in (entry_types + solution_types)]), + for x in (input_tags + solution_tags)]), id=response_id_str) - # assign one answer_id for each entry_type or solution_type + # assign one answer_id for each input type or solution type for entry in inputfields: entry.attrib['response_id'] = str(response_id) entry.attrib['answer_id'] = str(answer_id) diff --git a/common/lib/capa/capa/customrender.py b/common/lib/capa/capa/customrender.py new file mode 100644 index 0000000000..ef1044e8b1 --- /dev/null +++ b/common/lib/capa/capa/customrender.py @@ -0,0 +1,100 @@ +""" +This has custom renderers: classes that know how to render certain problem tags (e.g. and +) to html. + +These tags do not have state, so they just get passed the system (for access to render_template), +and the xml element. +""" + +from registry import TagRegistry + +import logging +import re +import shlex # for splitting quoted strings +import json + +from lxml import etree +import xml.sax.saxutils as saxutils +from registry import TagRegistry + +log = logging.getLogger('mitx.' + __name__) + +registry = TagRegistry() + +#----------------------------------------------------------------------------- +class MathRenderer(object): + tags = ['math'] + + def __init__(self, system, xml): + ''' + Render math using latex-like formatting. + + Examples: + + $\displaystyle U(r)=4 U_0 $ + $r_0$ + + We convert these to [mathjax]...[/mathjax] and [mathjaxinline]...[/mathjaxinline] + + TODO: use shorter tags (but this will require converting problem XML files!) + ''' + self.system = system + self.xml = xml + + mathstr = re.sub('\$(.*)\$', r'[mathjaxinline]\1[/mathjaxinline]', xml.text) + mtag = 'mathjax' + if not r'\displaystyle' in mathstr: + mtag += 'inline' + else: + mathstr = mathstr.replace(r'\displaystyle', '') + self.mathstr = mathstr.replace('mathjaxinline]', '%s]' % mtag) + + + def get_html(self): + """ + Return the contents of this tag, rendered to html, as an etree element. + """ + # TODO: why are there nested html tags here?? Why are there html tags at all, in fact? + html = '%s%s' % ( + self.mathstr, saxutils.escape(self.xml.tail)) + try: + xhtml = etree.XML(html) + except Exception as err: + if self.system.DEBUG: + msg = '

    Error %s

    ' % ( + str(err).replace('<', '<')) + msg += ('

    Failed to construct math expression from

    %s

    ' % + html.replace('<', '<')) + msg += "
    " + log.error(msg) + return etree.XML(msg) + else: + raise + return xhtml + + +registry.register(MathRenderer) + +#----------------------------------------------------------------------------- + +class SolutionRenderer(object): + ''' + A solution is just a ... which is given an ID, that is used for displaying an + extended answer (a problem "solution") after "show answers" is pressed. + + Note that the solution content is NOT rendered and returned in the HTML. It is obtained by an + ajax call. + ''' + tags = ['solution'] + + def __init__(self, system, xml): + self.system = system + self.id = xml.get('id') + + def get_html(self): + context = {'id': self.id} + html = self.system.render_template("solutionspan.html", context) + return etree.XML(html) + +registry.register(SolutionRenderer) + diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 145eabd953..9569958c96 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -1,29 +1,3 @@ - - -# template: -''' -class ClassName(InputTypeBase): - """ - """ - - template = "tagname.html" - tags = ['tagname'] - - def __init__(self, system, xml, state): - super(ClassName, self).__init__(system, xml, state) - - - def _get_render_context(self): - - context = {'id': self.id, - - } - return context - -register_input_class(ClassName) -''' - - # # File: courseware/capa/inputtypes.py # @@ -32,11 +6,9 @@ register_input_class(ClassName) Module containing the problem elements which render into input objects - textline -- textbox (change this to textarea?) -- schemmatic -- choicegroup -- radiogroup -- checkboxgroup +- textbox (aka codeinput) +- schematic +- choicegroup (aka radiogroup, checkboxgroup) - javascriptinput - imageinput (for clickable image) - optioninput (for option list) @@ -60,53 +32,13 @@ import json from lxml import etree import xml.sax.saxutils as saxutils +from registry import TagRegistry log = logging.getLogger('mitx.' + __name__) ######################################################################### -_TAGS_TO_CLASSES = {} - -def register_input_class(cls): - """ - Register cls as a supported input type. It is expected to have the same constructor as - InputTypeBase, and to define cls.tags as a list of tags that it implements. - - If an already-registered input type has claimed one of those tags, will raise ValueError. - - If there are no tags in cls.tags, will also raise ValueError. - """ - - # Do all checks and complain before changing any state. - if len(cls.tags) == 0: - raise ValueError("No supported tags for class {0}".format(cls.__name__)) - - for t in cls.tags: - if t in _TAGS_TO_CLASSES: - other_cls = _TAGS_TO_CLASSES[t] - if cls == other_cls: - # registering the same class multiple times seems silly, but ok - continue - raise ValueError("Tag {0} already registered by class {1}. Can't register for class {2}" - .format(t, other_cls.__name__, cls.__name__)) - - # Ok, should be good to change state now. - for t in cls.tags: - _TAGS_TO_CLASSES[t] = cls - -def registered_input_tags(): - """ - Get a list of all the xml tags that map to known input types. - """ - return _TAGS_TO_CLASSES.keys() - - -def get_class_for_tag(tag): - """ - For any tag in registered_input_tags(), return the corresponding class. Otherwise, will raise KeyError. - """ - return _TAGS_TO_CLASSES[tag] - +registry = TagRegistry() class InputTypeBase(object): """ @@ -119,16 +51,18 @@ class InputTypeBase(object): """ Instantiate an InputType class. Arguments: - - system : ModuleSystem instance which provides OS, rendering, and user context. Specifically, must - have a render_template function. + - system : ModuleSystem instance which provides OS, rendering, and user context. + Specifically, must have a render_template function. - xml : Element tree of this Input element - state : a dictionary with optional keys: - * 'value' -- the current value of this input (what the student entered last time) - * 'id' -- the id of this input, typically "{problem-location}_{response-num}_{input-num}" + * 'value' -- the current value of this input + (what the student entered last time) + * 'id' -- the id of this input, typically + "{problem-location}_{response-num}_{input-num}" * 'status' (answered, unanswered, unsubmitted) * 'feedback' (dictionary containing keys for hints, errors, or other - feedback from previous attempt. Specifically 'message', 'hint', 'hintmode'. If 'hintmode' - is 'always', the hint is always displayed.) + feedback from previous attempt. Specifically 'message', 'hint', + 'hintmode'. If 'hintmode' is 'always', the hint is always displayed.) """ self.xml = xml @@ -172,40 +106,13 @@ class InputTypeBase(object): Return the html for this input, as an etree element. """ if self.template is None: - raise NotImplementedError("no rendering template specified for class {0}".format(self.__class__)) + raise NotImplementedError("no rendering template specified for class {0}" + .format(self.__class__)) html = self.system.render_template(self.template, self._get_render_context()) return etree.XML(html) -## TODO: Remove once refactor is complete -def make_class_for_render_function(fn): - """ - Take an old-style render function, return a new-style input class. - """ - - class Impl(InputTypeBase): - """ - Inherit all the constructor logic from InputTypeBase... - """ - tags = [fn.__name__] - def get_html(self): - """...delegate to the render function to do the work""" - return fn(self.xml, self.value, self.status, self.system.render_template, self.msg) - - # don't want all the classes to be called Impl (confuses register_input_class). - Impl.__name__ = fn.__name__.capitalize() - return Impl - - -def _reg(fn): - """ - Register an old-style inputtype render function as a new-style subclass of InputTypeBase. - This will go away once converting all input types to the new format is complete. (TODO) - """ - register_input_class(make_class_for_render_function(fn)) - - #----------------------------------------------------------------------------- @@ -253,7 +160,7 @@ class OptionInput(InputTypeBase): } return context -register_input_class(OptionInput) +registry.register(OptionInput) #----------------------------------------------------------------------------- @@ -346,7 +253,7 @@ def extract_choices(element): return choices -register_input_class(ChoiceGroup) +registry.register(ChoiceGroup) #----------------------------------------------------------------------------- @@ -389,7 +296,7 @@ class JavascriptInput(InputTypeBase): } return context -register_input_class(JavascriptInput) +registry.register(JavascriptInput) #----------------------------------------------------------------------------- @@ -445,7 +352,7 @@ class TextLine(InputTypeBase): } return context -register_input_class(TextLine) +registry.register(TextLine) #----------------------------------------------------------------------------- @@ -485,7 +392,7 @@ class FileSubmission(InputTypeBase): 'required_files': self.required_files,} return context -register_input_class(FileSubmission) +registry.register(FileSubmission) #----------------------------------------------------------------------------- @@ -542,7 +449,7 @@ class CodeInput(InputTypeBase): } return context -register_input_class(CodeInput) +registry.register(CodeInput) #----------------------------------------------------------------------------- @@ -576,74 +483,7 @@ class Schematic(InputTypeBase): 'submit_analyses': self.submit_analyses, } return context -register_input_class(Schematic) - -#----------------------------------------------------------------------------- -### TODO: Move out of inputtypes -def math(element, value, status, render_template, msg=''): - ''' - This is not really an input type. It is a convention from Lon-CAPA, used for - displaying a math equation. - - Examples: - - $\displaystyle U(r)=4 U_0 - $r_0$ - - We convert these to [mathjax]...[/mathjax] and [mathjaxinline]...[/mathjaxinline] - - TODO: use shorter tags (but this will require converting problem XML files!) - ''' - mathstr = re.sub('\$(.*)\$', '[mathjaxinline]\\1[/mathjaxinline]', element.text) - mtag = 'mathjax' - if not '\\displaystyle' in mathstr: - mtag += 'inline' - else: - mathstr = mathstr.replace('\\displaystyle', '') - mathstr = mathstr.replace('mathjaxinline]', '%s]' % mtag) - - - # TODO: why are there nested html tags here?? Why are there html tags at all, in fact? - html = '%s%s' % (mathstr, saxutils.escape(element.tail)) - try: - xhtml = etree.XML(html) - except Exception as err: - if False: # TODO needs to be self.system.DEBUG - but can't access system - msg = '

    Error %s

    ' % str(err).replace('<', '<') - msg += ('

    Failed to construct math expression from

    %s

    ' % - html.replace('<', '<')) - msg += "
    " - log.error(msg) - return etree.XML(msg) - else: - raise - # xhtml.tail = element.tail # don't forget to include the tail! - return xhtml - -_reg(math) - -#----------------------------------------------------------------------------- - - -def solution(element, value, status, render_template, msg=''): - ''' - This is not really an input type. It is just a ... which is given an ID, - that is used for displaying an extended answer (a problem "solution") after "show answers" - is pressed. Note that the solution content is NOT sent with the HTML. It is obtained - by an ajax call. - ''' - eid = element.get('id') - size = element.get('size') - context = {'id': eid, - 'value': value, - 'state': status, - 'size': size, - 'msg': msg, - } - html = render_template("solutionspan.html", context) - return etree.XML(html) - -_reg(solution) +registry.register(Schematic) #----------------------------------------------------------------------------- @@ -690,7 +530,7 @@ class ImageInput(InputTypeBase): } return context -register_input_class(ImageInput) +registry.register(ImageInput) #----------------------------------------------------------------------------- @@ -730,7 +570,7 @@ class Crystallography(InputTypeBase): } return context -register_input_class(Crystallography) +registry.register(Crystallography) # ------------------------------------------------------------------------- @@ -799,4 +639,4 @@ class ChemicalEquationInput(InputTypeBase): } return context -register_input_class(ChemicalEquationInput) +registry.register(ChemicalEquationInput) diff --git a/common/lib/capa/capa/tests/__init__.py b/common/lib/capa/capa/tests/__init__.py index c72d2a1538..b06975f6ce 100644 --- a/common/lib/capa/capa/tests/__init__.py +++ b/common/lib/capa/capa/tests/__init__.py @@ -4,13 +4,23 @@ import os from mock import Mock +import xml.sax.saxutils as saxutils + TEST_DIR = os.path.dirname(os.path.realpath(__file__)) +def tst_render_template(template, context): + """ + A test version of render to template. Renders to the repr of the context, completely ignoring + the template name. To make the output valid xml, quotes the content, and wraps it in a
    + """ + return '
    {0}
    '.format(saxutils.escape(repr(context))) + + test_system = Mock( ajax_url='courses/course_id/modx/a_location', track_function=Mock(), get_module=Mock(), - render_template=Mock(), + render_template=tst_render_template, replace_urls=Mock(), user=Mock(), filestore=fs.osfs.OSFS(os.path.join(TEST_DIR, "test_files")), diff --git a/common/lib/capa/capa/tests/test_customrender.py b/common/lib/capa/capa/tests/test_customrender.py new file mode 100644 index 0000000000..7208ab2941 --- /dev/null +++ b/common/lib/capa/capa/tests/test_customrender.py @@ -0,0 +1,76 @@ +from lxml import etree +import unittest +import xml.sax.saxutils as saxutils + +from . import test_system +from capa import customrender + +# just a handy shortcut +lookup_tag = customrender.registry.get_class_for_tag + +def extract_context(xml): + """ + Given an xml element corresponding to the output of test_system.render_template, get back the + original context + """ + return eval(xml.text) + +def quote_attr(s): + return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes + +class HelperTest(unittest.TestCase): + ''' + Make sure that our helper function works! + ''' + def check(self, d): + xml = etree.XML(test_system.render_template('blah', d)) + self.assertEqual(d, extract_context(xml)) + + def test_extract_context(self): + self.check({}) + self.check({1, 2}) + self.check({'id', 'an id'}) + self.check({'with"quote', 'also"quote'}) + + +class SolutionRenderTest(unittest.TestCase): + ''' + Make sure solutions render properly. + ''' + + def test_rendering(self): + solution = 'To compute unicorns, count them.' + xml_str = """{s}""".format(s=solution) + element = etree.fromstring(xml_str) + + renderer = lookup_tag('solution')(test_system, element) + + self.assertEqual(renderer.id, 'solution_12') + + # our test_system "renders" templates to a div with the repr of the context + xml = renderer.get_html() + context = extract_context(xml) + self.assertEqual(context, {'id' : 'solution_12'}) + + +class MathRenderTest(unittest.TestCase): + ''' + Make sure math renders properly. + ''' + + def check_parse(self, latex_in, mathjax_out): + xml_str = """{tex}""".format(tex=latex_in) + element = etree.fromstring(xml_str) + + renderer = lookup_tag('math')(test_system, element) + + self.assertEqual(renderer.mathstr, mathjax_out) + + def test_parsing(self): + self.check_parse('$abc$', '[mathjaxinline]abc[/mathjaxinline]') + self.check_parse('$abc', '$abc') + self.check_parse(r'$\displaystyle 2+2$', '[mathjax] 2+2[/mathjax]') + + + # NOTE: not testing get_html yet because I don't understand why it's doing what it's doing. + diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 773f2b3519..992af14bf9 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -1,5 +1,5 @@ """ -Tests of input types (and actually responsetypes too). +Tests of input types. TODO: - test unicode in values, parameters, etc. @@ -7,27 +7,16 @@ TODO: - test funny xml chars -- should never get xml parse error if things are escaped properly. """ -from datetime import datetime -import json -from mock import Mock -from nose.plugins.skip import SkipTest -import os +from lxml import etree import unittest import xml.sax.saxutils as saxutils from . import test_system from capa import inputtypes -from lxml import etree +# just a handy shortcut +lookup_tag = inputtypes.registry.get_class_for_tag -def tst_render_template(template, context): - """ - A test version of render to template. Renders to the repr of the context, completely ignoring the template name. - """ - return repr(context) - - -system = Mock(render_template=tst_render_template) def quote_attr(s): return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes @@ -44,7 +33,7 @@ class OptionInputTest(unittest.TestCase): state = {'value': 'Down', 'id': 'sky_input', 'status': 'answered'} - option_input = inputtypes.get_class_for_tag('optioninput')(system, element, state) + option_input = lookup_tag('optioninput')(test_system, element, state) context = option_input._get_render_context() @@ -80,7 +69,7 @@ class ChoiceGroupTest(unittest.TestCase): 'id': 'sky_input', 'status': 'answered'} - option_input = inputtypes.get_class_for_tag('choicegroup')(system, element, state) + option_input = lookup_tag('choicegroup')(test_system, element, state) context = option_input._get_render_context() @@ -119,7 +108,7 @@ class ChoiceGroupTest(unittest.TestCase): 'id': 'sky_input', 'status': 'answered'} - the_input = inputtypes.get_class_for_tag(tag)(system, element, state) + the_input = lookup_tag(tag)(test_system, element, state) context = the_input._get_render_context() @@ -164,7 +153,7 @@ class JavascriptInputTest(unittest.TestCase): element = etree.fromstring(xml_str) state = {'value': '3',} - the_input = inputtypes.get_class_for_tag('javascriptinput')(system, element, state) + the_input = lookup_tag('javascriptinput')(test_system, element, state) context = the_input._get_render_context() @@ -191,7 +180,7 @@ class TextLineTest(unittest.TestCase): element = etree.fromstring(xml_str) state = {'value': 'BumbleBee',} - the_input = inputtypes.get_class_for_tag('textline')(system, element, state) + the_input = lookup_tag('textline')(test_system, element, state) context = the_input._get_render_context() @@ -219,7 +208,7 @@ class TextLineTest(unittest.TestCase): element = etree.fromstring(xml_str) state = {'value': 'BumbleBee',} - the_input = inputtypes.get_class_for_tag('textline')(system, element, state) + the_input = lookup_tag('textline')(test_system, element, state) context = the_input._get_render_context() @@ -260,7 +249,7 @@ class FileSubmissionTest(unittest.TestCase): state = {'value': 'BumbleBee.py', 'status': 'incomplete', 'feedback' : {'message': '3'}, } - the_input = inputtypes.get_class_for_tag('filesubmission')(system, element, state) + the_input = lookup_tag('filesubmission')(test_system, element, state) context = the_input._get_render_context() @@ -304,7 +293,7 @@ class CodeInputTest(unittest.TestCase): 'status': 'incomplete', 'feedback' : {'message': '3'}, } - the_input = inputtypes.get_class_for_tag('codeinput')(system, element, state) + the_input = lookup_tag('codeinput')(test_system, element, state) context = the_input._get_render_context() @@ -354,7 +343,7 @@ class SchematicTest(unittest.TestCase): state = {'value': value, 'status': 'unsubmitted'} - the_input = inputtypes.get_class_for_tag('schematic')(system, element, state) + the_input = lookup_tag('schematic')(test_system, element, state) context = the_input._get_render_context() @@ -393,7 +382,7 @@ class ImageInputTest(unittest.TestCase): state = {'value': value, 'status': 'unsubmitted'} - the_input = inputtypes.get_class_for_tag('imageinput')(system, element, state) + the_input = lookup_tag('imageinput')(test_system, element, state) context = the_input._get_render_context() @@ -447,7 +436,7 @@ class CrystallographyTest(unittest.TestCase): state = {'value': value, 'status': 'unsubmitted'} - the_input = inputtypes.get_class_for_tag('crystallography')(system, element, state) + the_input = lookup_tag('crystallography')(test_system, element, state) context = the_input._get_render_context() @@ -476,7 +465,7 @@ class ChemicalEquationTest(unittest.TestCase): element = etree.fromstring(xml_str) state = {'value': 'H2OYeah',} - the_input = inputtypes.get_class_for_tag('chemicalequationinput')(system, element, state) + the_input = lookup_tag('chemicalequationinput')(test_system, element, state) context = the_input._get_render_context() From cc8f0a16342e009ae2a545fa7021ddaea90df3d4 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sat, 27 Oct 2012 19:57:29 -0400 Subject: [PATCH 020/228] Fix merge bugs --- common/lib/capa/capa/inputtypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 9569958c96..33e9048131 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -584,7 +584,7 @@ class VseprInput(InputTypeBase): tags = ['vsepr_input'] def __init__(self, system, xml, state): - super(ImageInput, self).__init__(system, xml, state) + super(VseprInput, self).__init__(system, xml, state) self.height = xml.get('height') self.width = xml.get('width') @@ -609,7 +609,7 @@ class VseprInput(InputTypeBase): } return context -register_input_class(VseprInput) +registry.register(VseprInput) #-------------------------------------------------------------------------------- From 6324eeeecbf872f39af1c2c8aa672a9a2f7a13e2 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sat, 27 Oct 2012 20:00:18 -0400 Subject: [PATCH 021/228] add note about mysterious -15 in the code --- common/lib/capa/capa/inputtypes.py | 3 ++- common/lib/capa/capa/tests/test_inputtypes.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 33e9048131..0bbf54d153 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -510,7 +510,8 @@ class ImageInput(InputTypeBase): # if value is of the form [x,y] then parse it and send along coordinates of previous answer m = re.match('\[([0-9]+),([0-9]+)]', self.value.strip().replace(' ', '')) if m: - # TODO (vshnayder): why is there a "-15" here?? + # Note: we subtract 15 to compensate for the size of the dot on the screen. + # (which supposedly has size 30). (self.gx, self.gy) = [int(x) - 15 for x in m.groups()] else: (self.gx, self.gy) = (0, 0) diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 992af14bf9..ba20f84cb1 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -400,6 +400,7 @@ class ImageInputTest(unittest.TestCase): self.assertEqual(context, expected) def test_with_value(self): + # Check that compensating for the dot size works properly. self.check('[50,40]', 35, 25) def test_without_value(self): From 1d4990458e5248565f68e59ede55409c2b5a6f0a Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sat, 27 Oct 2012 20:09:02 -0400 Subject: [PATCH 022/228] add todos about would-be-nice refactors --- common/lib/capa/capa/inputtypes.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 0bbf54d153..08cd9ce413 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -25,6 +25,13 @@ graded status as'status' # problem ID for the input element, but it will turn into a dict containing both the # answer and any associated message for the problem ID for the input element. +# TODO: there is a lot of repetitive "grab these elements from xml attributes, with these defaults, +# put them in the context" code. Refactor so class just specifies required and optional attrs (with +# defaults for latter), and InputTypeBase does the right thing. + +# TODO: Quoting and unquoting is handled in a pretty ad-hoc way. Also something that could be done +# properly once in InputTypeBase. + import logging import re import shlex # for splitting quoted strings From 1a843c2d77ba61664d2b913b55785806045c9e4e Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 29 Oct 2012 07:23:27 -0400 Subject: [PATCH 023/228] Remove non-working TrueFalse functionality from choicegroup, combine tests with radiogroup and checkboxgroup --- common/lib/capa/capa/inputtypes.py | 11 ++--- common/lib/capa/capa/tests/test_inputtypes.py | 47 ++----------------- 2 files changed, 7 insertions(+), 51 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 08cd9ce413..690ece4dc5 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -202,16 +202,11 @@ class ChoiceGroup(InputTypeBase): def __init__(self, system, xml, state): super(ChoiceGroup, self).__init__(system, xml, state) + # suffix is '' or [] to change the way the input is handled in --as a scalar or vector + # value. (VS: would be nice to make to this less hackish). if self.tag == 'choicegroup': self.suffix = '' - if self.xml.get('type') == "MultipleChoice": - self.element_type = "radio" - elif self.xml.get('type') == "TrueFalse": - # Huh? Why TrueFalse->checkbox? Each input can be true / false separately? - self.element_type = "checkbox" - else: - self.element_type = "radio" - + self.element_type = "radio" elif self.tag == 'radiogroup': self.element_type = "radio" self.suffix = '[]' diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index ba20f84cb1..6c5b1cca15 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -48,50 +48,8 @@ class OptionInputTest(unittest.TestCase): class ChoiceGroupTest(unittest.TestCase): ''' - Test choice groups. + Test choice groups, radio groups, and checkbox groups ''' - def test_mult_choice(self): - xml_template = """ - - This is foil One. - This is foil Two. - This is foil Three. - - """ - - def check_type(type_str, expected_input_type): - print "checking for type_str='{0}'".format(type_str) - xml_str = xml_template.format(type_str) - - element = etree.fromstring(xml_str) - - state = {'value': 'foil3', - 'id': 'sky_input', - 'status': 'answered'} - - option_input = lookup_tag('choicegroup')(test_system, element, state) - - context = option_input._get_render_context() - - expected = {'id': 'sky_input', - 'value': 'foil3', - 'state': 'answered', - 'input_type': expected_input_type, - 'choices': [('foil1', 'This is foil One.'), - ('foil2', 'This is foil Two.'), - ('foil3', 'This is foil Three.'),], - 'name_array_suffix': '', # what is this for?? - } - - self.assertEqual(context, expected) - - check_type('', 'radio') - check_type('type=""', 'radio') - check_type('type="MultipleChoice"', 'radio') - check_type('type="TrueFalse"', 'checkbox') - # fallback. - check_type('type="StrangeUnknown"', 'radio') - def check_group(self, tag, expected_input_type, expected_suffix): xml_str = """ @@ -124,6 +82,9 @@ class ChoiceGroupTest(unittest.TestCase): self.assertEqual(context, expected) + def test_choicegroup(self): + self.check_group('choicegroup', 'radio', '') + def test_radiogroup(self): self.check_group('radiogroup', 'radio', '[]') From d6cb432842e26e56ad3d085ceb1788c9ea09f256 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 29 Oct 2012 07:42:11 -0400 Subject: [PATCH 024/228] Replace overriding constructor with a setup() method. - allows catching any exceptions and making sure the xml is in the error message - isolates subclasses from external interface a bit --- common/lib/capa/capa/inputtypes.py | 130 +++++++++++++++-------------- 1 file changed, 69 insertions(+), 61 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 690ece4dc5..812c9a6555 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -32,13 +32,14 @@ graded status as'status' # TODO: Quoting and unquoting is handled in a pretty ad-hoc way. Also something that could be done # properly once in InputTypeBase. +import json import logging +from lxml import etree import re import shlex # for splitting quoted strings -import json - -from lxml import etree +import sys import xml.sax.saxutils as saxutils + from registry import TagRegistry log = logging.getLogger('mitx.' + __name__) @@ -99,6 +100,26 @@ class InputTypeBase(object): self.status = state.get('status', 'unanswered') + # Call subclass "constructor" -- means they don't have to worry about calling + # super().__init__, and are isolated from changes to the input constructor interface. + try: + self.setup() + except Exception as err: + # Something went wrong: add xml to message, but keep the traceback + msg = "Error in xml '{x}': {err} ".format(x=etree.tostring(xml), err=str(err)) + raise Exception, msg, sys.exc_info()[2] + + + def setup(self): + """ + InputTypes should override this to do any needed initialization. It is called after the + constructor, so all base attributes will be set. + + If this method raises an exception, it will be wrapped with a message that includes the + problem xml. + """ + pass + def _get_render_context(self): """ Abstract method. Subclasses should implement to return the dictionary @@ -135,15 +156,11 @@ class OptionInput(InputTypeBase): template = "optioninput.html" tags = ['optioninput'] - def __init__(self, system, xml, state): - super(OptionInput, self).__init__(system, xml, state) - + def setup(self): # Extract the options... options = self.xml.get('options') if not options: - raise Exception( - "[courseware.capa.inputtypes.optioninput] Missing options specification in " - + etree.tostring(self.xml)) + raise ValueError("optioninput: Missing 'options' specification.") # parse the set of possible options oset = shlex.shlex(options[1:-1]) @@ -199,9 +216,7 @@ class ChoiceGroup(InputTypeBase): template = "choicegroup.html" tags = ['choicegroup', 'radiogroup', 'checkboxgroup'] - def __init__(self, system, xml, state): - super(ChoiceGroup, self).__init__(system, xml, state) - + def setup(self): # suffix is '' or [] to change the way the input is handled in --as a scalar or vector # value. (VS: would be nice to make to this less hackish). if self.tag == 'choicegroup': @@ -242,9 +257,9 @@ def extract_choices(element): for choice in element: if choice.tag != 'choice': - raise Exception("[courseware.capa.inputtypes.extract_choices] \ - Expected a tag; got %s instead" - % choice.tag) + raise Exception( + "[capa.inputtypes.extract_choices] Expected a tag; got %s instead" + % choice.tag) choice_text = ''.join([etree.tostring(x) for x in choice]) if choice.text is not None: # TODO: fix order? @@ -270,8 +285,7 @@ class JavascriptInput(InputTypeBase): template = "javascriptinput.html" tags = ['javascriptinput'] - def __init__(self, system, xml, state): - super(JavascriptInput, self).__init__(system, xml, state) + def setup(self): # Need to provide a value that JSON can parse if there is no # student-supplied value yet. if self.value == "": @@ -311,8 +325,7 @@ class TextLine(InputTypeBase): template = "textinput.html" tags = ['textline'] - def __init__(self, system, xml, state): - super(TextLine, self).__init__(system, xml, state) + def setup(self): self.size = self.xml.get('size') # if specified, then textline is hidden and input id is stored @@ -366,12 +379,11 @@ class FileSubmission(InputTypeBase): template = "filesubmission.html" tags = ['filesubmission'] - def __init__(self, system, xml, state): - super(FileSubmission, self).__init__(system, xml, state) + def setup(self): escapedict = {'"': '"'} - self.allowed_files = json.dumps(xml.get('allowed_files', '').split()) + self.allowed_files = json.dumps(self.xml.get('allowed_files', '').split()) self.allowed_files = saxutils.escape(self.allowed_files, escapedict) - self.required_files = json.dumps(xml.get('required_files', '').split()) + self.required_files = json.dumps(self.xml.get('required_files', '').split()) self.required_files = saxutils.escape(self.required_files, escapedict) # Check if problem has been queued @@ -410,17 +422,16 @@ class CodeInput(InputTypeBase): 'textbox', # Old name for this. Still supported, but deprecated. ] - def __init__(self, system, xml, state): - super(CodeInput, self).__init__(system, xml, state) - self.rows = xml.get('rows') or '30' - self.cols = xml.get('cols') or '80' + def setup(self): + self.rows = self.xml.get('rows') or '30' + self.cols = self.xml.get('cols') or '80' # if specified, then textline is hidden and id is stored in div of name given by hidden - self.hidden = xml.get('hidden', '') + self.hidden = self.xml.get('hidden', '') # if no student input yet, then use the default input given by the problem if not self.value: - self.value = xml.text + self.value = self.xml.text # Check if problem has been queued self.queue_len = 0 @@ -431,9 +442,9 @@ class CodeInput(InputTypeBase): self.msg = 'Submitted to grader.' # For CodeMirror - self.mode = xml.get('mode', 'python') - self.linenumbers = xml.get('linenumbers', 'true') - self.tabsize = int(xml.get('tabsize', '4')) + self.mode = self.xml.get('mode', 'python') + self.linenumbers = self.xml.get('linenumbers', 'true') + self.tabsize = int(self.xml.get('tabsize', '4')) def _get_render_context(self): @@ -462,14 +473,13 @@ class Schematic(InputTypeBase): template = "schematicinput.html" tags = ['schematic'] - def __init__(self, system, xml, state): - super(Schematic, self).__init__(system, xml, state) - self.height = xml.get('height') - self.width = xml.get('width') - self.parts = xml.get('parts') - self.analyses = xml.get('analyses') - self.initial_value = xml.get('initial_value') - self.submit_analyses = xml.get('submit_analyses') + def setup(self): + self.height = self.xml.get('height') + self.width = self.xml.get('width') + self.parts = self.xml.get('parts') + self.analyses = self.xml.get('analyses') + self.initial_value = self.xml.get('initial_value') + self.submit_analyses = self.xml.get('submit_analyses') def _get_render_context(self): @@ -482,7 +492,7 @@ class Schematic(InputTypeBase): 'height': self.height, 'parts': self.parts, 'analyses': self.analyses, - 'submit_analyses': self.submit_analyses, } + 'submit_analyses': self.submit_analyses,} return context registry.register(Schematic) @@ -503,11 +513,10 @@ class ImageInput(InputTypeBase): template = "imageinput.html" tags = ['imageinput'] - def __init__(self, system, xml, state): - super(ImageInput, self).__init__(system, xml, state) - self.src = xml.get('src') - self.height = xml.get('height') - self.width = xml.get('width') + def setup(self): + self.src = self.xml.get('src') + self.height = self.xml.get('height') + self.width = self.xml.get('width') # if value is of the form [x,y] then parse it and send along coordinates of previous answer m = re.match('\[([0-9]+),([0-9]+)]', self.value.strip().replace(' ', '')) @@ -547,15 +556,14 @@ class Crystallography(InputTypeBase): template = "crystallography.html" tags = ['crystallography'] - def __init__(self, system, xml, state): - super(Crystallography, self).__init__(system, xml, state) - self.height = xml.get('height') - self.width = xml.get('width') - self.size = xml.get('size') + def setup(self): + self.height = self.xml.get('height') + self.width = self.xml.get('width') + self.size = self.xml.get('size') # if specified, then textline is hidden and id is stored in div of name given by hidden - self.hidden = xml.get('hidden', '') + self.hidden = self.xml.get('hidden', '') # Escape answers with quotes, so they don't crash the system! escapedict = {'"': '"'} @@ -586,18 +594,16 @@ class VseprInput(InputTypeBase): template = 'vsepr_input.html' tags = ['vsepr_input'] - def __init__(self, system, xml, state): - super(VseprInput, self).__init__(system, xml, state) - - self.height = xml.get('height') - self.width = xml.get('width') + def setup(self): + self.height = self.xml.get('height') + self.width = self.xml.get('width') # Escape answers with quotes, so they don't crash the system! escapedict = {'"': '"'} self.value = saxutils.escape(self.value, escapedict) - self.molecules = xml.get('molecules') - self.geometries = xml.get('geometries') + self.molecules = self.xml.get('molecules') + self.geometries = self.xml.get('geometries') def _get_render_context(self): @@ -631,13 +637,15 @@ class ChemicalEquationInput(InputTypeBase): template = "chemicalequationinput.html" tags = ['chemicalequationinput'] + def setup(self): + self.size = self.xml.get('size', '20') + def _get_render_context(self): - size = self.xml.get('size', '20') context = { 'id': self.id, 'value': self.value, 'status': self.status, - 'size': size, + 'size': self.size, 'previewer': '/static/js/capa/chemical_equation_preview.js', } return context From a97298e8f7c1dd85fd49af191d21d5ada3fc5488 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 29 Oct 2012 07:48:53 -0400 Subject: [PATCH 025/228] add note about documenting js input --- common/lib/capa/capa/inputtypes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 812c9a6555..e295ac9013 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -280,6 +280,11 @@ class JavascriptInput(InputTypeBase): """ Hidden field for javascript to communicate via; also loads the required scripts for rendering the problem and passes data to the problem. + + TODO (arjun?): document this in detail. Initial notes: + - display_class is a subclass of XProblemClassDisplay (see + xmodule/xmodule/js/src/capa/display.coffee), + - display_file is the js script to be in /static/js/ where display_class is defined. """ template = "javascriptinput.html" From 02bd99697b9907da3ddf860bb25618a074fbc98b Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 29 Oct 2012 07:49:33 -0400 Subject: [PATCH 026/228] Clean up section ids and classes - all inputtypes should have id inputtype_${id} - should have class capa_inputtype --- common/lib/capa/capa/templates/crystallography.html | 2 +- common/lib/capa/capa/templates/textinput.html | 8 +------- common/lib/capa/capa/templates/vsepr_input.html | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/common/lib/capa/capa/templates/crystallography.html b/common/lib/capa/capa/templates/crystallography.html index 71578f1fa0..ee9b249d3d 100644 --- a/common/lib/capa/capa/templates/crystallography.html +++ b/common/lib/capa/capa/templates/crystallography.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/common/lib/capa/capa/templates/textinput.html b/common/lib/capa/capa/templates/textinput.html index 14d54d4cde..d2059fdc58 100644 --- a/common/lib/capa/capa/templates/textinput.html +++ b/common/lib/capa/capa/templates/textinput.html @@ -1,12 +1,6 @@ <% doinline = "inline" if inline else "" %> -<% -# TODO: -# Is id inputtype_${id} vs textinput_${id} important? -# Is class capa_inputtype vs textinput important? -# should really just use one. -%> -
    +
    % if preprocessor is not None:
    diff --git a/common/lib/capa/capa/templates/vsepr_input.html b/common/lib/capa/capa/templates/vsepr_input.html index 5194551d50..a6bd465554 100644 --- a/common/lib/capa/capa/templates/vsepr_input.html +++ b/common/lib/capa/capa/templates/vsepr_input.html @@ -1,4 +1,4 @@ -
    +
    -
    From 318ea9b1353fccfefbeec1eda646d2927527c16a Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 29 Oct 2012 07:51:58 -0400 Subject: [PATCH 027/228] add back mathjax textarea --- common/lib/capa/capa/templates/textinput.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/lib/capa/capa/templates/textinput.html b/common/lib/capa/capa/templates/textinput.html index d2059fdc58..3685742a3c 100644 --- a/common/lib/capa/capa/templates/textinput.html +++ b/common/lib/capa/capa/templates/textinput.html @@ -48,6 +48,9 @@ % if do_math:
    `{::}`
    + + % endif % if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: From ab03f3dddf4e32f7200c2951b7a2349aca771d9b Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 29 Oct 2012 07:56:10 -0400 Subject: [PATCH 028/228] move textinput.html to textline.html for consistency with other inputs --- common/lib/capa/capa/inputtypes.py | 2 +- .../lib/capa/capa/templates/{textinput.html => textline.html} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename common/lib/capa/capa/templates/{textinput.html => textline.html} (100%) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index e295ac9013..26e2de8fa4 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -327,7 +327,7 @@ class TextLine(InputTypeBase): """ - template = "textinput.html" + template = "textline.html" tags = ['textline'] def setup(self): diff --git a/common/lib/capa/capa/templates/textinput.html b/common/lib/capa/capa/templates/textline.html similarity index 100% rename from common/lib/capa/capa/templates/textinput.html rename to common/lib/capa/capa/templates/textline.html From 516b720f1e393a04443e37fea844d53134a8697c Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 29 Oct 2012 08:05:07 -0400 Subject: [PATCH 029/228] Rename: use 'status' in templates as well as in the code. --- common/lib/capa/capa/inputtypes.py | 22 ++++++++----------- .../lib/capa/capa/templates/choicegroup.html | 8 +++---- common/lib/capa/capa/templates/codeinput.html | 10 ++++----- .../capa/capa/templates/crystallography.html | 18 +++++++-------- .../capa/capa/templates/filesubmission.html | 10 ++++----- .../lib/capa/capa/templates/imageinput.html | 8 +++---- .../lib/capa/capa/templates/jstextline.html | 8 +++---- .../lib/capa/capa/templates/optioninput.html | 8 +++---- .../capa/capa/templates/schematicinput.html | 8 +++---- common/lib/capa/capa/templates/textline.html | 18 +++++++-------- .../lib/capa/capa/templates/vsepr_input.html | 18 +++++++-------- common/lib/capa/capa/tests/test_inputtypes.py | 19 ++++++++-------- 12 files changed, 75 insertions(+), 80 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 26e2de8fa4..e2d7708ee3 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -21,10 +21,6 @@ Each input type takes the xml tree as 'element', the previous answer as 'value', graded status as'status' """ -# TODO: rename "state" to "status" for all below. status is currently the answer for the -# problem ID for the input element, but it will turn into a dict containing both the -# answer and any associated message for the problem ID for the input element. - # TODO: there is a lot of repetitive "grab these elements from xml attributes, with these defaults, # put them in the context" code. Refactor so class just specifies required and optional attrs (with # defaults for latter), and InputTypeBase does the right thing. @@ -177,7 +173,7 @@ class OptionInput(InputTypeBase): context = { 'id': self.id, 'value': self.value, - 'state': self.status, + 'status': self.status, 'msg': self.msg, 'options': self.osetdict, 'inline': self.xml.get('inline',''), @@ -236,7 +232,7 @@ class ChoiceGroup(InputTypeBase): def _get_render_context(self): context = {'id': self.id, 'value': self.value, - 'state': self.status, + 'status': self.status, 'input_type': self.element_type, 'choices': self.choices, 'name_array_suffix': self.suffix} @@ -362,7 +358,7 @@ class TextLine(InputTypeBase): context = {'id': self.id, 'value': value, - 'state': self.status, + 'status': self.status, 'size': self.size, 'msg': self.msg, 'hidden': self.hidden, @@ -403,7 +399,7 @@ class FileSubmission(InputTypeBase): def _get_render_context(self): context = {'id': self.id, - 'state': self.status, + 'status': self.status, 'msg': self.msg, 'value': self.value, 'queue_len': self.queue_len, @@ -455,7 +451,7 @@ class CodeInput(InputTypeBase): context = {'id': self.id, 'value': self.value, - 'state': self.status, + 'status': self.status, 'msg': self.msg, 'mode': self.mode, 'linenumbers': self.linenumbers, @@ -492,7 +488,7 @@ class Schematic(InputTypeBase): context = {'id': self.id, 'value': self.value, 'initial_value': self.initial_value, - 'state': self.status, + 'status': self.status, 'width': self.width, 'height': self.height, 'parts': self.parts, @@ -542,7 +538,7 @@ class ImageInput(InputTypeBase): 'src': self.src, 'gx': self.gx, 'gy': self.gy, - 'state': self.status, # to change (VS: to what??) + 'status': self.status, # to change (VS: to what??) 'msg': self.msg, # to change } return context @@ -577,7 +573,7 @@ class Crystallography(InputTypeBase): def _get_render_context(self): context = {'id': self.id, 'value': self.value, - 'state': self.status, + 'status': self.status, 'size': self.size, 'msg': self.msg, 'hidden': self.hidden, @@ -614,7 +610,7 @@ class VseprInput(InputTypeBase): context = {'id': self.id, 'value': self.value, - 'state': self.status, + 'status': self.status, 'msg': self.msg, 'width': self.width, 'height': self.height, diff --git a/common/lib/capa/capa/templates/choicegroup.html b/common/lib/capa/capa/templates/choicegroup.html index ce1b6d9476..457d9e7817 100644 --- a/common/lib/capa/capa/templates/choicegroup.html +++ b/common/lib/capa/capa/templates/choicegroup.html @@ -1,12 +1,12 @@
    - % if state == 'unsubmitted': + % if status == 'unsubmitted': - % elif state == 'correct': + % elif status == 'correct': - % elif state == 'incorrect': + % elif status == 'incorrect': - % elif state == 'incomplete': + % elif status == 'incomplete': % endif
    diff --git a/common/lib/capa/capa/templates/codeinput.html b/common/lib/capa/capa/templates/codeinput.html index 6e44712d9f..5c2ff2aca5 100644 --- a/common/lib/capa/capa/templates/codeinput.html +++ b/common/lib/capa/capa/templates/codeinput.html @@ -6,13 +6,13 @@ >${value|h}
    - % if state == 'unsubmitted': + % if status == 'unsubmitted': Unanswered - % elif state == 'correct': + % elif status == 'correct': Correct - % elif state == 'incorrect': + % elif status == 'incorrect': Incorrect - % elif state == 'queued': + % elif status == 'queued': Queued % endif @@ -21,7 +21,7 @@
    % endif -

    ${state}

    +

    ${status}

    diff --git a/common/lib/capa/capa/templates/crystallography.html b/common/lib/capa/capa/templates/crystallography.html index ee9b249d3d..f46e2f753a 100644 --- a/common/lib/capa/capa/templates/crystallography.html +++ b/common/lib/capa/capa/templates/crystallography.html @@ -6,13 +6,13 @@
    - % if state == 'unsubmitted': + % if status == 'unsubmitted':
    - % elif state == 'correct': + % elif status == 'correct':
    - % elif state == 'incorrect': + % elif status == 'incorrect':
    - % elif state == 'incomplete': + % elif status == 'incomplete':
    % endif % if hidden: @@ -29,13 +29,13 @@ />

    - % if state == 'unsubmitted': + % if status == 'unsubmitted': unanswered - % elif state == 'correct': + % elif status == 'correct': correct - % elif state == 'incorrect': + % elif status == 'incorrect': incorrect - % elif state == 'incomplete': + % elif status == 'incomplete': incomplete % endif

    @@ -45,7 +45,7 @@ % if msg: ${msg|n} % endif -% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: +% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
    % endif diff --git a/common/lib/capa/capa/templates/filesubmission.html b/common/lib/capa/capa/templates/filesubmission.html index 630a3222dc..2572b25f8a 100644 --- a/common/lib/capa/capa/templates/filesubmission.html +++ b/common/lib/capa/capa/templates/filesubmission.html @@ -1,16 +1,16 @@
    - % if state == 'unsubmitted': + % if status == 'unsubmitted': Unanswered - % elif state == 'correct': + % elif status == 'correct': Correct - % elif state == 'incorrect': + % elif status == 'incorrect': Incorrect - % elif state == 'queued': + % elif status == 'queued': Queued % endif -

    ${state}

    +

    ${status}

    diff --git a/common/lib/capa/capa/templates/imageinput.html b/common/lib/capa/capa/templates/imageinput.html index ceda98ee8f..97279c1b8c 100644 --- a/common/lib/capa/capa/templates/imageinput.html +++ b/common/lib/capa/capa/templates/imageinput.html @@ -4,13 +4,13 @@
    - % if state == 'unsubmitted': + % if status == 'unsubmitted': - % elif state == 'correct': + % elif status == 'correct': - % elif state == 'incorrect': + % elif status == 'incorrect': - % elif state == 'incomplete': + % elif status == 'incomplete': % endif diff --git a/common/lib/capa/capa/templates/jstextline.html b/common/lib/capa/capa/templates/jstextline.html index 045f329ad4..7e5fd94a4b 100644 --- a/common/lib/capa/capa/templates/jstextline.html +++ b/common/lib/capa/capa/templates/jstextline.html @@ -18,13 +18,13 @@ % endif - % if state == 'unsubmitted': + % if status == 'unsubmitted': - % elif state == 'correct': + % elif status == 'correct': - % elif state == 'incorrect': + % elif status == 'incorrect': - % elif state == 'incomplete': + % elif status == 'incomplete': % endif % if msg: diff --git a/common/lib/capa/capa/templates/optioninput.html b/common/lib/capa/capa/templates/optioninput.html index 06c7ed1257..ac62c0c9cb 100644 --- a/common/lib/capa/capa/templates/optioninput.html +++ b/common/lib/capa/capa/templates/optioninput.html @@ -12,13 +12,13 @@ - % if state == 'unsubmitted': + % if status == 'unsubmitted': - % elif state == 'correct': + % elif status == 'correct': - % elif state == 'incorrect': + % elif status == 'incorrect': - % elif state == 'incomplete': + % elif status == 'incomplete': % endif diff --git a/common/lib/capa/capa/templates/schematicinput.html b/common/lib/capa/capa/templates/schematicinput.html index f79dc66d24..ff8cc64073 100644 --- a/common/lib/capa/capa/templates/schematicinput.html +++ b/common/lib/capa/capa/templates/schematicinput.html @@ -12,13 +12,13 @@ - % if state == 'unsubmitted': + % if status == 'unsubmitted': - % elif state == 'correct': + % elif status == 'correct': - % elif state == 'incorrect': + % elif status == 'incorrect': - % elif state == 'incomplete': + % elif status == 'incomplete': % endif diff --git a/common/lib/capa/capa/templates/textline.html b/common/lib/capa/capa/templates/textline.html index 3685742a3c..97c512fc00 100644 --- a/common/lib/capa/capa/templates/textline.html +++ b/common/lib/capa/capa/templates/textline.html @@ -7,13 +7,13 @@
    % endif - % if state == 'unsubmitted': + % if status == 'unsubmitted':
    - % elif state == 'correct': + % elif status == 'correct':
    - % elif state == 'incorrect': + % elif status == 'incorrect':
    - % elif state == 'incomplete': + % elif status == 'incomplete':
    % endif % if hidden: @@ -33,13 +33,13 @@ />

    - % if state == 'unsubmitted': + % if status == 'unsubmitted': unanswered - % elif state == 'correct': + % elif status == 'correct': correct - % elif state == 'incorrect': + % elif status == 'incorrect': incorrect - % elif state == 'incomplete': + % elif status == 'incomplete': incomplete % endif

    @@ -53,7 +53,7 @@ % endif -% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: +% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
    % endif diff --git a/common/lib/capa/capa/templates/vsepr_input.html b/common/lib/capa/capa/templates/vsepr_input.html index a6bd465554..eaa6ac2174 100644 --- a/common/lib/capa/capa/templates/vsepr_input.html +++ b/common/lib/capa/capa/templates/vsepr_input.html @@ -11,13 +11,13 @@
    - % if state == 'unsubmitted': + % if status == 'unsubmitted':
    - % elif state == 'correct': + % elif status == 'correct':
    - % elif state == 'incorrect': + % elif status == 'incorrect':
    - % elif state == 'incomplete': + % elif status == 'incomplete':
    % endif @@ -26,13 +26,13 @@ />

    - % if state == 'unsubmitted': + % if status == 'unsubmitted': unanswered - % elif state == 'correct': + % elif status == 'correct': correct - % elif state == 'incorrect': + % elif status == 'incorrect': incorrect - % elif state == 'incomplete': + % elif status == 'incomplete': incomplete % endif

    @@ -42,7 +42,7 @@ % if msg: ${msg|n} % endif -% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: +% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
    % endif diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 6c5b1cca15..65a22a876e 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -39,7 +39,7 @@ class OptionInputTest(unittest.TestCase): expected = {'value': 'Down', 'options': [('Up', 'Up'), ('Down', 'Down')], - 'state': 'answered', + 'status': 'answered', 'msg': '', 'inline': '', 'id': 'sky_input'} @@ -72,7 +72,7 @@ class ChoiceGroupTest(unittest.TestCase): expected = {'id': 'sky_input', 'value': 'foil3', - 'state': 'answered', + 'status': 'answered', 'input_type': expected_input_type, 'choices': [('foil1', 'This is foil One.'), ('foil2', 'This is foil Two.'), @@ -147,7 +147,7 @@ class TextLineTest(unittest.TestCase): expected = {'id': 'prob_1_2', 'value': 'BumbleBee', - 'state': 'unanswered', + 'status': 'unanswered', 'size': size, 'msg': '', 'hidden': False, @@ -175,7 +175,7 @@ class TextLineTest(unittest.TestCase): expected = {'id': 'prob_1_2', 'value': 'BumbleBee', - 'state': 'unanswered', + 'status': 'unanswered', 'size': size, 'msg': '', 'hidden': False, @@ -215,7 +215,7 @@ class FileSubmissionTest(unittest.TestCase): context = the_input._get_render_context() expected = {'id': 'prob_1_2', - 'state': 'queued', + 'status': 'queued', 'msg': 'Submitted to grader.', 'value': 'BumbleBee.py', 'queue_len': '3', @@ -260,7 +260,7 @@ class CodeInputTest(unittest.TestCase): expected = {'id': 'prob_1_2', 'value': 'print "good evening"', - 'state': 'queued', + 'status': 'queued', 'msg': 'Submitted to grader.', 'mode': mode, 'linenumbers': linenumbers, @@ -311,7 +311,7 @@ class SchematicTest(unittest.TestCase): expected = {'id': 'prob_1_2', 'value': value, 'initial_value': initial_value, - 'state': 'unsubmitted', + 'status': 'unsubmitted', 'width': width, 'height': height, 'parts': parts, @@ -349,13 +349,12 @@ class ImageInputTest(unittest.TestCase): expected = {'id': 'prob_1_2', 'value': value, - 'state': 'unsubmitted', + 'status': 'unsubmitted', 'width': width, 'height': height, 'src': src, 'gx': egx, 'gy': egy, - 'state': 'unsubmitted', 'msg': ''} self.assertEqual(context, expected) @@ -404,7 +403,7 @@ class CrystallographyTest(unittest.TestCase): expected = {'id': 'prob_1_2', 'value': value, - 'state': 'unsubmitted', + 'status': 'unsubmitted', 'size': size, 'msg': '', 'hidden': '', From b03789156c2e21cc4a98626c174c3bd1fd955aed Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 29 Oct 2012 08:16:31 -0400 Subject: [PATCH 030/228] add test for vsepr --- common/lib/capa/capa/tests/test_inputtypes.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 65a22a876e..cab9bfe86b 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -414,6 +414,48 @@ class CrystallographyTest(unittest.TestCase): self.assertEqual(context, expected) +class VseprTest(unittest.TestCase): + ''' + Check that vsepr inputs work + ''' + + def test_rendering(self): + height = '12' + width = '33' + molecules = "H2O, C2O" + geometries = "AX12,TK421" + + xml_str = """""".format(h=height, w=width, m=molecules, g=geometries) + + element = etree.fromstring(xml_str) + + value = 'abc' + state = {'value': value, + 'status': 'unsubmitted'} + + the_input = lookup_tag('vsepr_input')(test_system, element, state) + + context = the_input._get_render_context() + + expected = {'id': 'prob_1_2', + 'value': value, + 'status': 'unsubmitted', + 'msg': '', + 'width': width, + 'height': height, + 'molecules': molecules, + 'geometries': geometries, + } + + self.assertEqual(context, expected) + + + class ChemicalEquationTest(unittest.TestCase): ''' Check that chemical equation inputs work. From 6cc196394a9d845224fe0dae0da86cdac85f85e2 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 29 Oct 2012 12:06:42 -0400 Subject: [PATCH 031/228] fixing comments --- common/lib/capa/capa/inputtypes.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 38f2e5da8f..d47c5a3006 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -28,6 +28,12 @@ graded status as'status' # TODO: Quoting and unquoting is handled in a pretty ad-hoc way. Also something that could be done # properly once in InputTypeBase. +# Possible todo: make inline the default for textlines and other "one-line" inputs. It probably +# makes sense, but a bunch of problems have markup that assumes block. Bigger TODO: figure out a +# general css and layout strategy for capa, document it, then implement it. + + + import json import logging from lxml import etree @@ -214,7 +220,7 @@ class ChoiceGroup(InputTypeBase): def setup(self): # suffix is '' or [] to change the way the input is handled in --as a scalar or vector - # value. (VS: would be nice to make to this less hackish). + # value. (VS: would be nice to make this less hackish). if self.tag == 'choicegroup': self.suffix = '' self.element_type = "radio" @@ -333,8 +339,6 @@ class TextLine(InputTypeBase): # in div with name=self.hidden. self.hidden = self.xml.get('hidden', False) - # TODO (vshnayder): can we get rid of inline? Was it one of - # the styling hacks early this semester? self.inline = self.xml.get('inline', False) # TODO: 'dojs' flag is temporary, for backwards compatibility with 8.02x @@ -383,7 +387,7 @@ class FileSubmission(InputTypeBase): # pulled out for testing submitted_msg = ("Your file(s) have been submitted; as soon as your submission is" " graded, this message will be replaced with the grader's feedback.") - + def setup(self): escapedict = {'"': '"'} self.allowed_files = json.dumps(self.xml.get('allowed_files', '').split()) @@ -423,7 +427,8 @@ class CodeInput(InputTypeBase): template = "codeinput.html" tags = ['codeinput', - 'textbox', # Old name for this. Still supported, but deprecated. + 'textbox', # Another (older) name--at some point we may want to make it use a + # non-codemirror editor. ] @@ -526,7 +531,7 @@ class ImageInput(InputTypeBase): m = re.match('\[([0-9]+),([0-9]+)]', self.value.strip().replace(' ', '')) if m: # Note: we subtract 15 to compensate for the size of the dot on the screen. - # (which supposedly has size 30). + # (is a 30x30 image--lms/static/green-pointer.png). (self.gx, self.gy) = [int(x) - 15 for x in m.groups()] else: (self.gx, self.gy) = (0, 0) @@ -541,8 +546,8 @@ class ImageInput(InputTypeBase): 'src': self.src, 'gx': self.gx, 'gy': self.gy, - 'status': self.status, # to change (VS: to what??) - 'msg': self.msg, # to change + 'status': self.status, + 'msg': self.msg, } return context From a1552654098ead17673abbd154a9356b08dc8894 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 29 Oct 2012 23:44:38 -0400 Subject: [PATCH 032/228] minor whitespace + comment fixes --- common/lib/capa/capa/correctmap.py | 2 +- common/lib/capa/capa/responsetypes.py | 10 ++++------ common/lib/xmodule/xmodule/capa_module.py | 6 ------ lms/djangoapps/courseware/module_render.py | 17 +++++++++-------- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/common/lib/capa/capa/correctmap.py b/common/lib/capa/capa/correctmap.py index 3c4f43a1d6..c7386219b1 100644 --- a/common/lib/capa/capa/correctmap.py +++ b/common/lib/capa/capa/correctmap.py @@ -68,7 +68,7 @@ class CorrectMap(object): correct_map is saved by LMS as a plaintext JSON dump of the correctmap dict. This means that when the definition of CorrectMap (e.g. its properties) are altered, - an existing correct_map dict not coincide with the newest CorrectMap format as + an existing correct_map dict will not coincide with the newest CorrectMap format as defined by self.set. For graceful migration, feed the contents of each correct map to self.set, rather than diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 648fc9e861..b990c489b3 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -81,7 +81,7 @@ class LoncapaResponse(object): by __init__ - check_hint_condition : check to see if the student's answers satisfy a particular - condition for a hint to be displayed + condition for a hint to be displayed - render_html : render this Response as HTML (must return XHTML-compliant string) - __unicode__ : unicode representation of this Response @@ -149,6 +149,7 @@ class LoncapaResponse(object): # for convenience self.answer_id = self.answer_ids[0] + # map input_id -> maxpoints self.maxpoints = dict() for inputfield in self.inputfields: # By default, each answerfield is worth 1 point @@ -280,17 +281,14 @@ class LoncapaResponse(object): (correctness, npoints, msg) for each answer_id. Arguments: - - student_answers : dict of (answer_id,answer) where answer = student input (string) - - - old_cmap : previous CorrectMap (may be empty); useful for analyzing or - recording history of responses + - student_answers : dict of (answer_id, answer) where answer = student input (string) ''' pass @abc.abstractmethod def get_answers(self): ''' - Return a dict of (answer_id,answer_text) for each answer for this question. + Return a dict of (answer_id, answer_text) for each answer for this question. ''' pass diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 151c726f66..ba7b8542f3 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -538,15 +538,9 @@ class CapaModule(XModule): lcp_id = self.lcp.problem_id correct_map = self.lcp.grade_answers(answers) except StudentInputError as inst: - # TODO (vshnayder): why is this line here? - #self.lcp = LoncapaProblem(self.definition['data'], - # id=lcp_id, state=old_state, system=self.system) log.exception("StudentInputError in capa_module:problem_check") return {'success': inst.message} except Exception, err: - # TODO: why is this line here? - #self.lcp = LoncapaProblem(self.definition['data'], - # id=lcp_id, state=old_state, system=self.system) if self.system.DEBUG: msg = "Error checking problem: " + str(err) msg += '\nTraceback:\n' + traceback.format_exc() diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index e0b0c3aaec..67927c0ee7 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -337,7 +337,7 @@ def xqueue_callback(request, course_id, userid, id, dispatch): ''' # Test xqueue package, which we expect to be: # xpackage = {'xqueue_header': json.dumps({'lms_key':'secretkey',...}), - # 'xqueue_body' : 'Message from grader} + # 'xqueue_body' : 'Message from grader'} get = request.POST.copy() for key in ['xqueue_header', 'xqueue_body']: if not get.has_key(key): @@ -372,7 +372,8 @@ def xqueue_callback(request, course_id, userid, id, dispatch): # We go through the "AJAX" path # So far, the only dispatch from xqueue will be 'score_update' try: - ajax_return = instance.handle_ajax(dispatch, get) # Can ignore the "ajax" return in 'xqueue_callback' + # Can ignore the return value--not used for xqueue_callback + instance.handle_ajax(dispatch, get) except: log.exception("error processing ajax call") raise @@ -386,12 +387,12 @@ def xqueue_callback(request, course_id, userid, id, dispatch): #Bin score into range and increment stats score_bucket=get_score_bucket(instance_module.grade, instance_module.max_grade) - org, course_num, run=course_id.split("/") + org, course_num, run=course_id.split("/") statsd.increment("lms.courseware.question_answered", tags=["org:{0}".format(org), "course:{0}".format(course_num), - "run:{0}".format(run), - "score_bucket:{0}".format(score_bucket), + "run:{0}".format(run), + "score_bucket:{0}".format(score_bucket), "type:xqueue"]) return HttpResponse("") @@ -479,12 +480,12 @@ def modx_dispatch(request, dispatch, location, course_id): #Bin score into range and increment stats score_bucket=get_score_bucket(instance_module.grade, instance_module.max_grade) - org, course_num, run=course_id.split("/") + org, course_num, run=course_id.split("/") statsd.increment("lms.courseware.question_answered", tags=["org:{0}".format(org), "course:{0}".format(course_num), - "run:{0}".format(run), - "score_bucket:{0}".format(score_bucket), + "run:{0}".format(run), + "score_bucket:{0}".format(score_bucket), "type:ajax"]) From 535980eb6dcf8a2bc3dbdb579df66c11641791d8 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Tue, 30 Oct 2012 11:06:50 -0400 Subject: [PATCH 033/228] Allow optional advertised_start metadata in Course, to let people pre-launch thier courses quietly if desired. --- common/lib/xmodule/xmodule/course_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 0a5108590f..de8eddd0b8 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -247,7 +247,8 @@ class CourseDescriptor(SequenceDescriptor): @property def start_date_text(self): - return time.strftime("%b %d, %Y", self.start) + displayed_start = self._try_parse_time('advertised_start') or self.start + return time.strftime("%b %d, %Y", displayed_start) # An extra property is used rather than the wiki_slug/number because # there are courses that change the number for different runs. This allows From ab57c7d7598734a06b8b0c339e4094dddea3be5c Mon Sep 17 00:00:00 2001 From: Ashley Penney Date: Tue, 30 Oct 2012 14:32:50 -0400 Subject: [PATCH 034/228] Add MySQL-python to the requirements. --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dc13ae873e..ebc818b29d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,4 +50,5 @@ pygraphviz -r repo-requirements.txt pil nltk -dogstatsd-python \ No newline at end of file +dogstatsd-python +MySQL-python From 376dcb7982fb92406f60bf29fc85b64a688da0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20Rocha?= Date: Tue, 30 Oct 2012 15:44:28 -0400 Subject: [PATCH 035/228] Correct job description item --- lms/templates/static_templates/jobs.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lms/templates/static_templates/jobs.html b/lms/templates/static_templates/jobs.html index a8afdb7712..c78ce266ed 100644 --- a/lms/templates/static_templates/jobs.html +++ b/lms/templates/static_templates/jobs.html @@ -99,7 +99,8 @@

    Qualifications

    The ideal candidate for the Production Coordinator position will have

      -
    • relentless attention to detailability to communicate and collaborate effectively across the organization
    • +
    • relentless attention to detail
    • +
    • ability to communicate and collaborate effectively across the organization
    • knowledge and understanding of digital media production tools and processes
    • experience with compression techniques, image processing, and presentation software preferred
    • proficiency with standard office applications
    • From 8ec2c34ea5f4fa45f9cb566d901b6608fc019b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20Rocha?= Date: Tue, 30 Oct 2012 16:05:06 -0400 Subject: [PATCH 036/228] Add google analytics to course info pages --- lms/templates/google_analytics.html | 11 +++++++++++ lms/templates/main.html | 15 ++------------- lms/templates/portal/course_about.html | 5 +++++ 3 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 lms/templates/google_analytics.html diff --git a/lms/templates/google_analytics.html b/lms/templates/google_analytics.html new file mode 100644 index 0000000000..273fbac970 --- /dev/null +++ b/lms/templates/google_analytics.html @@ -0,0 +1,11 @@ + diff --git a/lms/templates/main.html b/lms/templates/main.html index f234aa72cf..87490a07e4 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -21,20 +21,9 @@ % if not course: - + <%include file="google_analytics.html" /> % endif + diff --git a/lms/templates/portal/course_about.html b/lms/templates/portal/course_about.html index 6fced73565..b35c7a1b6f 100644 --- a/lms/templates/portal/course_about.html +++ b/lms/templates/portal/course_about.html @@ -7,7 +7,12 @@ <%inherit file="../main.html" /> +<%block name="headextra"> + <%include file="../google_analytics.html" /> + + <%block name="js_extra"> + % if not registered: %if user.is_authenticated(): ## If the user is authenticated, clicking the enroll button just submits a form From e5b11a9c576226cbcf0643a3c7583ef82139bfa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20Rocha?= Date: Tue, 30 Oct 2012 19:19:16 -0400 Subject: [PATCH 037/228] Add HD control to video player --- .../xmodule/xmodule/css/video/display.scss | 28 ++++++++++++++++++ .../js/src/video/display/video_caption.coffee | 4 +-- .../js/src/video/display/video_control.coffee | 2 +- .../js/src/video/display/video_player.coffee | 13 +++++++- .../display/video_quality_control.coffee | 26 ++++++++++++++++ .../video/display/video_volume_control.coffee | 2 +- common/static/images/hd.png | Bin 0 -> 364 bytes 7 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 common/lib/xmodule/xmodule/js/src/video/display/video_quality_control.coffee create mode 100644 common/static/images/hd.png diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss index e8aba4d671..43b024ec32 100644 --- a/common/lib/xmodule/xmodule/css/video/display.scss +++ b/common/lib/xmodule/xmodule/css/video/display.scss @@ -355,6 +355,34 @@ div.video { } } + a.quality_control { + background: url(../images/hd.png) center no-repeat; + border-right: 1px solid #000; + @include box-shadow(1px 0 0 #555, inset 1px 0 0 #555); + color: #797979; + display: block; + float: left; + line-height: 46px; //height of play pause buttons + margin-left: 0; + padding: 0 lh(.5); + text-indent: -9999px; + @include transition(); + width: 30px; + + &:hover { + background-color: #444; + color: #fff; + text-decoration: none; + } + + &.active { + background-color: #F44; + color: #0ff; + text-decoration: none; + } + } + + a.hide-subtitles { background: url('../images/cc.png') center no-repeat; color: #797979; diff --git a/common/lib/xmodule/xmodule/js/src/video/display/video_caption.coffee b/common/lib/xmodule/xmodule/js/src/video/display/video_caption.coffee index b1e41afc3c..cdd74c5d07 100644 --- a/common/lib/xmodule/xmodule/js/src/video/display/video_caption.coffee +++ b/common/lib/xmodule/xmodule/js/src/video/display/video_caption.coffee @@ -22,7 +22,7 @@ class @VideoCaption extends Subview """ @$('.video-controls .secondary-controls').append """ Captions - """ + """#" @$('.subtitles').css maxHeight: @$('.video-wrapper').height() - 5 @fetchCaption() @@ -144,7 +144,7 @@ class @VideoCaption extends Subview @el.removeClass('closed') @scrollCaption() $.cookie('hide_captions', hide_captions, expires: 3650, path: '/') - + captionHeight: -> if @el.hasClass('fullscreen') $(window).height() - @$('.video-controls').height() diff --git a/common/lib/xmodule/xmodule/js/src/video/display/video_control.coffee b/common/lib/xmodule/xmodule/js/src/video/display/video_control.coffee index 5053f1dcb1..856549c3e2 100644 --- a/common/lib/xmodule/xmodule/js/src/video/display/video_control.coffee +++ b/common/lib/xmodule/xmodule/js/src/video/display/video_control.coffee @@ -16,7 +16,7 @@ class @VideoControl extends Subview Fill Browser
    - """ + """#" unless onTouchBasedDevice() @$('.video_control').addClass('play').html('Play') diff --git a/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee b/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee index bb89def63d..8829e25dac 100644 --- a/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee +++ b/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee @@ -9,6 +9,7 @@ class @VideoPlayer extends Subview bind: -> $(@control).bind('play', @play) .bind('pause', @pause) + $(@qualityControl).bind('changeQuality', @handlePlaybackQualityChange) $(@caption).bind('seek', @onSeek) $(@speedControl).bind('speedChange', @onSpeedChange) $(@progressSlider).bind('seek', @onSeek) @@ -25,6 +26,7 @@ class @VideoPlayer extends Subview render: -> @control = new VideoControl el: @$('.video-controls') + @qualityControl = new VideoQualityControl el: @$('.secondary-controls') @caption = new VideoCaption el: @el youtubeId: @video.youtubeId('1.0') @@ -41,10 +43,12 @@ class @VideoPlayer extends Subview rel: 0 showinfo: 0 enablejsapi: 1 + modestbranding: 1 videoId: @video.youtubeId() events: onReady: @onReady onStateChange: @onStateChange + onPlaybackQualityChange: @onPlaybackQualityChange @caption.hideCaptions(@['video'].hide_captions) addToolTip: -> @@ -53,7 +57,7 @@ class @VideoPlayer extends Subview my: 'top right' at: 'top center' - onReady: => + onReady: (event) => unless onTouchBasedDevice() $('.video-load-complete:first').data('video').player.play() @@ -68,6 +72,13 @@ class @VideoPlayer extends Subview when YT.PlayerState.ENDED @onEnded() + onPlaybackQualityChange: (event, value) => + quality = @player.getPlaybackQuality() + @qualityControl.onQualityChange(quality) + + handlePlaybackQualityChange: (event, value) => + @player.setPlaybackQuality(value) + onUnstarted: => @control.pause() @caption.pause() diff --git a/common/lib/xmodule/xmodule/js/src/video/display/video_quality_control.coffee b/common/lib/xmodule/xmodule/js/src/video/display/video_quality_control.coffee new file mode 100644 index 0000000000..f8f6167075 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/video/display/video_quality_control.coffee @@ -0,0 +1,26 @@ +class @VideoQualityControl extends Subview + initialize: -> + @quality = null; + + bind: -> + @$('.quality_control').click @toggleQuality + + render: -> + @el.append """ + HD + """#" + + onQualityChange: (value) -> + @quality = value + if @quality in ['hd720', 'hd1080', 'highres'] + @el.addClass('active') + else + @el.removeClass('active') + + toggleQuality: (event) => + event.preventDefault() + if @quality in ['hd720', 'hd1080', 'highres'] + newQuality = 'large' + else + newQuality = 'hd720' + $(@).trigger('changeQuality', newQuality) \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/js/src/video/display/video_volume_control.coffee b/common/lib/xmodule/xmodule/js/src/video/display/video_volume_control.coffee index 10d3f6b044..096b50042d 100644 --- a/common/lib/xmodule/xmodule/js/src/video/display/video_volume_control.coffee +++ b/common/lib/xmodule/xmodule/js/src/video/display/video_volume_control.coffee @@ -17,7 +17,7 @@ class @VideoVolumeControl extends Subview
    - """ + """#" @slider = @$('.volume-slider').slider orientation: "vertical" range: "min" diff --git a/common/static/images/hd.png b/common/static/images/hd.png new file mode 100644 index 0000000000000000000000000000000000000000..d6b8c1b7d1a242e8b5159e8fe3fb1c3dd83c6a31 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^azHG`!3HF+C+nOCQjEnx?oJHr&dIz4a@dl*-Cck* z5d3uBT@R!<3p^r=85p>QL70(Y)*K0-AbW|YuPgfgeTcDB^lvdu|7Ftjp^Qq85VUiY2UN%I_Aea}Zzn!0FYUM!zXyU7xs*dul!W9NhK8 z_`zZ;tI;xr z3~MeGhzhnhdu@~5qI109cCMG*zecynKX>dr=g&&{n5=6ZyJG&#xqiQL6x>%f-dXXQ zXXdrMnfY(0S^jytT_j?;|0C8J%-;{hy6Akb|MdTocuzK4`8y5qb3lJHc)I$ztaD0e F0ssc-j=cZ? literal 0 HcmV?d00001 From 188e1c68d79d108f59adbb54018343bcbfe7fd4d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 30 Oct 2012 20:13:35 -0400 Subject: [PATCH 038/228] add self assessment module --- .idea/.name | 1 + .idea/encodings.xml | 5 + .../inspectionProfiles/profiles_settings.xml | 7 + .idea/misc.xml | 34 ++ .idea/mitx.iml | 9 + .idea/modules.xml | 9 + .idea/scopes/scope_settings.xml | 5 + .idea/vcs.xml | 7 + .idea/workspace.xml | 394 ++++++++++++++++++ common/lib/xmodule/setup.py | 1 + .../xmodule/xmodule/self_assessment_module.py | 174 ++++++++ 11 files changed, 646 insertions(+) create mode 100644 .idea/.name create mode 100644 .idea/encodings.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/mitx.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/scopes/scope_settings.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 common/lib/xmodule/xmodule/self_assessment_module.py diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000000..36d4bf6d51 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +mitx \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000000..e206d70d85 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000..c60c33bb47 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000..715d0f8f2d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,34 @@ + + + + $APPLICATION_HOME_DIR$/lib/pycharm.jar!/resources/html5-schema/html5.rnc + + + + + + + + + + diff --git a/.idea/mitx.iml b/.idea/mitx.iml new file mode 100644 index 0000000000..a34a85703f --- /dev/null +++ b/.idea/mitx.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000..7eaf1301cf --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000000..922003b843 --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000..c80f2198b5 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000000..08fc21c094 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,394 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1351640399572 + 1351640399572 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index 0441357427..e6c8fe577d 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -28,6 +28,7 @@ setup( "problem = xmodule.capa_module:CapaDescriptor", "problemset = xmodule.seq_module:SequenceDescriptor", "section = xmodule.backcompat_module:SemanticSectionDescriptor", + "selfassessment = xmodule.self_assessment_module:SelfAssessmentModule" "sequential = xmodule.seq_module:SequenceDescriptor", "slides = xmodule.backcompat_module:TranslateCustomTagDescriptor", "vertical = xmodule.vertical_module:VerticalDescriptor", diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py new file mode 100644 index 0000000000..c0dd835208 --- /dev/null +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -0,0 +1,174 @@ +import copy +from fs.errors import ResourceNotFoundError +import logging +import os +import sys +from lxml import etree +from lxml.html import rewrite_links +from path import path + +from .x_module import XModule +from pkg_resources import resource_string +from .xml_module import XmlDescriptor, name_to_pathname +from .editing_module import EditingDescriptor +from .stringify import stringify_children +from .html_checker import check_html +from xmodule.modulestore import Location + +from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent + +log = logging.getLogger("mitx.courseware") + + +class SelfAssessmentModule(XModule): + js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'), + resource_string(__name__, 'js/src/collapsible.coffee'), + resource_string(__name__, 'js/src/html/display.coffee') + ] + } + js_module_name = "SelfAssessmentModule" + + def get_html(self): + # cdodge: perform link substitutions for any references to course static content (e.g. images) + return rewrite_links(self.html, self.rewrite_content_links) + + def __init__(self, system, location, definition, descriptor, + instance_state=None, shared_state=None, **kwargs): + XModule.__init__(self, system, location, definition, descriptor, + instance_state, shared_state, **kwargs) + self.html = self.definition['data'] + + + +class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): + """ + Module for putting raw html in a course + """ + mako_template = "widgets/html-edit.html" + module_class = HtmlModule + filename_extension = "xml" + template_dir_name = "selfassessment" + + js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]} + js_module_name = "HTMLEditingDescriptor" + + # VS[compat] TODO (cpennington): Delete this method once all fall 2012 course + # are being edited in the cms + @classmethod + def backcompat_paths(cls, path): + if path.endswith('.html.xml'): + path = path[:-9] + '.html' # backcompat--look for html instead of xml + if path.endswith('.html.html'): + path = path[:-5] # some people like to include .html in filenames.. + candidates = [] + while os.sep in path: + candidates.append(path) + _, _, path = path.partition(os.sep) + + # also look for .html versions instead of .xml + nc = [] + for candidate in candidates: + if candidate.endswith('.xml'): + nc.append(candidate[:-4] + '.html') + return candidates + nc + + # NOTE: html descriptors are special. We do not want to parse and + # export them ourselves, because that can break things (e.g. lxml + # adds body tags when it exports, but they should just be html + # snippets that will be included in the middle of pages. + + @classmethod + def load_definition(cls, xml_object, system, location): + '''Load a descriptor from the specified xml_object: + + If there is a filename attribute, load it as a string, and + log a warning if it is not parseable by etree.HTMLParser. + + If there is not a filename attribute, the definition is the body + of the xml_object, without the root tag (do not want in the + middle of a page) + ''' + filename = xml_object.get('filename') + if filename is None: + definition_xml = copy.deepcopy(xml_object) + cls.clean_metadata_from_xml(definition_xml) + return {'data': stringify_children(definition_xml)} + else: + # html is special. cls.filename_extension is 'xml', but + # if 'filename' is in the definition, that means to load + # from .html + # 'filename' in html pointers is a relative path + # (not same as 'html/blah.html' when the pointer is in a directory itself) + pointer_path = "{category}/{url_path}".format(category='html', + url_path=name_to_pathname(location.name)) + base = path(pointer_path).dirname() + #log.debug("base = {0}, base.dirname={1}, filename={2}".format(base, base.dirname(), filename)) + filepath = "{base}/{name}.html".format(base=base, name=filename) + #log.debug("looking for html file for {0} at {1}".format(location, filepath)) + + + + # VS[compat] + # TODO (cpennington): If the file doesn't exist at the right path, + # give the class a chance to fix it up. The file will be written out + # again in the correct format. This should go away once the CMS is + # online and has imported all current (fall 2012) courses from xml + if not system.resources_fs.exists(filepath): + candidates = cls.backcompat_paths(filepath) + #log.debug("candidates = {0}".format(candidates)) + for candidate in candidates: + if system.resources_fs.exists(candidate): + filepath = candidate + break + + try: + with system.resources_fs.open(filepath) as file: + html = file.read() + # Log a warning if we can't parse the file, but don't error + if not check_html(html): + msg = "Couldn't parse html in {0}.".format(filepath) + log.warning(msg) + system.error_tracker("Warning: " + msg) + + definition = {'data': html} + + # TODO (ichuang): remove this after migration + # for Fall 2012 LMS migration: keep filename (and unmangled filename) + definition['filename'] = [ filepath, filename ] + + return definition + + except (ResourceNotFoundError) as err: + msg = 'Unable to load file contents at path {0}: {1} '.format( + filepath, err) + # add more info and re-raise + raise Exception(msg), None, sys.exc_info()[2] + + # TODO (vshnayder): make export put things in the right places. + + def definition_to_xml(self, resource_fs): + '''If the contents are valid xml, write them to filename.xml. Otherwise, + write just to filename.xml, and the html + string to filename.html. + ''' + try: + return etree.fromstring(self.definition['data']) + except etree.XMLSyntaxError: + pass + + # Not proper format. Write html to file, return an empty tag + pathname = name_to_pathname(self.url_name) + pathdir = path(pathname).dirname() + filepath = u'{category}/{pathname}.html'.format(category=self.category, + pathname=pathname) + + resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True) + with resource_fs.open(filepath, 'w') as file: + file.write(self.definition['data']) + + # write out the relative name + relname = path(pathname).basename() + + elt = etree.Element('html') + elt.set("filename", relname) + return elt From 62d1f1fe3ee54423012eecc89a050d353c5764f5 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 30 Oct 2012 20:14:55 -0400 Subject: [PATCH 039/228] update gitignore for pycharm --- .gitignore | 1 + .idea/.name | 1 - .idea/encodings.xml | 5 - .../inspectionProfiles/profiles_settings.xml | 7 - .idea/misc.xml | 34 -- .idea/mitx.iml | 9 - .idea/modules.xml | 9 - .idea/scopes/scope_settings.xml | 5 - .idea/vcs.xml | 7 - .idea/workspace.xml | 394 ------------------ lms/static/admin/css/ie.css | 63 --- test_root/log/.git-keep | 0 12 files changed, 1 insertion(+), 534 deletions(-) delete mode 100644 .idea/.name delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/mitx.iml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/scopes/scope_settings.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml delete mode 100644 lms/static/admin/css/ie.css delete mode 100644 test_root/log/.git-keep diff --git a/.gitignore b/.gitignore index 81d9a57d3c..f24bdb1bfc 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ cms/static/sass/*.css lms/lib/comment_client/python nosetests.xml cover_html/ +.idea/ diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 36d4bf6d51..0000000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -mitx \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index e206d70d85..0000000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index c60c33bb47..0000000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 715d0f8f2d..0000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - $APPLICATION_HOME_DIR$/lib/pycharm.jar!/resources/html5-schema/html5.rnc - - - - - - - - - - diff --git a/.idea/mitx.iml b/.idea/mitx.iml deleted file mode 100644 index a34a85703f..0000000000 --- a/.idea/mitx.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 7eaf1301cf..0000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml deleted file mode 100644 index 922003b843..0000000000 --- a/.idea/scopes/scope_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index c80f2198b5..0000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 08fc21c094..0000000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,394 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1351640399572 - 1351640399572 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lms/static/admin/css/ie.css b/lms/static/admin/css/ie.css deleted file mode 100644 index fd00f7f204..0000000000 --- a/lms/static/admin/css/ie.css +++ /dev/null @@ -1,63 +0,0 @@ -/* IE 6 & 7 */ - -/* Proper fixed width for dashboard in IE6 */ - -.dashboard #content { - *width: 768px; -} - -.dashboard #content-main { - *width: 535px; -} - -/* IE 6 ONLY */ - -/* Keep header from flowing off the page */ - -#container { - _position: static; -} - -/* Put the right sidebars back on the page */ - -.colMS #content-related { - _margin-right: 0; - _margin-left: 10px; - _position: static; -} - -/* Put the left sidebars back on the page */ - -.colSM #content-related { - _margin-right: 10px; - _margin-left: -115px; - _position: static; -} - -.form-row { - _height: 1%; -} - -/* Fix right margin for changelist filters in IE6 */ - -#changelist-filter ul { - _margin-right: -10px; -} - -/* IE ignores min-height, but treats height as if it were min-height */ - -.change-list .filtered { - _height: 400px; -} - -/* IE doesn't know alpha transparency in PNGs */ - -.inline-deletelink { - background: transparent url(../img/inline-delete-8bit.png) no-repeat; -} - -/* IE7 doesn't support inline-block */ -.change-list ul.toplinks li { - zoom: 1; - *display: inline; -} \ No newline at end of file diff --git a/test_root/log/.git-keep b/test_root/log/.git-keep deleted file mode 100644 index e69de29bb2..0000000000 From 5586139594b0b4295aba32be00abef95cfaea52c Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 30 Oct 2012 20:36:45 -0400 Subject: [PATCH 040/228] bugfix to self assessment --- common/lib/xmodule/setup.py | 2 +- common/lib/xmodule/xmodule/self_assessment_module.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index e6c8fe577d..d3889bc388 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -28,7 +28,7 @@ setup( "problem = xmodule.capa_module:CapaDescriptor", "problemset = xmodule.seq_module:SequenceDescriptor", "section = xmodule.backcompat_module:SemanticSectionDescriptor", - "selfassessment = xmodule.self_assessment_module:SelfAssessmentModule" + "selfassessment = xmodule.self_assessment_module:SelfAssessmentDescriptor", "sequential = xmodule.seq_module:SequenceDescriptor", "slides = xmodule.backcompat_module:TranslateCustomTagDescriptor", "vertical = xmodule.vertical_module:VerticalDescriptor", diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index c0dd835208..b8c2e85ef0 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -45,7 +45,7 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): Module for putting raw html in a course """ mako_template = "widgets/html-edit.html" - module_class = HtmlModule + module_class = SelfAssessmentModule filename_extension = "xml" template_dir_name = "selfassessment" From 7fc54dc717e3cdac12059a766e5d9bbe6e94ea0f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 30 Oct 2012 20:38:33 -0400 Subject: [PATCH 041/228] fix docstring --- common/lib/xmodule/xmodule/self_assessment_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index b8c2e85ef0..f1b8353c75 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -42,7 +42,7 @@ class SelfAssessmentModule(XModule): class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): """ - Module for putting raw html in a course + Module for putting self assessment questions into a course """ mako_template = "widgets/html-edit.html" module_class = SelfAssessmentModule From 0f5af40bf5fa490e958c48207e5ec46493b93bae Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 31 Oct 2012 13:06:05 -0400 Subject: [PATCH 042/228] add self assessment --- .../js/src/selfassessment/display.coffee | 303 ++++++++++++++++++ .../xmodule/xmodule/self_assessment_module.py | 29 +- 2 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee new file mode 100644 index 0000000000..5d5e3a8eb8 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -0,0 +1,303 @@ +class @Problem + + constructor: (element) -> + @el = $(element).find('.problems-wrapper') + @id = @el.data('problem-id') + @element_id = @el.attr('id') + @url = @el.data('url') + @render() + + $: (selector) -> + $(selector, @el) + + bind: => + problem_prefix = @element_id.replace(/problem_/,'') + @inputs = @$("[id^=input_#{problem_prefix}_]") + + @$('section.action input:button').click @refreshAnswers + @$('section.action input.check').click @check_fd + #@$('section.action input.check').click @check + @$('section.action input.show').click @show + @$('section.action input.save').click @save + + render: (content) -> + if content + @el.html(content) + JavascriptLoader.executeModuleScripts @el, () => + @setupInputTypes() + @bind() + else + $.postWithPrefix "#{@url}/problem_get", (response) => + @el.html(response.html) + JavascriptLoader.executeModuleScripts @el, () => + @setupInputTypes() + @bind() + + + # TODO add hooks for problem types here by inspecting response.html and doing + # stuff if a div w a class is found + + setupInputTypes: => + @inputtypeDisplays = {} + @el.find(".capa_inputtype").each (index, inputtype) => + classes = $(inputtype).attr('class').split(' ') + id = $(inputtype).attr('id') + for cls in classes + setupMethod = @inputtypeSetupMethods[cls] + if setupMethod? + @inputtypeDisplays[id] = setupMethod(inputtype) + + + ### + # 'check_fd' uses FormData to allow file submissions in the 'problem_check' dispatch, + # in addition to simple querystring-based answers + # + # NOTE: The dispatch 'problem_check' is being singled out for the use of FormData; + # maybe preferable to consolidate all dispatches to use FormData + ### + check_fd: => + Logger.log 'problem_check', @answers + + # If there are no file inputs in the problem, we can fall back on @check + if $('input:file').length == 0 + @check() + return + + if not window.FormData + alert "Submission aborted! Sorry, your browser does not support file uploads. If you can, please use Chrome or Safari which have been verified to support file uploads." + return + + fd = new FormData() + + # Sanity checks on submission + max_filesize = 4*1000*1000 # 4 MB + file_too_large = false + file_not_selected = false + required_files_not_submitted = false + unallowed_file_submitted = false + + errors = [] + + @inputs.each (index, element) -> + if element.type is 'file' + required_files = $(element).data("required_files") + allowed_files = $(element).data("allowed_files") + for file in element.files + if allowed_files.length != 0 and file.name not in allowed_files + unallowed_file_submitted = true + errors.push "You submitted #{file.name}; only #{allowed_files} are allowed." + if file.name in required_files + required_files.splice(required_files.indexOf(file.name), 1) + if file.size > max_filesize + file_too_large = true + errors.push 'Your file "' + file.name '" is too large (max size: ' + max_filesize/(1000*1000) + ' MB)' + fd.append(element.id, file) + if element.files.length == 0 + file_not_selected = true + fd.append(element.id, '') # In case we want to allow submissions with no file + if required_files.length != 0 + required_files_not_submitted = true + errors.push "You did not submit the required files: #{required_files}." + else + fd.append(element.id, element.value) + + + if file_not_selected + errors.push 'You did not select any files to submit' + + error_html = '
      \n' + for error in errors + error_html += '
    • ' + error + '
    • \n' + error_html += '
    ' + @gentle_alert error_html + + abort_submission = file_too_large or file_not_selected or unallowed_file_submitted or required_files_not_submitted + + settings = + type: "POST" + data: fd + processData: false + contentType: false + success: (response) => + switch response.success + when 'incorrect', 'correct' + @render(response.contents) + @updateProgress response + else + @gentle_alert response.success + + if not abort_submission + $.ajaxWithPrefix("#{@url}/problem_check", settings) + + check: => + Logger.log 'problem_check', @answers + $.postWithPrefix "#{@url}/problem_check", @answers, (response) => + switch response.success + when 'incorrect', 'correct' + @render(response.contents) + @updateProgress response + if @el.hasClass 'showed' + @el.removeClass 'showed' + else + @gentle_alert response.success + + reset: => + Logger.log 'problem_reset', @answers + $.postWithPrefix "#{@url}/problem_reset", id: @id, (response) => + @render(response.html) + @updateProgress response + + # TODO this needs modification to deal with javascript responses; perhaps we + # need something where responsetypes can define their own behavior when show + # is called. + show: => + if !@el.hasClass 'showed' + Logger.log 'problem_show', problem: @id + $.postWithPrefix "#{@url}/problem_show", (response) => + answers = response.answers + $.each answers, (key, value) => + if $.isArray(value) + for choice in value + @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true' + else + answer = @$("#answer_#{key}, #solution_#{key}") + answer.html(value) + Collapsible.setCollapsibles(answer) + + # TODO remove the above once everything is extracted into its own + # inputtype functions. + + @el.find(".capa_inputtype").each (index, inputtype) => + classes = $(inputtype).attr('class').split(' ') + for cls in classes + display = @inputtypeDisplays[$(inputtype).attr('id')] + showMethod = @inputtypeShowAnswerMethods[cls] + showMethod(inputtype, display, answers) if showMethod? + + @el.find('.problem > div').each (index, element) => + MathJax.Hub.Queue ["Typeset", MathJax.Hub, element] + + @$('.show').val 'Hide Answer' + @el.addClass 'showed' + @updateProgress response + else + @$('[id^=answer_], [id^=solution_]').text '' + @$('[correct_answer]').attr correct_answer: null + @el.removeClass 'showed' + @$('.show').val 'Show Answer' + + @el.find(".capa_inputtype").each (index, inputtype) => + display = @inputtypeDisplays[$(inputtype).attr('id')] + classes = $(inputtype).attr('class').split(' ') + for cls in classes + hideMethod = @inputtypeHideAnswerMethods[cls] + hideMethod(inputtype, display) if hideMethod? + + gentle_alert: (msg) => + if @el.find('.capa_alert').length + @el.find('.capa_alert').remove() + alert_elem = "
    " + msg + "
    " + @el.find('.action').after(alert_elem) + @el.find('.capa_alert').css(opacity: 0).animate(opacity: 1, 700) + + save: => + Logger.log 'problem_save', @answers + $.postWithPrefix "#{@url}/problem_save", @answers, (response) => + if response.success + saveMessage = "Your answers have been saved but not graded. Hit 'Check' to grade them." + @gentle_alert saveMessage + @updateProgress response + + refreshMath: (event, element) => + element = event.target unless element + elid = element.id.replace(/^input_/,'') + target = "display_" + elid + + # MathJax preprocessor is loaded by 'setupInputTypes' + preprocessor_tag = "inputtype_" + elid + mathjax_preprocessor = @inputtypeDisplays[preprocessor_tag] + + if jax = MathJax.Hub.getAllJax(target)[0] + eqn = $(element).val() + if mathjax_preprocessor + eqn = mathjax_preprocessor(eqn) + MathJax.Hub.Queue(['Text', jax, eqn], [@updateMathML, jax, element]) + + return # Explicit return for CoffeeScript + + updateMathML: (jax, element) => + try + $("##{element.id}_dynamath").val(jax.root.toMathML '') + catch exception + throw exception unless exception.restart + MathJax.Callback.After [@refreshMath, jax], exception.restart + + refreshAnswers: => + @$('input.schematic').each (index, element) -> + element.schematic.update_value() + @$(".CodeMirror").each (index, element) -> + element.CodeMirror.save() if element.CodeMirror.save + @answers = @inputs.serialize() + + inputtypeSetupMethods: + + 'text-input-dynamath': (element) => + ### + Return: function (eqn) -> eqn that preprocesses the user formula input before + it is fed into MathJax. Return 'false' if no preprocessor specified + ### + data = $(element).find('.text-input-dynamath_data') + + preprocessorClassName = data.data('preprocessor') + preprocessorClass = window[preprocessorClassName] + if not preprocessorClass? + return false + else + preprocessor = new preprocessorClass() + return preprocessor.fn + + javascriptinput: (element) => + + data = $(element).find(".javascriptinput_data") + + params = data.data("params") + submission = data.data("submission") + evaluation = data.data("evaluation") + problemState = data.data("problem_state") + displayClass = window[data.data('display_class')] + + if evaluation == '' + evaluation = null + + container = $(element).find(".javascriptinput_container") + submissionField = $(element).find(".javascriptinput_input") + + display = new displayClass(problemState, submission, evaluation, container, submissionField, params) + display.render() + + return display + + inputtypeShowAnswerMethods: + choicegroup: (element, display, answers) => + element = $(element) + + element.find('input').attr('disabled', 'disabled') + + input_id = element.attr('id').replace(/inputtype_/,'') + answer = answers[input_id] + for choice in answer + element.find("label[for='input_#{input_id}_#{choice}']").addClass 'choicegroup_correct' + + javascriptinput: (element, display, answers) => + answer_id = $(element).attr('id').split("_")[1...].join("_") + answer = JSON.parse(answers[answer_id]) + display.showAnswer(answer) + + inputtypeHideAnswerMethods: + choicegroup: (element, display) => + element = $(element) + element.find('input').attr('disabled', null) + element.find('label').removeClass('choicegroup_correct') + + javascriptinput: (element, display) => + display.hideAnswer() diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index f1b8353c75..17aa3832d8 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -23,7 +23,7 @@ log = logging.getLogger("mitx.courseware") class SelfAssessmentModule(XModule): js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'), resource_string(__name__, 'js/src/collapsible.coffee'), - resource_string(__name__, 'js/src/html/display.coffee') + resource_string(__name__, 'js/src/selfassessment/display.coffee') ] } js_module_name = "SelfAssessmentModule" @@ -38,6 +38,33 @@ class SelfAssessmentModule(XModule): instance_state, shared_state, **kwargs) self.html = self.definition['data'] + def handle_ajax(self, dispatch, get): + ''' + This is called by courseware.module_render, to handle an AJAX call. + "get" is request.POST. + + Returns a json dictionary: + { 'progress_changed' : True/False, + 'progress' : 'none'/'in_progress'/'done', + } + ''' + handlers = { + 'problem_get': self.get_problem, + 'problem_check': self.check_problem, + 'problem_save': self.save_problem, + } + + if dispatch not in handlers: + return 'Error' + + before = self.get_progress() + d = handlers[dispatch](get) + after = self.get_progress() + d.update({ + 'progress_changed': after != before, + 'progress_status': Progress.to_js_status_str(after), + }) + return json.dumps(d, cls=ComplexEncoder) class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): From ca5c33cdf3d6bfca61d7ffddfcfa0d22c6c9ce6d Mon Sep 17 00:00:00 2001 From: JM Van Thong Date: Wed, 31 Oct 2012 13:46:27 -0400 Subject: [PATCH 043/228] Fixed install by adding mysql --- brew-formulas.txt | 1 + create-dev-env.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/brew-formulas.txt b/brew-formulas.txt index 0aed9645d0..b5b555e2a0 100644 --- a/brew-formulas.txt +++ b/brew-formulas.txt @@ -7,3 +7,4 @@ python yuicompressor node graphviz +mysql diff --git a/create-dev-env.sh b/create-dev-env.sh index 5691315039..656a40524e 100755 --- a/create-dev-env.sh +++ b/create-dev-env.sh @@ -105,7 +105,7 @@ NUMPY_VER="1.6.2" SCIPY_VER="0.10.1" BREW_FILE="$BASE/mitx/brew-formulas.txt" LOG="/var/tmp/install-$(date +%Y%m%d-%H%M%S).log" -APT_PKGS="pkg-config curl git python-virtualenv build-essential python-dev gfortran liblapack-dev libfreetype6-dev libpng12-dev libxml2-dev libxslt-dev yui-compressor coffeescript graphviz graphviz-dev" +APT_PKGS="pkg-config curl git python-virtualenv build-essential python-dev gfortran liblapack-dev libfreetype6-dev libpng12-dev libxml2-dev libxslt-dev yui-compressor coffeescript graphviz graphviz-dev mysql" if [[ $EUID -eq 0 ]]; then error "This script should not be run using sudo or as the root user" From 2cb38a2c0fd81384ab293f8e896756526c4b6fb1 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 31 Oct 2012 15:37:43 -0400 Subject: [PATCH 044/228] working on new xmodule --- .../0-4455953e190a120e56a33841e5c04a9b.coffee | 9 + .../0-792b1e29b66e460f0798bdd940c30ad8.coffee | 10 + .../0-79a64f2010d8b4cb8d0f0d6912c70c12.coffee | 10 + .../0-c0f10bbfa80be09f66bef86df2e1152a.coffee | 11 + .../0-fdfb7ccca8edfb703fc7a5f124a67f24.coffee | 9 + .../_0-3d2f9902e9ecf2dac554cd9b9e661a89.scss | 5 + .../sass/descriptor/_module-styles.scss | 1 + .../js/src/selfassessment/display.coffee | 85 +-- .../xmodule/xmodule/self_assessment_module.py | 516 +++++++++++++----- 9 files changed, 441 insertions(+), 215 deletions(-) create mode 100644 cms/static/coffee/descriptor/0-4455953e190a120e56a33841e5c04a9b.coffee create mode 100644 cms/static/coffee/descriptor/0-792b1e29b66e460f0798bdd940c30ad8.coffee create mode 100644 cms/static/coffee/descriptor/0-79a64f2010d8b4cb8d0f0d6912c70c12.coffee create mode 100644 cms/static/coffee/descriptor/0-c0f10bbfa80be09f66bef86df2e1152a.coffee create mode 100644 cms/static/coffee/descriptor/0-fdfb7ccca8edfb703fc7a5f124a67f24.coffee create mode 100644 cms/static/sass/descriptor/_0-3d2f9902e9ecf2dac554cd9b9e661a89.scss create mode 100644 cms/static/sass/descriptor/_module-styles.scss diff --git a/cms/static/coffee/descriptor/0-4455953e190a120e56a33841e5c04a9b.coffee b/cms/static/coffee/descriptor/0-4455953e190a120e56a33841e5c04a9b.coffee new file mode 100644 index 0000000000..33942bc97d --- /dev/null +++ b/cms/static/coffee/descriptor/0-4455953e190a120e56a33841e5c04a9b.coffee @@ -0,0 +1,9 @@ +class @SequenceDescriptor extends XModule.Descriptor + constructor: (@element) -> + @$tabs = $(@element).find("#sequence-list") + @$tabs.sortable( + update: (event, ui) => @update() + ) + + save: -> + children: $('#sequence-list li a', @element).map((idx, el) -> $(el).data('id')).toArray() diff --git a/cms/static/coffee/descriptor/0-792b1e29b66e460f0798bdd940c30ad8.coffee b/cms/static/coffee/descriptor/0-792b1e29b66e460f0798bdd940c30ad8.coffee new file mode 100644 index 0000000000..7c3c09cefc --- /dev/null +++ b/cms/static/coffee/descriptor/0-792b1e29b66e460f0798bdd940c30ad8.coffee @@ -0,0 +1,10 @@ +class @JSONEditingDescriptor extends XModule.Descriptor + constructor: (@element) -> + @edit_box = CodeMirror.fromTextArea($(".edit-box", @element)[0], { + mode: { name: "javascript", json: true } + lineNumbers: true + lineWrapping: true + }) + + save: -> + data: JSON.parse @edit_box.getValue() diff --git a/cms/static/coffee/descriptor/0-79a64f2010d8b4cb8d0f0d6912c70c12.coffee b/cms/static/coffee/descriptor/0-79a64f2010d8b4cb8d0f0d6912c70c12.coffee new file mode 100644 index 0000000000..68981e465b --- /dev/null +++ b/cms/static/coffee/descriptor/0-79a64f2010d8b4cb8d0f0d6912c70c12.coffee @@ -0,0 +1,10 @@ +class @XMLEditingDescriptor extends XModule.Descriptor + constructor: (@element) -> + @edit_box = CodeMirror.fromTextArea($(".edit-box", @element)[0], { + mode: "xml" + lineNumbers: true + lineWrapping: true + }) + + save: -> + data: @edit_box.getValue() diff --git a/cms/static/coffee/descriptor/0-c0f10bbfa80be09f66bef86df2e1152a.coffee b/cms/static/coffee/descriptor/0-c0f10bbfa80be09f66bef86df2e1152a.coffee new file mode 100644 index 0000000000..e678a9b2eb --- /dev/null +++ b/cms/static/coffee/descriptor/0-c0f10bbfa80be09f66bef86df2e1152a.coffee @@ -0,0 +1,11 @@ +class @HTMLEditingDescriptor + constructor: (@element) -> + @edit_box = CodeMirror.fromTextArea($(".edit-box", @element)[0], { + mode: "text/html" + lineNumbers: true + lineWrapping: true + }) + + save: -> + data: @edit_box.getValue() + diff --git a/cms/static/coffee/descriptor/0-fdfb7ccca8edfb703fc7a5f124a67f24.coffee b/cms/static/coffee/descriptor/0-fdfb7ccca8edfb703fc7a5f124a67f24.coffee new file mode 100644 index 0000000000..7ce69e542a --- /dev/null +++ b/cms/static/coffee/descriptor/0-fdfb7ccca8edfb703fc7a5f124a67f24.coffee @@ -0,0 +1,9 @@ +class @VerticalDescriptor extends XModule.Descriptor + constructor: (@element) -> + @$items = $(@element).find(".vert-mod") + @$items.sortable( + update: (event, ui) => @update() + ) + + save: -> + children: $('.vert-mod li', @element).map((idx, el) -> $(el).data('id')).toArray() diff --git a/cms/static/sass/descriptor/_0-3d2f9902e9ecf2dac554cd9b9e661a89.scss b/cms/static/sass/descriptor/_0-3d2f9902e9ecf2dac554cd9b9e661a89.scss new file mode 100644 index 0000000000..0dc07919ae --- /dev/null +++ b/cms/static/sass/descriptor/_0-3d2f9902e9ecf2dac554cd9b9e661a89.scss @@ -0,0 +1,5 @@ +.CodeMirror { + background: #fff; + font-size: 13px; + color: #3c3c3c; +} \ No newline at end of file diff --git a/cms/static/sass/descriptor/_module-styles.scss b/cms/static/sass/descriptor/_module-styles.scss new file mode 100644 index 0000000000..81d1cc800f --- /dev/null +++ b/cms/static/sass/descriptor/_module-styles.scss @@ -0,0 +1 @@ +.xmodule_edit.xmodule_DiscussionDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }.xmodule_edit.xmodule_CapaDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }.xmodule_edit.xmodule_ABTestDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }.xmodule_edit.xmodule_RawDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }.xmodule_edit.xmodule_ErrorDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }.xmodule_edit.xmodule_VideoDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; }.xmodule_edit.xmodule_CustomTagDescriptor { @import "0-3d2f9902e9ecf2dac554cd9b9e661a89.scss"; } \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index 5d5e3a8eb8..da91c44af7 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -15,8 +15,7 @@ class @Problem @inputs = @$("[id^=input_#{problem_prefix}_]") @$('section.action input:button').click @refreshAnswers - @$('section.action input.check').click @check_fd - #@$('section.action input.check').click @check + @$('section.action input.check').click @check @$('section.action input.show').click @show @$('section.action input.save').click @save @@ -47,88 +46,6 @@ class @Problem if setupMethod? @inputtypeDisplays[id] = setupMethod(inputtype) - - ### - # 'check_fd' uses FormData to allow file submissions in the 'problem_check' dispatch, - # in addition to simple querystring-based answers - # - # NOTE: The dispatch 'problem_check' is being singled out for the use of FormData; - # maybe preferable to consolidate all dispatches to use FormData - ### - check_fd: => - Logger.log 'problem_check', @answers - - # If there are no file inputs in the problem, we can fall back on @check - if $('input:file').length == 0 - @check() - return - - if not window.FormData - alert "Submission aborted! Sorry, your browser does not support file uploads. If you can, please use Chrome or Safari which have been verified to support file uploads." - return - - fd = new FormData() - - # Sanity checks on submission - max_filesize = 4*1000*1000 # 4 MB - file_too_large = false - file_not_selected = false - required_files_not_submitted = false - unallowed_file_submitted = false - - errors = [] - - @inputs.each (index, element) -> - if element.type is 'file' - required_files = $(element).data("required_files") - allowed_files = $(element).data("allowed_files") - for file in element.files - if allowed_files.length != 0 and file.name not in allowed_files - unallowed_file_submitted = true - errors.push "You submitted #{file.name}; only #{allowed_files} are allowed." - if file.name in required_files - required_files.splice(required_files.indexOf(file.name), 1) - if file.size > max_filesize - file_too_large = true - errors.push 'Your file "' + file.name '" is too large (max size: ' + max_filesize/(1000*1000) + ' MB)' - fd.append(element.id, file) - if element.files.length == 0 - file_not_selected = true - fd.append(element.id, '') # In case we want to allow submissions with no file - if required_files.length != 0 - required_files_not_submitted = true - errors.push "You did not submit the required files: #{required_files}." - else - fd.append(element.id, element.value) - - - if file_not_selected - errors.push 'You did not select any files to submit' - - error_html = '
      \n' - for error in errors - error_html += '
    • ' + error + '
    • \n' - error_html += '
    ' - @gentle_alert error_html - - abort_submission = file_too_large or file_not_selected or unallowed_file_submitted or required_files_not_submitted - - settings = - type: "POST" - data: fd - processData: false - contentType: false - success: (response) => - switch response.success - when 'incorrect', 'correct' - @render(response.contents) - @updateProgress response - else - @gentle_alert response.success - - if not abort_submission - $.ajaxWithPrefix("#{@url}/problem_check", settings) - check: => Logger.log 'problem_check', @answers $.postWithPrefix "#{@url}/problem_check", @answers, (response) => diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 17aa3832d8..98b464d57e 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -26,6 +26,7 @@ class SelfAssessmentModule(XModule): resource_string(__name__, 'js/src/selfassessment/display.coffee') ] } + js_module_name = "SelfAssessmentModule" def get_html(self): @@ -36,22 +37,97 @@ class SelfAssessmentModule(XModule): instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs) - self.html = self.definition['data'] + + self.attempts = 0 + self.max_attempts = None + + dom2 = etree.fromstring(definition['data']) + + self.max_attempts = self.metadata.get('attempts', None) + if self.max_attempts is not None: + self.max_attempts = int(self.max_attempts) + + self.show_answer = self.metadata.get('showanswer', 'closed') + + self.force_save_button = self.metadata.get('force_save_button', 'false') + + if self.show_answer == "": + self.show_answer = "closed" + + if instance_state is not None: + instance_state = json.loads(instance_state) + if instance_state is not None and 'attempts' in instance_state: + self.attempts = instance_state['attempts'] + + self.name = only_one(dom2.xpath('/problem/@name')) + + self.seed = 1 + + try: + # TODO (vshnayder): move as much as possible of this work and error + # checking to descriptor load time + self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), + instance_state, seed=self.seed, system=self.system) + except Exception as err: + msg = 'cannot create LoncapaProblem {loc}: {err}'.format( + loc=self.location.url(), err=err) + # TODO (vshnayder): do modules need error handlers too? + # We shouldn't be switching on DEBUG. + if self.system.DEBUG: + log.warning(msg) + # TODO (vshnayder): This logic should be general, not here--and may + # want to preserve the data instead of replacing it. + # e.g. in the CMS + msg = '

    %s

    ' % msg.replace('<', '<') + msg += '

    %s

    ' % traceback.format_exc().replace('<', '<') + # create a dummy problem with error message instead of failing + problem_text = ('' + 'Problem %s has an error:%s' % + (self.location.url(), msg)) + self.lcp = LoncapaProblem( + problem_text, self.location.html_id(), + instance_state, seed=self.seed, system=self.system) + else: + # add extra info and raise + raise Exception(msg), None, sys.exc_info()[2] + + @staticmethod + def make_dict_of_responses(get): + '''Make dictionary of student responses (aka "answers") + get is POST dictionary. + ''' + answers = dict() + for key in get: + # e.g. input_resistor_1 ==> resistor_1 + _, _, name = key.partition('_') + + # This allows for answers which require more than one value for + # the same form input (e.g. checkbox inputs). The convention is that + # if the name ends with '[]' (which looks like an array), then the + # answer will be an array. + if not name.endswith('[]'): + answers[name] = get[key] + else: + name = name[:-2] + answers[name] = get.getlist(key) + + return answers + def handle_ajax(self, dispatch, get): - ''' - This is called by courseware.module_render, to handle an AJAX call. - "get" is request.POST. + ''' + This is called by courseware.module_render, to handle an AJAX call. + "get" is request.POST. - Returns a json dictionary: - { 'progress_changed' : True/False, - 'progress' : 'none'/'in_progress'/'done', - } - ''' + Returns a json dictionary: + { 'progress_changed' : True/False, + 'progress' : 'none'/'in_progress'/'done', + } + ''' handlers = { 'problem_get': self.get_problem, - 'problem_check': self.check_problem, 'problem_save': self.save_problem, + 'problem_check': self.check_problem, } if dispatch not in handlers: @@ -66,136 +142,314 @@ class SelfAssessmentModule(XModule): }) return json.dumps(d, cls=ComplexEncoder) + def save_problem(self, get): + ''' + Save the passed in answers. + Returns a dict { 'success' : bool, ['error' : error-msg]}, + with the error key only present if success is False. + ''' + event_info = dict() + event_info['state'] = self.lcp.get_state() + event_info['problem_id'] = self.location.url() + + answers = self.make_dict_of_responses(get) + event_info['answers'] = answers + + self.lcp.student_answers = answers + + # TODO: should this be save_problem_fail? Looks like success to me... + self.system.track_function('save_problem_fail', event_info) + return {'success': True} + + def check_problem(self, get): + ''' Checks whether answers to a problem are correct, and + returns a map of correct/incorrect answers: + + {'success' : bool, + 'contents' : html} + ''' + event_info = dict() + event_info['state'] = self.lcp.get_state() + event_info['problem_id'] = self.location.url() + + answers = self.make_dict_of_responses(get) + event_info['answers'] = convert_files_to_filenames(answers) + + try: + old_state = self.lcp.get_state() + lcp_id = self.lcp.problem_id + correct_map = self.lcp.grade_answers(answers) + except StudentInputError as inst: + # TODO (vshnayder): why is this line here? + #self.lcp = LoncapaProblem(self.definition['data'], + # id=lcp_id, state=old_state, system=self.system) + log.exception("StudentInputError in capa_module:problem_check") + return {'success': inst.message} + except Exception, err: + # TODO: why is this line here? + #self.lcp = LoncapaProblem(self.definition['data'], + # id=lcp_id, state=old_state, system=self.system) + if self.system.DEBUG: + msg = "Error checking problem: " + str(err) + msg += '\nTraceback:\n' + traceback.format_exc() + return {'success': msg} + log.exception("Error in capa_module problem checking") + raise Exception("error in capa_module") + + self.attempts = self.attempts + 1 + self.lcp.done = True + + # success = correct if ALL questions in this problem are correct + success = 'correct' + for answer_id in correct_map: + if not correct_map.is_correct(answer_id): + success = 'incorrect' + + # NOTE: We are logging both full grading and queued-grading submissions. In the latter, + # 'success' will always be incorrect + event_info['correct_map'] = correct_map.get_dict() + event_info['success'] = success + event_info['attempts'] = self.attempts + self.system.track_function('save_problem_check', event_info) + + if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback + self.system.psychometrics_handler(self.get_instance_state()) + + # render problem into HTML + html = self.get_problem_html(encapsulate=False) + + return {'success': success, + 'contents': html, + } + + # Figure out if we should move these to capa_problem? + def get_problem(self, get): + ''' Return results of get_problem_html, as a simple dict for json-ing. + { 'html': } + + Used if we want to reconfirm we have the right thing e.g. after + several AJAX calls. + ''' + return {'html': self.get_problem_html(encapsulate=False)} + + @staticmethod + def make_dict_of_responses(get): + '''Make dictionary of student responses (aka "answers") + get is POST dictionary. + ''' + answers = dict() + for key in get: + # e.g. input_resistor_1 ==> resistor_1 + _, _, name = key.partition('_') + + # This allows for answers which require more than one value for + # the same form input (e.g. checkbox inputs). The convention is that + # if the name ends with '[]' (which looks like an array), then the + # answer will be an array. + if not name.endswith('[]'): + answers[name] = get[key] + else: + name = name[:-2] + answers[name] = get.getlist(key) + + return answers + + def get_instance_state(self): + state = self.lcp.get_state() + state['attempts'] = self.attempts + return json.dumps(state) + + def get_score(self): + return self.lcp.get_score() + + def max_score(self): + return self.lcp.get_max_score() + + def get_progress(self): + ''' For now, just return score / max_score + ''' + d = self.get_score() + score = d['score'] + total = d['total'] + if total > 0: + try: + return Progress(score, total) + except Exception as err: + log.exception("Got bad progress") + return None + return None + + def get_html(self): + return self.system.render_template('problem_ajax.html', { + 'element_id': self.location.html_id(), + 'id': self.id, + 'ajax_url': self.system.ajax_url, + }) + + def get_problem_html(self, encapsulate=True): + '''Return html for the problem. Adds check, reset, save buttons + as necessary based on the problem config and state.''' + + try: + html = self.lcp.get_html() + except Exception, err: + log.exception(err) + + # TODO (vshnayder): another switch on DEBUG. + if self.system.DEBUG: + msg = ( + '[courseware.capa.capa_module] ' + 'Failed to generate HTML for problem %s' % + (self.location.url())) + msg += '

    Error:

    %s

    ' % str(err).replace('<', '<') + msg += '

    %s

    ' % traceback.format_exc().replace('<', '<') + html = msg + else: + # We're in non-debug mode, and possibly even in production. We want + # to avoid bricking of problem as much as possible + + # Presumably, student submission has corrupted LoncapaProblem HTML. + # First, pull down all student answers + student_answers = self.lcp.student_answers + answer_ids = student_answers.keys() + + # Some inputtypes, such as dynamath, have additional "hidden" state that + # is not exposed to the student. Keep those hidden + # TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id + hidden_state_keywords = ['dynamath'] + for answer_id in answer_ids: + for hidden_state_keyword in hidden_state_keywords: + if answer_id.find(hidden_state_keyword) >= 0: + student_answers.pop(answer_id) + + # Next, generate a fresh LoncapaProblem + self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), + state=None, # Tabula rasa + seed=self.seed, system=self.system) + + # Prepend a scary warning to the student + warning = '
    '\ + '

    Warning: The problem has been reset to its initial state!

    '\ + 'The problem\'s state was corrupted by an invalid submission. '\ + 'The submission consisted of:'\ + '
      ' + for student_answer in student_answers.values(): + if student_answer != '': + warning += '
    • ' + cgi.escape(student_answer) + '
    • ' + warning += '
    '\ + 'If this error persists, please contact the course staff.'\ + '
    ' + + html = warning + try: + html += self.lcp.get_html() + except Exception, err: # Couldn't do it. Give up + log.exception(err) + raise + + content = {'name': self.display_name, + 'html': html, + 'weight': self.descriptor.weight, + } + + # We using strings as truthy values, because the terminology of the + # check button is context-specific. + + # Put a "Check" button if unlimited attempts or still some left + if self.max_attempts is None or self.attempts < self.max_attempts-1: + check_button = "Check" + else: + # Will be final check so let user know that + check_button = "Final Check" + + reset_button = True + save_button = True + + # If we're after deadline, or user has exhausted attempts, + # question is read-only. + if self.closed(): + check_button = False + reset_button = False + save_button = False + + # User submitted a problem, and hasn't reset. We don't want + # more submissions. + if self.lcp.done and self.rerandomize == "always": + check_button = False + save_button = False + + # Only show the reset button if pressing it will show different values + if self.rerandomize not in ["always", "onreset"]: + reset_button = False + + # User hasn't submitted an answer yet -- we don't want resets + if not self.lcp.done: + reset_button = False + + # We may not need a "save" button if infinite number of attempts and + # non-randomized. The problem author can force it. It's a bit weird for + # randomization to control this; should perhaps be cleaned up. + if (self.force_save_button == "false") and (self.max_attempts is None and self.rerandomize != "always"): + save_button = False + + context = {'problem': content, + 'id': self.id, + 'check_button': check_button, + 'reset_button': reset_button, + 'save_button': save_button, + 'answer_available': self.answer_available(), + 'ajax_url': self.system.ajax_url, + 'attempts_used': self.attempts, + 'attempts_allowed': self.max_attempts, + 'progress': self.get_progress(), + } + + html = self.system.render_template('problem.html', context) + if encapsulate: + html = '
    '.format( + id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "
    " + + # cdodge: OK, we have to do two rounds of url reference subsitutions + # one which uses the 'asset library' that is served by the contentstore and the + # more global /static/ filesystem based static content. + # NOTE: rewrite_content_links is defined in XModule + # This is a bit unfortunate and I'm sure we'll try to considate this into + # a one step process. + html = rewrite_links(html, self.rewrite_content_links) + + # now do the substitutions which are filesystem based, e.g. '/static/' prefixes + return self.system.replace_urls(html, self.metadata['data_dir']) + class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): """ Module for putting self assessment questions into a course """ - mako_template = "widgets/html-edit.html" - module_class = SelfAssessmentModule - filename_extension = "xml" - template_dir_name = "selfassessment" - js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]} - js_module_name = "HTMLEditingDescriptor" + stores_state = True + has_score = True + template_dir_name = 'selfassessment' - # VS[compat] TODO (cpennington): Delete this method once all fall 2012 course - # are being edited in the cms + # Capa modules have some additional metadata: + # TODO (vshnayder): do problems have any other metadata? Do they + # actually use type and points? + metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points') + + # VS[compat] + # TODO (cpennington): Delete this method once all fall 2012 course are being + # edited in the cms @classmethod def backcompat_paths(cls, path): - if path.endswith('.html.xml'): - path = path[:-9] + '.html' # backcompat--look for html instead of xml - if path.endswith('.html.html'): - path = path[:-5] # some people like to include .html in filenames.. - candidates = [] - while os.sep in path: - candidates.append(path) - _, _, path = path.partition(os.sep) + return [ + 'problems/' + path[8:], + path[8:], + ] - # also look for .html versions instead of .xml - nc = [] - for candidate in candidates: - if candidate.endswith('.xml'): - nc.append(candidate[:-4] + '.html') - return candidates + nc + def __init__(self, *args, **kwargs): + super(CapaDescriptor, self).__init__(*args, **kwargs) - # NOTE: html descriptors are special. We do not want to parse and - # export them ourselves, because that can break things (e.g. lxml - # adds body tags when it exports, but they should just be html - # snippets that will be included in the middle of pages. - - @classmethod - def load_definition(cls, xml_object, system, location): - '''Load a descriptor from the specified xml_object: - - If there is a filename attribute, load it as a string, and - log a warning if it is not parseable by etree.HTMLParser. - - If there is not a filename attribute, the definition is the body - of the xml_object, without the root tag (do not want in the - middle of a page) - ''' - filename = xml_object.get('filename') - if filename is None: - definition_xml = copy.deepcopy(xml_object) - cls.clean_metadata_from_xml(definition_xml) - return {'data': stringify_children(definition_xml)} + weight_string = self.metadata.get('weight', None) + if weight_string: + self.weight = float(weight_string) else: - # html is special. cls.filename_extension is 'xml', but - # if 'filename' is in the definition, that means to load - # from .html - # 'filename' in html pointers is a relative path - # (not same as 'html/blah.html' when the pointer is in a directory itself) - pointer_path = "{category}/{url_path}".format(category='html', - url_path=name_to_pathname(location.name)) - base = path(pointer_path).dirname() - #log.debug("base = {0}, base.dirname={1}, filename={2}".format(base, base.dirname(), filename)) - filepath = "{base}/{name}.html".format(base=base, name=filename) - #log.debug("looking for html file for {0} at {1}".format(location, filepath)) - - - - # VS[compat] - # TODO (cpennington): If the file doesn't exist at the right path, - # give the class a chance to fix it up. The file will be written out - # again in the correct format. This should go away once the CMS is - # online and has imported all current (fall 2012) courses from xml - if not system.resources_fs.exists(filepath): - candidates = cls.backcompat_paths(filepath) - #log.debug("candidates = {0}".format(candidates)) - for candidate in candidates: - if system.resources_fs.exists(candidate): - filepath = candidate - break - - try: - with system.resources_fs.open(filepath) as file: - html = file.read() - # Log a warning if we can't parse the file, but don't error - if not check_html(html): - msg = "Couldn't parse html in {0}.".format(filepath) - log.warning(msg) - system.error_tracker("Warning: " + msg) - - definition = {'data': html} - - # TODO (ichuang): remove this after migration - # for Fall 2012 LMS migration: keep filename (and unmangled filename) - definition['filename'] = [ filepath, filename ] - - return definition - - except (ResourceNotFoundError) as err: - msg = 'Unable to load file contents at path {0}: {1} '.format( - filepath, err) - # add more info and re-raise - raise Exception(msg), None, sys.exc_info()[2] - - # TODO (vshnayder): make export put things in the right places. - - def definition_to_xml(self, resource_fs): - '''If the contents are valid xml, write them to filename.xml. Otherwise, - write just to filename.xml, and the html - string to filename.html. - ''' - try: - return etree.fromstring(self.definition['data']) - except etree.XMLSyntaxError: - pass - - # Not proper format. Write html to file, return an empty tag - pathname = name_to_pathname(self.url_name) - pathdir = path(pathname).dirname() - filepath = u'{category}/{pathname}.html'.format(category=self.category, - pathname=pathname) - - resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True) - with resource_fs.open(filepath, 'w') as file: - file.write(self.definition['data']) - - # write out the relative name - relname = path(pathname).basename() - - elt = etree.Element('html') - elt.set("filename", relname) - return elt + self.weight = None From 64c791b38a63e38eb2d2fa6fc80e088b2874e9ef Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 31 Oct 2012 16:18:55 -0400 Subject: [PATCH 045/228] working on self assessment module --- .../xmodule/xmodule/self_assessment_module.py | 535 ++++++------------ 1 file changed, 163 insertions(+), 372 deletions(-) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 98b464d57e..df69213281 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -1,24 +1,41 @@ -import copy -from fs.errors import ResourceNotFoundError +import cgi +import datetime +import dateutil +import dateutil.parser +import json import logging -import os +import traceback +import re import sys + +from datetime import timedelta from lxml import etree from lxml.html import rewrite_links -from path import path - -from .x_module import XModule from pkg_resources import resource_string -from .xml_module import XmlDescriptor, name_to_pathname -from .editing_module import EditingDescriptor -from .stringify import stringify_children -from .html_checker import check_html -from xmodule.modulestore import Location -from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent +from capa.capa_problem import LoncapaProblem +from capa.responsetypes import StudentInputError +from capa.util import convert_files_to_filenames +from progress import Progress +from xmodule.x_module import XModule +from xmodule.raw_module import RawDescriptor +from xmodule.exceptions import NotFoundError log = logging.getLogger("mitx.courseware") +def only_one(lst, default="", process=lambda x: x): + """ + If lst is empty, returns default + If lst has a single element, applies process to that element and returns it + Otherwise, raises an exeception + """ + if len(lst) == 0: + return default + elif len(lst) == 1: + return process(lst[0]) + else: + raise Exception('Malformed XML') + class SelfAssessmentModule(XModule): js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'), @@ -26,7 +43,6 @@ class SelfAssessmentModule(XModule): resource_string(__name__, 'js/src/selfassessment/display.coffee') ] } - js_module_name = "SelfAssessmentModule" def get_html(self): @@ -38,22 +54,15 @@ class SelfAssessmentModule(XModule): XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs) + dom2 = etree.fromstring(definition['data']) + self.attempts = 0 self.max_attempts = None - dom2 = etree.fromstring(definition['data']) - self.max_attempts = self.metadata.get('attempts', None) if self.max_attempts is not None: self.max_attempts = int(self.max_attempts) - self.show_answer = self.metadata.get('showanswer', 'closed') - - self.force_save_button = self.metadata.get('force_save_button', 'false') - - if self.show_answer == "": - self.show_answer = "closed" - if instance_state is not None: instance_state = json.loads(instance_state) if instance_state is not None and 'attempts' in instance_state: @@ -61,198 +70,11 @@ class SelfAssessmentModule(XModule): self.name = only_one(dom2.xpath('/problem/@name')) - self.seed = 1 + self.rubric=etree.tostring(only_one(dom2.xpath("/rubric"))) + self.problem=etree.tostring(only_one(dom2.xpath("/problem"))) - try: - # TODO (vshnayder): move as much as possible of this work and error - # checking to descriptor load time - self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), - instance_state, seed=self.seed, system=self.system) - except Exception as err: - msg = 'cannot create LoncapaProblem {loc}: {err}'.format( - loc=self.location.url(), err=err) - # TODO (vshnayder): do modules need error handlers too? - # We shouldn't be switching on DEBUG. - if self.system.DEBUG: - log.warning(msg) - # TODO (vshnayder): This logic should be general, not here--and may - # want to preserve the data instead of replacing it. - # e.g. in the CMS - msg = '

    %s

    ' % msg.replace('<', '<') - msg += '

    %s

    ' % traceback.format_exc().replace('<', '<') - # create a dummy problem with error message instead of failing - problem_text = ('' - 'Problem %s has an error:%s' % - (self.location.url(), msg)) - self.lcp = LoncapaProblem( - problem_text, self.location.html_id(), - instance_state, seed=self.seed, system=self.system) - else: - # add extra info and raise - raise Exception(msg), None, sys.exc_info()[2] - - @staticmethod - def make_dict_of_responses(get): - '''Make dictionary of student responses (aka "answers") - get is POST dictionary. - ''' - answers = dict() - for key in get: - # e.g. input_resistor_1 ==> resistor_1 - _, _, name = key.partition('_') - - # This allows for answers which require more than one value for - # the same form input (e.g. checkbox inputs). The convention is that - # if the name ends with '[]' (which looks like an array), then the - # answer will be an array. - if not name.endswith('[]'): - answers[name] = get[key] - else: - name = name[:-2] - answers[name] = get.getlist(key) - - return answers - - - def handle_ajax(self, dispatch, get): - ''' - This is called by courseware.module_render, to handle an AJAX call. - "get" is request.POST. - - Returns a json dictionary: - { 'progress_changed' : True/False, - 'progress' : 'none'/'in_progress'/'done', - } - ''' - handlers = { - 'problem_get': self.get_problem, - 'problem_save': self.save_problem, - 'problem_check': self.check_problem, - } - - if dispatch not in handlers: - return 'Error' - - before = self.get_progress() - d = handlers[dispatch](get) - after = self.get_progress() - d.update({ - 'progress_changed': after != before, - 'progress_status': Progress.to_js_status_str(after), - }) - return json.dumps(d, cls=ComplexEncoder) - - def save_problem(self, get): - ''' - Save the passed in answers. - Returns a dict { 'success' : bool, ['error' : error-msg]}, - with the error key only present if success is False. - ''' - event_info = dict() - event_info['state'] = self.lcp.get_state() - event_info['problem_id'] = self.location.url() - - answers = self.make_dict_of_responses(get) - event_info['answers'] = answers - - self.lcp.student_answers = answers - - # TODO: should this be save_problem_fail? Looks like success to me... - self.system.track_function('save_problem_fail', event_info) - return {'success': True} - - def check_problem(self, get): - ''' Checks whether answers to a problem are correct, and - returns a map of correct/incorrect answers: - - {'success' : bool, - 'contents' : html} - ''' - event_info = dict() - event_info['state'] = self.lcp.get_state() - event_info['problem_id'] = self.location.url() - - answers = self.make_dict_of_responses(get) - event_info['answers'] = convert_files_to_filenames(answers) - - try: - old_state = self.lcp.get_state() - lcp_id = self.lcp.problem_id - correct_map = self.lcp.grade_answers(answers) - except StudentInputError as inst: - # TODO (vshnayder): why is this line here? - #self.lcp = LoncapaProblem(self.definition['data'], - # id=lcp_id, state=old_state, system=self.system) - log.exception("StudentInputError in capa_module:problem_check") - return {'success': inst.message} - except Exception, err: - # TODO: why is this line here? - #self.lcp = LoncapaProblem(self.definition['data'], - # id=lcp_id, state=old_state, system=self.system) - if self.system.DEBUG: - msg = "Error checking problem: " + str(err) - msg += '\nTraceback:\n' + traceback.format_exc() - return {'success': msg} - log.exception("Error in capa_module problem checking") - raise Exception("error in capa_module") - - self.attempts = self.attempts + 1 - self.lcp.done = True - - # success = correct if ALL questions in this problem are correct - success = 'correct' - for answer_id in correct_map: - if not correct_map.is_correct(answer_id): - success = 'incorrect' - - # NOTE: We are logging both full grading and queued-grading submissions. In the latter, - # 'success' will always be incorrect - event_info['correct_map'] = correct_map.get_dict() - event_info['success'] = success - event_info['attempts'] = self.attempts - self.system.track_function('save_problem_check', event_info) - - if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback - self.system.psychometrics_handler(self.get_instance_state()) - - # render problem into HTML - html = self.get_problem_html(encapsulate=False) - - return {'success': success, - 'contents': html, - } - - # Figure out if we should move these to capa_problem? - def get_problem(self, get): - ''' Return results of get_problem_html, as a simple dict for json-ing. - { 'html': } - - Used if we want to reconfirm we have the right thing e.g. after - several AJAX calls. - ''' - return {'html': self.get_problem_html(encapsulate=False)} - - @staticmethod - def make_dict_of_responses(get): - '''Make dictionary of student responses (aka "answers") - get is POST dictionary. - ''' - answers = dict() - for key in get: - # e.g. input_resistor_1 ==> resistor_1 - _, _, name = key.partition('_') - - # This allows for answers which require more than one value for - # the same form input (e.g. checkbox inputs). The convention is that - # if the name ends with '[]' (which looks like an array), then the - # answer will be an array. - if not name.endswith('[]'): - answers[name] = get[key] - else: - name = name[:-2] - answers[name] = get.getlist(key) - - return answers + self.lcp = LoncapaProblem(self.problem, self.location.html_id(), + instance_state, seed=self.seed, system=self.system) def get_instance_state(self): state = self.lcp.get_state() @@ -260,14 +82,14 @@ class SelfAssessmentModule(XModule): return json.dumps(state) def get_score(self): - return self.lcp.get_score() + return self.lcp.get_score() def max_score(self): return self.lcp.get_max_score() - def get_progress(self): - ''' For now, just return score / max_score - ''' + def get_progress(self): + ''' For now, just return score / max_score + ''' d = self.get_score() score = d['score'] total = d['total'] @@ -286,170 +108,139 @@ class SelfAssessmentModule(XModule): 'ajax_url': self.system.ajax_url, }) - def get_problem_html(self, encapsulate=True): - '''Return html for the problem. Adds check, reset, save buttons - as necessary based on the problem config and state.''' - - try: - html = self.lcp.get_html() - except Exception, err: - log.exception(err) - - # TODO (vshnayder): another switch on DEBUG. - if self.system.DEBUG: - msg = ( - '[courseware.capa.capa_module] ' - 'Failed to generate HTML for problem %s' % - (self.location.url())) - msg += '

    Error:

    %s

    ' % str(err).replace('<', '<') - msg += '

    %s

    ' % traceback.format_exc().replace('<', '<') - html = msg - else: - # We're in non-debug mode, and possibly even in production. We want - # to avoid bricking of problem as much as possible - - # Presumably, student submission has corrupted LoncapaProblem HTML. - # First, pull down all student answers - student_answers = self.lcp.student_answers - answer_ids = student_answers.keys() - - # Some inputtypes, such as dynamath, have additional "hidden" state that - # is not exposed to the student. Keep those hidden - # TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id - hidden_state_keywords = ['dynamath'] - for answer_id in answer_ids: - for hidden_state_keyword in hidden_state_keywords: - if answer_id.find(hidden_state_keyword) >= 0: - student_answers.pop(answer_id) - - # Next, generate a fresh LoncapaProblem - self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), - state=None, # Tabula rasa - seed=self.seed, system=self.system) - - # Prepend a scary warning to the student - warning = '
    '\ - '

    Warning: The problem has been reset to its initial state!

    '\ - 'The problem\'s state was corrupted by an invalid submission. '\ - 'The submission consisted of:'\ - '
      ' - for student_answer in student_answers.values(): - if student_answer != '': - warning += '
    • ' + cgi.escape(student_answer) + '
    • ' - warning += '
    '\ - 'If this error persists, please contact the course staff.'\ - '
    ' - - html = warning - try: - html += self.lcp.get_html() - except Exception, err: # Couldn't do it. Give up - log.exception(err) - raise - - content = {'name': self.display_name, - 'html': html, - 'weight': self.descriptor.weight, - } - - # We using strings as truthy values, because the terminology of the - # check button is context-specific. - - # Put a "Check" button if unlimited attempts or still some left - if self.max_attempts is None or self.attempts < self.max_attempts-1: - check_button = "Check" - else: - # Will be final check so let user know that - check_button = "Final Check" - - reset_button = True - save_button = True - - # If we're after deadline, or user has exhausted attempts, - # question is read-only. - if self.closed(): - check_button = False - reset_button = False - save_button = False - - # User submitted a problem, and hasn't reset. We don't want - # more submissions. - if self.lcp.done and self.rerandomize == "always": - check_button = False - save_button = False - - # Only show the reset button if pressing it will show different values - if self.rerandomize not in ["always", "onreset"]: - reset_button = False - - # User hasn't submitted an answer yet -- we don't want resets - if not self.lcp.done: - reset_button = False - - # We may not need a "save" button if infinite number of attempts and - # non-randomized. The problem author can force it. It's a bit weird for - # randomization to control this; should perhaps be cleaned up. - if (self.force_save_button == "false") and (self.max_attempts is None and self.rerandomize != "always"): - save_button = False - - context = {'problem': content, - 'id': self.id, - 'check_button': check_button, - 'reset_button': reset_button, - 'save_button': save_button, - 'answer_available': self.answer_available(), - 'ajax_url': self.system.ajax_url, - 'attempts_used': self.attempts, - 'attempts_allowed': self.max_attempts, - 'progress': self.get_progress(), - } - - html = self.system.render_template('problem.html', context) - if encapsulate: - html = '
    '.format( - id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "
    " - - # cdodge: OK, we have to do two rounds of url reference subsitutions - # one which uses the 'asset library' that is served by the contentstore and the - # more global /static/ filesystem based static content. - # NOTE: rewrite_content_links is defined in XModule - # This is a bit unfortunate and I'm sure we'll try to considate this into - # a one step process. - html = rewrite_links(html, self.rewrite_content_links) - - # now do the substitutions which are filesystem based, e.g. '/static/' prefixes - return self.system.replace_urls(html, self.metadata['data_dir']) class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): """ - Module for putting self assessment questions into a course + Module for putting raw html in a course """ + mako_template = "widgets/html-edit.html" + module_class = HtmlModule + filename_extension = "xml" + template_dir_name = "html" + stores_state=True + has_score=True - stores_state = True - has_score = True - template_dir_name = 'selfassessment' + js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/edit.coffee')]} + js_module_name = "HTMLEditingDescriptor" - # Capa modules have some additional metadata: - # TODO (vshnayder): do problems have any other metadata? Do they - # actually use type and points? - metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points') - - # VS[compat] - # TODO (cpennington): Delete this method once all fall 2012 course are being - # edited in the cms + # VS[compat] TODO (cpennington): Delete this method once all fall 2012 course + # are being edited in the cms @classmethod def backcompat_paths(cls, path): - return [ - 'problems/' + path[8:], - path[8:], - ] + if path.endswith('.html.xml'): + path = path[:-9] + '.html' # backcompat--look for html instead of xml + if path.endswith('.html.html'): + path = path[:-5] # some people like to include .html in filenames.. + candidates = [] + while os.sep in path: + candidates.append(path) + _, _, path = path.partition(os.sep) - def __init__(self, *args, **kwargs): - super(CapaDescriptor, self).__init__(*args, **kwargs) + # also look for .html versions instead of .xml + nc = [] + for candidate in candidates: + if candidate.endswith('.xml'): + nc.append(candidate[:-4] + '.html') + return candidates + nc - weight_string = self.metadata.get('weight', None) - if weight_string: - self.weight = float(weight_string) + # NOTE: html descriptors are special. We do not want to parse and + # export them ourselves, because that can break things (e.g. lxml + # adds body tags when it exports, but they should just be html + # snippets that will be included in the middle of pages. + + @classmethod + def load_definition(cls, xml_object, system, location): + '''Load a descriptor from the specified xml_object: + + If there is a filename attribute, load it as a string, and + log a warning if it is not parseable by etree.HTMLParser. + + If there is not a filename attribute, the definition is the body + of the xml_object, without the root tag (do not want in the + middle of a page) + ''' + filename = xml_object.get('filename') + if filename is None: + definition_xml = copy.deepcopy(xml_object) + cls.clean_metadata_from_xml(definition_xml) + return {'data': stringify_children(definition_xml)} else: - self.weight = None + # html is special. cls.filename_extension is 'xml', but + # if 'filename' is in the definition, that means to load + # from .html + # 'filename' in html pointers is a relative path + # (not same as 'html/blah.html' when the pointer is in a directory itself) + pointer_path = "{category}/{url_path}".format(category='html', + url_path=name_to_pathname(location.name)) + base = path(pointer_path).dirname() + #log.debug("base = {0}, base.dirname={1}, filename={2}".format(base, base.dirname(), filename)) + filepath = "{base}/{name}.html".format(base=base, name=filename) + #log.debug("looking for html file for {0} at {1}".format(location, filepath)) + + + + # VS[compat] + # TODO (cpennington): If the file doesn't exist at the right path, + # give the class a chance to fix it up. The file will be written out + # again in the correct format. This should go away once the CMS is + # online and has imported all current (fall 2012) courses from xml + if not system.resources_fs.exists(filepath): + candidates = cls.backcompat_paths(filepath) + #log.debug("candidates = {0}".format(candidates)) + for candidate in candidates: + if system.resources_fs.exists(candidate): + filepath = candidate + break + + try: + with system.resources_fs.open(filepath) as file: + html = file.read() + # Log a warning if we can't parse the file, but don't error + if not check_html(html): + msg = "Couldn't parse html in {0}.".format(filepath) + log.warning(msg) + system.error_tracker("Warning: " + msg) + + definition = {'data': html} + + # TODO (ichuang): remove this after migration + # for Fall 2012 LMS migration: keep filename (and unmangled filename) + definition['filename'] = [ filepath, filename ] + + return definition + + except (ResourceNotFoundError) as err: + msg = 'Unable to load file contents at path {0}: {1} '.format( + filepath, err) + # add more info and re-raise + raise Exception(msg), None, sys.exc_info()[2] + + # TODO (vshnayder): make export put things in the right places. + + def definition_to_xml(self, resource_fs): + '''If the contents are valid xml, write them to filename.xml. Otherwise, + write just to filename.xml, and the html + string to filename.html. + ''' + try: + return etree.fromstring(self.definition['data']) + except etree.XMLSyntaxError: + pass + + # Not proper format. Write html to file, return an empty tag + pathname = name_to_pathname(self.url_name) + pathdir = path(pathname).dirname() + filepath = u'{category}/{pathname}.html'.format(category=self.category, + pathname=pathname) + + resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True) + with resource_fs.open(filepath, 'w') as file: + file.write(self.definition['data']) + + # write out the relative name + relname = path(pathname).basename() + + elt = etree.Element('html') + elt.set("filename", relname) + return elt From 9cb2fbdd2fad6a553342776a535d1bd74aea6197 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 31 Oct 2012 16:26:11 -0400 Subject: [PATCH 046/228] Remove newline from javascript source tags --- common/djangoapps/pipeline_mako/templates/static_content.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/djangoapps/pipeline_mako/templates/static_content.html b/common/djangoapps/pipeline_mako/templates/static_content.html index c153da22fe..302d4d7aa5 100644 --- a/common/djangoapps/pipeline_mako/templates/static_content.html +++ b/common/djangoapps/pipeline_mako/templates/static_content.html @@ -3,8 +3,7 @@ from staticfiles.storage import staticfiles_storage from pipeline_mako import compressed_css, compressed_js %> -<%def name='url(file)'> -<% +<%def name='url(file)'><% try: url = staticfiles_storage.url(file) except: From b74a484f75830e1ae92782f1f329ee8a25b63031 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 31 Oct 2012 16:39:04 -0400 Subject: [PATCH 047/228] working on self assessment --- common/lib/xmodule/xmodule/capa_module.py | 306 ++++++++-------- .../xmodule/xmodule/self_assessment_module.py | 335 ++++++++++++++++++ 2 files changed, 488 insertions(+), 153 deletions(-) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 151c726f66..31106a4aa8 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -29,10 +29,10 @@ TIMEDELTA_REGEX = re.compile(r'^((?P\d+?) day(?:s?))?(\s)?((?P\d+?) def only_one(lst, default="", process=lambda x: x): """ - If lst is empty, returns default - If lst has a single element, applies process to that element and returns it - Otherwise, raises an exeception - """ +If lst is empty, returns default +If lst has a single element, applies process to that element and returns it +Otherwise, raises an exeception +""" if len(lst) == 0: return default elif len(lst) == 1: @@ -43,14 +43,14 @@ def only_one(lst, default="", process=lambda x: x): def parse_timedelta(time_str): """ - time_str: A string with the following components: - day[s] (optional) - hour[s] (optional) - minute[s] (optional) - second[s] (optional) +time_str: A string with the following components: + day[s] (optional) + hour[s] (optional) + minute[s] (optional) + second[s] (optional) - Returns a datetime.timedelta parsed from the string - """ +Returns a datetime.timedelta parsed from the string +""" parts = TIMEDELTA_REGEX.match(time_str) if not parts: return @@ -71,15 +71,15 @@ class ComplexEncoder(json.JSONEncoder): class CapaModule(XModule): ''' - An XModule implementing LonCapa format problems, implemented by way of - capa.capa_problem.LoncapaProblem - ''' +An XModule implementing LonCapa format problems, implemented by way of +capa.capa_problem.LoncapaProblem +''' icon_class = 'problem' js = {'coffee': [resource_string(__name__, 'js/src/capa/display.coffee'), resource_string(__name__, 'js/src/collapsible.coffee'), resource_string(__name__, 'js/src/javascript_loader.coffee'), - ], + ], 'js': [resource_string(__name__, 'js/src/capa/imageinput.js'), resource_string(__name__, 'js/src/capa/schematic.js')]} @@ -89,7 +89,7 @@ class CapaModule(XModule): def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, descriptor, instance_state, - shared_state, **kwargs) + shared_state, **kwargs) self.attempts = 0 self.max_attempts = None @@ -100,7 +100,7 @@ class CapaModule(XModule): if display_due_date_string is not None: self.display_due_date = dateutil.parser.parse(display_due_date_string) #log.debug("Parsed " + display_due_date_string + - # " to " + str(self.display_due_date)) + # " to " + str(self.display_due_date)) else: self.display_due_date = None @@ -109,7 +109,7 @@ class CapaModule(XModule): self.grace_period = parse_timedelta(grace_period_string) self.close_date = self.display_due_date + self.grace_period #log.debug("Then parsed " + grace_period_string + - # " to closing date" + str(self.close_date)) + # " to closing date" + str(self.close_date)) else: self.grace_period = None self.close_date = self.display_due_date @@ -137,9 +137,9 @@ class CapaModule(XModule): elif self.rerandomize == "per_student" and hasattr(self.system, 'id'): # TODO: This line is badly broken: # (1) We're passing student ID to xmodule. - # (2) There aren't bins of students. -- we only want 10 or 20 randomizations, and want to assign students - # to these bins, and may not want cohorts. So e.g. hash(your-id, problem_id) % num_bins. - # - analytics really needs small number of bins. + # (2) There aren't bins of students. -- we only want 10 or 20 randomizations, and want to assign students + # to these bins, and may not want cohorts. So e.g. hash(your-id, problem_id) % num_bins. + # - analytics really needs small number of bins. self.seed = system.id else: self.seed = None @@ -148,7 +148,7 @@ class CapaModule(XModule): # TODO (vshnayder): move as much as possible of this work and error # checking to descriptor load time self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), - instance_state, seed=self.seed, system=self.system) + instance_state, seed=self.seed, system=self.system) except Exception as err: msg = 'cannot create LoncapaProblem {loc}: {err}'.format( loc=self.location.url(), err=err) @@ -175,9 +175,9 @@ class CapaModule(XModule): @property def rerandomize(self): """ - Property accessor that returns self.metadata['rerandomize'] in a - canonical form - """ +Property accessor that returns self.metadata['rerandomize'] in a +canonical form +""" rerandomize = self.metadata.get('rerandomize', 'always') if rerandomize in ("", "always", "true"): return "always" @@ -203,7 +203,7 @@ class CapaModule(XModule): def get_progress(self): ''' For now, just return score / max_score - ''' +''' d = self.get_score() score = d['score'] total = d['total'] @@ -220,11 +220,11 @@ class CapaModule(XModule): 'element_id': self.location.html_id(), 'id': self.id, 'ajax_url': self.system.ajax_url, - }) + }) def get_problem_html(self, encapsulate=True): - '''Return html for the problem. Adds check, reset, save buttons - as necessary based on the problem config and state.''' + '''Return html for the problem. Adds check, reset, save buttons +as necessary based on the problem config and state.''' try: html = self.lcp.get_html() @@ -242,15 +242,15 @@ class CapaModule(XModule): html = msg else: # We're in non-debug mode, and possibly even in production. We want - # to avoid bricking of problem as much as possible + # to avoid bricking of problem as much as possible # Presumably, student submission has corrupted LoncapaProblem HTML. - # First, pull down all student answers + # First, pull down all student answers student_answers = self.lcp.student_answers answer_ids = student_answers.keys() # Some inputtypes, such as dynamath, have additional "hidden" state that - # is not exposed to the student. Keep those hidden + # is not exposed to the student. Keep those hidden # TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id hidden_state_keywords = ['dynamath'] for answer_id in answer_ids: @@ -258,17 +258,17 @@ class CapaModule(XModule): if answer_id.find(hidden_state_keyword) >= 0: student_answers.pop(answer_id) - # Next, generate a fresh LoncapaProblem + # Next, generate a fresh LoncapaProblem self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), - state=None, # Tabula rasa - seed=self.seed, system=self.system) + state=None, # Tabula rasa + seed=self.seed, system=self.system) # Prepend a scary warning to the student - warning = '
    '\ - '

    Warning: The problem has been reset to its initial state!

    '\ - 'The problem\'s state was corrupted by an invalid submission. ' \ - 'The submission consisted of:'\ - '
      ' + warning = '
      '\ + '

      Warning: The problem has been reset to its initial state!

      '\ + 'The problem\'s state was corrupted by an invalid submission. '\ + 'The submission consisted of:'\ + '
        ' for student_answer in student_answers.values(): if student_answer != '': warning += '
      • ' + cgi.escape(student_answer) + '
      • ' @@ -292,11 +292,11 @@ class CapaModule(XModule): # check button is context-specific. # Put a "Check" button if unlimited attempts or still some left - if self.max_attempts is None or self.attempts < self.max_attempts-1: + if self.max_attempts is None or self.attempts < self.max_attempts-1: check_button = "Check" else: # Will be final check so let user know that - check_button = "Final Check" + check_button = "Final Check" reset_button = True save_button = True @@ -358,14 +358,14 @@ class CapaModule(XModule): def handle_ajax(self, dispatch, get): ''' - This is called by courseware.module_render, to handle an AJAX call. - "get" is request.POST. +This is called by courseware.module_render, to handle an AJAX call. +"get" is request.POST. - Returns a json dictionary: - { 'progress_changed' : True/False, - 'progress' : 'none'/'in_progress'/'done', - } - ''' +Returns a json dictionary: +{ 'progress_changed' : True/False, +'progress' : 'none'/'in_progress'/'done', + } +''' handlers = { 'problem_get': self.get_problem, 'problem_check': self.check_problem, @@ -398,7 +398,7 @@ class CapaModule(XModule): def answer_available(self): ''' Is the user allowed to see an answer? - ''' +''' if self.show_answer == '': return False @@ -425,26 +425,26 @@ class CapaModule(XModule): def update_score(self, get): """ - Delivers grading response (e.g. from asynchronous code checking) to - the capa problem, so its score can be updated +Delivers grading response (e.g. from asynchronous code checking) to +the capa problem, so its score can be updated - 'get' must have a field 'response' which is a string that contains the - grader's response +'get' must have a field 'response' which is a string that contains the +grader's response - No ajax return is needed. Return empty dict. - """ +No ajax return is needed. Return empty dict. +""" queuekey = get['queuekey'] score_msg = get['xqueue_body'] self.lcp.update_score(score_msg, queuekey) - return dict() # No AJAX return is needed + return dict() # No AJAX return is needed def get_answer(self, get): ''' - For the "show answer" button. +For the "show answer" button. - Returns the answers: {'answers' : answers} - ''' +Returns the answers: {'answers' : answers} +''' event_info = dict() event_info['problem_id'] = self.location.url() self.system.track_function('show_answer', event_info) @@ -453,8 +453,8 @@ class CapaModule(XModule): else: answers = self.lcp.get_question_answers() - # answers (eg ) may have embedded images - # but be careful, some problems are using non-string answer dicts + # answers (eg ) may have embedded images + # but be careful, some problems are using non-string answer dicts new_answers = dict() for answer_id in answers: try: @@ -469,18 +469,18 @@ class CapaModule(XModule): # Figure out if we should move these to capa_problem? def get_problem(self, get): ''' Return results of get_problem_html, as a simple dict for json-ing. - { 'html': } +{ 'html': } - Used if we want to reconfirm we have the right thing e.g. after - several AJAX calls. - ''' +Used if we want to reconfirm we have the right thing e.g. after +several AJAX calls. +''' return {'html': self.get_problem_html(encapsulate=False)} @staticmethod def make_dict_of_responses(get): '''Make dictionary of student responses (aka "answers") - get is POST dictionary. - ''' +get is POST dictionary. +''' answers = dict() for key in get: # e.g. input_resistor_1 ==> resistor_1 @@ -500,11 +500,11 @@ class CapaModule(XModule): def check_problem(self, get): ''' Checks whether answers to a problem are correct, and - returns a map of correct/incorrect answers: +returns a map of correct/incorrect answers: - {'success' : bool, - 'contents' : html} - ''' +{'success' : bool, +'contents' : html} +''' event_info = dict() event_info['state'] = self.lcp.get_state() event_info['problem_id'] = self.location.url() @@ -527,11 +527,11 @@ class CapaModule(XModule): # Problem queued. Students must wait a specified waittime before they are allowed to submit if self.lcp.is_queued(): current_time = datetime.datetime.now() - prev_submit_time = self.lcp.get_recentmost_queuetime() + prev_submit_time = self.lcp.get_recentmost_queuetime() waittime_between_requests = self.system.xqueue['waittime'] if (current_time-prev_submit_time).total_seconds() < waittime_between_requests: msg = 'You must wait at least %d seconds between submissions' % waittime_between_requests - return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback + return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback try: old_state = self.lcp.get_state() @@ -540,13 +540,13 @@ class CapaModule(XModule): except StudentInputError as inst: # TODO (vshnayder): why is this line here? #self.lcp = LoncapaProblem(self.definition['data'], - # id=lcp_id, state=old_state, system=self.system) + # id=lcp_id, state=old_state, system=self.system) log.exception("StudentInputError in capa_module:problem_check") return {'success': inst.message} except Exception, err: # TODO: why is this line here? #self.lcp = LoncapaProblem(self.definition['data'], - # id=lcp_id, state=old_state, system=self.system) + # id=lcp_id, state=old_state, system=self.system) if self.system.DEBUG: msg = "Error checking problem: " + str(err) msg += '\nTraceback:\n' + traceback.format_exc() @@ -564,99 +564,99 @@ class CapaModule(XModule): success = 'incorrect' # NOTE: We are logging both full grading and queued-grading submissions. In the latter, - # 'success' will always be incorrect + # 'success' will always be incorrect event_info['correct_map'] = correct_map.get_dict() event_info['success'] = success - event_info['attempts'] = self.attempts - self.system.track_function('save_problem_check', event_info) +event_info['attempts'] = self.attempts +self.system.track_function('save_problem_check', event_info) - if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback - self.system.psychometrics_handler(self.get_instance_state()) +if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback + self.system.psychometrics_handler(self.get_instance_state()) - # render problem into HTML - html = self.get_problem_html(encapsulate=False) +# render problem into HTML +html = self.get_problem_html(encapsulate=False) - return {'success': success, - 'contents': html, - } +return {'success': success, + 'contents': html, + } - def save_problem(self, get): - ''' - Save the passed in answers. - Returns a dict { 'success' : bool, ['error' : error-msg]}, - with the error key only present if success is False. - ''' - event_info = dict() - event_info['state'] = self.lcp.get_state() - event_info['problem_id'] = self.location.url() +def save_problem(self, get): + ''' + Save the passed in answers. + Returns a dict { 'success' : bool, ['error' : error-msg]}, + with the error key only present if success is False. + ''' + event_info = dict() + event_info['state'] = self.lcp.get_state() + event_info['problem_id'] = self.location.url() - answers = self.make_dict_of_responses(get) - event_info['answers'] = answers + answers = self.make_dict_of_responses(get) + event_info['answers'] = answers - # Too late. Cannot submit - if self.closed(): - event_info['failure'] = 'closed' - self.system.track_function('save_problem_fail', event_info) - return {'success': False, - 'error': "Problem is closed"} - - # Problem submitted. Student should reset before saving - # again. - if self.lcp.done and self.rerandomize == "always": - event_info['failure'] = 'done' - self.system.track_function('save_problem_fail', event_info) - return {'success': False, - 'error': "Problem needs to be reset prior to save."} - - self.lcp.student_answers = answers - - # TODO: should this be save_problem_fail? Looks like success to me... + # Too late. Cannot submit + if self.closed(): + event_info['failure'] = 'closed' self.system.track_function('save_problem_fail', event_info) - return {'success': True} + return {'success': False, + 'error': "Problem is closed"} - def reset_problem(self, get): - ''' Changes problem state to unfinished -- removes student answers, - and causes problem to rerender itself. + # Problem submitted. Student should reset before saving + # again. + if self.lcp.done and self.rerandomize == "always": + event_info['failure'] = 'done' + self.system.track_function('save_problem_fail', event_info) + return {'success': False, + 'error': "Problem needs to be reset prior to save."} - Returns problem html as { 'html' : html-string }. - ''' - event_info = dict() - event_info['old_state'] = self.lcp.get_state() - event_info['problem_id'] = self.location.url() + self.lcp.student_answers = answers - if self.closed(): - event_info['failure'] = 'closed' - self.system.track_function('reset_problem_fail', event_info) - return {'success': False, - 'error': "Problem is closed"} + # TODO: should this be save_problem_fail? Looks like success to me... + self.system.track_function('save_problem_fail', event_info) + return {'success': True} - if not self.lcp.done: - event_info['failure'] = 'not_done' - self.system.track_function('reset_problem_fail', event_info) - return {'success': False, - 'error': "Refresh the page and make an attempt before resetting."} +def reset_problem(self, get): + ''' Changes problem state to unfinished -- removes student answers, + and causes problem to rerender itself. - self.lcp.do_reset() - if self.rerandomize in ["always", "onreset"]: - # reset random number generator seed (note the self.lcp.get_state() - # in next line) - self.lcp.seed = None + Returns problem html as { 'html' : html-string }. + ''' + event_info = dict() + event_info['old_state'] = self.lcp.get_state() + event_info['problem_id'] = self.location.url() - self.lcp = LoncapaProblem(self.definition['data'], - self.location.html_id(), self.lcp.get_state(), - system=self.system) + if self.closed(): + event_info['failure'] = 'closed' + self.system.track_function('reset_problem_fail', event_info) + return {'success': False, + 'error': "Problem is closed"} - event_info['new_state'] = self.lcp.get_state() - self.system.track_function('reset_problem', event_info) + if not self.lcp.done: + event_info['failure'] = 'not_done' + self.system.track_function('reset_problem_fail', event_info) + return {'success': False, + 'error': "Refresh the page and make an attempt before resetting."} - return {'html': self.get_problem_html(encapsulate=False)} + self.lcp.do_reset() + if self.rerandomize in ["always", "onreset"]: + # reset random number generator seed (note the self.lcp.get_state() + # in next line) + self.lcp.seed = None + + self.lcp = LoncapaProblem(self.definition['data'], + self.location.html_id(), self.lcp.get_state(), + system=self.system) + + event_info['new_state'] = self.lcp.get_state() + self.system.track_function('reset_problem', event_info) + + return {'html': self.get_problem_html(encapsulate=False)} class CapaDescriptor(RawDescriptor): """ - Module implementing problems in the LON-CAPA format, - as implemented by capa.capa_problem - """ +Module implementing problems in the LON-CAPA format, +as implemented by capa.capa_problem +""" module_class = CapaModule @@ -665,7 +665,7 @@ class CapaDescriptor(RawDescriptor): template_dir_name = 'problem' # Capa modules have some additional metadata: - # TODO (vshnayder): do problems have any other metadata? Do they + # TODO (vshnayder): do problems have any other metadata? Do they # actually use type and points? metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points') @@ -677,13 +677,13 @@ class CapaDescriptor(RawDescriptor): return [ 'problems/' + path[8:], path[8:], - ] - + ] + def __init__(self, *args, **kwargs): super(CapaDescriptor, self).__init__(*args, **kwargs) - + weight_string = self.metadata.get('weight', None) if weight_string: self.weight = float(weight_string) else: - self.weight = None + self.weight = None \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index df69213281..4473b7d430 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -108,6 +108,341 @@ class SelfAssessmentModule(XModule): 'ajax_url': self.system.ajax_url, }) + def get_problem_html(self, encapsulate=True): + '''Return html for the problem. Adds check, reset, save buttons + as necessary based on the problem config and state.''' + + try: + html = self.lcp.get_html() + except Exception, err: + log.exception(err) + + # TODO (vshnayder): another switch on DEBUG. + if self.system.DEBUG: + msg = ( + '[courseware.capa.capa_module] ' + 'Failed to generate HTML for problem %s' % + (self.location.url())) + msg += '

        Error:

        %s

        ' % str(err).replace('<', '<') + msg += '

        %s

        ' % traceback.format_exc().replace('<', '<') + html = msg + else: + # We're in non-debug mode, and possibly even in production. We want + # to avoid bricking of problem as much as possible + + # Presumably, student submission has corrupted LoncapaProblem HTML. + # First, pull down all student answers + student_answers = self.lcp.student_answers + answer_ids = student_answers.keys() + + # Some inputtypes, such as dynamath, have additional "hidden" state that + # is not exposed to the student. Keep those hidden + # TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id + hidden_state_keywords = ['dynamath'] + for answer_id in answer_ids: + for hidden_state_keyword in hidden_state_keywords: + if answer_id.find(hidden_state_keyword) >= 0: + student_answers.pop(answer_id) + + # Next, generate a fresh LoncapaProblem + self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), + state=None, # Tabula rasa + seed=self.seed, system=self.system) + + # Prepend a scary warning to the student + warning = '
        '\ + '

        Warning: The problem has been reset to its initial state!

        '\ + 'The problem\'s state was corrupted by an invalid submission. '\ + 'The submission consisted of:'\ + '
          ' + for student_answer in student_answers.values(): + if student_answer != '': + warning += '
        • ' + cgi.escape(student_answer) + '
        • ' + warning += '
        '\ + 'If this error persists, please contact the course staff.'\ + '
        ' + + html = warning + try: + html += self.lcp.get_html() + except Exception, err: # Couldn't do it. Give up + log.exception(err) + raise + + content = {'name': self.display_name, + 'html': html, + 'weight': self.descriptor.weight, + } + + # We using strings as truthy values, because the terminology of the + # check button is context-specific. + + # Put a "Check" button if unlimited attempts or still some left + if self.max_attempts is None or self.attempts < self.max_attempts-1: + check_button = "Check" + else: + # Will be final check so let user know that + check_button = "Final Check" + + reset_button = True + save_button = True + + # If we're after deadline, or user has exhausted attempts, + # question is read-only. + if self.closed(): + check_button = False + reset_button = False + save_button = False + + # User submitted a problem, and hasn't reset. We don't want + # more submissions. + if self.lcp.done and self.rerandomize == "always": + check_button = False + save_button = False + + # Only show the reset button if pressing it will show different values + if self.rerandomize not in ["always", "onreset"]: + reset_button = False + + # User hasn't submitted an answer yet -- we don't want resets + if not self.lcp.done: + reset_button = False + + # We may not need a "save" button if infinite number of attempts and + # non-randomized. The problem author can force it. It's a bit weird for + # randomization to control this; should perhaps be cleaned up. + if (self.force_save_button == "false") and (self.max_attempts is None and self.rerandomize != "always"): + save_button = False + + context = {'problem': content, + 'id': self.id, + 'check_button': check_button, + 'reset_button': reset_button, + 'save_button': save_button, + 'answer_available': self.answer_available(), + 'ajax_url': self.system.ajax_url, + 'attempts_used': self.attempts, + 'attempts_allowed': self.max_attempts, + 'progress': self.get_progress(), + } + + html = self.system.render_template('problem.html', context) + if encapsulate: + html = '
        '.format( + id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "
        " + + # cdodge: OK, we have to do two rounds of url reference subsitutions + # one which uses the 'asset library' that is served by the contentstore and the + # more global /static/ filesystem based static content. + # NOTE: rewrite_content_links is defined in XModule + # This is a bit unfortunate and I'm sure we'll try to considate this into + # a one step process. + html = rewrite_links(html, self.rewrite_content_links) + + # now do the substitutions which are filesystem based, e.g. '/static/' prefixes + return self.system.replace_urls(html, self.metadata['data_dir']) + + def handle_ajax(self, dispatch, get): + ''' + This is called by courseware.module_render, to handle an AJAX call. + "get" is request.POST. + + Returns a json dictionary: + { 'progress_changed' : True/False, + 'progress' : 'none'/'in_progress'/'done', + } + ''' + handlers = { + 'problem_get': self.get_problem, + 'problem_check': self.check_problem, + 'problem_reset': self.reset_problem, + 'problem_save': self.save_problem, + 'problem_show': self.get_answer, + 'score_update': self.update_score, + } + + if dispatch not in handlers: + return 'Error' + + before = self.get_progress() + d = handlers[dispatch](get) + after = self.get_progress() + d.update({ + 'progress_changed': after != before, + 'progress_status': Progress.to_js_status_str(after), + }) + return json.dumps(d, cls=ComplexEncoder) + + def closed(self): + ''' Is the student still allowed to submit answers? ''' + if self.attempts == self.max_attempts: + return True + if self.close_date is not None and datetime.datetime.utcnow() > self.close_date: + return True + + return False + + def answer_available(self): + ''' Is the user allowed to see an answer? + ''' + if self.show_answer == '': + return False + + if self.show_answer == "never": + return False + + # Admins can see the answer, unless the problem explicitly prevents it + if self.system.user_is_staff: + return True + + if self.show_answer == 'attempted': + return self.attempts > 0 + + if self.show_answer == 'answered': + return self.lcp.done + + if self.show_answer == 'closed': + return self.closed() + + if self.show_answer == 'always': + return True + + return False + + # Figure out if we should move these to capa_problem? + def get_problem(self, get): + ''' Return results of get_problem_html, as a simple dict for json-ing. + { 'html': } + + Used if we want to reconfirm we have the right thing e.g. after + several AJAX calls. + ''' + return {'html': self.get_problem_html(encapsulate=False)} + + @staticmethod + def make_dict_of_responses(get): + '''Make dictionary of student responses (aka "answers") + get is POST dictionary. + ''' + answers = dict() + for key in get: + # e.g. input_resistor_1 ==> resistor_1 + _, _, name = key.partition('_') + + # This allows for answers which require more than one value for + # the same form input (e.g. checkbox inputs). The convention is that + # if the name ends with '[]' (which looks like an array), then the + # answer will be an array. + if not name.endswith('[]'): + answers[name] = get[key] + else: + name = name[:-2] + answers[name] = get.getlist(key) + + return answers + + def check_problem(self, get): + ''' Checks whether answers to a problem are correct, and + returns a map of correct/incorrect answers: + + {'success' : bool, + 'contents' : html} + ''' + event_info = dict() + event_info['state'] = self.lcp.get_state() + event_info['problem_id'] = self.location.url() + + answers = self.make_dict_of_responses(get) + event_info['answers'] = convert_files_to_filenames(answers) + + parsed_answer=False + if(answer[answers.keys()[0]]=="True"): + parsed_answer=True + + # Too late. Cannot submit + if self.closed(): + event_info['failure'] = 'closed' + self.system.track_function('save_problem_check_fail', event_info) + raise NotFoundError('Problem is closed') + + try: + old_state = self.lcp.get_state() + lcp_id = self.lcp.problem_id + correct_map = self.lcp.grade_answers(answers) + correct_map.set(correctness=parsed_answer) + except StudentInputError as inst: + # TODO (vshnayder): why is this line here? + #self.lcp = LoncapaProblem(self.definition['data'], + # id=lcp_id, state=old_state, system=self.system) + log.exception("StudentInputError in capa_module:problem_check") + return {'success': inst.message} + except Exception, err: + # TODO: why is this line here? + #self.lcp = LoncapaProblem(self.definition['data'], + # id=lcp_id, state=old_state, system=self.system) + if self.system.DEBUG: + msg = "Error checking problem: " + str(err) + msg += '\nTraceback:\n' + traceback.format_exc() + return {'success': msg} + log.exception("Error in capa_module problem checking") + raise Exception("error in capa_module") + + self.attempts = self.attempts + 1 + self.lcp.done = True + + # success = correct if ALL questions in this problem are correct + success = 'correct' + for answer_id in correct_map: + if not correct_map.is_correct(answer_id): + success = 'incorrect' + + # NOTE: We are logging both full grading and queued-grading submissions. In the latter, + # 'success' will always be incorrect + event_info['correct_map'] = correct_map.get_dict() + event_info['success'] = success + event_info['attempts'] = self.attempts + self.system.track_function('save_problem_check', event_info) + + # render problem into HTML + html = self.get_problem_html(encapsulate=False) + + return {'success': success, + 'contents': html, + } + + def save_problem(self, get): + ''' + Save the passed in answers. + Returns a dict { 'success' : bool, ['error' : error-msg]}, + with the error key only present if success is False. + ''' + event_info = dict() + event_info['state'] = self.lcp.get_state() + event_info['problem_id'] = self.location.url() + + answers = self.make_dict_of_responses(get) + event_info['answers'] = answers + + # Too late. Cannot submit + if self.closed(): + event_info['failure'] = 'closed' + self.system.track_function('save_problem_fail', event_info) + return {'success': False, + 'error': "Problem is closed"} + + # Problem submitted. Student should reset before saving + # again. + if self.lcp.done and self.rerandomize == "always": + event_info['failure'] = 'done' + self.system.track_function('save_problem_fail', event_info) + return {'success': False, + 'error': "Problem needs to be reset prior to save."} + + self.lcp.student_answers = answers + + # TODO: should this be save_problem_fail? Looks like success to me... + self.system.track_function('save_problem_fail', event_info) + return {'success': True} class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): From 32a8ed7be32c1451a3fc5b591f5ee86585162d96 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 31 Oct 2012 16:54:00 -0400 Subject: [PATCH 048/228] adding in self assessment grader --- common/lib/xmodule/xmodule/capa_module.py | 116 ++++++------ .../js/src/selfassessment/display.coffee | 24 --- .../xmodule/xmodule/self_assessment_module.py | 168 ++++-------------- 3 files changed, 91 insertions(+), 217 deletions(-) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 31106a4aa8..76158093b6 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -29,10 +29,10 @@ TIMEDELTA_REGEX = re.compile(r'^((?P\d+?) day(?:s?))?(\s)?((?P\d+?) def only_one(lst, default="", process=lambda x: x): """ -If lst is empty, returns default -If lst has a single element, applies process to that element and returns it -Otherwise, raises an exeception -""" + If lst is empty, returns default + If lst has a single element, applies process to that element and returns it + Otherwise, raises an exeception + """ if len(lst) == 0: return default elif len(lst) == 1: @@ -43,14 +43,14 @@ Otherwise, raises an exeception def parse_timedelta(time_str): """ -time_str: A string with the following components: - day[s] (optional) - hour[s] (optional) - minute[s] (optional) - second[s] (optional) + time_str: A string with the following components: + day[s] (optional) + hour[s] (optional) + minute[s] (optional) + second[s] (optional) -Returns a datetime.timedelta parsed from the string -""" + Returns a datetime.timedelta parsed from the string + """ parts = TIMEDELTA_REGEX.match(time_str) if not parts: return @@ -71,9 +71,9 @@ class ComplexEncoder(json.JSONEncoder): class CapaModule(XModule): ''' -An XModule implementing LonCapa format problems, implemented by way of -capa.capa_problem.LoncapaProblem -''' + An XModule implementing LonCapa format problems, implemented by way of + capa.capa_problem.LoncapaProblem + ''' icon_class = 'problem' js = {'coffee': [resource_string(__name__, 'js/src/capa/display.coffee'), @@ -175,9 +175,9 @@ capa.capa_problem.LoncapaProblem @property def rerandomize(self): """ -Property accessor that returns self.metadata['rerandomize'] in a -canonical form -""" + Property accessor that returns self.metadata['rerandomize'] in a + canonical form + """ rerandomize = self.metadata.get('rerandomize', 'always') if rerandomize in ("", "always", "true"): return "always" @@ -203,7 +203,7 @@ canonical form def get_progress(self): ''' For now, just return score / max_score -''' + ''' d = self.get_score() score = d['score'] total = d['total'] @@ -224,7 +224,7 @@ canonical form def get_problem_html(self, encapsulate=True): '''Return html for the problem. Adds check, reset, save buttons -as necessary based on the problem config and state.''' + as necessary based on the problem config and state.''' try: html = self.lcp.get_html() @@ -358,14 +358,14 @@ as necessary based on the problem config and state.''' def handle_ajax(self, dispatch, get): ''' -This is called by courseware.module_render, to handle an AJAX call. -"get" is request.POST. + This is called by courseware.module_render, to handle an AJAX call. + "get" is request.POST. -Returns a json dictionary: -{ 'progress_changed' : True/False, -'progress' : 'none'/'in_progress'/'done', - } -''' + Returns a json dictionary: + { 'progress_changed' : True/False, + 'progress' : 'none'/'in_progress'/'done', + } + ''' handlers = { 'problem_get': self.get_problem, 'problem_check': self.check_problem, @@ -398,7 +398,7 @@ Returns a json dictionary: def answer_available(self): ''' Is the user allowed to see an answer? -''' + ''' if self.show_answer == '': return False @@ -425,14 +425,14 @@ Returns a json dictionary: def update_score(self, get): """ -Delivers grading response (e.g. from asynchronous code checking) to -the capa problem, so its score can be updated + Delivers grading response (e.g. from asynchronous code checking) to + the capa problem, so its score can be updated -'get' must have a field 'response' which is a string that contains the -grader's response + 'get' must have a field 'response' which is a string that contains the + grader's response -No ajax return is needed. Return empty dict. -""" + No ajax return is needed. Return empty dict. + """ queuekey = get['queuekey'] score_msg = get['xqueue_body'] self.lcp.update_score(score_msg, queuekey) @@ -441,10 +441,10 @@ No ajax return is needed. Return empty dict. def get_answer(self, get): ''' -For the "show answer" button. + For the "show answer" button. -Returns the answers: {'answers' : answers} -''' + Returns the answers: {'answers' : answers} + ''' event_info = dict() event_info['problem_id'] = self.location.url() self.system.track_function('show_answer', event_info) @@ -469,18 +469,18 @@ Returns the answers: {'answers' : answers} # Figure out if we should move these to capa_problem? def get_problem(self, get): ''' Return results of get_problem_html, as a simple dict for json-ing. -{ 'html': } + { 'html': } -Used if we want to reconfirm we have the right thing e.g. after -several AJAX calls. -''' + Used if we want to reconfirm we have the right thing e.g. after + several AJAX calls. + ''' return {'html': self.get_problem_html(encapsulate=False)} @staticmethod def make_dict_of_responses(get): '''Make dictionary of student responses (aka "answers") -get is POST dictionary. -''' + get is POST dictionary. + ''' answers = dict() for key in get: # e.g. input_resistor_1 ==> resistor_1 @@ -500,11 +500,11 @@ get is POST dictionary. def check_problem(self, get): ''' Checks whether answers to a problem are correct, and -returns a map of correct/incorrect answers: + returns a map of correct/incorrect answers: -{'success' : bool, -'contents' : html} -''' + {'success' : bool, + 'contents' : html} + ''' event_info = dict() event_info['state'] = self.lcp.get_state() event_info['problem_id'] = self.location.url() @@ -567,18 +567,18 @@ returns a map of correct/incorrect answers: # 'success' will always be incorrect event_info['correct_map'] = correct_map.get_dict() event_info['success'] = success -event_info['attempts'] = self.attempts -self.system.track_function('save_problem_check', event_info) + event_info['attempts'] = self.attempts + self.system.track_function('save_problem_check', event_info) -if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback - self.system.psychometrics_handler(self.get_instance_state()) + if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback + self.system.psychometrics_handler(self.get_instance_state()) -# render problem into HTML -html = self.get_problem_html(encapsulate=False) + # render problem into HTML + html = self.get_problem_html(encapsulate=False) -return {'success': success, - 'contents': html, - } + return {'success': success, + 'contents': html, + } def save_problem(self, get): ''' @@ -654,9 +654,9 @@ def reset_problem(self, get): class CapaDescriptor(RawDescriptor): """ -Module implementing problems in the LON-CAPA format, -as implemented by capa.capa_problem -""" + Module implementing problems in the LON-CAPA format, + as implemented by capa.capa_problem + """ module_class = CapaModule diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index da91c44af7..5e16182553 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -125,30 +125,6 @@ class @Problem @gentle_alert saveMessage @updateProgress response - refreshMath: (event, element) => - element = event.target unless element - elid = element.id.replace(/^input_/,'') - target = "display_" + elid - - # MathJax preprocessor is loaded by 'setupInputTypes' - preprocessor_tag = "inputtype_" + elid - mathjax_preprocessor = @inputtypeDisplays[preprocessor_tag] - - if jax = MathJax.Hub.getAllJax(target)[0] - eqn = $(element).val() - if mathjax_preprocessor - eqn = mathjax_preprocessor(eqn) - MathJax.Hub.Queue(['Text', jax, eqn], [@updateMathML, jax, element]) - - return # Explicit return for CoffeeScript - - updateMathML: (jax, element) => - try - $("##{element.id}_dynamath").val(jax.root.toMathML '') - catch exception - throw exception unless exception.restart - MathJax.Callback.After [@refreshMath, jax], exception.restart - refreshAnswers: => @$('input.schematic').each (index, element) -> element.schematic.update_value() diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 4473b7d430..b6e8a667b4 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -57,9 +57,10 @@ class SelfAssessmentModule(XModule): dom2 = etree.fromstring(definition['data']) self.attempts = 0 - self.max_attempts = None + self.max_attempts = 1 self.max_attempts = self.metadata.get('attempts', None) + if self.max_attempts is not None: self.max_attempts = int(self.max_attempts) @@ -70,7 +71,7 @@ class SelfAssessmentModule(XModule): self.name = only_one(dom2.xpath('/problem/@name')) - self.rubric=etree.tostring(only_one(dom2.xpath("/rubric"))) + self.rubric=etree.tostring(only_one(dom2.xpath("/rubric/html"))) self.problem=etree.tostring(only_one(dom2.xpath("/problem"))) self.lcp = LoncapaProblem(self.problem, self.location.html_id(), @@ -255,10 +256,7 @@ class SelfAssessmentModule(XModule): handlers = { 'problem_get': self.get_problem, 'problem_check': self.check_problem, - 'problem_reset': self.reset_problem, 'problem_save': self.save_problem, - 'problem_show': self.get_answer, - 'score_update': self.update_score, } if dispatch not in handlers: @@ -274,7 +272,7 @@ class SelfAssessmentModule(XModule): return json.dumps(d, cls=ComplexEncoder) def closed(self): - ''' Is the student still allowed to submit answers? ''' + ''' Is the student still allowed to submit answers? ''' if self.attempts == self.max_attempts: return True if self.close_date is not None and datetime.datetime.utcnow() > self.close_date: @@ -319,9 +317,10 @@ class SelfAssessmentModule(XModule): ''' return {'html': self.get_problem_html(encapsulate=False)} - @staticmethod + @staticmethod def make_dict_of_responses(get): - '''Make dictionary of student responses (aka "answers") + ''' + Make dictionary of student responses (aka "answers") get is POST dictionary. ''' answers = dict() @@ -355,9 +354,9 @@ class SelfAssessmentModule(XModule): answers = self.make_dict_of_responses(get) event_info['answers'] = convert_files_to_filenames(answers) - parsed_answer=False - if(answer[answers.keys()[0]]=="True"): - parsed_answer=True + parsed_answer="incorrect" + if(answer[answers.keys()[1]]=="Correct"): + parsed_answer="correct" # Too late. Cannot submit if self.closed(): @@ -445,137 +444,36 @@ class SelfAssessmentModule(XModule): return {'success': True} -class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): +class SelfAssessmentDescriptor(RawDescriptor): """ Module for putting raw html in a course """ - mako_template = "widgets/html-edit.html" - module_class = HtmlModule - filename_extension = "xml" - template_dir_name = "html" - stores_state=True - has_score=True + module_class = SelfAssessmentModule - js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/edit.coffee')]} - js_module_name = "HTMLEditingDescriptor" + stores_state = True + has_score = True + template_dir_name = 'selfassessment' - # VS[compat] TODO (cpennington): Delete this method once all fall 2012 course - # are being edited in the cms + # Capa modules have some additional metadata: + # TODO (vshnayder): do problems have any other metadata? Do they + # actually use type and points? + metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points') + + # VS[compat] + # TODO (cpennington): Delete this method once all fall 2012 course are being + # edited in the cms @classmethod def backcompat_paths(cls, path): - if path.endswith('.html.xml'): - path = path[:-9] + '.html' # backcompat--look for html instead of xml - if path.endswith('.html.html'): - path = path[:-5] # some people like to include .html in filenames.. - candidates = [] - while os.sep in path: - candidates.append(path) - _, _, path = path.partition(os.sep) + return [ + 'problems/' + path[8:], + path[8:], + ] - # also look for .html versions instead of .xml - nc = [] - for candidate in candidates: - if candidate.endswith('.xml'): - nc.append(candidate[:-4] + '.html') - return candidates + nc + def __init__(self, *args, **kwargs): + super(CapaDescriptor, self).__init__(*args, **kwargs) - # NOTE: html descriptors are special. We do not want to parse and - # export them ourselves, because that can break things (e.g. lxml - # adds body tags when it exports, but they should just be html - # snippets that will be included in the middle of pages. - - @classmethod - def load_definition(cls, xml_object, system, location): - '''Load a descriptor from the specified xml_object: - - If there is a filename attribute, load it as a string, and - log a warning if it is not parseable by etree.HTMLParser. - - If there is not a filename attribute, the definition is the body - of the xml_object, without the root tag (do not want in the - middle of a page) - ''' - filename = xml_object.get('filename') - if filename is None: - definition_xml = copy.deepcopy(xml_object) - cls.clean_metadata_from_xml(definition_xml) - return {'data': stringify_children(definition_xml)} + weight_string = self.metadata.get('weight', None) + if weight_string: + self.weight = float(weight_string) else: - # html is special. cls.filename_extension is 'xml', but - # if 'filename' is in the definition, that means to load - # from .html - # 'filename' in html pointers is a relative path - # (not same as 'html/blah.html' when the pointer is in a directory itself) - pointer_path = "{category}/{url_path}".format(category='html', - url_path=name_to_pathname(location.name)) - base = path(pointer_path).dirname() - #log.debug("base = {0}, base.dirname={1}, filename={2}".format(base, base.dirname(), filename)) - filepath = "{base}/{name}.html".format(base=base, name=filename) - #log.debug("looking for html file for {0} at {1}".format(location, filepath)) - - - - # VS[compat] - # TODO (cpennington): If the file doesn't exist at the right path, - # give the class a chance to fix it up. The file will be written out - # again in the correct format. This should go away once the CMS is - # online and has imported all current (fall 2012) courses from xml - if not system.resources_fs.exists(filepath): - candidates = cls.backcompat_paths(filepath) - #log.debug("candidates = {0}".format(candidates)) - for candidate in candidates: - if system.resources_fs.exists(candidate): - filepath = candidate - break - - try: - with system.resources_fs.open(filepath) as file: - html = file.read() - # Log a warning if we can't parse the file, but don't error - if not check_html(html): - msg = "Couldn't parse html in {0}.".format(filepath) - log.warning(msg) - system.error_tracker("Warning: " + msg) - - definition = {'data': html} - - # TODO (ichuang): remove this after migration - # for Fall 2012 LMS migration: keep filename (and unmangled filename) - definition['filename'] = [ filepath, filename ] - - return definition - - except (ResourceNotFoundError) as err: - msg = 'Unable to load file contents at path {0}: {1} '.format( - filepath, err) - # add more info and re-raise - raise Exception(msg), None, sys.exc_info()[2] - - # TODO (vshnayder): make export put things in the right places. - - def definition_to_xml(self, resource_fs): - '''If the contents are valid xml, write them to filename.xml. Otherwise, - write just to filename.xml, and the html - string to filename.html. - ''' - try: - return etree.fromstring(self.definition['data']) - except etree.XMLSyntaxError: - pass - - # Not proper format. Write html to file, return an empty tag - pathname = name_to_pathname(self.url_name) - pathdir = path(pathname).dirname() - filepath = u'{category}/{pathname}.html'.format(category=self.category, - pathname=pathname) - - resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True) - with resource_fs.open(filepath, 'w') as file: - file.write(self.definition['data']) - - # write out the relative name - relname = path(pathname).basename() - - elt = etree.Element('html') - elt.set("filename", relname) - return elt + self.weight = None \ No newline at end of file From 6bbe82b697e30fb8852e87db15afa6711a4c7679 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Wed, 31 Oct 2012 17:05:46 -0400 Subject: [PATCH 049/228] Make work w/ ie8. Still complains that HTMLCanvasElement is undefined but doesn't try to manipulate it. --- common/lib/xmodule/xmodule/js/src/capa/schematic.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/capa/schematic.js b/common/lib/xmodule/xmodule/js/src/capa/schematic.js index b01f6e12e8..8fb6769342 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/schematic.js +++ b/common/lib/xmodule/xmodule/js/src/capa/schematic.js @@ -1995,7 +1995,7 @@ cktsim = (function() { // set up each schematic entry widget function update_schematics() { // set up each schematic on the page - var schematics = document.getElementsByClassName('schematic'); + var schematics = $('.schematic'); for (var i = 0; i < schematics.length; ++i) if (schematics[i].getAttribute("loaded") != "true") { try { @@ -2036,7 +2036,7 @@ function add_schematic_handler(other_onload) { // ask each schematic input widget to update its value field for submission function prepare_schematics() { - var schematics = document.getElementsByClassName('schematic'); + var schematics = $('.schematic'); for (var i = schematics.length - 1; i >= 0; i--) schematics[i].schematic.update_value(); } @@ -3339,7 +3339,7 @@ schematic = (function() { } // add method to canvas to compute relative coords for event - HTMLCanvasElement.prototype.relMouseCoords = function(event){ + if (HTMLCanvasElement) HTMLCanvasElement.prototype.relMouseCoords = function(event){ // run up the DOM tree to figure out coords for top,left of canvas var totalOffsetX = 0; var totalOffsetY = 0; @@ -3718,7 +3718,7 @@ schematic = (function() { // look for property input fields in the content and give // them a keypress listener that interprets ENTER as // clicking OK. - var plist = content.getElementsByClassName('property'); + var plist = $(content).find('.property'); for (var i = plist.length - 1; i >= 0; --i) { var field = plist[i]; field.dialog = dialog; // help event handler find us... From 562dee067c307edcabda4d5bbb79aefe5687d79c Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 31 Oct 2012 17:10:51 -0400 Subject: [PATCH 050/228] rolling back self assessment module --- common/lib/xmodule/xmodule/self_assessment_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index b6e8a667b4..0c31a48b1e 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -452,7 +452,7 @@ class SelfAssessmentDescriptor(RawDescriptor): stores_state = True has_score = True - template_dir_name = 'selfassessment' + template_dir_name = 'problem' # Capa modules have some additional metadata: # TODO (vshnayder): do problems have any other metadata? Do they @@ -465,7 +465,7 @@ class SelfAssessmentDescriptor(RawDescriptor): @classmethod def backcompat_paths(cls, path): return [ - 'problems/' + path[8:], + 'problem/' + path[8:], path[8:], ] From 2b5e55b6623af972b7b403a4e82d6771759ce2c8 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Wed, 31 Oct 2012 17:29:19 -0400 Subject: [PATCH 051/228] Use try/catch to detect undefined globals for canvas --- .../xmodule/xmodule/js/src/capa/schematic.js | 127 ++++++++++-------- 1 file changed, 68 insertions(+), 59 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/capa/schematic.js b/common/lib/xmodule/xmodule/js/src/capa/schematic.js index 8fb6769342..d2602d8ac3 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/schematic.js +++ b/common/lib/xmodule/xmodule/js/src/capa/schematic.js @@ -3339,23 +3339,28 @@ schematic = (function() { } // add method to canvas to compute relative coords for event - if (HTMLCanvasElement) HTMLCanvasElement.prototype.relMouseCoords = function(event){ - // run up the DOM tree to figure out coords for top,left of canvas - var totalOffsetX = 0; - var totalOffsetY = 0; - var currentElement = this; - do { - totalOffsetX += currentElement.offsetLeft; - totalOffsetY += currentElement.offsetTop; - } - while (currentElement = currentElement.offsetParent); - - // now compute relative position of click within the canvas - this.mouse_x = event.pageX - totalOffsetX; - this.mouse_y = event.pageY - totalOffsetY; - - this.page_x = event.pageX; - this.page_y = event.pageY; + try { + if (HTMLCanvasElement) + HTMLCanvasElement.prototype.relMouseCoords = function(event){ + // run up the DOM tree to figure out coords for top,left of canvas + var totalOffsetX = 0; + var totalOffsetY = 0; + var currentElement = this; + do { + totalOffsetX += currentElement.offsetLeft; + totalOffsetY += currentElement.offsetTop; + } + while (currentElement = currentElement.offsetParent); + + // now compute relative position of click within the canvas + this.mouse_x = event.pageX - totalOffsetX; + this.mouse_y = event.pageY - totalOffsetY; + + this.page_x = event.pageX; + this.page_y = event.pageY; + } + } + catch (err) { // ignore } /////////////////////////////////////////////////////////////////////////////// @@ -4091,48 +4096,52 @@ schematic = (function() { // add dashed lines! // from http://davidowens.wordpress.com/2010/09/07/html-5-canvas-and-dashed-lines/ - CanvasRenderingContext2D.prototype.dashedLineTo = function(fromX, fromY, toX, toY, pattern) { - // Our growth rate for our line can be one of the following: - // (+,+), (+,-), (-,+), (-,-) - // Because of this, our algorithm needs to understand if the x-coord and - // y-coord should be getting smaller or larger and properly cap the values - // based on (x,y). - var lt = function (a, b) { return a <= b; }; - var gt = function (a, b) { return a >= b; }; - var capmin = function (a, b) { return Math.min(a, b); }; - var capmax = function (a, b) { return Math.max(a, b); }; - - var checkX = { thereYet: gt, cap: capmin }; - var checkY = { thereYet: gt, cap: capmin }; - - if (fromY - toY > 0) { - checkY.thereYet = lt; - checkY.cap = capmax; - } - if (fromX - toX > 0) { - checkX.thereYet = lt; - checkX.cap = capmax; - } - - this.moveTo(fromX, fromY); - var offsetX = fromX; - var offsetY = fromY; - var idx = 0, dash = true; - while (!(checkX.thereYet(offsetX, toX) && checkY.thereYet(offsetY, toY))) { - var ang = Math.atan2(toY - fromY, toX - fromX); - var len = pattern[idx]; - - offsetX = checkX.cap(toX, offsetX + (Math.cos(ang) * len)); - offsetY = checkY.cap(toY, offsetY + (Math.sin(ang) * len)); - - if (dash) this.lineTo(offsetX, offsetY); - else this.moveTo(offsetX, offsetY); - - idx = (idx + 1) % pattern.length; - dash = !dash; - } - }; - + try { + if (CanvasRenderingContext2D) + CanvasRenderingContext2D.prototype.dashedLineTo = function(fromX, fromY, toX, toY, pattern) { + // Our growth rate for our line can be one of the following: + // (+,+), (+,-), (-,+), (-,-) + // Because of this, our algorithm needs to understand if the x-coord and + // y-coord should be getting smaller or larger and properly cap the values + // based on (x,y). + var lt = function (a, b) { return a <= b; }; + var gt = function (a, b) { return a >= b; }; + var capmin = function (a, b) { return Math.min(a, b); }; + var capmax = function (a, b) { return Math.max(a, b); }; + + var checkX = { thereYet: gt, cap: capmin }; + var checkY = { thereYet: gt, cap: capmin }; + + if (fromY - toY > 0) { + checkY.thereYet = lt; + checkY.cap = capmax; + } + if (fromX - toX > 0) { + checkX.thereYet = lt; + checkX.cap = capmax; + } + + this.moveTo(fromX, fromY); + var offsetX = fromX; + var offsetY = fromY; + var idx = 0, dash = true; + while (!(checkX.thereYet(offsetX, toX) && checkY.thereYet(offsetY, toY))) { + var ang = Math.atan2(toY - fromY, toX - fromX); + var len = pattern[idx]; + + offsetX = checkX.cap(toX, offsetX + (Math.cos(ang) * len)); + offsetY = checkY.cap(toY, offsetY + (Math.sin(ang) * len)); + + if (dash) this.lineTo(offsetX, offsetY); + else this.moveTo(offsetX, offsetY); + + idx = (idx + 1) % pattern.length; + dash = !dash; + } + }; + } + catch (err) { //noop + } // given a range of values, return a new range [vmin',vmax'] where the limits // have been chosen "nicely". Taken from matplotlib.ticker.LinearLocator function view_limits(vmin,vmax) { From 1bf548af2859bf784e47435d1d26ba216c8464a0 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 31 Oct 2012 17:41:46 -0400 Subject: [PATCH 052/228] basic parsing for problem and rubric --- .../xmodule/xmodule/self_assessment_module.py | 563 +++++------------- 1 file changed, 145 insertions(+), 418 deletions(-) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 0c31a48b1e..ad42090eea 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -1,28 +1,26 @@ -import cgi -import datetime -import dateutil -import dateutil.parser -import json +import copy +from fs.errors import ResourceNotFoundError import logging -import traceback -import re +import os import sys - -from datetime import timedelta from lxml import etree from lxml.html import rewrite_links -from pkg_resources import resource_string +from path import path -from capa.capa_problem import LoncapaProblem -from capa.responsetypes import StudentInputError -from capa.util import convert_files_to_filenames -from progress import Progress -from xmodule.x_module import XModule -from xmodule.raw_module import RawDescriptor -from xmodule.exceptions import NotFoundError +from .x_module import XModule +from pkg_resources import resource_string +from .xml_module import XmlDescriptor, name_to_pathname +from .editing_module import EditingDescriptor +from .stringify import stringify_children +from .html_checker import check_html +from xmodule.modulestore import Location + +from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent log = logging.getLogger("mitx.courseware") + + def only_one(lst, default="", process=lambda x: x): """ If lst is empty, returns default @@ -54,426 +52,155 @@ class SelfAssessmentModule(XModule): XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs) - dom2 = etree.fromstring(definition['data']) + dom2=etree.fromstring("" + self.definition['data'] + "") + rubric=''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))]) + problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))]) - self.attempts = 0 - self.max_attempts = 1 + #print(etree.tostring(rubric)) + #print(etree.tostring(problem)) - self.max_attempts = self.metadata.get('attempts', None) + self.html = etree.tostring(problem) - if self.max_attempts is not None: - self.max_attempts = int(self.max_attempts) + #self.html=self.definition['data'] - if instance_state is not None: - instance_state = json.loads(instance_state) - if instance_state is not None and 'attempts' in instance_state: - self.attempts = instance_state['attempts'] - self.name = only_one(dom2.xpath('/problem/@name')) - self.rubric=etree.tostring(only_one(dom2.xpath("/rubric/html"))) - self.problem=etree.tostring(only_one(dom2.xpath("/problem"))) - self.lcp = LoncapaProblem(self.problem, self.location.html_id(), - instance_state, seed=self.seed, system=self.system) - def get_instance_state(self): - state = self.lcp.get_state() - state['attempts'] = self.attempts - return json.dumps(state) - - def get_score(self): - return self.lcp.get_score() - - def max_score(self): - return self.lcp.get_max_score() - - def get_progress(self): - ''' For now, just return score / max_score - ''' - d = self.get_score() - score = d['score'] - total = d['total'] - if total > 0: - try: - return Progress(score, total) - except Exception as err: - log.exception("Got bad progress") - return None - return None - - def get_html(self): - return self.system.render_template('problem_ajax.html', { - 'element_id': self.location.html_id(), - 'id': self.id, - 'ajax_url': self.system.ajax_url, - }) - - def get_problem_html(self, encapsulate=True): - '''Return html for the problem. Adds check, reset, save buttons - as necessary based on the problem config and state.''' - - try: - html = self.lcp.get_html() - except Exception, err: - log.exception(err) - - # TODO (vshnayder): another switch on DEBUG. - if self.system.DEBUG: - msg = ( - '[courseware.capa.capa_module] ' - 'Failed to generate HTML for problem %s' % - (self.location.url())) - msg += '

        Error:

        %s

        ' % str(err).replace('<', '<') - msg += '

        %s

        ' % traceback.format_exc().replace('<', '<') - html = msg - else: - # We're in non-debug mode, and possibly even in production. We want - # to avoid bricking of problem as much as possible - - # Presumably, student submission has corrupted LoncapaProblem HTML. - # First, pull down all student answers - student_answers = self.lcp.student_answers - answer_ids = student_answers.keys() - - # Some inputtypes, such as dynamath, have additional "hidden" state that - # is not exposed to the student. Keep those hidden - # TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id - hidden_state_keywords = ['dynamath'] - for answer_id in answer_ids: - for hidden_state_keyword in hidden_state_keywords: - if answer_id.find(hidden_state_keyword) >= 0: - student_answers.pop(answer_id) - - # Next, generate a fresh LoncapaProblem - self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(), - state=None, # Tabula rasa - seed=self.seed, system=self.system) - - # Prepend a scary warning to the student - warning = '
        '\ - '

        Warning: The problem has been reset to its initial state!

        '\ - 'The problem\'s state was corrupted by an invalid submission. '\ - 'The submission consisted of:'\ - '
          ' - for student_answer in student_answers.values(): - if student_answer != '': - warning += '
        • ' + cgi.escape(student_answer) + '
        • ' - warning += '
        '\ - 'If this error persists, please contact the course staff.'\ - '
        ' - - html = warning - try: - html += self.lcp.get_html() - except Exception, err: # Couldn't do it. Give up - log.exception(err) - raise - - content = {'name': self.display_name, - 'html': html, - 'weight': self.descriptor.weight, - } - - # We using strings as truthy values, because the terminology of the - # check button is context-specific. - - # Put a "Check" button if unlimited attempts or still some left - if self.max_attempts is None or self.attempts < self.max_attempts-1: - check_button = "Check" - else: - # Will be final check so let user know that - check_button = "Final Check" - - reset_button = True - save_button = True - - # If we're after deadline, or user has exhausted attempts, - # question is read-only. - if self.closed(): - check_button = False - reset_button = False - save_button = False - - # User submitted a problem, and hasn't reset. We don't want - # more submissions. - if self.lcp.done and self.rerandomize == "always": - check_button = False - save_button = False - - # Only show the reset button if pressing it will show different values - if self.rerandomize not in ["always", "onreset"]: - reset_button = False - - # User hasn't submitted an answer yet -- we don't want resets - if not self.lcp.done: - reset_button = False - - # We may not need a "save" button if infinite number of attempts and - # non-randomized. The problem author can force it. It's a bit weird for - # randomization to control this; should perhaps be cleaned up. - if (self.force_save_button == "false") and (self.max_attempts is None and self.rerandomize != "always"): - save_button = False - - context = {'problem': content, - 'id': self.id, - 'check_button': check_button, - 'reset_button': reset_button, - 'save_button': save_button, - 'answer_available': self.answer_available(), - 'ajax_url': self.system.ajax_url, - 'attempts_used': self.attempts, - 'attempts_allowed': self.max_attempts, - 'progress': self.get_progress(), - } - - html = self.system.render_template('problem.html', context) - if encapsulate: - html = '
        '.format( - id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "
        " - - # cdodge: OK, we have to do two rounds of url reference subsitutions - # one which uses the 'asset library' that is served by the contentstore and the - # more global /static/ filesystem based static content. - # NOTE: rewrite_content_links is defined in XModule - # This is a bit unfortunate and I'm sure we'll try to considate this into - # a one step process. - html = rewrite_links(html, self.rewrite_content_links) - - # now do the substitutions which are filesystem based, e.g. '/static/' prefixes - return self.system.replace_urls(html, self.metadata['data_dir']) - - def handle_ajax(self, dispatch, get): - ''' - This is called by courseware.module_render, to handle an AJAX call. - "get" is request.POST. - - Returns a json dictionary: - { 'progress_changed' : True/False, - 'progress' : 'none'/'in_progress'/'done', - } - ''' - handlers = { - 'problem_get': self.get_problem, - 'problem_check': self.check_problem, - 'problem_save': self.save_problem, - } - - if dispatch not in handlers: - return 'Error' - - before = self.get_progress() - d = handlers[dispatch](get) - after = self.get_progress() - d.update({ - 'progress_changed': after != before, - 'progress_status': Progress.to_js_status_str(after), - }) - return json.dumps(d, cls=ComplexEncoder) - - def closed(self): - ''' Is the student still allowed to submit answers? ''' - if self.attempts == self.max_attempts: - return True - if self.close_date is not None and datetime.datetime.utcnow() > self.close_date: - return True - - return False - - def answer_available(self): - ''' Is the user allowed to see an answer? - ''' - if self.show_answer == '': - return False - - if self.show_answer == "never": - return False - - # Admins can see the answer, unless the problem explicitly prevents it - if self.system.user_is_staff: - return True - - if self.show_answer == 'attempted': - return self.attempts > 0 - - if self.show_answer == 'answered': - return self.lcp.done - - if self.show_answer == 'closed': - return self.closed() - - if self.show_answer == 'always': - return True - - return False - - # Figure out if we should move these to capa_problem? - def get_problem(self, get): - ''' Return results of get_problem_html, as a simple dict for json-ing. - { 'html': } - - Used if we want to reconfirm we have the right thing e.g. after - several AJAX calls. - ''' - return {'html': self.get_problem_html(encapsulate=False)} - - @staticmethod - def make_dict_of_responses(get): - ''' - Make dictionary of student responses (aka "answers") - get is POST dictionary. - ''' - answers = dict() - for key in get: - # e.g. input_resistor_1 ==> resistor_1 - _, _, name = key.partition('_') - - # This allows for answers which require more than one value for - # the same form input (e.g. checkbox inputs). The convention is that - # if the name ends with '[]' (which looks like an array), then the - # answer will be an array. - if not name.endswith('[]'): - answers[name] = get[key] - else: - name = name[:-2] - answers[name] = get.getlist(key) - - return answers - - def check_problem(self, get): - ''' Checks whether answers to a problem are correct, and - returns a map of correct/incorrect answers: - - {'success' : bool, - 'contents' : html} - ''' - event_info = dict() - event_info['state'] = self.lcp.get_state() - event_info['problem_id'] = self.location.url() - - answers = self.make_dict_of_responses(get) - event_info['answers'] = convert_files_to_filenames(answers) - - parsed_answer="incorrect" - if(answer[answers.keys()[1]]=="Correct"): - parsed_answer="correct" - - # Too late. Cannot submit - if self.closed(): - event_info['failure'] = 'closed' - self.system.track_function('save_problem_check_fail', event_info) - raise NotFoundError('Problem is closed') - - try: - old_state = self.lcp.get_state() - lcp_id = self.lcp.problem_id - correct_map = self.lcp.grade_answers(answers) - correct_map.set(correctness=parsed_answer) - except StudentInputError as inst: - # TODO (vshnayder): why is this line here? - #self.lcp = LoncapaProblem(self.definition['data'], - # id=lcp_id, state=old_state, system=self.system) - log.exception("StudentInputError in capa_module:problem_check") - return {'success': inst.message} - except Exception, err: - # TODO: why is this line here? - #self.lcp = LoncapaProblem(self.definition['data'], - # id=lcp_id, state=old_state, system=self.system) - if self.system.DEBUG: - msg = "Error checking problem: " + str(err) - msg += '\nTraceback:\n' + traceback.format_exc() - return {'success': msg} - log.exception("Error in capa_module problem checking") - raise Exception("error in capa_module") - - self.attempts = self.attempts + 1 - self.lcp.done = True - - # success = correct if ALL questions in this problem are correct - success = 'correct' - for answer_id in correct_map: - if not correct_map.is_correct(answer_id): - success = 'incorrect' - - # NOTE: We are logging both full grading and queued-grading submissions. In the latter, - # 'success' will always be incorrect - event_info['correct_map'] = correct_map.get_dict() - event_info['success'] = success - event_info['attempts'] = self.attempts - self.system.track_function('save_problem_check', event_info) - - # render problem into HTML - html = self.get_problem_html(encapsulate=False) - - return {'success': success, - 'contents': html, - } - - def save_problem(self, get): - ''' - Save the passed in answers. - Returns a dict { 'success' : bool, ['error' : error-msg]}, - with the error key only present if success is False. - ''' - event_info = dict() - event_info['state'] = self.lcp.get_state() - event_info['problem_id'] = self.location.url() - - answers = self.make_dict_of_responses(get) - event_info['answers'] = answers - - # Too late. Cannot submit - if self.closed(): - event_info['failure'] = 'closed' - self.system.track_function('save_problem_fail', event_info) - return {'success': False, - 'error': "Problem is closed"} - - # Problem submitted. Student should reset before saving - # again. - if self.lcp.done and self.rerandomize == "always": - event_info['failure'] = 'done' - self.system.track_function('save_problem_fail', event_info) - return {'success': False, - 'error': "Problem needs to be reset prior to save."} - - self.lcp.student_answers = answers - - # TODO: should this be save_problem_fail? Looks like success to me... - self.system.track_function('save_problem_fail', event_info) - return {'success': True} - - -class SelfAssessmentDescriptor(RawDescriptor): +class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): """ Module for putting raw html in a course """ + mako_template = "widgets/html-edit.html" module_class = SelfAssessmentModule + filename_extension = "xml" stores_state = True has_score = True - template_dir_name = 'problem' + template_dir_name = "html" - # Capa modules have some additional metadata: - # TODO (vshnayder): do problems have any other metadata? Do they - # actually use type and points? - metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points') - # VS[compat] - # TODO (cpennington): Delete this method once all fall 2012 course are being - # edited in the cms + + js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]} + js_module_name = "HTMLEditingDescriptor" + + # VS[compat] TODO (cpennington): Delete this method once all fall 2012 course + # are being edited in the cms @classmethod def backcompat_paths(cls, path): - return [ - 'problem/' + path[8:], - path[8:], - ] + if path.endswith('.html.xml'): + path = path[:-9] + '.html' # backcompat--look for html instead of xml + if path.endswith('.html.html'): + path = path[:-5] # some people like to include .html in filenames.. + candidates = [] + while os.sep in path: + candidates.append(path) + _, _, path = path.partition(os.sep) - def __init__(self, *args, **kwargs): - super(CapaDescriptor, self).__init__(*args, **kwargs) + # also look for .html versions instead of .xml + nc = [] + for candidate in candidates: + if candidate.endswith('.xml'): + nc.append(candidate[:-4] + '.html') + return candidates + nc - weight_string = self.metadata.get('weight', None) - if weight_string: - self.weight = float(weight_string) + # NOTE: html descriptors are special. We do not want to parse and + # export them ourselves, because that can break things (e.g. lxml + # adds body tags when it exports, but they should just be html + # snippets that will be included in the middle of pages. + + @classmethod + def load_definition(cls, xml_object, system, location): + '''Load a descriptor from the specified xml_object: + + If there is a filename attribute, load it as a string, and + log a warning if it is not parseable by etree.HTMLParser. + + If there is not a filename attribute, the definition is the body + of the xml_object, without the root tag (do not want in the + middle of a page) + ''' + filename = xml_object.get('filename') + if filename is None: + definition_xml = copy.deepcopy(xml_object) + cls.clean_metadata_from_xml(definition_xml) + return {'data': stringify_children(definition_xml)} else: - self.weight = None \ No newline at end of file + # html is special. cls.filename_extension is 'xml', but + # if 'filename' is in the definition, that means to load + # from .html + # 'filename' in html pointers is a relative path + # (not same as 'html/blah.html' when the pointer is in a directory itself) + pointer_path = "{category}/{url_path}".format(category='html', + url_path=name_to_pathname(location.name)) + base = path(pointer_path).dirname() + #log.debug("base = {0}, base.dirname={1}, filename={2}".format(base, base.dirname(), filename)) + filepath = "{base}/{name}.html".format(base=base, name=filename) + #log.debug("looking for html file for {0} at {1}".format(location, filepath)) + + + + # VS[compat] + # TODO (cpennington): If the file doesn't exist at the right path, + # give the class a chance to fix it up. The file will be written out + # again in the correct format. This should go away once the CMS is + # online and has imported all current (fall 2012) courses from xml + if not system.resources_fs.exists(filepath): + candidates = cls.backcompat_paths(filepath) + #log.debug("candidates = {0}".format(candidates)) + for candidate in candidates: + if system.resources_fs.exists(candidate): + filepath = candidate + break + + try: + with system.resources_fs.open(filepath) as file: + html = file.read() + # Log a warning if we can't parse the file, but don't error + if not check_html(html): + msg = "Couldn't parse html in {0}.".format(filepath) + log.warning(msg) + system.error_tracker("Warning: " + msg) + + definition = {'data': html} + + # TODO (ichuang): remove this after migration + # for Fall 2012 LMS migration: keep filename (and unmangled filename) + definition['filename'] = [ filepath, filename ] + + return definition + + except (ResourceNotFoundError) as err: + msg = 'Unable to load file contents at path {0}: {1} '.format( + filepath, err) + # add more info and re-raise + raise Exception(msg), None, sys.exc_info()[2] + + # TODO (vshnayder): make export put things in the right places. + + def definition_to_xml(self, resource_fs): + '''If the contents are valid xml, write them to filename.xml. Otherwise, + write just to filename.xml, and the html + string to filename.html. + ''' + try: + return etree.fromstring(self.definition['data']) + except etree.XMLSyntaxError: + pass + + # Not proper format. Write html to file, return an empty tag + pathname = name_to_pathname(self.url_name) + pathdir = path(pathname).dirname() + filepath = u'{category}/{pathname}.html'.format(category=self.category, + pathname=pathname) + + resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True) + with resource_fs.open(filepath, 'w') as file: + file.write(self.definition['data']) + + # write out the relative name + relname = path(pathname).basename() + + elt = etree.Element('html') + elt.set("filename", relname) + return elt From 74e6834cca71037669da531b4821e178e36d5e9f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 31 Oct 2012 18:05:04 -0400 Subject: [PATCH 053/228] move rubric and problem to forms --- .../xmodule/xmodule/self_assessment_module.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index ad42090eea..854c2cf1f3 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -19,7 +19,12 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent log = logging.getLogger("mitx.courseware") +problem_form=('

        ') +rubric_form=('
        Correct
        ' + '' + 'Incorrect
        ') def only_one(lst, default="", process=lambda x: x): """ @@ -53,15 +58,17 @@ class SelfAssessmentModule(XModule): instance_state, shared_state, **kwargs) dom2=etree.fromstring("" + self.definition['data'] + "") - rubric=''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))]) - problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))]) + self.rubric=''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))]) + self.problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))]) + + self.problem=''.join([self.problem,problem_form]) + + self.rubric=''.join([self.rubric,rubric_form]) #print(etree.tostring(rubric)) #print(etree.tostring(problem)) - self.html = etree.tostring(problem) - - #self.html=self.definition['data'] + self.html = self.problem From 947e6e6e0138b914f5090a9952ab3317c34254e6 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 31 Oct 2012 18:11:38 -0400 Subject: [PATCH 054/228] working on ajax calls --- .../js/src/selfassessment/display.coffee | 144 ++---------------- .../xmodule/xmodule/self_assessment_module.py | 28 ++++ 2 files changed, 44 insertions(+), 128 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index 5e16182553..8de2043e3b 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -11,11 +11,9 @@ class @Problem $(selector, @el) bind: => - problem_prefix = @element_id.replace(/problem_/,'') + problem_prefix = @element_id.replace(/sa_/,'') @inputs = @$("[id^=input_#{problem_prefix}_]") - - @$('section.action input:button').click @refreshAnswers - @$('section.action input.check').click @check + @$('section.action input.show').click @show @$('section.action input.save').click @save @@ -26,7 +24,7 @@ class @Problem @setupInputTypes() @bind() else - $.postWithPrefix "#{@url}/problem_get", (response) => + $.postWithPrefix "#{@url}/sa_get", (response) => @el.html(response.html) JavascriptLoader.executeModuleScripts @el, () => @setupInputTypes() @@ -36,86 +34,21 @@ class @Problem # TODO add hooks for problem types here by inspecting response.html and doing # stuff if a div w a class is found - setupInputTypes: => - @inputtypeDisplays = {} - @el.find(".capa_inputtype").each (index, inputtype) => - classes = $(inputtype).attr('class').split(' ') - id = $(inputtype).attr('id') - for cls in classes - setupMethod = @inputtypeSetupMethods[cls] - if setupMethod? - @inputtypeDisplays[id] = setupMethod(inputtype) - - check: => - Logger.log 'problem_check', @answers - $.postWithPrefix "#{@url}/problem_check", @answers, (response) => - switch response.success - when 'incorrect', 'correct' - @render(response.contents) - @updateProgress response - if @el.hasClass 'showed' - @el.removeClass 'showed' - else - @gentle_alert response.success - - reset: => - Logger.log 'problem_reset', @answers - $.postWithPrefix "#{@url}/problem_reset", id: @id, (response) => - @render(response.html) - @updateProgress response - - # TODO this needs modification to deal with javascript responses; perhaps we - # need something where responsetypes can define their own behavior when show - # is called. show: => - if !@el.hasClass 'showed' - Logger.log 'problem_show', problem: @id - $.postWithPrefix "#{@url}/problem_show", (response) => - answers = response.answers - $.each answers, (key, value) => - if $.isArray(value) - for choice in value - @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true' - else - answer = @$("#answer_#{key}, #solution_#{key}") - answer.html(value) - Collapsible.setCollapsibles(answer) + Logger.log 'sa_show', problem: @id + $.postWithPrefix "#{@url}/sa_show", (response) => + answers = response.answers + $.each answers, (key, value) => + if $.isArray(value) + for choice in value + @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true' + else + answer = @$("#answer_#{key}, #solution_#{key}") + answer.html(value) + Collapsible.setCollapsibles(answer) - # TODO remove the above once everything is extracted into its own - # inputtype functions. - - @el.find(".capa_inputtype").each (index, inputtype) => - classes = $(inputtype).attr('class').split(' ') - for cls in classes - display = @inputtypeDisplays[$(inputtype).attr('id')] - showMethod = @inputtypeShowAnswerMethods[cls] - showMethod(inputtype, display, answers) if showMethod? - - @el.find('.problem > div').each (index, element) => - MathJax.Hub.Queue ["Typeset", MathJax.Hub, element] - - @$('.show').val 'Hide Answer' - @el.addClass 'showed' - @updateProgress response - else - @$('[id^=answer_], [id^=solution_]').text '' - @$('[correct_answer]').attr correct_answer: null - @el.removeClass 'showed' - @$('.show').val 'Show Answer' - - @el.find(".capa_inputtype").each (index, inputtype) => - display = @inputtypeDisplays[$(inputtype).attr('id')] - classes = $(inputtype).attr('class').split(' ') - for cls in classes - hideMethod = @inputtypeHideAnswerMethods[cls] - hideMethod(inputtype, display) if hideMethod? - - gentle_alert: (msg) => - if @el.find('.capa_alert').length - @el.find('.capa_alert').remove() - alert_elem = "
        " + msg + "
        " - @el.find('.action').after(alert_elem) - @el.find('.capa_alert').css(opacity: 0).animate(opacity: 1, 700) + @$('.show').val 'Hide Answer' + @el.addClass 'showed' save: => Logger.log 'problem_save', @answers @@ -125,51 +58,6 @@ class @Problem @gentle_alert saveMessage @updateProgress response - refreshAnswers: => - @$('input.schematic').each (index, element) -> - element.schematic.update_value() - @$(".CodeMirror").each (index, element) -> - element.CodeMirror.save() if element.CodeMirror.save - @answers = @inputs.serialize() - - inputtypeSetupMethods: - - 'text-input-dynamath': (element) => - ### - Return: function (eqn) -> eqn that preprocesses the user formula input before - it is fed into MathJax. Return 'false' if no preprocessor specified - ### - data = $(element).find('.text-input-dynamath_data') - - preprocessorClassName = data.data('preprocessor') - preprocessorClass = window[preprocessorClassName] - if not preprocessorClass? - return false - else - preprocessor = new preprocessorClass() - return preprocessor.fn - - javascriptinput: (element) => - - data = $(element).find(".javascriptinput_data") - - params = data.data("params") - submission = data.data("submission") - evaluation = data.data("evaluation") - problemState = data.data("problem_state") - displayClass = window[data.data('display_class')] - - if evaluation == '' - evaluation = null - - container = $(element).find(".javascriptinput_container") - submissionField = $(element).find(".javascriptinput_input") - - display = new displayClass(problemState, submission, evaluation, container, submissionField, params) - display.render() - - return display - inputtypeShowAnswerMethods: choicegroup: (element, display, answers) => element = $(element) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 854c2cf1f3..9ed088d406 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -70,6 +70,34 @@ class SelfAssessmentModule(XModule): self.html = self.problem + def handle_ajax(self, dispatch, get): + ''' + This is called by courseware.module_render, to handle an AJAX call. + "get" is request.POST. + + Returns a json dictionary: + { 'progress_changed' : True/False, + 'progress' : 'none'/'in_progress'/'done', + } + ''' + handlers = { + 'sa_get' : self.show_problem + 'sa_show': self.show_rubric, + 'sa_save': self.save_problem, + } + + if dispatch not in handlers: + return 'Error' + + before = self.get_progress() + d = handlers[dispatch](get) + after = self.get_progress() + d.update({ + 'progress_changed': after != before, + 'progress_status': Progress.to_js_status_str(after), + }) + return json.dumps(d, cls=ComplexEncoder) + From cf2402aee9dc749c5bf8eb1a2a56185de7d597b7 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 31 Oct 2012 18:17:12 -0400 Subject: [PATCH 055/228] bugfix: always set queue len --- common/lib/capa/capa/inputtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index d47c5a3006..de29b5e664 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -396,7 +396,7 @@ class FileSubmission(InputTypeBase): self.required_files = saxutils.escape(self.required_files, escapedict) # Check if problem has been queued - queue_len = 0 + self.queue_len = 0 # Flag indicating that the problem has been queued, 'msg' is length of queue if self.status == 'incomplete': self.status = 'queued' From 74e23546af9dcad10d162e9491862b78f5310efe Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 31 Oct 2012 18:40:10 -0400 Subject: [PATCH 056/228] More inputtype refactor - add an Attribute class - input types just need to declare which attributes they want, and how to transform and validate them, and the base class will do all the rest. - change OptionInput to new format. --- common/lib/capa/capa/inputtypes.py | 156 +++++++++++++++++++++++------ 1 file changed, 124 insertions(+), 32 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index de29b5e664..f154569fe4 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -32,8 +32,7 @@ graded status as'status' # makes sense, but a bunch of problems have markup that assumes block. Bigger TODO: figure out a # general css and layout strategy for capa, document it, then implement it. - - +from collections import namedtuple import json import logging from lxml import etree @@ -50,6 +49,58 @@ log = logging.getLogger('mitx.' + __name__) registry = TagRegistry() +class Attribute(object): + """ + Allows specifying required and optional attributes for input types. + """ + + # want to allow default to be None, but also allow required objects + _sentinel = object() + + def __init__(self, name, default=_sentinel, transform=None, validate=None): + """ + Define an attribute + + name (str): then name of the attribute--should be alphanumeric (valid for an XML attribute) + + default (any type): If not specified, this attribute is required. If specified, use this as the default value + if the attribute is not specified. Note that this value will not be transformed or validated. + + transform (function str -> any type): If not None, will be called to transform the parsed value into an internal + representation. + + validate (function str-or-return-type-of-tranform -> unit or exception): If not None, called to validate the + (possibly transformed) value of the attribute. Should raise ValueError with a helpful message if + the value is invalid. + """ + self.name = name + self.default = default + self.validate = validate + self.transform = transform + + def parse_from_xml(self, element): + """ + Given an etree xml element that should have this attribute, do the obvious thing: + - look for it. raise ValueError if not found and required. + - transform and validate. pass through any exceptions from transform or validate. + """ + val = element.get(self.name) + if self.default == self._sentinel and val is None: + raise ValueError('Missing required attribute {0}.'.format(self.name)) + + if val is None: + # not required, so return default + return self.default + + if self.transform is not None: + val = self.transform(val) + + if self.validate is not None: + self.validate(val) + + return val + + class InputTypeBase(object): """ Abstract base class for input types. @@ -102,9 +153,12 @@ class InputTypeBase(object): self.status = state.get('status', 'unanswered') - # Call subclass "constructor" -- means they don't have to worry about calling - # super().__init__, and are isolated from changes to the input constructor interface. try: + # Pre-parse and propcess all the declared requirements. + self.process_requirements() + + # Call subclass "constructor" -- means they don't have to worry about calling + # super().__init__, and are isolated from changes to the input constructor interface. self.setup() except Exception as err: # Something went wrong: add xml to message, but keep the traceback @@ -112,6 +166,32 @@ class InputTypeBase(object): raise Exception, msg, sys.exc_info()[2] + @classmethod + def get_attributes(cls): + """ + Should return a list of Attribute objects (see docstring there for details). Subclasses should override. e.g. + + return super(MyClass, cls).attributes + [Attribute('unicorn', True), + Attribute('num_dragons', 12, transform=int), ...] + """ + return [] + + + def process_requirements(self): + """ + Subclasses can declare lists of required and optional attributes. This + function parses the input xml and pulls out those attributes. This + isolates most simple input types from needing to deal with xml parsing at all. + + Processes attributes, putting the results in the self.loaded_attributes dictionary. + """ + # Use a local dict so that if there are exceptions, we don't end up in a partially-initialized state. + d = {} + for a in self.get_attributes(): + d[a.name] = a.parse_from_xml(self.xml) + + self.loaded_attributes = d + def setup(self): """ InputTypes should override this to do any needed initialization. It is called after the @@ -122,14 +202,28 @@ class InputTypeBase(object): """ pass + def _get_render_context(self): """ - Abstract method. Subclasses should implement to return the dictionary - of keys needed to render their template. + Should return a dictionary of keys needed to render the template for the input type. (Separate from get_html to faciliate testing of logic separately from the rendering) + + The default implementation gets the following rendering context: basic things like value, id, + status, and msg, as well as everything in self.loaded_attributes. + + This means that input types that only parse attributes get everything they need, and don't need + to override this method. """ - raise NotImplementedError + context = { + 'id': self.id, + 'value': self.value, + 'status': self.status, + 'msg': self.msg, + } + context.update(self.loaded_attributes) + return context + def get_html(self): """ @@ -139,7 +233,10 @@ class InputTypeBase(object): raise NotImplementedError("no rendering template specified for class {0}" .format(self.__class__)) - html = self.system.render_template(self.template, self._get_render_context()) + context = self._default_render_context() + context.update(self._get_render_context()) + + html = self.system.render_template(self.template, context) return etree.XML(html) @@ -158,33 +255,28 @@ class OptionInput(InputTypeBase): template = "optioninput.html" tags = ['optioninput'] - def setup(self): - # Extract the options... - options = self.xml.get('options') - if not options: - raise ValueError("optioninput: Missing 'options' specification.") + @classmethod + def get_attributes(cls): + """ + Convert options to a convenient format. + """ - # parse the set of possible options - oset = shlex.shlex(options[1:-1]) - oset.quotes = "'" - oset.whitespace = "," - oset = [x[1:-1] for x in list(oset)] + def parse_options(options): + """Given options string, convert it into an ordered list of (option, option) tuples + (Why? I don't know--that's what the template uses at the moment) + """ + # parse the set of possible options + oset = shlex.shlex(options[1:-1]) + oset.quotes = "'" + oset.whitespace = "," + oset = [x[1:-1] for x in list(oset)] - # make ordered list with (key, value) same - self.osetdict = [(oset[x], oset[x]) for x in range(len(oset))] - # TODO: allow ordering to be randomized + # make ordered list with (key, value) same + return [(oset[x], oset[x]) for x in range(len(oset))] - def _get_render_context(self): - - context = { - 'id': self.id, - 'value': self.value, - 'status': self.status, - 'msg': self.msg, - 'options': self.osetdict, - 'inline': self.xml.get('inline',''), - } - return context + return super(OptionInput, cls).get_attributes() + [ + Attribute('options', transform=parse_options), + Attribute('inline', '')] registry.register(OptionInput) From a91a571fbfbb437f0a0499afec730eff47657357 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 31 Oct 2012 18:49:58 -0400 Subject: [PATCH 057/228] update javascript --- common/lib/xmodule/xmodule/capa_module.py | 118 +++++++++--------- .../js/src/selfassessment/display.coffee | 101 +++------------ .../xmodule/xmodule/self_assessment_module.py | 42 ++++--- 3 files changed, 106 insertions(+), 155 deletions(-) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 76158093b6..46f07796db 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -580,76 +580,76 @@ class CapaModule(XModule): 'contents': html, } -def save_problem(self, get): - ''' - Save the passed in answers. - Returns a dict { 'success' : bool, ['error' : error-msg]}, - with the error key only present if success is False. - ''' - event_info = dict() - event_info['state'] = self.lcp.get_state() - event_info['problem_id'] = self.location.url() + def save_problem(self, get): + ''' + Save the passed in answers. + Returns a dict { 'success' : bool, ['error' : error-msg]}, + with the error key only present if success is False. + ''' + event_info = dict() + event_info['state'] = self.lcp.get_state() + event_info['problem_id'] = self.location.url() - answers = self.make_dict_of_responses(get) - event_info['answers'] = answers + answers = self.make_dict_of_responses(get) + event_info['answers'] = answers - # Too late. Cannot submit - if self.closed(): - event_info['failure'] = 'closed' + # Too late. Cannot submit + if self.closed(): + event_info['failure'] = 'closed' + self.system.track_function('save_problem_fail', event_info) + return {'success': False, + 'error': "Problem is closed"} + + # Problem submitted. Student should reset before saving + # again. + if self.lcp.done and self.rerandomize == "always": + event_info['failure'] = 'done' + self.system.track_function('save_problem_fail', event_info) + return {'success': False, + 'error': "Problem needs to be reset prior to save."} + + self.lcp.student_answers = answers + + # TODO: should this be save_problem_fail? Looks like success to me... self.system.track_function('save_problem_fail', event_info) - return {'success': False, - 'error': "Problem is closed"} + return {'success': True} - # Problem submitted. Student should reset before saving - # again. - if self.lcp.done and self.rerandomize == "always": - event_info['failure'] = 'done' - self.system.track_function('save_problem_fail', event_info) - return {'success': False, - 'error': "Problem needs to be reset prior to save."} + def reset_problem(self, get): + ''' Changes problem state to unfinished -- removes student answers, + and causes problem to rerender itself. - self.lcp.student_answers = answers + Returns problem html as { 'html' : html-string }. + ''' + event_info = dict() + event_info['old_state'] = self.lcp.get_state() + event_info['problem_id'] = self.location.url() - # TODO: should this be save_problem_fail? Looks like success to me... - self.system.track_function('save_problem_fail', event_info) - return {'success': True} + if self.closed(): + event_info['failure'] = 'closed' + self.system.track_function('reset_problem_fail', event_info) + return {'success': False, + 'error': "Problem is closed"} -def reset_problem(self, get): - ''' Changes problem state to unfinished -- removes student answers, - and causes problem to rerender itself. + if not self.lcp.done: + event_info['failure'] = 'not_done' + self.system.track_function('reset_problem_fail', event_info) + return {'success': False, + 'error': "Refresh the page and make an attempt before resetting."} - Returns problem html as { 'html' : html-string }. - ''' - event_info = dict() - event_info['old_state'] = self.lcp.get_state() - event_info['problem_id'] = self.location.url() + self.lcp.do_reset() + if self.rerandomize in ["always", "onreset"]: + # reset random number generator seed (note the self.lcp.get_state() + # in next line) + self.lcp.seed = None - if self.closed(): - event_info['failure'] = 'closed' - self.system.track_function('reset_problem_fail', event_info) - return {'success': False, - 'error': "Problem is closed"} + self.lcp = LoncapaProblem(self.definition['data'], + self.location.html_id(), self.lcp.get_state(), + system=self.system) - if not self.lcp.done: - event_info['failure'] = 'not_done' - self.system.track_function('reset_problem_fail', event_info) - return {'success': False, - 'error': "Refresh the page and make an attempt before resetting."} + event_info['new_state'] = self.lcp.get_state() + self.system.track_function('reset_problem', event_info) - self.lcp.do_reset() - if self.rerandomize in ["always", "onreset"]: - # reset random number generator seed (note the self.lcp.get_state() - # in next line) - self.lcp.seed = None - - self.lcp = LoncapaProblem(self.definition['data'], - self.location.html_id(), self.lcp.get_state(), - system=self.system) - - event_info['new_state'] = self.lcp.get_state() - self.system.track_function('reset_problem', event_info) - - return {'html': self.get_problem_html(encapsulate=False)} + return {'html': self.get_problem_html(encapsulate=False)} class CapaDescriptor(RawDescriptor): diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index 8de2043e3b..aabe0cc0e5 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -1,84 +1,21 @@ -class @Problem +show: => + Logger.log 'sa_show', problem: @id + $.postWithPrefix "#{@url}/sa_show", (response) => + answers = response.answers + $.each answers, (key, value) => + if $.isArray(value) + for choice in value + @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true' + else + answer = @$("#answer_#{key}, #solution_#{key}") + answer.html(value) + Collapsible.setCollapsibles(answer) - constructor: (element) -> - @el = $(element).find('.problems-wrapper') - @id = @el.data('problem-id') - @element_id = @el.attr('id') - @url = @el.data('url') - @render() + @$('.show').val 'Hide Answer' + @el.addClass 'showed' - $: (selector) -> - $(selector, @el) - - bind: => - problem_prefix = @element_id.replace(/sa_/,'') - @inputs = @$("[id^=input_#{problem_prefix}_]") - - @$('section.action input.show').click @show - @$('section.action input.save').click @save - - render: (content) -> - if content - @el.html(content) - JavascriptLoader.executeModuleScripts @el, () => - @setupInputTypes() - @bind() - else - $.postWithPrefix "#{@url}/sa_get", (response) => - @el.html(response.html) - JavascriptLoader.executeModuleScripts @el, () => - @setupInputTypes() - @bind() - - - # TODO add hooks for problem types here by inspecting response.html and doing - # stuff if a div w a class is found - - show: => - Logger.log 'sa_show', problem: @id - $.postWithPrefix "#{@url}/sa_show", (response) => - answers = response.answers - $.each answers, (key, value) => - if $.isArray(value) - for choice in value - @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true' - else - answer = @$("#answer_#{key}, #solution_#{key}") - answer.html(value) - Collapsible.setCollapsibles(answer) - - @$('.show').val 'Hide Answer' - @el.addClass 'showed' - - save: => - Logger.log 'problem_save', @answers - $.postWithPrefix "#{@url}/problem_save", @answers, (response) => - if response.success - saveMessage = "Your answers have been saved but not graded. Hit 'Check' to grade them." - @gentle_alert saveMessage - @updateProgress response - - inputtypeShowAnswerMethods: - choicegroup: (element, display, answers) => - element = $(element) - - element.find('input').attr('disabled', 'disabled') - - input_id = element.attr('id').replace(/inputtype_/,'') - answer = answers[input_id] - for choice in answer - element.find("label[for='input_#{input_id}_#{choice}']").addClass 'choicegroup_correct' - - javascriptinput: (element, display, answers) => - answer_id = $(element).attr('id').split("_")[1...].join("_") - answer = JSON.parse(answers[answer_id]) - display.showAnswer(answer) - - inputtypeHideAnswerMethods: - choicegroup: (element, display) => - element = $(element) - element.find('input').attr('disabled', null) - element.find('label').removeClass('choicegroup_correct') - - javascriptinput: (element, display) => - display.hideAnswer() +save: => + Logger.log 'sa_save', @answers + $.postWithPrefix "#{@url}/sa_save", @answers, (response) => + if response.success + @$('p.rubric').replace(response.rubric) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 9ed088d406..41ac6a4f7b 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -19,12 +19,12 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent log = logging.getLogger("mitx.courseware") -problem_form=('

        ') +problem_form=('

        ') -rubric_form=('
        Correct
        ' +rubric_form=('Correct
        ' '' - 'Incorrect
        ') + 'Incorrect') def only_one(lst, default="", process=lambda x: x): """ @@ -46,7 +46,7 @@ class SelfAssessmentModule(XModule): resource_string(__name__, 'js/src/selfassessment/display.coffee') ] } - js_module_name = "SelfAssessmentModule" + js_module_name = "SelfAssessment" def get_html(self): # cdodge: perform link substitutions for any references to course static content (e.g. images) @@ -71,17 +71,18 @@ class SelfAssessmentModule(XModule): self.html = self.problem def handle_ajax(self, dispatch, get): - ''' - This is called by courseware.module_render, to handle an AJAX call. - "get" is request.POST. + ''' + This is called by courseware.module_render, to handle an AJAX call. + "get" is request.POST. + + Returns a json dictionary: + { 'progress_changed' : True/False, + 'progress' : 'none'/'in_progress'/'done', + } + ''' - Returns a json dictionary: - { 'progress_changed' : True/False, - 'progress' : 'none'/'in_progress'/'done', - } - ''' handlers = { - 'sa_get' : self.show_problem + 'sa_get' : self.show_problem, 'sa_show': self.show_rubric, 'sa_save': self.save_problem, } @@ -98,6 +99,19 @@ class SelfAssessmentModule(XModule): }) return json.dumps(d, cls=ComplexEncoder) + def save_problem(self, get): + ''' + Save the passed in answers. + Returns a dict { 'success' : bool, ['error' : error-msg]}, + with the error key only present if success is False. + ''' + event_info = dict() + event_info['state'] = self.lcp.get_state() + event_info['problem_id'] = self.location.url() + + return {'success': True, 'rubric' : self.rubric} + + From 75adeed063866dd66fd9eb3bc1bea20ce21a2e0b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 31 Oct 2012 20:04:21 -0400 Subject: [PATCH 058/228] changing coffee file --- .../js/src/selfassessment/display.coffee | 163 ++++++++++++++++-- .../xmodule/js/src/selfassessment/display.js | 20 +++ .../xmodule/xmodule/self_assessment_module.py | 8 +- 3 files changed, 169 insertions(+), 22 deletions(-) create mode 100644 common/lib/xmodule/xmodule/js/src/selfassessment/display.js diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index aabe0cc0e5..91582642b8 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -1,21 +1,148 @@ -show: => - Logger.log 'sa_show', problem: @id - $.postWithPrefix "#{@url}/sa_show", (response) => - answers = response.answers - $.each answers, (key, value) => - if $.isArray(value) - for choice in value - @$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true' +class @SelfAssessment + + constructor: (element) -> + @el = $(element).find('.sa-wrapper') + @id = @el.data('sa-id') + @element_id = @el.attr('id') + @url = @el.data('url') + @render() + + $: (selector) -> + $(selector, @el) + + bind: => + + window.update_schematics() + + problem_prefix = @element_id.replace(/sa_/,'') + @inputs = @$("[id^=input_#{problem_prefix}_]") + + @$('input:button').click @refreshAnswers + #@$('section.action input.check').click @check + @$('input.show').click @show + @$('input.save').click @save + + # Collapsibles + Collapsible.setCollapsibles(@el) + + # Dynamath + @$('input.math').keyup(@refreshMath) + @$('input.math').each (index, element) => + MathJax.Hub.Queue [@refreshMath, null, element] + + updateProgress: (response) => + if response.progress_changed + @el.attr progress: response.progress_status + @el.trigger('progressChanged') + + show: => + $.postWithPrefix "#{@url}/sa_show", (response) => + answers = response.answers + @el.addClass 'showed' + + save: => + $.postWithPrefix "/sa_save", @answers, (response) => + if response.success + @$('p.rubric').replace(response.rubric) + + render: (content) -> + if content + @el.html(content) + JavascriptLoader.executeModuleScripts @el, () => + @setupInputTypes() + @bind() + else + $.postWithPrefix "#{@url}/problem_get", (response) => + @el.html(response.html) + JavascriptLoader.executeModuleScripts @el, () => + @setupInputTypes() + @bind() + + setupInputTypes: => + @inputtypeDisplays = {} + @el.find(".capa_inputtype").each (index, inputtype) => + classes = $(inputtype).attr('class').split(' ') + id = $(inputtype).attr('id') + for cls in classes + setupMethod = @inputtypeSetupMethods[cls] + if setupMethod? + @inputtypeDisplays[id] = setupMethod(inputtype) + + gentle_alert: (msg) => + if @el.find('.capa_alert').length + @el.find('.capa_alert').remove() + alert_elem = "
        " + msg + "
        " + @el.find('.action').after(alert_elem) + @el.find('.capa_alert').css(opacity: 0).animate(opacity: 1, 700) + + refreshAnswers: => + @$('input.schematic').each (index, element) -> + element.schematic.update_value() + @$(".CodeMirror").each (index, element) -> + element.CodeMirror.save() if element.CodeMirror.save + @answers = @inputs.serialize() + + inputtypeSetupMethods: + + 'text-input-dynamath': (element) => + ### + Return: function (eqn) -> eqn that preprocesses the user formula input before + it is fed into MathJax. Return 'false' if no preprocessor specified + ### + data = $(element).find('.text-input-dynamath_data') + + preprocessorClassName = data.data('preprocessor') + preprocessorClass = window[preprocessorClassName] + if not preprocessorClass? + return false else - answer = @$("#answer_#{key}, #solution_#{key}") - answer.html(value) - Collapsible.setCollapsibles(answer) + preprocessor = new preprocessorClass() + return preprocessor.fn + + javascriptinput: (element) => + + data = $(element).find(".javascriptinput_data") + + params = data.data("params") + submission = data.data("submission") + evaluation = data.data("evaluation") + problemState = data.data("problem_state") + displayClass = window[data.data('display_class')] + + if evaluation == '' + evaluation = null + + container = $(element).find(".javascriptinput_container") + submissionField = $(element).find(".javascriptinput_input") + + display = new displayClass(problemState, submission, evaluation, container, submissionField, params) + display.render() + + return display + + inputtypeShowAnswerMethods: + choicegroup: (element, display, answers) => + element = $(element) + + element.find('input').attr('disabled', 'disabled') + + input_id = element.attr('id').replace(/inputtype_/,'') + answer = answers[input_id] + for choice in answer + element.find("label[for='input_#{input_id}_#{choice}']").addClass 'choicegroup_correct' + + javascriptinput: (element, display, answers) => + answer_id = $(element).attr('id').split("_")[1...].join("_") + answer = JSON.parse(answers[answer_id]) + display.showAnswer(answer) + + inputtypeHideAnswerMethods: + choicegroup: (element, display) => + element = $(element) + element.find('input').attr('disabled', null) + element.find('label').removeClass('choicegroup_correct') + + javascriptinput: (element, display) => + display.hideAnswer() - @$('.show').val 'Hide Answer' - @el.addClass 'showed' -save: => - Logger.log 'sa_save', @answers - $.postWithPrefix "#{@url}/sa_save", @answers, (response) => - if response.success - @$('p.rubric').replace(response.rubric) diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.js b/common/lib/xmodule/xmodule/js/src/selfassessment/display.js new file mode 100644 index 0000000000..e3b92cbff5 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.js @@ -0,0 +1,20 @@ + + show: function() { + Logger.log('sa_show', { + problem: _this.id + }); + return $.postWithPrefix("" + _this.url + "/sa_show", function(response) { + var answers; + answers = response.answers; + return _this.el.addClass('showed'); + }); + } + + save: function() { + Logger.log('sa_save', _this.answers); + return $.postWithPrefix("" + _this.url + "/sa_save", _this.answers, function(response) { + if (response.success) { + return _this.$('p.rubric').replace(response.rubric); + } + }); + } diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 41ac6a4f7b..56ac0d7911 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -19,12 +19,12 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent log = logging.getLogger("mitx.courseware") -problem_form=('

        ') +problem_form=('

        ') -rubric_form=('
        Correct
        ' +rubric_form=('
        Correct
        ' '' - 'Incorrect') + 'Incorrect
        ') def only_one(lst, default="", process=lambda x: x): """ From 7fcf02a0cc91e12e3889844e233c9b4b0ff26b8b Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 31 Oct 2012 23:40:19 -0400 Subject: [PATCH 059/228] Further refactor - small Attribute and InputTypeBase interface changes to make things cleaner - move html quoting into templates (use ${blah | h} syntax) - converting input types to use new format. --- common/lib/capa/capa/inputtypes.py | 230 +++++++++--------- .../capa/capa/templates/filesubmission.html | 2 +- .../capa/capa/templates/javascriptinput.html | 2 +- common/lib/capa/capa/templates/textline.html | 2 +- common/lib/capa/capa/tests/test_inputtypes.py | 28 ++- 5 files changed, 144 insertions(+), 120 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index f154569fe4..bd3642220f 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -57,7 +57,7 @@ class Attribute(object): # want to allow default to be None, but also allow required objects _sentinel = object() - def __init__(self, name, default=_sentinel, transform=None, validate=None): + def __init__(self, name, default=_sentinel, transform=None, validate=None, render=True): """ Define an attribute @@ -72,11 +72,14 @@ class Attribute(object): validate (function str-or-return-type-of-tranform -> unit or exception): If not None, called to validate the (possibly transformed) value of the attribute. Should raise ValueError with a helpful message if the value is invalid. + + render (bool): if False, don't include this attribute in the template context. """ self.name = name self.default = default self.validate = validate self.transform = transform + self.render = render def parse_from_xml(self, element): """ @@ -171,8 +174,7 @@ class InputTypeBase(object): """ Should return a list of Attribute objects (see docstring there for details). Subclasses should override. e.g. - return super(MyClass, cls).attributes + [Attribute('unicorn', True), - Attribute('num_dragons', 12, transform=int), ...] + return [Attribute('unicorn', True), Attribute('num_dragons', 12, transform=int), ...] """ return [] @@ -183,14 +185,19 @@ class InputTypeBase(object): function parses the input xml and pulls out those attributes. This isolates most simple input types from needing to deal with xml parsing at all. - Processes attributes, putting the results in the self.loaded_attributes dictionary. + Processes attributes, putting the results in the self.loaded_attributes dictionary. Also creates a set + self.to_render, containing the names of attributes that should be included in the context by default. """ - # Use a local dict so that if there are exceptions, we don't end up in a partially-initialized state. - d = {} + # Use local dicts and sets so that if there are exceptions, we don't end up in a partially-initialized state. + loaded = {} + to_render = set() for a in self.get_attributes(): - d[a.name] = a.parse_from_xml(self.xml) + loaded[a.name] = a.parse_from_xml(self.xml) + if a.render: + to_render.add(a.name) - self.loaded_attributes = d + self.loaded_attributes = loaded + self.to_render = to_render def setup(self): """ @@ -209,11 +216,11 @@ class InputTypeBase(object): (Separate from get_html to faciliate testing of logic separately from the rendering) - The default implementation gets the following rendering context: basic things like value, id, - status, and msg, as well as everything in self.loaded_attributes. + The default implementation gets the following rendering context: basic things like value, id, status, and msg, + as well as everything in self.loaded_attributes, and everything returned by self._extra_context(). - This means that input types that only parse attributes get everything they need, and don't need - to override this method. + This means that input types that only parse attributes and pass them to the template get everything they need, + and don't need to override this method. """ context = { 'id': self.id, @@ -221,9 +228,17 @@ class InputTypeBase(object): 'status': self.status, 'msg': self.msg, } - context.update(self.loaded_attributes) + context.update((a, v) for (a, v) in self.loaded_attributes.iteritems() if a in self.to_render) + context.update(self._extra_context()) return context + def _extra_context(self): + """ + Subclasses can override this to return extra context that should be passed to their templates for rendering. + + This is useful when the input type requires computing new template variables from the parsed attributes. + """ + return {} def get_html(self): """ @@ -233,8 +248,7 @@ class InputTypeBase(object): raise NotImplementedError("no rendering template specified for class {0}" .format(self.__class__)) - context = self._default_render_context() - context.update(self._get_render_context()) + context = self._get_render_context() html = self.system.render_template(self.template, context) return etree.XML(html) @@ -255,28 +269,32 @@ class OptionInput(InputTypeBase): template = "optioninput.html" tags = ['optioninput'] + @staticmethod + def parse_options(options): + """ + Given options string, convert it into an ordered list of (option_id, option_description) tuples, where + id==description for now. TODO: make it possible to specify different id and descriptions. + """ + # parse the set of possible options + lexer = shlex.shlex(options[1:-1]) + lexer.quotes = "'" + # Allow options to be separated by whitespace as well as commas + lexer.whitespace = ", " + + # remove quotes + tokens = [x[1:-1] for x in list(lexer)] + + # make list of (option_id, option_description), with description=id + return [(t, t) for t in tokens] + + @classmethod def get_attributes(cls): """ Convert options to a convenient format. """ - - def parse_options(options): - """Given options string, convert it into an ordered list of (option, option) tuples - (Why? I don't know--that's what the template uses at the moment) - """ - # parse the set of possible options - oset = shlex.shlex(options[1:-1]) - oset.quotes = "'" - oset.whitespace = "," - oset = [x[1:-1] for x in list(oset)] - - # make ordered list with (key, value) same - return [(oset[x], oset[x]) for x in range(len(oset))] - - return super(OptionInput, cls).get_attributes() + [ - Attribute('options', transform=parse_options), - Attribute('inline', '')] + return [Attribute('options', transform=cls.parse_options), + Attribute('inline', '')] registry.register(OptionInput) @@ -315,26 +333,22 @@ class ChoiceGroup(InputTypeBase): # value. (VS: would be nice to make this less hackish). if self.tag == 'choicegroup': self.suffix = '' - self.element_type = "radio" + self.html_input_type = "radio" elif self.tag == 'radiogroup': - self.element_type = "radio" + self.html_input_type = "radio" self.suffix = '[]' elif self.tag == 'checkboxgroup': - self.element_type = "checkbox" + self.html_input_type = "checkbox" self.suffix = '[]' else: raise Exception("ChoiceGroup: unexpected tag {0}".format(self.tag)) self.choices = extract_choices(self.xml) - def _get_render_context(self): - context = {'id': self.id, - 'value': self.value, - 'status': self.status, - 'input_type': self.element_type, - 'choices': self.choices, - 'name_array_suffix': self.suffix} - return context + def _extra_context(self): + return {'input_type': self.html_input_type, + 'choices': self.choices, + 'name_array_suffix': self.suffix} def extract_choices(element): ''' @@ -384,33 +398,23 @@ class JavascriptInput(InputTypeBase): template = "javascriptinput.html" tags = ['javascriptinput'] + @classmethod + def get_attributes(cls): + """ + Register the attributes. + """ + return [Attribute('params', None), + Attribute('problem_state', None), + Attribute('display_class', None), + Attribute('display_file', None),] + + def setup(self): # Need to provide a value that JSON can parse if there is no # student-supplied value yet. if self.value == "": self.value = 'null' - self.params = self.xml.get('params') - self.problem_state = self.xml.get('problem_state') - self.display_class = self.xml.get('display_class') - self.display_file = self.xml.get('display_file') - - - def _get_render_context(self): - escapedict = {'"': '"'} - value = saxutils.escape(self.value, escapedict) - msg = saxutils.escape(self.msg, escapedict) - - context = {'id': self.id, - 'params': self.params, - 'display_file': self.display_file, - 'display_class': self.display_class, - 'problem_state': self.problem_state, - 'value': value, - 'evaluation': msg, - } - return context - registry.register(JavascriptInput) @@ -418,51 +422,53 @@ registry.register(JavascriptInput) class TextLine(InputTypeBase): """ - + A text line input. Can do math preview if "math"="1" is specified. """ template = "textline.html" tags = ['textline'] + + @classmethod + def get_attributes(cls): + """ + Register the attributes. + """ + return [ + Attribute('size', None), + + # if specified, then textline is hidden and input id is stored + # in div with name=self.hidden. (TODO: is this functionality used by anyone?) + Attribute('hidden', False), + Attribute('inline', False), + + # Attributes below used in setup(), not rendered directly. + Attribute('math', None, render=False), + # TODO: 'dojs' flag is temporary, for backwards compatibility with 8.02x + Attribute('dojs', None, render=False), + Attribute('preprocessorClassName', None, render=False), + Attribute('preprocessorSrc', None, render=False), + ] + + def setup(self): - self.size = self.xml.get('size') + self.do_math = bool(self.loaded_attributes['math'] or + self.loaded_attributes['dojs']) - # if specified, then textline is hidden and input id is stored - # in div with name=self.hidden. - self.hidden = self.xml.get('hidden', False) - - self.inline = self.xml.get('inline', False) - - # TODO: 'dojs' flag is temporary, for backwards compatibility with 8.02x - self.do_math = bool(self.xml.get('math') or self.xml.get('dojs')) # TODO: do math checking using ajax instead of using js, so # that we only have one math parser. self.preprocessor = None if self.do_math: # Preprocessor to insert between raw input and Mathjax - self.preprocessor = {'class_name': self.xml.get('preprocessorClassName',''), - 'script_src': self.xml.get('preprocessorSrc','')} - if '' in self.preprocessor.values(): + self.preprocessor = {'class_name': self.loaded_attributes['preprocessorClassName'], + 'script_src': self.loaded_attributes['preprocessorSrc']} + if None in self.preprocessor.values(): self.preprocessor = None - - def _get_render_context(self): - # Escape answers with quotes, so they don't crash the system! - escapedict = {'"': '"'} - value = saxutils.escape(self.value, escapedict) - - context = {'id': self.id, - 'value': value, - 'status': self.status, - 'size': self.size, - 'msg': self.msg, - 'hidden': self.hidden, - 'inline': self.inline, - 'do_math': self.do_math, - 'preprocessor': self.preprocessor, - } - return context + def _extra_context(self): + return {'do_math': self.do_math, + 'preprocessor': self.preprocessor,} registry.register(TextLine) @@ -480,13 +486,26 @@ class FileSubmission(InputTypeBase): submitted_msg = ("Your file(s) have been submitted; as soon as your submission is" " graded, this message will be replaced with the grader's feedback.") - def setup(self): - escapedict = {'"': '"'} - self.allowed_files = json.dumps(self.xml.get('allowed_files', '').split()) - self.allowed_files = saxutils.escape(self.allowed_files, escapedict) - self.required_files = json.dumps(self.xml.get('required_files', '').split()) - self.required_files = saxutils.escape(self.required_files, escapedict) + @staticmethod + def parse_files(files): + """ + Given a string like 'a.py b.py c.out', split on whitespace and return as a json list. + """ + return json.dumps(files.split()) + @classmethod + def get_attributes(cls): + """ + Convert the list of allowed files to a convenient format. + """ + return [Attribute('allowed_files', '[]', transform=cls.parse_files), + Attribute('required_files', '[]', transform=cls.parse_files),] + + def setup(self): + """ + Do some magic to handle queueing status (render as "queued" instead of "incomplete"), + pull queue_len from the msg field. (TODO: get rid of the queue_len hack). + """ # Check if problem has been queued self.queue_len = 0 # Flag indicating that the problem has been queued, 'msg' is length of queue @@ -495,15 +514,8 @@ class FileSubmission(InputTypeBase): self.queue_len = self.msg self.msg = FileSubmission.submitted_msg - def _get_render_context(self): - - context = {'id': self.id, - 'status': self.status, - 'msg': self.msg, - 'value': self.value, - 'queue_len': self.queue_len, - 'allowed_files': self.allowed_files, - 'required_files': self.required_files,} + def _extra_context(self): + return {'queue_len': self.queue_len,} return context registry.register(FileSubmission) diff --git a/common/lib/capa/capa/templates/filesubmission.html b/common/lib/capa/capa/templates/filesubmission.html index 2572b25f8a..930469dc0d 100644 --- a/common/lib/capa/capa/templates/filesubmission.html +++ b/common/lib/capa/capa/templates/filesubmission.html @@ -12,7 +12,7 @@ % endif

        ${status}

        - +
      ${msg|n}
      diff --git a/common/lib/capa/capa/templates/javascriptinput.html b/common/lib/capa/capa/templates/javascriptinput.html index 8b4c8f7115..b4d007e4d8 100644 --- a/common/lib/capa/capa/templates/javascriptinput.html +++ b/common/lib/capa/capa/templates/javascriptinput.html @@ -2,7 +2,7 @@
      + data-submission="${value|h}" data-evaluation="${msg|h}">
      diff --git a/common/lib/capa/capa/templates/textline.html b/common/lib/capa/capa/templates/textline.html index 97c512fc00..fbb5467b67 100644 --- a/common/lib/capa/capa/templates/textline.html +++ b/common/lib/capa/capa/templates/textline.html @@ -20,7 +20,7 @@
      % endif - This is foil One.'), ('foil2', 'This is foil Two.'), @@ -119,12 +133,13 @@ class JavascriptInputTest(unittest.TestCase): context = the_input._get_render_context() expected = {'id': 'prob_1_2', + 'status': 'unanswered', + 'msg': '', + 'value': '3', 'params': params, 'display_file': display_file, 'display_class': display_class, - 'problem_state': problem_state, - 'value': '3', - 'evaluation': '',} + 'problem_state': problem_state,} self.assertEqual(context, expected) @@ -204,9 +219,6 @@ class FileSubmissionTest(unittest.TestCase): element = etree.fromstring(xml_str) - escapedict = {'"': '"'} - esc = lambda s: saxutils.escape(s, escapedict) - state = {'value': 'BumbleBee.py', 'status': 'incomplete', 'feedback' : {'message': '3'}, } @@ -220,8 +232,8 @@ class FileSubmissionTest(unittest.TestCase): 'msg': input_class.submitted_msg, 'value': 'BumbleBee.py', 'queue_len': '3', - 'allowed_files': esc('["runme.py", "nooooo.rb", "ohai.java"]'), - 'required_files': esc('["cookies.py"]')} + 'allowed_files': '["runme.py", "nooooo.rb", "ohai.java"]', + 'required_files': '["cookies.py"]'} self.assertEqual(context, expected) From b78fb8dff665cb15de2c378440d305d217c79456 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 1 Nov 2012 00:05:48 -0400 Subject: [PATCH 060/228] Refactored the rest of the input types --- common/lib/capa/capa/inputtypes.py | 218 ++++++++---------- .../capa/capa/templates/crystallography.html | 2 +- .../lib/capa/capa/templates/vsepr_input.html | 2 +- common/lib/capa/capa/tests/test_inputtypes.py | 18 +- 4 files changed, 110 insertions(+), 130 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index bd3642220f..2ff926479a 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -21,12 +21,14 @@ Each input type takes the xml tree as 'element', the previous answer as 'value', graded status as'status' """ -# TODO: there is a lot of repetitive "grab these elements from xml attributes, with these defaults, -# put them in the context" code. Refactor so class just specifies required and optional attrs (with -# defaults for latter), and InputTypeBase does the right thing. +# TODO: make hints do something + +# TODO: make all inputtypes actually render msg + +# TODO: remove unused fields (e.g. 'hidden' in a few places) + +# TODO: add validators so that content folks get better error messages. -# TODO: Quoting and unquoting is handled in a pretty ad-hoc way. Also something that could be done -# properly once in InputTypeBase. # Possible todo: make inline the default for textlines and other "one-line" inputs. It probably # makes sense, but a bunch of problems have markup that assumes block. Bigger TODO: figure out a @@ -39,7 +41,6 @@ from lxml import etree import re import shlex # for splitting quoted strings import sys -import xml.sax.saxutils as saxutils from registry import TagRegistry @@ -535,13 +536,30 @@ class CodeInput(InputTypeBase): # non-codemirror editor. ] + # pulled out for testing + submitted_msg = ("Your file(s) have been submitted; as soon as your submission is" + " graded, this message will be replaced with the grader's feedback.") + + @classmethod + def get_attributes(cls): + """ + Convert options to a convenient format. + """ + return [Attribute('rows', '30'), + Attribute('cols', '80'), + Attribute('hidden', ''), + + # For CodeMirror + Attribute('mode', 'python'), + Attribute('linenumbers', 'true'), + # Template expects tabsize to be an int it can do math with + Attribute('tabsize', 4, transform=int), + ] def setup(self): - self.rows = self.xml.get('rows') or '30' - self.cols = self.xml.get('cols') or '80' - # if specified, then textline is hidden and id is stored in div of name given by hidden - self.hidden = self.xml.get('hidden', '') - + """ + Implement special logic: handle queueing state, and default input. + """ # if no student input yet, then use the default input given by the problem if not self.value: self.value = self.xml.text @@ -552,28 +570,11 @@ class CodeInput(InputTypeBase): if self.status == 'incomplete': self.status = 'queued' self.queue_len = self.msg - self.msg = 'Submitted to grader.' + self.msg = self.submitted_msg - # For CodeMirror - self.mode = self.xml.get('mode', 'python') - self.linenumbers = self.xml.get('linenumbers', 'true') - self.tabsize = int(self.xml.get('tabsize', '4')) - - def _get_render_context(self): - - context = {'id': self.id, - 'value': self.value, - 'status': self.status, - 'msg': self.msg, - 'mode': self.mode, - 'linenumbers': self.linenumbers, - 'rows': self.rows, - 'cols': self.cols, - 'hidden': self.hidden, - 'tabsize': self.tabsize, - 'queue_len': self.queue_len, - } - return context + def _extra_context(self): + """Defined queue_len, add it """ + return {'queue_len': self.queue_len,} registry.register(CodeInput) @@ -586,26 +587,19 @@ class Schematic(InputTypeBase): template = "schematicinput.html" tags = ['schematic'] - def setup(self): - self.height = self.xml.get('height') - self.width = self.xml.get('width') - self.parts = self.xml.get('parts') - self.analyses = self.xml.get('analyses') - self.initial_value = self.xml.get('initial_value') - self.submit_analyses = self.xml.get('submit_analyses') + @classmethod + def get_attributes(cls): + """ + Convert options to a convenient format. + """ + return [ + Attribute('height', None), + Attribute('width', None), + Attribute('parts', None), + Attribute('analyses', None), + Attribute('initial_value', None), + Attribute('submit_analyses', None),] - - def _get_render_context(self): - - context = {'id': self.id, - 'value': self.value, - 'initial_value': self.initial_value, - 'status': self.status, - 'width': self.width, - 'height': self.height, - 'parts': self.parts, - 'analyses': self.analyses, - 'submit_analyses': self.submit_analyses,} return context registry.register(Schematic) @@ -626,12 +620,20 @@ class ImageInput(InputTypeBase): template = "imageinput.html" tags = ['imageinput'] - def setup(self): - self.src = self.xml.get('src') - self.height = self.xml.get('height') - self.width = self.xml.get('width') + @classmethod + def get_attributes(cls): + """ + Note: src, height, and width are all required. + """ + return [Attribute('src'), + Attribute('height'), + Attribute('width'),] - # if value is of the form [x,y] then parse it and send along coordinates of previous answer + + def setup(self): + """ + if value is of the form [x,y] then parse it and send along coordinates of previous answer + """ m = re.match('\[([0-9]+),([0-9]+)]', self.value.strip().replace(' ', '')) if m: # Note: we subtract 15 to compensate for the size of the dot on the screen. @@ -641,19 +643,10 @@ class ImageInput(InputTypeBase): (self.gx, self.gy) = (0, 0) - def _get_render_context(self): + def _extra_context(self): - context = {'id': self.id, - 'value': self.value, - 'height': self.height, - 'width': self.width, - 'src': self.src, - 'gx': self.gx, - 'gy': self.gy, - 'status': self.status, - 'msg': self.msg, - } - return context + return {'gx': self.gx, + 'gy': self.gy} registry.register(ImageInput) @@ -669,30 +662,18 @@ class Crystallography(InputTypeBase): template = "crystallography.html" tags = ['crystallography'] + @classmethod + def get_attributes(cls): + """ + Note: height, width are required. + """ + return [Attribute('size', None), + Attribute('height'), + Attribute('width'), - def setup(self): - self.height = self.xml.get('height') - self.width = self.xml.get('width') - self.size = self.xml.get('size') - - # if specified, then textline is hidden and id is stored in div of name given by hidden - self.hidden = self.xml.get('hidden', '') - - # Escape answers with quotes, so they don't crash the system! - escapedict = {'"': '"'} - self.value = saxutils.escape(self.value, escapedict) - - def _get_render_context(self): - context = {'id': self.id, - 'value': self.value, - 'status': self.status, - 'size': self.size, - 'msg': self.msg, - 'hidden': self.hidden, - 'width': self.width, - 'height': self.height, - } - return context + # can probably be removed (textline should prob be always-hidden) + Attribute('hidden', ''), + ] registry.register(Crystallography) @@ -707,29 +688,16 @@ class VseprInput(InputTypeBase): template = 'vsepr_input.html' tags = ['vsepr_input'] - def setup(self): - self.height = self.xml.get('height') - self.width = self.xml.get('width') - - # Escape answers with quotes, so they don't crash the system! - escapedict = {'"': '"'} - self.value = saxutils.escape(self.value, escapedict) - - self.molecules = self.xml.get('molecules') - self.geometries = self.xml.get('geometries') - - def _get_render_context(self): - - context = {'id': self.id, - 'value': self.value, - 'status': self.status, - 'msg': self.msg, - 'width': self.width, - 'height': self.height, - 'molecules': self.molecules, - 'geometries': self.geometries, - } - return context + @classmethod + def get_attributes(cls): + """ + Note: height, width are required. + """ + return [Attribute('height'), + Attribute('width'), + Attribute('molecules'), + Attribute('geometries'), + ] registry.register(VseprInput) @@ -750,17 +718,17 @@ class ChemicalEquationInput(InputTypeBase): template = "chemicalequationinput.html" tags = ['chemicalequationinput'] - def setup(self): - self.size = self.xml.get('size', '20') + @classmethod + def get_attributes(cls): + """ + Can set size of text field. + """ + return [Attribute('size', '20'),] - def _get_render_context(self): - context = { - 'id': self.id, - 'value': self.value, - 'status': self.status, - 'size': self.size, - 'previewer': '/static/js/capa/chemical_equation_preview.js', - } - return context + def _extra_context(self): + """ + TODO (vshnayder): Get rid of this once we have a standard way of requiring js to be loaded. + """ + return {'previewer': '/static/js/capa/chemical_equation_preview.js',} registry.register(ChemicalEquationInput) diff --git a/common/lib/capa/capa/templates/crystallography.html b/common/lib/capa/capa/templates/crystallography.html index f46e2f753a..2370f59dd2 100644 --- a/common/lib/capa/capa/templates/crystallography.html +++ b/common/lib/capa/capa/templates/crystallography.html @@ -19,7 +19,7 @@
      % endif - % endif - diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 5fbe73503f..826d304717 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -2,9 +2,18 @@ Tests of input types. TODO: +- refactor: so much repetive code (have factory methods that build xml elements directly, etc) + +- test error cases + +- check rendering -- e.g. msg should appear in the rendered output. If possible, test that + templates are escaping things properly. + + - test unicode in values, parameters, etc. - test various html escapes - test funny xml chars -- should never get xml parse error if things are escaped properly. + """ from lxml import etree @@ -267,14 +276,15 @@ class CodeInputTest(unittest.TestCase): 'status': 'incomplete', 'feedback' : {'message': '3'}, } - the_input = lookup_tag('codeinput')(test_system, element, state) + input_class = lookup_tag('codeinput') + the_input = input_class(test_system, element, state) context = the_input._get_render_context() expected = {'id': 'prob_1_2', 'value': 'print "good evening"', 'status': 'queued', - 'msg': 'Submitted to grader.', + 'msg': input_class.submitted_msg, 'mode': mode, 'linenumbers': linenumbers, 'rows': rows, @@ -323,8 +333,9 @@ class SchematicTest(unittest.TestCase): expected = {'id': 'prob_1_2', 'value': value, - 'initial_value': initial_value, 'status': 'unsubmitted', + 'msg': '', + 'initial_value': initial_value, 'width': width, 'height': height, 'parts': parts, @@ -488,6 +499,7 @@ class ChemicalEquationTest(unittest.TestCase): expected = {'id': 'prob_1_2', 'value': 'H2OYeah', 'status': 'unanswered', + 'msg': '', 'size': size, 'previewer': '/static/js/capa/chemical_equation_preview.js', } From fa2cf6a49c31101f86395f6b1fe81b41f7c94bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 1 Nov 2012 08:22:33 +0200 Subject: [PATCH 061/228] add convert_to_perepherial flag and removed order flags --- common/lib/capa/capa/chem/chemtools.py | 42 +++++++++----------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/common/lib/capa/capa/chem/chemtools.py b/common/lib/capa/capa/chem/chemtools.py index e5a8e1a6f6..3f6100cfff 100644 --- a/common/lib/capa/capa/chem/chemtools.py +++ b/common/lib/capa/capa/chem/chemtools.py @@ -16,12 +16,11 @@ def vsepr_build_correct_answer(geometry, atoms): return correct_answer -def vsepr_grade(user_input, correct_answer, ignore_p_order=False, ignore_a_order=False, ignore_e_order=False): - """ Flags ignore_(a,p,e)_order are for checking order in axial, perepherial or equatorial positions. +def vsepr_grade(user_input, correct_answer, convert_to_perepherial=False): + """ Allowed cases: c0, a, e c0, p - Not implemented and not tested cases when p with a or e (no need for now) """ # print user_input, type(user_input) # print correct_answer, type(correct_answer) @@ -31,31 +30,20 @@ def vsepr_grade(user_input, correct_answer, ignore_p_order=False, ignore_a_order if user_input['atoms']['c0'] != correct_answer['atoms']['c0']: return False - # not order-aware comparisons - for ignore in [(ignore_p_order, 'p'), (ignore_e_order, 'e'), (ignore_a_order, 'a')]: - if ignore[0]: - # collecting atoms: - a_user = [v for k, v in user_input['atoms'].items() if k.startswith(ignore[1])] - a_correct = [v for k, v in correct_answer['atoms'].items() if k.startswith(ignore[1])] - # print ignore[0], ignore[1], a_user, a_correct - if len(a_user) != len(a_correct): - return False - if sorted(a_user) != sorted(a_correct): - return False + if convert_to_perepherial: + # convert user_input from (a,e,e1,e2) to (p) + # correct_answer must be set in (p) using this flag + user_input['atoms'] = {'p' + str(i): v for i, v in enumerate(user_input['atoms'].values())} - # order-aware comparisons - for ignore in [(ignore_p_order, 'p'), (ignore_e_order, 'e'), (ignore_a_order, 'a')]: - if not ignore[0]: - # collecting atoms: - a_user = [v for k, v in user_input['atoms'].items() if k.startswith(ignore[1])] - a_correct = [v for k, v in correct_answer['atoms'].items() if k.startswith(ignore[1])] - # print '2nd', ignore[0], ignore[1], a_user, a_correct - if len(a_user) != len(a_correct): - return False - if len(a_correct) == 0: - continue - if a_user != a_correct: - return False + for ea_position in ['p', 'a', 'e', 'e1', 'e2']: + # collecting atoms: + a_user = [v for k, v in user_input['atoms'].items() if k.startswith(ea_position)] + a_correct = [v for k, v in correct_answer['atoms'].items() if k.startswith(ea_position)] + # print a_user, a_correct + if len(a_user) != len(a_correct): + return False + if sorted(a_user) != sorted(a_correct): + return False return True From 8a453e99ca826fe948c18ca732f6f67a4898ccc0 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Thu, 1 Nov 2012 04:36:54 -0400 Subject: [PATCH 062/228] Implement basic ability to close off a forum for a given course such that Students can no longer post or edit, but can still view. --- common/lib/xmodule/xmodule/course_module.py | 16 ++++++++++++++++ lms/djangoapps/django_comment_client/models.py | 7 +++++++ .../_discussion_course_navigation.html | 3 +++ lms/templates/discussion/_discussion_module.html | 2 ++ .../discussion/_underscore_templates.html | 6 ++++++ 5 files changed, 34 insertions(+) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index de8eddd0b8..64e947a5a1 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -276,6 +276,22 @@ class CourseDescriptor(SequenceDescriptor): more sensible framework later.""" return self.metadata.get('discussion_link', None) + @property + def forum_posts_allowed(self): + try: + blackout_periods = [(parse_time(start), parse_time(end)) + for start, end + in self.metadata.get('discussion_blackouts', [])] + now = time.gmtime() + for start, end in blackout_periods: + if start <= now <= end: + return False + except: + log.exception("Error parsing discussion_blackouts for course {0}".format(self.id)) + raise + + return True + @property def hide_progress_tab(self): """TODO: same as above, intended to let internal CS50 hide the progress tab diff --git a/lms/djangoapps/django_comment_client/models.py b/lms/djangoapps/django_comment_client/models.py index 605a731517..ff2146afac 100644 --- a/lms/djangoapps/django_comment_client/models.py +++ b/lms/djangoapps/django_comment_client/models.py @@ -2,6 +2,7 @@ from django.db import models from django.contrib.auth.models import User import logging +from courseware.courses import get_course_by_id class Role(models.Model): name = models.CharField(max_length=30, null=False, blank=False) @@ -23,6 +24,12 @@ class Role(models.Model): self.permissions.add(Permission.objects.get_or_create(name=permission)[0]) def has_permission(self, permission): + course = get_course_by_id(self.course_id) + if self.name == "Student" and \ + (permission.startswith('edit') or permission.startswith('update') or permission.startswith('create')) and \ + (not course.forum_posts_allowed): + return False + return self.permissions.filter(name=permission).exists() diff --git a/lms/templates/discussion/_discussion_course_navigation.html b/lms/templates/discussion/_discussion_course_navigation.html index 13b291196b..d770cacc96 100644 --- a/lms/templates/discussion/_discussion_course_navigation.html +++ b/lms/templates/discussion/_discussion_course_navigation.html @@ -1,5 +1,8 @@ +<%! from django_comment_client.permissions import has_permission %> <%inherit file="../courseware/course_navigation.html" /> <%block name="extratabs"> +% if has_permission(user, 'create_thread', course.id):
    • New Post
    • +% endif \ No newline at end of file diff --git a/lms/templates/discussion/_discussion_module.html b/lms/templates/discussion/_discussion_module.html index bb172093f6..8fced92f7f 100644 --- a/lms/templates/discussion/_discussion_module.html +++ b/lms/templates/discussion/_discussion_module.html @@ -3,4 +3,6 @@ diff --git a/lms/templates/discussion/_underscore_templates.html b/lms/templates/discussion/_underscore_templates.html index 0a691ac36f..be238811c2 100644 --- a/lms/templates/discussion/_underscore_templates.html +++ b/lms/templates/discussion/_underscore_templates.html @@ -1,3 +1,5 @@ +<%! from django_comment_client.permissions import has_permission %> + @@ -75,6 +79,7 @@
      1. + % if course is UNDEFINED or has_permission(user, 'create_sub_comment', course.id):
          Submit
          + % endif
        From 8f756f030476d862c34322cc35a63152ddc2b54d Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Thu, 1 Nov 2012 04:45:00 -0400 Subject: [PATCH 063/228] Document advertised_start and discussion_blackouts attributes of course --- doc/xml-format.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/xml-format.md b/doc/xml-format.md index d9c0d27653..0491641903 100644 --- a/doc/xml-format.md +++ b/doc/xml-format.md @@ -250,9 +250,11 @@ Values are dictionaries of the form {"metadata-key" : "metadata-value"}. Supported fields at the course level: * "start" -- specify the start date for the course. Format-by-example: "2012-09-05T12:00". +* "advertised_start" -- specify what you want displayed as the start date of the course in the course listing and course about pages. This can be useful if you want to let people in early before the formal start. Format-by-example: "2012-09-05T12:00". * "enrollment_start", "enrollment_end" -- when can students enroll? (if not specified, can enroll anytime). Same format as "start". * "end" -- specify the end date for the course. Format-by-example: "2012-11-05T12:00". * "tabs" -- have custom tabs in the courseware. See below for details on config. +* "discussion_blackouts" -- An array of time intervals during which you want to disable a student's ability to create or edit posts in the forum. Moderators, Community TAs, and Admins are unaffected. You might use this during exam periods, but please be aware that the forum is often a very good place to catch mistakes and clarify points to students. The better long term solution would be to have better flagging/moderation mechanisms, but this is the hammer we have today. Format by example: [["2012-10-29T04:00", "2012-11-03T04:00"], ["2012-12-30T04:00", "2013-01-02T04:00"]] * TODO: there are others ### Grading policy file contents From ddc4f705e0d02892f42ef8e28f1d03fe134e31f8 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Thu, 1 Nov 2012 04:49:45 -0400 Subject: [PATCH 064/228] Remove raise that was there just for debugging purposes --- common/lib/xmodule/xmodule/course_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 64e947a5a1..fc27a692ea 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -288,7 +288,6 @@ class CourseDescriptor(SequenceDescriptor): return False except: log.exception("Error parsing discussion_blackouts for course {0}".format(self.id)) - raise return True From 91160616849026ed9e83e3bb2594a37bd0f13634 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 1 Nov 2012 09:53:53 -0400 Subject: [PATCH 065/228] fixing javascript ajax path issues --- .../xmodule/js/src/capa/display.coffee | 1 + .../js/src/selfassessment/display.coffee | 159 ++---------------- .../xmodule/xmodule/self_assessment_module.py | 32 +++- 3 files changed, 43 insertions(+), 149 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.coffee b/common/lib/xmodule/xmodule/js/src/capa/display.coffee index 1c0ace9e59..6a3c719bb3 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/capa/display.coffee @@ -209,6 +209,7 @@ class @Problem show: => if !@el.hasClass 'showed' Logger.log 'problem_show', problem: @id + alert(@url) $.postWithPrefix "#{@url}/problem_show", (response) => answers = response.answers $.each answers, (key, value) => diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index 91582642b8..439f518590 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -1,148 +1,25 @@ -class @SelfAssessment - constructor: (element) -> - @el = $(element).find('.sa-wrapper') - @id = @el.data('sa-id') - @element_id = @el.attr('id') - @url = @el.data('url') - @render() +#@$('section.action input.check').click @check +$('input#show').click(@show) +#$('#save').click(function -> alert('save')) - $: (selector) -> - $(selector, @el) +$(document).on('click', 'input#save', ( -> + answer=$('#answer').text() + $.postWithPrefix "modx/6.002x/sa_save", answer, (response) -> + if response.success + $('p.rubric').replace(response.rubric) - bind: => + alert("save") +)); - window.update_schematics() +show: => + alert("show") + #$.postWithPrefix "/sa_show", (response) => + # answers = response.answers + # @el.addClass 'showed' - problem_prefix = @element_id.replace(/sa_/,'') - @inputs = @$("[id^=input_#{problem_prefix}_]") - - @$('input:button').click @refreshAnswers - #@$('section.action input.check').click @check - @$('input.show').click @show - @$('input.save').click @save - - # Collapsibles - Collapsible.setCollapsibles(@el) - - # Dynamath - @$('input.math').keyup(@refreshMath) - @$('input.math').each (index, element) => - MathJax.Hub.Queue [@refreshMath, null, element] - - updateProgress: (response) => - if response.progress_changed - @el.attr progress: response.progress_status - @el.trigger('progressChanged') - - show: => - $.postWithPrefix "#{@url}/sa_show", (response) => - answers = response.answers - @el.addClass 'showed' - - save: => - $.postWithPrefix "/sa_save", @answers, (response) => - if response.success - @$('p.rubric').replace(response.rubric) - - render: (content) -> - if content - @el.html(content) - JavascriptLoader.executeModuleScripts @el, () => - @setupInputTypes() - @bind() - else - $.postWithPrefix "#{@url}/problem_get", (response) => - @el.html(response.html) - JavascriptLoader.executeModuleScripts @el, () => - @setupInputTypes() - @bind() - - setupInputTypes: => - @inputtypeDisplays = {} - @el.find(".capa_inputtype").each (index, inputtype) => - classes = $(inputtype).attr('class').split(' ') - id = $(inputtype).attr('id') - for cls in classes - setupMethod = @inputtypeSetupMethods[cls] - if setupMethod? - @inputtypeDisplays[id] = setupMethod(inputtype) - - gentle_alert: (msg) => - if @el.find('.capa_alert').length - @el.find('.capa_alert').remove() - alert_elem = "
        " + msg + "
        " - @el.find('.action').after(alert_elem) - @el.find('.capa_alert').css(opacity: 0).animate(opacity: 1, 700) - - refreshAnswers: => - @$('input.schematic').each (index, element) -> - element.schematic.update_value() - @$(".CodeMirror").each (index, element) -> - element.CodeMirror.save() if element.CodeMirror.save - @answers = @inputs.serialize() - - inputtypeSetupMethods: - - 'text-input-dynamath': (element) => - ### - Return: function (eqn) -> eqn that preprocesses the user formula input before - it is fed into MathJax. Return 'false' if no preprocessor specified - ### - data = $(element).find('.text-input-dynamath_data') - - preprocessorClassName = data.data('preprocessor') - preprocessorClass = window[preprocessorClassName] - if not preprocessorClass? - return false - else - preprocessor = new preprocessorClass() - return preprocessor.fn - - javascriptinput: (element) => - - data = $(element).find(".javascriptinput_data") - - params = data.data("params") - submission = data.data("submission") - evaluation = data.data("evaluation") - problemState = data.data("problem_state") - displayClass = window[data.data('display_class')] - - if evaluation == '' - evaluation = null - - container = $(element).find(".javascriptinput_container") - submissionField = $(element).find(".javascriptinput_input") - - display = new displayClass(problemState, submission, evaluation, container, submissionField, params) - display.render() - - return display - - inputtypeShowAnswerMethods: - choicegroup: (element, display, answers) => - element = $(element) - - element.find('input').attr('disabled', 'disabled') - - input_id = element.attr('id').replace(/inputtype_/,'') - answer = answers[input_id] - for choice in answer - element.find("label[for='input_#{input_id}_#{choice}']").addClass 'choicegroup_correct' - - javascriptinput: (element, display, answers) => - answer_id = $(element).attr('id').split("_")[1...].join("_") - answer = JSON.parse(answers[answer_id]) - display.showAnswer(answer) - - inputtypeHideAnswerMethods: - choicegroup: (element, display) => - element = $(element) - element.find('input').attr('disabled', null) - element.find('label').removeClass('choicegroup_correct') - - javascriptinput: (element, display) => - display.hideAnswer() +save: => + alert("save") +alert($('input#save').attr('url')) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 56ac0d7911..bdabd9ec6e 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -19,9 +19,6 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent log = logging.getLogger("mitx.courseware") -problem_form=('

        ') - rubric_form=('
        Correct
        ' '' 'Incorrect
        ') @@ -61,6 +58,11 @@ class SelfAssessmentModule(XModule): self.rubric=''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))]) self.problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))]) + problem_form=('

        ' + '' + '

        ').format(self.location) + self.problem=''.join([self.problem,problem_form]) self.rubric=''.join([self.rubric,rubric_form]) @@ -70,6 +72,9 @@ class SelfAssessmentModule(XModule): self.html = self.problem + self.answers={} + + def handle_ajax(self, dispatch, get): ''' This is called by courseware.module_render, to handle an AJAX call. @@ -106,16 +111,27 @@ class SelfAssessmentModule(XModule): with the error key only present if success is False. ''' event_info = dict() - event_info['state'] = self.lcp.get_state() + event_info['state'] = {'seed': 1, + 'student_answers': self.answers, + 'correct_map': {'self_assess' : {'correctness': False, + 'npoints': 0, + 'msg': "", + 'hint': "", + 'hintmode': "", + 'queuestate': "", + }}, + 'done': True} + event_info['problem_id'] = self.location.url() + answers = self.make_dict_of_responses(get) + event_info['answers'] = answers + + self.system.track_function('save_problem_succeed', event_info) + return {'success': True, 'rubric' : self.rubric} - - - - class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): """ Module for putting raw html in a course From 2203a0bd7031da2e7d96bb20b5724e8082588528 Mon Sep 17 00:00:00 2001 From: Aaron Culich Date: Thu, 1 Nov 2012 14:05:11 +0000 Subject: [PATCH 066/228] Updated APT_PKGS with valid Ubuntu package name: mysql -> mysql-server --- create-dev-env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/create-dev-env.sh b/create-dev-env.sh index 656a40524e..c872f1dcbc 100755 --- a/create-dev-env.sh +++ b/create-dev-env.sh @@ -105,7 +105,7 @@ NUMPY_VER="1.6.2" SCIPY_VER="0.10.1" BREW_FILE="$BASE/mitx/brew-formulas.txt" LOG="/var/tmp/install-$(date +%Y%m%d-%H%M%S).log" -APT_PKGS="pkg-config curl git python-virtualenv build-essential python-dev gfortran liblapack-dev libfreetype6-dev libpng12-dev libxml2-dev libxslt-dev yui-compressor coffeescript graphviz graphviz-dev mysql" +APT_PKGS="pkg-config curl git python-virtualenv build-essential python-dev gfortran liblapack-dev libfreetype6-dev libpng12-dev libxml2-dev libxslt-dev yui-compressor coffeescript graphviz graphviz-dev mysql-server" if [[ $EUID -eq 0 ]]; then error "This script should not be run using sudo or as the root user" From 4712a039dc25214369eb26be8942874667279227 Mon Sep 17 00:00:00 2001 From: Aaron Culich Date: Thu, 1 Nov 2012 14:06:03 +0000 Subject: [PATCH 067/228] Added DEBIAN_FRONTEND=noninteractive to sudo apt-get which is required for silent mysql-server installation --- create-dev-env.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/create-dev-env.sh b/create-dev-env.sh index c872f1dcbc..96ec51d928 100755 --- a/create-dev-env.sh +++ b/create-dev-env.sh @@ -193,7 +193,8 @@ case `uname -s` in maya|lisa|natty|oneiric|precise|quantal) output "Installing ubuntu requirements" sudo apt-get -y update - sudo apt-get -y install $APT_PKGS + # DEBIAN_FRONTEND=noninteractive is required for silent mysql-server installation + sudo DEBIAN_FRONTEND=noninteractive apt-get -y install $APT_PKGS clone_repos ;; *) From 40ce5d2ff61861955a1f31ff3fb73d42729030e7 Mon Sep 17 00:00:00 2001 From: Aaron Culich Date: Thu, 1 Nov 2012 14:08:24 +0000 Subject: [PATCH 068/228] Fixed dependency problem introduced by MySQL-python by forcing distribute version >= 0.6.28 in pre-requirements --- pre-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/pre-requirements.txt b/pre-requirements.txt index 24ce15ab7e..6eb4669059 100644 --- a/pre-requirements.txt +++ b/pre-requirements.txt @@ -1 +1,2 @@ numpy +distribute>=0.6.28 From 5755325a41707eaaeed130a34b5b42f3a655cb21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 1 Nov 2012 16:26:54 +0200 Subject: [PATCH 069/228] changed syntax to support e1,e2 in AX6 and add convert_to_p behavior --- common/lib/capa/capa/chem/chemtools.py | 59 +++++++++++++++++--------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/common/lib/capa/capa/chem/chemtools.py b/common/lib/capa/capa/chem/chemtools.py index 3f6100cfff..ea40fb7cdf 100644 --- a/common/lib/capa/capa/chem/chemtools.py +++ b/common/lib/capa/capa/chem/chemtools.py @@ -1,19 +1,13 @@ -from collections import OrderedDict import json import unittest - +import itertools def vsepr_parse_user_answer(user_input): - d = OrderedDict(json.loads(user_input)) - d['atoms'] = OrderedDict(sorted(d['atoms'].items())) - return d + return json.loads(user_input) def vsepr_build_correct_answer(geometry, atoms): - correct_answer = OrderedDict() - correct_answer['geometry'] = geometry - correct_answer['atoms'] = OrderedDict(sorted(atoms.items())) - return correct_answer + return {'geometry': geometry, 'atoms': atoms} def vsepr_grade(user_input, correct_answer, convert_to_perepherial=False): @@ -22,6 +16,7 @@ def vsepr_grade(user_input, correct_answer, convert_to_perepherial=False): c0, a, e c0, p """ + # import ipdb; ipdb.set_trace() # print user_input, type(user_input) # print correct_answer, type(correct_answer) if user_input['geometry'] != correct_answer['geometry']: @@ -33,19 +28,45 @@ def vsepr_grade(user_input, correct_answer, convert_to_perepherial=False): if convert_to_perepherial: # convert user_input from (a,e,e1,e2) to (p) # correct_answer must be set in (p) using this flag + c0 = user_input['atoms'].pop('c0') user_input['atoms'] = {'p' + str(i): v for i, v in enumerate(user_input['atoms'].values())} + user_input['atoms']['c0'] = c0 - for ea_position in ['p', 'a', 'e', 'e1', 'e2']: - # collecting atoms: - a_user = [v for k, v in user_input['atoms'].items() if k.startswith(ea_position)] - a_correct = [v for k, v in correct_answer['atoms'].items() if k.startswith(ea_position)] - # print a_user, a_correct - if len(a_user) != len(a_correct): - return False - if sorted(a_user) != sorted(a_correct): - return False + # special case for AX6 + if 'e10' in correct_answer['atoms']: # need check e1x, e2x symmetry for AX6.. + a_user = {} + a_correct = {} + for ea_position in ['a', 'e1', 'e2']: # collecting positions: + a_user[ea_position] = [v for k, v in user_input['atoms'].items() if k.startswith(ea_position)] + a_correct[ea_position] = [v for k, v in correct_answer['atoms'].items() if k.startswith(ea_position)] - return True + correct = [sorted(a_correct['a'])] + [sorted(a_correct['e1'])] + [sorted(a_correct['e2'])] + for permutation in itertools.permutations(['a', 'e1', 'e2']): + if correct == [sorted(a_user[permutation[0]])] + [sorted(a_user[permutation[1]])] + [sorted(a_user[permutation[2]])]: + return True + return False + + else: # no need to checl e1x,e2x symmetry - convert them to ex + if 'e10' in user_input['atoms']: # e1x exists, it is AX6.. case + e_index = 0 + for k, v in user_input['atoms'].items(): + if len(k) == 3: # e1x + del user_input['atoms'][k] + user_input['atoms']['e' + str(e_index)] = v + e_index += 1 + + # common case + for ea_position in ['p', 'a', 'e']: + # collecting atoms: + a_user = [v for k, v in user_input['atoms'].items() if k.startswith(ea_position)] + a_correct = [v for k, v in correct_answer['atoms'].items() if k.startswith(ea_position)] + # print a_user, a_correct + if len(a_user) != len(a_correct): + return False + if sorted(a_user) != sorted(a_correct): + return False + + return True class Test_Grade(unittest.TestCase): From 3f29b74b205a60ad07e0b3a2dd59f11d7e637e53 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 1 Nov 2012 10:31:13 -0400 Subject: [PATCH 070/228] working on js --- .../js/src/selfassessment/display.coffee | 36 ++++++++++++++----- .../xmodule/xmodule/self_assessment_module.py | 21 ++++++----- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index 439f518590..9da875aaa1 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -1,11 +1,33 @@ +$(document).on('click', 'section.sa-wrapper input#show', ( -> + root = location.protocol + "//" + location.host + post_url=$('section.sa-wrapper input#show').attr('url') + alert(post_url) + final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_show" + alert(final_url) -#@$('section.action input.check').click @check -$('input#show').click(@show) -#$('#save').click(function -> alert('save')) + $.post final_url, answer, (response) -> + if response.success + $('p.rubric').replace(response.rubric) -$(document).on('click', 'input#save', ( -> - answer=$('#answer').text() - $.postWithPrefix "modx/6.002x/sa_save", answer, (response) -> + alert("save") +)); + +$(document).on('click', 'section.sa-wrapper input#save', ( -> + answer=$('section.sa-wrapper input#answer').val() + alert(answer) + assessment=0 + assessment_correct=$('section.sa-wrapper input#assessment_correct').val() + alert(assessment_correct) + assessment_incorrect=$('section.sa-wrapper input#assessment_incorrect').val() + alert(assessment_incorrect) + + root = location.protocol + "//" + location.host + post_url=$('section.sa-wrapper input#show').attr('url') + alert(post_url) + final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_save" + alert(final_url) + + $.post final_url, answer, (response) -> if response.success $('p.rubric').replace(response.rubric) @@ -21,5 +43,3 @@ show: => save: => alert("save") -alert($('input#save').attr('url')) - diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index bdabd9ec6e..06647b9af1 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -19,9 +19,9 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent log = logging.getLogger("mitx.courseware") -rubric_form=('
        Correct
        ' - '' - 'Incorrect
        ') +rubric_form=('
        Correct
        ' + '' + 'Incorrect
        ') def only_one(lst, default="", process=lambda x: x): """ @@ -58,9 +58,9 @@ class SelfAssessmentModule(XModule): self.rubric=''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))]) self.problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))]) - problem_form=('

        ' - '' + '' '

        ').format(self.location) self.problem=''.join([self.problem,problem_form]) @@ -87,7 +87,6 @@ class SelfAssessmentModule(XModule): ''' handlers = { - 'sa_get' : self.show_problem, 'sa_show': self.show_rubric, 'sa_save': self.save_problem, } @@ -104,6 +103,12 @@ class SelfAssessmentModule(XModule): }) return json.dumps(d, cls=ComplexEncoder) + + + def show_rubric: + return {'success': True} + + def save_problem(self, get): ''' Save the passed in answers. @@ -129,7 +134,7 @@ class SelfAssessmentModule(XModule): self.system.track_function('save_problem_succeed', event_info) - return {'success': True, 'rubric' : self.rubric} + return {'success': True} class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): @@ -144,8 +149,6 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): has_score = True template_dir_name = "html" - - js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]} js_module_name = "HTMLEditingDescriptor" From e11e5429a17fc04d48f8e6aaa5c67df0fa0b4e53 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 1 Nov 2012 10:40:42 -0400 Subject: [PATCH 071/228] adding in progress functionality --- .../js/src/selfassessment/display.coffee | 19 ++++--------- .../xmodule/xmodule/self_assessment_module.py | 28 +++++++++++++++++-- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index 9da875aaa1..20301e48c7 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -1,13 +1,14 @@ $(document).on('click', 'section.sa-wrapper input#show', ( -> root = location.protocol + "//" + location.host post_url=$('section.sa-wrapper input#show').attr('url') - alert(post_url) final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_show" - alert(final_url) + answer=$('section.sa-wrapper input#answer').val() $.post final_url, answer, (response) -> - if response.success - $('p.rubric').replace(response.rubric) + alert("posted") + if response.success + alert(response.rubric) + $('section.sa-wrapper p#rubric').replace(response.rubric) alert("save") )); @@ -33,13 +34,3 @@ $(document).on('click', 'section.sa-wrapper input#save', ( -> alert("save") )); - -show: => - alert("show") - #$.postWithPrefix "/sa_show", (response) => - # answers = response.answers - # @el.addClass 'showed' - -save: => - alert("save") - diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 06647b9af1..9f56a07c83 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -74,6 +74,30 @@ class SelfAssessmentModule(XModule): self.answers={} + self.score=0 + + self.max_score=1 + + def get_score(self): + return self.score + + def max_score(self): + return self.max_score + + def get_progress(self): + ''' For now, just return score / max_score + ''' + score = self.get_score() + score = d['score'] + total = self.max_score() + if total > 0: + try: + return Progress(score, total) + except Exception as err: + log.exception("Got bad progress") + return None + return None + def handle_ajax(self, dispatch, get): ''' @@ -105,8 +129,8 @@ class SelfAssessmentModule(XModule): - def show_rubric: - return {'success': True} + def show_rubric(self,get): + return {'success': True, 'rubric':rubric_form} def save_problem(self, get): From 5ba2c2e7852177cac8a4a3b779465656bc9e76bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 1 Nov 2012 16:48:44 +0200 Subject: [PATCH 072/228] converted unittests --- common/lib/capa/capa/chem/chemtools.py | 68 +++++++++++--------------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/common/lib/capa/capa/chem/chemtools.py b/common/lib/capa/capa/chem/chemtools.py index ea40fb7cdf..d9559cce1e 100644 --- a/common/lib/capa/capa/chem/chemtools.py +++ b/common/lib/capa/capa/chem/chemtools.py @@ -77,60 +77,50 @@ class Test_Grade(unittest.TestCase): user_answer = vsepr_parse_user_answer(u'{"geometry":"AX3E0","atoms":{"c0":"B","p0":"F","p1":"B","p2":"F"}}') self.assertFalse(vsepr_grade(user_answer, correct_answer)) - def test_incorrect_positions(self): - correct_answer = vsepr_build_correct_answer(geometry="AX4E0", atoms={"c0": "N", "p0": "H", "p1": "(ep)", "p2": "H", "p3": "H"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX4E0","atoms":{"c0":"B","p0":"F","p1":"B","p2":"F"}}') - self.assertFalse(vsepr_grade(user_answer, correct_answer)) - - def test_correct_answer(self): + def test_correct_answer_p(self): correct_answer = vsepr_build_correct_answer(geometry="AX4E0", atoms={"c0": "N", "p0": "H", "p1": "(ep)", "p2": "H", "p3": "H"}) user_answer = vsepr_parse_user_answer(u'{"geometry":"AX4E0","atoms":{"c0":"N","p0":"H","p1":"(ep)","p2":"H", "p3":"H"}}') self.assertTrue(vsepr_grade(user_answer, correct_answer)) - def test_incorrect_position_order_p(self): - correct_answer = vsepr_build_correct_answer(geometry="AX4E0", atoms={"c0": "N", "p0": "H", "p1": "(ep)", "p2": "H", "p3": "H"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX4E0","atoms":{"c0":"N","p0":"H","p1":"H","p2":"(ep)", "p3":"H"}}') - self.assertFalse(vsepr_grade(user_answer, correct_answer)) - - def test_correct_position_order_with_ignore_p_order(self): - correct_answer = vsepr_build_correct_answer(geometry="AX4E0", atoms={"c0": "N", "p0": "H", "p1": "(ep)", "p2": "H", "p3": "H"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX4E0","atoms":{"c0":"N","p0":"H","p1":"H","p2":"(ep)", "p3":"H"}}') - self.assertTrue(vsepr_grade(user_answer, correct_answer, ignore_p_order=True)) - - def test_incorrect_position_order_ae(self): + def test_correct_answer_ae(self): correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "test", "a1": "(ep)", "e0": "H", "e1": "H", "e2": "(ep)", "e3": "(ep)"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"test","a1":"(ep)","e0":"H","e1":"(ep)","e2":"(ep)","e3":"(ep)"}}') - self.assertFalse(vsepr_grade(user_answer, correct_answer)) + user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"test","a1":"(ep)","e10":"H","e11":"H","e20":"(ep)","e21":"(ep)"}}') + self.assertTrue(vsepr_grade(user_answer, correct_answer)) - def test_correct_position_order_with_ignore_a_order_not_e(self): + def test_correct_answer_ae_convert_to_p_but_input_not_in_p(self): correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "test", "e0": "H", "e1": "H", "e2": "(ep)", "e3": "(ep)"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"test","a1":"(ep)","e0":"H","e1":"H","e2":"(ep)","e3":"(ep)"}}') - self.assertTrue(vsepr_grade(user_answer, correct_answer, ignore_a_order=True)) + user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"test","a1":"(ep)","e10":"H","e11":"(ep)","e20":"H","e21":"(ep)"}}') + self.assertFalse(vsepr_grade(user_answer, correct_answer, convert_to_perepherial=True)) - def test_incorrect_position_order_with_ignore_a_order_not_e(self): - correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "test", "e0": "H", "e1": "H", "e2": "H", "e3": "(ep)"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"test","a1":"(ep)","e0":"H","e1":"H","e2":"(ep)","e3":"H"}}') - self.assertFalse(vsepr_grade(user_answer, correct_answer, ignore_a_order=True)) + def test_correct_answer_ae_convert_to_p(self): + correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "p0": "(ep)", "p1": "test", "p2": "H", "p3": "H", "p4": "(ep)", "p6": "(ep)"}) + user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"test","a1":"(ep)","e10":"H","e11":"(ep)","e20":"H","e21":"(ep)"}}') + self.assertTrue(vsepr_grade(user_answer, correct_answer, convert_to_perepherial=True)) - def test_correct_position_order_with_ignore_e_order_not_a(self): - correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "test", "e0": "H", "e1": "H", "e2": "H", "e3": "(ep)"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"(ep)","a1":"test","e0":"H","e1":"H","e2":"(ep)","e3":"H"}}') - self.assertTrue(vsepr_grade(user_answer, correct_answer, ignore_e_order=True)) + def test_correct_answer_e1e2_in_a(self): + correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "(ep)", "e10": "H", "e11": "H", "e20": "H", "e21": "H"}) + user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"(ep)","a1":"(ep)","e10":"H","e11":"H","e20":"H","e21":"H"}}') + self.assertTrue(vsepr_grade(user_answer, correct_answer)) - def test_incorrect_position_order_with_ignore_e_order__not_a(self): - correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "test", "e0": "H", "e1": "H", "e2": "H", "e3": "(ep)"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"test","a1":"(ep)","e0":"H","e1":"H","e2":"(ep)","e3":"H"}}') - self.assertFalse(vsepr_grade(user_answer, correct_answer, ignore_e_order=True)) + def test_correct_answer_e1e2_in_e1(self): + correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "(ep)", "e10": "H", "e11": "H", "e20": "H", "e21": "H"}) + user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"H","a1":"H","e10":"(ep)","e11":"(ep)","e20":"H","e21":"H"}}') + self.assertTrue(vsepr_grade(user_answer, correct_answer)) - def test_correct_position_order_with_ignore_ae_order(self): - correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "test", "e0": "H", "e1": "H", "e2": "H", "e3": "(ep)"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"test","a1":"(ep)","e0":"H","e1":"H","e2":"(ep)","e3":"H"}}') - self.assertTrue(vsepr_grade(user_answer, correct_answer, ignore_e_order=True, ignore_a_order=True)) + def test_correct_answer_e1e2_in_e2(self): + correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "(ep)", "e10": "H", "e11": "H", "e20": "H", "e21": "H"}) + user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"H","a1":"H","e10":"H","e11":"H","e20":"(ep)","e21":"(ep)"}}') + self.assertTrue(vsepr_grade(user_answer, correct_answer)) + + def test_incorrect_answer_e1e2(self): + correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "(ep)", "e10": "H", "e11": "H", "e20": "H", "e21": "H"}) + user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"H","a1":"H","e10":"(ep)","e11":"H","e20":"H","e21":"(ep)"}}') + self.assertFalse(vsepr_grade(user_answer, correct_answer)) def test_incorrect_c0(self): correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "test", "e0": "H", "e1": "H", "e2": "H", "e3": "(ep)"}) user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"H","a0":"test","a1":"(ep)","e0":"H","e1":"H","e2":"(ep)","e3":"H"}}') - self.assertFalse(vsepr_grade(user_answer, correct_answer, ignore_e_order=True, ignore_a_order=True)) + self.assertFalse(vsepr_grade(user_answer, correct_answer)) def suite(): From d12bc257b40fcf470c2f83b7521252c94f851912 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 1 Nov 2012 10:53:04 -0400 Subject: [PATCH 073/228] work on save functionality --- .../js/src/selfassessment/display.coffee | 4 +-- .../xmodule/xmodule/self_assessment_module.py | 27 +++++++++---------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index 20301e48c7..592214734a 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -8,9 +8,7 @@ $(document).on('click', 'section.sa-wrapper input#show', ( -> alert("posted") if response.success alert(response.rubric) - $('section.sa-wrapper p#rubric').replace(response.rubric) - - alert("save") + $('section.sa-wrapper p#rubric').append(response.rubric) )); $(document).on('click', 'section.sa-wrapper input#save', ( -> diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 9f56a07c83..3508886918 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -6,6 +6,8 @@ import sys from lxml import etree from lxml.html import rewrite_links from path import path +import json +from progress import Progress from .x_module import XModule from pkg_resources import resource_string @@ -36,6 +38,12 @@ def only_one(lst, default="", process=lambda x: x): else: raise Exception('Malformed XML') +class ComplexEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, complex): + return "{real:.7g}{imag:+.7g}*j".format(real=obj.real, imag=obj.imag) + return json.JSONEncoder.default(self, obj) + class SelfAssessmentModule(XModule): js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'), @@ -53,42 +61,31 @@ class SelfAssessmentModule(XModule): instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs) - + dom2=etree.fromstring("" + self.definition['data'] + "") self.rubric=''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))]) self.problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))]) - problem_form=('

        ' '' '

        ').format(self.location) - self.problem=''.join([self.problem,problem_form]) - self.rubric=''.join([self.rubric,rubric_form]) - - #print(etree.tostring(rubric)) - #print(etree.tostring(problem)) - self.html = self.problem - self.answers={} - self.score=0 - - self.max_score=1 + self.top_score=1 def get_score(self): return self.score def max_score(self): - return self.max_score + return self.top_score def get_progress(self): ''' For now, just return score / max_score ''' score = self.get_score() - score = d['score'] total = self.max_score() if total > 0: try: @@ -130,7 +127,7 @@ class SelfAssessmentModule(XModule): def show_rubric(self,get): - return {'success': True, 'rubric':rubric_form} + return {'success': True, 'rubric':self.rubric} def save_problem(self, get): From 68ca1251074158fd28203bf7adb4d1b583aa3450 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 1 Nov 2012 11:13:14 -0400 Subject: [PATCH 074/228] working on save functionality to select only one --- .../xmodule/js/src/capa/display.coffee | 1 - .../js/src/selfassessment/display.coffee | 17 +++++++--- .../xmodule/xmodule/self_assessment_module.py | 34 +++++++++++++++---- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.coffee b/common/lib/xmodule/xmodule/js/src/capa/display.coffee index 6a3c719bb3..1c0ace9e59 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/capa/display.coffee @@ -209,7 +209,6 @@ class @Problem show: => if !@el.hasClass 'showed' Logger.log 'problem_show', problem: @id - alert(@url) $.postWithPrefix "#{@url}/problem_show", (response) => answers = response.answers $.each answers, (key, value) => diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index 592214734a..9c7f7faac4 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -3,6 +3,8 @@ $(document).on('click', 'section.sa-wrapper input#show', ( -> post_url=$('section.sa-wrapper input#show').attr('url') final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_show" answer=$('section.sa-wrapper input#answer').val() + alert(answer) + alert(final_url) $.post final_url, answer, (response) -> alert("posted") @@ -15,20 +17,25 @@ $(document).on('click', 'section.sa-wrapper input#save', ( -> answer=$('section.sa-wrapper input#answer').val() alert(answer) assessment=0 - assessment_correct=$('section.sa-wrapper input#assessment_correct').val() + assessment_correct=$('section.sa-wrapper input#assessment_correct').selected() alert(assessment_correct) - assessment_incorrect=$('section.sa-wrapper input#assessment_incorrect').val() + assessment_incorrect=$('section.sa-wrapper input#assessment_incorrect').selected() alert(assessment_incorrect) root = location.protocol + "//" + location.host post_url=$('section.sa-wrapper input#show').attr('url') - alert(post_url) final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_save" - alert(final_url) + + $('section.sa-wrapper input#assessment option').each( -> + if (this.selected) + alert('this option is selected') + else + alert('this is not') + ); $.post final_url, answer, (response) -> if response.success - $('p.rubric').replace(response.rubric) + $('section.sa_wrapper p#save_message').replace(response.message) alert("save") )); diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 3508886918..d1d4c4205e 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -21,9 +21,9 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent log = logging.getLogger("mitx.courseware") -rubric_form=('
        Correct
        ' - '' - 'Incorrect
        ') +rubric_form=('
        Correct
        ' + '' + 'Incorrect



        ') def only_one(lst, default="", process=lambda x: x): """ @@ -61,7 +61,7 @@ class SelfAssessmentModule(XModule): instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs) - + dom2=etree.fromstring("" + self.definition['data'] + "") self.rubric=''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))]) self.problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))]) @@ -127,7 +127,7 @@ class SelfAssessmentModule(XModule): def show_rubric(self,get): - return {'success': True, 'rubric':self.rubric} + return {'success': True, 'rubric' : self.rubric} def save_problem(self, get): @@ -155,7 +155,29 @@ class SelfAssessmentModule(XModule): self.system.track_function('save_problem_succeed', event_info) - return {'success': True} + return {'success': True, 'message' : "Save Succcesful. Thanks for participating!"} + + @staticmethod + def make_dict_of_responses(get): + '''Make dictionary of student responses (aka "answers") + get is POST dictionary. + ''' + answers = dict() + for key in get: + # e.g. input_resistor_1 ==> resistor_1 + _, _, name = key.partition('_') + + # This allows for answers which require more than one value for + # the same form input (e.g. checkbox inputs). The convention is that + # if the name ends with '[]' (which looks like an array), then the + # answer will be an array. + if not name.endswith('[]'): + answers[name] = get[key] + else: + name = name[:-2] + answers[name] = get.getlist(key) + + return answers class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): From c6cf72d623bf4f47eec3e1d1ff21849a91595d2f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 1 Nov 2012 11:26:23 -0400 Subject: [PATCH 075/228] ajax working, refinements now --- .../xmodule/js/src/selfassessment/display.coffee | 10 +--------- common/lib/xmodule/xmodule/self_assessment_module.py | 9 +++++---- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index 9c7f7faac4..f393fc79ec 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -3,24 +3,16 @@ $(document).on('click', 'section.sa-wrapper input#show', ( -> post_url=$('section.sa-wrapper input#show').attr('url') final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_show" answer=$('section.sa-wrapper input#answer').val() - alert(answer) - alert(final_url) - $.post final_url, answer, (response) -> - alert("posted") if response.success - alert(response.rubric) $('section.sa-wrapper p#rubric').append(response.rubric) )); $(document).on('click', 'section.sa-wrapper input#save', ( -> answer=$('section.sa-wrapper input#answer').val() - alert(answer) assessment=0 - assessment_correct=$('section.sa-wrapper input#assessment_correct').selected() + assessment_correct=$('section.sa-wrapper #assessment').find(':selected').text() alert(assessment_correct) - assessment_incorrect=$('section.sa-wrapper input#assessment_incorrect').selected() - alert(assessment_incorrect) root = location.protocol + "//" + location.host post_url=$('section.sa-wrapper input#show').attr('url') diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index d1d4c4205e..3e65b16670 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -21,9 +21,9 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent log = logging.getLogger("mitx.courseware") -rubric_form=('
        Correct
        ' - '' - 'Incorrect



        ') +rubric_form=('

        Please assess your performance given the above rubric:


        ' + '



        ') def only_one(lst, default="", process=lambda x: x): """ @@ -68,7 +68,7 @@ class SelfAssessmentModule(XModule): problem_form=('

        ' '' - '

        ').format(self.location) + '



        ').format(self.location) self.problem=''.join([self.problem,problem_form]) self.rubric=''.join([self.rubric,rubric_form]) self.html = self.problem @@ -151,6 +151,7 @@ class SelfAssessmentModule(XModule): event_info['problem_id'] = self.location.url() answers = self.make_dict_of_responses(get) + log.debug(answers) event_info['answers'] = answers self.system.track_function('save_problem_succeed', event_info) From 07a2a105ca9540545d2ce5d0324b12d98b97a24a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 1 Nov 2012 17:35:27 +0200 Subject: [PATCH 076/228] added comments --- common/lib/capa/capa/chem/chemtools.py | 110 +++++++++++++++++++++---- 1 file changed, 92 insertions(+), 18 deletions(-) diff --git a/common/lib/capa/capa/chem/chemtools.py b/common/lib/capa/capa/chem/chemtools.py index d9559cce1e..2c7088cac9 100644 --- a/common/lib/capa/capa/chem/chemtools.py +++ b/common/lib/capa/capa/chem/chemtools.py @@ -1,20 +1,94 @@ +"""This module originally includes functions for grading Vsepr problems. + +Also, may be this module is the place for other chemistry-related grade functions. TODO: discuss it. +""" + import json import unittest import itertools + def vsepr_parse_user_answer(user_input): + """ + user_input is json generated by vsepr.js from dictionary. + There are must be only two keys in original user_input dictionary: "geometry" and "atoms". + Format: u'{"geometry": "AX3E0","atoms":{"c0": "B","p0": "F","p1": "B","p2": "F"}}' + Order of elements inside "atoms" subdict does not matters. + Return dict from parsed json. + + "Atoms" subdict stores positions of atoms in molecule. + General types of positions: + c0 - central atom + p0..pN - peripheral atoms + a0..aN - axial atoms + e0..eN - equatorial atoms + + Each position is dictionary key, i.e. user_input["atoms"]["c0"] is central atom, user_input["atoms"]["a0"] is one of axial atoms. + + Special position only for AX6 (Octahedral) geometry: + e10, e12 - atom pairs opposite the central atom, + e20, e22 - atom pairs opposite the central atom, + e1 and e2 pairs lying crosswise in equatorial plane. + + In user_input["atoms"] may be only 3 set of keys: + (c0,p0..pN), + (c0, a0..aN, e0..eN), + (c0, a0, a1, e10,e11,e20,e21) - if geometry is AX6. + """ return json.loads(user_input) def vsepr_build_correct_answer(geometry, atoms): + """ + geometry is string. + atoms is dict of atoms with proper positions. + Example: + + correct_answer = vsepr_build_correct_answer(geometry="AX4E0", atoms={"c0": "N", "p0": "H", "p1": "(ep)", "p2": "H", "p3": "H"}) + + returns a dictionary composed from input values: + {'geometry': geometry, 'atoms': atoms} + """ return {'geometry': geometry, 'atoms': atoms} -def vsepr_grade(user_input, correct_answer, convert_to_perepherial=False): +def vsepr_grade(user_input, correct_answer, convert_to_peripheral=False): """ - Allowed cases: - c0, a, e - c0, p + This function does comparison between user_input and correct_answer. + + Comparison is successful if all steps are successful: + + 1) geometries are equal + 2) central atoms (index in dictionary 'c0') are equal + 3): + In next steps there is comparing of corresponding subsets of atom positions: equatorial (e0..eN), axial (a0..aN) or peripheral (p0..pN) + + If convert_to_peripheral is True, then axial and equatorial positions are converted to peripheral. + This means that user_input from: + "atoms":{"c0": "Br","a0": "test","a1": "(ep)","e10": "H","e11": "(ep)","e20": "H","e21": "(ep)"}}' after parsing to json + is converted to: + {"c0": "Br", "p0": "(ep)", "p1": "test", "p2": "H", "p3": "H", "p4": "(ep)", "p6": "(ep)"} + i.e. aX and eX -> pX + + So if converted, p subsets are compared, + if not a and e subsets are compared + If all subsets are equal, grade succeeds. + + There is also one special case for AX6 geometry. + In this case user_input["atoms"] contains special 3 symbol keys: e10, e12, e20, and e21. + Correct answer for this geometry can be of 3 types: + 1) c0 and peripheral + 2) c0 and axial and equatorial + 3) c0 and axial and equatorial-subset-1 (e1X) and equatorial-subset-2 (e2X) + + If correct answer is type 1 or 2, then user_input is converted from type 3 to type 2 (or to type 1 if convert_to_peripheral is True) + + If correct_answer is type 3, then we done special case comparison. We have 3 sets of atoms positions both in user_input and correct_answer: axial, eq-1 and eq-2. + Answer will be correct if these sets are equals for one of permutations. For example, if : + user_axial = correct_eq-1 + user_eq-1 = correct-axial + user_eq-2 = correct-eq-2 + """ # import ipdb; ipdb.set_trace() # print user_input, type(user_input) @@ -25,7 +99,7 @@ def vsepr_grade(user_input, correct_answer, convert_to_perepherial=False): if user_input['atoms']['c0'] != correct_answer['atoms']['c0']: return False - if convert_to_perepherial: + if convert_to_peripheral: # convert user_input from (a,e,e1,e2) to (p) # correct_answer must be set in (p) using this flag c0 = user_input['atoms'].pop('c0') @@ -46,7 +120,7 @@ def vsepr_grade(user_input, correct_answer, convert_to_perepherial=False): return True return False - else: # no need to checl e1x,e2x symmetry - convert them to ex + else: # no need to check e1x,e2x symmetry - convert them to ex if 'e10' in user_input['atoms']: # e1x exists, it is AX6.. case e_index = 0 for k, v in user_input['atoms'].items(): @@ -74,52 +148,52 @@ class Test_Grade(unittest.TestCase): def test_incorrect_geometry(self): correct_answer = vsepr_build_correct_answer(geometry="AX4E0", atoms={"c0": "N", "p0": "H", "p1": "(ep)", "p2": "H", "p3": "H"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX3E0","atoms":{"c0":"B","p0":"F","p1":"B","p2":"F"}}') + user_answer = vsepr_parse_user_answer(u'{"geometry": "AX3E0","atoms":{"c0": "B","p0": "F","p1": "B","p2": "F"}}') self.assertFalse(vsepr_grade(user_answer, correct_answer)) def test_correct_answer_p(self): correct_answer = vsepr_build_correct_answer(geometry="AX4E0", atoms={"c0": "N", "p0": "H", "p1": "(ep)", "p2": "H", "p3": "H"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX4E0","atoms":{"c0":"N","p0":"H","p1":"(ep)","p2":"H", "p3":"H"}}') + user_answer = vsepr_parse_user_answer(u'{"geometry": "AX4E0","atoms":{"c0": "N","p0": "H","p1": "(ep)","p2": "H", "p3": "H"}}') self.assertTrue(vsepr_grade(user_answer, correct_answer)) def test_correct_answer_ae(self): correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "test", "a1": "(ep)", "e0": "H", "e1": "H", "e2": "(ep)", "e3": "(ep)"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"test","a1":"(ep)","e10":"H","e11":"H","e20":"(ep)","e21":"(ep)"}}') + user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "test","a1": "(ep)","e10": "H","e11": "H","e20": "(ep)","e21": "(ep)"}}') self.assertTrue(vsepr_grade(user_answer, correct_answer)) def test_correct_answer_ae_convert_to_p_but_input_not_in_p(self): correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "test", "e0": "H", "e1": "H", "e2": "(ep)", "e3": "(ep)"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"test","a1":"(ep)","e10":"H","e11":"(ep)","e20":"H","e21":"(ep)"}}') - self.assertFalse(vsepr_grade(user_answer, correct_answer, convert_to_perepherial=True)) + user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "test","a1": "(ep)","e10": "H","e11": "(ep)","e20": "H","e21": "(ep)"}}') + self.assertFalse(vsepr_grade(user_answer, correct_answer, convert_to_peripheral=True)) def test_correct_answer_ae_convert_to_p(self): correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "p0": "(ep)", "p1": "test", "p2": "H", "p3": "H", "p4": "(ep)", "p6": "(ep)"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"test","a1":"(ep)","e10":"H","e11":"(ep)","e20":"H","e21":"(ep)"}}') - self.assertTrue(vsepr_grade(user_answer, correct_answer, convert_to_perepherial=True)) + user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "test","a1": "(ep)","e10": "H","e11": "(ep)","e20": "H","e21": "(ep)"}}') + self.assertTrue(vsepr_grade(user_answer, correct_answer, convert_to_peripheral=True)) def test_correct_answer_e1e2_in_a(self): correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "(ep)", "e10": "H", "e11": "H", "e20": "H", "e21": "H"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"(ep)","a1":"(ep)","e10":"H","e11":"H","e20":"H","e21":"H"}}') + user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "(ep)","a1": "(ep)","e10": "H","e11": "H","e20": "H","e21": "H"}}') self.assertTrue(vsepr_grade(user_answer, correct_answer)) def test_correct_answer_e1e2_in_e1(self): correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "(ep)", "e10": "H", "e11": "H", "e20": "H", "e21": "H"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"H","a1":"H","e10":"(ep)","e11":"(ep)","e20":"H","e21":"H"}}') + user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "H","a1": "H","e10": "(ep)","e11": "(ep)","e20": "H","e21": "H"}}') self.assertTrue(vsepr_grade(user_answer, correct_answer)) def test_correct_answer_e1e2_in_e2(self): correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "(ep)", "e10": "H", "e11": "H", "e20": "H", "e21": "H"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"H","a1":"H","e10":"H","e11":"H","e20":"(ep)","e21":"(ep)"}}') + user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "H","a1": "H","e10": "H","e11": "H","e20": "(ep)","e21": "(ep)"}}') self.assertTrue(vsepr_grade(user_answer, correct_answer)) def test_incorrect_answer_e1e2(self): correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "(ep)", "e10": "H", "e11": "H", "e20": "H", "e21": "H"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"Br","a0":"H","a1":"H","e10":"(ep)","e11":"H","e20":"H","e21":"(ep)"}}') + user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "H","a1": "H","e10": "(ep)","e11": "H","e20": "H","e21": "(ep)"}}') self.assertFalse(vsepr_grade(user_answer, correct_answer)) def test_incorrect_c0(self): correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "test", "e0": "H", "e1": "H", "e2": "H", "e3": "(ep)"}) - user_answer = vsepr_parse_user_answer(u'{"geometry":"AX6E0","atoms":{"c0":"H","a0":"test","a1":"(ep)","e0":"H","e1":"H","e2":"(ep)","e3":"H"}}') + user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "H","a0": "test","a1": "(ep)","e0": "H","e1": "H","e2": "(ep)","e3": "H"}}') self.assertFalse(vsepr_grade(user_answer, correct_answer)) From 3ed4b77d8c8c31fe847c632f714ad0743760530c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 1 Nov 2012 17:43:41 +0200 Subject: [PATCH 077/228] removed testsing comments --- common/lib/capa/capa/chem/chemtools.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/lib/capa/capa/chem/chemtools.py b/common/lib/capa/capa/chem/chemtools.py index 2c7088cac9..ad6633fc00 100644 --- a/common/lib/capa/capa/chem/chemtools.py +++ b/common/lib/capa/capa/chem/chemtools.py @@ -90,9 +90,6 @@ def vsepr_grade(user_input, correct_answer, convert_to_peripheral=False): user_eq-2 = correct-eq-2 """ - # import ipdb; ipdb.set_trace() - # print user_input, type(user_input) - # print correct_answer, type(correct_answer) if user_input['geometry'] != correct_answer['geometry']: return False From 5b6045b5fceb14d39d2b1fef6a917bde3a6b0b75 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 1 Nov 2012 11:47:28 -0400 Subject: [PATCH 078/228] fixed ajax value passing to function --- .../js/src/selfassessment/display.coffee | 13 +------ .../xmodule/xmodule/self_assessment_module.py | 38 ++++--------------- 2 files changed, 8 insertions(+), 43 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index f393fc79ec..5627325433 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -9,23 +9,12 @@ $(document).on('click', 'section.sa-wrapper input#show', ( -> )); $(document).on('click', 'section.sa-wrapper input#save', ( -> - answer=$('section.sa-wrapper input#answer').val() - assessment=0 assessment_correct=$('section.sa-wrapper #assessment').find(':selected').text() - alert(assessment_correct) - root = location.protocol + "//" + location.host post_url=$('section.sa-wrapper input#show').attr('url') final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_save" - $('section.sa-wrapper input#assessment option').each( -> - if (this.selected) - alert('this option is selected') - else - alert('this is not') - ); - - $.post final_url, answer, (response) -> + $.post final_url, assessment_correct, (response) -> if response.success $('section.sa_wrapper p#save_message').replace(response.message) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 3e65b16670..22c5b9c9f3 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -72,7 +72,7 @@ class SelfAssessmentModule(XModule): self.problem=''.join([self.problem,problem_form]) self.rubric=''.join([self.rubric,rubric_form]) self.html = self.problem - self.answers={} + self.answer="" self.score=0 self.top_score=1 @@ -124,22 +124,23 @@ class SelfAssessmentModule(XModule): }) return json.dumps(d, cls=ComplexEncoder) - - def show_rubric(self,get): + self.answer=get.keys()[0] return {'success': True, 'rubric' : self.rubric} - def save_problem(self, get): ''' Save the passed in answers. Returns a dict { 'success' : bool, ['error' : error-msg]}, with the error key only present if success is False. ''' + + correctness=get.keys()[0] + log.debug(correctness) event_info = dict() event_info['state'] = {'seed': 1, 'student_answers': self.answers, - 'correct_map': {'self_assess' : {'correctness': False, + 'correct_map': {'self_assess' : {'correctness': correctness, 'npoints': 0, 'msg': "", 'hint': "", @@ -150,37 +151,12 @@ class SelfAssessmentModule(XModule): event_info['problem_id'] = self.location.url() - answers = self.make_dict_of_responses(get) - log.debug(answers) - event_info['answers'] = answers + event_info['answers'] = self.answer self.system.track_function('save_problem_succeed', event_info) return {'success': True, 'message' : "Save Succcesful. Thanks for participating!"} - @staticmethod - def make_dict_of_responses(get): - '''Make dictionary of student responses (aka "answers") - get is POST dictionary. - ''' - answers = dict() - for key in get: - # e.g. input_resistor_1 ==> resistor_1 - _, _, name = key.partition('_') - - # This allows for answers which require more than one value for - # the same form input (e.g. checkbox inputs). The convention is that - # if the name ends with '[]' (which looks like an array), then the - # answer will be an array. - if not name.endswith('[]'): - answers[name] = get[key] - else: - name = name[:-2] - answers[name] = get.getlist(key) - - return answers - - class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): """ Module for putting raw html in a course From 9238653f7abf84eb001dce15b55a2a70599b2fef Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 1 Nov 2012 12:27:42 -0400 Subject: [PATCH 079/228] fixed js to remove buttons when needed --- .../js/src/selfassessment/display.coffee | 15 ++++---- .../xmodule/xmodule/self_assessment_module.py | 35 +++++++++++++------ 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index 5627325433..add21ee927 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -2,21 +2,22 @@ $(document).on('click', 'section.sa-wrapper input#show', ( -> root = location.protocol + "//" + location.host post_url=$('section.sa-wrapper input#show').attr('url') final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_show" - answer=$('section.sa-wrapper input#answer').val() + answer=$('section.sa-wrapper textarea#answer').val() $.post final_url, answer, (response) -> if response.success + $('section.sa-wrapper input#show').remove() + $('section.sa-wrapper textarea#answer').remove() + $('section.sa-wrapper p#rubric').append(answer) $('section.sa-wrapper p#rubric').append(response.rubric) )); $(document).on('click', 'section.sa-wrapper input#save', ( -> assessment_correct=$('section.sa-wrapper #assessment').find(':selected').text() root = location.protocol + "//" + location.host - post_url=$('section.sa-wrapper input#show').attr('url') + post_url=$('section.sa-wrapper input#save').attr('url') final_url="/courses/MITx/6.002x/2012_Fall/modx/#{post_url}/sa_save" - $.post final_url, assessment_correct, (response) -> - if response.success - $('section.sa_wrapper p#save_message').replace(response.message) - - alert("save") + if response.success + $('section.sa-wrapper p#save_message').append(response.message) + $('section.sa-wrapper input#save').remove() )); diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 22c5b9c9f3..5c4cda71f7 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -21,10 +21,6 @@ from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent log = logging.getLogger("mitx.courseware") -rubric_form=('

        Please assess your performance given the above rubric:


        ' - '



        ') - def only_one(lst, default="", process=lambda x: x): """ If lst is empty, returns default @@ -63,18 +59,31 @@ class SelfAssessmentModule(XModule): instance_state, shared_state, **kwargs) dom2=etree.fromstring("" + self.definition['data'] + "") - self.rubric=''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))]) + self.rubric="

        " + ''.join([etree.tostring(child) for child in only_one(dom2.xpath('rubric'))]) self.problem=''.join([etree.tostring(child) for child in only_one(dom2.xpath('problem'))]) - problem_form=('

        ' + problem_form=('
        +
        + +
        + + -

        \ No newline at end of file diff --git a/lms/templates/self_assessment_rubric.html b/lms/templates/self_assessment_rubric.html index b157442b7a..0972998f5e 100644 --- a/lms/templates/self_assessment_rubric.html +++ b/lms/templates/self_assessment_rubric.html @@ -1,17 +1,18 @@ -
        -

        Rubric - ${ rubric } - Please assess your performance given the above rubric:
        - -
        - ${ hint_prompt } - +
        diff --git a/lms/templates/self_assessment_prompt.html b/lms/templates/self_assessment_prompt.html index 905f02596e..2d2c5779b2 100644 --- a/lms/templates/self_assessment_prompt.html +++ b/lms/templates/self_assessment_prompt.html @@ -8,7 +8,11 @@
      -
      +
      ${initial_rubric}
      +
      ${initial_hint}
      + +
      ${initial_message}
      + diff --git a/lms/templates/self_assessment_rubric.html b/lms/templates/self_assessment_rubric.html index 0972998f5e..479c19a027 100644 --- a/lms/templates/self_assessment_rubric.html +++ b/lms/templates/self_assessment_rubric.html @@ -4,15 +4,11 @@ ${rubric}
      + % if not read_only: + % endif -
      - ${hint_prompt} -
      - "); - - document.write("
      "); - - - } - - diff --git a/lms/askbot/skins/common/media/js/post.js b/lms/askbot/skins/common/media/js/post.js deleted file mode 100644 index e4ffdf3a50..0000000000 --- a/lms/askbot/skins/common/media/js/post.js +++ /dev/null @@ -1,1952 +0,0 @@ -/* -Scripts for cnprog.com -Project Name: Lanai -All Rights Resevred 2008. CNPROG.COM -*/ -var lanai = -{ - /** - * Finds any
      tags which aren't registered for - * pretty printing, adds the appropriate class name and invokes prettify. - */ - highlightSyntax: function(){ - var styled = false; - $("pre code").parent().each(function(){ - if (!$(this).hasClass('prettyprint')){ - $(this).addClass('prettyprint'); - styled = true; - } - }); - - if (styled){ - prettyPrint(); - } - } -}; - -function appendLoader(element) { - loading = gettext('loading...') - element.append('' +
-        loading +
-    ''); -} - -function removeLoader() { - $("img.ajax-loader").remove(); -} - -function setSubmitButtonDisabled(form, isDisabled) { - form.find("input[type='submit']").attr("disabled", isDisabled ? "true" : ""); -} - -function enableSubmitButton(form) { - setSubmitButtonDisabled(form, false); -} - -function disableSubmitButton(form) { - setSubmitButtonDisabled(form, true); -} - -function setupFormValidation(form, validationRules, validationMessages, onSubmitCallback) { - enableSubmitButton(form); - form.validate({ - debug: true, - rules: (validationRules ? validationRules : {}), - messages: (validationMessages ? validationMessages : {}), - errorElement: "span", - errorClass: "form-error", - errorPlacement: function(error, element) { - var span = element.next().find("span.form-error"); - if (span.length === 0) { - span = element.parent().find("span.form-error"); - if (span.length === 0){ - //for resizable textarea - var element_id = element.attr('id'); - span = $("label[for='" + element_id + "']"); - } - } - span.replaceWith(error); - }, - submitHandler: function(form_dom) { - disableSubmitButton($(form_dom)); - - if (onSubmitCallback){ - onSubmitCallback(); - } - else{ - form_dom.submit(); - } - } - }); -} - -var validateTagLength = function(value){ - var tags = getUniqueWords(value); - var are_tags_ok = true; - $.each(tags, function(index, value){ - if (value.length > askbot['settings']['maxTagLength']){ - are_tags_ok = false; - } - }); - return are_tags_ok; -}; -var validateTagCount = function(value){ - var tags = getUniqueWords(value); - return (tags.length <= askbot['settings']['maxTagsPerPost']); -}; - -$.validator.addMethod('limit_tag_count', validateTagCount); -$.validator.addMethod('limit_tag_length', validateTagLength); - -var CPValidator = function(){ - return { - getQuestionFormRules : function(){ - return { - tags: { - required: true, - maxlength: 105, - limit_tag_count: true, - limit_tag_length: true - }, - text: { - minlength: askbot['settings']['minQuestionBodyLength'] - }, - title: { - minlength: askbot['settings']['minTitleLength'] - } - }; - }, - getQuestionFormMessages: function(){ - return { - tags: { - required: " " + gettext('tags cannot be empty'), - maxlength: askbot['messages']['tagLimits'], - limit_tag_count: askbot['messages']['maxTagsPerPost'], - limit_tag_length: askbot['messages']['maxTagLength'] - }, - text: { - required: " " + gettext('content cannot be empty'), - minlength: interpolate( - ngettext( - 'question body must be > %s character', - 'question body must be > %s characters', - askbot['settings']['minQuestionBodyLength'] - ), - [askbot['settings']['minQuestionBodyLength'], ] - ) - }, - title: { - required: " " + gettext('please enter title'), - minlength: interpolate( - ngettext( - 'title must be > %s character', - 'title must be > %s characters', - askbot['settings']['minTitleLength'] - ), - [askbot['settings']['minTitleLength'], ] - ) - } - }; - }, - getAnswerFormRules : function(){ - return { - text: { - minlength: askbot['settings']['minAnswerBodyLength'] - }, - }; - }, - getAnswerFormMessages: function(){ - return { - text: { - required: " " + gettext('content cannot be empty'), - minlength: interpolate( - ngettext( - 'answer must be > %s character', - 'answer must be > %s characters', - askbot['settings']['minAnswerBodyLength'] - ), - [askbot['settings']['minAnswerBodyLength'], ] - ) - }, - } - } - }; -}(); - -/** - * @constructor - * @extends {SimpleControl} - * @param {Comment} comment to upvote - */ -var CommentVoteButton = function(comment){ - SimpleControl.call(this); - /** - * @param {Comment} - */ - this._comment = comment; - /** - * @type {boolean} - */ - this._voted = false; - /** - * @type {number} - */ - this._score = 0; -}; -inherits(CommentVoteButton, SimpleControl); -/** - * @param {number} score - */ -CommentVoteButton.prototype.setScore = function(score){ - this._score = score; - if (this._element){ - this._element.html(score); - } -}; -/** - * @param {boolean} voted - */ -CommentVoteButton.prototype.setVoted = function(voted){ - this._voted = voted; - if (this._element){ - this._element.addClass('upvoted'); - } -}; - -CommentVoteButton.prototype.getVoteHandler = function(){ - var me = this; - var comment = this._comment; - return function(){ - var voted = me._voted; - var post_id = me._comment.getId(); - var data = { - cancel_vote: voted ? true:false, - post_id: post_id - }; - $.ajax({ - type: 'POST', - data: data, - dataType: 'json', - url: askbot['urls']['upvote_comment'], - cache: false, - success: function(data){ - if (data['success'] == true){ - me.setScore(data['score']); - me.setVoted(true); - } else { - showMessage(comment.getElement(), data['message'], 'after'); - } - } - }); - }; -}; - -CommentVoteButton.prototype.decorate = function(element){ - this._element = element; - this.setHandler(this.getVoteHandler()); - - var element = this._element; - var comment = this._comment; - /* can't call comment.getElement() here due - * an issue in the getElement() of comment - * so use an "illegal" access to comment._element here - */ - comment._element.mouseenter(function(){ - //outside height may not be known - //var height = comment.getElement().height(); - //element.height(height); - element.addClass('hover'); - }); - comment._element.mouseleave(function(){ - element.removeClass('hover'); - }); - -}; - -CommentVoteButton.prototype.createDom = function(){ - this._element = this.makeElement('div'); - if (this._score > 0){ - this._element.html(this._score); - } - this._element.addClass('upvote'); - if (this._voted){ - this._element.addClass('upvoted'); - } - this.decorate(this._element); -}; - -/** - * legacy Vote class - * handles all sorts of vote-like operations - */ -var Vote = function(){ - // All actions are related to a question - var questionId; - //question slug to build redirect urls - var questionSlug; - // The object we operate on actually. It can be a question or an answer. - var postId; - var questionAuthorId; - var currentUserId; - var answerContainerIdPrefix = 'post-id-'; - var voteContainerId = 'vote-buttons'; - var imgIdPrefixAccept = 'answer-img-accept-'; - var classPrefixFollow= 'button follow'; - var classPrefixFollowed= 'button followed'; - var imgIdPrefixQuestionVoteup = 'question-img-upvote-'; - var imgIdPrefixQuestionVotedown = 'question-img-downvote-'; - var imgIdPrefixAnswerVoteup = 'answer-img-upvote-'; - var imgIdPrefixAnswerVotedown = 'answer-img-downvote-'; - var divIdFavorite = 'favorite-number'; - var commentLinkIdPrefix = 'comment-'; - var voteNumberClass = "vote-number"; - var offensiveIdPrefixQuestionFlag = 'question-offensive-flag-'; - var removeOffensiveIdPrefixQuestionFlag = 'question-offensive-remove-flag-'; - var removeAllOffensiveIdPrefixQuestionFlag = 'question-offensive-remove-all-flag-'; - var offensiveIdPrefixAnswerFlag = 'answer-offensive-flag-'; - var removeOffensiveIdPrefixAnswerFlag = 'answer-offensive-remove-flag-'; - var removeAllOffensiveIdPrefixAnswerFlag = 'answer-offensive-remove-all-flag-'; - var offensiveClassFlag = 'offensive-flag'; - var questionControlsId = 'question-controls'; - var removeQuestionLinkIdPrefix = 'question-delete-link-'; - var removeAnswerLinkIdPrefix = 'answer-delete-link-'; - var questionSubscribeUpdates = 'question-subscribe-updates'; - var questionSubscribeSidebar= 'question-subscribe-sidebar'; - - var acceptAnonymousMessage = gettext('insufficient privilege'); - var acceptOwnAnswerMessage = gettext('cannot pick own answer as best'); - - var pleaseLogin = " " - + gettext('please login') + ""; - - var favoriteAnonymousMessage = gettext('anonymous users cannot follow questions') + pleaseLogin; - var subscribeAnonymousMessage = gettext('anonymous users cannot subscribe to questions') + pleaseLogin; - var voteAnonymousMessage = gettext('anonymous users cannot vote') + pleaseLogin; - //there were a couple of more messages... - var offensiveConfirmation = gettext('please confirm offensive'); - var removeOffensiveConfirmation = gettext('please confirm removal of offensive flag'); - var offensiveAnonymousMessage = gettext('anonymous users cannot flag offensive posts') + pleaseLogin; - var removeConfirmation = gettext('confirm delete'); - var removeAnonymousMessage = gettext('anonymous users cannot delete/undelete') + pleaseLogin; - var recoveredMessage = gettext('post recovered'); - var deletedMessage = gettext('post deleted'); - - var VoteType = { - acceptAnswer : 0, - questionUpVote : 1, - questionDownVote : 2, - favorite : 4, - answerUpVote: 5, - answerDownVote:6, - offensiveQuestion : 7, - removeOffensiveQuestion : 7.5, - removeAllOffensiveQuestion : 7.6, - offensiveAnswer:8, - removeOffensiveAnswer:8.5, - removeAllOffensiveAnswer:8.6, - removeQuestion: 9,//deprecate - removeAnswer:10,//deprecate - questionSubscribeUpdates:11, - questionUnsubscribeUpdates:12 - }; - - var getFavoriteButton = function(){ - var favoriteButton = 'div.'+ voteContainerId +' a[class='+ classPrefixFollow +']'; - favoriteButton += ', div.'+ voteContainerId +' a[class='+ classPrefixFollowed +']'; - return $(favoriteButton); - }; - var getFavoriteNumber = function(){ - var favoriteNumber = '#'+ divIdFavorite ; - return $(favoriteNumber); - }; - var getQuestionVoteUpButton = function(){ - var questionVoteUpButton = 'div.'+ voteContainerId +' ul li[id^='+ imgIdPrefixQuestionVoteup +']'; - return $(questionVoteUpButton); - }; - var getQuestionVoteDownButton = function(){ - var questionVoteDownButton = 'div.'+ voteContainerId +' ul li[id^='+ imgIdPrefixQuestionVotedown +']'; - return $(questionVoteDownButton); - }; - var getAnswerVoteUpButtons = function(){ - var answerVoteUpButton = 'div.'+ voteContainerId +' ul li[id^='+ imgIdPrefixAnswerVoteup +']'; - return $(answerVoteUpButton); - }; - var getAnswerVoteDownButtons = function(){ - var answerVoteDownButton = 'div.'+ voteContainerId +' ul li[id^='+ imgIdPrefixAnswerVotedown +']'; - return $(answerVoteDownButton); - }; - var getAnswerVoteUpButton = function(id){ - var answerVoteUpButton = 'div.'+ voteContainerId +' ul li[id='+ imgIdPrefixAnswerVoteup + id + ']'; - return $(answerVoteUpButton); - }; - var getAnswerVoteDownButton = function(id){ - var answerVoteDownButton = 'div.'+ voteContainerId +' ul li[id='+ imgIdPrefixAnswerVotedown + id + ']'; - return $(answerVoteDownButton); - }; - - var getOffensiveQuestionFlag = function(){ - var offensiveQuestionFlag = 'div.question-actions span[id^="'+ offensiveIdPrefixQuestionFlag +'"]'; - return $(offensiveQuestionFlag); - }; - - var getRemoveOffensiveQuestionFlag = function(){ - var removeOffensiveQuestionFlag = 'div.question-actions span[id^="'+ removeOffensiveIdPrefixQuestionFlag +'"]'; - return $(removeOffensiveQuestionFlag); - }; - - var getRemoveAllOffensiveQuestionFlag = function(){ - var removeAllOffensiveQuestionFlag = 'div.question-actions span[id^="'+ removeAllOffensiveIdPrefixQuestionFlag +'"]'; - return $(removeAllOffensiveQuestionFlag); - }; - - var getOffensiveAnswerFlags = function(){ - var offensiveQuestionFlag = 'div.answer span[id^="'+ offensiveIdPrefixAnswerFlag +'"]'; - return $(offensiveQuestionFlag); - }; - - var getRemoveOffensiveAnswerFlag = function(){ - var removeOffensiveAnswerFlag = 'div.answer span[id^="'+ removeOffensiveIdPrefixAnswerFlag +'"]'; - return $(removeOffensiveAnswerFlag); - }; - - var getRemoveAllOffensiveAnswerFlag = function(){ - var removeAllOffensiveAnswerFlag = 'div.answer span[id^="'+ removeAllOffensiveIdPrefixAnswerFlag +'"]'; - return $(removeAllOffensiveAnswerFlag); - }; - - var getremoveQuestionLink = function(){ - var removeQuestionLink = 'div.question-actions a[id^='+ removeQuestionLinkIdPrefix +']'; - return $(removeQuestionLink); - }; - - var getquestionSubscribeUpdatesCheckbox = function(){ - return $('#' + questionSubscribeUpdates); - }; - - var getquestionSubscribeSidebarCheckbox= function(){ - return $('#' + questionSubscribeSidebar); - }; - - var getremoveAnswersLinks = function(){ - var removeAnswerLinks = 'div.answer-controls a[id^='+ removeAnswerLinkIdPrefix +']'; - return $(removeAnswerLinks); - }; - - var setVoteImage = function(voteType, undo, object){ - var flag = undo ? false : true; - if (object.hasClass("on")) { - object.removeClass("on"); - }else{ - object.addClass("on"); - } - - if(undo){ - if(voteType == VoteType.questionUpVote || voteType == VoteType.questionDownVote){ - $(getQuestionVoteUpButton()).removeClass("on"); - $(getQuestionVoteDownButton()).removeClass("on"); - } - else{ - $(getAnswerVoteUpButton(postId)).removeClass("on"); - $(getAnswerVoteDownButton(postId)).removeClass("on"); - } - } - }; - - var setVoteNumber = function(object, number){ - var voteNumber = object.parents('div.'+ voteContainerId).find('li.'+ voteNumberClass); - $(voteNumber).text(number); - }; - - var bindEvents = function(){ - // accept answers - var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']'; - $(acceptedButtons).unbind('click').click(function(event){ - Vote.accept($(event.target)); - }); - // set favorite question - var favoriteButton = getFavoriteButton(); - favoriteButton.unbind('click').click(function(event){ - //Vote.favorite($(event.target)); - Vote.favorite(favoriteButton); - }); - - // question vote up - var questionVoteUpButton = getQuestionVoteUpButton(); - questionVoteUpButton.unbind('click').click(function(event){ - Vote.vote($(event.target), VoteType.questionUpVote); - }); - - var questionVoteDownButton = getQuestionVoteDownButton(); - questionVoteDownButton.unbind('click').click(function(event){ - Vote.vote($(event.target), VoteType.questionDownVote); - }); - - var answerVoteUpButton = getAnswerVoteUpButtons(); - answerVoteUpButton.unbind('click').click(function(event){ - Vote.vote($(event.target), VoteType.answerUpVote); - }); - - var answerVoteDownButton = getAnswerVoteDownButtons(); - answerVoteDownButton.unbind('click').click(function(event){ - Vote.vote($(event.target), VoteType.answerDownVote); - }); - - getOffensiveQuestionFlag().unbind('click').click(function(event){ - Vote.offensive(this, VoteType.offensiveQuestion); - }); - - getRemoveOffensiveQuestionFlag().unbind('click').click(function(event){ - Vote.remove_offensive(this, VoteType.removeOffensiveQuestion); - }); - - getRemoveAllOffensiveQuestionFlag().unbind('click').click(function(event){ - Vote.remove_all_offensive(this, VoteType.removeAllOffensiveQuestion); - }); - - getOffensiveAnswerFlags().unbind('click').click(function(event){ - Vote.offensive(this, VoteType.offensiveAnswer); - }); - - getRemoveOffensiveAnswerFlag().unbind('click').click(function(event){ - Vote.remove_offensive(this, VoteType.removeOffensiveAnswer); - }); - - getRemoveAllOffensiveAnswerFlag().unbind('click').click(function(event){ - Vote.remove_all_offensive(this, VoteType.removeAllOffensiveAnswer); - }); - - getremoveQuestionLink().unbind('click').click(function(event){ - Vote.remove(this, VoteType.removeQuestion); - }); - - getquestionSubscribeUpdatesCheckbox().unbind('click').click(function(event){ - //despeluchar esto - if (this.checked){ - getquestionSubscribeSidebarCheckbox().attr({'checked': true}); - Vote.vote($(event.target), VoteType.questionSubscribeUpdates); - } - else { - getquestionSubscribeSidebarCheckbox().attr({'checked': false}); - Vote.vote($(event.target), VoteType.questionUnsubscribeUpdates); - } - }); - - getquestionSubscribeSidebarCheckbox().unbind('click').click(function(event){ - if (this.checked){ - getquestionSubscribeUpdatesCheckbox().attr({'checked': true}); - Vote.vote($(event.target), VoteType.questionSubscribeUpdates); - } - else { - getquestionSubscribeUpdatesCheckbox().attr({'checked': false}); - Vote.vote($(event.target), VoteType.questionUnsubscribeUpdates); - } - }); - - getremoveAnswersLinks().unbind('click').click(function(event){ - Vote.remove(this, VoteType.removeAnswer); - }); - }; - - var submit = function(object, voteType, callback) { - //this function submits votes - $.ajax({ - type: "POST", - cache: false, - dataType: "json", - url: askbot['urls']['vote_url_template'].replace('{{QuestionID}}', questionId), - data: { "type": voteType, "postId": postId }, - error: handleFail, - success: function(data){callback(object, voteType, data);} - }); - }; - - var handleFail = function(xhr, msg){ - alert("Callback invoke error: " + msg); - }; - - // callback function for Accept Answer action - var callback_accept = function(object, voteType, data){ - if(data.allowed == "0" && data.success == "0"){ - showMessage(object, acceptAnonymousMessage); - } - else if(data.allowed == "-1"){ - showMessage(object, acceptOwnAnswerMessage); - } - else if(data.status == "1"){ - object.attr("src", mediaUrl("media/images/vote-accepted.png")); - $("#"+answerContainerIdPrefix+postId).removeClass("accepted-answer"); - $("#"+commentLinkIdPrefix+postId).removeClass("comment-link-accepted"); - } - else if(data.success == "1"){ - var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']'; - $(acceptedButtons).attr("src", mediaUrl("media/images/vote-accepted.png")); - var answers = ("div[id^="+answerContainerIdPrefix +"]"); - $(answers).removeClass("accepted-answer"); - var commentLinks = ("div[id^="+answerContainerIdPrefix +"] div[id^="+ commentLinkIdPrefix +"]"); - $(commentLinks).removeClass("comment-link-accepted"); - - object.attr("src", mediaUrl("media/images/vote-accepted-on.png")); - $("#"+answerContainerIdPrefix+postId).addClass("accepted-answer"); - $("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted"); - } - else{ - showMessage(object, data.message); - } - }; - - var callback_favorite = function(object, voteType, data){ - if(data.allowed == "0" && data.success == "0"){ - showMessage( - object, - favoriteAnonymousMessage.replace( - '{{QuestionID}}', - questionId).replace( - '{{questionSlug}}', - '' - ) - ); - } - else if(data.status == "1"){ - var follow_html = gettext('Follow'); - object.attr("class", 'button follow'); - object.html(follow_html); - var fav = getFavoriteNumber(); - fav.removeClass("my-favorite-number"); - if(data.count === 0){ - data.count = ''; - fav.text(''); - }else{ - var fmts = ngettext('%s follower', '%s followers', data.count); - fav.text(interpolate(fmts, [data.count])); - } - } - else if(data.success == "1"){ - var followed_html = gettext('
      Unfollow
      '); - object.html(followed_html); - object.attr("class", 'button followed'); - var fav = getFavoriteNumber(); - var fmts = ngettext('%s follower', '%s followers', data.count); - fav.text(interpolate(fmts, [data.count])); - fav.addClass("my-favorite-number"); - } - else{ - showMessage(object, data.message); - } - }; - - var callback_vote = function(object, voteType, data){ - if (data.success == '0'){ - showMessage(object, data.message); - return; - } - else { - if (data.status == '1'){ - setVoteImage(voteType, true, object); - } - else { - setVoteImage(voteType, false, object); - } - setVoteNumber(object, data.count); - if (data.message && data.message.length > 0){ - showMessage(object, data.message); - } - return; - } - //may need to take a look at this again - if (data.status == "1"){ - setVoteImage(voteType, true, object); - setVoteNumber(object, data.count); - } - else if (data.success == "1"){ - setVoteImage(voteType, false, object); - setVoteNumber(object, data.count); - if (data.message.length > 0){ - showMessage(object, data.message); - } - } - }; - - var callback_offensive = function(object, voteType, data){ - //todo: transfer proper translations of these from i18n.js - //to django.po files - //_('anonymous users cannot flag offensive posts') + pleaseLogin; - if (data.success == "1"){ - if(data.count > 0) - $(object).children('span[class=darkred]').text("("+ data.count +")"); - else - $(object).children('span[class=darkred]').text(""); - - // Change the link text and rebind events - $(object).find("a.question-flag").html(gettext("remove flag")); - var obj_id = $(object).attr("id"); - $(object).attr("id", obj_id.replace("flag-", "remove-flag-")); - - getRemoveOffensiveQuestionFlag().unbind('click').click(function(event){ - Vote.remove_offensive(this, VoteType.removeOffensiveQuestion); - }); - - getRemoveOffensiveAnswerFlag().unbind('click').click(function(event){ - Vote.remove_offensive(this, VoteType.removeOffensiveAnswer); - }); - } - else { - object = $(object); - showMessage(object, data.message) - } - }; - - var callback_remove_offensive = function(object, voteType, data){ - //todo: transfer proper translations of these from i18n.js - //to django.po files - //_('anonymous users cannot flag offensive posts') + pleaseLogin; - if (data.success == "1"){ - if(data.count > 0){ - $(object).children('span[class=darkred]').text("("+ data.count +")"); - } - else{ - $(object).children('span[class=darkred]').text(""); - // Remove "remove all flags link" since there are no more flags to remove - var remove_all = $(object).siblings("span.offensive-flag[id*=-offensive-remove-all-flag-]"); - $(remove_all).next("span.sep").remove(); - $(remove_all).remove(); - } - // Change the link text and rebind events - $(object).find("a.question-flag").html(gettext("flag offensive")); - var obj_id = $(object).attr("id"); - $(object).attr("id", obj_id.replace("remove-flag-", "flag-")); - - getOffensiveQuestionFlag().unbind('click').click(function(event){ - Vote.offensive(this, VoteType.offensiveQuestion); - }); - - getOffensiveAnswerFlags().unbind('click').click(function(event){ - Vote.offensive(this, VoteType.offensiveAnswer); - }); - } - else { - object = $(object); - showMessage(object, data.message) - } - }; - - var callback_remove_all_offensive = function(object, voteType, data){ - //todo: transfer proper translations of these from i18n.js - //to django.po files - //_('anonymous users cannot flag offensive posts') + pleaseLogin; - if (data.success == "1"){ - if(data.count > 0) - $(object).children('span[class=darkred]').text("("+ data.count +")"); - else - $(object).children('span[class=darkred]').text(""); - // remove the link. All flags are gone - var remove_own = $(object).siblings("span.offensive-flag[id*=-offensive-remove-flag-]") - $(remove_own).find("a.question-flag").html(gettext("flag offensive")); - $(remove_own).attr("id", $(remove_own).attr("id").replace("remove-flag-", "flag-")); - - $(object).next("span.sep").remove(); - $(object).remove(); - - - - getOffensiveQuestionFlag().unbind('click').click(function(event){ - Vote.offensive(this, VoteType.offensiveQuestion); - }); - - getOffensiveAnswerFlags().unbind('click').click(function(event){ - Vote.offensive(this, VoteType.offensiveAnswer); - }); - } - else { - object = $(object); - showMessage(object, data.message) - } - }; - - var callback_remove = function(object, voteType, data){ - if (data.success == "1"){ - if (removeActionType == 'delete'){ - postNode.addClass('deleted'); - postRemoveLink.innerHTML = gettext('undelete'); - showMessage(object, deletedMessage); - } - else if (removeActionType == 'undelete') { - postNode.removeClass('deleted'); - postRemoveLink.innerHTML = gettext('delete'); - showMessage(object, recoveredMessage); - } - } - else { - showMessage(object, data.message) - } - }; - - return { - init : function(qId, qSlug, questionAuthor, userId){ - questionId = qId; - questionSlug = qSlug; - questionAuthorId = questionAuthor; - currentUserId = userId; - bindEvents(); - }, - - //accept answer - accept: function(object){ - postId = object.attr("id").substring(imgIdPrefixAccept.length); - submit(object, VoteType.acceptAnswer, callback_accept); - }, - //mark question as favorite - favorite: function(object){ - if (!currentUserId || currentUserId.toUpperCase() == "NONE"){ - showMessage( - object, - favoriteAnonymousMessage.replace( - "{{QuestionID}}", - questionId - ).replace( - '{{questionSlug}}', - questionSlug - ) - ); - return false; - } - submit(object, VoteType.favorite, callback_favorite); - }, - - vote: function(object, voteType){ - if (!currentUserId || currentUserId.toUpperCase() == "NONE"){ - if (voteType == VoteType.questionSubscribeUpdates || voteType == VoteType.questionUnsubscribeUpdates){ - getquestionSubscribeSidebarCheckbox().removeAttr('checked'); - getquestionSubscribeUpdatesCheckbox().removeAttr('checked'); - showMessage(object, subscribeAnonymousMessage); - }else { - showMessage( - $(object), - voteAnonymousMessage.replace( - "{{QuestionID}}", - questionId - ).replace( - '{{questionSlug}}', - questionSlug - ) - ); - } - return false; - } - // up and downvote processor - if (voteType == VoteType.answerUpVote){ - postId = object.attr("id").substring(imgIdPrefixAnswerVoteup.length); - } - else if (voteType == VoteType.answerDownVote){ - postId = object.attr("id").substring(imgIdPrefixAnswerVotedown.length); - } - - submit(object, voteType, callback_vote); - }, - //flag offensive - offensive: function(object, voteType){ - if (!currentUserId || currentUserId.toUpperCase() == "NONE"){ - showMessage( - $(object), - offensiveAnonymousMessage.replace( - "{{QuestionID}}", - questionId - ).replace( - '{{questionSlug}}', - questionSlug - ) - ); - return false; - } - if (confirm(offensiveConfirmation)){ - postId = object.id.substr(object.id.lastIndexOf('-') + 1); - submit(object, voteType, callback_offensive); - } - }, - //remove flag offensive - remove_offensive: function(object, voteType){ - if (!currentUserId || currentUserId.toUpperCase() == "NONE"){ - showMessage( - $(object), - offensiveAnonymousMessage.replace( - "{{QuestionID}}", - questionId - ).replace( - '{{questionSlug}}', - questionSlug - ) - ); - return false; - } - if (confirm(removeOffensiveConfirmation)){ - postId = object.id.substr(object.id.lastIndexOf('-') + 1); - submit(object, voteType, callback_remove_offensive); - } - }, - remove_all_offensive: function(object, voteType){ - if (!currentUserId || currentUserId.toUpperCase() == "NONE"){ - showMessage( - $(object), - offensiveAnonymousMessage.replace( - "{{QuestionID}}", - questionId - ).replace( - '{{questionSlug}}', - questionSlug - ) - ); - return false; - } - if (confirm(removeOffensiveConfirmation)){ - postId = object.id.substr(object.id.lastIndexOf('-') + 1); - submit(object, voteType, callback_remove_all_offensive); - } - }, - //delete question or answer (comments are deleted separately) - remove: function(object, voteType){ - if (!currentUserId || currentUserId.toUpperCase() == "NONE"){ - showMessage( - $(object), - removeAnonymousMessage.replace( - '{{QuestionID}}', - questionId - ).replace( - '{{questionSlug}}', - questionSlug - ) - ); - return false; - } - bits = object.id.split('-'); - postId = bits.pop();/* this seems to be used within submit! */ - postType = bits.shift(); - - var do_proceed = false; - if (postType == 'answer'){ - postNode = $('#post-id-' + postId); - } - else if (postType == 'question'){ - postNode = $('#question-table'); - } - postRemoveLink = object; - if (postNode.hasClass('deleted')){ - removeActionType = 'undelete'; - do_proceed = true; - } - else { - removeActionType = 'delete'; - do_proceed = confirm(removeConfirmation); - } - if (do_proceed) { - submit($(object), voteType, callback_remove); - } - } - }; -} (); - -var questionRetagger = function(){ - - var oldTagsHTML = ''; - var tagInput = null; - var tagsDiv = null; - var retagLink = null; - - var restoreEventHandlers = function(){ - $(document).unbind('click'); - }; - - var cancelRetag = function(){ - tagsDiv.html(oldTagsHTML); - tagsDiv.removeClass('post-retag'); - tagsDiv.addClass('post-tags'); - restoreEventHandlers(); - initRetagger(); - }; - - var render_tag = function(tag_name){ - //copy-paste from live search!!! - var tag = new Tag(); - tag.setName(tag_name); - return tag.getElement().outerHTML(); - }; - - var drawNewTags = function(new_tags){ - new_tags = new_tags.split(/\s+/); - var tags_html = '' - $.each(new_tags, function(index, name){ - tags_html += render_tag(name); - }); - tagsDiv.html(tags_html); - }; - - var doRetag = function(){ - $.ajax({ - type: "POST", - url: retagUrl, - dataType: "json", - data: { tags: getUniqueWords(tagInput.val()).join(' ') }, - success: function(json) { - if (json['success'] === true){ - new_tags = getUniqueWords(json['new_tags']); - oldTagsHtml = ''; - cancelRetag(); - drawNewTags(new_tags.join(' ')); - } - else { - cancelRetag(); - showMessage(tagsDiv, json['message']); - } - }, - error: function(xhr, textStatus, errorThrown) { - showMessage(tagsDiv, 'sorry, somethin is not right here'); - cancelRetag(); - } - }); - return false; - } - - var setupInputEventHandlers = function(input){ - input.keydown(function(e){ - if ((e.which && e.which == 27) || (e.keyCode && e.keyCode == 27)){ - cancelRetag(); - } - }); - $(document).unbind('click').click(cancelRetag, false); - input.click(function(){return false}); - }; - - var createRetagForm = function(old_tags_string){ - var div = $('
      '); - tagInput = $(''); - //var tagLabel = $(''); - tagInput.val(old_tags_string); - //populate input - var tagAc = new AutoCompleter({ - url: askbot['urls']['get_tag_list'], - preloadData: true, - minChars: 1, - useCache: true, - matchInside: true, - maxCacheLength: 100, - delay: 10 - }); - tagAc.decorate(tagInput); - div.append(tagInput); - //div.append(tagLabel); - setupInputEventHandlers(tagInput); - - //button = $(''); - //button.val(gettext('save tags')); - //div.append(button); - //setupButtonEventHandlers(button); - div.validate({//copy-paste from utils.js - rules: { - tags: { - required: true, - maxlength: askbot['settings']['maxTagsPerPost'] * askbot['settings']['maxTagLength'], - limit_tag_count: true, - limit_tag_length: true - } - }, - messages: { - tags: { - required: gettext('tags cannot be empty'), - maxlength: askbot['messages']['tagLimits'], - limit_tag_count: askbot['messages']['maxTagsPerPost'], - limit_tag_length: askbot['messages']['maxTagLength'] - } - }, - submitHandler: doRetag, - errorClass: "retag-error" - }); - - return div; - }; - - var getTagsAsString = function(tags_div){ - var links = tags_div.find('a'); - var tags_str = ''; - links.each(function(index, element){ - if (index === 0){ - tags_str = $(element).html(); - } - else { - tags_str += ' ' + $(element).html(); - } - }); - return tags_str; - }; - - var noopHandler = function(){ - tagInput.focus(); - return false; - }; - - var deactivateRetagLink = function(){ - retagLink.unbind('click').click(noopHandler); - retagLink.unbind('keypress').keypress(noopHandler); - }; - - var startRetag = function(){ - tagsDiv = $('#question-tags'); - oldTagsHTML = tagsDiv.html();//save to restore on cancel - var old_tags_string = getTagsAsString(tagsDiv); - var retag_form = createRetagForm(old_tags_string); - tagsDiv.html(''); - tagsDiv.append(retag_form); - tagsDiv.removeClass('post-tags'); - tagsDiv.addClass('post-retag'); - tagInput.focus(); - deactivateRetagLink(); - return false; - }; - - var setupClickAndEnterHandler = function(element, callback){ - element.unbind('click').click(callback); - element.unbind('keypress').keypress(function(e){ - if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)){ - callback(); - } - }); - } - - var initRetagger = function(){ - setupClickAndEnterHandler(retagLink, startRetag); - }; - - return { - init: function(){ - retagLink = $('#retag'); - initRetagger(); - } - }; -}(); - -var DeletePostLink = function(){ - SimpleControl.call(this); - this._post_id = null; -}; -inherits(DeletePostLink, SimpleControl); - -DeletePostLink.prototype.setPostId = function(id){ - this._post_id = id; -}; - -DeletePostLink.prototype.getPostId = function(){ - return this._post_id; -}; - -DeletePostLink.prototype.getPostElement = function(){ - return $('#post-id-' + this.getPostId()); -}; - -DeletePostLink.prototype.isPostDeleted = function(){ - return this._post_deleted; -}; - -DeletePostLink.prototype.setPostDeleted = function(is_deleted){ - var post = this.getPostElement(); - if (is_deleted === true){ - post.addClass('deleted'); - this._post_deleted = true; - this.getElement().html(gettext('undelete')); - } else if (is_deleted === false){ - post.removeClass('deleted'); - this._post_deleted = false; - this.getElement().html(gettext('delete')); - } -}; - -DeletePostLink.prototype.getDeleteHandler = function(){ - var me = this; - var post_id = this.getPostId(); - return function(){ - var data = { - 'post_id': me.getPostId(), - //todo rename cancel_vote -> undo - 'cancel_vote': me.isPostDeleted() ? true: false - }; - $.ajax({ - type: 'POST', - data: data, - dataType: 'json', - url: askbot['urls']['delete_post'], - cache: false, - success: function(data){ - if (data['success'] == true){ - me.setPostDeleted(data['is_deleted']); - } else { - showMessage(me.getElement(), data['message']); - } - } - }); - }; -}; - -DeletePostLink.prototype.decorate = function(element){ - this._element = element; - this._post_deleted = this.getPostElement().hasClass('deleted'); - this.setHandler(this.getDeleteHandler()); -} - -//constructor for the form -var EditCommentForm = function(){ - WrappedElement.call(this); - this._comment = null; - this._comment_widget = null; - this._element = null; - this._text = ''; - this._id = 'edit-comment-form'; -}; -inherits(EditCommentForm, WrappedElement); - -EditCommentForm.prototype.getElement = function(){ - EditCommentForm.superClass_.getElement.call(this); - this._textarea.val(this._text); - return this._element; -}; - -EditCommentForm.prototype.attachTo = function(comment, mode){ - this._comment = comment; - this._type = mode; - this._comment_widget = comment.getContainerWidget(); - this._text = comment.getText(); - comment.getElement().after(this.getElement()); - comment.getElement().hide(); - this._comment_widget.hideButton(); - if (this._type == 'add'){ - this._submit_btn.html(gettext('add comment')); - } - else { - this._submit_btn.html(gettext('save comment')); - } - this.getElement().show(); - this.focus(); - putCursorAtEnd(this._textarea); -}; - -EditCommentForm.prototype.getCounterUpdater = function(){ - //returns event handler - var counter = this._text_counter; - var handler = function(){ - var textarea = $(this); - var length = textarea.val() ? textarea.val().length : 0; - var length1 = maxCommentLength - 100; - if (length1 < 0){ - length1 = Math.round(0.7*maxCommentLength); - } - var length2 = maxCommentLength - 30; - if (length2 < 0){ - length2 = Math.round(0.9*maxCommentLength); - } - - //todo: - //1) use class instead of color - move color def to css - // var color = 'maroon'; - var chars = 10; - if (length === 0){ - var feedback = interpolate(gettext('%s title minchars'), [chars]); - } - else if (length < 10){ - var feedback = interpolate(gettext('enter %s more characters'), [chars - length]); - } - else { - color = length > length2 ? "#f00" : length > length1 ? "#f60" : "#999" - chars = maxCommentLength - length - var feedback = interpolate(gettext('%s characters left'), [chars]) - } - /* counter.html(feedback).css('color', color) */ - }; - return handler; -}; - -EditCommentForm.prototype.canCancel = function(){ - if (this._element === null){ - return true; - } - var ctext = $.trim(this._textarea.val()); - if ($.trim(ctext) == $.trim(this._text)){ - return true; - } - else if (this.confirmAbandon()){ - return true; - } - this.focus(); - return false; -}; - -EditCommentForm.prototype.getCancelHandler = function(){ - var form = this; - return function(){ - if (form.canCancel()){ - form.detach(); - } - return false; - }; -}; - -EditCommentForm.prototype.detach = function(){ - if (this._comment === null){ - return; - } - this._comment.getContainerWidget().showButton(); - if (this._comment.isBlank()){ - this._comment.dispose(); - } - else { - this._comment.getElement().show(); - } - this.reset(); - this._element = this._element.detach(); -}; - -EditCommentForm.prototype.createDom = function(){ - this._element = $('
      '); - this._element.attr('class', 'post-comments block'); - - var div = $('
      '); - this._textarea = $(''); - this._textarea.attr('id', this._id); - - /* - this._help_text = $('').attr('class', 'help-text'); - this._help_text.html(gettext('Markdown is allowed in the comments')); - div.append(this._help_text); - - this._help_text = $('
      ').attr('class', 'clearfix'); - div.append(this._help_text); - */ - - this._element.append(div); - div.append(this._textarea); - this._text_counter = $('').attr('class', 'counter'); - div.append(this._text_counter); - this._submit_btn = $(''); - div.append(this._submit_btn); - this._cancel_btn = $(''); - this._cancel_btn.html(gettext('cancel')); - div.append(this._cancel_btn); - - setupButtonEventHandlers(this._submit_btn, this.getSaveHandler()); - setupButtonEventHandlers(this._cancel_btn, this.getCancelHandler()); - - var update_counter = this.getCounterUpdater(); - var escape_handler = makeKeyHandler(27, this.getCancelHandler()); - this._textarea.attr('name', 'comment') - /* .attr('maxlength', maxCommentLength) */ - .blur(update_counter) - .focus(update_counter) - .keyup(update_counter) - .keyup(escape_handler); - if (askbot['settings']['saveCommentOnEnter']){ - var save_handler = makeKeyHandler(13, this.getSaveHandler()); - this._textarea.keydown(save_handler); - } - this._textarea.val(this._text); -}; - -EditCommentForm.prototype.enableButtons = function(){ - this._submit_btn.attr('disabled', ''); - this._cancel_btn.attr('disabled', ''); -}; - -EditCommentForm.prototype.disableButtons = function(){ - this._submit_btn.attr('disabled', 'disabled'); - this._cancel_btn.attr('disabled', 'disabled'); -}; - -EditCommentForm.prototype.reset = function(){ - this._comment = null; - this._text = ''; - this._textarea.val(''); - this.enableButtons(); -}; - -EditCommentForm.prototype.confirmAbandon = function(){ - this.focus(true); - this._textarea.addClass('highlight'); - var answer = confirm(gettext('confirm abandon comment')); - this._textarea.removeClass('highlight'); - return answer; -}; - -EditCommentForm.prototype.focus = function(hard){ - this._textarea.focus(); - if (hard === true){ - $(this._textarea).scrollTop(); - } -}; - -EditCommentForm.prototype.getSaveHandler = function(){ - - var me = this; - return function(){ - var text = me._textarea.val(); - if (text.length < 1){ - me.focus(); - return false; - } - - var post_data = { - comment: text - }; - - if (me._type == 'edit'){ - post_data['comment_id'] = me._comment.getId(); - post_url = askbot['urls']['editComment']; - } - else { - post_data['post_type'] = me._comment.getParentType(); - post_data['post_id'] = me._comment.getParentId(); - post_url = askbot['urls']['postComments']; - } - - me.disableButtons(); - - $.ajax({ - type: "POST", - url: post_url, - dataType: "json", - data: post_data, - success: function(json) { - if (me._type == 'add'){ - me._comment.dispose(); - me._comment.getContainerWidget().reRenderComments(json); - } - else { - me._comment.setContent(json); - me._comment.getElement().show(); - } - me.detach(); - }, - error: function(xhr, textStatus, errorThrown) { - me._comment.getElement().show(); - showMessage(me._comment.getElement(), xhr.responseText, 'after'); - me.detach(); - me.enableButtons(); - } - }); - return false; - }; -}; - -//a single instance to reuse -var editCommentForm = new EditCommentForm(); - -var Comment = function(widget, data){ - WrappedElement.call(this); - this._container_widget = widget; - this._data = data || {}; - this._blank = true;//set to false by setContent - this._element = null; - this._delete_prompt = gettext('delete this comment'); - if (data && data['is_deletable']){ - this._deletable = data['is_deletable']; - } - else { - this._deletable = false; - } - if (data && data['is_editable']){ - this._editable = data['is_deletable']; - } - else { - this._editable = false; - } -}; -inherits(Comment, WrappedElement); - -Comment.prototype.decorate = function(element){ - this._element = $(element); - var parent_type = this._element.parent().parent().attr('id').split('-')[2]; - var comment_id = this._element.attr('id').replace('comment-',''); - this._data = {id: comment_id}; - var delete_img = this._element.find('span.delete-icon'); - if (delete_img.length > 0){ - this._deletable = true; - this._delete_icon = new DeleteIcon(this.deletePrompt); - this._delete_icon.setHandler(this.getDeleteHandler()); - this._delete_icon.decorate(delete_img); - } - var edit_link = this._element.find('a.edit'); - if (edit_link.length > 0){ - this._editable = true; - this._edit_link = new EditLink(); - this._edit_link.setHandler(this.getEditHandler()); - this._edit_link.decorate(edit_link); - } - - var vote = new CommentVoteButton(this); - vote.decorate(this._element.find('.comment-votes .upvote')); - - this._blank = false; -}; - -Comment.prototype.isBlank = function(){ - return this._blank; -}; - -Comment.prototype.getId = function(){ - return this._data['id']; -}; - -Comment.prototype.hasContent = function(){ - return ('id' in this._data); - //shortcut for 'user_url' 'html' 'user_display_name' 'comment_age' -}; - -Comment.prototype.hasText = function(){ - return ('text' in this._data); -} - -Comment.prototype.getContainerWidget = function(){ - return this._container_widget; -}; - -Comment.prototype.getParentType = function(){ - return this._container_widget.getPostType(); -}; - -Comment.prototype.getParentId = function(){ - return this._container_widget.getPostId(); -}; - -Comment.prototype.setContent = function(data){ - this._data = data || this._data; - this._element.html(''); - this._element.attr('class', 'comment block'); - this._element.attr('id', 'comment-' + this._data['id']); - - - this._comment_controls = $(''); - this._element.append(this._comment_controls); - - - var votes = this.makeElement('div'); - votes.addClass('comment-votes'); - - var vote = new CommentVoteButton(this); - if (this._data['upvoted_by_user']){ - vote.setVoted(true); - } - vote.setScore(this._data['score']); - votes.append(vote.getElement()); - - /* this._element.append(votes); */ - - this._comment_delete = $('
      '); - this._comment_edit = $('
      '); - this._comment_meta = $('
      '); - if (this._deletable){ - this._delete_icon = new DeleteIcon(this._delete_prompt); - this._delete_icon.setHandler(this.getDeleteHandler()); - this._comment_delete.append(this._delete_icon.getElement()); - $(this).find('.delete-icon').append('✖'); - } - this._comment_controls.append(this._comment_delete); - this._comment_controls.append(this._comment_edit); - - this._comment_body = $('
      '); - this._comment_body.html(this._data['html']); - //this._comment_body.append(' – '); - this._comment_body.append(this._comment_meta); - - this._user_link = $('').attr('class', 'author'); - this._user_link.attr('href', this._data['user_url']); - this._user_link.html(this._data['user_display_name']); - this._comment_meta.append(this._user_link); - - this._comment_age = $(''); - this._comment_age.html(this._data['comment_age']); - this._comment_meta.append(' ('); - this._comment_meta.append(this._comment_age); - this._comment_meta.append(')'); - - if (this._editable){ - this._edit_link = new EditLink(); - this._edit_link.setHandler(this.getEditHandler()); - this._comment_edit.append(this._edit_link.getElement()); - } - this._element.append(this._comment_body); - - this._blank = false; -}; - -Comment.prototype.dispose = function(){ - if (this._comment_body){ - this._comment_body.remove(); - } - if (this._comment_delete){ - this._comment_delete.remove(); - } - if (this._user_link){ - this._user_link.remove(); - } - if (this._comment_age){ - this._comment_age.remove(); - } - if (this._delete_icon){ - this._delete_icon.dispose(); - } - if (this._edit_link){ - this._edit_link.dispose(); - } - this._data = null; - Comment.superClass_.dispose.call(this); -}; - -Comment.prototype.getElement = function(){ - Comment.superClass_.getElement.call(this); - if (this.isBlank() && this.hasContent()){ - this.setContent(); - if (enableMathJax === true){ - MathJax.Hub.Queue(['Typeset', MathJax.Hub]); - } - } - return this._element; -}; - -Comment.prototype.loadText = function(on_load_handler){ - var me = this; - $.ajax({ - type: "GET", - url: askbot['urls']['getComment'], - data: {id: this._data['id']}, - success: function(json){ - me._data['text'] = json['text']; - on_load_handler() - }, - error: function(xhr, textStatus, exception) { - showMessage(me.getElement(), xhr.responseText, 'after'); - } - }); -}; - -Comment.prototype.getText = function(){ - if (!this.isBlank()){ - if ('text' in this._data){ - return this._data['text']; - } - } - return ''; -} - -Comment.prototype.getEditHandler = function(){ - var comment = this; - return function(){ - if (editCommentForm.canCancel()){ - editCommentForm.detach(); - if (comment.hasText()){ - editCommentForm.attachTo(comment, 'edit'); - } - else { - comment.loadText( - function(){ - editCommentForm.attachTo(comment, 'edit'); - } - ); - } - } - }; -}; - -Comment.prototype.getDeleteHandler = function(){ - var comment = this; - var del_icon = this._delete_icon; - return function(){ - if (confirm(gettext('confirm delete comment'))){ - comment.getElement().hide(); - $.ajax({ - type: 'POST', - url: askbot['urls']['deleteComment'], - data: { comment_id: comment.getId() }, - success: function(json, textStatus, xhr) { - comment.dispose(); - }, - error: function(xhr, textStatus, exception) { - comment.getElement().show() - showMessage(del_icon.getElement(), xhr.responseText); - }, - dataType: "json" - }); - } - }; -}; - -var PostCommentsWidget = function(){ - WrappedElement.call(this); - this._denied = false; -}; -inherits(PostCommentsWidget, WrappedElement); - -PostCommentsWidget.prototype.decorate = function(element){ - var element = $(element); - this._element = element; - - var widget_id = element.attr('id'); - var id_bits = widget_id.split('-'); - this._post_id = id_bits[3]; - this._post_type = id_bits[2]; - this._is_truncated = askbot['data'][widget_id]['truncated']; - this._user_can_post = askbot['data'][widget_id]['can_post']; - - //see if user can comment here - var controls = element.find('.controls'); - this._activate_button = controls.find('.light-button'); - - if (this._user_can_post == false){ - setupButtonEventHandlers( - this._activate_button, - this.getReadOnlyLoadHandler() - ); - } - else { - setupButtonEventHandlers( - this._activate_button, - this.getActivateHandler() - ); - } - - this._cbox = element.find('.comments-content'); - var comments = new Array(); - var me = this; - this._cbox.children().each(function(index, element){ - var comment = new Comment(me); - comments.push(comment) - comment.decorate(element); - }); - this._comments = comments; -}; - -PostCommentsWidget.prototype.getPostType = function(){ - return this._post_type; -}; - -PostCommentsWidget.prototype.getPostId = function(){ - return this._post_id; -}; - -PostCommentsWidget.prototype.hideButton = function(){ - this._activate_button.hide(); -}; - -PostCommentsWidget.prototype.showButton = function(){ - if (this._is_truncated === false){ - this._activate_button.html(askbot['messages']['addComment']); - } - this._activate_button.show(); -} - -PostCommentsWidget.prototype.startNewComment = function(){ - var comment = new Comment(this); - this._cbox.append(comment.getElement()); - editCommentForm.attachTo(comment, 'add'); -}; - -PostCommentsWidget.prototype.needToReload = function(){ - return this._is_truncated; -}; - -PostCommentsWidget.prototype.getActivateHandler = function(){ - var me = this; - return function() { - if (editCommentForm.canCancel()){ - editCommentForm.detach(); - if (me.needToReload()){ - me.reloadAllComments(function(json){ - me.reRenderComments(json); - me.startNewComment(); - }); - } - else { - me.startNewComment(); - } - } - }; -}; - -PostCommentsWidget.prototype.getReadOnlyLoadHandler = function(){ - var me = this; - return function(){ - me.reloadAllComments(function(json){ - me.reRenderComments(json); - me._activate_button.remove(); - }); - }; -}; - - -PostCommentsWidget.prototype.reloadAllComments = function(callback){ - var post_data = {post_id: this._post_id, post_type: this._post_type}; - var me = this; - $.ajax({ - type: "GET", - url: askbot['urls']['postComments'], - data: post_data, - success: function(json){ - callback(json); - me._is_truncated = false; - }, - dataType: "json" - }); -}; - -PostCommentsWidget.prototype.reRenderComments = function(json){ - $.each(this._comments, function(i, item){ - item.dispose(); - }); - this._comments = new Array(); - var me = this; - $.each(json, function(i, item){ - var comment = new Comment(me, item); - me._cbox.append(comment.getElement()); - me._comments.push(comment); - }); -}; - - -var socialSharing = function(){ - - var SERVICE_DATA = { - //url - template for the sharing service url, params are for the popup - identica: { - url: "http://identi.ca/notice/new?status_textarea={TEXT}%20{URL}", - params: "width=820, height=526,toolbar=1,status=1,resizable=1,scrollbars=1" - }, - twitter: { - url: "http://twitter.com/share?url={URL}&ref=twitbtn&text={TEXT}", - params: "width=820,height=526,toolbar=1,status=1,resizable=1,scrollbars=1" - }, - facebook: { - url: "http://www.facebook.com/sharer.php?u={URL}&ref=fbshare&t={TEXT}", - params: "width=630,height=436,toolbar=1,status=1,resizable=1,scrollbars=1" - }, - linkedin: { - url: "http://www.linkedin.com/shareArticle?mini=true&url={URL}&source={TEXT}", - params: "width=630,height=436,toolbar=1,status=1,resizable=1,scrollbars=1" - } - }; - var URL = ""; - var TEXT = ""; - - var share_page = function(service_name){ - if (SERVICE_DATA[service_name]){ - var url = SERVICE_DATA[service_name]['url']; - $.ajax({ - async: false, - url: "http://json-tinyurl.appspot.com/?&callback=?", - dataType: "json", - data: {'url':URL}, - success: function(data){ - url = url.replace('{URL}', data.tinyurl); - }, - error: function(data){ - url = url.replace('{URL}', URL); - }, - complete: function(data){ - url = url.replace('{TEXT}', TEXT); - var params = SERVICE_DATA[service_name]['params']; - if(!window.open(url, "sharing", params)){ - window.location.href=url; - } - } - }); - } - } - - return { - init: function(){ - URL = window.location.href; - TEXT = escape($('h1 > a').html()); - var fb = $('a.facebook-share') - var tw = $('a.twitter-share'); - var ln = $('a.linkedin-share'); - var ica = $('a.identica-share'); - copyAltToTitle(fb); - copyAltToTitle(tw); - copyAltToTitle(ln); - copyAltToTitle(ica); - setupButtonEventHandlers(fb, function(){share_page("facebook")}); - setupButtonEventHandlers(tw, function(){share_page("twitter")}); - setupButtonEventHandlers(ln, function(){share_page("linkedin")}); - setupButtonEventHandlers(ica, function(){share_page("identica")}); - } - } -}(); - -/** - * @constructor - * @extends {SimpleControl} - */ -var QASwapper = function(){ - SimpleControl.call(this); - this._ans_id = null; -}; -inherits(QASwapper, SimpleControl); - -QASwapper.prototype.decorate = function(element){ - this._element = element; - this._ans_id = parseInt(element.attr('id').split('-').pop()); - var me = this; - this.setHandler(function(){ - me.startSwapping(); - }); -}; - -QASwapper.prototype.startSwapping = function(){ - while (true){ - var title = prompt(gettext('Please enter question title (>10 characters)')); - if (title.length >= 10){ - var data = {new_title: title, answer_id: this._ans_id}; - $.ajax({ - type: "POST", - cache: false, - dataType: "json", - url: askbot['urls']['swap_question_with_answer'], - data: data, - success: function(data){ - var url_template = askbot['urls']['question_url_template']; - new_question_url = url_template.replace( - '{{QuestionID}}', - data['id'] - ).replace( - '{{questionSlug}}', - data['slug'] - ); - window.location.href = new_question_url; - } - }); - break; - } - } -}; - -$(document).ready(function() { - $('[id^="comments-for-"]').each(function(index, element){ - var comments = new PostCommentsWidget(); - comments.decorate(element); - }); - $('[id^="swap-question-with-answer-"]').each(function(idx, element){ - var swapper = new QASwapper(); - swapper.decorate($(element)); - }); - $('[id^="post-id-"]').each(function(idx, element){ - var deleter = new DeletePostLink(); - //confusingly .question-delete matches the answers too need rename - var post_id = element.id.split('-').pop(); - deleter.setPostId(post_id); - deleter.decorate($(element).find('.question-delete')); - }); - questionRetagger.init(); - socialSharing.init(); -}); - - -/* -Prettify -http://www.apache.org/licenses/LICENSE-2.0 -*/ -var PR_SHOULD_USE_CONTINUATION = true; var PR_TAB_WIDTH = 8; var PR_normalizedHtml; var PR; var prettyPrintOne; var prettyPrint; function _pr_isIE6() { var isIE6 = navigator && navigator.userAgent && /\bMSIE 6\./.test(navigator.userAgent); _pr_isIE6 = function() { return isIE6; }; return isIE6; } (function() { function wordSet(words) { words = words.split(/ /g); var set = {}; for (var i = words.length; --i >= 0; ) { var w = words[i]; if (w) { set[w] = null; } } return set; } var FLOW_CONTROL_KEYWORDS = "break continue do else for if return while "; var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " + "double enum extern float goto int long register short signed sizeof " + "static struct switch typedef union unsigned void volatile "; var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " + "new operator private protected public this throw true try "; var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " + "concept concept_map const_cast constexpr decltype " + "dynamic_cast explicit export friend inline late_check " + "mutable namespace nullptr reinterpret_cast static_assert static_cast " + "template typeid typename typeof using virtual wchar_t where "; var JAVA_KEYWORDS = COMMON_KEYWORDS + "boolean byte extends final finally implements import instanceof null " + "native package strictfp super synchronized throws transient "; var CSHARP_KEYWORDS = JAVA_KEYWORDS + "as base by checked decimal delegate descending event " + "fixed foreach from group implicit in interface internal into is lock " + "object out override orderby params readonly ref sbyte sealed " + "stackalloc string select uint ulong unchecked unsafe ushort var "; var JSCRIPT_KEYWORDS = COMMON_KEYWORDS + "debugger eval export function get null set undefined var with " + "Infinity NaN "; var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " + "goto if import last local my next no our print package redo require " + "sub undef unless until use wantarray while BEGIN END "; var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " + "elif except exec finally from global import in is lambda " + "nonlocal not or pass print raise try with yield " + "False True None "; var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" + " defined elsif end ensure false in module next nil not or redo rescue " + "retry self super then true undef unless until when yield BEGIN END "; var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " + "function in local set then until "; var ALL_KEYWORDS = (CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS + PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS); var PR_STRING = 'str'; var PR_KEYWORD = 'kwd'; var PR_COMMENT = 'com'; var PR_TYPE = 'typ'; var PR_LITERAL = 'lit'; var PR_PUNCTUATION = 'pun'; var PR_PLAIN = 'pln'; var PR_TAG = 'tag'; var PR_DECLARATION = 'dec'; var PR_SOURCE = 'src'; var PR_ATTRIB_NAME = 'atn'; var PR_ATTRIB_VALUE = 'atv'; var PR_NOCODE = 'nocode'; function isWordChar(ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); } function spliceArrayInto(inserted, container, containerPosition, countReplaced) { inserted.unshift(containerPosition, countReplaced || 0); try { container.splice.apply(container, inserted); } finally { inserted.splice(0, 2); } } var REGEXP_PRECEDER_PATTERN = function() { var preceders = ["!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=", "&=", "(", "*", "*=", "+=", ",", "-=", "->", "/", "/=", ":", "::", ";", "<", "<<", "<<=", "<=", "=", "==", "===", ">", ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[", "^", "^=", "^^", "^^=", "{", "|", "|=", "||", "||=", "~", "break", "case", "continue", "delete", "do", "else", "finally", "instanceof", "return", "throw", "try", "typeof"]; var pattern = '(?:' + '(?:(?:^|[^0-9.])\\.{1,3})|' + '(?:(?:^|[^\\+])\\+)|' + '(?:(?:^|[^\\-])-)'; for (var i = 0; i < preceders.length; ++i) { var preceder = preceders[i]; if (isWordChar(preceder.charAt(0))) { pattern += '|\\b' + preceder; } else { pattern += '|' + preceder.replace(/([^=<>:&])/g, '\\$1'); } } pattern += '|^)\\s*$'; return new RegExp(pattern); } (); var pr_amp = /&/g; var pr_lt = //g; var pr_quot = /\"/g; function attribToHtml(str) { return str.replace(pr_amp, '&').replace(pr_lt, '<').replace(pr_gt, '>').replace(pr_quot, '"'); } function textToHtml(str) { return str.replace(pr_amp, '&').replace(pr_lt, '<').replace(pr_gt, '>'); } var pr_ltEnt = /</g; var pr_gtEnt = />/g; var pr_aposEnt = /'/g; var pr_quotEnt = /"/g; var pr_ampEnt = /&/g; var pr_nbspEnt = / /g; function htmlToText(html) { var pos = html.indexOf('&'); if (pos < 0) { return html; } for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0; ) { var end = html.indexOf(';', pos); if (end >= 0) { var num = html.substring(pos + 3, end); var radix = 10; if (num && num.charAt(0) === 'x') { num = num.substring(1); radix = 16; } var codePoint = parseInt(num, radix); if (!isNaN(codePoint)) { html = (html.substring(0, pos) + String.fromCharCode(codePoint) + html.substring(end + 1)); } } } return html.replace(pr_ltEnt, '<').replace(pr_gtEnt, '>').replace(pr_aposEnt, "'").replace(pr_quotEnt, '"').replace(pr_ampEnt, '&').replace(pr_nbspEnt, ' '); } function isRawContent(node) { return 'XMP' === node.tagName; } function normalizedHtml(node, out) { switch (node.nodeType) { case 1: var name = node.tagName.toLowerCase(); out.push('<', name); for (var i = 0; i < node.attributes.length; ++i) { var attr = node.attributes[i]; if (!attr.specified) { continue; } out.push(' '); normalizedHtml(attr, out); } out.push('>'); for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } if (node.firstChild || !/^(?:br|link|img)$/.test(name)) { out.push('<\/', name, '>'); } break; case 2: out.push(node.name.toLowerCase(), '="', attribToHtml(node.value), '"'); break; case 3: case 4: out.push(textToHtml(node.nodeValue)); break; } } var PR_innerHtmlWorks = null; function getInnerHtml(node) { if (null === PR_innerHtmlWorks) { var testNode = document.createElement('PRE'); testNode.appendChild(document.createTextNode('\n')); PR_innerHtmlWorks = !/= 0; nSpaces -= SPACES.length) { out.push(SPACES.substring(0, nSpaces)); } pos = i + 1; break; case '\n': charInLine = 0; break; default: ++charInLine; } } if (!out) { return plainText; } out.push(plainText.substring(pos)); return out.join(''); }; } var pr_chunkPattern = /(?:[^<]+|||<\/?[a-zA-Z][^>]*>|<)/g; var pr_commentPrefix = /^|$)/, null], [PR_SOURCE, /^<\?[\s\S]*?(?:\?>|$)/, null], [PR_SOURCE, /^<%[\s\S]*?(?:%>|$)/, null], [PR_SOURCE, /^<(script|style|xmp)\b[^>]*>[\s\S]*?<\/\1\b[^>]*>/i, null], [PR_TAG, /^<\/?\w[^<>]*>/, null]]); var PR_SOURCE_CHUNK_PARTS = /^(<[^>]*>)([\s\S]*)(<\/[^>]*>)$/; function tokenizeMarkup(source) { var decorations = PR_MARKUP_LEXER(source); for (var i = 0; i < decorations.length; i += 2) { if (decorations[i + 1] === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var sourceChunk = source.substring(start, end); var match = sourceChunk.match(PR_SOURCE_CHUNK_PARTS); if (match) { decorations.splice(i, 2, start, PR_TAG, start + match[1].length, PR_SOURCE, start + match[1].length + (match[2] || '').length, PR_TAG); } } } return decorations; } var PR_TAG_LEXER = createSimpleLexer([[PR_ATTRIB_VALUE, /^\'[^\']*(?:\'|$)/, null, "'"], [PR_ATTRIB_VALUE, /^\"[^\"]*(?:\"|$)/, null, '"'], [PR_PUNCTUATION, /^[<>\/=]+/, null, '<>/=']], [[PR_TAG, /^[\w:\-]+/, /^= 2 && /^[\"\']/.test(attribValue) && attribValue.charAt(0) === attribValue.charAt(attribLen - 1)); var attribSource; var attribSourceStart; var attribSourceEnd; if (quoted) { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue; } else { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue.substring(1, attribValue.length - 1); } var attribSourceDecorations = decorateSource(attribSource); for (var j = 0, m = attribSourceDecorations.length; j < m; j += 2) { attribSourceDecorations[j] += attribSourceStart; } if (quoted) { attribSourceDecorations.push(attribSourceEnd, PR_ATTRIB_VALUE); spliceArrayInto(attribSourceDecorations, decorations, i + 2, 0); } else { spliceArrayInto(attribSourceDecorations, decorations, i, 2); } } nextValueIsSource = false; } } return decorations; } function decorateMarkup(sourceCode) { var decorations = tokenizeMarkup(sourceCode); decorations = splitTagAttributes(sourceCode, decorations); decorations = splitSourceNodes(sourceCode, decorations); decorations = splitSourceAttributes(sourceCode, decorations); return decorations; } function recombineTagsAndDecorations(sourceText, extractedTags, decorations) { var html = []; var outputIdx = 0; var openDecoration = null; var currentDecoration = null; var tagPos = 0; var decPos = 0; var tabExpander = makeTabExpander(PR_TAB_WIDTH); var adjacentSpaceRe = /([\r\n ]) /g; var startOrSpaceRe = /(^| ) /gm; var newlineRe = /\r\n?|\n/g; var trailingSpaceRe = /[ \r\n]$/; var lastWasSpace = true; function emitTextUpTo(sourceIdx) { if (sourceIdx > outputIdx) { if (openDecoration && openDecoration !== currentDecoration) { html.push(''); openDecoration = null; } if (!openDecoration && currentDecoration) { openDecoration = currentDecoration; html.push(''); } var htmlChunk = textToHtml(tabExpander(sourceText.substring(outputIdx, sourceIdx))).replace(lastWasSpace ? startOrSpaceRe : adjacentSpaceRe, '$1 '); lastWasSpace = trailingSpaceRe.test(htmlChunk); html.push(htmlChunk.replace(newlineRe, '
      ')); outputIdx = sourceIdx; } } while (true) { var outputTag; if (tagPos < extractedTags.length) { if (decPos < decorations.length) { outputTag = extractedTags[tagPos] <= decorations[decPos]; } else { outputTag = true; } } else { outputTag = false; } if (outputTag) { emitTextUpTo(extractedTags[tagPos]); if (openDecoration) { html.push('
      '); openDecoration = null; } html.push(extractedTags[tagPos + 1]); tagPos += 2; } else if (decPos < decorations.length) { emitTextUpTo(decorations[decPos]); currentDecoration = decorations[decPos + 1]; decPos += 2; } else { break; } } emitTextUpTo(sourceText.length); if (openDecoration) { html.push(''); } return html.join(''); } var langHandlerRegistry = {}; function registerLangHandler(handler, fileExtensions) { for (var i = fileExtensions.length; --i >= 0; ) { var ext = fileExtensions[i]; if (!langHandlerRegistry.hasOwnProperty(ext)) { langHandlerRegistry[ext] = handler; } else if ('console' in window) { console.log('cannot override language handler %s', ext); } } } registerLangHandler(decorateSource, ['default-code']); registerLangHandler(decorateMarkup, ['default-markup', 'html', 'htm', 'xhtml', 'xml', 'xsl']); registerLangHandler(sourceDecorator({ keywords: CPP_KEYWORDS, hashComments: true, cStyleComments: true }), ['c', 'cc', 'cpp', 'cs', 'cxx', 'cyc']); registerLangHandler(sourceDecorator({ keywords: JAVA_KEYWORDS, cStyleComments: true }), ['java']); registerLangHandler(sourceDecorator({ keywords: SH_KEYWORDS, hashComments: true, multiLineStrings: true }), ['bsh', 'csh', 'sh']); registerLangHandler(sourceDecorator({ keywords: PYTHON_KEYWORDS, hashComments: true, multiLineStrings: true, tripleQuotedStrings: true }), ['cv', 'py']); registerLangHandler(sourceDecorator({ keywords: PERL_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['perl', 'pl', 'pm']); registerLangHandler(sourceDecorator({ keywords: RUBY_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['rb']); registerLangHandler(sourceDecorator({ keywords: JSCRIPT_KEYWORDS, cStyleComments: true, regexLiterals: true }), ['js']); function prettyPrintOne(sourceCodeHtml, opt_langExtension) { try { var sourceAndExtractedTags = extractTags(sourceCodeHtml); var source = sourceAndExtractedTags.source; var extractedTags = sourceAndExtractedTags.tags; if (!langHandlerRegistry.hasOwnProperty(opt_langExtension)) { opt_langExtension = /^\s*= 0) { var langExtension = cs.className.match(/\blang-(\w+)\b/); if (langExtension) { langExtension = langExtension[1]; } var nested = false; for (var p = cs.parentNode; p; p = p.parentNode) { if ((p.tagName === 'pre' || p.tagName === 'code' || p.tagName === 'xmp') && p.className && p.className.indexOf('prettyprint') >= 0) { nested = true; break; } } if (!nested) { var content = getInnerHtml(cs); content = content.replace(/(?:\r\n?|\n)$/, ''); var newContent = prettyPrintOne(content, langExtension); if (!isRawContent(cs)) { cs.innerHTML = newContent; } else { var pre = document.createElement('PRE'); for (var i = 0; i < cs.attributes.length; ++i) { var a = cs.attributes[i]; if (a.specified) { var aname = a.name.toLowerCase(); if (aname === 'class') { pre.className = a.value; } else { pre.setAttribute(a.name, a.value); } } } pre.innerHTML = newContent; cs.parentNode.replaceChild(pre, cs); cs = pre; } if (isIE6 && cs.tagName === 'PRE') { var lineBreaks = cs.getElementsByTagName('br'); for (var j = lineBreaks.length; --j >= 0; ) { var lineBreak = lineBreaks[j]; lineBreak.parentNode.replaceChild(document.createTextNode('\r\n'), lineBreak); } } } } } if (k < elements.length) { setTimeout(doWork, 250); } else if (opt_whenDone) { opt_whenDone(); } } doWork(); } window['PR_normalizedHtml'] = normalizedHtml; window['prettyPrintOne'] = prettyPrintOne; window['prettyPrint'] = prettyPrint; window['PR'] = { 'createSimpleLexer': createSimpleLexer, 'registerLangHandler': registerLangHandler, 'sourceDecorator': sourceDecorator, 'PR_ATTRIB_NAME': PR_ATTRIB_NAME, 'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE, 'PR_COMMENT': PR_COMMENT, 'PR_DECLARATION': PR_DECLARATION, 'PR_KEYWORD': PR_KEYWORD, 'PR_LITERAL': PR_LITERAL, 'PR_NOCODE': PR_NOCODE, 'PR_PLAIN': PR_PLAIN, 'PR_PUNCTUATION': PR_PUNCTUATION, 'PR_SOURCE': PR_SOURCE, 'PR_STRING': PR_STRING, 'PR_TAG': PR_TAG, 'PR_TYPE': PR_TYPE }; })(); diff --git a/lms/askbot/skins/common/media/js/se_hilite.js b/lms/askbot/skins/common/media/js/se_hilite.js deleted file mode 100644 index 42e99c8e8f..0000000000 --- a/lms/askbot/skins/common/media/js/se_hilite.js +++ /dev/null @@ -1 +0,0 @@ -Hilite={elementid:"content",exact:true,max_nodes:1000,onload:true,style_name:"hilite",style_name_suffix:true,debug_referrer:""};Hilite.search_engines=[["google\\.","q"],["search\\.yahoo\\.","p"],["search\\.msn\\.","q"],["search\\.live\\.","query"],["search\\.aol\\.","userQuery"],["ask\\.com","q"],["altavista\\.","q"],["feedster\\.","q"],["search\\.lycos\\.","q"],["alltheweb\\.","q"],["technorati\\.com/search/([^\\?/]+)",1],["dogpile\\.com/info\\.dogpl/search/web/([^\\?/]+)",1,true]];Hilite.decodeReferrer=function(d){var g=null;var e=new RegExp("");for(var c=0;c2&&f[2]){a=decodeURIComponent(a)}a=a.replace(/\'|"/g,"");a=a.split(/[\s,\+\.]+/);return a}break}}return null};Hilite.decodeReferrerQS=function(f,d){var b=f.indexOf("?");var c;if(b>=0){var a=new String(f.substring(b+1));b=0;c=0;while((b>=0)&&((c=a.indexOf("=",b))>=0)){var e,g;e=a.substring(b,c);b=a.indexOf("&",c)+1;if(e==d){if(b<=0){return a.substring(c+1)}else{return a.substring(c+1,b-1)}}else{if(b<=0){return null}}}}return null};Hilite.hiliteElement=function(f,e){if(!e||f.childNodes.length==0){return}var c=new Array();for(var b=0;b0){c++;if(c>=Hilite.max_nodes){var b=function(){Hilite.walkElements(d,f,e)};setTimeout(b,50);return}if(d.nodeType==1){if(!a.test(d.tagName)&&d.childNodes.length>0){d=d.childNodes[0];f++;continue}}else{if(d.nodeType==3){d=e(d)}}if(d.nextSibling){d=d.nextSibling}else{while(f>0){d=d.parentNode;f--;if(d.nextSibling){d=d.nextSibling;break}}}}};if(Hilite.onload){if(window.attachEvent){window.attachEvent("onload",Hilite.hilite)}else{if(window.addEventListener){window.addEventListener("load",Hilite.hilite,false)}else{var __onload=window.onload;window.onload=function(){Hilite.hilite();__onload()}}}}; \ No newline at end of file diff --git a/lms/askbot/skins/common/media/js/se_hilite_src.js b/lms/askbot/skins/common/media/js/se_hilite_src.js deleted file mode 100644 index b604f15639..0000000000 --- a/lms/askbot/skins/common/media/js/se_hilite_src.js +++ /dev/null @@ -1,273 +0,0 @@ -/** - * Search Engine Keyword Highlight (http://fucoder.com/code/se-hilite/) - * - * This module can be imported by any HTML page, and it would analyse the - * referrer for search engine keywords, and then highlight those keywords on - * the page, by wrapping them around ... tags. - * Document can then define styles else where to provide visual feedbacks. - * - * Usage: - * - * In HTML. Add the following line towards the end of the document. - * - * - * - * In CSS, define the following style: - * - * .hilite { background-color: #ff0; } - * - * If Hilite.style_name_suffix is true, then define the follow styles: - * - * .hilite1 { background-color: #ff0; } - * .hilite2 { background-color: #f0f; } - * .hilite3 { background-color: #0ff; } - * .hilite4 ... - * - * @author Scott Yang - * @version 1.5 - */ - -// Configuration: -Hilite = { - /** - * Element ID to be highlighted. If set, then only content inside this DOM - * element will be highlighted, otherwise everything inside document.body - * will be searched. - */ - elementid: 'content', - - /** - * Whether we are matching an exact word. For example, searching for - * "highlight" will only match "highlight" but not "highlighting" if exact - * is set to true. - */ - exact: true, - - /** - * Maximum number of DOM nodes to test, before handing the control back to - * the GUI thread. This prevents locking up the UI when parsing and - * replacing inside a large document. - */ - max_nodes: 1000, - - /** - * Whether to automatically hilite a section of the HTML document, by - * binding the "Hilite.hilite()" to window.onload() event. If this - * attribute is set to false, you can still manually trigger the hilite by - * calling Hilite.hilite() in Javascript after document has been fully - * loaded. - */ - onload: true, - - /** - * Name of the style to be used. Default to 'hilite'. - */ - style_name: 'hilite', - - /** - * Whether to use different style names for different search keywords by - * appending a number starting from 1, i.e. hilite1, hilite2, etc. - */ - style_name_suffix: true, - - /** - * Set it to override the document.referrer string. Used for debugging - * only. - */ - debug_referrer: '' -}; - -Hilite.search_engines = [ - ['google\\.', 'q'], // Google - ['search\\.yahoo\\.', 'p'], // Yahoo - ['search\\.msn\\.', 'q'], // MSN - ['search\\.live\\.', 'query'], // MSN Live - ['search\\.aol\\.', 'userQuery'], // AOL - ['ask\\.com', 'q'], // Ask.com - ['altavista\\.', 'q'], // AltaVista - ['feedster\\.', 'q'], // Feedster - ['search\\.lycos\\.', 'q'], // Lycos - ['alltheweb\\.', 'q'], // AllTheWeb - ['technorati\\.com/search/([^\\?/]+)', 1], // Technorati - ['dogpile\\.com/info\\.dogpl/search/web/([^\\?/]+)', 1, true] // DogPile -]; - -/** - * Decode the referrer string and return a list of search keywords. - */ -Hilite.decodeReferrer = function(referrer) { - var query = null; - var regex = new RegExp(''); - - for (var i = 0; i < Hilite.search_engines.length; i ++) { - var se = Hilite.search_engines[i]; - regex.compile('^http://(www\\.)?' + se[0], 'i'); - var match = referrer.match(regex); - if (match) { - var result; - if (isNaN(se[1])) { - result = Hilite.decodeReferrerQS(referrer, se[1]); - } else { - result = match[se[1] + 1]; - } - if (result) { - result = decodeURIComponent(result); - // XXX: DogPile's URI requires decoding twice. - if (se.length > 2 && se[2]) - result = decodeURIComponent(result); - result = result.replace(/\'|"/g, ''); - result = result.split(/[\s,\+\.]+/); - return result; - } - break; - } - } - return null; -}; - -Hilite.decodeReferrerQS = function(referrer, match) { - var idx = referrer.indexOf('?'); - var idx2; - if (idx >= 0) { - var qs = new String(referrer.substring(idx + 1)); - idx = 0; - idx2 = 0; - while ((idx >= 0) && ((idx2 = qs.indexOf('=', idx)) >= 0)) { - var key, val; - key = qs.substring(idx, idx2); - idx = qs.indexOf('&', idx2) + 1; - if (key == match) { - if (idx <= 0) { - return qs.substring(idx2+1); - } else { - return qs.substring(idx2+1, idx - 1); - } - } - else if (idx <=0) { - return null; - } - } - } - return null; -}; - -/** - * Highlight a DOM element with a list of keywords. - */ -Hilite.hiliteElement = function(elm, query) { - if (!query || elm.childNodes.length == 0) - return; - - var qre = new Array(); - for (var i = 0; i < query.length; i ++) { - query[i] = query[i].toLowerCase(); - if (Hilite.exact) - qre.push('\\b'+query[i]+'\\b'); - else - qre.push(query[i]); - } - - qre = new RegExp(qre.join("|"), "i"); - - var stylemapper = {}; - for (var i = 0; i < query.length; i ++) { - if (Hilite.style_name_suffix) - stylemapper[query[i]] = Hilite.style_name+(i+1); - else - stylemapper[query[i]] = Hilite.style_name; - } - - var textproc = function(node) { - var match = qre.exec(node.data); - if (match) { - var val = match[0]; - var k = ''; - var node2 = node.splitText(match.index); - var node3 = node2.splitText(val.length); - var span = node.ownerDocument.createElement('SPAN'); - node.parentNode.replaceChild(span, node2); - span.className = stylemapper[val.toLowerCase()]; - span.appendChild(node2); - return span; - } else { - return node; - } - }; - Hilite.walkElements(elm.childNodes[0], 1, textproc); -}; - -/** - * Highlight a HTML document using keywords extracted from document.referrer. - * This is the main function to be called to perform search engine highlight - * on a document. - * - * Currently it would check for DOM element 'content', element 'container' and - * then document.body in that order, so it only highlights appropriate section - * on WordPress and Movable Type pages. - */ -Hilite.hilite = function() { - // If 'debug_referrer' then we will use that as our referrer string - // instead. - var q = Hilite.debug_referrer ? Hilite.debug_referrer : document.referrer; - var e = null; - q = Hilite.decodeReferrer(q); - if (q && ((Hilite.elementid && - (e = document.getElementById(Hilite.elementid))) || - (e = document.body))) - { - Hilite.hiliteElement(e, q); - } -}; - -Hilite.walkElements = function(node, depth, textproc) { - var skipre = /^(script|style|textarea)/i; - var count = 0; - while (node && depth > 0) { - count ++; - if (count >= Hilite.max_nodes) { - var handler = function() { - Hilite.walkElements(node, depth, textproc); - }; - setTimeout(handler, 50); - return; - } - - if (node.nodeType == 1) { // ELEMENT_NODE - if (!skipre.test(node.tagName) && node.childNodes.length > 0) { - node = node.childNodes[0]; - depth ++; - continue; - } - } else if (node.nodeType == 3) { // TEXT_NODE - node = textproc(node); - } - - if (node.nextSibling) { - node = node.nextSibling; - } else { - while (depth > 0) { - node = node.parentNode; - depth --; - if (node.nextSibling) { - node = node.nextSibling; - break; - } - } - } - } -}; - -// Trigger the highlight using the onload handler. -if (Hilite.onload) { - if (window.attachEvent) { - window.attachEvent('onload', Hilite.hilite); - } else if (window.addEventListener) { - window.addEventListener('load', Hilite.hilite, false); - } else { - var __onload = window.onload; - window.onload = function() { - Hilite.hilite(); - __onload(); - }; - } -} diff --git a/lms/askbot/skins/common/media/js/tag_selector.js b/lms/askbot/skins/common/media/js/tag_selector.js deleted file mode 100644 index 445a1e44e4..0000000000 --- a/lms/askbot/skins/common/media/js/tag_selector.js +++ /dev/null @@ -1,375 +0,0 @@ - -var TagDetailBox = function(box_type){ - WrappedElement.call(this); - this.box_type = box_type; - this._is_blank = true; - this._tags = new Array(); - this.wildcard = undefined; -}; -inherits(TagDetailBox, WrappedElement); - -TagDetailBox.prototype.createDom = function(){ - this._element = this.makeElement('div'); - this._element.addClass('wildcard-tags'); - this._headline = this.makeElement('p'); - this._headline.html(gettext('Tag "" matches:')); - this._element.append(this._headline); - this._tag_list_element = this.makeElement('ul'); - this._tag_list_element.addClass('tags'); - this._element.append(this._tag_list_element); - this._footer = this.makeElement('p'); - this._footer.css('clear', 'left'); - this._element.append(this._footer); - this._element.hide(); -}; - -TagDetailBox.prototype.belongsTo = function(wildcard){ - return (this.wildcard === wildcard); -}; - -TagDetailBox.prototype.isBlank = function(){ - return this._is_blank; -}; - -TagDetailBox.prototype.clear = function(){ - if (this.isBlank()){ - return; - } - this._is_blank = true; - this.getElement().hide(); - this.wildcard = null; - $.each(this._tags, function(idx, item){ - item.dispose(); - }); - this._tags = new Array(); -}; - -TagDetailBox.prototype.loadTags = function(wildcard, callback){ - var me = this; - $.ajax({ - type: 'GET', - dataType: 'json', - cache: false, - url: askbot['urls']['get_tags_by_wildcard'], - data: { wildcard: wildcard }, - success: callback, - failure: function(){ me._loading = false; } - }); -}; - -TagDetailBox.prototype.renderFor = function(wildcard){ - var me = this; - if (this._loading === true){ - return; - } - this._loading = true; - this.loadTags( - wildcard, - function(data, text_status, xhr){ - me._tag_names = data['tag_names']; - if (data['tag_count'] > 0){ - var wildcard_display = wildcard.replace(/\*$/, '✽'); - me._headline.find('span').html(wildcard_display); - $.each(me._tag_names, function(idx, name){ - var tag = new Tag(); - tag.setName(name); - //tag.setLinkable(false); - me._tags.push(tag); - me._tag_list_element.append(tag.getElement()); - }); - me._is_blank = false; - me.wildcard = wildcard; - var tag_count = data['tag_count']; - if (tag_count > 20){ - var fmts = gettext('and %s more, not shown...'); - var footer_text = interpolate(fmts, [tag_count - 20]); - me._footer.html(footer_text); - me._footer.show(); - } else { - me._footer.hide(); - } - me._element.show(); - } else { - me.clear(); - } - me._loading = false; - } - ); -} - -function pickedTags(){ - - var interestingTags = {}; - var ignoredTags = {}; - var interestingTagDetailBox = new TagDetailBox('interesting'); - var ignoredTagDetailBox = new TagDetailBox('ignored'); - - var sendAjax = function(tagnames, reason, action, callback){ - var url = ''; - if (action == 'add'){ - if (reason == 'good'){ - url = askbot['urls']['mark_interesting_tag']; - } - else { - url = askbot['urls']['mark_ignored_tag']; - } - } - else { - url = askbot['urls']['unmark_tag']; - } - - var call_settings = { - type:'POST', - url:url, - data: JSON.stringify({tagnames: tagnames}), - dataType: 'json' - }; - if (callback !== false){ - call_settings.success = callback; - } - $.ajax(call_settings); - }; - - var unpickTag = function(from_target, tagname, reason, send_ajax){ - //send ajax request to delete tag - var deleteTagLocally = function(){ - from_target[tagname].remove(); - delete from_target[tagname]; - }; - if (send_ajax){ - sendAjax( - [tagname], - reason, - 'remove', - function(){ - deleteTagLocally(); - liveSearch.refresh(); - } - ); - } - else { - deleteTagLocally(); - } - }; - - var getTagList = function(reason){ - var base_selector = '.marked-tags'; - if (reason === 'good'){ - var extra_selector = '.interesting'; - } else { - var extra_selector = '.ignored'; - } - return $(base_selector + extra_selector); - }; - - var getWildcardTagDetailBox = function(reason){ - if (reason === 'good'){ - return interestingTagDetailBox; - } else { - return ignoredTagDetailBox; - } - }; - - var handleWildcardTagClick = function(tag_name, reason){ - var detail_box = getWildcardTagDetailBox(reason); - var tag_box = getTagList(reason); - if (detail_box.isBlank()){ - detail_box.renderFor(tag_name); - } else if (detail_box.belongsTo(tag_name)){ - detail_box.clear();//toggle off - } else { - detail_box.clear();//redraw with new data - detail_box.renderFor(tag_name); - } - if (!detail_box.inDocument()){ - tag_box.after(detail_box.getElement()); - detail_box.enterDocument(); - } - }; - - var renderNewTags = function( - clean_tag_names, - reason, - to_target, - to_tag_container - ){ - $.each(clean_tag_names, function(idx, tag_name){ - var tag = new Tag(); - tag.setName(tag_name); - tag.setDeletable(true); - - if (/\*$/.test(tag_name)){ - tag.setLinkable(false); - var detail_box = getWildcardTagDetailBox(reason); - tag.setHandler(function(){ - handleWildcardTagClick(tag_name, reason); - if (detail_box.belongsTo(tag_name)){ - detail_box.clear(); - } - }); - var delete_handler = function(){ - unpickTag(to_target, tag_name, reason, true); - if (detail_box.belongsTo(tag_name)){ - detail_box.clear(); - } - } - } else { - var delete_handler = function(){ - unpickTag(to_target, tag_name, reason, true); - } - } - - tag.setDeleteHandler(delete_handler); - var tag_element = tag.getElement(); - to_tag_container.append(tag_element); - to_target[tag_name] = tag_element; - }); - }; - - var handlePickedTag = function(reason){ - var to_target = interestingTags; - var from_target = ignoredTags; - var to_tag_container; - if (reason == 'bad'){ - var input_sel = '#ignoredTagInput'; - to_target = ignoredTags; - from_target = interestingTags; - to_tag_container = $('div .tags.ignored'); - } - else if (reason == 'good'){ - var input_sel = '#interestingTagInput'; - to_tag_container = $('div .tags.interesting'); - } - else { - return; - } - - var tagnames = getUniqueWords($(input_sel).attr('value')); - - $.each(tagnames, function(idx, tagname){ - if (tagname in from_target){ - unpickTag(from_target,tagname,reason,false); - } - }); - - var clean_tagnames = []; - $.each(tagnames, function(idx, tagname){ - if (!(tagname in to_target)){ - clean_tagnames.push(tagname); - } - }); - - if (clean_tagnames.length > 0){ - //send ajax request to pick this tag - - sendAjax( - clean_tagnames, - reason, - 'add', - function(){ - renderNewTags( - clean_tagnames, - reason, - to_target, - to_tag_container - ); - $(input_sel).val(''); - liveSearch.refresh(); - } - ); - } - }; - - var collectPickedTags = function(section){ - if (section === 'interesting'){ - var reason = 'good'; - var tag_store = interestingTags; - } - else if (section === 'ignored'){ - var reason = 'bad'; - var tag_store = ignoredTags; - } - else { - return; - } - $('.' + section + '.tags.marked-tags .tag-left').each( - function(i,item){ - var tag = new Tag(); - tag.decorate($(item)); - tag.setDeleteHandler(function(){ - unpickTag( - tag_store, - tag.getName(), - reason, - true - ) - }); - if (tag.isWildcard()){ - tag.setHandler(function(){ - handleWildcardTagClick(tag.getName(), reason) - }); - } - tag_store[tag.getName()] = $(item); - } - ); - }; - - var setupTagFilterControl = function(control_type){ - $('#' + control_type + 'TagFilterControl input') - .unbind('click') - .click(function(){ - $.ajax({ - type: 'POST', - dataType: 'json', - cache: false, - url: askbot['urls']['set_tag_filter_strategy'], - data: { - filter_type: control_type, - filter_value: $(this).val() - }, - success: function(){ - liveSearch.refresh(); - } - }); - }); - }; - - var getResultCallback = function(reason){ - return function(){ - handlePickedTag(reason); - }; - }; - - return { - init: function(){ - collectPickedTags('interesting'); - collectPickedTags('ignored'); - setupTagFilterControl('display'); - var ac = new AutoCompleter({ - url: askbot['urls']['get_tag_list'], - preloadData: true, - minChars: 1, - useCache: true, - matchInside: true, - maxCacheLength: 100, - delay: 10 - }); - - - var interestingTagAc = $.extend(true, {}, ac); - interestingTagAc.decorate($('#interestingTagInput')); - interestingTagAc.setOption('onItemSelect', getResultCallback('good')); - - var ignoredTagAc = $.extend(true, {}, ac); - ignoredTagAc.decorate($('#ignoredTagInput')); - ignoredTagAc.setOption('onItemSelect', getResultCallback('bad')); - - $("#interestingTagAdd").click(getResultCallback('good')); - $("#ignoredTagAdd").click(getResultCallback('bad')); - } - }; -} - -$(document).ready( function(){ - pickedTags().init(); -}); diff --git a/lms/askbot/skins/common/media/js/user.js b/lms/askbot/skins/common/media/js/user.js deleted file mode 100644 index 5d205560f3..0000000000 --- a/lms/askbot/skins/common/media/js/user.js +++ /dev/null @@ -1,215 +0,0 @@ -$(document).ready(function(){ - - var getSelected = function(){ - - var id_list = new Array(); - var elements = $('#responses input:checked').parent(); - - elements.each(function(index, element){ - var id = $(element).attr('id').replace(/^re_/,''); - id_list.push(id); - }); - - if (id_list.length === 0){ - alert(gettext('Please select at least one item')); - } - - return {id_list: id_list, elements: elements}; - }; - - var submit = function(id_list, elements, action_type){ - if (action_type == 'delete' || action_type == 'mark_new' || action_type == 'mark_seen' || action_type == 'remove_flag' || action_type == 'close' || action_type == 'delete_post'){ - $.ajax({ - type: 'POST', - cache: false, - dataType: 'json', - data: JSON.stringify({memo_list: id_list, action_type: action_type}), - url: askbot['urls']['manageInbox'], - success: function(response_data){ - if (response_data['success'] === true){ - if (action_type == 'delete' || action_type == 'remove_flag' || action_type == 'close' || action_type == 'delete_post'){ - elements.remove(); - } - else if (action_type == 'mark_new'){ - elements.addClass('highlight'); - elements.addClass('new'); - elements.removeClass('seen'); - } - else if (action_type == 'mark_seen'){ - elements.removeClass('highlight'); - elements.addClass('seen'); - elements.removeClass('new'); - } - } - else { - showMessage($('#responses'), response_data['message']); - } - } - }); - } - }; - - var startAction = function(action_type){ - var data = getSelected(); - if (data['id_list'].length === 0){ - return; - } - if (action_type == 'delete'){ - msg = ngettext('Delete this notification?', - 'Delete these notifications?', data['id_list'].length); - if (confirm(msg) === false){ - return; - } - } - if (action_type == 'close'){ - msg = ngettext('Close this entry?', - 'Close these entries?', data['id_list'].length); - if (confirm(msg) === false){ - return; - } - } - if (action_type == 'remove_flag'){ - msg = ngettext('Remove all flags on this entry?', - 'Remove all flags on these entries?', data['id_list'].length); - if (confirm(msg) === false){ - return; - } - } - if (action_type == 'delete_post'){ - msg = ngettext('Delete this entry?', - 'Delete these entries?', data['id_list'].length); - if (confirm(msg) === false){ - return; - } - } - submit(data['id_list'], data['elements'], action_type); - }; - setupButtonEventHandlers($('#re_mark_seen'), function(){startAction('mark_seen')}); - setupButtonEventHandlers($('#re_mark_new'), function(){startAction('mark_new')}); - setupButtonEventHandlers($('#re_dismiss'), function(){startAction('delete')}); - setupButtonEventHandlers($('#re_remove_flag'), function(){startAction('remove_flag')}); - setupButtonEventHandlers($('#re_close'), function(){startAction('close')}); - setupButtonEventHandlers($('#re_delete_post'), function(){startAction('delete_post')}); - setupButtonEventHandlers( - $('#sel_all'), - function(){ - setCheckBoxesIn('#responses .new', true); - setCheckBoxesIn('#responses .seen', true); - } - ); - setupButtonEventHandlers( - $('#sel_seen'), - function(){ - setCheckBoxesIn('#responses .seen', true); - } - ); - setupButtonEventHandlers( - $('#sel_new'), - function(){ - setCheckBoxesIn('#responses .new', true); - } - ); - setupButtonEventHandlers( - $('#sel_none'), - function(){ - setCheckBoxesIn('#responses .new', false); - setCheckBoxesIn('#responses .seen', false); - } - ); - - setupButtonEventHandlers($('.re_expand'), - function(e){ - e.preventDefault(); - var re_snippet = $(this).find(".re_snippet:first") - var re_content = $(this).find(".re_content:first") - $(re_snippet).slideToggle(); - $(re_content).slideToggle(); - } - ); -}); - -/** - * @constructor - * allows to follow/unfollow users - */ -var FollowUser = function(){ - WrappedElement.call(this); - this._user_id = null; - this._user_name = null; -}; -inherits(FollowUser, WrappedElement); - -/** - * @param {string} user_name - */ -FollowUser.prototype.setUserName = function(user_name){ - this._user_name = user_name; -}; - -FollowUser.prototype.decorate = function(element){ - this._element = element; - this._user_id = parseInt(element.attr('id').split('-').pop()); - this._available_action = element.children().hasClass('follow') ? 'follow':'unfollow'; - var me = this; - setupButtonEventHandlers(this._element, function(){ me.go() }); -}; - -FollowUser.prototype.go = function(){ - if (askbot['data']['userIsAuthenticated'] === false){ - var message = gettext('Please signin to follow %(username)s'); - var message_data = { - signin_url: askbot['urls']['user_signin'] + '?next=' + window.location.href, - username: this._user_name - } - message = interpolate(message, message_data, true); - showMessage(this._element, message); - return; - } - var user_id = this._user_id; - if (this._available_action === 'follow'){ - var url = askbot['urls']['follow_user']; - } else { - var url = askbot['urls']['unfollow_user']; - } - var me = this; - $.ajax({ - type: 'POST', - cache: false, - dataType: 'json', - url: url.replace('{{userId}}', user_id), - success: function(){ me.toggleState() } - }); -}; - -FollowUser.prototype.toggleState = function(){ - if (this._available_action === 'follow'){ - this._available_action = 'unfollow'; - var unfollow_div = document.createElement('div'); - unfollow_div.setAttribute('class', 'unfollow'); - var red_div = document.createElement('div'); - red_div.setAttribute('class', 'unfollow-red'); - red_div.innerHTML = interpolate(gettext('unfollow %s'), [this._user_name]); - var green_div = document.createElement('div'); - green_div.setAttribute('class', 'unfollow-green'); - green_div.innerHTML = interpolate(gettext('following %s'), [this._user_name]); - unfollow_div.appendChild(red_div); - unfollow_div.appendChild(green_div); - this._element.html(unfollow_div); - } else { - var follow_div = document.createElement('div'); - follow_div.innerHTML = interpolate(gettext('follow %s'), [this._user_name]); - follow_div.setAttribute('class', 'follow'); - this._available_action = 'follow'; - this._element.html(follow_div); - } -}; - -(function(){ - var fbtn = $('.follow-toggle'); - if (fbtn.length === 1){ - var follow_user = new FollowUser(); - follow_user.decorate(fbtn); - follow_user.setUserName(askbot['data']['viewUserName']); - } -})(); - diff --git a/lms/askbot/skins/common/media/js/utils.js b/lms/askbot/skins/common/media/js/utils.js deleted file mode 100644 index 290ade1d8e..0000000000 --- a/lms/askbot/skins/common/media/js/utils.js +++ /dev/null @@ -1,493 +0,0 @@ -//var $, scriptUrl, askbotSkin -var mediaUrl = function(resource){ - return askbot['settings']['static_url'] + askbotSkin + '/' + resource; -}; - -var cleanUrl = function(url){ - var re = new RegExp('//', 'g'); - return url.replace(re, '/'); -}; - -var copyAltToTitle = function(sel){ - sel.attr('title', sel.attr('alt')); -}; - -var animateHashes = function(){ - var id_value = window.location.hash; - // if (id_value != ""){ - // var previous_color = $(id_value).css('background-color'); - // $(id_value).css('backgroundColor', '#FFF8C6'); - // $(id_value) - // .animate({backgroundColor: '#ff7f2a'}, 500) - // .animate({backgroundColor: '#FFF8C6'}, 500, function(){ - // $(id_value).css('backgroundColor', previous_color); - // }); - // } -}; - -var getUniqueWords = function(value){ - var words = $.trim(value).split(/\s+/); - var uniques = new Object(); - var out = new Array(); - $.each(words, function(idx, item){ - if (!(item in uniques)){ - uniques[item] = 1; - out.push(item); - }; - }); - return out; -}; - -var showMessage = function(element, msg, where) { - var div = $('

      ' + msg + '

      (' + - gettext('click to close') + ')
      '); - - div.click(function(event) { - $(".vote-notification").fadeOut("fast", function() { $(this).remove(); }); - }); - - var where = where || 'parent'; - - if (where == 'parent'){ - element.parent().append(div); - } - else { - element.after(div); - } - - div.fadeIn("fast"); -}; - -//outer html hack - https://github.com/brandonaaron/jquery-outerhtml/ -(function($){ - var div; - $.fn.outerHTML = function() { - var elem = this[0], - tmp; - return !elem ? null - : typeof ( tmp = elem.outerHTML ) === 'string' ? tmp - : ( div = div || $('
      ') ).html( this.eq(0).clone() ).html(); - }; -})(jQuery); - -var makeKeyHandler = function(key, callback){ - return function(e){ - if ((e.which && e.which == key) || (e.keyCode && e.keyCode == key)){ - if(!e.shiftKey){ - callback(); - return false; - } - } - }; -}; - - -var setupButtonEventHandlers = function(button, callback){ - button.keydown(makeKeyHandler(13, callback)); - button.click(callback); -}; - - -var putCursorAtEnd = function(element){ - var el = element.get()[0]; - if (el.setSelectionRange){ - var len = element.val().length * 2; - el.setSelectionRange(len, len); - } - else{ - element.val(element.val()); - } - element.scrollTop = 999999; -}; - -var setCheckBoxesIn = function(selector, value){ - return $(selector + '> input[type=checkbox]').attr('checked', value); -}; - -var notify = function() { - var visible = false; - return { - show: function(html) { - if (html) { - $("body").css("margin-top", "2.2em"); - $(".notify span").html(html); - } - $(".notify").fadeIn("slow"); - visible = true; - }, - close: function(doPostback) { - if (doPostback) { - $.post( - askbot['urls']['mark_read_message'], - { formdata: "required" } - ); - } - $(".notify").fadeOut("fast"); - $("body").css("margin-top", "0"); - visible = false; - }, - isVisible: function() { return visible; } - }; -} (); - -/* **************************************************** */ -// Search query-string manipulation utils -/* **************************************************** */ - -var QSutils = QSutils || {}; // TODO: unit-test me - -QSutils.TAG_SEP = ','; // should match const.TAG_SEP; TODO: maybe prepopulate this in javascript.html ? - -QSutils.get_query_string_selector_value = function (query_string, selector) { - var params = query_string.split('/'); - for(var i=0; i -1) { - tags.splice(pos, 1); /* array.splice() works in-place */ - } - - if(tags.length === 0) { - return this.patch_query_string(query_string, 'tags:', true); - } else { - return this.patch_query_string(query_string, 'tags:' + tags.join(this.TAG_SEP)); - } -}; - -QSutils.add_search_tag = function(query_string, tag){ - var tag_string = this.get_query_string_selector_value(query_string, 'tags'); - tag = encodeURIComponent(tag); - if(!tag_string) { - tag_string = tag; - } else { - tag_string = [tag_string, tag].join(this.TAG_SEP); - } - - return this.patch_query_string(query_string, 'tags:' + tag_string); -}; - - -/* **************************************************** */ - -/* some google closure-like code for the ui elements */ -var inherits = function(childCtor, parentCtor) { - /** @constructor taken from google closure */ - function tempCtor() {}; - tempCtor.prototype = parentCtor.prototype; - childCtor.superClass_ = parentCtor.prototype; - childCtor.prototype = new tempCtor(); - childCtor.prototype.constructor = childCtor; -}; - -/* wrapper around jQuery object */ -var WrappedElement = function(){ - this._element = null; - this._in_document = false; -}; -WrappedElement.prototype.setElement = function(element){ - this._element = element; -}; -WrappedElement.prototype.createDom = function(){ - this._element = $('
      '); -}; -WrappedElement.prototype.getElement = function(){ - if (this._element === null){ - this.createDom(); - } - return this._element; -}; -WrappedElement.prototype.inDocument = function(){ - return this._in_document; -}; -WrappedElement.prototype.enterDocument = function(){ - return this._in_document = true; -}; -WrappedElement.prototype.hasElement = function(){ - return (this._element !== null); -}; -WrappedElement.prototype.makeElement = function(html_tag){ - //makes jQuery element with tags - return $('<' + html_tag + '>'); -}; -WrappedElement.prototype.dispose = function(){ - this._element.remove(); - this._in_document = false; -}; - -var SimpleControl = function(){ - WrappedElement.call(this); - this._handler = null; - this._title = null; -}; -inherits(SimpleControl, WrappedElement); - -SimpleControl.prototype.setHandler = function(handler){ - this._handler = handler; - if (this.hasElement()){ - this.setHandlerInternal(); - } -}; - -SimpleControl.prototype.setHandlerInternal = function(){ - //default internal setHandler behavior - setupButtonEventHandlers(this._element, this._handler); -}; - -SimpleControl.prototype.setTitle = function(title){ - this._title = title; -}; - -var EditLink = function(){ - SimpleControl.call(this) -}; -inherits(EditLink, SimpleControl); - -EditLink.prototype.createDom = function(){ - var element = $(''); - element.addClass('edit edit-icon'); - this.decorate(element); -}; - -EditLink.prototype.decorate = function(element){ - this._element = element; - this._element.attr('title', gettext('click to edit this comment')); - this._element.html(gettext('✒')); - this.setHandlerInternal(); -}; - -var DeleteIcon = function(title){ - SimpleControl.call(this); - this._title = title; -}; -inherits(DeleteIcon, SimpleControl); - -DeleteIcon.prototype.decorate = function(element){ - this._element = element; - this._element.attr('class', 'delete-icon'); - this._element.attr('title', this._title); - this._element.html(gettext('✖')); - if (this._handler !== null){ - this.setHandlerInternal(); - } -}; - -DeleteIcon.prototype.setHandlerInternal = function(){ - setupButtonEventHandlers(this._element, this._handler); -}; - -DeleteIcon.prototype.createDom = function(){ - this._element = this.makeElement('span'); - this.decorate(this._element); -}; - -var Tag = function(){ - SimpleControl.call(this); - this._deletable = false; - this._delete_handler = null; - this._delete_icon_title = null; - this._tag_title = null; - this._name = null; - this._url_params = null; - this._inner_html_tag = 'a'; - this._html_tag = 'li'; -} -inherits(Tag, SimpleControl); - -Tag.prototype.setName = function(name){ - this._name = name; -}; - -Tag.prototype.getName = function(){ - return this._name; -}; - -Tag.prototype.setHtmlTag = function(html_tag){ - this._html_tag = html_tag; -}; - -Tag.prototype.setDeletable = function(is_deletable){ - this._deletable = is_deletable; -}; - -Tag.prototype.setLinkable = function(is_linkable){ - if (is_linkable === true){ - this._inner_html_tag = 'a'; - } else { - this._inner_html_tag = 'span'; - } -}; - -Tag.prototype.isLinkable = function(){ - return (this._inner_html_tag === 'a'); -}; - -Tag.prototype.isDeletable = function(){ - return this._deletable; -}; - -Tag.prototype.isWildcard = function(){ - return (this.getName().substr(-1) === '*'); -}; - -Tag.prototype.setUrlParams = function(url_params){ - this._url_params = url_params; -}; - -Tag.prototype.setHandlerInternal = function(){ - setupButtonEventHandlers(this._element.find('.tag'), this._handler); -}; - -/* delete handler will be specific to the task */ -Tag.prototype.setDeleteHandler = function(delete_handler){ - this._delete_handler = delete_handler; - if (this.hasElement() && this.isDeletable()){ - this._delete_icon.setHandler(delete_handler); - } -}; - -Tag.prototype.getDeleteHandler = function(){ - return this._delete_handler; -}; - -Tag.prototype.setDeleteIconTitle = function(title){ - this._delete_icon_title = title; -}; - -Tag.prototype.decorate = function(element){ - this._element = element; - var del = element.find('.delete-icon'); - if (del.length === 1){ - this.setDeletable(true); - this._delete_icon = new DeleteIcon(); - if (this._delete_icon_title != null){ - this._delete_icon.setTitle(this._delete_icon_title); - } - //do not set the delete handler here - this._delete_icon.decorate(del); - } - this._inner_element = this._element.find('.tag'); - this._name = this.decodeTagName($.trim(this._inner_element.html())); - if (this._title !== null){ - this._inner_element.attr('title', this._title); - } - if (this._handler !== null){ - this.setHandlerInternal(); - } -}; - -Tag.prototype.getDisplayTagName = function(){ - //replaces the trailing * symbol with the unicode asterisk - return this._name.replace(/\*$/, '✽'); -}; - -Tag.prototype.decodeTagName = function(encoded_name){ - return encoded_name.replace('\u273d', '*'); -}; - -Tag.prototype.createDom = function(){ - this._element = this.makeElement(this._html_tag); - //render the outer element - if (this._deletable){ - this._element.addClass('deletable-tag'); - } - this._element.addClass('tag-left'); - - //render the inner element - this._inner_element = this.makeElement(this._inner_html_tag); - if (this.isLinkable()){ - var url = askbot['urls']['questions']; - var flag = false - var author = '' - if (this._url_params){ - url += QSutils.add_search_tag(this._url_params, this.getName()); - } - this._inner_element.attr('href', url); - } - this._inner_element.addClass('tag tag-right'); - this._inner_element.attr('rel', 'tag'); - if (this._title === null){ - this.setTitle( - interpolate(gettext("see questions tagged '%s'"), [this.getName()]) - ); - } - this._inner_element.attr('title', this._title); - this._inner_element.html(this.getDisplayTagName()); - - this._element.append(this._inner_element); - - if (!this.isLinkable() && this._handler !== null){ - this.setHandlerInternal(); - } - - if (this._deletable){ - this._delete_icon = new DeleteIcon(); - this._delete_icon.setHandler(this.getDeleteHandler()); - if (this._delete_icon_title !== null){ - this._delete_icon.setTitle(this._delete_icon_title); - } - var del_icon_elem = this._delete_icon.getElement(); - del_icon_elem.text('✕'); // HACK by Tomasz - this._element.append(del_icon_elem); - } -}; - -//Search Engine Keyword Highlight with Javascript -//http://scott.yang.id.au/code/se-hilite/ -Hilite={elementid:"content",exact:true,max_nodes:1000,onload:true,style_name:"hilite",style_name_suffix:true,debug_referrer:""};Hilite.search_engines=[["local","q"],["cnprog\\.","q"],["google\\.","q"],["search\\.yahoo\\.","p"],["search\\.msn\\.","q"],["search\\.live\\.","query"],["search\\.aol\\.","userQuery"],["ask\\.com","q"],["altavista\\.","q"],["feedster\\.","q"],["search\\.lycos\\.","q"],["alltheweb\\.","q"],["technorati\\.com/search/([^\\?/]+)",1],["dogpile\\.com/info\\.dogpl/search/web/([^\\?/]+)",1,true]];Hilite.decodeReferrer=function(d){var g=null;var e=new RegExp("");for(var c=0;c2&&f[2]){a=decodeURIComponent(a)}a=a.replace(/\'|"/g,"");a=a.split(/[\s,\+\.]+/);return a}break}}return null};Hilite.decodeReferrerQS=function(f,d){var b=f.indexOf("?");var c;if(b>=0){var a=new String(f.substring(b+1));b=0;c=0;while((b>=0)&&((c=a.indexOf("=",b))>=0)){var e,g;e=a.substring(b,c);b=a.indexOf("&",c)+1;if(e==d){if(b<=0){return a.substring(c+1)}else{return a.substring(c+1,b-1)}}else{if(b<=0){return null}}}}return null};Hilite.hiliteElement=function(f,e){if(!e||f.childNodes.length==0){return}var c=new Array();for(var b=0;b0){c++;if(c>=Hilite.max_nodes){var b=function(){Hilite.walkElements(d,f,e)};setTimeout(b,50);return}if(d.nodeType==1){if(!a.test(d.tagName)&&d.childNodes.length>0){d=d.childNodes[0];f++;continue}}else{if(d.nodeType==3){d=e(d)}}if(d.nextSibling){d=d.nextSibling}else{while(f>0){d=d.parentNode;f--;if(d.nextSibling){d=d.nextSibling;break}}}}};if(Hilite.onload){if(window.attachEvent){window.attachEvent("onload",Hilite.hilite)}else{if(window.addEventListener){window.addEventListener("load",Hilite.hilite,false)}else{var __onload=window.onload;window.onload=function(){Hilite.hilite();__onload()}}}}; -/* json2.js by D. Crockford */ -if(!this.JSON){this.JSON={}}(function(){function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i
      ").hide();if(this.options.resultsClass){this._results.addClass(this.options.resultsClass)}this._results.css({position:"absolute"});$("body").append(this._results);this.setEventHandlers()};AutoCompleter.prototype.setEventHandlers=function(){var a=this;a._element.keydown(function(b){a.lastKeyPressed_=b.keyCode;switch(a.lastKeyPressed_){case 38:b.preventDefault();if(a.active_){a.focusPrev()}else{a.activate()}return false;break;case 40:b.preventDefault();if(a.active_){a.focusNext()}else{a.activate()}return false;break;case 9:case 13:if(a.active_){b.preventDefault();a.selectCurrent();return false}break;case 27:if(a.active_){b.preventDefault();a.finish();return false}break;default:a.activate()}});a._element.blur(function(){if(a.finishOnBlur_){setTimeout(function(){a.finish()},200)}})};AutoCompleter.prototype.position=function(){var a=this._element.offset();this._results.css({top:a.top+this._element.outerHeight(),left:a.left})};AutoCompleter.prototype.cacheRead=function(d){var f,c,b,a,e;if(this.options.useCache){d=String(d);f=d.length;if(this.options.matchSubset){c=1}else{c=f}while(c<=f){if(this.options.matchInside){a=f-c}else{a=0}e=0;while(e<=a){b=d.substr(0,c);if(this.cacheData_[b]!==undefined){return this.cacheData_[b]}e++}c++}}return false};AutoCompleter.prototype.cacheWrite=function(a,b){if(this.options.useCache){if(this.cacheLength_>=this.options.maxCacheLength){this.cacheFlush()}a=String(a);if(this.cacheData_[a]!==undefined){this.cacheLength_++}return this.cacheData_[a]=b}return false};AutoCompleter.prototype.cacheFlush=function(){this.cacheData_={};this.cacheLength_=0};AutoCompleter.prototype.callHook=function(c,b){var a=this.options[c];if(a&&$.isFunction(a)){return a(b,this)}return false};AutoCompleter.prototype.activate=function(){var b=this;var a=function(){b.activateNow()};var c=parseInt(this.options.delay,10);if(isNaN(c)||c<=0){c=250}if(this.keyTimeout_){clearTimeout(this.keyTimeout_)}this.keyTimeout_=setTimeout(a,c)};AutoCompleter.prototype.activateNow=function(){var a=this.getValue();if(a!==this.lastProcessedValue_&&a!==this.lastSelectedValue_){if(a.length>=this.options.minChars){this.active_=true;this.lastProcessedValue_=a;this.fetchData(a)}}};AutoCompleter.prototype.fetchData=function(b){if(this.options.data){this.filterAndShowResults(this.options.data,b)}else{var a=this;this.fetchRemoteData(b,function(c){a.filterAndShowResults(c,b)})}};AutoCompleter.prototype.fetchRemoteData=function(c,e){var d=this.cacheRead(c);if(d){e(d)}else{var a=this;if(this._element){this._element.addClass(this.options.loadingClass)}var b=function(g){var f=false;if(g!==false){f=a.parseRemoteData(g);a.options.data=f;a.cacheWrite(c,f)}if(a._element){a._element.removeClass(a.options.loadingClass)}e(f)};$.ajax({url:this.makeUrl(c),success:b,error:function(){b(false)}})}};AutoCompleter.prototype.setOption=function(a,b){this.options[a]=b};AutoCompleter.prototype.setExtraParam=function(b,c){var a=$.trim(String(b));if(a){if(!this.options.extraParams){this.options.extraParams={}}if(this.options.extraParams[a]!==c){this.options.extraParams[a]=c;this.cacheFlush()}}};AutoCompleter.prototype.makeUrl=function(e){var a=this;var b=this.options.url;var d=$.extend({},this.options.extraParams);if(this.options.queryParamName===false){b+=encodeURIComponent(e)}else{d[this.options.queryParamName]=e}if(this.options.limitParamName&&this.options.maxItemsToShow){d[this.options.limitParamName]=this.options.maxItemsToShow}var c=[];$.each(d,function(f,g){c.push(a.makeUrlParam(f,g))});if(c.length){b+=b.indexOf("?")==-1?"?":"&";b+=c.join("&")}return b};AutoCompleter.prototype.makeUrlParam=function(a,b){return String(a)+"="+encodeURIComponent(b)};AutoCompleter.prototype.splitText=function(a){return String(a).replace(/(\r\n|\r|\n)/g,"\n").split(this.options.lineSeparator)};AutoCompleter.prototype.parseRemoteData=function(c){var h,b,f,d,g;var e=[];var b=this.splitText(c);for(f=0;f""){if(typeof c!=="object"){c={}}if(this.options.filterResults){h=String(b);g=String(l);if(!this.options.matchCase){h=h.toLowerCase();g=g.toLowerCase()}a=g.indexOf(h);if(this.options.matchInside){a=a>-1}else{a=a===0}}else{a=true}if(a){f.push({value:l,data:c})}}}if(this.options.sortResults){f=this.sortResults(f,b)}if(this.options.maxItemsToShow>0&&this.options.maxItemsToShowc){return 1}if(d");var f,l,j,a,h=false,d=false;var c=e.length;for(f=0;f"+this.showResult(l.value,l.data)+"");j.data("value",l.value);j.data("data",l.data);j.click(function(){var i=$(this);k.selectItem(i)}).mousedown(function(){k.finishOnBlur_=false}).mouseup(function(){k.finishOnBlur_=true});g.append(j);if(h===false){h=String(l.value);d=j;j.addClass(this.options.firstItemClass)}if(f==c-1){j.addClass(this.options.lastItemClass)}}this.position();this._results.html(g).show();a=this._results.outerWidth()-this._results.width();this._results.width(this._element.outerWidth()-a);$("li",this._results).hover(function(){k.focusItem(this)},function(){});if(this.autoFill(h,b)){this.focusItem(d)}};AutoCompleter.prototype.showResult=function(b,a){if($.isFunction(this.options.showResult)){return this.options.showResult(b,a)}else{return b}};AutoCompleter.prototype.autoFill=function(e,c){var b,a,d,f;if(this.options.autoFill&&this.lastKeyPressed_!=8){b=String(e).toLowerCase();a=String(c).toLowerCase();d=e.length;f=c.length;if(b.substr(0,f)===a){this._element.val(e);this.selectRange(f,d);return true}}return false};AutoCompleter.prototype.focusNext=function(){this.focusMove(+1)};AutoCompleter.prototype.focusPrev=function(){this.focusMove(-1)};AutoCompleter.prototype.focusMove=function(a){var b,c=$("li",this._results);a=parseInt(a,10);for(var b=0;b=c.length){b=c.length-1}}a=$(c[b])}else{a=$(b)}if(a){a.addClass(this.selectClass_).addClass(this.options.selectClass)}}};AutoCompleter.prototype.selectCurrent=function(){var a=$("li."+this.selectClass_,this._results);if(a.length==1){this.selectItem(a)}else{this.finish()}};AutoCompleter.prototype.selectItem=function(d){var c=d.data("value");var b=d.data("data");var a=this.displayValue(c,b);this.lastProcessedValue_=a;this.lastSelectedValue_=a;this.setValue(a);this.setCaret(a.length);this.callHook("onItemSelect",{value:c,data:b});this.finish()};AutoCompleter.prototype.isContentChar=function(a){if(a.match(this.options.stopCharRegex)){return false}else{if(a===this.options.multipleSeparator){return false}else{return true}}};AutoCompleter.prototype.getValue=function(){var c=this._element.getSelection();var d=this._element.val();var f=c.start;var e=f;for(cpos=f;cpos>=0;cpos=cpos-1){if(cpos===d.length){continue}var b=d.charAt(cpos);if(!this.isContentChar(b)){break}e=cpos}var a=f;for(cpos=f;cpos9gP2NC6d1_jGX# z$!L6gYaNf2-{;82uk5#Vt;^`AlS)m~wqJ6EEVC3dX5cFLA5FKSx9Tfc+c zt!*z&s#rcSS)c$!11h){}WpJ0^4#C|C?(XjH?oM!bg1b8ecL)|7f;;Sd_x)|vyS4kr z?K@L7b-ViXIek9Qb0;fH27l&-{RAFaN?7E0s!u7Im%`#%Ey>|4_mjw z(&7He(soK1u*6EjQU2%&6f`7os8Zo1`KuU;eP6zRhr%1p3x|x2^^e9-q(h8^UxC>t zDToa!438Xt+3_m0TIziGJN4eQ%zs#QlhZf@(+i83D#5Dw%^$H`nDEP=ppn7hoqYzu z0C_QG@n5bpB`ut2YDRxcBN*aymqaE3O7e=tO^a{{L{ ztUo#+=ou?q0tkvh_~)ckC5+L%&A` z4od(V%AcD_0LV+g0I&Au&3ziVW_p@Ewi#KE{-g)`gVfM)@BLt=(q0e%cAR}@-{}|| zas7Fq{cYY0$SSePz0NRyyG?X8~gua^F2SlZFTive}6@4P*}@gT;1p0 ztY5EJ{muHxm-qGI?oanFD1b#LKpg7vPyghtOfmjUB3!WH#@{&c*A|42SAu!cK}o|l z4I1=4MKp(4(ah)@mSWNf;Y8{o^0^P=-EF!TNdE0_K%FH&gjWK6nT`?Uy)-4NhI8-k z0{~oi*!3+?!9x0*2kp$beOwB>iRY06{-%yxTr( zx?X7fzRw$d2(S9=!NQ=yeqmH$cw>KjM}6w5Kw+xTsX780eZ~br0=8a7^Ux%FWQKm# zR%AYVlxG9j+&-$#05~Y2QP{5rBx~UihLIX%v9MSfA&(?|5>RMSAmXubbVX9>gl`he zN|7qWsuBd(e2$;E!nDQO6L>}emcZB0PI2bsfCfdr1;i$iwlWUfV6g&(-^R|&nQ@qT zS-%~B;qpbZ=O6yAG)GyB;};oW>OI1SVI8z)Vi+C}uIHqKOEQ|MC#^!P9LB9vtAgx^ zxHFomcY{I;_}&Z04&^3HY(OX^p&_Fow<1wSj1OY_vIfTi2N|r`Pf40oCjE<|{mauJ zwjm31n1%!$*%DgZS4LE?aFIa{5L$!?3uRmUC&~IEl{vIIu{m2MvSYgPEH(*TvZS#! zW2*)ju4Fo5+R>2xoc)1))_uHvl4~va9792eqDSRjDy4BtDVzh;1GWQblXMm#)xu1r z&9Z!zFW=&R2v_GfE7dB7m3?J%$>f>}!jzOQt}aNK$2G??f3T=Ma&G^!nL;R+_FH{! z;#mAB?FjPL4G}36BWNVtj0z_U8wT49#|^tSU8V$QJ_=_9tbr_r5`#0sf0)Ft5CAVvMus<$gm zNBSEXsQ}Dgy2E3(vmD$ZRcYFdZ{!yGCG{>fUJ08_8?%YF zRYhuAsz#+mrDCP{X%~CVd1+2b*Y^(1mjGm;5h+S-N^!He>CZWb%B#worSPSgvV7ck zH4GJ2#d!H0qHaC5r5EYB-Map{GIpg~9$kme_&$tpDsMK=rFY_p8qkf1An0|(X>8u1 zZw_|61dJo=SBfG!1A-#O1}O$2l3Zo(lG#ArRr6SLc?(Y6!3y+xO|wn2 zkAayXmRZYC&A>%cSCUV=Ps|%13{u!CawWkJ0$-s#@nBW};b#hy)pnJyyoh{&S<@jIqY-0* zdi8Q$OR-zUgTW;h|2@Cs&prMbX#KUYihxP+VYj;6YzCiTUVRd^O3#Vfu8! zv;}gAg74ANZy1BW22lgPh?F5=A?l$5vG(B_xU7JsxiLmvnD-!vvA3g$`7h#8khz%6Lr&`sfcl|)XH4i zsq@Lk@%4C)?8~1Y=V<+CJBiK3q0;K~1R8(4sw}x0cpW^};rB2;(}t=+SMjz+Hq+QF z%2%GNr`7m&QFzK)1?cvT2lh2VSe+uM!XJeAoNg?i>&R;`YHl|fJcs7s@nb|p)}XIr zrD6E3P;Iy$;DyY%`uQoijcDyG5U?g`8TE_BRdL7HM_u7t*%cuk*Y6 z@9T_dQ&j66LCqpaUJ-S0VjlkThep%emMC_Y?WoQ>bKgIr4tY9xrnRqIov$0#55B6ms%O@6>)D(*pYXmX&C8E; zWOroNv}s$lU3hjid6_;OJ zx=~(KcglC_-3p!yt%_`hEedG8Z>(Hy_*wS4<=xC~h(ADw1kJoB+{K}Ye2&YAn+Qb; zJs@h*Nl={?1% zT_5frChQ(_XJyvCReL$_bK7tuxGda8k>B?w_M`eRf2zMz{$2W#gp-u?0R@?U^J>IO zzYgY2Vj~G9Spe`P2LS&d0C@TU-;V*ng$V#o^Z|f79RRTG67`0}0Du%yQdCIAeeF!g z%^iPeGk5c}lt)LI>n@4@r+tEp*sxgt_htwLBfJny;=a!}y-85vRMdL-Xt12g-u~1c zu=@PQN!Y^p8&vGXblQDlYE9$io8>v;-Xrd$r^H}4wX20E9&UH4Stp9%#ll*_zbMHI_%w_ zuct_nXIZK1=v2=i=fTb&J1;ISZk*OsS65e68IPx?r7bTlH5l`Icb2byi)Zezcx3fn zUMgJla>ID6>2Vqs8G0%Z*ly|W3nb*$9VwpkdAZM!DH%=iMj&JC@ydpT zoI~h-Ig#5nGNt(O?o;$2kpu}Sc|6QM9+} zZig)x;j~F+(`BU1k_Al=Gi*>xlqEPJOELdC8Hqs~*wd4rpWo0hXU%T6)tNEYTl{=|?O3eT=J)ZwyKCr*t~q>$K-t$hmXmQmKj2 z+4s6m{u!#d3DQJf;K48{5sN_pAW8e<`se58fB*>mk)|K#;H|!FKV^t@=H7wxH8i0N zHNKZ4yxfdk%82E-zthYXDaWeOcfBL6`dW@&E}m+imNrs$z&q*@K{k<&mI8z+as3F~aGBvJn1leVi?vwc&V3 zhOo|K&MEw{dpKLYzhQ*yE~)Vw-6d@oLk*i!Q&W1am5*y=x(xh`fCw!jA`&E5iqN3! z;nCKo&y*o!&|^NXf{Pt$KsX7`(f~o!2Q{;q-1FmO%@*6uDUNMzHMP{--1j9dyLxvu zA+?N2C;`=dCP}eKE!UM$eE!uj{J52P0wUj+TKP=m#b&~fwG|`Wr+-nYrMk5ds`mq# zOFrU11O(R9wpXmq2v+NBZRhts&d(Z|)dl#rR#{OgH%xx-I&&i+AOt~<9XN93Ogls& zC0r1Ti_@Z|CYUzAG*H<$&oo`FZR#~$cJ_E~raZNrH2T>xqbRpZ(2gxDVPs@XcFmSM1Sdwal!>-mat8H4c6(hswPiXKAoyB0F+xVAyTJ3pOnh_G>b$_Q z?LEJ_nUj=sbC9N2ququE`0sHFp(wPh`%1JsgRtK8F<0V%5t9Js$=%su7($?BTUs zc-;9dd3vwhe5k+leQe>RB_Kiv_37*PLs8VXwsLZsIDJl>KSo4TjL+l@mGtoNkU`Lx z3=%S}to_lnLIFqvC zIDtA`pzdjDNWR?@%F8Y6 z>}FiMpZF~<`t43I(uwoiz8+OiN4K#ycUJN7-7mW~GB>#E z>gpPI4Gj(DN6_KIk^OFj-KCqi?;W}EeB=+tx|=!OdFo_(ys%L!+H~KG!Hp>aZ|dn% zwcp3f_WIh|+}s>D56@n~kz)iBDoh7<4FQx8X?Ut|vQ)o3+Kt|8Pi=(&uM#sbv7F*M%_F=!`S)~|bYN1v~TLdVCY#9mz6 zw81^mfs8z9%$PkZjZts$+2Ob9-o<;eXkUVnjwnG&VDHb9L>0+>OGLXG%BLaA`9H zMM&7Yk974Z2j*ELUwWtsSZZr0=cHjc?MUdBoMfVH29Q;|RWSnq=G4Tq;YgSF`@?jz zDBb=_G`JnQ8vb$WGo&gj`CSVMm@9vi-}YThB62~;H67qw-0A<`se+RV+AX(F^w$r! zS9tS*@Ts4!$8A47?(lkFB`BcSaJB=%0L^$ejdB8Q#22#s zR=upu&w!|+qH)l&p5LZ9Tlejp^bFsFwx6G$t*x!R{0#W$ajoCWo+w_EH8p{R3IL6L z9>__+QON$amO$S^XV`PYC!pEdQi3rAn-DL+YPLHuB}1VM?#9xcQ*jG$q$$jQ_4kHj z>P;V*>=Fz0MpjP95ty^xKL*>HCerj+rSHiBrVpJGWi$Stq>w zHomHYaOKmY59akGw{GjKkIV0`WLfEzO_a+i=8=kCV@=c5XssX z8(J6(=E#}5admR?RHhR>Evllj5~n3nk%F_|Vl%R`Hna5OVMkd4NXc8B(o0tzBlCxT z2PhN+HPwrj@hxkgP7_w!8r$Y=wHZwP9MC;n;E@KHs?|Dd*4Yz5FfcIN;G%HwFc*^0 zmZ~>ksr*V&@>q{8sq`a;?t980`OQY4bLD_^B6=K0Ul+2FtA)9F__wmW<7QpdA$!0) zU$FPqcU(sb2r!z-&CFr-%KR0t`D>i{rWw3!+fCdI-;cKkR+v5ES!0a@mi7>3TQ;O& zS{s=>2`Uj4Rn=qXb}$tUIoju0bk|>&lr9NqKS_8fKHXX*ZrTbs?25X%aY!b5^Zzu# z9d_8*yMprB3w{enk~)i*1tYXw1VICsr9VmhpJ63#xMYF@ud`e5LE>i;VLmTuHtC@E z_XY)aT6Nl#@Di=Gd@AmmSJDJ3=4s#GON)!&*w~-j3eqka7Fe|>;4XjD{c?do2nht18mNl|3=-fO85xzA zmn$!>`G~^+K*~hJ|BIeUc|4xeXU$*l*X@`Q2bd6nyyer0@$q0`5>#5n2!RiHL77I4qHf_QsG*i-u7yqDiI6nRI8o!08UE?Y@Z}jidgZ(m1 zqbzKLUnB+w2H0dfHZRJE%Hd*{HAFIapc(j(OYtc|l5Jg7P!Pr1XdI0rQHGr5@lj8E zek7+??v_?oi{nipXKmwb!b-Q8qfwlGXEr6rUD@xn%2wKio|~D zSUw%Gh@4)em>HMGQP**CaRC6>z{semE?vd}q)AA4GIHPl0QnoPtfC?qj7rn5HzdbQ z8yz5Y-9Eyla%jFvB$AgSU#{%-pDN%#Dm-BC7E)(Iw`vHMmYQ?FghjzS8Zq11*i`G* zqI`}XGp?wvUOaZjIu1et&m)K$-sT1bPS-L8lB6R<;vbK&IMm8VMbxp^a+(n_S)S$at7fy)6DsRA@k)}5~T@?CY3%D zVr6wT27HKv;^1U*G$Ypj&;P^2y^^qG`ZUAce2KwNP=i41CNwlOKC-x@BTHjF3LvP6 zj$w58*SNDm($m>tFFOiKjvs{`iAyq4P7du3k=q*i=jP5%pTbTwTV?^zv-PIW(r$mW z1W{Je(KLV7{4xFLBJ87m=_>BOB#_Wz?s3;}eAE{eqcD@jMb=d-y;gezDXB=YLby;k z#E#(as8<2O1{zLItWJ}&+py4-EE&-j5K%jzU?^A)3{=>pIi;EgFFIRvp&UZ2x9WJt zlAIqT@)U{xm=gQpgi-4WOKWP72kMx%jD$rP_Hpk2tjd%9kTU>DjsWmNv%)Oh%4Y0Y zIOs94u{0^;^QH2PKYoRS#x&r6$5Bu3nDY__?-Z@>7Fqle`oxgNA)wV&N2N~n2LgZ| zM*Ycw+69vBNbpw~>~XYZ2lDL2JawddEJmqQVYS-DnWJx?6i5W7VJw4%4eW_=V#FxO zPoSoti-KTKz!Fa1fWeC@kgA_4TG}@nq~n_n1Cf4x!>ls69PWXGZ0F^eD@m8yeun8p z2Ev?{Oc)0U<^z%^4#4T{*_E3?Dg4!iTSuEoelj=_BJuKYV790EvvROtB9tIWo{^H0 zynNiErE0kf85vn|aWPoLLkazAZsyLJ217EYT1NU7lO$>bs}fy$h}!r8CoVo9IqS=K za<>PH)ziu5UBkDjRZj;?QeV73PaR72QcsrZEf)F-a;v$WBnt(i2N36 zAXb6@Ksgh{a=fE=!K||rz_V#u#i*)JE_zTcER5*x2QUSz_{vphyRQQH5 zvC(3M;3ZL^V@8>*Hkic3#2|->6f0>~FPTiF*6)#U{$VIyVPY18AA(NXD-#sAN zyfuF-!kF45QbLDcrHUg%#=nLLJ_&+iMd2L3pp~ODEG=M)b zwczZ{El&OTG_?ee!;&Z}`cD){p#AxLia-Qi65az9K>axr1T5!ZV27rprZynHKN^a} zYcL_69vy|_QG4(bLJ6_3u(S%=s>yjiXv7g=!I4CMVv8_>I-jIg>*f9O{WlxZ<0lQ$ zFXo3Z{1z)MmRl#z#q2Uh#=Of4G31C62@VF>rUl)ck)56-L=bwtQBWJg-ArOoAuug) z!*i+x$Rkb5x?U#>)Gg~d+1b`D>*}>&y9z2uGE_B#fPC-3{LUa*FyJrQ9(HO`0xS?00Ou8OR$RhzB%&awe@)p?IkR|u9DL3m3v?S z{s6CuiAjFM&EKg#ezn%gj_CVpIjIzQ6K_sWm_&4v1W*9>y1+~}mr>&?IFnS43yM6Ctf1wT0J! zd84gG{LpUKX3ztRwBNtur%Y+-=!~%?snEqJK6tS_G-SibomrvUU#;^oCUc_M&+16+Bp-9o-A@bl7 zcM$@Fsd3KSHd@WArAped8?7}I&}AYp$YQLjbr$4j@CAIcAvQ4|& z^X^1Ln`N^llZfA|RYEfC*6x+*Azd-jC?7rb2_s zt>NkFMc57CvOewgJYnP>g#?q+^?}KAg-yT6Vh%ZM({bt1 zU(EK+wIwp`Pq+u6IX?-QZ`g)zu=F}BE*Y`96{9}KK>&U8b(Q(9Gffs~5Wz{-ZkYc7 zq~5PQaweyxh!S=^UQj^Y!39RzsU$N}INLo1Gr@$RB9BMr%9uz8zvm$%|KPzxd+(N; z6zxU#TL4xO3prkVI!kjxcb!b;?M749hl*N#fYIh~Xl?i73~dZ0@+aVSXky96SKCg5 zkk!e{=|>4UbyxW%>ub{ALSq)so3kmmNiT^Ey0!bLJH~UawKZ4Q=`-3rwKe-!K{=s% zu`H}5G(fOwO@zoEl{FuR*#kcmZ0On(r)yyQSkQzjA5)KG+^GIdU9;w+eDbPqNGT|L ze}6X^LaM*EZu9zHl|M-UnMN3<9Q!pA^hyXLCnV+aTD=ER`xPl~&NxMo>Eathv%$@@ z*<}u^o37ky#g3xYUPMqSa#!W-$&!s{Hj;!12ARP)&?+vjR=VEN*eLv7+gDSMnaC`3KQc) z`+icPYAx%CjAK(66jYcCNSXefO6TZt*LESOdPe|y3H;MGpUZqE`|U-h{B$*4PJ;}J z2IB?=k1XVbt}50EpZ_$C{D-dPJ(XVo1y_;m*QrCf?RHbeOeP=Glm6yDyPxe|agY#& z)46t&BA{4h6=L!XIz8jEx4-)hXBWgL??OTbB8qZKb8lsSSjH{!rhfbI-Dd>?y~PVM@z3U;qDt*)pQ~xESO8 zX(-?iM6Fp-nv8cvHGPb!PHgX~xzYV@M%U6Hp_v@JeY z>ZQ{{_p%JPR|B)H!@+pCbawj_DJzUxI~jB{My{rqIEZCGWVnT*>!XsCiwBQQv|HtC z8l_Sa9z|+eV(i^U{1F@?Q{N97MCtJ64UD{4d-@-*SEc^Z+RMvLPA(Q+U};DT9_V$* zCN8)%&5TxOmyBbu&2~!Yg}Z?w^8tIJsx&EHt%cn-Wo<)~LGDo#QdEf*c9ymkb{apr zLD$=X3LuZ>?zSeMyfX6|QPPT9tA9@lJwyVnZrkm59I(W%=I8Y;ErSN{O<9C93{=z9 zRLG%lfd5&>%>Zfmqu@2kbmm8AKYnfdImt!O_KH1tArrs+ktEm_*!SJM&Ltb@-l|HE z`U-8-8poEbFy1sNB(HwyCeh8An1-yI7RvMTa#-^33ZI@HaG8kA4!-7zS&ff zN(rK;0VT@$CqN<*T`}l0J8{>)Q>&O5@t_bS=_MyDyjR^c1B=&JWTGwJKcNt)PvV2N z9WtDH1=?$uE6fiikhn3;T~9~(J^dA1debUq;+yCj+pDm^Z}S^uH33!d0rwvagC8~W z`6f78PVQrakw7x?O29azRe>i-MGQsT}Gbw|JO-#n4>Ai@|nltrHiUS zs6v46{@eQDWy+-h1l1O$4zy7+94FITQm@$J);tkbc{LJWf@9Fk*80!$YE$NvrS z#_tjAgnRV){H$3&j){pGji_7K!yrjRY(IDWua`|$WJ6#TkRw46_KB@%T zM~<}7c=)bo$LD^Zb-;+Wsl1WYEwduQdnArfho^^o;{fpnnw(VhpNw*)$vCZ9k| z0Q1M@Zr+(^jOnlPa_Wb6fjxS=dt}7@zkk8@#$WB1($*?R&B4$K3^EAVcx?JSs)TqH z;v>;>X6(>fZIw>|F>GLOL_;KbeWb2V`%&*!5N9YtF!)_tTWV^Oc-%|8e=>>)DhM@# zc$z2KJN&d1rMs8qaW4ufl>F4qmr^a8`+T)d9m*qE< z5!hhiE?az-8joN8lyiT4155lxDP-i6bRJ+%wfwZ6Z?3zZaEh_aqY+Yna^GG&5LRo$ z0izkJ`0z!etxSllihB6pb-Uu@{D-)I+nq|LihjiCE zM&^3tEOMO~x+``8CCYG>;q;)n$PS~ai+tDXc^&QG%RC+C7pX*0X#31&r{$Bp)Pmn? z)Wf3J9J(bwBwL<7tda%W)Qe3iisnVv-JAWgT-L+UgdR3t_sOLtUG++@eV$55v`n_4 z8)Tp@V^^58Ty}IrD?chMDij2upz{VR#JfE|?{tt;>u8)Z(bQnj-qqOf!^Y#ugQYE+ zFH44DVq&Io&HDKG>&Kzs;*6~U{!j51KSC@0Fvc&2{dmLg|K>kYd`bXOgS+!KIAjjr zLX&=87;jW$^3^7*TK|D9ZM(rdXhlFK;;BY80>=1n$|ZBzgdtJ?8J)1%4`tsD5Vd@q zG|#ZIsf*IraL~n7QCS+Qyhl9mQeB^*Z?DDz!Qum%BUnsC zmoEc&<-jMDO-C6#$nR-eOto+Ci^rejYPI@udb}-%;bX#5mZn{U@71(gp+M;cD zP&RR6i5~wfinIOLWNbc?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,function(K,M,L,J,I){M=M.toLowerCase();a[M]=h(L);if(J){return J+I}else{if(I){j[M]=I.replace(/"/g,""")}}return""});return H};var m=function(J){J=J.replace(/\n/g,"\n\n");var I="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del";var H="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math";J=J.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,x);J=J.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,x);J=J.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,x);J=J.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,x);J=J.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,x);J=J.replace(/\n\n/g,"\n");return J};var x=function(H,I){var J=I;J=J.replace(/\n\n/g,"\n");J=J.replace(/^\n/,"");J=J.replace(/\n+$/g,"");J="\n\n~K"+(A.push(J)-1)+"K\n\n";return J};var G=function(I){I=f(I);var H=o("
      ");I=I.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,H);I=I.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm,H);I=I.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm,H);I=E(I);I=b(I);I=u(I);I=m(I);I=g(I);return I};var r=function(H){H=C(H);H=l(H);H=e(H);H=F(H);H=y(H);H=n(H);H=h(H);H=c(H);H=H.replace(/ +\n/g,"
      \n");return H};var l=function(I){var H=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi;I=I.replace(H,function(K){var J=K.replace(/(.)<\/?code>(?=.)/g,"$1`");J=w(J,"\\`*_");return J});return I};var y=function(H){H=H.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,D);H=H.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,D);H=H.replace(/(\[([^\[\]]+)\])()()()()()/g,D);return H};var D=function(N,T,S,R,Q,P,M,L){if(L==undefined){L=""}var K=T;var I=S;var J=R.toLowerCase();var H=Q;var O=L;if(H==""){if(J==""){J=I.toLowerCase().replace(/ ?\n/g," ")}H="#"+J;if(a[J]!=undefined){H=a[J];if(j[J]!=undefined){O=j[J]}}else{if(K.search(/\(\s*\)$/m)>-1){H=""}else{return K}}}H=w(H,"*_");var U='";return U};var F=function(H){H=H.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,t);H=H.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,t);return H};var t=function(N,T,S,R,Q,P,M,L){var K=T;var J=S;var I=R.toLowerCase();var H=Q;var O=L;if(!O){O=""}if(H==""){if(I==""){I=J.toLowerCase().replace(/ ?\n/g," ")}H="#"+I;if(a[I]!=undefined){H=a[I];if(j[I]!=undefined){O=j[I]}}else{return K}}J=J.replace(/"/g,""");H=w(H,"*_");var U=''+J+'"+r(J)+"")});H=H.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,function(J,I){return o("

      "+r(I)+"

      ")});H=H.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,function(I,L,K){var J=L.length;return o(""+r(K)+"")});return H};var p;var E=function(I){I+="~0";var H=/^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;if(i){I=I.replace(H,function(K,N,M){var O=N;var L=(M.search(/[*+-]/g)>-1)?"ul":"ol";O=O.replace(/\n{2,}/g,"\n\n\n");var J=p(O);J=J.replace(/\s+$/,"");J="<"+L+">"+J+"\n";return J})}else{H=/(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;I=I.replace(H,function(L,P,N,K){var O=P;var Q=N;var M=(K.search(/[*+-]/g)>-1)?"ul":"ol";var Q=Q.replace(/\n{2,}/g,"\n\n\n");var J=p(Q);J=O+"<"+M+">\n"+J+"\n";return J})}I=I.replace(/~0/,"");return I};p=function(H){i++;H=H.replace(/\n{2,}$/,"\n");H+="~0";H=H.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,function(K,M,L,J,I){var O=I;var N=M;var P=L;if(N||(O.search(/\n{2,}/)>-1)){O=G(s(O))}else{O=E(s(O));O=O.replace(/\n$/,"");O=r(O)}return"
    • "+O+"
    • \n"});H=H.replace(/~0/g,"");i--;return H};var b=function(H){H+="~0";H=H.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,function(I,K,J){var L=K;var M=J;L=v(s(L));L=z(L);L=L.replace(/^\n+/g,"");L=L.replace(/\n+$/g,"");L="
      "+L+"\n
      ";return o(L)+M});H=H.replace(/~0/,"");return H};var o=function(H){H=H.replace(/(^\n+|\n+$)/g,"");return"\n\n~K"+(A.push(H)-1)+"K\n\n"};var C=function(H){H=H.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(K,M,L,J,I){var N=J;N=N.replace(/^([ \t]*)/g,"");N=N.replace(/[ \t]*$/g,"");N=v(N);return M+""+N+""});return H};var v=function(H){H=H.replace(/&/g,"&");H=H.replace(//g,">");H=w(H,"*_{}[]\\",false);return H};var c=function(H){H=H.replace(/(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\1/g,"$2");H=H.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"$2");return H};var u=function(H){H=H.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(I,J){var K=J;K=K.replace(/^[ \t]*>[ \t]?/gm,"~0");K=K.replace(/~0/g,"");K=K.replace(/^[ \t]+$/gm,"");K=G(K);K=K.replace(/(^|\n)/g,"$1 ");K=K.replace(/(\s*
      [^\r]+?<\/pre>)/gm,function(L,M){var N=M;N=N.replace(/^  /mg,"~0");N=N.replace(/~0/g,"");return N});return o("
      \n"+K+"\n
      ")});return H};var g=function(N){N=N.replace(/^\n+/g,"");N=N.replace(/\n+$/g,"");var M=N.split(/\n{2,}/g);var J=new Array();var H=M.length;for(var I=0;I=0){J.push(L)}else{if(L.search(/\S/)>=0){L=r(L);L=L.replace(/^([ \t]*)/g,"

      ");L+="

      ";J.push(L)}}}H=J.length;for(var I=0;I=0){var K=A[RegExp.$1];K=K.replace(/\$/g,"$$$$");J[I]=J[I].replace(/~K\d+K/,K)}}return J.join("\n\n")};var h=function(H){H=H.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&");H=H.replace(/<(?![a-z\/?\$!])/gi,"<");return H};var e=function(H){H=H.replace(/\\(\\)/g,k);H=H.replace(/\\([`*_{}\[\]()>#+-.!])/g,k);return H};var n=function(H){H=H.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,'
      $1');H=H.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,function(I,J){return B(q(J))});return H};var B=function(J){function I(L){var K="0123456789ABCDEF";var M=L.charCodeAt(0);return(K.charAt(M>>4)+K.charAt(M&15))}var H=[function(K){return"&#"+K.charCodeAt(0)+";"},function(K){return"&#x"+I(K)+";"},function(K){return K}];J="mailto:"+J;J=J.replace(/./g,function(K){if(K=="@"){K=H[Math.floor(Math.random()*2)](K)}else{if(K!=":"){var L=Math.random();K=(L>0.9?H[2](K):L>0.45?H[1](K):H[0](K))}}return K});J=''+J+"";J=J.replace(/">.+:/g,'">');return J};var q=function(H){H=H.replace(/~E(\d+)E/g,function(I,K){var J=parseInt(K);return String.fromCharCode(J)});return H};var s=function(H){H=H.replace(/^(\t|[ ]{1,4})/gm,"~0");H=H.replace(/~0/g,"");return H};var z=function(H){H=H.replace(/\t(?=\t)/g," ");H=H.replace(/\t/g,"~A~B");H=H.replace(/~B(.+?)~A/g,function(I,L,K){var N=L;var J=4-N.length%4;for(var M=0;M -// -// Redistributable under a BSD-style open source license. -// See license.txt for more information. -// -// The full source distribution is at: -// -// A A L -// T C A -// T K B -// -// -// - -// -// Wherever possible, Showdown is a straight, line-by-line port -// of the Perl version of Markdown. -// -// This is not a normal parser design; it's basically just a -// series of string substitutions. It's hard to read and -// maintain this way, but keeping Showdown close to the original -// design makes it easier to port new features. -// -// More importantly, Showdown behaves like markdown.pl in most -// edge cases. So web applications can do client-side preview -// in Javascript, and then build identical HTML on the server. -// -// This port needs the new RegExp functionality of ECMA 262, -// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers -// should do fine. Even with the new regular expression features, -// We do a lot of work to emulate Perl's regex functionality. -// The tricky changes in this file mostly have the "attacklab:" -// label. Major or self-explanatory changes don't. -// -// Smart diff tools like Araxis Merge will be able to match up -// this file with markdown.pl in a useful way. A little tweaking -// helps: in a copy of markdown.pl, replace "#" with "//" and -// replace "$text" with "text". Be sure to ignore whitespace -// and line endings. -// - - -// -// Showdown usage: -// -// var text = "Markdown *rocks*."; -// -// var converter = new Attacklab.showdown.converter(); -// var html = converter.makeHtml(text); -// -// alert(html); -// -// Note: move the sample code to the bottom of this -// file before uncommenting it. -// - - -// -// Attacklab namespace -// -var Attacklab = Attacklab || {} - -// -// Showdown namespace -// -Attacklab.showdown = Attacklab.showdown || {} - -// -// converter -// -// Wraps all "globals" so that the only thing -// exposed is makeHtml(). -// -Attacklab.showdown.converter = function() { - -// -// Globals: -// - -// Global hashes, used by various utility routines -var g_urls; -var g_titles; -var g_html_blocks; - -// Used to track when we're inside an ordered or unordered list -// (see _ProcessListItems() for details): -var g_list_level = 0; - -var makeHtmlBase = function(text) { -// -// Main function. The order in which other subs are called here is -// essential. Link and image substitutions need to happen before -// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the -// and tags get encoded. -// - - // Clear the global hashes. If we don't clear these, you get conflicts - // from other articles when generating a page which contains more than - // one article (e.g. an index page that shows the N most recent - // articles): - g_urls = new Array(); - g_titles = new Array(); - g_html_blocks = new Array(); - - // attacklab: Replace ~ with ~T - // This lets us use tilde as an escape char to avoid md5 hashes - // The choice of character is arbitray; anything that isn't - // magic in Markdown will work. - text = text.replace(/~/g,"~T"); - - // attacklab: Replace $ with ~D - // RegExp interprets $ as a special character - // when it's in a replacement string - text = text.replace(/\$/g,"~D"); - - // Standardize line endings - text = text.replace(/\r\n/g,"\n"); // DOS to Unix - text = text.replace(/\r/g,"\n"); // Mac to Unix - - // Make sure text begins and ends with a couple of newlines: - text = "\n\n" + text + "\n\n"; - - // Convert all tabs to spaces. - text = _Detab(text); - - // Strip any lines consisting only of spaces and tabs. - // This makes subsequent regexen easier to write, because we can - // match consecutive blank lines with /\n+/ instead of something - // contorted like /[ \t]*\n+/ . - text = text.replace(/^[ \t]+$/mg,""); - - // Turn block-level HTML blocks into hash entries - text = _HashHTMLBlocks(text); - - // Strip link definitions, store in hashes. - text = _StripLinkDefinitions(text); - - text = _RunBlockGamut(text); - - text = _UnescapeSpecialChars(text); - - // attacklab: Restore dollar signs - text = text.replace(/~D/g,"$$"); - - // attacklab: Restore tildes - text = text.replace(/~T/g,"~"); - - return text; -} - -this.makeHtml = function(text){ - if (enableMathJax === false){ - return makeHtmlBase(text); - } - else { - MathJax.Hub.queue.Push( - function(){ - $('#previewer').html(makeHtmlBase(text)); - } - ); - MathJax.Hub.Queue(['Typeset', MathJax.Hub, 'previewer']); - return $('#previewer').html(); - } -} - - -var _StripLinkDefinitions = function(text) { -// -// Strips link definitions from text, stores the URLs and titles in -// hash references. -// - - // Link defs are in the form: ^[id]: url "optional title" - - /* - var text = text.replace(/ - ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 - [ \t]* - \n? // maybe *one* newline - [ \t]* - ? // url = $2 - [ \t]* - \n? // maybe one newline - [ \t]* - (?: - (\n*) // any lines skipped = $3 attacklab: lookbehind removed - ["(] - (.+?) // title = $4 - [")] - [ \t]* - )? // title is optional - (?:\n+|$) - /gm, - function(){...}); - */ - var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm, - function (wholeMatch,m1,m2,m3,m4) { - m1 = m1.toLowerCase(); - g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive - if (m3) { - // Oops, found blank lines, so it's not a title. - // Put back the parenthetical statement we stole. - return m3+m4; - } else if (m4) { - g_titles[m1] = m4.replace(/"/g,"""); - } - - // Completely remove the definition from the text - return ""; - } - ); - - return text; -} - -var _HashHTMLBlocks = function(text) { - // attacklab: Double up blank lines to reduce lookaround - text = text.replace(/\n/g,"\n\n"); - - // Hashify HTML blocks: - // We only want to do this for block-level HTML tags, such as headers, - // lists, and tables. That's because we still want to wrap

      s around - // "paragraphs" that are wrapped in non-block-level tags, such as anchors, - // phrase emphasis, and spans. The list of tags we're looking for is - // hard-coded: - var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del" - var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" - - // First, look for nested blocks, e.g.: - //

      - //
      - // tags for inner block must be indented. - //
      - //
      - // - // The outermost tags must start at the left margin for this to match, and - // the inner nested divs must be indented. - // We need to do this before the next, more liberal match, because the next - // match will start at the first `
      ` and stop at the first `
      `. - - // attacklab: This regex can be expensive when it fails. - /* - var text = text.replace(/ - ( // save in $1 - ^ // start of line (with /m) - <($block_tags_a) // start tag = $2 - \b // word break - // attacklab: hack around khtml/pcre bug... - [^\r]*?\n // any number of lines, minimally matching - // the matching end tag - [ \t]* // trailing spaces/tabs - (?=\n+) // followed by a newline - ) // attacklab: there are sentinel newlines at end of document - /gm,function(){...}}; - */ - text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement); - - // - // Now match more liberally, simply from `\n` to `\n` - // - - /* - var text = text.replace(/ - ( // save in $1 - ^ // start of line (with /m) - <($block_tags_b) // start tag = $2 - \b // word break - // attacklab: hack around khtml/pcre bug... - [^\r]*? // any number of lines, minimally matching - .* // the matching end tag - [ \t]* // trailing spaces/tabs - (?=\n+) // followed by a newline - ) // attacklab: there are sentinel newlines at end of document - /gm,function(){...}}; - */ - text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement); - - // Special case just for
      . It was easier to make a special case than - // to make the other regex more complicated. - - /* - text = text.replace(/ - ( // save in $1 - \n\n // Starting after a blank line - [ ]{0,3} - (<(hr) // start tag = $2 - \b // word break - ([^<>])*? // - \/?>) // the matching end tag - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); - - // Special case for standalone HTML comments: - - /* - text = text.replace(/ - ( // save in $1 - \n\n // Starting after a blank line - [ ]{0,3} // attacklab: g_tab_width - 1 - - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,hashElement); - - // PHP and ASP-style processor instructions ( and <%...%>) - - /* - text = text.replace(/ - (?: - \n\n // Starting after a blank line - ) - ( // save in $1 - [ ]{0,3} // attacklab: g_tab_width - 1 - (?: - <([?%]) // $2 - [^\r]*? - \2> - ) - [ \t]* - (?=\n{2,}) // followed by a blank line - ) - /g,hashElement); - */ - text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement); - - // attacklab: Undo double lines (see comment at top of this function) - text = text.replace(/\n\n/g,"\n"); - return text; -} - -var hashElement = function(wholeMatch,m1) { - var blockText = m1; - - // Undo double lines - blockText = blockText.replace(/\n\n/g,"\n"); - blockText = blockText.replace(/^\n/,""); - - // strip trailing blank lines - blockText = blockText.replace(/\n+$/g,""); - - // Replace the element text with a marker ("~KxK" where x is its key) - blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n"; - - return blockText; -}; - -var _RunBlockGamut = function(text) { -// -// These are all the transformations that form block-level -// tags like paragraphs, headers, and list items. -// - text = _DoHeaders(text); - - // Do Horizontal Rules: - var key = hashBlock("
      "); - text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key); - text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm,key); - text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm,key); - - text = _DoLists(text); - text = _DoCodeBlocks(text); - text = _DoBlockQuotes(text); - - // We already ran _HashHTMLBlocks() before, in Markdown(), but that - // was to escape raw HTML in the original Markdown source. This time, - // we're escaping the markup we've just created, so that we don't wrap - //

      tags around block-level tags. - text = _HashHTMLBlocks(text); - text = _FormParagraphs(text); - - return text; -} - - -var _RunSpanGamut = function(text) { -// -// These are all the transformations that occur *within* block-level -// tags like paragraphs, headers, and list items. -// - - text = _DoCodeSpans(text); - text = _EscapeSpecialCharsWithinTagAttributes(text); - text = _EncodeBackslashEscapes(text); - - // Process anchor and image tags. Images must come first, - // because ![foo][f] looks like an anchor. - text = _DoImages(text); - text = _DoAnchors(text); - - // Make links out of things like `` - // Must come after _DoAnchors(), because you can use < and > - // delimiters in inline links like [this](). - text = _DoAutoLinks(text); - text = _EncodeAmpsAndAngles(text); - text = _DoItalicsAndBold(text); - - // Do hard breaks: - text = text.replace(/ +\n/g,"
      \n"); - return text; -} - -var _EscapeSpecialCharsWithinTagAttributes = function(text) { -// -// Within tags -- meaning between < and > -- encode [\ ` * _] so they -// don't conflict with their use in Markdown for code, italics and strong. -// - - // Build a regex to find HTML tags and comments. See Friedl's - // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. - var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; - - text = text.replace(regex, function(wholeMatch) { - var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`"); - tag = escapeCharacters(tag,"\\`*_"); - return tag; - }); - - return text; -} - -var _DoAnchors = function(text) { -// -// Turn Markdown link shortcuts into XHTML
      tags. -// - // - // First, handle reference-style links: [link text] [id] - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ( - (?: - \[[^\]]*\] // allow brackets nested one level - | - [^\[] // or anything else - )* - ) - \] - - [ ]? // one optional space - (?:\n[ ]*)? // one optional newline followed by spaces - - \[ - (.*?) // id = $3 - \] - )()()()() // pad remaining backreferences - /g,_DoAnchors_callback); - */ - text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag); - - // - // Next, inline-style links: [link text](url "optional title") - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ( - (?: - \[[^\]]*\] // allow brackets nested one level - | - [^\[\]] // or anything else - ) - ) - \] - \( // literal paren - [ \t]* - () // no id, so leave $3 empty - ? // href = $4 - [ \t]* - ( // $5 - (['"]) // quote char = $6 - (.*?) // Title = $7 - \6 // matching quote - [ \t]* // ignore any spaces/tabs between closing quote and ) - )? // title is optional - \) - ) - /g,writeAnchorTag); - */ - text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag); - - // - // Last, handle reference-style shortcuts: [link text] - // These must come last in case you've also got [link test][1] - // or [link test](/foo) - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - \[ - ([^\[\]]+) // link text = $2; can't contain '[' or ']' - \] - )()()()()() // pad rest of backreferences - /g, writeAnchorTag); - */ - text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); - - return text; -} - -var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { - if (m7 == undefined) m7 = ""; - var whole_match = m1; - var link_text = m2; - var link_id = m3.toLowerCase(); - var url = m4; - var title = m7; - - if (url == "") { - if (link_id == "") { - // lower-case and turn embedded newlines into spaces - link_id = link_text.toLowerCase().replace(/ ?\n/g," "); - } - url = "#"+link_id; - - if (g_urls[link_id] != undefined) { - url = g_urls[link_id]; - if (g_titles[link_id] != undefined) { - title = g_titles[link_id]; - } - } - else { - if (whole_match.search(/\(\s*\)$/m)>-1) { - // Special case for explicit empty url - url = ""; - } else { - return whole_match; - } - } - } - - url = escapeCharacters(url,"*_"); - var result = ""; - - return result; -} - - -var _DoImages = function(text) { -// -// Turn Markdown image shortcuts into tags. -// - - // - // First, handle reference-style labeled images: ![alt text][id] - // - - /* - text = text.replace(/ - ( // wrap whole match in $1 - !\[ - (.*?) // alt text = $2 - \] - - [ ]? // one optional space - (?:\n[ ]*)? // one optional newline followed by spaces - - \[ - (.*?) // id = $3 - \] - )()()()() // pad rest of backreferences - /g,writeImageTag); - */ - text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag); - - // - // Next, handle inline images: ![alt text](url "optional title") - // Don't forget: encode * and _ - - /* - text = text.replace(/ - ( // wrap whole match in $1 - !\[ - (.*?) // alt text = $2 - \] - \s? // One optional whitespace character - \( // literal paren - [ \t]* - () // no id, so leave $3 empty - ? // src url = $4 - [ \t]* - ( // $5 - (['"]) // quote char = $6 - (.*?) // title = $7 - \6 // matching quote - [ \t]* - )? // title is optional - \) - ) - /g,writeImageTag); - */ - text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag); - - return text; -} - -var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { - var whole_match = m1; - var alt_text = m2; - var link_id = m3.toLowerCase(); - var url = m4; - var title = m7; - - if (!title) title = ""; - - if (url == "") { - if (link_id == "") { - // lower-case and turn embedded newlines into spaces - link_id = alt_text.toLowerCase().replace(/ ?\n/g," "); - } - url = "#"+link_id; - - if (g_urls[link_id] != undefined) { - url = g_urls[link_id]; - if (g_titles[link_id] != undefined) { - title = g_titles[link_id]; - } - } - else { - return whole_match; - } - } - - alt_text = alt_text.replace(/"/g,"""); - url = escapeCharacters(url,"*_"); - var result = "\""" + _RunSpanGamut(m1) + "");}); - - text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, - function(matchFound,m1){return hashBlock("

      " + _RunSpanGamut(m1) + "

      ");}); - - // atx-style headers: - // # Header 1 - // ## Header 2 - // ## Header 2 with closing hashes ## - // ... - // ###### Header 6 - // - - /* - text = text.replace(/ - ^(\#{1,6}) // $1 = string of #'s - [ \t]* - (.+?) // $2 = Header text - [ \t]* - \#* // optional closing #'s (not counted) - \n+ - /gm, function() {...}); - */ - - text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, - function(wholeMatch,m1,m2) { - var h_level = m1.length; - return hashBlock("" + _RunSpanGamut(m2) + ""); - }); - - return text; -} - -// This declaration keeps Dojo compressor from outputting garbage: -var _ProcessListItems; - -var _DoLists = function(text) { -// -// Form HTML ordered (numbered) and unordered (bulleted) lists. -// - - // attacklab: add sentinel to hack around khtml/safari bug: - // http://bugs.webkit.org/show_bug.cgi?id=11231 - text += "~0"; - - // Re-usable pattern to match any entirel ul or ol list: - - /* - var whole_list = / - ( // $1 = whole list - ( // $2 - [ ]{0,3} // attacklab: g_tab_width - 1 - ([*+-]|\d+[.]) // $3 = first list item marker - [ \t]+ - ) - [^\r]+? - ( // $4 - ~0 // sentinel for workaround; should be $ - | - \n{2,} - (?=\S) - (?! // Negative lookahead for another list item marker - [ \t]* - (?:[*+-]|\d+[.])[ \t]+ - ) - ) - )/g - */ - var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; - - if (g_list_level) { - text = text.replace(whole_list,function(wholeMatch,m1,m2) { - var list = m1; - var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol"; - - // Turn double returns into triple returns, so that we can make a - // paragraph for the last item in a list, if necessary: - list = list.replace(/\n{2,}/g,"\n\n\n");; - var result = _ProcessListItems(list); - - // Trim any trailing whitespace, to put the closing `` - // up on the preceding line, to get it past the current stupid - // HTML block parser. This is a hack to work around the terrible - // hack that is the HTML block parser. - result = result.replace(/\s+$/,""); - result = "<"+list_type+">" + result + "\n"; - return result; - }); - } else { - whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; - text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) { - var runup = m1; - var list = m2; - - var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol"; - // Turn double returns into triple returns, so that we can make a - // paragraph for the last item in a list, if necessary: - var list = list.replace(/\n{2,}/g,"\n\n\n");; - var result = _ProcessListItems(list); - result = runup + "<"+list_type+">\n" + result + "\n"; - return result; - }); - } - - // attacklab: strip sentinel - text = text.replace(/~0/,""); - - return text; -} - -_ProcessListItems = function(list_str) { -// -// Process the contents of a single ordered or unordered list, splitting it -// into individual list items. -// - // The $g_list_level global keeps track of when we're inside a list. - // Each time we enter a list, we increment it; when we leave a list, - // we decrement. If it's zero, we're not in a list anymore. - // - // We do this because when we're not inside a list, we want to treat - // something like this: - // - // I recommend upgrading to version - // 8. Oops, now this line is treated - // as a sub-list. - // - // As a single paragraph, despite the fact that the second line starts - // with a digit-period-space sequence. - // - // Whereas when we're inside a list (or sub-list), that line will be - // treated as the start of a sub-list. What a kludge, huh? This is - // an aspect of Markdown's syntax that's hard to parse perfectly - // without resorting to mind-reading. Perhaps the solution is to - // change the syntax rules such that sub-lists must start with a - // starting cardinal number; e.g. "1." or "a.". - - g_list_level++; - - // trim trailing blank lines: - list_str = list_str.replace(/\n{2,}$/,"\n"); - - // attacklab: add sentinel to emulate \z - list_str += "~0"; - - /* - list_str = list_str.replace(/ - (\n)? // leading line = $1 - (^[ \t]*) // leading whitespace = $2 - ([*+-]|\d+[.]) [ \t]+ // list marker = $3 - ([^\r]+? // list item text = $4 - (\n{1,2})) - (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+)) - /gm, function(){...}); - */ - list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, - function(wholeMatch,m1,m2,m3,m4){ - var item = m4; - var leading_line = m1; - var leading_space = m2; - - if (leading_line || (item.search(/\n{2,}/)>-1)) { - item = _RunBlockGamut(_Outdent(item)); - } - else { - // Recursion for sub-lists: - item = _DoLists(_Outdent(item)); - item = item.replace(/\n$/,""); // chomp(item) - item = _RunSpanGamut(item); - } - - return "
    • " + item + "
    • \n"; - } - ); - - // attacklab: strip sentinel - list_str = list_str.replace(/~0/g,""); - - g_list_level--; - return list_str; -} - - -var _DoCodeBlocks = function(text) { -// -// Process Markdown `
      ` blocks.
      -//  
      -
      -	/*
      -		text = text.replace(text,
      -			/(?:\n\n|^)
      -			(								// $1 = the code block -- one or more lines, starting with a space/tab
      -				(?:
      -					(?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
      -					.*\n+
      -				)+
      -			)
      -			(\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
      -		/g,function(){...});
      -	*/
      -
      -	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
      -	text += "~0";
      -	
      -	text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
      -		function(wholeMatch,m1,m2) {
      -			var codeblock = m1;
      -			var nextChar = m2;
      -		
      -			codeblock = _EncodeCode( _Outdent(codeblock));
      -			codeblock = _Detab(codeblock);
      -			codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
      -			codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
      -
      -			codeblock = "
      " + codeblock + "\n
      "; - - return hashBlock(codeblock) + nextChar; - } - ); - - // attacklab: strip sentinel - text = text.replace(/~0/,""); - - return text; -} - -var hashBlock = function(text) { - text = text.replace(/(^\n+|\n+$)/g,""); - return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n"; -} - - -var _DoCodeSpans = function(text) { -// -// * Backtick quotes are used for spans. -// -// * You can use multiple backticks as the delimiters if you want to -// include literal backticks in the code span. So, this input: -// -// Just type ``foo `bar` baz`` at the prompt. -// -// Will translate to: -// -//

      Just type foo `bar` baz at the prompt.

      -// -// There's no arbitrary limit to the number of backticks you -// can use as delimters. If you need three consecutive backticks -// in your code, use four for delimiters, etc. -// -// * You can use spaces to get literal backticks at the edges: -// -// ... type `` `bar` `` ... -// -// Turns to: -// -// ... type `bar` ... -// - - /* - text = text.replace(/ - (^|[^\\]) // Character before opening ` can't be a backslash - (`+) // $2 = Opening run of ` - ( // $3 = The code block - [^\r]*? - [^`] // attacklab: work around lack of lookbehind - ) - \2 // Matching closer - (?!`) - /gm, function(){...}); - */ - - text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, - function(wholeMatch,m1,m2,m3,m4) { - var c = m3; - c = c.replace(/^([ \t]*)/g,""); // leading whitespace - c = c.replace(/[ \t]*$/g,""); // trailing whitespace - c = _EncodeCode(c); - return m1+""+c+""; - }); - - return text; -} - - -var _EncodeCode = function(text) { -// -// Encode/escape certain characters inside Markdown code runs. -// The point is that in code, these characters are literals, -// and lose their special Markdown meanings. -// - // Encode all ampersands; HTML entities are not - // entities within a Markdown code span. - text = text.replace(/&/g,"&"); - - // Do the angle bracket song and dance: - text = text.replace(//g,">"); - - // Now, escape characters that are magic in Markdown: - text = escapeCharacters(text,"\*_{}[]\\",false); - -// jj the line above breaks this: -//--- - -//* Item - -// 1. Subitem - -// special char: * -//--- - - return text; -} - - -var _DoItalicsAndBold = function(text) { - - // must go first: - if (codeFriendlyMarkdown === true){ - text = text.replace(/(\*\*)(?=\S)([^\r]*?\S[\*]*)\1/g, - "$2"); - - text = text.replace(/(\*)(?=\S)([^\r]*?\S)\1/g, - "$2"); - } - else { - text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\1/g, - "$2"); - - text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, - "$2"); - } - - return text; -} - - -var _DoBlockQuotes = function(text) { - - /* - text = text.replace(/ - ( // Wrap whole match in $1 - ( - ^[ \t]*>[ \t]? // '>' at the start of a line - .+\n // rest of the first line - (.+\n)* // subsequent consecutive lines - \n* // blanks - )+ - ) - /gm, function(){...}); - */ - - text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, - function(wholeMatch,m1) { - var bq = m1; - - // attacklab: hack around Konqueror 3.5.4 bug: - // "----------bug".replace(/^-/g,"") == "bug" - - bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting - - // attacklab: clean up hack - bq = bq.replace(/~0/g,""); - - bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines - bq = _RunBlockGamut(bq); // recurse - - bq = bq.replace(/(^|\n)/g,"$1 "); - // These leading spaces screw with
       content, so we need to fix that:
      -			bq = bq.replace(
      -					/(\s*
      [^\r]+?<\/pre>)/gm,
      -				function(wholeMatch,m1) {
      -					var pre = m1;
      -					// attacklab: hack around Konqueror 3.5.4 bug:
      -					pre = pre.replace(/^  /mg,"~0");
      -					pre = pre.replace(/~0/g,"");
      -					return pre;
      -				});
      -			
      -			return hashBlock("
      \n" + bq + "\n
      "); - }); - return text; -} - - -var _FormParagraphs = function(text) { -// -// Params: -// $text - string to process with html

      tags -// - - // Strip leading and trailing lines: - text = text.replace(/^\n+/g,""); - text = text.replace(/\n+$/g,""); - - var grafs = text.split(/\n{2,}/g); - var grafsOut = new Array(); - - // - // Wrap

      tags. - // - var end = grafs.length; - for (var i=0; i= 0) { - grafsOut.push(str); - } - else if (str.search(/\S/) >= 0) { - str = _RunSpanGamut(str); - str = str.replace(/^([ \t]*)/g,"

      "); - str += "

      " - grafsOut.push(str); - } - - } - - // - // Unhashify HTML blocks - // - end = grafsOut.length; - for (var i=0; i= 0) { - var blockText = g_html_blocks[RegExp.$1]; - blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs - grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); - } - } - - return grafsOut.join("\n\n"); -} - - -var _EncodeAmpsAndAngles = function(text) { -// Smart processing for ampersands and angle brackets that need to be encoded. - - // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: - // http://bumppo.net/projects/amputator/ - text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); - - // Encode naked <'s - text = text.replace(/<(?![a-z\/?\$!])/gi,"<"); - - return text; -} - - -var _EncodeBackslashEscapes = function(text) { -// -// Parameter: String. -// Returns: The string, with after processing the following backslash -// escape sequences. -// - - // attacklab: The polite way to do this is with the new - // escapeCharacters() function: - // - // text = escapeCharacters(text,"\\",true); - // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); - // - // ...but we're sidestepping its use of the (slow) RegExp constructor - // as an optimization for Firefox. This function gets called a LOT. - - text = text.replace(/\\(\\)/g,escapeCharacters_callback); - text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback); - return text; -} - - -var _DoAutoLinks = function(text) { - - text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"
      $1"); - - // Email addresses: - - /* - text = text.replace(/ - < - (?:mailto:)? - ( - [-.\w]+ - \@ - [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ - ) - > - /gi, _DoAutoLinks_callback()); - */ - text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, - function(wholeMatch,m1) { - return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); - } - ); - - return text; -} - - -var _EncodeEmailAddress = function(addr) { -// -// Input: an email address, e.g. "foo@example.com" -// -// Output: the email address as a mailto link, with each character -// of the address encoded as either a decimal or hex entity, in -// the hopes of foiling most address harvesting spam bots. E.g.: -// -// foo -// @example.com -// -// Based on a filter by Matthew Wickline, posted to the BBEdit-Talk -// mailing list: -// - - // attacklab: why can't javascript speak hex? - function char2hex(ch) { - var hexDigits = '0123456789ABCDEF'; - var dec = ch.charCodeAt(0); - return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15)); - } - - var encode = [ - function(ch){return "&#"+ch.charCodeAt(0)+";";}, - function(ch){return "&#x"+char2hex(ch)+";";}, - function(ch){return ch;} - ]; - - addr = "mailto:" + addr; - - addr = addr.replace(/./g, function(ch) { - if (ch == "@") { - // this *must* be encoded. I insist. - ch = encode[Math.floor(Math.random()*2)](ch); - } else if (ch !=":") { - // leave ':' alone (to spot mailto: later) - var r = Math.random(); - // roughly 10% raw, 45% hex, 45% dec - ch = ( - r > .9 ? encode[2](ch) : - r > .45 ? encode[1](ch) : - encode[0](ch) - ); - } - return ch; - }); - - addr = "" + addr + ""; - addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part - - return addr; -} - - -var _UnescapeSpecialChars = function(text) { -// -// Swap back in all the special characters we've hidden. -// - text = text.replace(/~E(\d+)E/g, - function(wholeMatch,m1) { - var charCodeToReplace = parseInt(m1); - return String.fromCharCode(charCodeToReplace); - } - ); - return text; -} - - -var _Outdent = function(text) { -// -// Remove one level of line-leading tabs or spaces -// - - // attacklab: hack around Konqueror 3.5.4 bug: - // "----------bug".replace(/^-/g,"") == "bug" - - text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width - - // attacklab: clean up hack - text = text.replace(/~0/g,"") - - return text; -} - -var _Detab = function(text) { -// attacklab: Detab's completely rewritten for speed. -// In perl we could fix it by anchoring the regexp with \G. -// In javascript we're less fortunate. - - // expand first n-1 tabs - text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width - - // replace the nth with two sentinels - text = text.replace(/\t/g,"~A~B"); - - // use the sentinel to anchor our regex so it doesn't explode - text = text.replace(/~B(.+?)~A/g, - function(wholeMatch,m1,m2) { - var leadingText = m1; - var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width - - // there *must* be a better way to do this: - for (var i=0; i";var D="

      输入Web地址

      示例:
      http://www.cnprog.com/ \"我的网站\"

      ";var n='
      或者上传本地图片:

      ';var b="http://";var g="http://";var o="images/";var A=500;var x=100;var k="http://wmd-editor.com/";var r="WMD website";var w="_blank";y.PanelCollection=function(){this.buttonBar=E.getElementById("wmd-button-bar");this.preview=E.getElementById("previewer");this.output=E.getElementById("wmd-output");this.input=E.getElementById("editor")};y.panels=undefined;y.ieCachedRange=null;y.ieRetardedClick=false;a.isVisible=function(F){if(window.getComputedStyle){return window.getComputedStyle(F,null).getPropertyValue("display")!=="none"}else{if(F.currentStyle){return F.currentStyle.display!=="none"}}};a.addEvent=function(G,F,H){if(G.attachEvent){G.attachEvent("on"+F,H)}else{G.addEventListener(F,H,false)}};a.removeEvent=function(G,F,H){if(G.detachEvent){G.detachEvent("on"+F,H)}else{G.removeEventListener(F,H,false)}};a.fixEolChars=function(F){F=F.replace(/\r\n/g,"\n");F=F.replace(/\r/g,"\n");return F};a.extendRegExp=function(H,J,G){if(J===null||J===undefined){J=""}if(G===null||G===undefined){G=""}var I=H.toString();var F;I=I.replace(/\/([gim]*)$/,"");F=s.$1;I=I.replace(/(^\/|\/$)/g,"");I=J+I+G;return new s(I,F)};a.createImage=function(F){var H=o+F;var G=E.createElement("img");G.className="wmd-button";G.src=H;return G};a.prompt=function(M,P,H){var I;var F;var K;var J=0;if(arguments.length==4){J=arguments[3]}if(P===undefined){P=""}var L=function(Q){var R=(Q.charCode||Q.keyCode);if(R===27){N(true)}};var N=function(Q){a.removeEvent(E.body,"keydown",L);var R=K.value;if(Q){R=null}else{R=R.replace("http://http://","http://");R=R.replace("http://https://","https://");R=R.replace("http://ftp://","ftp://");if(R.indexOf("http://")===-1&&R.indexOf("ftp://")===-1){R="http://"+R}}I.parentNode.removeChild(I);F.parentNode.removeChild(F);H(R);return false};var G=function(){F=E.createElement("div");F.className="wmd-prompt-background";style=F.style;style.position="absolute";style.top="0";style.zIndex="1000";if(v.isKonqueror){style.backgroundColor="transparent"}else{if(v.isIE){style.filter="alpha(opacity=50)"}else{style.opacity="0.5"}}var Q=C.getPageSize();style.height=Q[1]+"px";if(v.isIE){style.left=E.documentElement.scrollLeft;style.width=E.documentElement.clientWidth}else{style.left="0";style.width="100%"}E.body.appendChild(F)};var O=function(){I=E.createElement("div");I.className="wmd-prompt-dialog";I.style.padding="10px;";I.style.position="fixed";I.style.width="400px";I.style.zIndex="1001";var Q=E.createElement("div");Q.innerHTML=M;Q.style.padding="5px";I.appendChild(Q);var S=E.createElement("form");S.onsubmit=function(){return N(false)};style=S.style;style.padding="0";style.margin="0";style.cssFloat="left";style.width="100%";style.textAlign="center";style.position="relative";I.appendChild(S);K=E.createElement("input");if(J==1){K.id="image-url"}K.type="text";K.value=P;style=K.style;style.display="block";style.width="80%";style.marginLeft=style.marginRight="auto";S.appendChild(K);if(J==1){var R=E.createElement("div");R.innerHTML=n;R.style.padding="5px";S.appendChild(R)}var U=E.createElement("input");U.type="button";U.onclick=function(){return N(false)};U.value="OK";style=U.style;style.margin="10px";style.display="inline";style.width="7em";var T=E.createElement("input");T.type="button";T.onclick=function(){return N(true)};T.value="Cancel";style=T.style;style.margin="10px";style.display="inline";style.width="7em";if(/mac/.test(l.platform.toLowerCase())){S.appendChild(T);S.appendChild(U)}else{S.appendChild(U);S.appendChild(T)}a.addEvent(E.body,"keydown",L);I.style.top="50%";I.style.left="50%";I.style.display="block";if(v.isIE_5or6){I.style.position="absolute";I.style.top=E.documentElement.scrollTop+200+"px";I.style.left="50%"}E.body.appendChild(I);I.style.marginTop=-(C.getHeight(I)/2)+"px";I.style.marginLeft=-(C.getWidth(I)/2)+"px"};G();top.setTimeout(function(){O();var R=P.length;if(K.selectionStart!==undefined){K.selectionStart=0;K.selectionEnd=R}else{if(K.createTextRange){var Q=K.createTextRange();Q.collapse(false);Q.moveStart("character",-R);Q.moveEnd("character",R);Q.select()}}K.focus()},0)};C.getTop=function(H,G){var F=H.offsetTop;if(!G){while(H=H.offsetParent){F+=H.offsetTop}}return F};C.getHeight=function(F){return F.offsetHeight||F.scrollHeight};C.getWidth=function(F){return F.offsetWidth||F.scrollWidth};C.getPageSize=function(){var G,H;var F,K;if(self.innerHeight&&self.scrollMaxY){G=E.body.scrollWidth;H=self.innerHeight+self.scrollMaxY}else{if(E.body.scrollHeight>E.body.offsetHeight){G=E.body.scrollWidth;H=E.body.scrollHeight}else{G=E.body.offsetWidth;H=E.body.offsetHeight}}if(self.innerHeight){F=self.innerWidth;K=self.innerHeight}else{if(E.documentElement&&E.documentElement.clientHeight){F=E.documentElement.clientWidth;K=E.documentElement.clientHeight}else{if(E.body){F=E.body.clientWidth;K=E.body.clientHeight}}}var J=Math.max(G,F);var I=Math.max(H,K);return[J,I,F,K]};y.inputPoller=function(O,H){var F=this;var K=y.panels.input;var G;var I;var L;var J;this.tick=function(){if(!a.isVisible(K)){return}if(K.selectionStart||K.selectionStart===0){var Q=K.selectionStart;var P=K.selectionEnd;if(Q!=G||P!=I){G=Q;I=P;if(L!=K.value){L=K.value;return true}}}return false};var N=function(){if(!a.isVisible(K)){return}if(F.tick()){O()}};var M=function(){J=top.setInterval(N,H)};this.destroy=function(){top.clearInterval(J)};M()};y.undoManager=function(Q){var U=this;var O=[];var M=0;var L="none";var G;var R;var H;var K;var F=function(W,V){if(L!=W){L=W;if(!V){I()}}if(!v.isIE||L!="moving"){H=top.setTimeout(N,1)}else{K=null}};var N=function(){K=new y.TextareaState();R.tick();H=undefined};this.setCommandMode=function(){L="command";I();H=top.setTimeout(N,0)};this.canUndo=function(){return M>1};this.canRedo=function(){if(O[M+1]){return true}return false};this.undo=function(){if(U.canUndo()){if(G){G.restore();G=null}else{O[M]=new y.TextareaState();O[--M].restore();if(Q){Q()}}}L="none";y.panels.input.focus();N()};this.redo=function(){if(U.canRedo()){O[++M].restore();if(Q){Q()}}L="none";y.panels.input.focus();N()};var I=function(){var V=K||new y.TextareaState();if(!V){return false}if(L=="moving"){if(!G){G=V}return}if(G){if(O[M-1].text!=G.text){O[M++]=G}G=null}O[M++]=V;O[M+1]=null;if(Q){Q()}};var P=function(V){var X=false;if(V.ctrlKey||V.metaKey){var W=V.charCode||V.keyCode;var Y=String.fromCharCode(W);switch(Y){case"y":U.redo();X=true;break;case"z":if(!V.shiftKey){U.undo()}else{U.redo()}X=true;break}}if(X){if(V.preventDefault){V.preventDefault()}if(top.event){top.event.returnValue=false}return}};var T=function(V){if(!V.ctrlKey&&!V.metaKey){var W=V.keyCode;if((W>=33&&W<=40)||(W>=63232&&W<=63235)){F("moving")}else{if(W==8||W==46||W==127){F("deleting")}else{if(W==13){F("newlines")}else{if(W==27){F("escape")}else{if((W<16||W>20)&&W!=91){F("typing")}}}}}}};var J=function(){a.addEvent(y.panels.input,"keypress",function(W){if((W.ctrlKey||W.metaKey)&&(W.keyCode==89||W.keyCode==90)){W.preventDefault()}});var V=function(){if(v.isIE||(K&&K.text!=y.panels.input.value)){if(H==undefined){L="paste";I();N()}}};R=new y.inputPoller(V,x);a.addEvent(y.panels.input,"keydown",P);a.addEvent(y.panels.input,"keydown",T);a.addEvent(y.panels.input,"mousedown",function(){F("moving")});y.panels.input.onpaste=V;y.panels.input.ondrop=V};var S=function(){J();N();I()};this.destroy=function(){if(R){R.destroy()}};S()};y.editor=function(O){if(!O){O=function(){}}var L=y.panels.input;var I=0;var P=this;var K;var R;var G;var M;var N;var U=function(W){L.focus();if(W.textOp){if(N){N.setCommandMode()}var Y=new y.TextareaState();if(!Y){return}var Z=Y.getChunks();var V=function(){L.focus();if(Z){Y.setChunks(Z)}Y.restore();O()};var X=W.textOp(Z,V);if(!X){V()}}if(W.execute){W.execute(P)}};var S=function(){if(N){F(document.getElementById("wmd-undo-button"),N.canUndo());F(document.getElementById("wmd-redo-button"),N.canRedo())}};var F=function(V,X){var Y="0px";var Z="-20px";var W="-40px";if(X){V.style.backgroundPosition=V.XShift+" "+Y;V.onmouseover=function(){this.style.backgroundPosition=this.XShift+" "+W};V.onmouseout=function(){this.style.backgroundPosition=this.XShift+" "+Y};if(v.isIE){V.onmousedown=function(){y.ieRetardedClick=true;y.ieCachedRange=document.selection.createRange()}}if(!V.isHelp){V.onclick=function(){if(this.onmouseout){this.onmouseout()}U(this);return false}}}else{V.style.backgroundPosition=V.XShift+" "+Z;V.onmouseover=V.onmouseout=V.onclick=function(){}}};var J=function(){var Z=document.getElementById("wmd-button-bar");var W="0px";var Y="-20px";var ae="-40px";var ak=document.createElement("ul");ak.id="wmd-button-row";ak=Z.appendChild(ak);var ad=document.createElement("li");ad.className="wmd-button";ad.id="wmd-bold-button";ad.title=c;ad.XShift="0px";ad.textOp=h.doBold;F(ad,true);ak.appendChild(ad);var ac=document.createElement("li");ac.className="wmd-button";ac.id="wmd-italic-button";ac.title=f;ac.XShift="-20px";ac.textOp=h.doItalic;F(ac,true);ak.appendChild(ac);var ah=document.createElement("li");ah.className="wmd-spacer";ah.id="wmd-spacer1";ak.appendChild(ah);var ai=document.createElement("li");ai.className="wmd-button";ai.id="wmd-link-button";ai.title=z;ai.XShift="-40px";ai.textOp=function(ap,aq){return h.doLinkOrImage(ap,aq,false)};F(ai,true);ak.appendChild(ai);var al=document.createElement("li");al.className="wmd-button";al.id="wmd-quote-button";al.title=u;al.XShift="-60px";al.textOp=h.doBlockquote;F(al,true);ak.appendChild(al);var am=document.createElement("li");am.className="wmd-button";am.id="wmd-code-button";am.title=e;am.XShift="-80px";am.textOp=h.doCode;F(am,true);ak.appendChild(am);var aa=document.createElement("li");aa.className="wmd-button";aa.id="wmd-image-button";aa.title=d;aa.XShift="-100px";aa.textOp=function(ap,aq){return h.doLinkOrImage(ap,aq,true)};F(aa,true);ak.appendChild(aa);var ag=document.createElement("li");ag.className="wmd-spacer";ag.id="wmd-spacer2";ak.appendChild(ag);var ab=document.createElement("li");ab.className="wmd-button";ab.id="wmd-olist-button";ab.title=q;ab.XShift="-120px";ab.textOp=function(ap,aq){h.doList(ap,aq,true)};F(ab,true);ak.appendChild(ab);var ao=document.createElement("li");ao.className="wmd-button";ao.id="wmd-ulist-button";ao.title=t;ao.XShift="-140px";ao.textOp=function(ap,aq){h.doList(ap,aq,false)};F(ao,true);ak.appendChild(ao);var aj=document.createElement("li");aj.className="wmd-button";aj.id="wmd-heading-button";aj.title=i;aj.XShift="-160px";aj.textOp=h.doHeading;F(aj,true);ak.appendChild(aj);var X=document.createElement("li");X.className="wmd-button";X.id="wmd-hr-button";X.title=p;X.XShift="-180px";X.textOp=h.doHorizontalRule;F(X,true);ak.appendChild(X);var af=document.createElement("li");af.className="wmd-spacer";af.id="wmd-spacer3";ak.appendChild(af);var V=document.createElement("li");V.className="wmd-button";V.id="wmd-undo-button";V.title=m;V.XShift="-200px";V.execute=function(ap){ap.undo()};F(V,true);ak.appendChild(V);var an=document.createElement("li");an.className="wmd-button";an.id="wmd-redo-button";an.title=j;if(/win/.test(l.platform.toLowerCase())){an.title=j}else{an.title="重做 - Ctrl+Shift+Z"}an.XShift="-220px";an.execute=function(ap){ap.redo()};F(an,true);ak.appendChild(an);S()};var H=function(){if(/\?noundo/.test(E.location.href)){y.nativeUndo=true}if(!y.nativeUndo){N=new y.undoManager(function(){O();S()})}J();var W="keydown";if(v.isOpera){W="keypress"}a.addEvent(L,W,function(Y){if(Y.ctrlKey||Y.metaKey){var Z=Y.charCode||Y.keyCode;var X=String.fromCharCode(Z).toLowerCase();if(Z===46){X=""}if(Z===190){X="."}switch(X){case"b":U(document.getElementById("wmd-bold-button"));break;case"i":U(document.getElementById("wmd-italic-button"));break;case"l":U(document.getElementById("wmd-link-button"));break;case".":U(document.getElementById("wmd-quote-button"));break;case"k":U(document.getElementById("wmd-code-button"));break;case"g":U(document.getElementById("wmd-image-button"));break;case"o":U(document.getElementById("wmd-olist-button"));break;case"u":U(document.getElementById("wmd-ulist-button"));break;case"h":U(document.getElementById("wmd-heading-button"));break;case"r":U(document.getElementById("wmd-hr-button"));break;case"y":U(document.getElementById("wmd-redo-button"));break;case"z":if(Y.shiftKey){U(document.getElementById("wmd-redo-button"))}else{U(document.getElementById("wmd-undo-button"))}break;default:return}if(Y.preventDefault){Y.preventDefault()}if(top.event){top.event.returnValue=false}}});a.addEvent(L,"keyup",function(X){if(X.shiftKey&&!X.ctrlKey&&!X.metaKey){var Y=X.charCode||X.keyCode;if(Y===13){fakeButton={};fakeButton.textOp=h.doAutoindent;U(fakeButton)}}});if(L.form){var V=L.form.onsubmit;L.form.onsubmit=function(){Q();if(V){return V.apply(this,arguments)}}}};var Q=function(){if(y.showdown){var V=new y.showdown.converter()}var W=L.value;var X=function(){L.value=W};if(!/markdown/.test(y.wmd_env.output.toLowerCase())){if(V){L.value=V.makeHtml(W);top.setTimeout(X,0)}}return true};this.undo=function(){if(N){N.undo()}};this.redo=function(){if(N){N.redo()}};var T=function(){H()};this.destroy=function(){if(N){N.destroy()}if(G.parentNode){G.parentNode.removeChild(G)}if(L){L.style.marginTop=""}top.clearInterval(M)};T()};y.TextareaState=function(){var F=this;var G=y.panels.input;this.init=function(){if(!a.isVisible(G)){return}this.setInputAreaSelectionStartEnd();this.scrollTop=G.scrollTop;if(!this.text&&G.selectionStart||G.selectionStart===0){this.text=G.value}};this.setInputAreaSelection=function(){if(!a.isVisible(G)){return}if(G.selectionStart!==undefined&&!v.isOpera){G.focus();G.selectionStart=F.start;G.selectionEnd=F.end;G.scrollTop=F.scrollTop}else{if(E.selection){if(E.activeElement&&E.activeElement!==G){return}G.focus();var H=G.createTextRange();H.moveStart("character",-G.value.length);H.moveEnd("character",-G.value.length);H.moveEnd("character",F.end);H.moveStart("character",F.start);H.select()}}};this.setInputAreaSelectionStartEnd=function(){if(G.selectionStart||G.selectionStart===0){F.start=G.selectionStart;F.end=G.selectionEnd}else{if(E.selection){F.text=a.fixEolChars(G.value);var K;if(y.ieRetardedClick&&y.ieCachedRange){K=y.ieCachedRange;y.ieRetardedClick=false}else{K=E.selection.createRange()}var L=a.fixEolChars(K.text);var J="\x07";var I=J+L+J;K.text=I;var M=a.fixEolChars(G.value);K.moveStart("character",-I.length);K.text=L;F.start=M.indexOf(J);F.end=M.lastIndexOf(J)-J.length;var H=F.text.length-a.fixEolChars(G.value).length;if(H){K.moveStart("character",-L.length);while(H--){L+="\n";F.end+=1}K.text=L}this.setInputAreaSelection()}}};this.restore=function(){if(F.text!=undefined&&F.text!=G.value){G.value=F.text}this.setInputAreaSelection();G.scrollTop=F.scrollTop};this.getChunks=function(){var H=new y.Chunks();H.before=a.fixEolChars(F.text.substring(0,F.start));H.startTag="";H.selection=a.fixEolChars(F.text.substring(F.start,F.end));H.endTag="";H.after=a.fixEolChars(F.text.substring(F.end));H.scrollTop=F.scrollTop;return H};this.setChunks=function(H){H.before=H.before+H.startTag;H.after=H.endTag+H.after;if(v.isOpera){H.before=H.before.replace(/\n/g,"\r\n");H.selection=H.selection.replace(/\n/g,"\r\n");H.after=H.after.replace(/\n/g,"\r\n")}this.start=H.before.length;this.end=H.before.length+H.selection.length;this.text=H.before+H.selection+H.after;this.scrollTop=H.scrollTop};this.init()};y.Chunks=function(){};y.Chunks.prototype.findTags=function(G,I){var F=this;var H;if(G){H=a.extendRegExp(G,"","$");this.before=this.before.replace(H,function(J){F.startTag=F.startTag+J;return""});H=a.extendRegExp(G,"^","");this.selection=this.selection.replace(H,function(J){F.startTag=F.startTag+J;return""})}if(I){H=a.extendRegExp(I,"","$");this.selection=this.selection.replace(H,function(J){F.endTag=J+F.endTag;return""});H=a.extendRegExp(I,"^","");this.after=this.after.replace(H,function(J){F.endTag=J+F.endTag;return""})}};y.Chunks.prototype.trimWhitespace=function(F){this.selection=this.selection.replace(/^(\s*)/,"");if(!F){this.before+=s.$1}this.selection=this.selection.replace(/(\s*)$/,"");if(!F){this.after=s.$1+this.after}};y.Chunks.prototype.skipLines=function(H,G,F){if(H===undefined){H=1}if(G===undefined){G=1}H++;G++;var I;var J;this.selection=this.selection.replace(/(^\n*)/,"");this.startTag=this.startTag+s.$1;this.selection=this.selection.replace(/(\n*$)/,"");this.endTag=this.endTag+s.$1;this.startTag=this.startTag.replace(/(^\n*)/,"");this.before=this.before+s.$1;this.endTag=this.endTag.replace(/(\n*$)/,"");this.after=this.after+s.$1;if(this.before){I=J="";while(H--){I+="\\n?";J+="\n"}if(F){I="\\n*"}this.before=this.before.replace(new s(I+"$",""),J)}if(this.after){I=J="";while(G--){I+="\\n?";J+="\n"}if(F){I="\\n*"}this.after=this.after.replace(new s(I,""),J)}};h.prefixes="(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)";h.unwrap=function(G){var F=new s("([^\\n])\\n(?!(\\n|"+h.prefixes+"))","g");G.selection=G.selection.replace(F,"$1 $2")};h.wrap=function(G,F){h.unwrap(G);var H=new s("(.{1,"+F+"})( +|$\\n?)","gm");G.selection=G.selection.replace(H,function(I,J){if(new s("^"+h.prefixes,"").test(I)){return I}return J+"\n"});G.selection=G.selection.replace(/\s+$/,"")};h.doBold=function(F,G){return h.doBorI(F,G,2,"strong text")};h.doItalic=function(F,G){return h.doBorI(F,G,1,"emphasized text")};h.doBorI=function(L,J,K,F){L.trimWhitespace();L.selection=L.selection.replace(/\n{2,}/g,"\n");L.before.search(/(\**$)/);var I=s.$1;L.after.search(/(^\**)/);var G=s.$1;var M=Math.min(I.length,G.length);if((M>=K)&&(M!=2||K!=1)){L.before=L.before.replace(s("[*]{"+K+"}$",""),"");L.after=L.after.replace(s("^[*]{"+K+"}",""),"")}else{if(!L.selection&&G){L.after=L.after.replace(/^([*_]*)/,"");L.before=L.before.replace(/(\s?)$/,"");var H=s.$1;L.before=L.before+G+H}else{if(!L.selection&&!G){L.selection=F}var N=K<=1?"*":"**";L.before=L.before+N;L.after=N+L.after}}return};h.stripLinkDefs=function(G,F){G=G.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm,function(K,L,H,I,J){F[L]=K.replace(/\s*$/,"");if(I){F[L]=K.replace(/["(](.+?)[")]$/,"");return I+J}return""});return G};h.addLinkDef=function(M,I){var F=0;var H={};M.before=h.stripLinkDefs(M.before,H);M.selection=h.stripLinkDefs(M.selection,H);M.after=h.stripLinkDefs(M.after,H);var G="";var L=/(\[(?:\[[^\]]*\]|[^\[\]])*\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g;var K=function(O){F++;O=O.replace(/^[ ]{0,3}\[(\d+)\]:/," ["+F+"]:");G+="\n"+O};var J=function(P,Q,R,O){if(H[R]){K(H[R]);return Q+F+O}return P};M.before=M.before.replace(L,J);if(I){K(I)}else{M.selection=M.selection.replace(L,J)}var N=F;M.after=M.after.replace(L,J);if(M.after){M.after=M.after.replace(/\n*$/,"")}if(!M.after){M.selection=M.selection.replace(/\n*$/,"")}M.after+="\n\n"+G;return N};h.doLinkOrImage=function(F,G,I){F.trimWhitespace();F.findTags(/\s*!?\[/,/\][ ]?(?:\n[ ]*)?(\[.*?\])?/);if(F.endTag.length>1){F.startTag=F.startTag.replace(/!?\[/,"");F.endTag="";h.addLinkDef(F,null)}else{if(/\n\n/.test(F.selection)){h.addLinkDef(F,null);return}var H=function(L){if(L!==null){F.startTag=F.endTag="";var K=" [999]: "+L;var J=h.addLinkDef(F,K);F.startTag=I?"![":"[";F.endTag="]["+J+"]";if(!F.selection){if(I){F.selection="alt text"}else{F.selection="link text"}}}G()};if(I){a.prompt(B,b,H,1)}else{a.prompt(D,g,H)}return true}};a.makeAPI=function(){y.wmd={};y.wmd.editor=y.editor;y.wmd.previewManager=y.previewManager};a.startEditor=function(){if(y.wmd_env.autostart===false){a.makeAPI();return}var G;var F;var H=function(){y.panels=new y.PanelCollection();F=new y.previewManager();var I=F.refresh;G=new y.editor(I);F.refresh(true)};a.addEvent(top,"load",H)};y.previewManager=function(){var H=this;var V;var F;var N;var M;var S;var O;var I=3000;var P="delayed";var K=function(X,Y){a.addEvent(X,"input",Y);X.onpaste=Y;X.ondrop=Y;a.addEvent(X,"keypress",Y);a.addEvent(X,"keydown",Y);F=new y.inputPoller(Y,A)};var R=function(){var X=0;if(top.innerHeight){X=top.pageYOffset}else{if(E.documentElement&&E.documentElement.scrollTop){X=E.documentElement.scrollTop}else{if(E.body){X=E.body.scrollTop}}}return X};var L=function(){if(!y.panels.preview&&!y.panels.output){return}var Z=y.panels.input.value;if(Z&&Z==S){return}else{S=Z}var Y=new Date().getTime();if(!V&&y.showdown){V=new y.showdown.converter()}if(V){Z=V.makeHtml(Z)}var X=new Date().getTime();M=X-Y;G(Z);O=Z};var U=function(){if(N){top.clearTimeout(N);N=undefined}if(P!=="manual"){var X=0;if(P==="delayed"){X=M}if(X>I){X=I}N=top.setTimeout(L,X)}};var J=function(X){if(X.scrollHeight<=X.clientHeight){return 1}return X.scrollTop/(X.scrollHeight-X.clientHeight)};var W=function(){if(y.panels.preview){y.panels.preview.scrollTop=(y.panels.preview.scrollHeight-y.panels.preview.clientHeight)*J(y.panels.preview)}if(y.panels.output){y.panels.output.scrollTop=(y.panels.output.scrollHeight-y.panels.output.clientHeight)*J(y.panels.output)}};this.refresh=function(X){if(X){S="";L()}else{U()}};this.processingTime=function(){return M};this.output=function(){return O};this.setUpdateMode=function(X){P=X;H.refresh()};var Q=true;var G=function(aa){var X=C.getTop(y.panels.input)-R();if(y.panels.output){if(y.panels.output.value!==undefined){y.panels.output.value=aa;y.panels.output.readOnly=true}else{var Z=aa.replace(/&/g,"&");Z=Z.replace(/"+Z+"
      "}}if(y.panels.preview){y.panels.preview.innerHTML=aa}W();if(Q){Q=false;return}var Y=C.getTop(y.panels.input)-R();if(v.isIE){top.setTimeout(function(){top.scrollBy(0,Y-X)},0)}else{top.scrollBy(0,Y-X)}};var T=function(){K(y.panels.input,U);L();if(y.panels.preview){y.panels.preview.scrollTop=0}if(y.panels.output){y.panels.output.scrollTop=0}};this.destroy=function(){if(F){F.destroy()}};T()};h.doAutoindent=function(F,G){F.before=F.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/,"\n\n");F.before=F.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/,"\n\n");F.before=F.before.replace(/(\n|^)[ \t]+\n$/,"\n\n");if(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(F.before)){if(h.doList){h.doList(F)}}if(/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(F.before)){if(h.doBlockquote){h.doBlockquote(F)}}if(/(\n|^)(\t|[ ]{4,}).*\n$/.test(F.before)){if(h.doCode){h.doCode(F)}}};h.doBlockquote=function(F,G){F.selection=F.selection.replace(/^(\n*)([^\r]+?)(\n*)$/,function(L,K,J,I){F.before+=K;F.after=I+F.after;return J});F.before=F.before.replace(/(>[ \t]*)$/,function(J,I){F.selection=I+F.selection;return""});F.selection=F.selection.replace(/^(\s|>)+$/,"");F.selection=F.selection||"Blockquote";if(F.before){F.before=F.before.replace(/\n?$/,"\n")}if(F.after){F.after=F.after.replace(/^\n?/,"\n")}F.before=F.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/,function(I){F.startTag=I;return""});F.after=F.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/,function(I){F.endTag=I;return""});var H=function(J){var I=J?"> ":"";if(F.startTag){F.startTag=F.startTag.replace(/\n((>|\s)*)\n$/,function(L,K){return"\n"+K.replace(/^[ ]{0,3}>?[ \t]*$/gm,I)+"\n"})}if(F.endTag){F.endTag=F.endTag.replace(/^\n((>|\s)*)\n/,function(L,K){return"\n"+K.replace(/^[ ]{0,3}>?[ \t]*$/gm,I)+"\n"})}};if(/^(?![ ]{0,3}>)/m.test(F.selection)){h.wrap(F,y.wmd_env.lineLength-2);F.selection=F.selection.replace(/^/gm,"> ");H(true);F.skipLines()}else{F.selection=F.selection.replace(/^[ ]{0,3}> ?/gm,"");h.unwrap(F);H(false);if(!/^(\n|^)[ ]{0,3}>/.test(F.selection)&&F.startTag){F.startTag=F.startTag.replace(/\n{0,2}$/,"\n\n")}if(!/(\n|^)[ ]{0,3}>.*$/.test(F.selection)&&F.endTag){F.endTag=F.endTag.replace(/^\n{0,2}/,"\n\n")}}if(!/\n/.test(F.selection)){F.selection=F.selection.replace(/^(> *)/,function(I,J){F.startTag+=J;return""})}};h.doCode=function(F,G){var I=/\S[ ]*$/.test(F.before);var K=/^[ ]*\S/.test(F.after);if((!K&&!I)||/\n/.test(F.selection)){F.before=F.before.replace(/[ ]{4}$/,function(L){F.selection=L+F.selection;return""});var J=1;var H=1;if(/\n(\t|[ ]{4,}).*\n$/.test(F.before)){J=0}if(/^\n(\t|[ ]{4,})/.test(F.after)){H=0}F.skipLines(J,H);if(!F.selection){F.startTag=" ";F.selection="enter code here"}else{if(/^[ ]{0,3}\S/m.test(F.selection)){F.selection=F.selection.replace(/^/gm," ")}else{F.selection=F.selection.replace(/^[ ]{4}/gm,"")}}}else{F.trimWhitespace();F.findTags(/`/,/`/);if(!F.startTag&&!F.endTag){F.startTag=F.endTag="`";if(!F.selection){F.selection="enter code here"}}else{if(F.endTag&&!F.startTag){F.before+=F.endTag;F.endTag=""}else{F.startTag=F.endTag=""}}}};h.doList=function(Q,J,I){var S=/(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/;var R=/^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/;var F="-";var N=1;var L=function(){var T;if(I){T=" "+N+". ";N++}else{T=" "+F+" "}return T};var M=function(T){if(I===undefined){I=/^\s*\d/.test(T)}T=T.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm,function(U){return L()});return T};Q.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/,null);if(Q.before&&!/\n$/.test(Q.before)&&!/^\n/.test(Q.startTag)){Q.before+=Q.startTag;Q.startTag=""}if(Q.startTag){var H=/\d+[.]/.test(Q.startTag);Q.startTag="";Q.selection=Q.selection.replace(/\n[ ]{4}/g,"\n");h.unwrap(Q);Q.skipLines();if(H){Q.after=Q.after.replace(R,M)}if(I==H){return}}var K=1;Q.before=Q.before.replace(S,function(T){if(/^\s*([*+-])/.test(T)){F=s.$1}K=/[^\n]\n\n[^\n]/.test(T)?1:0;return M(T)});if(!Q.selection){Q.selection="List item"}var O=L();var G=1;Q.after=Q.after.replace(R,function(T){G=/[^\n]\n\n[^\n]/.test(T)?1:0;return M(T)});Q.trimWhitespace(true);Q.skipLines(K,G,true);Q.startTag=O;var P=O.replace(/./g," ");h.wrap(Q,y.wmd_env.lineLength-P.length);Q.selection=Q.selection.replace(/\n/g,"\n"+P)};h.doHeading=function(H,I){H.selection=H.selection.replace(/\s+/g," ");H.selection=H.selection.replace(/(^\s+|\s+$)/g,"");if(!H.selection){H.startTag="## ";H.selection="Heading";H.endTag=" ##";return}var J=0;H.findTags(/#+[ ]*/,/[ ]*#+/);if(/#+/.test(H.startTag)){J=s.lastMatch.length}H.startTag=H.endTag="";H.findTags(null,/\s?(-+|=+)/);if(/=+/.test(H.endTag)){J=1}if(/-+/.test(H.endTag)){J=2}H.startTag=H.endTag="";H.skipLines(1,1);var K=J==0?2:J-1;if(K>0){var G=K>=2?"-":"=";var F=H.selection.length;if(F>y.wmd_env.lineLength){F=y.wmd_env.lineLength}H.endTag="\n";while(F--){H.endTag+=G}}};h.doHorizontalRule=function(F,G){F.startTag="----------\n";F.selection="";F.skipLines(2,1,true)}};Attacklab.wmd_env={};Attacklab.account_options={};Attacklab.wmd_defaults={version:1,output:"Markdown",lineLength:40,delayLoad:false};if(!Attacklab.wmd){Attacklab.wmd=function(){Attacklab.loadEnv=function(){var b=function(d){if(!d){return}for(var c in d){Attacklab.wmd_env[c]=d[c]}};b(Attacklab.wmd_defaults);b(Attacklab.account_options);b(top.wmd_options);Attacklab.full=true;var a="bold italic link blockquote code image ol ul heading hr";Attacklab.wmd_env.buttons=Attacklab.wmd_env.buttons||a};Attacklab.loadEnv()};Attacklab.wmd();Attacklab.wmdBase();Attacklab.Util.startEditor()}; \ No newline at end of file diff --git a/lms/askbot/skins/common/media/js/wmd/wmd-test.html b/lms/askbot/skins/common/media/js/wmd/wmd-test.html deleted file mode 100644 index d748501a5b..0000000000 --- a/lms/askbot/skins/common/media/js/wmd/wmd-test.html +++ /dev/null @@ -1,158 +0,0 @@ - - - - - Test WMD Page - - - - - - - - - - - - - - -
      -
      - -
      -
      -
      -
      - -

      To test that page up/down and arrow keys work, copy this above the WMD - control.

      - - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - Scroll Down!
      - - - - \ No newline at end of file diff --git a/lms/askbot/skins/common/media/js/wmd/wmd.css b/lms/askbot/skins/common/media/js/wmd/wmd.css deleted file mode 100644 index 4ca764029f..0000000000 --- a/lms/askbot/skins/common/media/js/wmd/wmd.css +++ /dev/null @@ -1,131 +0,0 @@ -/* .wmd-panel */ -/* { */ -/* } */ - -/* #wmd-button-bar */ -/* { */ -/* background: url(images/editor-toolbar-background.png) repeat-x bottom; */ -/* height:36px; */ -/* border-left:#cce6ec 3px solid; */ -/* border-top:#cce6ec 3px solid; */ -/* border-right:#cce6ec 3px solid; */ -/* float:left; */ -/* width:730px; */ -/* } */ - -/* #wmd-input */ -/* { */ -/* height: 500px; */ -/* background-color: Gainsboro; */ -/* border: 1px solid DarkGray; */ -/* margin-top: -20px; */ -/* } */ - -/* #wmd-preview */ -/* { */ -/* background-color: LightSkyBlue; */ -/* } */ - -/* #wmd-output */ -/* { */ -/* background-color: Pink; */ -/* } */ - -/* #wmd-button-row */ -/* { */ -/* position: relative; */ -/* margin-left: 5px; */ -/* margin-right: 5px; */ -/* margin-bottom: 0px; */ -/* margin-top: 10px; */ -/* padding: 0px; */ -/* height: 20px; */ -/* } */ - -/* .wmd-spacer */ -/* { */ -/* width: 1px; */ -/* height: 20px; */ -/* margin-left: 14px; */ - -/* position: absolute; */ -/* background-color: Silver; */ -/* display: inline-block; */ -/* list-style: none; */ -/* } */ - -/* .wmd-button */ -/* { */ -/* width: 20px; */ -/* height: 20px; */ -/* margin-left: 5px; */ -/* margin-right: 5px; */ - -/* position: absolute; */ -/* background-image: url(images/wmd-buttons.png); */ -/* background-repeat: no-repeat; */ -/* background-position: 0px 0px; */ -/* display: inline-block; */ -/* list-style: none; */ -/* } */ - -/* .wmd-button > a */ -/* { */ -/* width: 20px; */ -/* height: 20px; */ -/* margin-left: 5px; */ -/* margin-right: 5px; */ - -/* position: absolute; */ -/* display: inline-block; */ -/* } */ - - -/* /1* sprite button slicing style information *1/ */ -/* #wmd-button-bar #wmd-bold-button {left: 0px; background-position: 0px 0;} */ -/* #wmd-button-bar #wmd-italic-button {left: 25px; background-position: -20px 0;} */ -/* #wmd-button-bar #wmd-spacer1 {left: 50px;} */ -/* #wmd-button-bar #wmd-link-button {left: 75px; background-position: -40px 0;} */ -/* #wmd-button-bar #wmd-quote-button {left: 100px; background-position: -60px 0;} */ -/* #wmd-button-bar #wmd-code-button {left: 125px; background-position: -80px 0;} */ -/* #wmd-button-bar #wmd-image-button {left: 150px; background-position: -100px 0;} */ -/* #wmd-button-bar #wmd-attachment-button {left: 175px; background-position: -120px 0;} */ -/* #wmd-button-bar #wmd-spacer2 {left: 200px;} */ -/* #wmd-button-bar #wmd-olist-button {left: 225px; background-position: -140px 0;} */ -/* #wmd-button-bar #wmd-ulist-button {left: 250px; background-position: -160px 0;} */ -/* #wmd-button-bar #wmd-heading-button {left: 275px; background-position: -180px 0;} */ -/* #wmd-button-bar #wmd-hr-button {left: 300px; background-position: -200px 0;} */ -/* #wmd-button-bar #wmd-spacer3 {left: 325px;} */ -/* #wmd-button-bar #wmd-undo-button {left: 350px; background-position: -220px 0;} */ -/* #wmd-button-bar #wmd-redo-button {left: 375px; background-position: -240px 0;} */ -/* #wmd-button-bar #wmd-help-button {right: 0px; background-position: -260px 0;} */ - - -/* .wmd-prompt-background */ -/* { */ -/* background-color: Black; */ -/* } */ - -/* .wmd-prompt-dialog */ -/* { */ -/* border: 1px solid #999999; */ -/* background-color: #F5F5F5; */ -/* } */ - -/* .wmd-prompt-dialog > div { */ -/* font-size: 1em; */ -/* font-family: arial, helvetica, sans-serif; */ -/* } */ - - -/* .wmd-prompt-dialog > form > input[type="text"] { */ -/* border: 1px solid #999999; */ -/* color: black; */ -/* } */ - -/* .wmd-prompt-dialog > form > input[type="button"]{ */ -/* border: 1px solid #888888; */ -/* font-family: trebuchet MS, helvetica, sans-serif; */ -/* font-size: 1em; */ -/* font-weight: bold; */ -/* } */ diff --git a/lms/askbot/skins/common/media/js/wmd/wmd.js b/lms/askbot/skins/common/media/js/wmd/wmd.js deleted file mode 100644 index 1d524361fb..0000000000 --- a/lms/askbot/skins/common/media/js/wmd/wmd.js +++ /dev/null @@ -1,2438 +0,0 @@ -var Attacklab = Attacklab || {}; - -Attacklab.wmdBase = function(){ - - // A few handy aliases for readability. - var wmd = top.Attacklab; - var doc = top.document; - var re = top.RegExp; - var nav = top.navigator; - - // Some namespaces. - wmd.Util = {}; - wmd.Position = {}; - wmd.Command = {}; - wmd.Global = {}; - - var util = wmd.Util; - var position = wmd.Position; - var command = wmd.Command; - var global = wmd.Global; - - - // Used to work around some browser bugs where we can't use feature testing. - global.isIE = /msie/.test(nav.userAgent.toLowerCase()); - global.isIE_5or6 = /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase()); - global.isIE_7plus = global.isIE && !global.isIE_5or6; - global.isOpera = /opera/.test(nav.userAgent.toLowerCase()); - global.isKonqueror = /konqueror/.test(nav.userAgent.toLowerCase()); - - var toolbar_strong_label = gettext('bold') + " Ctrl-B"; - var toolbar_emphasis_label = gettext('italic') + " Ctrl-I"; - var toolbar_hyperlink_label = gettext('link') + " Ctrl-L"; - var toolbar_blockquote_label = gettext('quote') + "
      Ctrl-."; - var toolbar_code_label = gettext('preformatted text') + "
       Ctrl-K";
      -    var toolbar_image_label = gettext('image') + "  Ctrl-G";
      -    var toolbar_attachment_label = gettext('attachment') + " Ctrl-F";
      -    var toolbar_numbered_label = gettext('numbered list') + " 
        Ctrl-O"; - var toolbar_bulleted_label = gettext('bulleted list') + "
      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 U*|ijlW33qLU($Q2<8bqT0T*p%aR2}S diff --git a/lms/askbot/skins/mitx/media/images/mail-envelope-empty.png b/lms/askbot/skins/mitx/media/images/mail-envelope-empty.png deleted file mode 100644 index 0fde87dc9d6d8a3861fc1d0565cd778620356194..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 547 zcmV+;0^I$HP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2igV( z0XG0dpY@gi00E;(L_t(I%hi)TYZOrwhM$>9alx7F&XUDO2?RA0W(zkdA|lbhWTUNr zAWhgJtssVwGE0&rh5n8uT@qHrh*@-)xgY0R?C$6kHa6m^4iD$P_kFqN!hc@@!0T5p zKNLTT7f}=uMN33cNW0y_Ifru&YhB$tCpc$k+_^UZ4EA35%Jbq zZ>{xO>&Dz*Z`X$lE9av#!Z1Wcu+~zRB~?|SwZ<5OF$QZbB0?C3oR7``2my>S^f$K{ zot~hSA_xLR z^(9LF3T9?;8k(oV|a+!BB`rtvh7 z`+eTOd)r{nJDzMmZ7^-jOh80xSQG`*+bOM9O5W?${QLBVn=fBkZY|diL}cz@9^v}x znopAn_wU_hBj0G;mUdD;PcFH04~O30G{nU|KrxJu4SyQWn7HM_53`7 l|F*wBX#d3~e*E{J=_j4B6J&6NXg>e|002ovPDHLkV1lFs>mvXF diff --git a/lms/askbot/skins/mitx/media/images/mail-envelope-full.png b/lms/askbot/skins/mitx/media/images/mail-envelope-full.png deleted file mode 100644 index 2277e919779eeb21478ab71d5598346dfa701e54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 482 zcmV<80UiE{P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2igV( z0X8WgHe9;^00Cl2L_t(I%hi)TP6IIzg+Dvaf{V=p1rel=lr9AlEe(g@3N&1Snu~A+ zBnqfNxks`~gd!9QlC0Mr3hxpEqM<>IG_v*l{N~4F`1hs&E;^mNw-8R0QW(GhS*wK? zp#r=JpY3O;eV2zgUR?khR+kfZNH0(_Zge$8RSSj*^6|?$1gj z3iw)}v0;*MGaheX6>z+@wSoCYAbbKBBD{t`M{5SnO8|2bX5Q1$nykjc*Akw}lBxF` z8bg13dm~x~JUd5TRZx~3Si3F(pqS5N`g?mnPTjt>?AP3euK>=|^u>wn{9+RC|Nb+* Y1G^5%Mvgs9zyJUM07*qoM6N<$f>X1_mjD0& diff --git a/lms/askbot/skins/mitx/media/images/medala.gif b/lms/askbot/skins/mitx/media/images/medala.gif deleted file mode 100755 index 93dd1a3960a9668a92ed55a81402592f296c9333..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 801 zcmV++1K#{cNk%w1VJrb40K@oE*q;kn@I-k&}bV{vSuh^`1%k6r<;IMd1E+ZG{w0g~MyWjA*d`_?1 z@A$la&+q&HfPsR8goTEOh-(y!jE#0PICFAzz_X{% zpFo2O9ZIyQ(W6L{DqYI7sne%Wqe>k@wW`&tShH%~%C)Q4uV7~+8cVjU*|TU7DOk(4 zt=qS7(BTJr4xw7TUm@^~T z%(=7Y&!9t#9!C>oFt6t5zwd>cgV;`7JySDAyxO3~?&AYen-@tu`Sa-0t6$H)z5Dm@NG7S| zk}d||0+o_zM{=bwNED(Iku7HVjkApih7e}UBq diff --git a/lms/askbot/skins/mitx/media/images/medala_on.gif b/lms/askbot/skins/mitx/media/images/medala_on.gif deleted file mode 100755 index a18f9e8562941254941a446efad3e6edcb651d9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 957 zcmV;u148^qNk%w1VJrb40K@E)v5}!; z41e42_PDkdcy;l$Dm3n3(BTJqC>oFt6n{%wd>cgW6KueptkMXxO3~?&AYen-@t5f($n3;DZoGDB*+&RA}La7-p#9h8%Y2;fElG zDB_4DmT2OMD5j|5iU_pm;)^iGDC3MY)@b96IOeG1jy(400+o_zM{=bwNED(Iku7Ha6Bh$gD&q69SR=%bKED(R$@R%+>`m}aW!rkr-_ z>8GHED(a{Mlxpg!sHUpws;su^>Z`EED(kGY)@tjmxaO+st^xGw>#x8DE9|hu7HjOW z$R?}ovdlK??6c5DEA6xbRBP=zwb*8>?Y7)@>+QGThAZy4+ZYo f#w+i<^ww+dz4+#<@4o!@>+in+2Q2WwApih7u&Gpl diff --git a/lms/askbot/skins/mitx/media/images/medium-button.png b/lms/askbot/skins/mitx/media/images/medium-button.png deleted file mode 100644 index f384be914e238c32719ac9748b5fdf51b4d2f4ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 217 zcmeAS@N?(olHy`uVBq!ia0vp^+(7KY!3HG57oDFEq*#ibJVQ8upoSx*1IXtr@Q5r1 zs=p4xj7}P}D}aKMC9V-A!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2NC6dvdAc}; zcyuQ3`S#^#J)07v@J23o(S9vCrmo1k|397gJd)pEclns);x2d5%khS%x4*y7-e~Lw zRIzM-(zNyW>jl>TIK2G)mcnBz9t9o^2|S~utmw8SiQ(D@p}+61)B!DEVDNPHb6Mw< G&;$T4Z%eNL diff --git a/lms/askbot/skins/mitx/media/images/new.gif b/lms/askbot/skins/mitx/media/images/new.gif deleted file mode 100755 index 8a220b531225397b6a304918e4d96f6196ef40a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 635 zcmZ?wbhEHblwuHIc*el+@5|9!VKPrU6JO0wzv{&J_sfTWzkfci3%c&b^ZDfVC#}&> zyOaNZ`}}-T?z6u1zh6H-?MZpollt+%s=r@9J?->=+L`dQ-S^kCd)M4KzTZCopv3i7 zg#7c#dEf6|d^NB3$Neksx6gmOcH+A&vz~S(UH9g@mudETVb^R-1c%-`NuBSmGx$*^t}v|N3{XpZ=QLxvghB=@852m{z+->`t|DZla{DoFCJX;;C#~TaW_r#|NsA29T^4|DE?#tJ3t3Sg5rdM{YC?W zNN`JQTYE>a2m^CyU;l)Olls+yCQqLcG;PM53A6e*r27K<7?(+e+V$BkQ0bc=>K)=6 z>S?AMvXf0cM9U_`CsbZ({cMkrfKVO*0bx^n7M2ip!4S?%Tu1x7I766NnV3TOd0ax> zT^02BokLcHZqTpCs*6-yL$WVW?v z$*r&`a5&o0$QRlWP#C1jBFZZwJ5}J(3P%A63#oz=VNXx?YB3g7+`6(-c+I>yOH7zf SWil{ucTQ5<#GoL+U=08@?DshU diff --git a/lms/askbot/skins/mitx/media/images/nophoto.png b/lms/askbot/skins/mitx/media/images/nophoto.png deleted file mode 100755 index 2daf0ffd4333c90aafd71479510144bcdcb16c79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 696 zcmV;p0!RIcP)FMR=<>BGs+}zyR+1b_A)zQ(>&CSip$;tWo`Nqb^+obycyPRT{LUB1#`*2@<8tPF=iD3b$&izilao_AJ$Uu| z?T7EI;9G2Ni9S*S|C71nPeS~@YXtLGZ#Xme_@@>8G%0p2sX6212og-Y1w$?e z!WtwvbPJl0Aa)A^G6(_^hxshQF(4>p2|9paHcPMx2&P29rJ}2$!H> zp6f%wU$+bD9?CDf;J*mCI1D_PzD=;|;mna;B%E*;B%w%FK=_dsy!3SVseTRsf;O0PA5l@7S3xZ$It;!i#Ky zUwupv47dxD@P=FMLIa$I+=l%s0_x#ZiV;DS09Qn^j9pByOm?A=ICBWn)?Lr=Eg7SN z03L<9;aJ866<{Oli{Qt&ARUsBlRXUURcV2;m^$aahm^dfY4!Hqk3O{)q1D z1TF3(2qBkcAppN3{)S!D3NGw#J)8~}C44zdYXvndi+CRUT0sljYWS7$P%CI+|Ayli em8wo4&)6FRqy5_K=O4cS0000BCRYi5j5JF2~jeZ#+NQ|EQvzRK)r^|WW5FkgGHP+$Myhf{bS s7kC}|SZFb4QlgBXr-dDw#MT14UKh34((P5!KpPl5UHx3vIVCg!0E*s5BLDyZ diff --git a/lms/askbot/skins/mitx/media/images/openid.gif b/lms/askbot/skins/mitx/media/images/openid.gif deleted file mode 100755 index 8540e12bcd8303b44a1f1676b59e9ff61713a2da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 910 zcmZ?wbhEHb6krfwXlG#fm(2V(jpJV?&;M+JKNXr6_HFsUvFYNGJ;#CI^x0FVPMtk_ z_WY$Qch4QYcj?q6Ah>${>eZ`v@813YY~Q2faPbmo5s9gt%|d4ho>o57Mp#$&^RgUuZ5eg+dZJYZnp<>ZnO zn8@79#>ir^py2~!6AQbMoJWI#lOrpaM#=_*i%myaxx_eX7A$CT;APg}>iKX$rJbGI pjAPFPVa3)-5*81xR0c3Hu?omAG~5VaYGz`Q2)lD?YqkS}H2_zIYl#2= diff --git a/lms/askbot/skins/mitx/media/images/print.png b/lms/askbot/skins/mitx/media/images/print.png deleted file mode 100644 index 37bf88afb27a85a0201006be6515f307ffce4bd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1391 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE(}6TtKSPDj(q#+`%nLkS978;g&z*8QU&dA7SiPQ{aIOUr9b<4dOt34TkisQA&nbLY;9Ccc(3tE@kX znE6UyUl+T&^ThS)X`LSZvHSkK+^(b)C2gpd8>Yw9YOz%K{?MI3b8>FYng zg7JWavpq*sgNo;g6r<+f&-XN&Sv-%4pIH6yyXyw`2g(l?RLOZPm@;X(+iPCEdC$MT zd94@`6*bGYIIYa@^}jFd8?H?{n|Afkp+oJzpI3Fg`MvM??|IKF<-EQw52?i2W)YJWX>apJ^D?$6>Lmqjk`V}C23|M_GcQ|q-^FJHc#ZujfQtp_>8oo%osN*4%%ep{Ml2kwIxe$mgJ+jD9T! zJbP!(oLSND@-~N6QTWG^k0OZ@LT-ZRqF#Nq@?3oI*<#;5d3#5Rwv^3^$1=*cmEX7# zq3$WQ#LQK5#w;tlt?dh91G06mW=+jn+T>?)Okz!`MY^Gy>+6>NVgFs!IeS$UGfgg7 zEeyN;>`eFOIn~c&=al;N$j^Qu;N#;XVmy zzozET@ulpPJd+a?#`RV3j#D! zq7O@~VBF34pn+`yQ>Fk*@OdE_h3Tia?tWSI`t7`&>zW5XefqR`?b@}0RaMV|7n+%y zOFut9fBDps%atN?=d=E2KKDwZt`|B)KRxu_RSD1xPR$85mgV8XD>v zS%esxTNzqd8CmEWm|Gbb7*5=z1JeLfX$8>*)L>|!Yha{nWDsIxY-MC-Wn>J|@H!7D^o*=hRq6(LFI@k$Von#$*Fn8sSFt>$tTND5Q diff --git a/lms/askbot/skins/mitx/media/images/pw-login.gif b/lms/askbot/skins/mitx/media/images/pw-login.gif deleted file mode 100644 index f88b1bcf0bdf05b8dd91b0927333fed55b6ae6df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1818 zcmW+#eN>ZG7{6nHsJTrP6%if5F=J}s)iKnudGH`AFc3lrln?O=g2yHze&9g^mFy@B zwYZf9;qjx?R?{8I10k45z6dmOd^;OTsN^(00)pG~?*8|j=Xri#&-<=hAFK#SjztU6 zFhY!f9AlI*!Wd=@@fF80jxvrg4l@q%P0BG!86}KjMiD!#~P#gyS&BA)Xd0N;yh6iaCmS70@J@peH4G%=wVQ;fhph=N&)2t}A8um^d7OxT7vz(0rphoBn3Ks2a?QP2Uz zfDO=qh4|$7Hq`JiA&3tW`~fXg7!!;jSFj5zph*xW2y%p^AppI^VkOew`7_b2v@h9S^p$#=W1Q&xK!5`2z|+BIvso;brluynUa!;Xl9G}lA|ljkwMwOuN~IEs1U`MQ z5k~>8Xwv+^+bO{<>4N^ zcH7Oy#GiI|zZNvfMIFZ6-s+>Pb~vexyISvM-@52`!G9NWIB>t-v&OoOkU@`V;<|WO5O6hdrrp3 zM_B^15^wddy7w}(rB-w^-SB4P`EC1NI|%l-i@Qu2Yo1i@uRG9PSyLGF@%*q+>Bi5F zPwu`W7k#_-Q19LBti!R10hwJ7MxLAZbi^GRy-xp`B8pe!$>O)Bu54Wxt{ofNe&1MR zT_Fp)|JbAA=j@zzN56cwbjka#Hq;N=-i`?^n*Y?+Z_cRQEJoiMto6?~SG?0THjbPZ zS&le5Ts|9hMcdmFRkq!y7Xn|vRjCo*2$l>~AYnZlsdGD9n{Ows{ z*~9F&sCwA4yd%e@IVds6oGU!78ea5Xr+hde>a;?$zD=Wel;oTu*B|vhts1x*Eb<>s zEiVeV+9y=2{!D1OB=_@NSETIhxlrWR;kH(z63;4`6FnGusqf;XUC;W|BXR9I`AF)S zqLh5a#b+zqbQKomKOeA#8LQiE`5O7~N50L<3CC~ds79~Wd86^t1oZ9e~pFZiglv!LAH{BE(v)BCcmysWr%~D>?^fvus*>8H0-kRDuH|3~)lMfpB z%M>r)eU~;WqqYRO-k$XJjJ}8zVM&Sa&@ZYLlu&c)>k!hlBw)`kMFbLGXx zc1ur~v`cGhkiXTi_o1@4I!@HL?2i@hRox|4pEYH^(Hd#9=M`hhV4HQ{-|?Et`NdVT zi(jRPo*6V(ivKLn-Spz1`}x4zv!lmuKHZ+LP<7@sYJ#2mO+~|9FO?4mAAWwRL}|Wb zdNB4%@!!{KvwEI*^}lslVan37#A|NNeJ4`d%Y1Y6!T@u`2?w`A_q@#D7~5sH^+RQf zOt-Zj#ji?MKaG5IbozbOndu>c*Zstz*FrR5D2?u^t@&Z*Mrqx)GP&>Nn!*BZ!KZEO LF3fY4pjH0^IY)F; diff --git a/lms/askbot/skins/mitx/media/images/quest-bg.gif b/lms/askbot/skins/mitx/media/images/quest-bg.gif deleted file mode 100755 index b754023882679555b5d31862f777e5f8b1877bcf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmb79KQF^k6n#yEq+=^$FjT|RNGC(FR2r3bG9We##9%NQ3>uLbk@yBARv*JGzJSqW zdH4Uj&-?iX&Pnb)_vW6Q-0jh@w{~0$JK-|~h%skR?y5pa2$0O&vznEh|N+RO!h?sK@|HmRv z{h3y(TKyEJ2mNOMtn<{HAAUcKPwGpL&s#5zxz_#Zs$8wV{*2!`hmD)b^4H{i;qK%8 aqJF!3xj$$f?HydV+s)ZpZKm7Z3>&}3(53|d diff --git a/lms/askbot/skins/mitx/media/images/retag.png b/lms/askbot/skins/mitx/media/images/retag.png deleted file mode 100644 index 836c043c081c607699097107e618a3e0e239ad80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 474 zcmV<00VV#4P)5r00004b3#c}2nYxW zd?UaQpx69WL;M+p(R2JjxC)|$=cS*21LPHOPb8Axs#W9F1n z(*RnLy~QvLZ>+VGaSfsYcL0#f<(>e%_3ch6rEbgRa?gFSP$-NmrJmdMuSn(rECI+S z9J>V|t+hS^0E!5la}5Ba35FzWYpwIDJ=Fhb27s({uBrgQdv8T#_a7n46p`*!EEdxw zs{jUm;}((Co`7~pisT7^p&$HvTI&tf&l+RSz4x;K9=|bPdhfS@_AA7}T027Wl;nJ$ z)bQTV1VQiue14=@1VQjBA}Nx`5js>#t#r)aHi)8BDrH1uT|_d*n8T>{0g}FiA5Yau Q$p8QV07*qoM6N<$f+GgJTmS$7 diff --git a/lms/askbot/skins/mitx/media/images/scopearrow.png b/lms/askbot/skins/mitx/media/images/scopearrow.png deleted file mode 100644 index 73dc674452aa75bc0968f1e3118235401955aa92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 538 zcmV+#0_FXQP)fhZiG;freZ-_=)YZr zQUWocVB91WMFj)nswF>Myf<^ZczMYSn)YE249w=vH|N}AyuVdy$QV=JFa}fs6xCYe zxVn(ySd6g%s4A)vh_zt<1eE}51rfno0U*XmjA4FC*m|I?2XHu-k|Y~kT@C1Tz7b=@ zxdai1wRYip?R(Gds2Y`sLnJ{J&d)oHMz;WLZf(lLXK*fNBY#+6VTGh z67u*squV`UJpR2h>|8>-{h8g}<^mSu^RN}r(uE+BAd=wyJ!fY>$g*s0#I4px_V(Tp zLPnmKu+@^x%TPpRZMl;o`{A(9U~sWEcPQG|@1HW6{G!$R02o4m5TM#fSNw_v#JPkR zm2US0@9+N#sp|TA$Ye66-9APn0ajt=;o(jTHx3cUY&NCW`_6Pat_`WG(Rj__;TO{M z6(MBv>c2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4etLXe|BcZ8|9@l+!g&ojr{&+@pEzB@x6{RY qL(SiR%)5`AkKfOJCe+!7i9sM&XoFY6dNw0Q;hj?Mru|uJTpMcsf1F)&c7NS})#%Rxyc+_~ez|hy&|%NAhn69K zZB|vD$VmJ7;i2-+S^MjM`)zxsD3!2y*2|TW41HY!>&;CT2m#Gz@O1TaS?83{1OUqg BOS%96 diff --git a/lms/askbot/skins/mitx/media/images/social/email-sharing.png b/lms/askbot/skins/mitx/media/images/social/email-sharing.png deleted file mode 100644 index 57fcee00e9fe761674706aa60e8c699c9ca87fee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1095 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 buwVZI>rjb<6xVPw00000NkvXXu0mjfEyo+L diff --git a/lms/askbot/skins/mitx/media/images/social/twitter-sharing.png b/lms/askbot/skins/mitx/media/images/social/twitter-sharing.png deleted file mode 100644 index 7d171d0fefa3f5dfbff33538098deb100ac5fbaf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1065 zcmV+^1lIeBP)Jd{pvbu~9Q04K%o@#gf3#ShK`gd%r<((>U>f#M>Y#00 z8^v}0(f+wo3H+lqw7+apP^gfSIh0Y7BG->~F7wBIdj8QLZ$5l~-|dfRFL=QI*nPjx z{kZ#ncT-yH|CGhx#bCGK(a}-W(b3`8{n&^;yoZmF#wd274Ruh`$jC_H!EJ4A%Ht~W z3MTOzw2SLFfgPxY;#`FX^Od`WF2y#?KnHXcdpXzgg$`beX_xQfC7k)!4E}(&Qe9nr zzM-LE6O@(C&d!2@U&0);;WEbYJl0^DcuXl)VFUKzJhUN?gtAy95(ymKWAkGjz`gLg zW!Q=zpbbOHSbcqcpx_sw4WD5(g02clXv6M8gC9cda8_ z@h0=R;wN}YJn#ytVD3SzgVP?zcj8!o;vKXiiItF(&zXw^mwO3bXCao~A8 z0IO{x;vbO`>{=E4)g1reO~5~=TO$oYsphf-MWE}Rv?6JCNh8z*?uCHMri5ACqqYRrq^ zx4ZURevixGwmrD%5Su{=wli-D2(I)R zyaQU;0=ZJO;8h&JDTmN`yp0k#tr557D+$4#-wB0VLUn3W5VH8Vix0g6kc}B z_`|dz91gn&kByC~SS+TRnws`n+*Dhw5{-x=h8M6774W)Vc`iba(4Ynf2c3hZR4R3k z#obm9ioK_BL!2WQkH^El-waG9lZri!yz!%Y4aM3 z8(KIB6*yRI$mRy76@J%@Nod0ngbEFo+YN1D8oh|vTymG74qNd#^3aB~e2wnv>Ixie zE{dOQ!f)XMzQkc1!W7QpI<%>*tZc5PrX~(04Gatv94uFh_eDeyd3@5|-X6(jv&xjq z<@^T^4-YGsJ%WAs(MRad_z+D{(P*@I|KrGHGKx#17v*S1A5xgaSD3~ze2h2If-3pg jL?W^HU!=w0|6TAuu>;j?=9u~f00000NkvXXu0mjfR7eE# diff --git a/lms/askbot/skins/mitx/media/images/social/youtube-sharing.png b/lms/askbot/skins/mitx/media/images/social/youtube-sharing.png deleted file mode 100644 index a26b18121b8ac99b19bec12d907a0840bd18b208..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1396 zcmV-)1&jKLP)wb%bGfoP)4K!F|Djf@^AOL{U*uutp#k<@f-*(TEzng*=RbQ0>^UBmCgJygXrZ zqObtR@H>nEy3vf~7!JX{!Vl&*4;1q;I+q8Yv2OB&Xw_r$j zF~Z?+h=ha$k(ih$A|oRO^9(NGM-<`^uEC9^ScO^?=!Nm|@xFB;A|lLplS?q9Kamtn zup5SA1$;Z`?(P;nJv|~TD@({ZgI|y-cUp?Cu>qTL2n+P!=H_O>I==T?IkU8VZV4v% zgmz3tLnI|739r}d+ivRAsY1>M97h7$;Fgi=@h(0`u^xQk!Uf+tlP6DB?{0-5-5@y9 zPVg}81hEUk41W0Vp>MmKoE#x%4UQlYKcEO-p-O7<7u?#~>RTs0Jzc$f2@Dg#nRbHp zm&O$!cpXk58dq=;+p!Eca0f*;gH!N839hgcT%p9?$H)M|@t7cQk4G{3VK8zq5z#h- z9k`|hH`)oVR)TLLB0%skjFi!1k&HA@tVQqvCHTCZ;5udD z>u^{EpO$C235ddbTGSFOhv5&w<1B)YE5WDi1bdW)J2BiM*gV#1k%znT_DSr=CQLyo z?%^R?mzChp>;&g)&&Ep@!OhCmTRzUqZMUFA{(Kcqd?Mp3F+m1r`3W9{ z`%3T{JHeynx!jQ6w+KF`1m~d*E>xoe8?gb!sKt2X;zb#p?I(D?cH)?B_tbn|JB}m# z1b?Rmw;~@Oq8tQ+VAQ~gZ?O+vf~Tm#`tgf91V`Bo*8f=Mo58)ky=L%H`PfFxz(pj= z+sjY|7oJxRiB*^cZ(Cd2L@n5fUKmmX!M4AbcnTfbvphICIr*RMrcIl+S3Y(IALAY> zun9+T7OPQ?PJDrU`Iyn(-X707A|oTi3_huSFm&>%_#w2xh7JIH8AW5S5jc*1@`lhK48Q#fzaRfjUWe zUAb6)8|$!MT8Bz_P>#iz33X-qUhSoN6OU_h{e01~bkU+k zBBWqB9vGUfh(tJ3rarUM9*B^G^$&@fi*O;>$;2@jn!|8}6|Bu#7=E;%6sZ^hefnbz z+&Bs^3{9hY&9ZRe!tjIT6yj%#@CUex(>R2KIF8HchN0sVbLWsDL&_jjD_5=zD_Ec5 z*l9+D$cz021qG=c9UVf~)zuYzaCLRH2ry%@8CQb{y^W8M0Z~|3`1t4Z})EE4}3;r9G(Uv=hjtIX10000Ci diff --git a/lms/askbot/skins/mitx/media/images/socialsprite.png b/lms/askbot/skins/mitx/media/images/socialsprite.png deleted file mode 100644 index 8d01ed773758bab8d625ac89bfed4ed63917cd61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3030 zcmV;{3n}!8P)?&p3`fPVAY1D&tHXz_HsX zArG~QRboS%6omV-E2BOrQcC3lEp3U?N>oV`Qc6Wc3j|07q$Gy4!HJzhupzW4k^u)B z;#ZspW4nnn$;^1}>+HQ(|G4*_d(VB?V`i3gq_g)~XRW>WTI;vg-sh<8kI&}wk-a7G z7}rV#ECM-^xN10Bta!T~xTP=$aP50{WL4~M3VeTD;f0H%$Hlfaa=k{)ZTV(j6Wsq*gA4}NG4VDH|&{@B>q7T_Zy@|MNWqm=rQi2UQ| z=;*`#K+bL{gnCElt8uOHm0Fl^5paU37&#Jnwg5Z;5ZLT(nOyF#{2&}x$_rTpMb24c zGw~5GoK5sBSytHMXQMyYfj_R57qUP#2sp7-7n{RNJb$2}XLNM*_dL(Le_&uBm(6BT z3!IOLP%4$yO;1mM?u8d#5Wg>@cPuUFD{_IxQ?M8lv4UTW-Gn~Nj>bfyZ2r&k{TE#s zMmep{@k!&WPao* zeyFte5D{b%6>06^1!1B`dx3}2I43OXgp{tyZXuDh{8O*}gl~Lz2eByu5cm;+pQBiw z!q_U$9{&c%Cin4MH~uqA`^UPfX~^^iuq^Hz5+!Z-P8_)Td$H#!)~y+4cv(N0&<7wk zmPfyR2&I}Z)vCvd;G9dz5rp-Z4Dq&0hWPR16#FNqexay!(tDAn=T7pUPk)5ilvq-@ zlW z$7IP~Fx1%=x#Aq++`REp#BtS{mArf76|7j+ml&p4s`AJa`|*P)B|Fh+9LCu6IN~bz zzH=S9(5F}(;q9N?g%@0S!DdaH!CU6{%ul~Wr8>)!zE%A0%@5#vQSv+=UBtxbCWe=; zx#1(G2 z{xU``8BC1Q7dH6Rx0-dAZ2{om^Z(V;0RVg>U|eU!lwx5xTZ4<0IZY{+3yv7mBGxt*aGpZq zxXdq&EKAxCJ-U}-sft5)7^5{}oV8e+jub!`L_`lWFdEAADpVl^Fa?M3fsy873Vug0^YT zb|M_TYkSgObLW3>=x^T*!2XwBcz%d+j;}v?uqLZTc5+{bozyrg)l|DuEGGBsS1)7z>Sd|t?_K|9KE3x4|5r4r zerLPqn8Ctol+u)|Gn6XR=S=D^zw$!>l+rloI*gx0jB#MPKxenUR#Md9u41I;Vz1206yW>2{@@YF~H97g*7skegF2MqR&Fn#vtsifad({m@;_qls0e*YCz<2E8Ps!6s%d-nPb|a ztu2Y4c)iG}*>qF|O0dSHP7m7Uv{JGB8cX_Ca%z8?B9Qx-P=FR+M~A-hWX}@j6mEC%IYO^_UHj=x~%t=rK#pNnp~oSWYFu?}9}BB=#TQ5{VV%o^+)G13y*T7e%Zf}nFV+8|4N z#LO4UKv1mT_yJ5@#t$Mg`GF3MPh`8aSBvh#r#w(zb3}?}+ogG+9w@f0DCiL}7eb>~ zUeEh?exEPCe-o#79$?cy+?$erlvnOfr0?>ZCZc)@6&rx@*E|faNbW(?$`58uO zk095F@A4?EQQGT(w+5x4OkD3WaiP?rM=4ct&SgCB{Kqiu4W7CE={E}xuhz9h{?7qUVE_9D*81w;%eJTHWnk@bQSaW2^%IxAI_Ta=$L zI><_*M?}6-DwVe8^Z6bb_T1!o88&_J6CF`kE|+8A3%(T&YsbW*eT8jM?b!ctqiLPp zJh}5d?#?KgW9G`X0 zoz+_3=R4=_GfwYNnsHM8xgfIQIP9FeuhF4Q`F5*=P2ca?X@hzZ!Aiw?~RO%>;~Nb Y1IWFupjs*@u>b%707*qoM6N<$f)g?pQ~&?~ diff --git a/lms/askbot/skins/mitx/media/images/sprite.png b/lms/askbot/skins/mitx/media/images/sprite.png deleted file mode 100644 index 1a0fbc78dfff11e390d63d765d07d219e0842487..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5325 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 diff --git a/lms/askbot/skins/mitx/media/images/sprites.png b/lms/askbot/skins/mitx/media/images/sprites.png deleted file mode 100644 index 8c513508dbd403aaf7fef76222ffd3327d8c0461..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12705 zcmeIZWmH>Hw=TS~;?@=^E^TqQ;1nsvOK^u$oFK(1TC`B0lpw_^1b25W(Bf_>RtWBH zft$YPoFCse?%(_G?2+swJ6U_Ixz?0tK6B>tTXjW3JSsc@00@=d$b$g@T_5#+6z37@ z%$8a~gnGerlU35jK^=ZL?;}y4ab4aRxS@VO`|pV+%bMnmy7kd~=j7mKY2jkU?fk(e?LdMG02lxz`B&QB>HCXb-cP6RA01t$3kbB>eSIs7t%-?A zM$JG`f%PSdRhZ;$rE%4#NUq4p$a`)1DiN6q6E@FMiY`2Ka!gWOoSXt#s=U2}p=)H7 z!CdI#*Ur$lOQUX^^HyFL>*BxLM%ypNZANg)giymHe@RxC<1;WZF^Ns_nvguH^0ktA3!N%&bVOL_F8f{PEW>FV)z@3Ihvsh?D*g^Q{u(|w8WaO3% zgS`vc>Kbq$lW4+2C^LjMMl7>U*VjVO84Y|Zp&Z4I#06fW;p`Q4mX;bogwn+U8Z-tv zbX?$XPCUSXCS&Y_q#EnMS^54_?NM7xfva8emoM874unh16~>lXsPC5AF~Z(*_oO-7p8}6}`oAJD&m= z_Pk43vZ;E2zUg$p_j!T#px5;MWE5HN^)Q7&7fAg>DO;49@5z;)-b$284Be9fX7M zFW$A5O+JdP*EyT1YkVE3?#S6qUSsaa}dRbDOxgV8<`Z(FBwO07I z;X0i=gyt?+_yg6Ijyu|Uf%iUPw0a#6Qun?{4@y(#uvyC|adGfW@rRI;zR)dzUI)k&6sKRk^9-}le-UoW$>Ne^1z2| zR07FZn;S5&+I3fRqjm76_k{Br)o$mSonCcZN^DguBJJUSzWoL zB%+f7p!&ma%lT3IKr#?ncGQ!Q5P!6uFn4^lhQn!s2e97ma6nZvqI{j(apx;no!0E& zBC8EW-CwjTncY)K(EDj7#U|h?XvpSShkeiUmB5EvAt&l<^m=EWGU6N?8|#rq@d+S8 z0Bzi!G(bZdnWJL} zp5Yd7d!2RE_RlE{4y6ZJ>&}#}U1;>m%WG?CVkFHpN3VejTZN>yp9_j2o^oah6DkEdSg3j-QZ8#A56Av3Q{^h7&G*WIcxV>4LdS+C&hDaCB3f|e7fi-@Ru%t za_8q0=GaZ8t*=|pr*~Co?@mZ9NI zVk`wKZh@T-TDmh|;5#QNk&&#`g=mBI`}y(cv1=fZ?-;|CigM*uPQ=*O)!O*o*(CG9 zCWweYxjTi+JYx*@Wp4XB@U2Fy_+` zglH|R^8{94$xhDmEne$2+}qpV8&3ulk5505jgiQ;@+}>@xfO^kx0vlSvb4EvGJk!` zQ$LVlSZf zI2{ppvz(l@*1lR^Fa0h}Ihd5svcCRwXlUg31D^oLVU_a+r&4p}GLlck_`y9ryj_IH zSr^$X_L7MyigQ6W*R;%IO5sh+oc>QChj0joZuaVNzomvAlcB4k=agZ;!eUN zD$&){I}MZh+-bEkBmROujhPptTXjQL_Sdq2m335n}BGC+o*OsAxeOpMF4!sf2h zVsrSx`rvT)^%w8^Zyvk+C3mp7`Vf}PlrY!JHlmK@Wt#45DdvwGU)!#+=-KO?5;gG5 z-INEEyf2zo-#C5KUqvQyQLn<1oqN~{YOyhOul>55nD&3vNgk$H8rrl=AnLz4Jh)|6 zoZL$Kx_845|3v@RSPGpQ58;I@(0ZTzPLnM#_OhQUY!`qm?7w*M-yeBwi+psbEF-z)F}b_ix;5PgBHe^#Wd6QzcKpP z!@t95NADr=WN>!a$*sDtnCIC6;b@^|oSzNb{%MqAIP#ucPrL-yG9&{}#^MtMN{aSD zlYsFGh-!D%#^QWOEiyXhm6Fmk!#bxgLJs_2)6*k~_KhC(l*!Q1_$yB4hnQCE-Vi@; zYH}8-fw(n>)4A@~Y5aU>dl!Np#2n`s#ir#PLAn|!XJ<#vdzZ9nk<4Sme{j!e<$vq> zF#qUfgDwA24e}!<%B9h2Zkj9AEd3bBU=}BlS=g;$F1Jz;TS$U%SvPdiRVV%WsT!!r9;vl38V7~o=> zRbGDg>HbcDR*xWnG7wouH`XXF(9)x?_ekoEBm6>L67ITz*0>VQL(YuY8*jY&aol)Y zZg;hwfCUmqJm*AOa`pW=WiP)SeV^{RuuthWTJq~R_nS{755p*@tY$*r6P%}+%lJ$! zm7rBauHEIfaM07U>Ji}q8J{tlOaxKcc-u#p&-LmV@VU*bWk`hQo&#lhWF(RCI7X6k zQkVt>-nd_R;X}mxZ(kTLD*M==u{N)KasqAV zd6wUOtFFQS>U*h;i7pJdR&j*TJ--XEd>mw%8F;VtrD;=VedECJP2foSa;5#Wb?eQJ z9+}i>FGVz;_gK%b5pEKazw>Ho)(Yuz?jV6Y?}ICDMX`tbOII`iv{P7I0kd6}@-8U0 zm_0wnXVVjO@a)Z;9b#bmA@)Sbytl9Kz6vrtm{vQ31K}DG#a?`=_9+n>dNjLR=MbTl z<%<{7>{)hnGjmonSLG2*URluB*Jtf$bb1o8dPvQz$YhOB&bAuF%StH7j7ss?Ww^bJ z6EqDDlH@U5jG;Q`cs$0IZ)c^N;fc+?OMk5TE(Wq}ipTZ>@cTOugdK(p#z-D{*yd49 zySa8c6pQUUS{-E)aDzJ?BG#Xp4@p)4GN0_kD5e9-uqoiLBdWpDx@8N`(ad*eiVHNY zY_Og8uNJ)qpJt1@;gT}_z%WW|7+^EF9pl3?lA6|01I3BCwz<5ho_r~5d;DP*4W67B zCz5AASOC9pZCy3NcC7f2H{Yox9 z^*;r{L`+82aocFw$1GW#kBml#FPTMSufXHXvzM9t>`jxF-BHm} z6~8Yw8AbNdo>~Q<-wEjM^=TOU|TJ)U9~5=5PJoehZgkQ^EPW zeEfGwB)9GH+{SRzNqs!eyYrpl>3he^1KQ)iD$FWS!H31t`Jf*Ak*J5BN#cpyRqAFC1E9kw$KI zl}NUn6GpcDOjnjg-&K+MZ8KK;kB+yxXA}cbNnN;(UJO4~79TzRQuwabAbKhK!D5n! zqTRgLqtTIg4Ivu|Zzg2*_Hrh!wmZpY;s3B?wDdzEciXvLsQG@b>Y=x_NoTp15-_Z@ z|HATe8JK+gU^lUTgr|qV1aKJ9_DnjfhgJf6LyvmVL8TbF0qx(jJ-!=uNU2bTPx$j} z(M86=>rCN?D!RX$+>#5M+&Da|Z_c*KQIVvbIWGu`KA2Rl)qzaPPkQ*yjpqL|K#Bl` z!^7>dIDsuG@7oUm7saq@V_Tm2HjJb>WXv{l#jfu$Kpi%tqWT$Io#t(0HPxCG+E$0H zb+^F_ER~P1wi3enegjm%6@I6)?C!GaX9E@@)}i^99yrWkt;-BD&tLMtlU2_rS{{Tf^QE#625prR-mKAcC__# z*JV9^SlVIXJ;J|EbFiy3n1s8RBPF({WC?JbH+C_JCO_X0RxPi{M(kOuVXp(7NnDdg zb@tP?eVxHJnI{+Up+j~Ul@lye?iC!yfps$ebM#rJR8g6VzCGRzpRFz-!r3)%5}H* zUpD?wKh&8R0WaQ9Y)q^$hff^N=wsI;XtZ|`ufz;gh&W5xT3TxL=bTmw22^150Pt2 zoukqfKeM}RR+=p~5p0aNv&Iy6Y~!#k4m$xe3WrVq;rf+svdN|8v-2kvfp% z@<`Z8;wY=RykZ2GF(=q-sm&%U0uNIVV}|a%Rif}z<(I(wc_uV-6W{v153()(Mx8N{ zkvsBrNp_$q#76rfr#bZlcFYBM2Rk7Fd$edbl?e0ucY#p~S^C2f+xE>=6erM=QAK2e z;zFe68W}fR69b*!+GNhZ=wFNXM-AaA*=>%DEVQPqj3i>%ooPEb_q7biWqeHA6VPGM z)Ag~HDH(+sB>tOQDml>={YNrsyZ!eC%T5#%#q{AB204m}iXSA}^{6Czy1lKEhITaN zL3`WT)kQngPz!;F?Yg7Bcc82!B8Wq!zU9$#*lB3Bg}Y1_U4g)z!K?xa>3GhX`GFV- z>OQnzQL{lE#(i3$5!q!%r$cIK!XKleqjg+eKeKalYl87;pFDBs@9hO0A0G$nmKj24 zN|McIJ<8%~_RgP>lJc3;GU4GzPb~))^g1spxa~mMKzd_klH|hrK$|{5F)mDQ~W=fUf+-L|HF*ye(fR$6M zpRu&P#f^>n^Yt;Pvp6H;$gyu*>oP-Rw_Dlr+GL|+1Os>L0O5ZNo3{!05Uki6BGQ`m&+yZ zCMWKXJ@>rbl^h*Qmzq7&`dw+pD=k8HJU>MNQ{s`k56{_(>^wboS(um>o;)hWkWoXQ zLCOAC1Tqkr2+nD2{CTw+p%Bvm;W!G#PLK>sI0|~~YpPvq!evm+dGU)G4i&ba8slbX z=a;05%lJw{U-;T!nn)kp7Wq$Q*V*-EyOh;vH`LT!)u=UQgt%ag7V0>jqIM* z4&Q%-mjIfSR8*w3?D#FSeTknyuArbogO!t0&_MbhA=oI4%^~EWZwX|fgUGsew|Tw` zZYLg^JZnwL%R6(#TM+Db+#_t50_|7T09kQFZRdlAx2Nh$SiuJ$n>k;VM$%BV7#}o+E4FzDwWK3H@_e34U%`?Ro^)Ak>lIZA06dT1G8|#z>C8mn)X#&ZiZD+solqy=E zT?AkQW&nuak)A$i_obRI0+MWB77vAHCZL@WezdzoP17c{Es!&46s?vBUhuSY+HGSf zt0}OUpjf@ru*K8F`1NbuQWEs6zBavAinr{BJPbJ;Cx_+c}eso9TOo1bfO z(y^mbx19DMTuIbo{}KuC+aZTNM}dSYNz{z($PoJ|J6t&zK+)XsCB-eOast3=0a7A| zrKqT91UQN(VJd1>Il>BBLfp1`j7o~oh`Ror!Zs;xcxjmZZdi9qNm+TcCXD1zg>46) zkr7i0DN0xo0ym&p{I|#*oUuu5g3sfOyW8olOxViN#^!OX{A>zJ>L9k zoTzBiOq?`#r1b5cK=g+XA1tYa9rg2}e@Z+zyG;M-gWCQu4X1R_GMf3>)^5Zbwub_! zGFaITHZ}M8Nx_AVaz+_+L3b%5#pSR~q)p0tD8@sSq7Qq87yw=V34G`mjN#!LEOjf{=V`yXmmTCEJ@qd28{`kO`u^nhd{^@P6aI)8!wfFGo@=xOI+bs?<=l~x^;Ar9 z14L9i)3{sS@--zTGCiF};;0iNMbPeX``w93sbL*nGzCu|wR3B*MNhZ|3Szm?U?XoY z6)Q~JNS0nj)%qXziYV1XFi3t>R*o%L=0w;cQNch+XEBJZnNW%ClUqqZ%Vs9^fu3?3 z3|b8ZOTnhbBIpCQ<8Ke?U59#zJI)fo#VD5uxlUf3RxYaOk8KmJN{VIvHs-&h3>ZHT z)epG8wvli&*eJ=jqe`+$G)(fjX36psxIP)Yr8_O~8sx8N;M_fp z=20)YIKilc723AgrG}6E`c*Fs88`|ZmEKwIkQVbgAm~_*dz+ zfg2pgdlA%jRB*svLIbZ69Iz4ptsIIapOxTv5ms1|T8azYLAl5sHdO>3P(YY@+bFS7 z{P9}mf;b#|cVEj1sjzhjshyfk3LXlnJxQ{HpoG~&)f;w@92Kowx`X`5#I(M@wFj#w zI0q%BtA~~c^4B1frO26xYxq;Id?&d8fpwpqEE&S}v z{R$ayg}l#T#F7bv5ifbK5uzdyIU$vx%;8cSBTU}*hfiMr3zhpXTFUlWqc*l>CFyJd ztTQO_YDJ2vD`(I_YB*K$%GZg|lbsWnI#af3E#+%u2ytv`AvE;|lphM7WreltDq{_V zO!uRN>wm*W5lb!7>HwkBZVCj_^PxABg2T?Llax;h^fssl`9G%QU!Mxi8ec%I3`Px; zUWCwcoT>~~M{x~`^#~603?$=#Mg)h$Pzok$AwAWa_U+r_9$o?nr81A%y_cgm=yPu*xtFr9Rp#5PBA4n|u>j)2qf|C;yi+aie zn3#UFX_f^E*-dc7;V?$!K~G`OwtHuiYjE188`&M}6uH+@8m-z3F*t$OaSOuP!}08qbk) zAqsr``Vq=0klgsAy_YHA}n%b6+scAcy-PgzY`sF%BCI#NB-_1XdpQ->VwO=#R~~gF-(9L96AJi}Ij6D0}6K zTl#pYRvvJSir9NLlsn=*Z+~~`v`-6!Tbzjy|6q zDr+!xyRNmHWJ%3;4YQ!M3b&vP&&fCOocAGaGH}+ZosngOot~WNASP{4-W&_4bzY`3 z+-2`k@@6SS1zRBK?CdNaMIxczuIOlL*8V-{|~$xDz6*}C9BxK(${CSgt14w#9w z=G&>#i=Hfq7pVI?H(L}!O;j{~-Eudrr#IYONS4g-AVmletgu=u19flE@hwunBS5kq z0)G_O9}-I|{+fU#{?mYKKP@((-{yPn^@FqAC9Yp2Q|y|TG~Fptl!w-C6ME=%C?() z89YGCfO7K{?Kdbg%Jlo>)Ifn4L%ni44HWqFMH>QK^n4_nl#5Jjto@Hy$8wVYz@X{n z&=bmVSzG+D(l@V4PKLu0!OV%^8t6CbJZM4EsA358M>6zX07_@5W{pj{xYMW3cY5LB zIq3t8h@1#IS(K+~cHjO)%KW;se!q}5J1_6~7kX(rWwzGagO*? znWxc#27pkP|FQwnqL67489Wr9bB`OAeZ1!EFNDRVtm$V05HJxxnL4HI91+9D*>ZQZ1l-sJlOL*f@v!#Hdk&pTX*G% z4y*nvw$Cdz2pD*v36XI2QHf28Q#wfT=Cs)GDwi7!w;&&tI*VKS9N%8fh#%6gu zSoItD%U5uy$Bd^WJIph@^oU!x@FQ6OjcjPF!kvop@wt(+zcy1SB}1q;7{_%D1=ppQ zBRXCs63ERfKZ^w`U+&GfvHCp=858z8Fg`n6rl%Hml({@uJU*W_v+VkWl_KWBZatFz zxi^|(Z8d`aHOe`BO-sWEwwBt&O#`m2e)jh(66zZ|r2BnH{;?va8{U@eR>?H$3_s#{ z5@5A@+uQMOi?G2QaWiZS-v&ttA#_^r`otR@{$PIjc!n$Su#?vK_a<#spjAIPF#-Y; zHmp1RBHphSBvQnOu1kP!&HG+-2?Sa?$;R4q`_Br>$&10j^T^9MZxdx7kGum^2SCIp z?LzU#0a*Glj&8iZ*bHNihS8?GlhXpDBq82bakBk!K{ZUCHLsOR?3L5&)zfQK=H4S? z@SW$bu2j$T`_j~~;Up?!ezY=YdzhS*WDqPP1NqZ9zc=gba%3hCoake!M+AXaa zD>o5;^K59e9%A|X+Y9gGUWzK)aVFJt5GjSJH)GI>{4okn6V1=hlYP=*b%~+&+H9ti zYX}jSf{*nS>b>@VMkas`9u<{{Eo&!Q62zskb)a5@+Zd9Vp zQQ^!H&6KP4&zc3sOh1(Njd4`SUpf_ZlAzCjH#(YnlJK=dhyv#d=pa5y?mA9H7sul} z$RwREdXR>|J+Eu10@+c>$G+%d_={XlQSofIxS`=L-l5`+-~MAw`Y@d7s1si-)kzr! zlENe3!u6GoU}OLeLUj@c6w3^+wL^&SmIiK)rd`Ut^TuH}Tf@D+zHU5hzh!{Q`}s+r zaP*gbc!MSK=A7(c-)l3&i;0JaSkJ_RELQ5qR^t9@jgtR84xo*r4_T;X-yY9(h$lf0 z&)JiN8tmV6nAAQW*L_*bpF@a0aU_#w`t+Kn_2j}ma+|)3nmTTy%rJLqvu6h4Ge#9e zLNXCDyY!L1N#O-(ZCR2OUda|t(DF<}BLH2NGKd#o%wZhM*ZjfpkLH=yuEWrMp4eiR z!?Q7VI-uvRu+VX_RTyWeNAS3QzjO06B7=Q0Q9aRObFJLRol@(S$UrFWmlYU)0b(dTm^ zAa=Ct)}@;p_+;rFG8#h(dd=x5K#nFrgvLx>Pl^qXl#{_S%My8@Pz|u4;y-EV5q>uy z!b2nQsNVxryk%C9x%VGGC#0E6q+6T)R1Envprdh2YBiyt2SaZQxlmowZVtk8>Us;) z=G6~VHwxdGr4pBW&0wC4T^Yq#a>94OBl|{?&B|f_*VI(}chsEEhq5{<1Ge-rf`L4! zxVV1K<~X-74g_Um7Sha(LtUhbwcJe)_btLnhn5((OGw;`z@ZEgo6?1p{cDJpsicK3 zFo~~d5VRVmOZSEh!@@f|5t~;P#|{9$l3}=_C(OYhi4ED8k~aS-BlAiIoyyjbXXf%( zA>;01DT=2nd}%+`g-5KNh@q9Iv=^TQ6yE6@u}AzkT0jq8*|@|WXweIRChqUNyGFJo zkBgo4_oRGEF({eedD zu(<4zx;|Q$cDmTk4l?~Olct>(Qz)#f=g1-= z$Qt7g^(N_DUfnfLv|-V5K>k<*9mlIvh{b9-m<7eI&DHPAC}Rf)5i-i|x_lmMEyaz^ z)mx|!g?7!N5%!NKt$}#v>S^im=vu@K(B^XHS3I733*{2nmL;OP_`UZ8LSpVLmz+)qfCNiAe8*|QAHj^=9bZs9upI4xdzSG?XrB>ZsX?b zt8}RO&sG#Vb)H0o72y^9uATV`Zg;s{jI&m}*~?S9{8L^hRRylZle}zkC5cB#xGeO* z+W54m+=^VV*3&dy!pHDus$xvD6Lf8ksfe2XoJ`r0HcSNj=F|Lky86Z^#HD z*{phhyTHu;W;LRQ8Wq2Ewp7FKO;=Tfa2U-~^ADzX&mg(47t_a^JqD{+8i9)**tga5 zWw(8WXClVwS;$U~8YBM>80h!Ka~zx}Z*_Sc6C*g^JfT7#L&Ir#>Oxa1Qom0ry%kE> zPd8uc9r%wV&`w0v)B0V}6I*mUM_jN_-9S#ks2nwKG72%8%XQZ{>IlbuV|TbWfw@cQ z6q}o)efC>Bpkox*#DbEpdY}O%!A7W7`v)`>4Tw%G<$ajXXL&Hk@4R%0yX<%B{}+|S zziV(>wBA6Fr2t=1w5{!2W$;5`ZY=9IN|-yhk!_un6n*Jd=7y}0-{m*d&tG6=sZH25Ed8tX~+mR37kkx7Xz@ygW97Kli$r zHu(oC;-?!!$hh~Q=2Mf_C|!$x*^nGjYPfG6X2wNvbFg?`lWW#|czV6RG9A+8qt~)w zfrU!hi6V(~DG4#fMGP_c_)J@zm)%ZpFC;~zYeOLL?s|xRk_W@1uXyMEy}tVn+#s&a zRD5-Ov#7r^7}8&q!j28(v8U&kZHg96`;gy$SSm|H zJeK>=TvVJT7<)Ed?5mhQnSzm>DH|*i9KS3P^k0mBn6|h%TFb_XHlCi&(xd+R{fsB; z91-aY5(<>XqLkI__j@brms=EX!z*^$In4K4@s5KC|1}4@o1-+79bXc+BZ~JVsb|VL_~&{E zG=L}6-jk*u<&;3Np%D02@awYq%~N6z8d|-j-ig??r|7>j1Z)DPM)#uYAuWH5sMj8& zn9@ZO4-aQaRE6n>oKBtY2UJY}<>>x@HK+fn6$PO>MdjD|A94PxNG0?AUwfnfcWB@L oF4O&XjDS|odQr&P?s;4HUIQ~0E5It9{>OV diff --git a/lms/askbot/skins/mitx/media/images/sprites_source/graphics.svg b/lms/askbot/skins/mitx/media/images/sprites_source/graphics.svg deleted file mode 100644 index 72e1d2aa20..0000000000 --- a/lms/askbot/skins/mitx/media/images/sprites_source/graphics.svg +++ /dev/null @@ -1,1291 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - ASK A QUESTION - ASK A QUESTION - - - - - - - - - - - - - - - cancel - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - x - - example - - - - x - - x - - - - - - - - - - - - - - - - - - - - diff --git a/lms/askbot/skins/mitx/media/images/sprites_source/sprites.svg b/lms/askbot/skins/mitx/media/images/sprites_source/sprites.svg deleted file mode 100644 index 585e578f4d..0000000000 --- a/lms/askbot/skins/mitx/media/images/sprites_source/sprites.svg +++ /dev/null @@ -1,507 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - X - - - X - - - X - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lms/askbot/skins/mitx/media/images/summary-background.png b/lms/askbot/skins/mitx/media/images/summary-background.png deleted file mode 100644 index 28a6a398d4371307d8a633be6046ce63f35e692f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 233 zcmeAS@N?(olHy`uVBq!ia0vp^vOpZg!3HE<&c)saQY^(zo*^7SP{WbZ0pu_6ba4#H zXncFso3BAZfaT(`g8#oa9h$25)O^B>b(RhJCys<@Ee(;K_u7Pm>sP+X@+pVxqh5bs zKmV|+=i2rkcSL~TiVUxRw08ETwk=<64n1GC00^FCMBJ*H^lg#iqFP)5O z)Q2{VJ2s*BS!ZT#8NU(QGu@eB=T6X(AH$}m- zQViLQ1S@v$?^MR{_4=A8rSQ{#jrgfGw`GZGTigZH7omsEhBsrl$}(1Y{(n#`CExGw zd21T7Bw@W=PD22(lzh9pLtBdwg4?1PjeCGTz=zES+jShh=gVp}3Y~S&dCy~2ktPXw zmQh0(9P=BhMb~9UaIs9gP2NC6eS@^ou@V0Jm z-IMEe?^onrJa+iu|GQDu*F@6|cUv$;yXP+b<8sF;OIy`=_Tp_tEoZXNCzJ)=yAsw1 z{2s9hP@N$YX(nOKbLh*2~7af CaE`P9 diff --git a/lms/askbot/skins/mitx/media/images/tips.png b/lms/askbot/skins/mitx/media/images/tips.png deleted file mode 100644 index 7e8aafdf9654fcd9867918d06779c1df4f008799..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 716 zcmV;-0yF)IP)HoVv^USjYY&aEvvdg*88S2FT8vYktNAiqpG4FEA5RYOSAQP4JW z=KPh}XLlXP1LFv*VqMPhs*v?xxcT7rp!@Vi=whm=9GtjMpaB82H#K?f-c?nr5lw3} zX@d+SAVn!FrJWT#J##Ar1m_g(fM90sFbpUFz{0GRJ7zkB|cWktw zpkZAccQ-hWK z(e(Fk@pYbeaV%?Lwqjq_Gl^9STsU-ZdhbC;RX8P_SpB#GV2)udJoTX@Ov{cWkYYXY zR5lrvF=j9!ET%q<8Oi4#`fNd3YwL{}7jinCwQ!VVCL;r`R4Rq$p33%16R2j?b<5$m zfL<^CMWIj-ydnw&{HW33Q9oe6El^(owM&vTLIZ_D0hP;nBTr)vP_Y|_jpvMEU@+EB y(I9+Vz|MhspyocJd{pvbu~9Q04K%o@#gf3#ShK`gd%r<((>U>f#M>Y#00 z8^v}0(f+wo3H+lqw7+apP^gfSIh0Y7BG->~F7wBIdj8QLZ$5l~-|dfRFL=QI*nPjx z{kZ#ncT-yH|CGhx#bCGK(a}-W(b3`8{n&^;yoZmF#wd274Ruh`$jC_H!EJ4A%Ht~W z3MTOzw2SLFfgPxY;#`FX^Od`WF2y#?KnHXcdpXzgg$`beX_xQfC7k)!4E}(&Qe9nr zzM-LE6O@(C&d!2@U&0);;WEbYJl0^DcuXl)VFUKzJhUN?gtAy95(ymKWAkGjz`gLg zW!Q=zpbbOHSbcqcpx_sw4WD5(g02clXv6M8gC9cda8_ z@h0=R;wN}YJn#ytVD3SzgVP?zcj8!o;vKXiiItF(&zXw^mwO3bXCao~A8 z0IO{x;vbO`>{=E4)g1reO~5~=TO$oYsphf-MWE}Rv?6JCNh8z*?uCHMri5ACqqYRrq^ zx4ZURevixGwmrD%5Su{=wli-D2(I)R zyaQU;0=ZJO;8h&JDTmN`yp0k#tr557D+$4#-wB0VLUn3W5VH8Vix0g6kc}B z_`|dz91gn&kByC~SS+TRnws`n+*Dhw5{-x=h8M6774W)Vc`iba(4Ynf2c3hZR4R3k z#obm9ioK_BL!2WQkH^El-waG9lZri!yz!%Y4aM3 z8(KIB6*yRI$mRy76@J%@Nod0ngbEFo+YN1D8oh|vTymG74qNd#^3aB~e2wnv>Ixie zE{dOQ!f)XMzQkc1!W7QpI<%>*tZc5PrX~(04Gatv94uFh_eDeyd3@5|-X6(jv&xjq z<@^T^4-YGsJ%WAs(MRad_z+D{(P*@I|KrGHGKx#17v*S1A5xgaSD3~ze2h2If-3pg jL?W^HU!=w0|6TAuu>;j?=9u~f00000NkvXXu0mjfR7eE# diff --git a/lms/askbot/skins/mitx/media/images/view-background.png b/lms/askbot/skins/mitx/media/images/view-background.png deleted file mode 100644 index 5e951f401ea1219b9705382ffe2469e301c9f063..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^Ahrny8<5=Wv@!umu@pObhHwBu4M$1`kk47*5m^jW ze;tGwoit`w00kvWTq8#C_FR@f z=4#j!R2>VncxA;^UYd6Hi%DR|>Q1KbZzs;3`Quxe=nJPpCfoBTfG%S2boFyt=akR{ E0G+vALjV8( diff --git a/lms/askbot/skins/mitx/media/images/vote-accepted-on.png b/lms/askbot/skins/mitx/media/images/vote-accepted-on.png deleted file mode 100755 index 2026f3bcc50e2738bdb6c21f32ffb2a82d088e11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1124 zcmV-q1e^PbP)(98f962%urEyvrobTSI2iWDonJaRXmcCZ2&_7W|s$8;q zX78?p{;d3j=Jt6u zRf1NRn45~bKOeQloe<4k4F*a0^w988Pn>Cnf)+6fhvK8hS8!=&g-Tr5fO=!taU4$~ zOcn}~1dq~+P+Mn_{a3^epPXDNqV_QrDaOtjsGVmiri&cVy-8sMq#8b+xcl4la{1KX zd)90%K-Mw{%5_9>cd}5B8H|=?=ws;;*H`e>Y)`)g+x+z483yu`hh? z)vUt~N$7kH$t{g&Q6XMdNY^x24}P89!Jn~piFLVw3}%z-ZxONXwNtVCms$I=Hpt<1 z;>5`C2ui916R`wY2}KWfU9>D9Dg;Y<_4cY;b#?q^Z}exPXQRCee0Tq%=HACy;vCVa z)oQ5K>nMeJcUM{?j$?G%ZU0GpPJBRg?!=6hb@~7WeF-jmZ%z`A5yxGcdRvZYk@$u9 qnpoR$QRL8vDfll$;(wNZ1Q-D2C|}A%ZKM4F00002jDb`!&=tG_0s>$|K>*|`prJ6sL73(3+qcd@u04=FA87F=kXo2eKmZ_sFf2$; zPWA&zCIel<#Ldmk0CYVA$YsbuiEN3WpdiEV-@jQtefpFN6jJ#4^XDXHW@fmf00M{! z+3>`~M022FsXRP9Oq`sYV1r>6fM^&7@nIMgTp$C5g@r+ma0TLs@87?}%>)P_xCIFb z3Bo|lB^(?aU=1+CkRuHnMz)lXj}Igk4Pmg5nWK zgM9!HKul2A3d156+4UepK$wMv1;uh~hS${8fRioA43Jqsb)q0~fB<5G=1>+`+J?D) z%9JT^&HerTa5l0y4Aj-tf${>ww{PE&GbGq-fB*uAJJ7~2AO*0T3i2vCz-BNA)YjHA z05ccZaO6}4^z~N|8z6v~pcXs;rCV4KfV@~$RYfo!V1|GF`V}PzL1I7)9)r{X1Q0AA zT!9r*AO#=|po~*pU5(RlP?-Pt@dK;cg2@4U%(nbkq83~4GrM%MlNq) zt_Q^*$WcIgHIVxmBnA*bjL2RCmZUYn2r>s2cJMME1VFhHRs;jfeUu1=7iz%L6XYu( z0EK-K5dWAvcP;}!05PGJEj>WnAAr0L$}BL~0gGY=V7brm?%g}EuaFBtSO$Xz??)hR z2I3zuIe-8{v0&c3c_1f%lFyZoA3rjH3O+6_F0kui`2-depv(ty1ju)w_yQXE94J)^ z#Ltn<1PCBTY{dew=mzBvKWLT74YCM?K^B1kDEN?45GX8n0Pzwa{)%kQf&~j000M{+ zU%3+(7sm^vtby1Jh*f}C5Gn;S=spmi2jcxe{2E;^$d?NjE@S`*AVy+~WpV*P05LJp c7ytwq03Nl7Z}J%VEdT%j07*qoM6N<$f`!tbz5oCK diff --git a/lms/askbot/skins/mitx/media/images/vote-arrow-down-new.png b/lms/askbot/skins/mitx/media/images/vote-arrow-down-new.png deleted file mode 100644 index f2a28aea28c05382f18068b97c948c90b33c8fb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1458 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 BwKxC( diff --git a/lms/askbot/skins/mitx/media/images/vote-arrow-up-on-new.png b/lms/askbot/skins/mitx/media/images/vote-arrow-up-on-new.png deleted file mode 100644 index ef895206bbbbb62992cbd5095d1c41d0ecb7167f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1029 zcmV+g1p51lP)EV!e)g%b50AL6M{muKaIec#w!u;m=7b6ef7NAsC?}M-l z00@LWa{$pg)9Y=16lQ2ca80wIPmX5B;;RBAGo#}K90=+jb2sj4X!i<*>{Y+);+D!`nsq|-K;hF&BnbAQ4zZ%rr)^_8*rU$uLw5||Q&T)#rgjJ-tx9tEVNdN!? zJ6Gzw_x=@-`f99;!5^aHn0UixJQCjv$0=U+{qmgFMo~&#u&n&0mhhd+d*b`5Vxp&# znZ(h51fn^xAayS zz_H|)iT=L;($m8s2>(>zn2-u%sB;9G>aN+gHDee$XBaxCbzNR-o!46BnZ2vFT|B?L z?Qv|nqvMZxMEEE-p zR4QF8m&@M{-ZPA7C>G3IL(G%u(@!@7fOLDVy#%7eLASqs2wR)FY|F|{F!PcS(h)-1 zLP$p`=}4&^DU~CovZa)c>#qFR5LvUnJ+@`t#rbdGu?GM#Pa505p1AO_?0vTD9RMZ( zJkZ*QzD)yV+qRCouKSae>IRXpwN?(1bcm!arLct%wv^HlLfArx8=mKWzplaDVb*V` z|6$>~VAl>WUVwbz$nyjF<)ynjV>_^K%X28Z?(4;(b%ux*04xyEJQ3!IXif;R;CcRn zQfkrj{6(eIA~P>Zspfs3XIi6edqB`vvaNn`8Ff~$ckkp z5vb}q;#-WuH(d%uB8?9zt=~F5dqQ0J^&GaD_h*QiUj&c>@Hv2kf#C1LvhpB;sDP-l z1XXK@NTlH~5xv69Afnltf2LLDF=n1)=0m>&5Ejn5$77Am00000NkvXXu0mjfqR`=k diff --git a/lms/askbot/skins/mitx/media/images/vote-arrow-up-on.png b/lms/askbot/skins/mitx/media/images/vote-arrow-up-on.png deleted file mode 100755 index 56ad0c2591434e732e2148420330878414dd25b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 906 zcmV;519kj~P)xCZ&X`T>s1rrok967{t#$kYGS7Vd|fw?Kji~ z;lV5VHnRYeM`{4BF0c$^a34H4Y$j8XY%{~vP4Ur0f;38Hd%(7f1L*It^x#ox6Y^@F zBDnIA8Ir>UzFOH#7SIa-feR)47XkUfbW{ysEx$_Za+(2mrv@e0$jB+NG1p<%zK%|C zDhyl)$6NrHqq@G{IrJPRPyB_8o%g~qnsOblfcn? zud345g@qRO!2EE%0Z?uLjv-MxAZ&$ojSA9F#F=yV|e*{B8pP@ zPpy`qnVS0ErfIu&a2$lel&veGH2NtLOlegf9(x9-G9&7J^nDl1oLg=Hr16Fr(Oro+ zQbLGVpQ1pj;IRb_B#w0GLze=Va%%)w6bBhuOzm9OdN+s8=o$#_ccLp*OIu$G;h>rLx z;mgpUhOxf69+e^?1Ywq=jh2GNqFYu`xESK?pEsfZhXwuLEj*_r99^_vuKcEHdY&#l z0Wa8SHm?w7E{Z_H{N^6JFTqUk){$jh)SV7SR)<^84Pbk)5p-5xEIhKMBS92|x<@3= zZ)!HBX})$JOk*s=%QA0e+p{3c*vl0e=9sUD))!9#$+3Q`u!ebIIb1s(-e@F=*U1b% z9-vyObQ;pB6zoI_#Ud071$xZ|IC*EJ6cLci_@% diff --git a/lms/askbot/skins/mitx/media/images/vote-arrow-up.png b/lms/askbot/skins/mitx/media/images/vote-arrow-up.png deleted file mode 100755 index 6e9a51c7df2da30fd2e56359a93f4712077ee62d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 843 zcmV-R1GM~!P)Ze18CM!Al?Dub8&GoOqw)_0U&_j1|%mZ z3j?V-AXWlNa&mGq04*cZdym|9R2`JR?`}glckWT;thzab!KYu)chAIdN z2{HWr`xlHsmVhvfpOlou(AU?;;Ns$fQw<0U3k!q96hLNz%mxS`umwN?TMiBmxCT(X zz%UHx=;(lHFE1~+7_vH;K9D6Kvw*ZMNDV*$!7NY%c@N!iY!;y#j4TgxAjmABm>Nh7 zAb^;l8VIHqU?k#|$KnXEnE(OAf-H%y2t6WU0F?aD!Pc!?8Fud6iD4l?05Re7HZlf< zD?Z@u?ajc%#Do!S00G2;&E;unX$-o$x?py0ZZ5;*$&)b*1c~GFC9-({0mOnW3}J@j z3pQ+)=H=yK1UEncF%grAhy|b^LI(f=#DX0Dq!^ZylS5Px0R#}iRD>&trc9ZFHD1uo z0tg@`LIz>;HohQ2&zJxK#DXjV0<5g8D4IZp7&efdosDoMP(O0;g2Z7C0|+1%sG)yA zIS*DU-nemtK|@0WtN>JWV*^vCPQ{suK*a^n%pV{zfB=G7a0gg^Yk+0$lVq#(#)YQ~)1`j9~!(0t4szGKx0I>lA2$l~H18vm+Dfss78yF*}3LYLF zh6M{2fGtH24p`ms^XE@^(165%^kI-ZKmfrk*aj4E1sbd%ARqvC0Z0=pj6Z+=Ot?w| zS`LaokmG=6fn2{0Bn}Wjj4(qI5)wdFCb(AP=H>>wniTNm%NGV#&7yzfB V!E{DAJ$nEE002ovPDHLkV1i?NM1}wW diff --git a/lms/askbot/skins/mitx/media/images/vote-background.png b/lms/askbot/skins/mitx/media/images/vote-background.png deleted file mode 100644 index 0e1ded6e1b4aa094d29e31409ce8e2bc158fb903..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 225 zcmeAS@N?(olHy`uVBq!ia0vp^Ahrny8<5=Wv@!umu@pObhHwBu4M$1`kk47*5m^jW ze;tGwoit`w00kvWTq8*M!#1Rg)&GG&$E>Itmz8OBQ5N7A`ADVh{0GY5J$HKYgls$4p8 zP|A4A)+z>D=aLxy)oUF7ez6b<;{W>j>xNTY%&*<Q zl~7{r#<-|Zx+tX^7v1(nLEMz+FA(Vu=&lr@i%_r>io5Ej3qgvws#2;kN-aKOFk(}p z#$w4mvd&mnLCfqGB`LW%tTQXT~$@pwWz4*(Z$7u`~3X;?Ck6f-uOMADC8`%H49ug9F8Y$ zw_8+IRf)E?w)=F~D6T0AgF(40thghvTO@bbp`oEcsi>%U%r*rBP;F6Pqt;fdRTLK& z>)!xC_VxAkhJ(#!6RoYS59m#UO~C*#0w5d#&H*ynH51s~l9G~3PA->Ap>jP%V%H7; z1$bs+Zcu>R9*^fGmE?+(-EJ4{?d|vr-^j>_mM?&1XlTeop!aHPYwM^Sb=B3?ZURv7 zN9E<^df;4gaBv`wkB@cT+uMthZf$OE#_0YhP1;0PS65oCtgL+9(a~Wg;GAGQS@Lv2 zkN}h`N1!eQU0hu3oSU1o(cP$Wa&nTu!eDWr*}!96BogVHo}P|wZ*Lo!n{BzQY=b5*FE0apfWSv{QAJ{HZLNQP zem;yw6#{1^;uGuZ>*=Yfskc2nJ!4n)Ad9ee={G;^@9!H;&ZqE2X`<2Sx8B~~uQ}N? z!#RA{$lSzt+5M1{!p76PmY3|<5RSAL&84NK2CzIy@&xf8fJSJCngW4N5yyPM&Jfr$uh-k@bUF=s0dG-KZhD`{`3D>TQ-Os; z#w#l;;f;+A00-HDHurrf6dIy?@=((>&<&onoKqxC$uC;-AG8mSL?SV4mQhBw zj*gB7W@ctSFDxuPr@!!0KgLYy`Z|>GFQSP5EPn+U0OR;~TRRy%rT_o{07*qoM6N<$ Ef@zSrumAu6 diff --git a/lms/askbot/skins/mitx/media/images/vote-favorite-on.png b/lms/askbot/skins/mitx/media/images/vote-favorite-on.png deleted file mode 100755 index 1f9c14ab0813a1aec1f66b12c3eebe7232be3d8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1023 zcmVZd<5{Le6mt|)_<`GX($dlRu2ZJkuE zjf4$Qewch0=TDhyPWI*W3&@@n;Mrh z;ON^;+o1J6x+lSyC4yAc5*r#_RRe6^%F}2A*z}DOqn}Ca7lL#(u})I%7x#^k&|B0; z`f()bM-h)+;pZ4u$ALJ}P;Wvb3dM-H#z&Yj5-&MXQ!CKC#TXEuPEEBSJ=cPnS)ej| z2r5#+g@;jQ$TU_*6Ij=36b6CwjWTT^%5_m~{H>S28)%4W9OsGaYMC6L=yK8ysOFtZ z$a|Cc`qnVdXsKFY-eyhS#>TZMyvv&Gk-Z`#?>^Zjhh+Uaj*dLD2Glrl0XhE%M&6JK zdqZ9sxMdI+<1Dr0Qu()0dB28}pC5!-B*l|clf(rIB=P3C6)a-E6moW+nx^tEqNfk1 zhaN6W(@j2{T4UmW|9SG(9X_!#|lMVmx<&Y38Ta_F{^dJ0jyZ z^KE3Yfqc<E7fl_u~mXc`B_ShF#sjOqSPbOw6(spucpG!yqP5!xrMP zXa--)`><8~qDbZjaUY(43Ik6LqKX$#+j`!7QB^cQueWs7N thq=s2qJz(mO1EpR@jr*ce;$7X7ywPEpB^Im6SDvS002ovPDHLkV1mKF-a7yQ diff --git a/lms/askbot/skins/mitx/media/images/wiki.png b/lms/askbot/skins/mitx/media/images/wiki.png deleted file mode 100644 index 06d487f3e2a76ed2783f6c3f95b56bd2e2249680..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5178 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 diff --git a/lms/askbot/skins/mitx/media/images/youtube-sharing.png b/lms/askbot/skins/mitx/media/images/youtube-sharing.png deleted file mode 100644 index a26b18121b8ac99b19bec12d907a0840bd18b208..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1396 zcmV-)1&jKLP)wb%bGfoP)4K!F|Djf@^AOL{U*uutp#k<@f-*(TEzng*=RbQ0>^UBmCgJygXrZ zqObtR@H>nEy3vf~7!JX{!Vl&*4;1q;I+q8Yv2OB&Xw_r$j zF~Z?+h=ha$k(ih$A|oRO^9(NGM-<`^uEC9^ScO^?=!Nm|@xFB;A|lLplS?q9Kamtn zup5SA1$;Z`?(P;nJv|~TD@({ZgI|y-cUp?Cu>qTL2n+P!=H_O>I==T?IkU8VZV4v% zgmz3tLnI|739r}d+ivRAsY1>M97h7$;Fgi=@h(0`u^xQk!Uf+tlP6DB?{0-5-5@y9 zPVg}81hEUk41W0Vp>MmKoE#x%4UQlYKcEO-p-O7<7u?#~>RTs0Jzc$f2@Dg#nRbHp zm&O$!cpXk58dq=;+p!Eca0f*;gH!N839hgcT%p9?$H)M|@t7cQk4G{3VK8zq5z#h- z9k`|hH`)oVR)TLLB0%skjFi!1k&HA@tVQqvCHTCZ;5udD z>u^{EpO$C235ddbTGSFOhv5&w<1B)YE5WDi1bdW)J2BiM*gV#1k%znT_DSr=CQLyo z?%^R?mzChp>;&g)&&Ep@!OhCmTRzUqZMUFA{(Kcqd?Mp3F+m1r`3W9{ z`%3T{JHeynx!jQ6w+KF`1m~d*E>xoe8?gb!sKt2X;zb#p?I(D?cH)?B_tbn|JB}m# z1b?Rmw;~@Oq8tQ+VAQ~gZ?O+vf~Tm#`tgf91V`Bo*8f=Mo58)ky=L%H`PfFxz(pj= z+sjY|7oJxRiB*^cZ(Cd2L@n5fUKmmX!M4AbcnTfbvphICIr*RMrcIl+S3Y(IALAY> zun9+T7OPQ?PJDrU`Iyn(-X707A|oTi3_huSFm&>%_#w2xh7JIH8AW5S5jc*1@`lhK48Q#fzaRfjUWe zUAb6)8|$!MT8Bz_P>#iz33X-qUhSoN6OU_h{e01~bkU+k zBBWqB9vGUfh(tJ3rarUM9*B^G^$&@fi*O;>$;2@jn!|8}6|Bu#7=E;%6sZ^hefnbz z+&Bs^3{9hY&9ZRe!tjIT6yj%#@CUex(>R2KIF8HchN0sVbLWsDL&_jjD_5=zD_Ec5 z*l9+D$cz021qG=c9UVf~)zuYzaCLRH2ry%@8CQb{y^W8M0Z~|3`1t4Z})EE4}3;r9G(Uv=hjtIX10000Ci diff --git a/lms/askbot/skins/mitx/media/style/auth.css b/lms/askbot/skins/mitx/media/style/auth.css deleted file mode 100644 index 33702758c8..0000000000 --- a/lms/askbot/skins/mitx/media/style/auth.css +++ /dev/null @@ -1,48 +0,0 @@ -#bigicon_providers, #smallicon_providers { - display: block; - padding: 0px; - width:600px; - margin:0px 0px 5px 0px; -} - -.provider_logo { - display: inline-block; - padding: 4px; - border: 1px solid #DDD; - text-align: center; - vertical-align: middle; -} - -.provider_logo.big { - height: 40px; - width: 90px; -} - -.provider_logo.small { - height: 32px; - width: 32px; -} - -.provider_logo.selected { - outline: 2px solid #FFF8C6; -} - -.provider_logo .provider_url { - display: none; -} - -.signin_form input[type="text"], .signin_form input[type="password"], .signin_form input[type="submit"] { - height: 28px; - line-height: 22px; - font-size: 140%; - border: 1px solid #999; -} - -.signin_form .icon_input { - padding-left: 20px; -} - -.or_label { - margin-top: 20px; - margin-bottom: 10px; -} \ No newline at end of file diff --git a/lms/askbot/skins/mitx/media/style/extra.css b/lms/askbot/skins/mitx/media/style/extra.css deleted file mode 100644 index 8877522b2e..0000000000 --- a/lms/askbot/skins/mitx/media/style/extra.css +++ /dev/null @@ -1,227 +0,0 @@ -div.header-wrapper-mitx header div.table-wrapper, div.header-wrapper-mitx header div.course-wrapper, div.header-wrapper-mitx header div.book-wrapper, div.header-wrapper-mitx header div.profile-wrapper, html body.askbot div.header-wrapper-mitx header section.main-content div.discussion-wrapper { - display: table; -width: 100%; } - -div.header-wrapper { - -webkit-box-shadow: inset 0 -1px 2px #732626; - -moz-box-shadow: inset 0 -1px 2px #732626; - -ms-box-shadow: inset 0 -1px 2px #732626; - -o-box-shadow: inset 0 -1px 2px #732626; - box-shadow: inset 0 -1px 2px #732626; - background: #993333; - border-bottom: 1px solid #fff; - overflow: hidden; - font-family: "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; - margin: 0 auto; - padding: 0; -} - -div.header-wrapper header { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; - -o-box-sizing: border-box; - box-sizing: border-box; - padding: 0 22.652px; - margin: 0 auto; - max-width: 1400px; - min-width: 810px; - width: 100%; - text-align: left; - display: block; -} - -div.header-wrapper header hgroup { -float: left; } - -div.header-wrapper header hgroup:after { - content: "."; - display: block; - height: 0; - clear: both; - visibility: hidden; -} - -div.header-wrapper header hgroup h1 { - border-right: 1px solid #862d2d; - color: #4d1919; - font-size: 20px; - font-weight: 800; - margin: 0 22.652px 0 0; - padding: 17px 22.652px 14px 0; - text-shadow: 0 1px 0 #bf4040; - line-height: 22.652px; - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; - *vertical-align: auto; -} - -div.header-wrapper header hgroup h2 { - display: -moz-inline-box; - -moz-box-orient: vertical; - display: inline-block; - vertical-align: baseline; - zoom: 1; - *display: inline; *vertical-align: auto; - margin: 0 22.652px 0 0; - padding: 19px 22.652px 9px 0; - line-height: 22.652px; - border-right: 1px solid #862d2d; - -webkit-font-smoothing: antialiased; - font-size: 14px; - font-weight: bold; - letter-spacing: 1px; - text-transform: uppercase; -} - -div.header-wrapper header hgroup h2 a { - color: #fff; -text-decoration: none; } - -div.header-wrapper header hgroup h2 a:hover { -color: rgba(255, 255, 255, 0.7); } - - @media screen and (max-width: 920px) { - div.header-wrapper header hgroup { - border-bottom: 1px solid #862d2d; - display: block; - float: none; } - div.header-wrapper header hgroup h1 { - float: right; - border: 0; - margin-right: 0; - padding-right: 0; - - } - div.header-wrapper header hgroup h2 { - float: left; - border: 0; - margin-right: 0; - padding-right: 0; } } - -div.header-wrapper header nav { - float: left; - display: block; - margin: 0; - padding: 0; - text-shadow: 0 -1px 0 #732626; --webkit-font-smoothing: antialiased; } - -div.header-wrapper header nav ul { - display: inline-block; - padding: 19px 0 9px; -margin: 0; } - -div.header-wrapper header nav ul li { - margin-right: 22.652px; - display: inline-block; - margin-bottom: 0; -line-height: 22.652px; } - -div.header-wrapper header nav ul li a { - color: #fff; -text-decoration: none; } - -div.header-wrapper header nav ul li a:hover { - color: rgba(255, 255, 255, 0.7); - background-color: none; -text-decoration: none; } - -.content-wrapper { - /* margin: 0 auto; */ - padding: 0 20px; - max-width: 1300px; - min-width: 975px; - width: 100%; -} -#ContentLeft { - width: 83%; - margin-right: 2%; -} -#ContentRight { - width: 15%; -} -.short-summary { - width:98%; - background: none; -} -.short-summary h2 { - font-size:18px; -} -.short-summary .counts .item-count { - font-size: 20px; -} -#ground { - border: 0; - background: none; -} -#ground .content-wrapper { - border-top: 1px solid #ddd; - padding: 10px; -} -.powered-link { - color: #999; -} -.copyright a { - color: #bbb; -} -#searchBar { - float: left; - /* margin-left: 40px; */ -} -#scopeWrapper { - width: 60% !important; - min-width: 725px; - box-sizing: border-box; -} - -.question-page .question-content { - width: 95%; -} -.question-page .answer-table { - padding: 12px; -} -.question-page .comments div.controls { - float: none; -} -.question-page .tabBar-answer { - width: 100%; -} -#ContentFull { - float: left; - width: 100%; -} - -.user-stats-table .narrow { - width: 100%; -} -#askButton { - text-transform: capitalize; - font-size: 18px; - width: 15%; - overflow: hidden; -} -#secondaryHeader #scopeWrapper .scope-selector { - font-size: 15px; -} - -#secondaryHeader { - height: auto !important; -} -.tabBar { - font-family: "Open sans", sans-serif; -} -.rss { - display: none; -} -#question-table { - width: 100%; -} -h2.share-question { - font-weight: normal; - font-size: 16px; -} diff --git a/lms/askbot/skins/mitx/media/style/jquery.autocomplete.css b/lms/askbot/skins/mitx/media/style/jquery.autocomplete.css deleted file mode 100644 index 09b08192db..0000000000 --- a/lms/askbot/skins/mitx/media/style/jquery.autocomplete.css +++ /dev/null @@ -1,40 +0,0 @@ -.acInput { - width: 200px; -} -.acResults { - padding: 0px; - border: 1px solid WindowFrame; - background-color: #fff; - overflow: hidden; - -webkit-box-shadow: 0px 1px 1px #A7A7A7; - -moz-box-shadow: 0px 1px 1px #BFBFBF; - box-shadow: 0px 1px 1px #BFBFBF; -} - -.acResults ul { - width: 100%; - list-style-position: outside; - list-style: none; - padding: 0; - margin: 0; -} - -.acResults li { - margin: 0px; - padding: 2px 5px; - cursor: pointer; - display: block; - width: 100%; - font: menu; - font-size: 14px; - overflow: hidden; -} - -.acLoading { - background : url('../images/indicator.gif') right center no-repeat; -} - -.acSelect { - background-color: Highlight; - color: HighlightText; -} diff --git a/lms/askbot/skins/mitx/media/style/lib_style.less b/lms/askbot/skins/mitx/media/style/lib_style.less deleted file mode 100644 index 941c83ff96..0000000000 --- a/lms/askbot/skins/mitx/media/style/lib_style.less +++ /dev/null @@ -1,65 +0,0 @@ -/* General Predifined classes, read more in lesscss.org */ - -/* Variables for Colors*/ - -@header-color:#16160f; -@link:#1b79bd; -@question-link:#464646; -@button-label:#4a757f; -@section-title:#7ea9b3; -@info-text:#707070; -@info-text-dark:#525252; - -/* Variables for fonts*/ - -@body-font:Arial; /* "Trebuchet MS", sans-serif;*/ -@sort-font:Georgia, serif; -@main-font:'Yanone Kaffeesatz', sans-serif; -@secondary-font:Arial; - -/* Receive exactly positions for background Sprite */ - -.sprites(@hor,@vert,@back:url(../images/sprites.png)){ - background:@hor @vert @back no-repeat; -} - -/* CSS3 Elements */ - -.box-shadow (@hor: 0px, @vert: 0px, @blur: 5px, @shadow: #929292){ - -webkit-box-shadow: @arguments ; - -moz-box-shadow: @arguments; - box-shadow: @arguments; -} - -.text-shadow(@hor: 0px, @vert: 0px, @blur: 5px, @shadow: #929292){ - text-shadow: @arguments; - -moz-text-shadow: @arguments; - -webkit-text-shadow: @arguments; -} - -.rounded-corners(@radio: 5px){ - border-radius: @radio; - -ms-border-radius: @radio; - -moz-border-radius: @radio; - -webkit-border-radius: @radio; - -khtml-border-radius: @radio; -} - -.rounded-corners-top(@radio:5px){ - border-top-right-radius:@radio; - border-top-left-radius:@radio; - -moz-border-radius-topright:@radio; - -moz-border-radius-topleft:@radio; - -webkit-border-top-left-radius:@radio; - -webkit-border-top-right-radius:@radio; -} - -.rounded-corners-right(@radio:5px){ - border-top-right-radius:@radio; - border-bottom-right-radius:@radio; - -moz-border-radius-topright:@radio; - -moz-border-radius-bottomright:@radio; - -webkit-border-bottom-right-radius:@radio; - -webkit-border-top-right-radius:@radio; -} - diff --git a/lms/askbot/skins/mitx/media/style/openid.css b/lms/askbot/skins/mitx/media/style/openid.css deleted file mode 100644 index 0d201df2f4..0000000000 --- a/lms/askbot/skins/mitx/media/style/openid.css +++ /dev/null @@ -1,45 +0,0 @@ -#openid_form { - width: 470px; -} - #openid_form legend { - font-weight: bold; - } -#openid_choice { - display: none; -} -#openid_input_area { - clear: both; - padding: 10px; -} -#openid_btns, #openid_btns br { - clear: both; -} - #openid_highlight { - padding: 3px; - background-color: #FFFCC9; - float: left; - } - .openid_large_btn { - width: 100px; - height: 60px; - border: 1px solid #DDD; - margin: 3px; - float: left; - } - .openid_small_btn { - width: 24px; - height: 24px; - border: 1px solid #DDD; - margin: 3px; - float: left; - } - a.openid_large_btn:focus { - outline: none; - } - a.openid_large_btn:focus - { - -moz-outline-style: none; - } - .openid_selected { - border: 4px solid #DDD; - } diff --git a/lms/askbot/skins/mitx/media/style/prettify.css b/lms/askbot/skins/mitx/media/style/prettify.css deleted file mode 100644 index 10a37577c7..0000000000 --- a/lms/askbot/skins/mitx/media/style/prettify.css +++ /dev/null @@ -1,27 +0,0 @@ -/* Pretty printing styles. Used with prettify.js. */ - -.str { color: #080; } -.kwd { color: #008; } -.com { color: #800; } -.typ { color: #606; } -.lit { color: #066; } -.pun { color: #660; } -.pln { color: #000; } -.tag { color: #008; } -.atn { color: #606; } -.atv { color: #080; } -.dec { color: #606; } -pre.prettyprint { padding: 3px; border: 0px solid #888; } - -@media print { - .str { color: #060; } - .kwd { color: #006; font-weight: bold; } - .com { color: #600; font-style: italic; } - .typ { color: #404; font-weight: bold; } - .lit { color: #044; } - .pun { color: #440; } - .pln { color: #000; } - .tag { color: #006; font-weight: bold; } - .atn { color: #404; } - .atv { color: #060; } -} diff --git a/lms/askbot/skins/mitx/media/style/style.css b/lms/askbot/skins/mitx/media/style/style.css deleted file mode 100644 index 487e7faf93..0000000000 --- a/lms/askbot/skins/mitx/media/style/style.css +++ /dev/null @@ -1,3165 +0,0 @@ -@import url(jquery.autocomplete.css); -/* General Predifined classes, read more in lesscss.org */ -/* Variables for Colors*/ -/* Variables for fonts*/ -/* "Trebuchet MS", sans-serif;*/ -/* Receive exactly positions for background Sprite */ -/* CSS3 Elements */ -/* Library of predifined less functions styles */ -/* ----- General HTML Styles----- */ -body { - background: #FFF; - font-size: 14px; - line-height: 150%; - margin: 0; - padding: 0; - color: #000; - font-family: Arial; -} -div { - margin: 0 auto; - padding: 0; -} -h1, -h2, -h3, -h4, -h5, -h6, -ul, -li, -dl, -dt, -dd, -form, -img, -p { - margin: 0; - padding: 0; - border: none; -} -label { - vertical-align: middle; -} -hr { - border: none; - border-top: 1px dashed #ccccce; -} -input, select { - vertical-align: middle; - font-family: Trebuchet MS, "segoe ui", Helvetica, Tahoma, Verdana, MingLiu, PMingLiu, Arial, sans-serif; - margin-left: 0px; -} -textarea:focus, input:focus { - outline: none; -} -iframe { - border: none; -} -p { - font-size: 14px; - line-height: 140%; - margin-bottom: 6px; -} -a { - color: #1b79bd; - text-decoration: none; - cursor: pointer; -} -h2 { - font-size: 21px; - padding: 3px 0 3px 5px; -} -h3 { - font-size: 19px; - padding: 3px 0 3px 5px; -} -ul { - list-style: disc; - margin-left: 20px; - padding-left: 0px; - margin-bottom: 1em; -} -ol { - list-style: decimal; - margin-left: 30px; - margin-bottom: 1em; - padding-left: 0px; -} -td ul { - vertical-align: middle; -} -li input { - margin: 3px 3px 4px 3px; -} -pre { - font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace; - font-size: 100%; - margin-bottom: 10px; - /*overflow: auto;*/ - - background-color: #F5F5F5; - padding-left: 5px; - padding-top: 5px; - /*width: 671px;*/ - - padding-bottom: 20px ! ie7; -} -code { - font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace; - font-size: 100%; -} -blockquote { - margin-bottom: 10px; - margin-right: 15px; - padding: 10px 0px 1px 10px; - background-color: #F5F5F5; -} -/* http://pathfindersoftware.com/2007/09/developers-note-2/ */ -* html .clearfix, * html .paginator { - height: 1; - overflow: visible; -} -+ html .clearfix, + html .paginator { - min-height: 1%; -} -.clearfix:after, .paginator:after { - clear: both; - content: "."; - display: block; - height: 0; - visibility: hidden; -} -.badges a { - color: #763333; - text-decoration: underline; -} -a:hover { - text-decoration: underline; -} -.badge-context-toggle.active { - cursor: pointer; - text-decoration: underline; -} -h1 { - font-size: 24px; - padding: 10px 0 5px 0px; -} -/* ----- Extra space above for messages ----- */ -body.user-messages { - margin-top: 2.4em; -} -/* ----- Custom positions ----- */ -.left { - float: left; -} -.right { - float: right; -} -.clean { - clear: both; -} -.center { - margin: 0 auto; - padding: 0; -} -/* ----- Notify message bar , check blocks/system_messages.html ----- */ -.notify { - position: fixed; - top: 0px; - left: 0px; - width: 100%; - z-index: 100; - padding: 0; - text-align: center; - background-color: #f5dd69; - border-top: #fff 1px solid; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -.notify p.notification { - margin-top: 6px; - margin-bottom: 6px; - font-size: 16px; - color: #424242; -} -#closeNotify { - position: absolute; - right: 5px; - top: 7px; - color: #735005; - text-decoration: none; - line-height: 18px; - background: -6px -5px url(../images/sprites.png) no-repeat; - cursor: pointer; - width: 20px; - height: 20px; -} -#closeNotify:hover { - background: -26px -5px url(../images/sprites.png) no-repeat; -} -/* ----- Header, check blocks/header.html ----- */ -#header { - margin-top: 0px; - background: #16160f; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -.content-wrapper { - /* wrapper positioning class */ - - width: 960px; - margin: auto; - position: relative; -} -#logo img { - padding: 5px 0px 5px 0px; - height: 75px; - width: auto; - float: left; -} -#userToolsNav { - /* Navigation bar containing login link or user information, check widgets/user_navigation.html*/ - - height: 20px; - padding-bottom: 5px; -} -#userToolsNav a { - height: 35px; - text-align: right; - margin-left: 20px; - text-decoration: underline; - color: #d0e296; - font-size: 16px; -} -#userToolsNav a:first-child { - margin-left: 0; -} -#userToolsNav a#ab-responses { - margin-left: 3px; -} -#userToolsNav .user-info, #userToolsNav .user-micro-info { - color: #b5b593; -} -#userToolsNav a img { - vertical-align: middle; - margin-bottom: 2px; -} -#userToolsNav .user-info a { - margin: 0; - text-decoration: none; -} -#metaNav { - /* Top Navigation bar containing links for tags, people and badges, check widgets/header.html */ - - float: right; - /* for #header.with-logo it is modified */ - -} -#metaNav a { - color: #e2e2ae; - padding: 0px 0px 0px 35px; - height: 25px; - line-height: 30px; - margin: 5px 0px 0px 10px; - font-size: 18px; - font-weight: 100; - text-decoration: none; - display: block; - float: left; -} -#metaNav a:hover { - text-decoration: underline; -} -#metaNav a.on { - font-weight: bold; - color: #FFF; - text-decoration: none; -} -#metaNav a.special { - font-size: 18px; - color: #B02B2C; - font-weight: bold; - text-decoration: none; -} -#metaNav a.special:hover { - text-decoration: underline; -} -#metaNav #navTags { - background: -50px -5px url(../images/sprites.png) no-repeat; -} -#metaNav #navUsers { - background: -125px -5px url(../images/sprites.png) no-repeat; -} -#metaNav #navBadges { - background: -210px -5px url(../images/sprites.png) no-repeat; -} -#header.with-logo #userToolsNav { - position: absolute; - bottom: 0; - right: 0px; -} -#header.without-logo #userToolsNav { - float: left; - margin-top: 7px; -} -#header.without-logo #metaNav { - margin-bottom: 7px; -} -#secondaryHeader { - /* Div containing Home button, scope navigation, search form and ask button, check blocks/secondary_header.html */ - - height: 55px; - background: #e9e9e1; - border-bottom: #d3d3c2 1px solid; - border-top: #fcfcfc 1px solid; - margin-bottom: 10px; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -#secondaryHeader #homeButton { - border-right: #afaf9e 1px solid; - background: -6px -36px url(../images/sprites.png) no-repeat; - height: 55px; - width: 43px; - display: block; - float: left; -} -#secondaryHeader #homeButton:hover { - background: -51px -36px url(../images/sprites.png) no-repeat; -} -#secondaryHeader #scopeWrapper { - width: 688px; - float: left; -} -#secondaryHeader #scopeWrapper a { - display: block; - float: left; -} -#secondaryHeader #scopeWrapper .scope-selector { - font-size: 21px; - color: #5a5a4b; - height: 55px; - line-height: 55px; - margin-left: 24px; -} -#secondaryHeader #scopeWrapper .on { - background: url(../images/scopearrow.png) no-repeat center bottom; -} -#secondaryHeader #scopeWrapper .ask-message { - font-size: 24px; -} -#searchBar { - /* Main search form , check widgets/search_bar.html */ - - display: inline-block; - background-color: #fff; - width: 412px; - border: 1px solid #c9c9b5; - float: right; - height: 42px; - margin: 6px 0px 0px 15px; -} -#searchBar .searchInput, #searchBar .searchInputCancelable { - font-size: 30px; - height: 40px; - font-weight: 300; - background: #FFF; - border: 0px; - color: #484848; - padding-left: 10px; - font-family: Arial; - vertical-align: middle; -} -#searchBar .searchInput { - width: 352px; -} -#searchBar .searchInputCancelable { - width: 317px; -} -#searchBar .logoutsearch { - width: 337px; -} -#searchBar .searchBtn { - font-size: 10px; - color: #666; - background-color: #eee; - height: 42px; - border: #FFF 1px solid; - line-height: 22px; - text-align: center; - float: right; - margin: 0px; - width: 48px; - background: -98px -36px url(../images/sprites.png) no-repeat; - cursor: pointer; -} -#searchBar .searchBtn:hover { - background: -146px -36px url(../images/sprites.png) no-repeat; -} -#searchBar .cancelSearchBtn { - font-size: 30px; - color: #ce8888; - background: #fff; - height: 42px; - border: 0px; - border-left: #deded0 1px solid; - text-align: center; - width: 35px; - cursor: pointer; -} -#searchBar .cancelSearchBtn:hover { - color: #d84040; -} -body.anon #searchBar { - width: 500px; -} -body.anon #searchBar .searchInput { - width: 440px; -} -body.anon #searchBar .searchInputCancelable { - width: 405px; -} -#askButton { - /* check blocks/secondary_header.html and widgets/ask_button.html*/ - - background: url(../images/bigbutton.png) repeat-x bottom; - line-height: 44px; - text-align: center; - width: 200px; - height: 42px; - font-size: 23px; - color: #4a757f; - margin-top: 7px; - float: right; - text-transform: uppercase; - border-radius: 5px; - -ms-border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - -khtml-border-radius: 5px; - -webkit-box-shadow: 1px 1px 2px #636363; - -moz-box-shadow: 1px 1px 2px #636363; - box-shadow: 1px 1px 2px #636363; -} -#askButton:hover { - text-decoration: none; - background: url(../images/bigbutton.png) repeat-x top; - text-shadow: 0px 1px 0px #c6d9dd; - -moz-text-shadow: 0px 1px 0px #c6d9dd; - -webkit-text-shadow: 0px 1px 0px #c6d9dd; -} -/* ----- Content layout, check two_column_body.html or one_column_body.html ----- */ -#ContentLeft { - width: 730px; - float: left; - position: relative; - padding-bottom: 10px; -} -#ContentRight { - width: 200px; - float: right; - padding: 0 0px 10px 0px; -} -#ContentFull { - float: left; - width: 960px; -} -/* ----- Sidebar Widgets Box, check main_page/sidebar.html or question/sidebar.html ----- */ -.box { - background: #fff; - padding: 4px 0px 10px 0px; - width: 200px; - /* widgets for question template */ - - /* notify by email box */ - -} -.box p { - margin-bottom: 4px; -} -.box p.info-box-follow-up-links { - text-align: right; - margin: 0; -} -.box h2 { - padding-left: 0; - background: #eceeeb; - height: 30px; - line-height: 30px; - text-align: right; - font-size: 18px !important; - font-weight: normal; - color: #656565; - padding-right: 10px; - margin-bottom: 10px; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -.box h3 { - color: #4a757f; - font-size: 18px; - text-align: left; - font-weight: normal; - font-family: 'Yanone Kaffeesatz', sans-serif; - padding-left: 0px; -} -.box .contributorback { - background: #eceeeb url(../images/contributorsback.png) no-repeat center left; -} -.box label { - color: #707070; - font-size: 15px; - display: block; - float: right; - text-align: left; - font-family: 'Yanone Kaffeesatz', sans-serif; - width: 80px; - margin-right: 18px; -} -.box #displayTagFilterControl label { - /*Especial width just for the display tag filter box in index page*/ - - width: 160px; -} -.box ul { - margin-left: 22px; -} -.box li { - list-style-type: disc; - font-size: 13px; - line-height: 20px; - margin-bottom: 10px; - color: #707070; -} -.box ul.tags { - list-style: none; - margin: 0; - padding: 0; - line-height: 170%; - display: block; -} -.box #displayTagFilterControl p label { - color: #707070; - font-size: 15px; -} -.box .inputs #interestingTagInput, .box .inputs #ignoredTagInput { - width: 153px; - padding-left: 5px; - border: #c9c9b5 1px solid; - height: 25px; -} -.box .inputs #interestingTagAdd, .box .inputs #ignoredTagAdd { - background: url(../images/small-button-blue.png) repeat-x top; - border: 0; - color: #4a757f; - font-weight: bold; - font-size: 12px; - width: 30px; - height: 27px; - margin-top: -2px; - cursor: pointer; - border-radius: 4px; - -ms-border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - -khtml-border-radius: 4px; - text-shadow: 0px 1px 0px #e6f6fa; - -moz-text-shadow: 0px 1px 0px #e6f6fa; - -webkit-text-shadow: 0px 1px 0px #e6f6fa; - -webkit-box-shadow: 1px 1px 2px #808080; - -moz-box-shadow: 1px 1px 2px #808080; - box-shadow: 1px 1px 2px #808080; -} -.box .inputs #interestingTagAdd:hover, .box .inputs #ignoredTagAdd:hover { - background: url(../images/small-button-blue.png) repeat-x bottom; -} -.box img.gravatar { - margin: 1px; -} -.box a.followed, .box a.follow { - background: url(../images/medium-button.png) top repeat-x; - height: 34px; - line-height: 34px; - text-align: center; - border: 0; - font-family: 'Yanone Kaffeesatz', sans-serif; - color: #4a757f; - font-weight: normal; - font-size: 21px; - margin-top: 3px; - display: block; - width: 120px; - text-decoration: none; - border-radius: 4px; - -ms-border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - -khtml-border-radius: 4px; - -webkit-box-shadow: 1px 1px 2px #636363; - -moz-box-shadow: 1px 1px 2px #636363; - box-shadow: 1px 1px 2px #636363; - margin: 0 auto; - padding: 0; -} -.box a.followed:hover, .box a.follow:hover { - text-decoration: none; - background: url(../images/medium-button.png) bottom repeat-x; - text-shadow: 0px 1px 0px #c6d9dd; - -moz-text-shadow: 0px 1px 0px #c6d9dd; - -webkit-text-shadow: 0px 1px 0px #c6d9dd; -} -.box a.followed div.unfollow { - display: none; -} -.box a.followed:hover div { - display: none; -} -.box a.followed:hover div.unfollow { - display: inline; - color: #a05736; -} -.box .favorite-number { - padding: 5px 0 0 5px; - font-size: 100%; - font-family: Arial; - font-weight: bold; - color: #777; - text-align: center; -} -.box .notify-sidebar #question-subscribe-sidebar { - margin: 7px 0 0 3px; -} -.statsWidget p { - color: #707070; - font-size: 16px; - border-bottom: #cccccc 1px solid; - font-size: 13px; -} -.statsWidget p strong { - float: right; - padding-right: 10px; -} -.questions-related { - word-wrap: break-word; -} -.questions-related p { - line-height: 20px; - padding: 4px 0px 4px 0px; - font-size: 16px; - font-weight: normal; - border-bottom: #cccccc 1px solid; -} -.questions-related a { - font-size: 13px; -} -/* tips and markdown help are widgets for ask template */ -#tips li { - color: #707070; - font-size: 13px; - list-style-image: url(../images/tips.png); -} -#tips a { - font-size: 16px; -} -#markdownHelp li { - color: #707070; - font-size: 13px; -} -#markdownHelp a { - font-size: 16px; -} -/* ----- Sorting top Tab, check main_page/tab_bar.html ------*/ -.tabBar { - background-color: #eff5f6; - height: 30px; - margin-bottom: 3px; - margin-top: 3px; - float: right; - font-family: Georgia, serif; - font-size: 16px; - border-radius: 5px; - -ms-border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - -khtml-border-radius: 5px; -} -.tabBar h2 { - float: left; -} -.tabsA, .tabsC { - float: right; - position: relative; - display: block; - height: 20px; -} -/* tabsA - used for sorting */ -.tabsA { - float: right; -} -.tabsC { - float: left; -} -.tabsA a, .tabsC a { - border-left: 1px solid #d0e1e4; - color: #7ea9b3; - display: block; - float: left; - height: 20px; - line-height: 20px; - padding: 4px 7px 4px 7px; - text-decoration: none; -} -.tabsA a.on, -.tabsC a.on, -.tabsA a:hover, -.tabsC a:hover { - color: #4a757f; -} -.tabsA .label, .tabsC .label { - float: left; - color: #646464; - margin-top: 4px; - margin-right: 5px; -} -.main-page .tabsA .label { - margin-left: 8px; -} -.tabsB a { - background: #eee; - border: 1px solid #eee; - color: #777; - display: block; - float: left; - height: 22px; - line-height: 28px; - margin: 5px 0px 0 4px; - padding: 0 11px 0 11px; - text-decoration: none; -} -.tabsC .first { - border: none; -} -.rss { - float: right; - font-size: 16px; - color: #f57900; - margin: 5px 0px 3px 7px; - width: 52px; - padding-left: 2px; - padding-top: 3px; - background: #ffffff url(../images/feed-icon-small.png) no-repeat center right; - float: right; - font-family: Georgia, serif; - font-size: 16px; -} -.rss:hover { - color: #F4A731 !important; -} -/* ----- Headline, containing number of questions and tags selected, check main_page/headline.html ----- */ -#questionCount { - font-weight: bold; - font-size: 23px; - color: #7ea9b3; - width: 200px; - float: left; - margin-bottom: 8px; - padding-top: 6px; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -#listSearchTags { - float: left; - margin-top: 3px; - color: #707070; - font-size: 16px; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -ul#searchTags { - margin-left: 10px; - float: right; - padding-top: 2px; -} -.search-tips { - font-size: 16px; - line-height: 17px; - color: #707070; - margin: 5px 0 10px 0; - padding: 0px; - float: left; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -.search-tips a { - text-decoration: underline; - color: #1b79bd; -} -/* ----- Question list , check main_page/content.html and macros/macros.html----- */ -#question-list { - float: left; - position: relative; - background-color: #FFF; - padding: 0; - width: 100%; -} -.short-summary { - position: relative; - filter: inherit; - padding: 10px; - border-bottom: 1px solid #DDDBCE; - margin-bottom: 1px; - overflow: hidden; - width: 710px; - float: left; - background: url(../images/summary-background.png) repeat-x; -} -.short-summary h2 { - font-size: 24px; - font-weight: normal; - line-height: 26px; - padding-left: 0; - margin-bottom: 6px; - display: block; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -.short-summary a { - color: #464646; -} -.short-summary .userinfo { - text-align: right; - line-height: 16px; - font-family: Arial; - padding-right: 4px; -} -.short-summary .userinfo .relativetime, .short-summary span.anonymous { - font-size: 11px; - clear: both; - font-weight: normal; - color: #555; -} -.short-summary .userinfo a { - font-weight: bold; - font-size: 11px; -} -.short-summary .counts { - float: right; - margin: 4px 0 0 5px; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -.short-summary .counts .item-count { - padding: 0px 5px 0px 5px; - font-size: 25px; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -.short-summary .counts .votes div, -.short-summary .counts .views div, -.short-summary .counts .answers div, -.short-summary .counts .favorites div { - margin-top: 3px; - font-size: 14px; - line-height: 14px; - color: #646464; -} -.short-summary .tags { - margin-top: 0; -} -.short-summary .votes, -.short-summary .answers, -.short-summary .favorites, -.short-summary .views { - text-align: center; - margin: 0 3px; - padding: 8px 2px 0px 2px; - width: 51px; - float: right; - height: 44px; - border: #dbdbd4 1px solid; -} -.short-summary .votes { - background: url(../images/vote-background.png) repeat-x; -} -.short-summary .answers { - background: url(../images/answers-background.png) repeat-x; -} -.short-summary .views { - background: url(../images/view-background.png) repeat-x; -} -.short-summary .no-votes .item-count { - color: #b1b5b6; -} -.short-summary .some-votes .item-count { - color: #4a757f; -} -.short-summary .no-answers .item-count { - color: #b1b5b6; -} -.short-summary .some-answers .item-count { - color: #eab243; -} -.short-summary .no-views .item-count { - color: #b1b5b6; -} -.short-summary .some-views .item-count { - color: #d33f00; -} -.short-summary .accepted .item-count { - background: url(../images/accept.png) no-repeat top right; - display: block; - text-align: center; - width: 40px; - color: #eab243; -} -.short-summary .some-favorites .item-count { - background: #338333; - color: #d0f5a9; -} -.short-summary .no-favorites .item-count { - background: #eab243; - color: yellow; -} -/* ----- Question list Paginator , check main_content/pager.html and macros/utils_macros.html----- */ -.evenMore { - font-size: 13px; - color: #707070; - padding: 15px 0px 10px 0px; - clear: both; -} -.evenMore a { - text-decoration: underline; - color: #1b79bd; -} -.pager { - margin-top: 10px; - margin-bottom: 16px; -} -.pagesize { - margin-top: 10px; - margin-bottom: 16px; - float: right; -} -.paginator { - padding: 5px 0 10px 0; - font-size: 13px; - margin-bottom: 10px; -} -.paginator .prev a, -.paginator .prev a:visited, -.paginator .next a, -.paginator .next a:visited { - background-color: #fff; - color: #777; - padding: 2px 4px 3px 4px; -} -.paginator a { - color: #7ea9b3; -} -.paginator .prev { - margin-right: .5em; -} -.paginator .next { - margin-left: .5em; -} -.paginator .page a, .paginator .page a:visited, .paginator .curr { - padding: .25em; - background-color: #fff; - margin: 0em .25em; - color: #ff; -} -.paginator .curr { - background-color: #8ebcc7; - color: #fff; - font-weight: bold; -} -.paginator .next a, .paginator .prev a { - color: #7ea9b3; -} -.paginator .page a:hover, -.paginator .curr a:hover, -.paginator .prev a:hover, -.paginator .next a:hover { - color: #8C8C8C; - background-color: #E1E1E1; - text-decoration: none; -} -.paginator .text { - color: #777; - padding: .3em; -} -.paginator .paginator-container-left { - padding: 5px 0 10px 0; -} -/* ----- Tags Styles ----- */ -/* tag formatting is also copy-pasted in template - because it must be the same in the emails - askbot/models/__init__.py:format_instant_notification_email() -*/ -/* tag cloud */ -.tag-size-1 { - font-size: 12px; -} -.tag-size-2 { - font-size: 13px; -} -.tag-size-3 { - font-size: 14px; -} -.tag-size-4 { - font-size: 15px; -} -.tag-size-5 { - font-size: 16px; -} -.tag-size-6 { - font-size: 17px; -} -.tag-size-7 { - font-size: 18px; -} -.tag-size-8 { - font-size: 19px; -} -.tag-size-9 { - font-size: 20px; -} -.tag-size-10 { - font-size: 21px; -} -ul.tags, ul.tags.marked-tags, ul#related-tags { - list-style: none; - margin: 0; - padding: 0; - line-height: 170%; - display: block; -} -ul.tags li { - float: left; - display: block; - margin: 0 8px 0 0; - padding: 0; - height: 20px; -} -.wildcard-tags { - clear: both; -} -ul.tags.marked-tags li, .wildcard-tags ul.tags li { - margin-bottom: 5px; -} -#tagSelector div.inputs { - clear: both; - float: none; - margin-bottom: 10px; -} -.tags-page ul.tags li, ul#ab-user-tags li { - width: 160px; - margin: 5px; -} -ul#related-tags li { - margin: 0 5px 8px 0; - float: left; - clear: left; -} -/* .tag-left and .tag-right are for the sliding doors decoration of tags */ -.tag-left { - cursor: pointer; - display: block; - float: left; - height: 17px; - margin: 0 5px 0 0; - padding: 0; - -webkit-box-shadow: 0px 0px 5px #d3d6d7; - -moz-box-shadow: 0px 0px 5px #d3d6d7; - box-shadow: 0px 0px 5px #d3d6d7; -} -.tag-right { - background: #f3f6f6; - border: #fff 1px solid ; - border-top: #fff 2px solid; - outline: #cfdbdb 1px solid; - /* .box-shadow(0px,1px,0px,#88a8a8);*/ - - display: block; - float: left; - height: 17px; - line-height: 17px; - font-weight: normal; - font-size: 11px; - padding: 0px 8px 0px 8px; - text-decoration: none; - text-align: center; - white-space: nowrap; - vertical-align: middle; - font-family: Arial; - color: #717179; -} -.deletable-tag { - margin-right: 3px; - white-space: nowrap; - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-bottomright: 4px; - -webkit-border-bottom-right-radius: 4px; - -webkit-border-top-right-radius: 4px; -} -.tags a.tag-right, .tags span.tag-right { - color: #585858; - text-decoration: none; -} -.tags a:hover { - color: #1A1A1A; -} -.users-page h1, .tags-page h1 { - float: left; -} -.main-page h1 { - margin-right: 5px; -} -.delete-icon { - margin-top: -1px; - float: left; - height: 21px; - width: 18px; - display: block; - line-height: 20px; - text-align: center; - background: #bbcdcd; - cursor: default; - color: #fff; - border-top: #cfdbdb 1px solid; - font-family: Arial; - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-bottomright: 4px; - -webkit-border-bottom-right-radius: 4px; - -webkit-border-top-right-radius: 4px; - text-shadow: 0px 1px 0px #7ea0a0; - -moz-text-shadow: 0px 1px 0px #7ea0a0; - -webkit-text-shadow: 0px 1px 0px #7ea0a0; -} -.delete-icon:hover { - background: #b32f2f; -} -.tag-number { - font-weight: normal; - float: left; - font-size: 16px; - color: #5d5d5d; -} -.badges .tag-number { - float: none; - display: inline; - padding-right: 15px; -} -/* ----- Ask and Edit Question Form template----- */ -.section-title { - color: #7ea9b3; - font-family: 'Yanone Kaffeesatz', sans-serif; - font-weight: bold; - font-size: 24px; -} -#fmask { - margin-bottom: 30px; - width: 100%; -} -#askFormBar { - display: inline-block; - padding: 4px 7px 5px 0px; - margin-top: 0px; -} -#askFormBar p { - margin: 0 0 5px 0; - font-size: 14px; - color: #525252; - line-height: 1.4; -} -#askFormBar .questionTitleInput { - font-size: 24px; - line-height: 24px; - height: 36px; - margin: 0px; - padding: 0px 0 0 5px; - border: #cce6ec 3px solid; - width: 725px; -} -.ask-page div#question-list, .edit-question-page div#question-list { - float: none; - border-bottom: #f0f0ec 1px solid; - float: left; - margin-bottom: 10px; -} -.ask-page div#question-list a, .edit-question-page div#question-list a { - line-height: 30px; -} -.ask-page div#question-list h2, .edit-question-page div#question-list h2 { - font-size: 13px; - padding-bottom: 0; - color: #1b79bd; - border-top: #f0f0ec 1px solid; - border-left: #f0f0ec 1px solid; - height: 30px; - line-height: 30px; - font-weight: normal; -} -.ask-page div#question-list span, .edit-question-page div#question-list span { - width: 28px; - height: 26px; - line-height: 26px; - text-align: center; - margin-right: 10px; - float: left; - display: block; - color: #fff; - background: #b8d0d5; - border-radius: 3px; - -ms-border-radius: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - -khtml-border-radius: 3px; -} -.ask-page label, .edit-question-page label { - color: #525252; - font-size: 13px; -} -.ask-page #id_tags, .edit-question-page #id_tags { - border: #cce6ec 3px solid; - height: 25px; - padding-left: 5px; - width: 395px; - font-size: 14px; -} -.title-desc { - color: #707070; - font-size: 13px; -} -#fmanswer input.submit, .ask-page input.submit, .edit-question-page input.submit { - float: left; - background: url(../images/medium-button.png) top repeat-x; - height: 34px; - border: 0; - font-family: 'Yanone Kaffeesatz', sans-serif; - color: #4a757f; - font-weight: normal; - font-size: 21px; - margin-top: 3px; - border-radius: 4px; - -ms-border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - -khtml-border-radius: 4px; - -webkit-box-shadow: 1px 1px 2px #636363; - -moz-box-shadow: 1px 1px 2px #636363; - box-shadow: 1px 1px 2px #636363; - margin-right: 7px; -} -#fmanswer input.submit:hover, .ask-page input.submit:hover, .edit-question-page input.submit:hover { - text-decoration: none; - background: url(../images/medium-button.png) bottom repeat-x; - text-shadow: 0px 1px 0px #c6d9dd; - -moz-text-shadow: 0px 1px 0px #c6d9dd; - -webkit-text-shadow: 0px 1px 0px #c6d9dd; -} -#editor { - /*adjustment for editor preview*/ - - font-size: 100%; - min-height: 200px; - line-height: 18px; - margin: 0; - border-left: #cce6ec 3px solid; - border-bottom: #cce6ec 3px solid; - border-right: #cce6ec 3px solid; - border-top: 0; - padding: 10px; - margin-bottom: 10px; - width: 717px; -} -#id_title { - width: 100%; -} -.wmd-preview { - margin: 3px 0 5px 0; - padding: 6px; - background-color: #F5F5F5; - min-height: 20px; - overflow: auto; - font-size: 13px; - font-family: Arial; -} -.wmd-preview p { - margin-bottom: 14px; - line-height: 1.4; - font-size: 14px; -} -.wmd-preview pre { - background-color: #E7F1F8; -} -.wmd-preview blockquote { - background-color: #eee; -} -.wmd-preview IMG { - max-width: 600px; -} -.preview-toggle { - width: 100%; - color: #b6a475; - /*letter-spacing:1px;*/ - - text-align: left; -} -.preview-toggle span:hover { - cursor: pointer; -} -.after-editor { - margin-top: 15px; - margin-bottom: 15px; -} -.checkbox { - margin-left: 5px; - font-weight: normal; - cursor: help; -} -.question-options { - margin-top: 1px; - color: #666; - line-height: 13px; - margin-bottom: 5px; -} -.question-options label { - vertical-align: text-bottom; -} -.edit-content-html { - border-top: 1px dotted #D8D2A9; - border-bottom: 1px dotted #D8D2A9; - margin: 5px 0 5px 0; -} -.edit-question-page, #fmedit, .wmd-preview { - color: #525252; -} -.edit-question-page #id_revision, #fmedit #id_revision, .wmd-preview #id_revision { - font-size: 14px; - margin-top: 5px; - margin-bottom: 5px; -} -.edit-question-page #id_title, #fmedit #id_title, .wmd-preview #id_title { - font-size: 24px; - line-height: 24px; - height: 36px; - margin: 0px; - padding: 0px 0 0 5px; - border: #cce6ec 3px solid; - width: 725px; - margin-bottom: 10px; -} -.edit-question-page #id_summary, #fmedit #id_summary, .wmd-preview #id_summary { - border: #cce6ec 3px solid; - height: 25px; - padding-left: 5px; - width: 395px; - font-size: 14px; -} -.edit-question-page .title-desc, #fmedit .title-desc, .wmd-preview .title-desc { - margin-bottom: 10px; -} -/* ----- Question template ----- */ -.question-page h1 { - padding-top: 0px; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -.question-page h1 a { - color: #464646; - font-size: 30px; - font-weight: normal; - line-height: 1; -} -.question-page p.rss { - float: none; - clear: both; - padding: 3px 0 0 23px; - font-size: 15px; - width: 110px; - background-position: center left; - margin-left: 0px !important; -} -.question-page p.rss a { - font-family: 'Yanone Kaffeesatz', sans-serif; - vertical-align: top; -} -.question-page .question-content { - float: right; - width: 682px; - margin-bottom: 10px; -} -.question-page #question-table { - float: left; - border-top: #f0f0f0 1px solid; -} -.question-page #question-table, .question-page .answer-table { - margin: 6px 0 6px 0; - border-spacing: 0px; - /* width: 670px; */ - padding-right: 10px; -} -.question-page .answer-table { - margin-top: 0px; - border-bottom: 1px solid #D4D4D4; - /* float: right; */ -} -.question-page .answer-table td, .question-page #question-table td { - width: 20px; - vertical-align: top; -} -.question-page .question-body, .question-page .answer-body { - overflow: auto; - margin-top: 10px; - font-family: Arial; - color: #4b4b4b; -} -.question-page .question-body p, .question-page .answer-body p { - margin-bottom: 14px; - line-height: 1.4; - font-size: 14px; - padding: 0px 5px 5px 0px; -} -.question-page .question-body a, .question-page .answer-body a { - color: #1b79bd; -} -.question-page .question-body li, .question-page .answer-body li { - margin-bottom: 7px; -} -.question-page .question-body IMG, .question-page .answer-body IMG { - max-width: 600px; -} -.question-page .post-update-info-container { - float: right; - width: 175px; -} -.question-page .post-update-info { - background: #ffffff url(../images/background-user-info.png) repeat-x bottom; - float: right; - font-size: 9px; - font-family: Arial; - width: 158px; - padding: 4px; - margin: 0px 0px 5px 5px; - line-height: 14px; - border-radius: 4px; - -ms-border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - -khtml-border-radius: 4px; - -webkit-box-shadow: 0px 2px 1px #bfbfbf; - -moz-box-shadow: 0px 2px 1px #bfbfbf; - box-shadow: 0px 2px 1px #bfbfbf; -} -.question-page .post-update-info p { - line-height: 13px; - font-size: 11px; - margin: 0 0 2px 1px; - padding: 0; -} -.question-page .post-update-info a { - color: #444; -} -.question-page .post-update-info .gravatar { - float: left; - margin-right: 4px; -} -.question-page .post-update-info p.tip { - color: #444; - line-height: 13px; - font-size: 10px; -} -.question-page .post-controls { - font-size: 11px; - line-height: 12px; - min-width: 200px; - padding-left: 5px; - text-align: right; - clear: left; - float: right; - margin-top: 10px; - margin-bottom: 8px; -} -.question-page .post-controls a { - color: #777; - padding: 0px 3px 3px 22px; - cursor: pointer; - border: none; - font-size: 12px; - font-family: Arial; - text-decoration: none; - height: 18px; - display: block; - float: right; - line-height: 18px; - margin-top: -2px; - margin-left: 4px; -} -.question-page .post-controls a:hover { - background-color: #f5f0c9; - border-radius: 3px; - -ms-border-radius: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - -khtml-border-radius: 3px; -} -.question-page .post-controls .sep { - color: #ccc; - float: right; - height: 18px; - font-size: 18px; -} -.question-page .post-controls .question-delete, .question-page .answer-controls .question-delete { - background: url(../images/delete.png) no-repeat center left; - padding-left: 16px; -} -.question-page .post-controls .question-flag, .question-page .answer-controls .question-flag { - background: url(../images/flag.png) no-repeat center left; -} -.question-page .post-controls .question-edit, .question-page .answer-controls .question-edit { - background: url(../images/edit2.png) no-repeat center left; -} -.question-page .post-controls .question-retag, .question-page .answer-controls .question-retag { - background: url(../images/retag.png) no-repeat center left; -} -.question-page .post-controls .question-close, .question-page .answer-controls .question-close { - background: url(../images/close.png) no-repeat center left; -} -.question-page .post-controls .permant-link, .question-page .answer-controls .permant-link { - background: url(../images/link.png) no-repeat center left; -} -.question-page .tabBar { - width: 100%; -} -.question-page #questionCount { - float: left; - font-family: 'Yanone Kaffeesatz', sans-serif; - line-height: 15px; -} -.question-page .question-img-upvote, -.question-page .question-img-downvote, -.question-page .answer-img-upvote, -.question-page .answer-img-downvote { - width: 25px; - height: 20px; - cursor: pointer; -} -.question-page .question-img-upvote, .question-page .answer-img-upvote { - background: url(../images/vote-arrow-up-new.png) no-repeat; -} -.question-page .question-img-downvote, .question-page .answer-img-downvote { - background: url(../images/vote-arrow-down-new.png) no-repeat; -} -.question-page .question-img-upvote:hover, -.question-page .question-img-upvote.on, -.question-page .answer-img-upvote:hover, -.question-page .answer-img-upvote.on { - background: url(../images/vote-arrow-up-on-new.png) no-repeat; -} -.question-page .question-img-downvote:hover, -.question-page .question-img-downvote.on, -.question-page .answer-img-downvote:hover, -.question-page .answer-img-downvote.on { - background: url(../images/vote-arrow-down-on-new.png) no-repeat; -} -.question-page #fmanswer_button { - margin: 8px 0px ; -} -.question-page .question-img-favorite:hover { - background: url(../images/vote-favorite-on.png); -} -.question-page div.comments { - padding: 0; -} -.question-page #comment-title { - font-weight: bold; - font-size: 23px; - color: #7ea9b3; - width: 200px; - float: left; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -.question-page .comments { - font-size: 12px; - clear: both; - /* A small hack to solve 1px problem on webkit browsers */ - -} -.question-page .comments div.controls { - clear: both; - float: left; - width: 100%; - margin: 3px 0 20px 5px; -} -.question-page .comments .controls a { - color: #988e4c; - padding: 0 3px 2px 22px; - font-family: Arial; - font-size: 13px; - background: url(../images/comment.png) no-repeat center left; -} -.question-page .comments .controls a:hover { - background-color: #f5f0c9; - text-decoration: none; -} -.question-page .comments .button { - color: #988e4c; - font-size: 11px; - padding: 3px; - cursor: pointer; -} -.question-page .comments a { - background-color: inherit; - color: #1b79bd; - padding: 0; -} -.question-page .comments form.post-comments { - margin: 3px 26px 0 42px; -} -.question-page .comments form.post-comments textarea { - font-size: 13px; - line-height: 1.3; -} -.question-page .comments textarea { - height: 42px; - width: 100%; - margin: 7px 0 5px 1px; - font-family: Arial; - outline: none; - overflow: auto; - font-size: 12px; - line-height: 140%; - padding-left: 2px; - padding-top: 3px; - border: #cce6ec 3px solid; -} -@media screen and (-webkit-min-device-pixel-ratio:0) { - textarea { - padding-left: 3px !important; - } -} -.question-page .comments input { - margin-left: 10px; - margin-top: 1px; - vertical-align: top; - width: 100px; -} -.question-page .comments button { - background: url(../images/small-button-blue.png) repeat-x top; - border: 0; - color: #4a757f; - font-family: Arial; - font-size: 13px; - width: 100px; - font-weight: bold; - height: 27px; - line-height: 25px; - margin-bottom: 5px; - cursor: pointer; - border-radius: 4px; - -ms-border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - -khtml-border-radius: 4px; - text-shadow: 0px 1px 0px #e6f6fa; - -moz-text-shadow: 0px 1px 0px #e6f6fa; - -webkit-text-shadow: 0px 1px 0px #e6f6fa; - -webkit-box-shadow: 1px 1px 2px #808080; - -moz-box-shadow: 1px 1px 2px #808080; - box-shadow: 1px 1px 2px #808080; -} -.question-page .comments button:hover { - background: url(../images/small-button-blue.png) bottom repeat-x; - text-shadow: 0px 1px 0px #c6d9dd; - -moz-text-shadow: 0px 1px 0px #c6d9dd; - -webkit-text-shadow: 0px 1px 0px #c6d9dd; -} -.question-page .comments .counter { - display: inline-block; - width: 245px; - float: right; - color: #b6a475 !important; - vertical-align: top; - font-family: Arial; - float: right; - text-align: right; -} -.question-page .comments .comment { - border-bottom: 1px solid #edeeeb; - clear: both; - margin: 0; - margin-top: 8px; - padding-bottom: 4px; - overflow: auto; - font-family: Arial; - font-size: 11px; - min-height: 25px; - background: #ffffff url(../images/comment-background.png) bottom repeat-x; - border-radius: 5px; - -ms-border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - -khtml-border-radius: 5px; -} -.question-page .comments div.comment:hover { - background-color: #efefef; -} -.question-page .comments a.author { - background-color: inherit; - color: #1b79bd; - padding: 0; -} -.question-page .comments a.author:hover { - text-decoration: underline; -} -.question-page .comments span.delete-icon { - background: url(../images/close-small.png) no-repeat; - border: 0; - width: 14px; - height: 14px; -} -.question-page .comments span.delete-icon:hover { - border: #BC564B 2px solid; - border-radius: 10px; - -ms-border-radius: 10px; - -moz-border-radius: 10px; - -webkit-border-radius: 10px; - -khtml-border-radius: 10px; - margin: -3px 0px 0px -2px; -} -.question-page .comments .content { - margin-bottom: 7px; -} -.question-page .comments .comment-votes { - float: left; - width: 37px; - line-height: 130%; - padding: 6px 5px 6px 3px; -} -.question-page .comments .comment-body { - line-height: 1.3; - margin: 3px 26px 0 46px; - padding: 5px 3px; - color: #666; - font-size: 13px; -} -.question-page .comments .comment-body .edit { - padding-left: 6px; -} -.question-page .comments .comment-body p { - font-size: 13px; - line-height: 1.3; - margin-bottom: 3px; - padding: 0; -} -.question-page .comments .comment-delete { - float: right; - width: 14px; - line-height: 130%; - padding: 8px 6px; -} -.question-page .comments .upvote { - margin: 0px; - padding-right: 17px; - padding-top: 2px; - text-align: right; - height: 20px; - font-size: 13px; - font-weight: bold; - color: #777; -} -.question-page .comments .upvote.upvoted { - color: #d64000; -} -.question-page .comments .upvote.hover { - background: url(../images/go-up-grey.png) no-repeat; - background-position: right 1px; -} -.question-page .comments .upvote:hover { - background: url(../images/go-up-orange.png) no-repeat; - background-position: right 1px; -} -.question-page .comments .help-text { - float: right; - text-align: right; - color: gray; - margin-bottom: 0px; - margin-top: 0px; - line-height: 50%; -} -.question-page #questionTools { - font-size: 22px; - margin-top: 11px; - text-align: left; -} -.question-page .question-status { - margin-top: 10px; - margin-bottom: 15px; - padding: 20px; - background-color: #fef7cc; - text-align: center; - border: #e1c04a 1px solid; -} -.question-page .question-status h3 { - font-size: 20px; - color: #707070; - font-weight: normal; -} -.question-page .vote-buttons { - float: left; - text-align: center; - padding-top: 2px; - margin: 10px 10px 0px 3px; -} -.question-page .vote-buttons IMG { - cursor: pointer; -} -.question-page .vote-number { - font-family: 'Yanone Kaffeesatz', sans-serif; - padding: 0px 0 5px 0; - font-size: 25px; - font-weight: bold; - color: #777; -} -.question-page .vote-buttons .notify-sidebar { - text-align: left; - width: 120px; -} -.question-page .vote-buttons .notify-sidebar label { - vertical-align: top; -} -.question-page .tabBar-answer { - margin-bottom: 15px; - padding-left: 7px; - width: 723px; - margin-top: 10px; -} -.question-page .answer .vote-buttons { - float: left; -} -.question-page .accepted-answer { - background-color: #f7fecc; - border-bottom-color: #9BD59B; -} -.question-page .accepted-answer .vote-buttons { - width: 27px; - margin-right: 10px; - margin-top: 10px; -} -.question-page .answer .post-update-info a { - color: #444444; -} -.question-page .answered { - background: #CCC; - color: #999; -} -.question-page .answered-accepted { - background: #DCDCDC; - color: #763333; -} -.question-page .answered-accepted strong { - color: #E1E818; -} -.question-page .answered-by-owner { - background: #F1F1FF; -} -.question-page .answered-by-owner .comments .button { - background-color: #E6ECFF; -} -.question-page .answered-by-owner .comments { - background-color: #E6ECFF; -} -.question-page .answered-by-owner .vote-buttons { - margin-right: 10px; -} -.question-page .answer-img-accept:hover { - background: url(../images/vote-accepted-on.png); -} -.question-page .answer-body a { - color: #1b79bd; -} -.question-page .answer-body li { - margin-bottom: 0.7em; -} -.question-page #fmanswer { - color: #707070; - line-height: 1.2; - margin-top: 10px; -} -.question-page #fmanswer h2 { - font-family: 'Yanone Kaffeesatz', sans-serif; - color: #7ea9b3; - font-size: 24px; -} -.question-page #fmanswer label { - font-size: 13px; -} -.question-page .message { - padding: 5px; - margin: 0px 0 10px 0; -} -.facebook-share.icon, -.twitter-share.icon, -.linkedin-share.icon, -.identica-share.icon { - background: url(../images/socialsprite.png) no-repeat; - display: block; - text-indent: -100em; - height: 25px; - width: 25px; - margin-bottom: 3px; -} -.facebook-share.icon:hover, -.twitter-share.icon:hover, -.linkedin-share.icon:hover, -.identica-share.icon:hover { - opacity: 0.8; - filter: alpha(opacity=80); -} -.facebook-share.icon { - background-position: -26px 0px; -} -.identica-share.icon { - background-position: -78px 0px; -} -.twitter-share.icon { - margin-top: 10px; - background-position: 0px 0px; -} -.linkedin-share.icon { - background-position: -52px 0px; -} -/* -----Content pages, Login, About, FAQ, Users----- */ -.openid-signin, -.meta, -.users-page, -.user-profile-edit-page { - font-size: 13px; - line-height: 1.3; - color: #525252; -} -.openid-signin p, -.meta p, -.users-page p, -.user-profile-edit-page p { - font-size: 13px; - color: #707070; - line-height: 1.3; - font-family: Arial; - color: #525252; - margin-bottom: 12px; -} -.openid-signin h2, -.meta h2, -.users-page h2, -.user-profile-edit-page h2 { - color: #525252; - padding-left: 0px; - font-size: 16px; -} -.openid-signin form, -.meta form, -.users-page form, -.user-profile-edit-page form, -.user-profile-page form { - margin-bottom: 15px; -} -.openid-signin input[type="text"], -.meta input[type="text"], -.users-page input[type="text"], -.user-profile-edit-page input[type="text"], -.user-profile-page input[type="text"], -.openid-signin input[type="password"], -.meta input[type="password"], -.users-page input[type="password"], -.user-profile-edit-page input[type="password"], -.user-profile-page input[type="password"], -.openid-signin select, -.meta select, -.users-page select, -.user-profile-edit-page select, -.user-profile-page select { - border: #cce6ec 3px solid; - height: 25px; - padding-left: 5px; - width: 395px; - font-size: 14px; -} -.openid-signin select, -.meta select, -.users-page select, -.user-profile-edit-page select, -.user-profile-page select { - width: 405px; - height: 30px; -} -.openid-signin textarea, -.meta textarea, -.users-page textarea, -.user-profile-edit-page textarea, -.user-profile-page textarea { - border: #cce6ec 3px solid; - padding-left: 5px; - padding-top: 5px; - width: 395px; - font-size: 14px; -} -.openid-signin input.submit, -.meta input.submit, -.users-page input.submit, -.user-profile-edit-page input.submit, -.user-profile-page input.submit { - background: url(../images/small-button-blue.png) repeat-x top; - border: 0; - color: #4a757f; - font-weight: bold; - font-size: 13px; - font-family: Arial; - height: 26px; - margin: 5px 0px; - width: 100px; - cursor: pointer; - border-radius: 4px; - -ms-border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - -khtml-border-radius: 4px; - text-shadow: 0px 1px 0px #e6f6fa; - -moz-text-shadow: 0px 1px 0px #e6f6fa; - -webkit-text-shadow: 0px 1px 0px #e6f6fa; - -webkit-box-shadow: 1px 1px 2px #808080; - -moz-box-shadow: 1px 1px 2px #808080; - box-shadow: 1px 1px 2px #808080; -} -.openid-signin input.submit:hover, -.meta input.submit:hover, -.users-page input.submit:hover, -.user-profile-edit-page input.submit:hover, -.user-profile-page input.submit:hover { - background: url(../images/small-button-blue.png) repeat-x bottom; - text-decoration: none; -} -.openid-signin .cancel, -.meta .cancel, -.users-page .cancel, -.user-profile-edit-page .cancel, -.user-profile-page .cancel { - background: url(../images/small-button-cancel.png) repeat-x top !important; - color: #525252 !important; -} -.openid-signin .cancel:hover, -.meta .cancel:hover, -.users-page .cancel:hover, -.user-profile-edit-page .cancel:hover, -.user-profile-page .cancel:hover { - background: url(../images/small-button-cancel.png) repeat-x bottom !important; -} -#email-input-fs, -#local_login_buttons, -#password-fs, -#openid-fs { - margin-top: 10px; -} -#email-input-fs #id_email, -#local_login_buttons #id_email, -#password-fs #id_email, -#openid-fs #id_email, -#email-input-fs #id_username, -#local_login_buttons #id_username, -#password-fs #id_username, -#openid-fs #id_username, -#email-input-fs #id_password, -#local_login_buttons #id_password, -#password-fs #id_password, -#openid-fs #id_password { - font-size: 12px; - line-height: 20px; - height: 20px; - margin: 0px; - padding: 0px 0 0 5px; - border: #cce6ec 3px solid; - width: 200px; -} -#email-input-fs .submit-b, -#local_login_buttons .submit-b, -#password-fs .submit-b, -#openid-fs .submit-b { - background: url(../images/small-button-blue.png) repeat-x top; - border: 0; - color: #4a757f; - font-weight: bold; - font-size: 13px; - font-family: Arial; - height: 24px; - margin-top: -2px; - padding-left: 10px; - padding-right: 10px; - cursor: pointer; - border-radius: 4px; - -ms-border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - -khtml-border-radius: 4px; - text-shadow: 0px 1px 0px #e6f6fa; - -moz-text-shadow: 0px 1px 0px #e6f6fa; - -webkit-text-shadow: 0px 1px 0px #e6f6fa; - -webkit-box-shadow: 1px 1px 2px #808080; - -moz-box-shadow: 1px 1px 2px #808080; - box-shadow: 1px 1px 2px #808080; -} -#email-input-fs .submit-b:hover, -#local_login_buttons .submit-b:hover, -#password-fs .submit-b:hover, -#openid-fs .submit-b:hover { - background: url(../images/small-button-blue.png) repeat-x bottom; -} -.openid-input { - background: url(../images/openid.gif) no-repeat; - padding-left: 15px; - cursor: pointer; -} -.openid-login-input { - background-position: center left; - background: url(../images/openid.gif) no-repeat 0% 50%; - padding: 5px 5px 5px 15px; - cursor: pointer; - font-family: Trebuchet MS; - font-weight: 300; - font-size: 150%; - width: 500px; -} -.openid-login-submit { - height: 40px; - width: 80px; - line-height: 40px; - cursor: pointer; - border: 1px solid #777; - font-weight: bold; - font-size: 120%; -} -/* People page */ -.tabBar-user { - width: 375px; -} -.user { - padding: 5px; - line-height: 140%; - width: 166px; - border: #eee 1px solid; - margin-bottom: 5px; - border-radius: 3px; - -ms-border-radius: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - -khtml-border-radius: 3px; -} -.user .user-micro-info { - color: #525252; -} -.user ul { - margin: 0; - list-style-type: none; -} -.user .thumb { - clear: both; - float: left; - margin-right: 4px; - display: inline; -} -/* tags page */ -.tabBar-tags { - width: 270px; - margin-bottom: 15px; -} -/* badges page */ -a.medal { - font-size: 17px; - line-height: 250%; - margin-right: 5px; - color: #333; - text-decoration: none; - background: url(../images/medala.gif) no-repeat; - border-left: 1px solid #EEE; - border-top: 1px solid #EEE; - border-bottom: 1px solid #CCC; - border-right: 1px solid #CCC; - padding: 4px 12px 4px 6px; -} -a:hover.medal { - color: #333; - text-decoration: none; - background: url(../images/medala_on.gif) no-repeat; - border-left: 1px solid #E7E296; - border-top: 1px solid #E7E296; - border-bottom: 1px solid #D1CA3D; - border-right: 1px solid #D1CA3D; -} -#award-list .user { - float: left; - margin: 5px; -} -/* profile page */ -.tabBar-profile { - width: 100%; - margin-bottom: 15px; - float: left; -} -.user-profile-page { - font-size: 13px; - color: #525252; -} -.user-profile-page p { - font-size: 13px; - line-height: 1.3; - color: #525252; -} -.user-profile-page .avatar img { - border: #eee 1px solid; - padding: 5px; -} -.user-profile-page h2 { - padding: 10px 0px 10px 0px; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -.user-details { - font-size: 13px; -} -.user-details h3 { - font-size: 16px; -} -.user-about { - background-color: #EEEEEE; - height: 200px; - line-height: 20px; - overflow: auto; - padding: 10px; - width: 90%; -} -.user-about p { - font-size: 13px; -} -.follow-toggle, .submit { - border: 0 !important; - color: #4a757f; - font-weight: bold; - font-size: 12px; - height: 26px; - line-height: 26px; - margin-top: -2px; - font-size: 15px; - cursor: pointer; - font-family: 'Yanone Kaffeesatz', sans-serif; - background: url(../images/small-button-blue.png) repeat-x top; - border-radius: 4px; - -ms-border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - -khtml-border-radius: 4px; - text-shadow: 0px 1px 0px #e6f6fa; - -moz-text-shadow: 0px 1px 0px #e6f6fa; - -webkit-text-shadow: 0px 1px 0px #e6f6fa; - -webkit-box-shadow: 1px 1px 2px #808080; - -moz-box-shadow: 1px 1px 2px #808080; - box-shadow: 1px 1px 2px #808080; -} -.follow-toggle:hover, .submit:hover { - background: url(../images/small-button-blue.png) repeat-x bottom; - text-decoration: none !important; -} -.follow-toggle .follow { - font-color: #000; - font-style: normal; -} -.follow-toggle .unfollow div.unfollow-red { - display: none; -} -.follow-toggle .unfollow:hover div.unfollow-red { - display: inline; - color: #fff; - font-weight: bold; - color: #A05736; -} -.follow-toggle .unfollow:hover div.unfollow-green { - display: none; -} -.count { - font-family: 'Yanone Kaffeesatz', sans-serif; - font-size: 200%; - font-weight: 700; - color: #777777; -} -.scoreNumber { - font-family: 'Yanone Kaffeesatz', sans-serif; - font-size: 35px; - font-weight: 800; - color: #777; - line-height: 40px; - /*letter-spacing:0px*/ - - margin-top: 3px; -} -.vote-count { - font-family: Arial; - font-size: 160%; - font-weight: 700; - color: #777; -} -.answer-summary { - display: block; - clear: both; - padding: 3px; -} -.answer-votes { - background-color: #EEEEEE; - color: #555555; - float: left; - font-family: Arial; - font-size: 15px; - font-weight: bold; - height: 17px; - padding: 2px 4px 5px; - text-align: center; - text-decoration: none; - width: 20px; - margin-right: 10px; - border-radius: 4px; - -ms-border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - -khtml-border-radius: 4px; -} -.karma-summary { - padding: 5px; - font-size: 13px; -} -.karma-summary h3 { - text-align: center; - font-weight: bold; - padding: 5px; -} -.karma-diagram { - width: 477px; - height: 300px; - float: left; - margin-right: 10px; -} -.karma-details { - float: right; - width: 450px; - height: 250px; - overflow-y: auto; - word-wrap: break-word; -} -.karma-details p { - margin-bottom: 10px; -} -.karma-gained { - font-weight: bold; - background: #eee; - width: 25px; - margin-right: 5px; - color: green; - padding: 3px; - display: block; - float: left; - text-align: center; - border-radius: 3px; - -ms-border-radius: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - -khtml-border-radius: 3px; -} -.karma-lost { - font-weight: bold; - background: #eee; - width: 25px; - color: red; - padding: 3px; - display: block; - margin-right: 5px; - float: left; - text-align: center; - border-radius: 3px; - -ms-border-radius: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - -khtml-border-radius: 3px; -} -.submit-row { - margin-bottom: 10px; -} -/*----- Revision pages ----- */ -.revision { - margin: 10px 0 10px 0; - font-size: 13px; - color: #525252; -} -.revision p { - font-size: 13px; - line-height: 1.3; - color: #525252; -} -.revision h3 { - font-family: 'Yanone Kaffeesatz', sans-serif; - font-size: 21px; - padding-left: 0px; -} -.revision .header { - background-color: #F5F5F5; - padding: 5px; - cursor: pointer; -} -.revision .author { - background-color: #e9f3f5; -} -.revision .summary { - padding: 5px 0 10px 0; -} -.revision .summary span { - background-color: #fde785; - padding: 6px; - border-radius: 4px; - -ms-border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - -khtml-border-radius: 4px; - display: inline; - -webkit-box-shadow: 1px 1px 4px #cfb852; - -moz-box-shadow: 1px 1px 4px #cfb852; - box-shadow: 1px 1px 4px #cfb852; -} -.revision .answerbody { - padding: 10px 0 5px 10px; -} -.revision .revision-mark { - width: 150px; - text-align: left; - display: inline-block; - font-size: 11px; - overflow: hidden; -} -.revision .revision-mark .gravatar { - float: left; - margin-right: 4px; - padding-top: 5px; -} -.revision .revision-number { - font-size: 300%; - font-weight: bold; - font-family: sans-serif; -} -del, del .post-tag { - color: #C34719; -} -ins .post-tag, ins p, ins { - background-color: #E6F0A2; -} -/* ----- Red Popup notification ----- */ -.vote-notification { - z-index: 1; - cursor: pointer; - display: none; - position: absolute; - font-family: Arial; - font-size: 14px; - font-weight: normal; - color: white; - background-color: #8e0000; - text-align: center; - padding-bottom: 10px; - -webkit-box-shadow: 0px 2px 4px #370000; - -moz-box-shadow: 0px 2px 4px #370000; - box-shadow: 0px 2px 4px #370000; - border-radius: 4px; - -ms-border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - -khtml-border-radius: 4px; -} -.vote-notification h3 { - background: url(../images/notification.png) repeat-x top; - padding: 10px 10px 10px 10px; - font-size: 13px; - margin-bottom: 5px; - border-top: #8e0000 1px solid; - color: #fff; - font-weight: normal; - border-top-right-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-topleft: 4px; - -webkit-border-top-left-radius: 4px; - -webkit-border-top-right-radius: 4px; -} -.vote-notification a { - color: #fb7321; - text-decoration: underline; - font-weight: bold; -} -/* ----- Footer links , check blocks/footer.html----- */ -#ground { - width: 100%; - clear: both; - border-top: 1px solid #000; - padding: 6px 0 0 0; - background: #16160f; - font-size: 16px; - font-family: 'Yanone Kaffeesatz', sans-serif; -} -#ground p { - margin-bottom: 0; -} -.footer-links { - color: #EEE; - text-align: left; - width: 500px; - float: left; -} -.footer-links a { - color: #e7e8a8; -} -.powered-link { - width: 500px; - float: left; - text-align: left; -} -.powered-link a { - color: #8ebcc7; -} -.copyright { - color: #616161; - width: 450px; - float: right; - text-align: right; -} -.copyright a { - color: #8ebcc7; -} -.copyright img.license-logo { - margin: 6px 0px 20px 10px; - float: right; -} -.notify-me { - float: left; -} -span.text-counter { - margin-right: 20px; -} -span.form-error { - color: #990000; - font-weight: normal; - margin-left: 5px; -} -ul.errorlist { - margin-bottom: 0; -} -p.form-item { - margin: 0px; -} -.deleted { - background: #F4E7E7 none repeat scroll 0 0; -} -/* openid styles */ -.form-row { - line-height: 25px; -} -table.form-as-table { - margin-top: 5px; -} -table.form-as-table ul { - list-style-type: none; - display: inline; -} -table.form-as-table li { - display: inline; -} -table.form-as-table td { - text-align: right; -} -table.form-as-table th { - text-align: left; - font-weight: normal; -} -table.ab-subscr-form { - width: 45em; -} -table.ab-tag-filter-form { - width: 45em; -} -.submit-row { - line-height: 30px; - padding-top: 10px; - display: block; - clear: both; -} -.errors { - line-height: 20px; - color: red; -} -.error { - color: darkred; - margin: 0; - font-size: 10px; -} -label.retag-error { - color: darkred; - padding-left: 5px; - font-size: 10px; -} -.fieldset { - border: none; - margin-top: 10px; - padding: 10px; -} -span.form-error { - color: #990000; - font-size: 90%; - font-weight: normal; - margin-left: 5px; -} -/* -.favorites-count-off { - color: #919191; - float: left; - text-align: center; -} - -.favorites-count { - color: #D4A849; - float: left; - text-align: center; -} -*/ -/* todo: get rid of this in html */ -.favorites-empty { - width: 32px; - height: 45px; - float: left; -} -.user-info-table { - margin-bottom: 10px; - border-spacing: 0; -} -/* todo: remove this hack? */ -.user-stats-table .narrow { - width: 660px; -} -.narrow .summary h3 { - padding: 0px; - margin: 0px; -} -.relativetime { - font-weight: bold; - text-decoration: none; -} -.narrow .tags { - float: left; -} -/* todo: make these more semantic */ -.user-action-1 { - font-weight: bold; - color: #333; -} -.user-action-2 { - font-weight: bold; - color: #CCC; -} -.user-action-3 { - color: #333; -} -.user-action-4 { - color: #333; -} -.user-action-5 { - color: darkred; -} -.user-action-6 { - color: darkred; -} -.user-action-7 { - color: #333; -} -.user-action-8 { - padding: 3px; - font-weight: bold; - background-color: #CCC; - color: #763333; -} -.revision-summary { - background-color: #FFFE9B; - padding: 2px; -} -.question-title-link a { - font-weight: bold; - color: #0077CC; -} -.answer-title-link a { - color: #333; -} -/* todo: make these more semantic */ -.post-type-1 a { - font-weight: bold; -} -.post-type-3 a { - font-weight: bold; -} -.post-type-5 a { - font-weight: bold; -} -.post-type-2 a { - color: #333; -} -.post-type-4 a { - color: #333; -} -.post-type-6 a { - color: #333; -} -.post-type-8 a { - color: #333; -} -.hilite { - background-color: #ff0; -} -.hilite1 { - background-color: #ff0; -} -.hilite2 { - background-color: #f0f; -} -.hilite3 { - background-color: #0ff; -} -.gold, .badge1 { - color: #FFCC00; -} -.silver, .badge2 { - color: #CCCCCC; -} -.bronze, .badge3 { - color: #CC9933; -} -.score { - font-weight: 800; - color: #333; -} -a.comment { - background: #EEE; - color: #993300; - padding: 5px; -} -a.offensive { - color: #999; -} -.message h1 { - padding-top: 0px; - font-size: 15px; -} -.message p { - margin-bottom: 0px; -} -p.space-above { - margin-top: 10px; -} -.warning { - color: red; -} -button::-moz-focus-inner { - padding: 0; - border: none; -} -.submit { - cursor: pointer; - /*letter-spacing:1px;*/ - - background-color: #D4D0C8; - height: 30px; - border: 1px solid #777777; - /* width:100px; */ - - font-weight: bold; - font-size: 120%; -} -.submit:hover { - text-decoration: underline; -} -.submit.small { - margin-right: 5px; - height: 20px; - font-weight: normal; - font-size: 12px; - padding: 1px 5px; -} -.submit.small:hover { - text-decoration: none; -} -.question-page a.submit { - display: -moz-inline-stack; - display: inline-block; - line-height: 30px; - padding: 0 5px; - *display: inline; -} -.noscript { - position: fixed; - top: 0px; - left: 0px; - width: 100%; - z-index: 100; - padding: 5px 0; - text-align: center; - font-family: sans-serif; - font-size: 120%; - font-weight: Bold; - color: #FFFFFF; - background-color: #AE0000; -} -.big { - font-size: 14px; -} -.strong { - font-weight: bold; -} -.orange { - /* used in django.po */ - - color: #d64000; - font-weight: bold; -} -.grey { - color: #808080; -} -.about div { - padding: 10px 5px 10px 5px; - border-top: 1px dashed #aaaaaa; -} -.highlight { - background-color: #FFF8C6; -} -.nomargin { - margin: 0; -} -.margin-bottom { - margin-bottom: 10px; -} -.margin-top { - margin-top: 10px; -} -.inline-block { - display: inline-block; -} -.action-status { - margin: 0; - border: none; - text-align: center; - line-height: 10px; - font-size: 12px; - padding: 0; -} -.action-status span { - padding: 3px 5px 3px 5px; - background-color: #fff380; - /* nice yellow */ - - font-weight: normal; - -moz-border-radius: 5px; - -khtml-border-radius: 5px; - -webkit-border-radius: 5px; -} -.list-table td { - vertical-align: top; -} -/* these need to go */ -table.form-as-table .errorlist { - display: block; - margin: 0; - padding: 0 0 0 5px; - text-align: left; - font-size: 10px; - color: darkred; -} -table.form-as-table input { - display: inline; - margin-left: 4px; -} -table.form-as-table th { - vertical-align: bottom; - padding-bottom: 4px; -} -.form-row-vertical { - margin-top: 8px; - display: block; -} -.form-row-vertical label { - margin-bottom: 3px; - display: block; -} -/* above stuff needs to go */ -.text-align-right { - text-align: center; -} -ul.form-horizontal-rows { - list-style: none; - margin: 0; -} -ul.form-horizontal-rows li { - position: relative; - height: 40px; -} -ul.form-horizontal-rows label { - display: inline-block; -} -ul.form-horizontal-rows ul.errorlist { - list-style: none; - color: darkred; - font-size: 10px; - line-height: 10px; - position: absolute; - top: 2px; - left: 180px; - text-align: left; - margin: 0; -} -ul.form-horizontal-rows ul.errorlist li { - height: 10px; -} -ul.form-horizontal-rows label { - position: absolute; - left: 0px; - bottom: 6px; - margin: 0px; - line-height: 12px; - font-size: 12px; -} -ul.form-horizontal-rows li input { - position: absolute; - bottom: 0px; - left: 180px; - margin: 0px; -} -.narrow .summary { - float: left; -} -.user-profile-tool-links { - font-weight: bold; - vertical-align: top; -} -ul.post-tags { - margin-left: 3px; -} -ul.post-tags li { - margin-top: 4px; - margin-bottom: 3px; -} -ul.post-retag { - margin-bottom: 0px; - margin-left: 5px; -} -#question-controls .tags { - margin: 0 0 3px 0; -} -#tagSelector { - padding-bottom: 2px; - margin-bottom: 0; -} -#related-tags { - padding-left: 3px; -} -#hideIgnoredTagsControl { - margin: 5px 0 0 0; -} -#hideIgnoredTagsControl label { - font-size: 12px; - color: #666; -} -#hideIgnoredTagsCb { - margin: 0 2px 0 1px; -} -#recaptcha_widget_div { - width: 318px; - float: left; - clear: both; -} -p.signup_p { - margin: 20px 0px 0px 0px; -} -.simple-subscribe-options ul { - list-style: none; - list-style-position: outside; - margin: 0; -} -/* a workaround to set link colors correctly */ -.wmd-preview a { - color: #1b79bd; -} -.wmd-preview li { - margin-bottom: 7px; - font-size: 14px; -} -.search-result-summary { - font-weight: bold; - font-size: 18px; - line-height: 22px; - margin: 0px 0px 0px 0px; - padding: 2px 0 0 0; - float: left; -} -.faq-rep-item { - text-align: right; - padding-right: 5px; -} -.user-info-table .gravatar { - margin: 0; -} -#responses { - clear: both; - line-height: 18px; - margin-bottom: 15px; -} -#responses div.face { - float: left; - text-align: center; - width: 54px; - padding: 3px; - overflow: hidden; -} -.response-parent { - margin-top: 18px; -} -.response-parent strong { - font-size: 20px; -} -.re { - min-height: 57px; - clear: both; - margin-top: 10px; -} -#responses input { - float: left; -} -#re_tools { - margin-bottom: 10px; -} -#re_sections { - margin-bottom: 6px; -} -#re_sections .on { - font-weight: bold; -} -.avatar-page ul { - list-style: none; -} -.avatar-page li { - display: inline; -} -.user-profile-page .avatar p { - margin-bottom: 0px; -} -.user-profile-page .tabBar a#stats { - margin-left: 0; -} -.user-profile-page img.gravatar { - margin: 2px 0 3px 0; -} -.user-profile-page h3 { - padding: 0; - margin-top: -3px; -} -.userList { - font-size: 13px; -} -img.flag { - border: 1px solid #eee; - vertical-align: text-top; -} -.main-page img.flag { - vertical-align: text-bottom; -} -/* Pretty printing styles. Used with prettify.js. */ -a.edit { - padding-left: 3px; - color: #145bff; -} -.str { - color: #080; -} -.kwd { - color: #008; -} -.com { - color: #800; -} -.typ { - color: #606; -} -.lit { - color: #066; -} -.pun { - color: #660; -} -.pln { - color: #000; -} -.tag { - color: #008; -} -/* name conflict here */ -.atn { - color: #606; -} -.atv { - color: #080; -} -.dec { - color: #606; -} -pre.prettyprint { - clear: both; - padding: 3px; - border: 0px solid #888; -} -@media print { - .str { - color: #060; - } - .kwd { - color: #006; - font-weight: bold; - } - .com { - color: #600; - font-style: italic; - } - .typ { - color: #404; - font-weight: bold; - } - .lit { - color: #044; - } - .pun { - color: #440; - } - .pln { - color: #000; - } - .tag { - color: #006; - font-weight: bold; - } - .atn { - color: #404; - } - .atv { - color: #060; - } -} -#leading-sidebar { - float: left; -} - -a.re_expand{ - color: #616161; - text-decoration:none; -} - -a.re_expand .re_content{ - display:none; - margin-left:77px; -} \ No newline at end of file diff --git a/lms/askbot/skins/mitx/media/style/style.less b/lms/askbot/skins/mitx/media/style/style.less deleted file mode 100644 index f7efdc18bb..0000000000 --- a/lms/askbot/skins/mitx/media/style/style.less +++ /dev/null @@ -1,3351 +0,0 @@ -@import url(jquery.autocomplete.css); -@import "lib_style.less"; /* Library of predifined less functions styles */ - -/* ----- General HTML Styles----- */ - -body { - background: #FFF; - font-size: 14px; - line-height: 150%; - margin: 0; - padding: 0; - color: #000; - font-family:@body-font; -} - -div { - margin: 0 auto; - padding: 0; -} - -h1, h2, h3, h4, h5, h6, ul, li, dl, dt, dd, form, img, p { - margin: 0; - padding: 0; - border: none; -} - -label { - vertical-align: middle; -} - -hr { - border: none; - border-top: 1px dashed #ccccce; -} - -input, select { - vertical-align: middle; - font-family: Trebuchet MS, "segoe ui", Helvetica, Tahoma, Verdana, MingLiu, PMingLiu, Arial, sans-serif; - margin-left:0px; -} - -textarea:focus, input:focus{ - outline: none; -} - -iframe { - border: none; -} - -p { - font-size: 14px; - line-height: 140%; - margin-bottom: 6px; -} - -a { - color:@link; - text-decoration: none; - cursor: pointer; -} - -h2 { - font-size: 21px; - padding: 3px 0 3px 5px; -} - -h3 { - font-size: 19px; - padding: 3px 0 3px 5px; -} - -ul { - list-style: disc; - margin-left: 20px; - padding-left: 0px; - margin-bottom: 1em; -} - -ol { - list-style: decimal; - margin-left: 30px; - margin-bottom: 1em; - padding-left: 0px; -} - -td ul { - vertical-align: middle; -} - -li input { - margin: 3px 3px 4px 3px; -} - -pre { - font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace; - font-size: 100%; - margin-bottom: 10px; - /*overflow: auto;*/ - background-color: #F5F5F5; - padding-left: 5px; - padding-top: 5px; - /*width: 671px;*/ - padding-bottom: 20px ! ie7; -} - -code { - font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace; - font-size: 100%; - -} - -blockquote { - margin-bottom: 10px; - margin-right: 15px; - padding: 10px 0px 1px 10px; - background-color: #F5F5F5; -} - -/* http://pathfindersoftware.com/2007/09/developers-note-2/ */ -* html .clearfix, -* html .paginator { - height: 1; - overflow: visible; -} -+ html .clearfix, -+ html .paginator { - min-height: 1%; -} -.clearfix:after, -.paginator:after { - clear: both; - content:"."; - display:block; - height: 0; - visibility: hidden; -} - -.badges a { - color: #763333; - text-decoration: underline; -} - -a:hover { - text-decoration: underline; -} - -.badge-context-toggle.active { - cursor: pointer; - text-decoration: underline; -} - -h1 { - font-size: 24px; - padding: 10px 0 5px 0px; -} - -/* ----- Extra space above for messages ----- */ - -body.user-messages { - margin-top: 2.4em; -} - -/* ----- Custom positions ----- */ - -.left{float:left} -.right{float:right} -.clean{clear:both} -.center{ - margin: 0 auto; - padding: 0; -} - - -/* ----- Notify message bar , check blocks/system_messages.html ----- */ - -.notify { - position: fixed; - top: 0px; - left: 0px; - width: 100%; - z-index: 100; - padding: 0; - text-align: center; - background-color: #f5dd69; - border-top:#fff 1px solid; - font-family:@main-font; - - p.notification { - margin-top: 6px; - margin-bottom: 6px; - font-size: 16px; - color:#424242 - } -} - -#closeNotify { - position: absolute; - right: 5px; - top: 7px; - color: #735005; - text-decoration: none; - line-height: 18px; - .sprites(-6px,-5px); - cursor: pointer; - width:20px; - height:20px; -} - -#closeNotify:hover { - .sprites(-26px,-5px); -} - -/* ----- Header, check blocks/header.html ----- */ - -#header { - margin-top: 0px; - background: @header-color; - font-family:@main-font; -} - -.content-wrapper {/* wrapper positioning class */ - width: 960px; - margin: auto; - position:relative; -} - -#logo img{ - padding: 5px 0px 5px 0px; - height: 75px; - width: auto; - float: left; -} - -#userToolsNav {/* Navigation bar containing login link or user information, check widgets/user_navigation.html*/ - height: 20px; - padding-bottom:5px; - - a { - height: 35px; - text-align: right; - margin-left: 20px; - text-decoration: underline; - color:#d0e296; - font-size:16px; - } - - a:first-child { - margin-left: 0; - } - - a#ab-responses { - margin-left: 3px; - } - - .user-info,.user-micro-info{ - color:#b5b593; - } - - a img { - vertical-align:middle; - margin-bottom:2px; - } - - .user-info a { - margin: 0; - text-decoration: none; - } -} - -#metaNav {/* Top Navigation bar containing links for tags, people and badges, check widgets/header.html */ - float: right;/* for #header.with-logo it is modified */ - - a { - color: #e2e2ae; - padding: 0px 0px 0px 35px; - height: 25px; - line-height: 30px; - margin:5px 0px 0px 10px; - font-size: 18px; - font-weight: 100; - text-decoration: none; - display: block; - float: left; - } - - a:hover { - text-decoration: underline; - } - - a.on { - font-weight:bold; - color: #FFF; - text-decoration: none; - } - - a.special { - font-size: 18px; - color: #B02B2C; - font-weight: bold; - text-decoration: none; - } - - a.special:hover { - text-decoration: underline; - } - - #navTags{ - .sprites(-50px,-5px) - } - - #navUsers{ - .sprites(-125px,-5px) - } - - #navBadges{ - .sprites(-210px,-5px) - } -} - -#header.with-logo #userToolsNav { - position:absolute; - bottom: 0; - right:0px; -} - -#header.without-logo #userToolsNav { - float:left; - margin-top: 7px; -} - -#header.without-logo #metaNav { - margin-bottom: 7px; -} - -#secondaryHeader{ /* Div containing Home button, scope navigation, search form and ask button, check blocks/secondary_header.html */ - height:55px; - background:#e9e9e1; - border-bottom:#d3d3c2 1px solid; - border-top:#fcfcfc 1px solid; - margin-bottom:10px; - font-family:@main-font; - - #homeButton{ - border-right:#afaf9e 1px solid; - .sprites(-6px,-36px); - height:55px; - width:43px; - display:block; - float:left; - } - - #homeButton:hover{ - .sprites(-6px-45,-36px); - } - - #scopeWrapper{ - width:688px; - float:left; - - a{ - display:block; - float:left; - } - - .scope-selector{ - font-size:21px; - color:#5a5a4b; - height:55px; - line-height:55px; - margin-left:24px - } - .on{ - background:url(../images/scopearrow.png) no-repeat center bottom; - } - - .ask-message{ - font-size:24px; - } - } -} - -#searchBar { /* Main search form , check widgets/search_bar.html */ - display:inline-block; - background-color: #fff; - width:412px; - border: 1px solid #c9c9b5; - float:right; - height:42px; - margin:6px 0px 0px 15px; - - .searchInput, .searchInputCancelable{ - font-size: 30px; - height: 40px; - font-weight:300; - background:#FFF; - border:0px; - color:#484848; - padding-left:10px; - font-family:@body-font; - vertical-align: middle; - } - - .searchInput,{ - width: 352px; - } - - .searchInputCancelable { - width: 317px; - } - - .logoutsearch { - width: 337px; - } - - .searchBtn { - font-size: 10px; - color: #666; - background-color: #eee; - height: 42px; - border:#FFF 1px solid; - line-height: 22px; - text-align: center; - float:right; - margin: 0px; - width:48px; - .sprites(-98px,-36px); - cursor:pointer; - } - - .searchBtn:hover { - .sprites(-98px-48,-36px); - } - - .cancelSearchBtn { - font-size: 30px; - color: #ce8888; - background:#fff; - height: 42px; - border:0px; - border-left:#deded0 1px solid; - text-align: center; - width: 35px; - cursor:pointer; - } - - .cancelSearchBtn:hover { - color: #d84040; - } -} - -body.anon { - #searchBar { - width: 500px; - .searchInput { - width: 440px; - } - - .searchInputCancelable { - width: 405px; - } - } -} - - -#askButton{ /* check blocks/secondary_header.html and widgets/ask_button.html*/ - background: url(../images/bigbutton.png) repeat-x bottom; - line-height:44px; - text-align:center; - width:200px; - height:42px; - font-size:23px; - color:@button-label; - margin-top:7px; - float:right; - text-transform:uppercase; - .rounded-corners(5px); - .box-shadow(1px, 1px, 2px, #636363) -} - -#askButton:hover{ - text-decoration:none; - background: url(../images/bigbutton.png) repeat-x top; - .text-shadow(0px, 1px, 0px, #c6d9dd) -} - -/* ----- Content layout, check two_column_body.html or one_column_body.html ----- */ - -#ContentLeft { - width: 730px; - float: left; - position: relative; - padding-bottom:10px; -} - -#ContentRight { - width: 200px; - float: right; - padding: 0 0px 10px 0px; -} - -#ContentFull { - float: left; - width: 960px; -} - -/* ----- Sidebar Widgets Box, check main_page/sidebar.html or question/sidebar.html ----- */ - -.box { - background: #fff; - padding: 4px 0px 10px 0px; - width:200px; - - p { - margin-bottom: 4px; - } - - p.info-box-follow-up-links { - text-align: right; - margin: 0; - } - - h2 { - padding-left: 0; - background:#eceeeb; - height:30px; - line-height:30px; - text-align:right; - font-size:18px !important; - font-weight:normal; - color:#656565; - padding-right:10px; - margin-bottom:10px; - font-family:@main-font; - } - h3{ - color:#4a757f; - font-size:18px; - text-align:left; - font-weight:normal; - font-family:@main-font; - padding-left:0px; - } - .contributorback{ - background: #eceeeb url(../images/contributorsback.png) no-repeat center left; - } - - label { - color: @info-text; - font-size:15px; - display: block; - float: right; - text-align:left; - font-family:@main-font; - width:80px; - margin-right:18px; - } - - #displayTagFilterControl label { /*Especial width just for the display tag filter box in index page*/ - width:160px; - } - - ul { - margin-left: 22px; - } - - li { - list-style-type: disc; - font-size: 13px; - line-height: 20px; - margin-bottom: 10px; - color:@info-text; - } - ul.tags { - list-style: none; - margin: 0; - padding: 0; - line-height: 170%; - display: block; - } - #displayTagFilterControl p label{ - color:@info-text; - font-size:15px; - } - - .inputs{ - #interestingTagInput, #ignoredTagInput{ - width:153px; - padding-left:5px; - border:#c9c9b5 1px solid; - height:25px; - } - #interestingTagAdd, #ignoredTagAdd{ - background:url(../images/small-button-blue.png) repeat-x top; - border:0; - color:@button-label; - font-weight:bold; - font-size:12px; - width:30px; - height:27px; - margin-top:-2px; - cursor:pointer; - .rounded-corners(4px); - .text-shadow(0px,1px,0px,#E6F6FA); - .box-shadow(1px, 1px, 2px, #808080); - - - } - #interestingTagAdd:hover, #ignoredTagAdd:hover{ - background:url(../images/small-button-blue.png) repeat-x bottom; - } - } - - img.gravatar { - margin:1px; - } - -/* widgets for question template */ - - a.followed, a.follow{ - background: url(../images/medium-button.png) top repeat-x; - height:34px; - line-height:34px; - text-align:center; - border:0; - font-family:@main-font; - color:@button-label; - font-weight:normal; - font-size:21px; - margin-top:3px; - - display:block; - width:120px; - text-decoration:none; - .rounded-corners(4px); - .box-shadow(1px, 1px, 2px, #636363); - .center; - } - - a.followed:hover, a.follow:hover{ - text-decoration:none; - background: url(../images/medium-button.png) bottom repeat-x; - .text-shadow(0px, 1px, 0px, #c6d9dd); - } - - a.followed div.unfollow{ - display:none; - } - - a.followed:hover div{ - display:none; - } - a.followed:hover div.unfollow{ - display:inline; - color:#a05736; - } - - .favorite-number { - padding: 5px 0 0 5px; - font-size: 100%; - font-family: Arial; - font-weight: bold; - color: #777; - text-align:center; - } - - /* notify by email box */ - .notify-sidebar #question-subscribe-sidebar { - margin: 7px 0 0 3px; - } -} - -.statsWidget p{ - color:@info-text; - font-size:16px; - border-bottom:#cccccc 1px solid; - font-size:13px; - - strong{ - float:right; - padding-right:10px; - } -} -.questions-related { - word-wrap: break-word; - - p { - line-height: 20px; - padding: 4px 0px 4px 0px; - font-size: 16px; - font-weight:normal; - border-bottom:#cccccc 1px solid; - } - a{ - font-size:13px; - } -} -/* tips and markdown help are widgets for ask template */ - -#tips{ - li{ - color:@info-text; - font-size:13px; - list-style-image: url(../images/tips.png); - } - a{ - font-size:16px; - } -} - -#markdownHelp{ - li{ - color:@info-text; - font-size:13px; - } - a{ - font-size:16px; - } -} - - -/* ----- Sorting top Tab, check main_page/tab_bar.html ------*/ - -.tabBar { - background-color: #eff5f6; - height: 30px; - margin-bottom: 3px; - margin-top: 3px; - float:right; - font-family:@sort-font; - font-size:16px; - .rounded-corners(5px); -} - -.tabBar h2 { - float: left; -} - -.tabsA, .tabsC { - float: right; - position: relative; - display: block; - height: 20px; -} - -/* tabsA - used for sorting */ -.tabsA { float: right; } -.tabsC { float: left; } - -.tabsA a, .tabsC a{ - - border-left: 1px solid #d0e1e4; - color: @section-title; - display: block; - float: left; - height: 20px; - line-height: 20px; - padding:4px 7px 4px 7px; - text-decoration: none; -} - -.tabsA a.on, .tabsC a.on, .tabsA a:hover, .tabsC a:hover { - color: @button-label; -} - -.tabsA a.rev.on, tabsA a.rev.on:hover { -} - -.tabsA .label, .tabsC .label { - float: left; - color: #646464; - margin-top:4px; - margin-right:5px; -} - -.main-page .tabsA .label { - margin-left: 8px; -} - -.tabsB a { - background: #eee; - border: 1px solid #eee; - color: #777; - display: block; - float: left; - height: 22px; - line-height: 28px; - margin: 5px 0px 0 4px; - padding: 0 11px 0 11px; - text-decoration: none; -} - -.tabsC .first{ - border:none; -} - -.rss { - float: right; - font-size: 16px; - color: #f57900; - margin: 5px 0px 3px 7px; - width:52px; - padding-left: 2px; - padding-top:3px; - background:#fff url(../images/feed-icon-small.png) no-repeat center right; - float:right; - font-family:@sort-font; - font-size:16px; -} - -.rss:hover { - color: #F4A731 !important; -} - -/* ----- Headline, containing number of questions and tags selected, check main_page/headline.html ----- */ - -#questionCount{ - font-weight:bold; - font-size:23px; - color:@section-title; - width:200px; - float:left; - margin-bottom:8px; - padding-top:6px; - font-family:@main-font; -} - -#listSearchTags{ - float:left; - margin-top:3px; - color:@info-text; - font-size:16px; - font-family:@main-font; -} - -ul#searchTags { - margin-left:10px; - float:right; - padding-top:2px; -} - -.search-tips { - font-size:16px; - line-height:17px; - color: @info-text; - margin:5px 0 10px 0; - padding:0px; - float:left; - font-family:@main-font; - - a { - text-decoration: underline; - color: @link; - } -} - -/* ----- Question list , check main_page/content.html and macros/macros.html----- */ - -#question-list { - float: left; - position: relative; - background-color: #FFF; - padding: 0; - width: 100%; -} - -.short-summary { - position: relative; - filter: inherit; - padding: 10px; - border-bottom: 1px solid #DDDBCE; - margin-bottom:1px; - overflow: hidden; - width: 710px; - float: left; - background: url(../images/summary-background.png) repeat-x; - - h2 { - font-size: 24px; - font-weight:normal; - line-height: 26px; - padding-left: 0; - margin-bottom:6px; - display:block; - font-family:@main-font; - } - - a { - color:@question-link; - } - - .userinfo { - text-align:right; - line-height:16px; - font-family:@body-font; - padding-right:4px; - } - - - .userinfo .relativetime, span.anonymous - { - font-size: 11px; - clear:both; - font-weight: normal; - color: #555; - } - - .userinfo a{ - font-weight:bold; - font-size:11px; - } - - .counts { - float: right; - margin: 4px 0 0 5px; - font-family:@main-font; - } - - .counts .item-count { - padding:0px 5px 0px 5px; - font-size: 25px; - font-family:@main-font; - } - - .counts .votes div, - .counts .views div, - .counts .answers div, - .counts .favorites div - { - margin-top:3px; - font-size: 14px; - line-height:14px; - color: #646464; - } - - .tags { - margin-top: 0; - } - - .votes, .answers, .favorites, .views { - text-align: center; - margin: 0 3px; - padding: 8px 2px 0px 2px; - width: 51px; - float: right; - height:44px; - border:#dbdbd4 1px solid; - } - - .votes{ - background: url(../images/vote-background.png) repeat-x; - } - - .answers{ - background:url(../images/answers-background.png) repeat-x; - } - - .views { - background:url(../images/view-background.png) repeat-x; - } - - .no-votes .item-count { - color: #b1b5b6; - } - .some-votes .item-count { - color: #4a757f; - } - - .no-answers .item-count { - color: #b1b5b6; - } - .some-answers .item-count { - color: #eab243; - } - - .no-views .item-count { - color: #b1b5b6; - } - .some-views .item-count { - color: #d33f00; - } - - .accepted .item-count { - background:url(../images/accept.png) no-repeat top right; - display: block; - text-align: center; - width: 40px; - color: #eab243; - } - - .some-favorites .item-count { - background:#338333; - color:#d0f5a9; - } - .no-favorites .item-count { - background: #eab243; - color: yellow; - } - -} - -/* ----- Question list Paginator , check main_content/pager.html and macros/utils_macros.html----- */ - -.evenMore { - font-size: 13px; - color:@info-text; - padding:15px 0px 10px 0px; - clear:both; -} - -.evenMore a { - text-decoration: underline; - color:@link; -} - -.pager { - margin-top: 10px; - margin-bottom: 16px; -} - -.pagesize { - margin-top: 10px; - margin-bottom: 16px; - float: right; -} - -.paginator { - padding: 5px 0 10px 0; - font-size:13px; - margin-bottom:10px; - - .prev a, .prev a:visited, - .next a, .next a:visited { - background-color: #fff; - color: #777; - padding: 2px 4px 3px 4px; - } - a{ - color:@section-title; - } - .prev { - margin-right: .5em; - } - - .next { - margin-left: .5em; - } - - .page a, .page a:visited, .curr { - padding: .25em; - background-color: #fff; - margin: 0em .25em; - color: #ff; - } - - .curr { - background-color: #8ebcc7; - color: #fff; - font-weight: bold; - } - .next a, .prev a{ - color:@section-title - } - .page a:hover, - .curr a:hover, - .prev a:hover, - .next a:hover { - color: #8C8C8C; - background-color: #E1E1E1; - text-decoration: none; - } - - .text { - color: #777; - padding: .3em; - } - - .paginator-container-left { - padding: 5px 0 10px 0; - } - -} - -/* ----- Tags Styles ----- */ - -/* tag formatting is also copy-pasted in template - because it must be the same in the emails - askbot/models/__init__.py:format_instant_notification_email() -*/ - -/* tag cloud */ - -.tag-size-1 { - font-size:12px; -} -.tag-size-2 { - font-size:13px; -} -.tag-size-3 { - font-size:14px; -} -.tag-size-4 { - font-size:15px; -} -.tag-size-5 { - font-size:16px; -} -.tag-size-6 { - font-size:17px; -} -.tag-size-7 { - font-size:18px; -} -.tag-size-8 { - font-size:19px; -} -.tag-size-9 { - font-size:20px; -} -.tag-size-10 { - font-size:21px; -} - -ul.tags, -ul.tags.marked-tags, -ul#related-tags { - list-style: none; - margin: 0; - padding: 0; - line-height: 170%; - display: block; -} - -ul.tags li { - float:left; - display: block; - margin: 0 8px 0 0; - padding: 0; - height:20px; -} - -.wildcard-tags { - clear: both; -} - -ul.tags.marked-tags li, -.wildcard-tags ul.tags li { - margin-bottom: 5px; -} - -#tagSelector div.inputs { - clear: both; - float: none; - margin-bottom:10px; -} - -.tags-page ul.tags li, -ul#ab-user-tags li { - width: 160px; - margin:5px; -} - -ul#related-tags li { - margin: 0 5px 8px 0; - float: left; - clear: left; -} - -/* .tag-left and .tag-right are for the sliding doors decoration of tags */ - -.tag-left { - cursor: pointer; - display: block; - float: left; - height: 17px; - margin: 0 5px 0 0; - padding: 0; - .box-shadow(0px,0px,5px,#d3d6d7); -} - -.tag-right { - background: #f3f6f6; - border:#fff 1px solid ; - border-top:#fff 2px solid; - outline:#cfdbdb 1px solid; - /* .box-shadow(0px,1px,0px,#88a8a8);*/ - display: block; - float: left; - height: 17px; - line-height: 17px; - font-weight: normal; - font-size: 11px; - padding: 0px 8px 0px 8px; - text-decoration: none; - text-align: center; - white-space: nowrap; - vertical-align: middle; - font-family:@body-font; - color:#717179; -} -.deletable-tag { - margin-right: 3px; - white-space: nowrap; - .rounded-corners-right(4px); -} - -.tags a.tag-right, -.tags span.tag-right { - color: #585858; - text-decoration: none; - -} -.tags a:hover{ - color: #1A1A1A; -} - -.users-page h1, .tags-page h1 { - float: left; -} - -.main-page h1 { - margin-right: 5px; -} - -.delete-icon { - margin-top:-1px; - float: left; - height: 21px; - width:18px; - display: block; - line-height:20px; - text-align:center; - background: #bbcdcd; - cursor: default; - color:#fff; - border-top:#cfdbdb 1px solid; - font-family:@body-font; - .rounded-corners-right(4px); - .text-shadow(0px,1px,0px,#7ea0a0) - -} -.delete-icon:hover { - background: #b32f2f; -} - - - -.tag-number { - font-weight: normal; - float: left; - font-size:16px; - color:#5d5d5d; -} - -.badges .tag-number { - float: none; - display: inline; - padding-right: 15px; -} - -/* ----- Ask and Edit Question Form template----- */ - -.section-title{ - color:@section-title; - font-family:@main-font; - font-weight:bold; - font-size:24px; -} - -#fmask{ - margin-bottom:30px; - width:100%; -} - -#askFormBar { - display:inline-block; - padding: 4px 7px 5px 0px; - margin-top:0px; - - p{ - margin:0 0 5px 0; - font-size:14px; - color:@info-text-dark; - line-height:1.4; - } - .questionTitleInput { - font-size: 24px; - line-height: 24px; - height: 36px; - margin: 0px; - padding: 0px 0 0 5px; - border:#cce6ec 3px solid; - width:725px; - } -} - -.ask-page, .edit-question-page{ - - div#question-list { - float: none; - border-bottom:#f0f0ec 1px solid; - float:left; - margin-bottom:10px; - a{ - line-height:30px; - } - - } - - div#question-list h2 { - font-size: 13px; - padding-bottom: 0; - color:@link; - border-top:#f0f0ec 1px solid; - border-left:#f0f0ec 1px solid; - height:30px; - line-height:30px; - font-weight:normal; - } - - div#question-list span { - width:28px; - height:26px; - line-height:26px; - text-align:center; - margin-right: 10px; - float:left; - display:block; - color:#fff; - background: #b8d0d5; - .rounded-corners(3px); - } - label{ - color:@info-text-dark; - font-size:13px; - } - - #id_tags{ - border:#cce6ec 3px solid; - height:25px; - padding-left:5px; - width:395px; - font-size:14px; - } -} - -.title-desc { - color: @info-text; - font-size: 13px; -} - -#fmanswer input.submit, -.ask-page input.submit, -.edit-question-page input.submit { - float: left; - background: url(../images/medium-button.png) top repeat-x; - height:34px; - border:0; - font-family:@main-font; - color:@button-label; - font-weight:normal; - font-size:21px; - margin-top:3px; - .rounded-corners(4px); - .box-shadow(1px, 1px, 2px, #636363); - margin-right:7px; -} - -#fmanswer input.submit:hover, -.ask-page input.submit:hover, -.edit-question-page input.submit:hover{ - text-decoration:none; - background: url(../images/medium-button.png) bottom repeat-x; - .text-shadow(0px, 1px, 0px, #c6d9dd) -} -#editor { /*adjustment for editor preview*/ - font-size: 100%; - min-height: 200px; - line-height: 18px; - margin:0; - border-left:#cce6ec 3px solid; - border-bottom:#cce6ec 3px solid; - border-right:#cce6ec 3px solid; - border-top:0; - padding:10px; - margin-bottom:10px; - width:717px; -} - -#id_title { - width: 100%; -} - -.wmd-preview { - margin: 3px 0 5px 0; - padding: 6px; - background-color: #F5F5F5; - min-height: 20px; - overflow: auto; - font-size:13px; - font-family:@body-font; - - p{ - margin-bottom:14px; - line-height:1.4; - font-size:14px; - } -} - -.wmd-preview pre { - background-color: #E7F1F8; - -} - -.wmd-preview blockquote { - background-color: #eee; -} - -.wmd-preview IMG { - max-width: 600px; -} - -.preview-toggle { - width: 100%; - color: #b6a475; /*letter-spacing:1px;*/ - text-align: left; -} - -.preview-toggle span:hover { - cursor: pointer; -} - -.after-editor { - margin-top: 15px; - margin-bottom: 15px; -} - -.checkbox { - margin-left:5px; - font-weight:normal; - cursor:help -} - -.question-options { - margin-top: 1px; - color: #666; - line-height: 13px; - margin-bottom:5px; -} -.question-options label { - vertical-align: text-bottom; -} - -.edit-content-html { - border-top: 1px dotted #D8D2A9; - border-bottom: 1px dotted #D8D2A9; - margin: 5px 0 5px 0; -} - -.edit-question-page, #fmedit, .wmd-preview{ - color:@info-text-dark; - - #id_revision{ - font-size:14px; - margin-top:5px; - margin-bottom:5px; - } - #id_title{ - font-size: 24px; - line-height: 24px; - height: 36px; - margin: 0px; - padding: 0px 0 0 5px; - border:#cce6ec 3px solid; - width:725px; - margin-bottom:10px; - } - #id_summary{ - border:#cce6ec 3px solid; - height:25px; - padding-left:5px; - width:395px; - font-size:14px; - } - .title-desc{ - margin-bottom:10px; - } -} - -/* ----- Question template ----- */ - -.question-page{ - - h1{ - padding-top:0px; - font-family:@main-font; - } - - h1 a{ - color:@question-link; - font-size:30px; - font-weight:normal; - line-height:1; - } - - p.rss { - float:none; - clear:both; - padding: 3px 0 0 23px; - font-size: 15px; - width:110px; - background-position:center left; - margin-left:0px !important; - } - - p.rss a { - font-family:@main-font; - vertical-align: top; - } - - .question-content{ - float:right; - width:682px; - margin-bottom:10px; - } - - #question-table{ - float:left; - border-top:#f0f0f0 1px solid; - } - - #question-table, - .answer-table { - margin: 6px 0 6px 0; - border-spacing: 0px; - padding-right:10px; - } - - .answer-table { - margin-top:0px; - border-bottom: 1px solid #D4D4D4; - } - - .answer-table td, - #question-table td { - width:20px; - vertical-align:top; - } - .question-body, .answer-body { - overflow: auto; - margin-top:10px; - font-family:@body-font; - color:#4b4b4b; - - p{ - margin-bottom:14px; - line-height:1.4; - font-size:14px; - padding:0px 5px 5px 0px; - } - - a { - color:@link; - } - - li { - margin-bottom:7px; - } - } - - .question-body IMG, .answer-body IMG { - max-width: 600px; - } - - .post-update-info-container { - float: right; - width: 175px; - } - - .post-update-info { - background: #fff url(../images/background-user-info.png) repeat-x bottom; - float: right; - font-size: 9px; - font-family:@secondary-font; - width: 158px; - padding:4px; - margin:0px 0px 5px 5px; - line-height: 14px; - .rounded-corners(4px); - .box-shadow (0px, 2px,1px,#bfbfbf); - - p { - line-height: 13px; - font-size: 11px; - margin: 0 0 2px 1px; - padding: 0; - } - a{ - color:#444; - } - .gravatar { - float: left; - margin-right: 4px; - } - - p.tip { - color: #444; - line-height: 13px; - font-size: 10px; - } - } - - .post-controls{ - font-size: 11px; - line-height: 12px; - min-width: 200px; - padding-left: 5px; - text-align:right; - clear: left; - float: right; - margin-top:10px; - margin-bottom:8px; - - a { - color: #777; - padding: 0px 7px 3px 18px; - cursor: pointer; - border: none; - font-size:12px; - font-family:@body-font; - text-decoration: none; - height:18px; - display:block; - float:right; - line-height:18px; - margin-top:-2px; - margin-left:4px; - } - - a:hover { - background-color: #f5f0c9; - .rounded-corners(3px); - - } - .sep { - color: #ccc; - float:right; - height:18px; - font-size:18px; - } - } - .post-controls, .answer-controls{ - .question-delete{ - background: url(../images/delete.png) no-repeat center left; - padding-left:11px; - } - .question-flag{ - background: url(../images/flag.png) no-repeat center left; - } - .question-edit{ - background: url(../images/edit2.png) no-repeat center left; - } - .question-retag{ - background: url(../images/retag.png) no-repeat center left; - } - .question-close{ - background: url(../images/close.png) no-repeat center left; - } - .permant-link{ - background: url(../images/link.png) no-repeat center left; - } - } - .tabBar{ - width:100%; - } - - #questionCount{ - float:left; - font-family:@main-font; - line-height:15px; - } - - .question-img-upvote, .question-img-downvote, - .answer-img-upvote, .answer-img-downvote { - width: 25px; - height: 20px; - cursor:pointer; - } - - .question-img-upvote, .answer-img-upvote { - background: url(../images/vote-arrow-up-new.png) no-repeat; - } - - .question-img-downvote, .answer-img-downvote { - background: url(../images/vote-arrow-down-new.png) no-repeat; - } - - .question-img-upvote:hover, .question-img-upvote.on, - .answer-img-upvote:hover, .answer-img-upvote.on { - background: url(../images/vote-arrow-up-on-new.png) no-repeat; - } - - .question-img-downvote:hover, .question-img-downvote.on, - .answer-img-downvote:hover, .answer-img-downvote.on { - background: url(../images/vote-arrow-down-on-new.png) no-repeat; - } - - #fmanswer_button{ - margin:8px 0px ; - } - .question-img-favorite:hover { - background: url(../images/vote-favorite-on.png) - } - div.comments { - padding: 0; - } - #comment-title{ - font-weight:bold; - font-size:23px; - color:@section-title; - width:200px; - float:left; - font-family:@main-font; - } - .comments { - font-size: 12px; - clear: both; - - div.controls { - clear: both; - float:left; - width: 100%; - margin: 3px 0 20px 5px; - } - - .controls a { - color: #988e4c; - padding: 0 3px 2px 22px; - font-family:@body-font; - font-size:13px; - background:url(../images/comment.png) no-repeat center left; - } - - .controls a:hover { - background-color: #f5f0c9; - text-decoration: none; - } - - .button { - color: #988e4c; - font-size: 11px; - padding: 3px; - cursor: pointer; - } - a { - background-color: inherit; - color: @link; - padding: 0; - } - - form.post-comments { - margin: 3px 26px 0 42px; - textarea{ - font-size: 13px; - line-height: 1.3; - - } - } - - textarea { - height: 42px; - width:100%; - margin: 7px 0 5px 1px; - font-family: @body-font; - outline: none; - overflow:auto; - font-size: 12px; - line-height: 140%; - padding-left:2px; - padding-top:3px; - border:#cce6ec 3px solid; - } - - /* A small hack to solve 1px problem on webkit browsers */ - @media screen and (-webkit-min-device-pixel-ratio:0){ - textarea{ - padding-left:3px !important; - } - } - - input { - margin-left: 10px; - margin-top: 1px; - vertical-align: top; - width: 100px; - } - button{ - background:url(../images/small-button-blue.png) repeat-x top; - border:0; - color:@button-label; - font-family:@body-font; - font-size:13px; - width:100px; - font-weight:bold; - height:27px; - line-height:25px; - margin-bottom:5px; - cursor:pointer; - .rounded-corners(4px); - .text-shadow(0px,1px,0px,#E6F6FA); - .box-shadow(1px, 1px, 2px, #808080); - } - button:hover{ - background: url(../images/small-button-blue.png) bottom repeat-x; - .text-shadow(0px, 1px, 0px, #c6d9dd); - } - .counter { - display: inline-block; - width: 245px; - float:right; - color:#b6a475 !important; - vertical-align: top; - font-family:@body-font; - float:right; - text-align:right; - } - .comment { - border-bottom: 1px solid #edeeeb; - clear:both; - margin: 0; - margin-top:8px; - padding-bottom:4px; - overflow: auto; - font-family:@body-font; - font-size:11px; - min-height: 25px; - background:#fff url(../images/comment-background.png) bottom repeat-x; - .rounded-corners(5px); - } - div.comment:hover { - background-color: #efefef; - } - a.author{ - background-color: inherit; - color: @link; - padding: 0; - } - - a.author:hover { - text-decoration: underline; - } - span.delete-icon{ - background:url(../images/close-small.png) no-repeat; - border:0; - width:14px; - height:14px; - } - span.delete-icon:hover{ - border:#BC564B 2px solid; - .rounded-corners(10px); - margin: -3px 0px 0px -2px; - } - .content { - margin-bottom: 7px; - } - - .comment-votes { - float: left; - width: 37px; - line-height: 130%; - padding: 6px 5px 6px 3px; - } - - .comment-body { - line-height: 1.3; - margin: 3px 26px 0 46px; - padding: 5px 3px; - color: #666; - font-size:13px; - - .edit{ - padding-left:6px; - } - } - - .comment-body p{ - font-size:13px; - line-height:1.3; - margin-bottom: 3px; - padding: 0; - } - - .comment-delete { - float: right; - width: 14px; - line-height: 130%; - padding: 8px 6px; - } - - .upvote { - margin: 0px; - padding-right: 17px; - padding-top: 2px; - text-align: right; - height: 20px; - font-size: 13px; - font-weight: bold; - color: #777; - } - - .upvote.upvoted { - color: #d64000; - } - - .upvote.hover { - background: url(../images/go-up-grey.png) no-repeat; - background-position: right 1px; - } - - .upvote:hover { - background: url(../images/go-up-orange.png) no-repeat; - background-position: right 1px; - } - - .help-text{ - float: right; - text-align:right; - color: gray; - margin-bottom: 0px; - margin-top: 0px; - line-height: 50%; - } - } - #questionTools { - font-size: 22px; - margin-top: 11px; - text-align: left; - } - - .question-status { - margin-top: 10px; - margin-bottom:15px; - padding: 20px; - background-color: #fef7cc; - text-align: center; - border:#e1c04a 1px solid; - } - - .question-status h3 { - font-size: 20px; - color:@info-text; - font-weight:normal; - } - - .vote-buttons { - float: left; - text-align: center; - padding-top: 2px; - margin:10px 10px 0px 3px; - } - - .vote-buttons IMG { - cursor: pointer; - } - - .vote-number { - font-family: @main-font; - padding: 0px 0 5px 0; - font-size: 25px; - font-weight: bold; - color: #777; - } - - .vote-buttons .notify-sidebar { - text-align: left; - width:120px; - } - .vote-buttons .notify-sidebar label { - vertical-align: top; - } - - .tabBar-answer{ - margin-bottom:15px; - padding-left:7px; - width:723px; - margin-top:10px; - } - .answer{ - .vote-buttons { - float:left; - } - } - .accepted-answer { - background-color: #f7fecc; - border-bottom-color: #9BD59B; - - .vote-buttons { - width:27px; - margin-right:10px; - margin-top:10px; - } - } - - .answer .post-update-info a{ - color:#444444; - } - - .answered { - background: #CCC; - color: #999; - } - - .answered-accepted { - background: #DCDCDC; - color: #763333; - - strong { - color: #E1E818; - } - } - - .answered-by-owner { - background: #F1F1FF; - - .comments .button { - background-color: #E6ECFF; - } - .comments { - background-color: #E6ECFF; - } - .vote-buttons { - margin-right:10px; - } - } - - .answer-img-accept:hover { - background: url(../images/vote-accepted-on.png) - } - .answer-body a { - color:@link; - } - .answer-body li { - margin-bottom:0.7em; - } - - #fmanswer{ - color:@info-text; - line-height:1.2; - margin-top:10px; - - h2{ - font-family:@main-font; - color:@section-title; - font-size:24px; - } - label{ - font-size:13px; - } - } - .message { - padding: 5px; - margin: 0px 0 10px 0; - - } - -} - -.facebook-share.icon, .twitter-share.icon, .linkedin-share.icon, .identica-share.icon { - background: url(../images/socialsprite.png) no-repeat; - display:block; - text-indent:-100em; - height:25px; - width:25px; - margin-bottom:3px; -} - -.facebook-share.icon:hover, .twitter-share.icon:hover, .linkedin-share.icon:hover, .identica-share.icon:hover{ - opacity:0.8; - filter: alpha(opacity=80); -} - -.facebook-share.icon { - background-position: -26px 0px; -} -.identica-share.icon { - background-position: -78px 0px; -} -.twitter-share.icon { - margin-top:10px; - background-position: 0px 0px; -} -.linkedin-share.icon { - background-position: -52px 0px; -} - -/* -----Content pages, Login, About, FAQ, Users----- */ - -.openid-signin, -.meta, -.users-page, -.user-profile-edit-page, -{ - font-size:13px; - line-height:1.3; - color:@info-text-dark; - p{ - font-size:13px; - color:@info-text; - line-height:1.3; - font-family:@body-font; - color:@info-text-dark; - margin-bottom:12px; - } - h2{ - color:@info-text-dark; - padding-left:0px; - font-size:16px; - } -} - -.openid-signin, -.meta, -.users-page, -.user-profile-edit-page, -.user-profile-page, -{ - form{ - margin-bottom:15px; - } - input[type="text"],input[type="password"],select{ - border:#cce6ec 3px solid; - height:25px; - padding-left:5px; - width:395px; - font-size:14px; - } - select{ - width:405px; - height:30px; - } - textarea{ - border:#cce6ec 3px solid; - padding-left:5px; - padding-top:5px; - width:395px; - font-size:14px; - } - input.submit{ - background:url(../images/small-button-blue.png) repeat-x top; - border:0; - color:@button-label; - font-weight:bold; - font-size:13px; - font-family:@body-font; - height:26px; - margin:5px 0px; - width:100px; - cursor:pointer; - .rounded-corners(4px); - .text-shadow(0px,1px,0px,#E6F6FA); - .box-shadow(1px, 1px, 2px, #808080); - } - input.submit:hover{ - background:url(../images/small-button-blue.png) repeat-x bottom; - text-decoration:none; - } - .cancel{ - background:url(../images/small-button-cancel.png) repeat-x top !important; - color:#525252 !important; - } - .cancel:hover{ - background:url(../images/small-button-cancel.png) repeat-x bottom !important; - } -} - -#email-input-fs,#local_login_buttons,#password-fs,#openid-fs{ - margin-top:10px; - #id_email,#id_username,#id_password{ - font-size: 12px; - line-height: 20px; - height: 20px; - margin: 0px; - padding: 0px 0 0 5px; - border:#cce6ec 3px solid; - width:200px; - } - .submit-b{ - background:url(../images/small-button-blue.png) repeat-x top; - border:0; - color:@button-label; - font-weight:bold; - font-size:13px; - font-family:@body-font; - height:24px; - margin-top:-2px; - padding-left:10px; - padding-right:10px; - cursor:pointer; - .rounded-corners(4px); - .text-shadow(0px,1px,0px,#E6F6FA); - .box-shadow(1px, 1px, 2px, #808080) - } - .submit-b:hover{ - background:url(../images/small-button-blue.png) repeat-x bottom; - } -} -.openid-input { - background: url(../images/openid.gif) no-repeat; - padding-left: 15px; - cursor: pointer; -} - -.openid-login-input { - background-position: center left; - background: url(../images/openid.gif) no-repeat 0% 50%; - padding: 5px 5px 5px 15px; - cursor: pointer; - font-family: Trebuchet MS; - font-weight: 300; - font-size: 150%; - width: 500px; -} - -.openid-login-submit { - height: 40px; - width: 80px; - line-height: 40px; - cursor: pointer; - border: 1px solid #777; - font-weight: bold; - font-size: 120%; -} - -/* People page */ - -.tabBar-user{ - width:375px; -} - -.user { - padding: 5px; - line-height: 140%; - width: 166px; - border:#eee 1px solid; - margin-bottom:5px; - .rounded-corners(3px); - .user-micro-info{ - color:@info-text-dark; - } - -} - -.user ul { - margin: 0; - list-style-type: none; -} - -.user .thumb { - clear: both; - float: left; - margin-right: 4px; - display: inline; -} - -/* tags page */ - -.tabBar-tags{ - width:270px; - margin-bottom:15px; -} - -/* badges page */ - -a.medal { - font-size: 17px; - line-height: 250%; - margin-right:5px; - color: #333; - text-decoration: none; - background: url(../images/medala.gif) no-repeat; - border-left: 1px solid #EEE; - border-top: 1px solid #EEE; - border-bottom: 1px solid #CCC; - border-right: 1px solid #CCC; - padding: 4px 12px 4px 6px; -} - -a:hover.medal { - color: #333; - text-decoration: none; - background: url(../images/medala_on.gif) no-repeat; - border-left: 1px solid #E7E296; - border-top: 1px solid #E7E296; - border-bottom: 1px solid #D1CA3D; - border-right: 1px solid #D1CA3D; -} - -#award-list{ - .user{ - float:left; - margin:5px; - } -} - -/* profile page */ - -.tabBar-profile{ - width:100%; - margin-bottom:15px; - float:left; -} - -.user-profile-page{ - font-size:13px; - color:@info-text-dark; - - p{ - font-size:13px; - line-height:1.3; - color:@info-text-dark; - } - .avatar img{ - border:#eee 1px solid; - padding:5px; - } - h2{ - padding:10px 0px 10px 0px; - font-family:@main-font; - } -} - -.user-details { - font-size: 13px; - h3{ - font-size:16px; - } -} - -.user-about { - background-color: #EEEEEE; - height: 200px; - line-height: 20px; - overflow: auto; - padding: 10px; - width: 90%; - p{font-size:13px;} -} - -.follow-toggle,.submit { - border:0 !important; - color:@button-label; - font-weight:bold; - font-size:12px; - height:26px; - line-height:26px; - margin-top:-2px; - font-size:15px; - cursor:pointer; - font-family:@main-font; - background:url(../images/small-button-blue.png) repeat-x top; - .rounded-corners(4px); - .text-shadow(0px,1px,0px,#E6F6FA); - .box-shadow(1px, 1px, 2px, #808080) -} - -.follow-toggle:hover, .submit:hover { - background:url(../images/small-button-blue.png) repeat-x bottom; - text-decoration:none !important; -} - -.follow-toggle .follow{ - font-color: #000; - font-style:normal; -} - -.follow-toggle .unfollow div.unfollow-red{ - display:none; -} - -.follow-toggle .unfollow:hover div.unfollow-red{ - display:inline; - color:#fff; - font-weight:bold; - color:#A05736; -} - -.follow-toggle .unfollow:hover div.unfollow-green{ - display:none; -} - -.count { - font-family: @main-font; - font-size: 200%; - font-weight: 700; - color: #777 -} - -.scoreNumber { - font-family: @main-font; - font-size: 35px; - font-weight: 800; - color: #777; - line-height: 40px; /*letter-spacing:0px*/ - margin-top: 3px; -} - -.vote-count { - font-family: Arial; - font-size: 160%; - font-weight: 700; - color: #777; -} - -.answer-summary { - display: block; - clear: both; - padding: 3px; -} - -.answer-votes { - background-color: #EEEEEE; - color: #555555; - float: left; - font-family: Arial; - font-size: 15px; - font-weight: bold; - height: 17px; - padding: 2px 4px 5px; - text-align: center; - text-decoration: none; - width: 20px; - margin-right: 10px; - .rounded-corners(4px); -} - -.karma-summary { - padding:5px; - font-size:13px; -} - -.karma-summary h3 { - text-align: center; - font-weight: bold; - padding:5px; -} - -.karma-diagram { - width:477px; - height:300px; - float:left; - margin-right:10px; -} - -.karma-details { - float:right; - width:450px; - height:250px; - overflow-y:auto; - word-wrap:break-word; - p{margin-bottom:10px;} -} - -.karma-gained { - font-weight:bold; - background:#eee; - width:25px; - margin-right:5px; - color:green; - padding:3px; - display:block; - float:left; - text-align:center; - .rounded-corners(3px); -} - -.karma-lost { - font-weight:bold; - background:#eee; - width:25px; - color:red; - padding:3px; - display:block; - margin-right:5px; - float:left; - text-align:center; - .rounded-corners(3px); -} - -.submit-row{ - margin-bottom:10px; -} - -/*----- Revision pages ----- */ - -.revision { - margin: 10px 0 10px 0; - font-size: 13px; - color:@info-text-dark; - - p{ - font-size:13px; - line-height:1.3; - color:@info-text-dark; - } - - h3{ - font-family:@main-font; - font-size:21px; - padding-left:0px; - } - - .header { - background-color: #F5F5F5; - padding: 5px; - cursor: pointer; - } - - .author { - background-color: #e9f3f5; - } - - .summary { - padding: 5px 0 10px 0; - } - - .summary span { - background-color:#fde785; - padding:6px; - .rounded-corners(4px); - display: inline; - .box-shadow(1px, 1px, 4px, #cfb852); - } - - .answerbody { - padding: 10px 0 5px 10px; - } - - .revision-mark { - width: 150px; - text-align: left; - display: inline-block; - font-size: 11px; - overflow: hidden; - - .gravatar{ - float:left; - margin-right:4px; - padding-top:5px; - } - } - - .revision-number { - font-size: 300%; - font-weight: bold; - font-family: sans-serif; - } -} - -del, del .post-tag { - color: #C34719; -} - -ins .post-tag, ins p, ins { - background-color: #E6F0A2; -} - -/* ----- Red Popup notification ----- */ - -.vote-notification { - z-index: 1; - cursor: pointer; - display: none; - position: absolute; - font-family:@secondary-font; - font-size:14px; - font-weight:normal; - color: white; - background-color: #8e0000; - text-align: center; - padding-bottom:10px; - .box-shadow(0px, 2px, 4px, #370000); - .rounded-corners(4px); - - h3{ - background:url(../images/notification.png) repeat-x top; - padding:10px 10px 10px 10px; - font-size:13px; - margin-bottom:5px; - border-top:#8e0000 1px solid; - color:#fff; - font-weight:normal; - .rounded-corners-top(4px); - } - a { - color: #fb7321; - text-decoration: underline; - font-weight:bold; - } - -} - - -/* ----- Footer links , check blocks/footer.html----- */ - -#ground { - width: 100%; - clear: both; - border-top: 1px solid #000; - padding: 6px 0 0 0; - background: @header-color; - font-size:16px; - font-family:@main-font; - - p { - margin-bottom:0; - } -} - -.footer-links { - color: #EEE; - text-align:left; - width:500px; - float:left; - a { - color: #e7e8a8; - } -} - -.powered-link{ - width:500px; - float:left; - text-align:left; - a{ - color:#8ebcc7; - } -} - -.copyright{ - color:#616161; - width:450px; - float:right; - text-align:right; - - a{ - color:#8ebcc7; - } - img.license-logo { - margin: 6px 0px 20px 10px; - float:right; - } -} - - -.notify-me { - float: left; -} - - -span.text-counter { - margin-right: 20px; -} - -span.form-error { - color: #990000; - font-weight: normal; - margin-left: 5px; -} - -ul.errorlist { - margin-bottom: 0; -} - -p.form-item { - margin: 0px; -} - - - - -.deleted { - background: #F4E7E7 none repeat scroll 0 0; -} - - -/* openid styles */ -.form-row { - line-height: 25px; -} - -table.form-as-table { - margin-top: 5px; -} - -table.form-as-table ul { - list-style-type: none; - display: inline; -} - -table.form-as-table li { - display: inline; -} - -table.form-as-table td { - text-align: right; -} - -table.form-as-table th { - text-align: left; - font-weight: normal; -} - -table.ab-subscr-form { - width: 45em; -} - -table.ab-tag-filter-form { - width: 45em; -} - -.submit-row { - line-height: 30px; - padding-top: 10px; - display: block; - clear: both; -} - -.errors { - line-height: 20px; - color: red; -} - -.error { - color: darkred; - margin: 0; - font-size: 10px; -} - -label.retag-error { - color: darkred; - padding-left: 5px; - font-size: 10px; -} - -.fieldset { - border: none; - margin-top: 10px; - padding: 10px; -} - - -span.form-error { - color: #990000; - font-size: 90%; - font-weight: normal; - margin-left: 5px; -} - - -/* -.favorites-count-off { - color: #919191; - float: left; - text-align: center; -} - -.favorites-count { - color: #D4A849; - float: left; - text-align: center; -} -*/ - -/* todo: get rid of this in html */ -.favorites-empty { - width: 32px; - height: 45px; - float: left; -} - -.user-info-table { - margin-bottom: 10px; - border-spacing: 0; -} - -/* todo: remove this hack? */ -.user-stats-table .narrow { - width: 660px; -} - -.narrow .summary h3 { - padding: 0px; - margin: 0px; -} - -.relativetime { - font-weight: bold; - text-decoration: none; -} - -.narrow .tags { - float: left; -} - - - - -/* todo: make these more semantic */ -.user-action-1 { - font-weight: bold; - color: #333; -} - -.user-action-2 { - font-weight: bold; - color: #CCC; -} - -.user-action-3 { - color: #333; -} - -.user-action-4 { - color: #333; -} - -.user-action-5 { - color: darkred; -} - -.user-action-6 { - color: darkred; -} - -.user-action-7 { - color: #333; -} - -.user-action-8 { - padding: 3px; - font-weight: bold; - background-color: #CCC; - color: #763333; -} - -.revision-summary { - background-color: #FFFE9B; - padding: 2px; -} - -.question-title-link a { - font-weight: bold; - color: #0077CC; -} - -.answer-title-link a { - color: #333; -} - -/* todo: make these more semantic */ -.post-type-1 a { - font-weight: bold; - -} - -.post-type-3 a { - font-weight: bold; - -} - -.post-type-5 a { - font-weight: bold; -} - -.post-type-2 a { - color: #333; -} - -.post-type-4 a { - color: #333; -} - -.post-type-6 a { - color: #333; -} - -.post-type-8 a { - color: #333; -} - -.hilite { - background-color: #ff0; -} - -.hilite1 { - background-color: #ff0; -} - -.hilite2 { - background-color: #f0f; -} - -.hilite3 { - background-color: #0ff; -} - -.gold, .badge1 { - color: #FFCC00; -} - -.silver, .badge2 { - color: #CCCCCC; -} - -.bronze, .badge3 { - color: #CC9933; -} - -.score { - font-weight: 800; - color: #333; -} - - -a.comment { - background: #EEE; - color: #993300; - padding: 5px; -} - -a.offensive { - color: #999; -} - -.message h1 { - padding-top: 0px; - font-size: 15px; -} - -.message p { - margin-bottom: 0px; -} - -p.space-above { - margin-top: 10px; -} - -.warning { - color: red; -} - - - -button::-moz-focus-inner { - padding:0; - border:none; -} -.submit { - cursor: pointer; /*letter-spacing:1px;*/ - background-color: #D4D0C8; - height: 30px; - border: 1px solid #777777; /* width:100px; */ - font-weight: bold; - font-size: 120%; -} - -.submit:hover { - text-decoration: underline; -} - -.submit.small { - margin-right:5px; - height:20px; - font-weight:normal; - font-size:12px; - padding:1px 5px; -} -.submit.small:hover { - text-decoration:none; -} -.question-page a.submit { - display: -moz-inline-stack; - display: inline-block; - line-height: 30px; - padding: 0 5px; - *display: inline; -} - -.noscript { - position: fixed; - top: 0px; - left: 0px; - width: 100%; - z-index: 100; - padding: 5px 0; - text-align: center; - font-family: sans-serif; - font-size: 120%; - font-weight: Bold; - color: #FFFFFF; - background-color: #AE0000; -} - -.big { - font-size: 14px; -} - -.strong { - font-weight: bold; -} - -.orange {/* used in django.po */ - color: #d64000; - font-weight: bold; -} - -.grey { - color: #808080; -} - -.about div { - padding: 10px 5px 10px 5px; - border-top: 1px dashed #aaaaaa; -} - -.highlight { - background-color: #FFF8C6; -} - -.nomargin { - margin: 0; -} - -.margin-bottom { - margin-bottom: 10px; -} - -.margin-top { - margin-top: 10px; -} - -.inline-block { - display: inline-block; -} - -.action-status { - margin: 0; - border: none; - text-align: center; - line-height: 10px; - font-size: 12px; - padding: 0; -} - -.action-status span { - padding: 3px 5px 3px 5px; - background-color: #fff380; /* nice yellow */ - font-weight: normal; - -moz-border-radius: 5px; - -khtml-border-radius: 5px; - -webkit-border-radius: 5px; -} - -.list-table td { - vertical-align: top; -} - -/* these need to go */ -table.form-as-table .errorlist { - display: block; - margin: 0; - padding: 0 0 0 5px; - text-align: left; - font-size: 10px; - color: darkred; -} - -table.form-as-table input { - display: inline; - margin-left: 4px; -} - -table.form-as-table th { - vertical-align: bottom; - padding-bottom: 4px; -} - -.form-row-vertical { - margin-top: 8px; - display: block; -} - -.form-row-vertical label { - margin-bottom: 3px; - display: block; -} - -/* above stuff needs to go */ -.text-align-right { - text-align: center; -} - -ul.form-horizontal-rows { - list-style: none; - margin: 0; -} - -ul.form-horizontal-rows li { - position: relative; - height: 40px; -} - -ul.form-horizontal-rows label { - display: inline-block; -} - -ul.form-horizontal-rows ul.errorlist { - list-style: none; - color: darkred; - font-size: 10px; - line-height: 10px; - position: absolute; - top: 2px; - left: 180px; - text-align: left; - margin: 0; -} - -ul.form-horizontal-rows ul.errorlist li { - height: 10px; -} - -ul.form-horizontal-rows label { - position: absolute; - left: 0px; - bottom: 6px; - margin: 0px; - line-height: 12px; - font-size: 12px; -} - -ul.form-horizontal-rows li input { - position: absolute; - bottom: 0px; - left: 180px; - margin: 0px; -} - -.narrow .summary { - float: left; -} - -.user-profile-tool-links { - font-weight: bold; - vertical-align: top; -} - - -ul.post-tags { - margin-left: 3px; -} -ul.post-tags li { - margin-top: 4px; - margin-bottom: 3px; -} - -ul.post-retag { - margin-bottom:0px; - margin-left:5px; -} - -#question-controls .tags { - margin: 0 0 3px 0; -} - -#tagSelector { - padding-bottom: 2px; - margin-bottom: 0; -} - -#related-tags { - padding-left: 3px; -} - -#hideIgnoredTagsControl { - margin: 5px 0 0 0; -} - -#hideIgnoredTagsControl label { - font-size: 12px; - color: #666; -} - -#hideIgnoredTagsCb { - margin: 0 2px 0 1px; -} - -#recaptcha_widget_div { - width: 318px; - float: left; - clear: both; -} - -p.signup_p { - margin: 20px 0px 0px 0px; -} - -.simple-subscribe-options ul { - list-style: none; - list-style-position: outside; - margin: 0; -} - -/* a workaround to set link colors correctly */ - -.wmd-preview a { - color:@link; -} - -.wmd-preview li { - margin-bottom:7px; - font-size:14px; -} - -.search-result-summary { - font-weight: bold; - font-size:18px; - line-height:22px; - margin:0px 0px 0px 0px; - padding:2px 0 0 0; - float: left; -} - -.faq-rep-item { - text-align:right; - padding-right:5px; -} - - -.user-info-table .gravatar { - margin:0; -} - -#responses { - clear:both; - line-height:18px; - margin-bottom:15px; -} - -#responses div.face { - float:left; - text-align: center; - width: 54px; - padding: 3px; - overflow:hidden; -} - -.response-parent { - margin-top: 18px; -} - -.response-parent strong{ - font-size: 20px; -} - -.re { - min-height: 57px; - clear: both; - margin-top: 10px; -} - -#responses input { - float:left; -} -#re_tools { - margin-bottom:10px; -} -#re_sections { - margin-bottom:6px; -} -#re_sections .on { - font-weight:bold; -} - -.avatar-page ul { - list-style: none; -} -.avatar-page li { - display: inline; -} -.user-profile-page .avatar p { - margin-bottom: 0px; -} -.user-profile-page .tabBar a#stats { - margin-left: 0; -} -.user-profile-page img.gravatar { - margin: 2px 0 3px 0; -} -.user-profile-page h3 { - padding: 0; - margin-top: -3px; -} -.userList { - font-size: 13px; -} - -img.flag { - border: 1px solid #eee; - vertical-align: text-top; -} - -.main-page img.flag { - vertical-align: text-bottom; -} - - -/* Pretty printing styles. Used with prettify.js. */ - -a.edit { - padding-left:3px; - color: #145bff; -} - -.str { color: #080; } -.kwd { color: #008; } -.com { color: #800; } -.typ { color: #606; } -.lit { color: #066; } -.pun { color: #660; } -.pln { color: #000; } -.tag { color: #008; }/* name conflict here */ -.atn { color: #606; } -.atv { color: #080; } -.dec { color: #606; } -pre.prettyprint { clear:both;padding: 3px; border: 0px solid #888; } - -@media print { - .str { color: #060; } - .kwd { color: #006; font-weight: bold; } - .com { color: #600; font-style: italic; } - .typ { color: #404; font-weight: bold; } - .lit { color: #044; } - .pun { color: #440; } - .pln { color: #000; } - .tag { color: #006; font-weight: bold; } - .atn { color: #404; } - .atv { color: #060; } -} - -#leading-sidebar { - float: left; -} diff --git a/lms/askbot/skins/mitx/templates/404.html b/lms/askbot/skins/mitx/templates/404.html deleted file mode 100644 index 158bfb9475..0000000000 --- a/lms/askbot/skins/mitx/templates/404.html +++ /dev/null @@ -1,5 +0,0 @@ -{% load extra_tags %} -{% include_jinja "404.jinja.html" request %} -{% comment %} -this one has to be a django template because of use of default hander404 -{% endcomment %} diff --git a/lms/askbot/skins/mitx/templates/404.jinja.html b/lms/askbot/skins/mitx/templates/404.jinja.html deleted file mode 100644 index 2da99646d9..0000000000 --- a/lms/askbot/skins/mitx/templates/404.jinja.html +++ /dev/null @@ -1,44 +0,0 @@ -{% extends "one_column_body.html" %} - -{% block title %}{% spaceless %}{% trans %}Page not found{% endtrans %}{% endspaceless %}{% endblock %} -{% block forestyle%} - -{% endblock %} -{% block content %} -

      {% trans %}Page not found{% endtrans %}

      -
      -
      -

      {% trans %}Sorry, could not find the page you requested.{% endtrans %}

      -
      - {% trans %}This might have happened for the following reasons:{% endtrans %}
      -
        -
      • {% trans %}this question or answer has been deleted;{% endtrans %}
      • -
      • {% trans %}url has error - please check it;{% endtrans %}
      • -
      • {% trans %}the page you tried to visit is protected or you don't have sufficient points, see{% endtrans %} {% trans %}faq{% endtrans %};
      • -
      • {% trans %}if you believe this error 404 should not have occured, please{% endtrans %} - {% trans %}report this problem{% endtrans %}
      • - -
      - - -
      -
      -{% endblock %} -{% block endjs %} - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/500.html b/lms/askbot/skins/mitx/templates/500.html deleted file mode 100644 index 8ec1bce495..0000000000 --- a/lms/askbot/skins/mitx/templates/500.html +++ /dev/null @@ -1,5 +0,0 @@ -{% load extra_tags %} -{% include_jinja "500.jinja.html" request %} -{% comment %}this template must be django -because of the use of default handler500 -{% endcomment %} diff --git a/lms/askbot/skins/mitx/templates/500.jinja.html b/lms/askbot/skins/mitx/templates/500.jinja.html deleted file mode 100644 index 297ae7367e..0000000000 --- a/lms/askbot/skins/mitx/templates/500.jinja.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "two_column_body.html" %} - -{% block title %}{% spaceless %}{% trans %}Internal server error{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} -

      {% trans %}Internal server error{% endtrans %}

      -
      -
      - {% trans %}system error log is recorded, error will be fixed as soon as possible{% endtrans %}
      - {% trans %}please report the error to the site administrators if you wish{% endtrans %} -
      -
      -{% endblock %} -{% block endjs %} - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/answer_edit.html b/lms/askbot/skins/mitx/templates/answer_edit.html deleted file mode 100644 index e9674a3141..0000000000 --- a/lms/askbot/skins/mitx/templates/answer_edit.html +++ /dev/null @@ -1,84 +0,0 @@ -{% extends "two_column_body.html" %} -{% import "macros.html" as macros %} - -{% block title %}{% spaceless %}{% trans %}Edit answer{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} - - -
      -

      {% trans %}Edit answer{% endtrans %} {% trans %}back{% endtrans %} -

      -
      - -
      {% csrf_token %} - - -
      - {{ macros.edit_post(form) }} - {% if settings.WIKI_ON and answer.wiki == False %} - {{ macros.checkbox_in_div(form.wiki) }} - {% endif %} -
      - -
      -   - -
      - -
      -{% endblock %} - -{% block sidebar %} -{% include "widgets/answer_edit_tips.html" %} -{% endblock %} - -{% block endjs %} -{% include "meta/editor_data.html" %} - - - - - - - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/ask.html b/lms/askbot/skins/mitx/templates/ask.html deleted file mode 100644 index e75a53bde2..0000000000 --- a/lms/askbot/skins/mitx/templates/ask.html +++ /dev/null @@ -1,67 +0,0 @@ -{% extends "two_column_body.html" %} -{% import "macros.html" as macros %} - -{% block title %}{% spaceless %}{% trans %}Ask a question{% endtrans %}{% endspaceless %}{% endblock %} -{% block forestyle %} - -{% endblock %} -{# main contents of ask form is in the template input_bar #} -{% block sidebar %} -{% include "widgets/question_edit_tips.html" %} -{% endblock %} -{% block content %} - {% include "widgets/ask_form.html" %} -{% endblock %} -{% block endjs %} - - - - - - - - {% include "meta/editor_data.html" %} - {% if mandatory_tags %} - {% include "meta/mandatory_tags_js.html" %} - {% endif %} - -{% endblock %} - - diff --git a/lms/askbot/skins/mitx/templates/badge.html b/lms/askbot/skins/mitx/templates/badge.html deleted file mode 100644 index dea62bc784..0000000000 --- a/lms/askbot/skins/mitx/templates/badge.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends "two_column_body.html" %} -{% import "macros.html" as macros %} -{%from "macros.html" import gravatar %} - -{% block title %}{% spaceless %}{% trans name=badge.name %}{{name}}{% endtrans %} - {% trans %}Badge{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} -
      -

      {% trans name=badge.name %}Badge "{{name}}"{% endtrans %}

      -
      - - - -
      - {% if badge.awarded_count %} -

      {{ badge_recipients|length|intcomma }} - {% trans num_awardees=badge_recipients|length %}user received this badge:{% pluralize %}users received this badge:{% endtrans %}

      - {% endif %} -
      - -
      - {% for recipient in badge_recipients %} -
      - -
      - {% endfor %} -
      - -{% endblock %} -{% block sidebar %} -

      Description

      -
      -

      - {% trans description=badge.description %}{{description}}{% endtrans %} -

      -
      -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/badges.html b/lms/askbot/skins/mitx/templates/badges.html deleted file mode 100644 index 64d3ee3030..0000000000 --- a/lms/askbot/skins/mitx/templates/badges.html +++ /dev/null @@ -1,66 +0,0 @@ -{% extends "two_column_body.html" %} - -{% block title %}{% spaceless %}{% trans %}Badges summary{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} -
      -

      {% trans %}Badges{% endtrans %}

      -
      -
      -

      - {% trans %}Community gives you awards for your questions, answers and votes.{% endtrans %}
      - {% trans %}Below is the list of available badges and number - of times each type of badge has been awarded. Give us feedback at {{feedback_faq_url}}. - {% endtrans %} -

      -
      -
        - {% for badge in badges %} -
      • -
        - {% for a in mybadges %} - {% if a.badge_id == badge.id %} - - {% endif %} - {% endfor %} -
        -
        -  {{badge.name}} - × {{ badge.awarded_count|intcomma }} -
        -

        {{badge.description}}

        -
      • - {% endfor %} -
      -{% endblock %} -{% block sidebar %} - -

      {% trans %}Community badges{% endtrans %}

      -
      - -
      -  {% trans %}gold{% endtrans %} -

      - {% trans %}gold badge description{% endtrans %} -

      -
      - -
      -  {% trans %}silver{% endtrans %} -

      - {% trans %}silver badge description{% endtrans %} -

      -
      - - -
      -  {% trans %}bronze{% endtrans %} -

      - {% trans %}bronze badge description{% endtrans %} -

      -
      - -
      -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/base.html b/lms/askbot/skins/mitx/templates/base.html deleted file mode 100644 index 4c5a36bd46..0000000000 --- a/lms/askbot/skins/mitx/templates/base.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - {% spaceless %} - - {% block title %}{% endblock %} - {% include "meta/html_head_meta.html" %} - - {% include "meta/html_head_stylesheets.html" %} - {% block forestyle %}{% endblock %} - {% include "meta/html_head_javascript.html" %} - {% if settings.USE_CUSTOM_HTML_HEAD %} - {{ settings.CUSTOM_HTML_HEAD }} - {% endif %} - - {% endspaceless %} - - {% include "widgets/system_messages.html" %} - {% include "debug_header.html" %} - {% include "widgets/header.html" %} {# Logo, user tool navigation and meta navitation #} - -
      - {# include "widgets/secondary_header.html" #} {# Scope selector, search input and ask button #} - -
      - {% block body %} - {% endblock %} -
      -
      - - {% if settings.FOOTER_MODE == 'default' %} - {% include "widgets/footer.html" %} - {% elif settings.FOOTER_MODE == 'customize' %} - {{ settings.CUSTOM_FOOTER }} - {% endif %} - {% include "custom_footer.html" ignore missing %} - {% include "meta/bottom_scripts.html" %} - {% block endjs %} - {% endblock %} - - - - diff --git a/lms/askbot/skins/mitx/templates/close.html b/lms/askbot/skins/mitx/templates/close.html deleted file mode 100644 index 6446ffd828..0000000000 --- a/lms/askbot/skins/mitx/templates/close.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "one_column_body.html" %} - -{% block title %}{% spaceless %}{% trans %}Close question{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} - -

      {% trans %}Close question{% endtrans %}

      -

      {% trans %}Close the question{% endtrans %}: - {{ question.get_question_title()|escape }} -

      -
      {% csrf_token %} -

      - {% trans %}Reasons{% endtrans %}: - {{ form.reason }} -

      -

      -   - -

      -
      -{% endblock %} -{% block endjs %} - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/course_navigation.jinja.html b/lms/askbot/skins/mitx/templates/course_navigation.jinja.html deleted file mode 100644 index d39fd22166..0000000000 --- a/lms/askbot/skins/mitx/templates/course_navigation.jinja.html +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/lms/askbot/skins/mitx/templates/faq_static.html b/lms/askbot/skins/mitx/templates/faq_static.html deleted file mode 100644 index b676f7939a..0000000000 --- a/lms/askbot/skins/mitx/templates/faq_static.html +++ /dev/null @@ -1,93 +0,0 @@ -{% extends "two_column_body.html" %} - -{% block title %}{% spaceless %}{% trans %}FAQ{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} -

      {% trans %}Frequently Asked Questions {% endtrans %}({% trans %}FAQ{% endtrans %})

      -

      {% trans %}What kinds of questions can I ask here?{% endtrans %}

      -

      {% trans %}Most importanly - questions should be relevant to this community.{% endtrans %} -{% trans %}Before asking the question - please make sure to use search to see whether your question has alredy been answered.{% endtrans %} -

      -

      {% trans %}What questions should I avoid asking?{% endtrans %}

      -

      {% trans %}Please avoid asking questions that are not relevant to this community, too subjective and argumentative.{% endtrans %} -

      -

      {% trans %}What should I avoid in my answers?{% endtrans %}

      -

      {{ settings.APP_TITLE }} {% trans %}is a Q&A site, not a discussion group. Therefore - please avoid having discussions in your answers, comment facility allows some space for brief discussions.{% endtrans %}

      -

      {% trans %}Who moderates this community?{% endtrans %}

      -

      {% trans %}The short answer is: you.{% endtrans %} -{% trans %}This website is moderated by the users.{% endtrans %} -{% trans %}The reputation system allows users earn the authorization to perform a variety of moderation tasks.{% endtrans %} -

      -

      {% trans %}How does reputation system work?{% endtrans %}

      -

      {% trans %}Rep system summary{% endtrans %}

      -

      {% trans MAX_REP_GAIN_PER_USER_PER_DAY=settings.MAX_REP_GAIN_PER_USER_PER_DAY, REP_GAIN_FOR_RECEIVING_UPVOTE=settings.REP_GAIN_FOR_RECEIVING_UPVOTE, REP_LOSS_FOR_RECEIVING_DOWNVOTE=settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE|absolute_value %}For example, if you ask an interesting question or give a helpful answer, your input will be upvoted. On the other hand if the answer is misleading - it will be downvoted. Each vote in favor will generate {{REP_GAIN_FOR_RECEIVING_UPVOTE}} points, each vote against will subtract {{REP_LOSS_FOR_RECEIVING_DOWNVOTE}} points. There is a limit of {{MAX_REP_GAIN_PER_USER_PER_DAY}} points that can be accumulated for a question or answer per day. The table below explains reputation point requirements for each type of moderation task.{% endtrans %} -

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% if settings.WIKI_ON %} - - - - - {% endif %} - - - - - - - - -
      {{settings.MIN_REP_TO_VOTE_UP}}{% trans %}upvote{% endtrans %}
      {{settings.MIN_REP_TO_LEAVE_COMMENTS}}{% trans %}add comments{% endtrans %}
      {{settings.MIN_REP_TO_VOTE_DOWN}}{% trans %}downvote{% endtrans %}
      {{settings.MIN_REP_TO_ACCEPT_OWN_ANSWER}}{% trans %} accept own answer to own questions{% endtrans %}
      {{settings.MIN_REP_TO_CLOSE_OWN_QUESTIONS}}{% trans %}open and close own questions{% endtrans %}
      {{settings.MIN_REP_TO_RETAG_OTHERS_QUESTIONS}}{% trans %}retag other's questions{% endtrans %}
      {{settings.MIN_REP_TO_EDIT_WIKI}}{% trans %}edit community wiki questions{% endtrans %}
      {{settings.MIN_REP_TO_EDIT_OTHERS_POSTS}}{% trans %}edit any answer{% endtrans %}
      {{settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS}}{% trans %}delete any comment{% endtrans %}
      -

      {% trans %}what is gravatar{% endtrans %}

      -{% trans %}gravatar faq info{% endtrans %} -

      {% trans %}To register, do I need to create new password?{% endtrans %}

      -

      {% trans %}No, you don't have to. You can login through any service that supports OpenID, e.g. Google, Yahoo, AOL, etc."{% endtrans %} -{% trans %}"Login now!"{% endtrans %} » -

      -

      {% trans %}Why other people can edit my questions/answers?{% endtrans %}

      -

      {% trans %}Goal of this site is...{% endtrans %} {% trans %}So questions and answers can be edited like wiki pages by experienced users of this site and this improves the overall quality of the knowledge base content.{% endtrans %} -{% trans %}If this approach is not for you, we respect your choice.{% endtrans %} -

      -

      {% trans %}Still have questions?{% endtrans %}

      -

      {% trans %}Please ask your question at {{ask_question_url}}, help make our community better!{% endtrans %} -

      - - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/feedback.html b/lms/askbot/skins/mitx/templates/feedback.html deleted file mode 100644 index 85b5d00a93..0000000000 --- a/lms/askbot/skins/mitx/templates/feedback.html +++ /dev/null @@ -1,68 +0,0 @@ -{% extends "two_column_body.html" %} - -{% block title %}{% spaceless %}{% trans %}Feedback{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} -

      {% trans %}Give us your feedback!{% endtrans %}

      -{% if form.errors %} - - {{form.errors.__all__}} - -{%endif%} -
      {% csrf_token %} - {% if user.is_authenticated() %} -

      - {% trans user_name=user.username %} - Dear {{user_name}}, we look forward to hearing your feedback. - Please type and send us your message below. - {% endtrans %} -

      - {% else %} -

      - {% trans %} - Dear visitor, we look forward to hearing your feedback. - Please type and send us your message below. - {% endtrans %} -

      -

      {{form.name}}
      -
      -
      {{form.email}} -
      -
      - -
      - {% endif %} -
      - - {% endif %} -
      - {{form.message}} -
      - {% if form.recaptcha %} -
      - {% if form.errors.recaptcha%} - {% trans %}(Please solve the captcha){% endtrans %} - - {% endif %} - {{form.recaptcha}} -
      - {% endif %} - {{form.next}} -
      -   - -
      -
      -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/feedback_email.txt b/lms/askbot/skins/mitx/templates/feedback_email.txt deleted file mode 100644 index a729066aba..0000000000 --- a/lms/askbot/skins/mitx/templates/feedback_email.txt +++ /dev/null @@ -1,13 +0,0 @@ -{% spaceless %} -{% trans site_title = settings.APP_SHORT_NAME|safe %} -Hello, this is a {{site_title}} forum feedback message. -{% endtrans %} -{% endspaceless %} -{% if user.is_authenticated() -%} - {{user.username|safe}} ({{user.email|safe}}, ip:{{request.META.REMOTE_ADDR}}) -{%- else %} - {%- if name %}{{name|safe}} {% else %}{% trans %}Anonymous{% endtrans %} {% endif -%} - ({%- if email %}, {% endif -%}ip:{{request.META.REMOTE_ADDR}}) -{%- endif %} wrote: - -{{message|safe}} diff --git a/lms/askbot/skins/mitx/templates/help.html b/lms/askbot/skins/mitx/templates/help.html deleted file mode 100644 index 7dc58f5d5c..0000000000 --- a/lms/askbot/skins/mitx/templates/help.html +++ /dev/null @@ -1,33 +0,0 @@ -{% extends "two_column_body.html" %} -{% block title %}{% trans %}Help{% endtrans %}{% endblock %} -{% block content %} -

      {% trans %}Help{% endtrans %}

      -

      - {% if request.user.is_authenticated() %} - {% trans username = request.user.username %}Welcome {{username}},{% endtrans %} - {% else %} - {% trans %}Welcome,{% endtrans %} - {% endif %} -

      -

      - {% trans %}Thank you for using {{app_name}}, here is how it works.{% endtrans %} -

      -

      - {% trans %}This site is for asking and answering questions, not for open-ended discussions.{% endtrans %} - {% trans %}We encourage everyone to use “question” space for asking and “answer” for answering.{% endtrans %} -

      -

      - {% trans %}Despite that, each question and answer can be commented – - the comments are good for the limited discussions.{% endtrans %} -

      -

      - {% trans %}Voting in {{app_name}} helps to select best answers and thank most helpful users.{% endtrans %} -

      - {% trans %}Please vote when you find helpful information, - it really helps the {{app_name}} community.{% endtrans %} - - {% trans %}Besides, you can @mention users anywhere in the text to point their attention, - follow users and conversations and report inappropriate content by flagging it.{% endtrans %} -

      -

      {% trans %}Enjoy.{% endtrans %}

      -{% endblock %} diff --git a/lms/askbot/skins/mitx/templates/import_data.html b/lms/askbot/skins/mitx/templates/import_data.html deleted file mode 100644 index affeaa735a..0000000000 --- a/lms/askbot/skins/mitx/templates/import_data.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "two_column_body.html" %} -{% block title %}{% trans %}Import StackExchange data{% endtrans %}{% endblock %} -{% block content %} -

      {% trans %}Import StackExchange data{% endtrans %}

      - {% if need_configuration %} -

      Note: to import stackexchange data, first add - 'askbot.importers.stackexchange', - to the INSTALLED_APPS setting in your settings.py - file, then run python manage.py syncdb, and restart the application. - After that, pleale return to here and try again. -

      - {% else %} -

      {% trans %}Warning: if your database is not empty, please back it up - before attempting this operation.{% endtrans %} -

      -

      {% trans %}Upload your stackexchange dump .zip file, then wait until - the data import completes. This process may take several minutes. - Please note that feedback will be printed in plain text. - {% endtrans %} -

      -
      {% csrf_token %} - - {{dump_upload_form.as_table()}} -
      - -
      -

      {% trans %}In the case you experience any difficulties in using this import tool, - please try importing your data via command line: python manage.py load_stackexchange path/to/your-data.zip{% endtrans %} -

      - {% endif %} -{% endblock %} diff --git a/lms/askbot/skins/mitx/templates/instant_notification.html b/lms/askbot/skins/mitx/templates/instant_notification.html deleted file mode 100644 index 92799a963f..0000000000 --- a/lms/askbot/skins/mitx/templates/instant_notification.html +++ /dev/null @@ -1,42 +0,0 @@ -{% trans %}

      Dear {{receiving_user_name}},

      {% endtrans %} - {% if update_type == 'question_comment' %} -{% trans %} -

      {{update_author_name}} left a new comment:

      -{% endtrans %} - {% endif %} - {% if update_type == 'answer_comment' %} -{% trans %} -

      {{update_author_name}} left a new comment

      -{% endtrans %} - {% endif %} - {% if update_type == 'new_answer' %} -{% trans %} -

      {{update_author_name}} answered a question -{{origin_post_title}}

      -{% endtrans %} - {% endif %} - {% if update_type == 'new_question' %} -{% trans %} -

      {{update_author_name}} posted a new question -{{origin_post_title}}

      -{% endtrans %} - {% endif %} - {%if update_type == 'answer_update' %} -{% trans %} -

      {{update_author_name}} updated an answer to the question -{{origin_post_title}}

      -{% endtrans %} - {% endif %} - {% if update_type == 'question_update' %} -{% trans %} -

      {{update_author_name}} updated a question -{{origin_post_title}}

      -{% endtrans %} - {% endif %} -

      -{% trans %} -
      {{content_preview}}
      -

      Please note - you can easily change -how often you receive these notifications or unsubscribe. Thank you for your interest in our forum!

      -{% endtrans %} -{% trans %}

      Sincerely,
      Forum Administrator

      {% endtrans %} diff --git a/lms/askbot/skins/mitx/templates/macros.html b/lms/askbot/skins/mitx/templates/macros.html deleted file mode 100644 index 855bf9efe5..0000000000 --- a/lms/askbot/skins/mitx/templates/macros.html +++ /dev/null @@ -1,625 +0,0 @@ -{% load extra_filters_jinja %} - -{%- macro share(site = None, site_label = None, icon = False) -%} - {% if icon == False %}{% if site_label %}{{ site_label }}{% else %}{{ site }}{% endif %}{% endif %} -{%- endmacro -%} - -{%- macro follow_toggle(follow, name, alias, id) -%} - {# follow - boolean; name - object type name; alias - e.g. users name; id - object id #} - -{%- endmacro -%} - -{%- macro post_vote_buttons(post = None, visitor_vote = None) -%} - -
        -
      • -
      • {{ post.score }}
      • -
      • -
      - -{%- endmacro -%} - -{%- macro post_contributor_avatar_and_credentials(post, user) -%} - {% if post.is_anonymous %} - -

      {{ user.get_anonymous_name() }}

      - {% else %} - - - -
      - {{ user.get_profile_link()}} - - {{ user_score_and_badge_summary(user) }} - - {{ user_website_link(user) }} -
      - {% endif %} -{%- endmacro -%} - -{%- macro post_last_updater_and_creator_info(post, min_rep_to_edit_wiki) -%} - {{ post_contributor_info(post, "original_author", post.wiki, min_rep_to_edit_wiki) }} - {{ post_contributor_info(post, "last_updater", post.wiki, min_rep_to_edit_wiki) }} -{%- endmacro -%} - -{%- macro post_contributor_info(post, contributor_type, is_wiki, wiki_min_rep) -%} -{# there is a whole bunch of trickery here, probably indicative of -poor design of the data or methods on data objects #} -{% if contributor_type=="original_author" %} -
      - {% if is_wiki %} -

      - {%- if post.post_type == 'question' -%} - {%- trans %}Asked{% endtrans %} - {% elif post.post_type == 'answer' %} - {%- trans %}Answered{% endtrans %} - {% else %} - {%- trans %}Posted{% endtrans %} - {% endif %} - {{post.added_at|diff_date}} -

      - {% trans %}this post is marked as community wiki{% endtrans %} -

      {% trans %}This post is a wiki. - Anyone with karma >{{wiki_min_rep}} is welcome to improve it.{% endtrans %}

      - {% else %} -
      - {% if post.post_type == 'question' %} - {% trans %}Asked{% endtrans %} - {% elif post.post_type == 'answer' %} - {% trans %}Answered{% endtrans %} - {% else %} - {% trans %}Posted{% endtrans %} - {% endif %} - {% if post.__class__.__name__ == 'PostRevision' %} - {{post.revised_at|diff_date}} by - {% else %} - {{post.added_at|diff_date}} by - {% endif %} -
      - {{ post_contributor_avatar_and_credentials(post, post.author) }} - {% endif %} -
      -{% elif contributor_type=="last_updater" %} - {% if post.post_type in ('question', 'answer') %} - {% set last_edited_at = post.last_edited_at %} - {% set original_author = post.author %} - {% set update_author = post.last_edited_by %} - {% elif post.__class__.__name__ == 'PostRevision' %} - {% set last_edited_at = post.revised_at %} - {% set original_author = None %}{# fake value to force display widget in the revision views #} - {% set update_author = post.author %} - {% endif %} - {% if last_edited_at %} -
      - {% trans %}Updated{% endtrans %} {{ last_edited_at|diff_date }} - {% if original_author != update_author or is_wiki %} - {{ post_contributor_avatar_and_credentials(post, update_author) }} - {% endif %} -
      - {% endif %} -{% endif %} -{%- endmacro -%} - -{%- macro if_else(condition, if_true, if_false) -%} - {%- if condition == True -%} - {{if_true}} - {%- else -%} - {{if_false}} - {%- endif -%} -{%- endmacro -%} - -{%- macro tag_cloud(tags = None, font_sizes = None) -%} - {% for tag in tags %} - - {{ tag.name }} - - {% endfor %} -{%- endmacro -%} - -{%- macro tag_list_widget( - tags, - id = None, - deletable = False, - make_links = True, - search_state = None, - css_class = None, - tag_css_class = None, - tag_html_tag = 'li' - ) --%} -
        - {% if tags %} - {% for tag in tags %} - {{ tag_widget( - tag, - css_class = tag_css_class, - deletable = deletable, - is_link = make_links, - search_state = search_state, - html_tag = tag_html_tag - )}} - {% endfor %} - {% endif %} -
      -{%- endmacro -%} - -{# todo: remove the extra content argument to make its usage more explicit #} -{%- macro tag_widget( - tag, - deletable = False, - is_link = True, - delete_link_title = None, - css_class = None, - search_state = None, - html_tag = 'div', - extra_content = '' - ) --%} - {% if not search_state %} {# get empty SearchState() if there's none; CAUTION: for some reason this doesn't work inside `spaceless` tag below! #} - {% set search_state=search_state|get_empty_search_state %} - {% endif %} - {% spaceless %} - <{{ html_tag }} class="tag-left{% if deletable %} deletable-tag{% endif %}"> - <{% if not is_link or tag[-1] == '*' %}span{% else %}a{% endif %} - class="tag tag-right{% if css_class %} {{ css_class }}{% endif %}" - {% if is_link %} - href="{{ search_state.add_tag(tag).full_url() }}" - title="{% trans %}see questions tagged '{{ tag }}'{% endtrans %}" - {% endif %} - rel="tag" - >{{ tag|replace('*', '✽')|truncate(20,True)}} - {% if deletable %} -
      - {% endif %} - - {{ extra_content }} - {% endspaceless %} -{%- endmacro -%} - -{%- macro radio_select(name = None, value = None, choices = None) -%} - {% for choice in choices %} -

      - {% set id = "id_" ~ name ~ "_" ~ choice[0] %} - - -

      - {% endfor %} -{%- endmacro -%} - -{%- macro question_summary(thread, question, extra_class=None, search_state=None) -%} -{%include "widgets/question_summary.html" %} -{%- endmacro -%} - -{# Warning! Any changes to the comment markup here must be duplicated in post.js -for the purposes of the AJAX comment editor #} - -{%- macro add_or_show_comments_button(post = None, can_post = None, max_comments = None, widget_id = None) -%} - - {% if post.comment_count > max_comments %} - {% set remaining_comments = post.comment_count - max_comments %} - - {% if can_post %} - {% trans %}add comment{% endtrans %} / - {% trans counter=remaining_comments %}see {{counter}} more{% pluralize %}see {{counter}} more{% endtrans %} - {% else %} - {% trans counter=remaining_comments %}see {{counter}} more comment{% pluralize %}see {{counter}} more comments - {% endtrans %} - {% endif %} - - {% elif can_post %} - {% trans %}add comment{% endtrans %} - {% endif %} -{%- endmacro -%} - -{%- macro post_comments_widget( - post=None, - show_post = None, - show_comment = None, - show_comment_position = None, - user=None, - max_comments=None - ) --%} - {% spaceless %} - - - - {% set widget_id = 'comments-for-' + post.post_type + '-' + post.id|string %} -
      -
      - {% if show_post == post and show_comment and show_comment_position > max_comments %} - {% set comments = post._cached_comments[:show_comment_position] %} - {% else %} - {% set comments = post._cached_comments[:max_comments] %} - {% endif %} - {% for comment in comments %} - {# Warning! Any changes to the comment markup IN THIS `FOR` LOOP must be duplicated in post.js - for the purposes of the AJAX comment editor #} -
      - {% if comment.author.is_administrator() %}Staff comment{%endif%} - {% if user|can_delete_comment(comment) %} - - {% endif %} - -
      - {{comment.html}} -
      - {{comment.author.username}} -  ({{comment.added_at|diff_date}}) -
      -
      -
      - {% endfor %} -
      -
      - {% set can_post = user.is_authenticated() and user.can_post_comment(post) %} - {% if show_post == post and show_comment %} - {% if show_comment_position > max_comments %} - {{ add_or_show_comments_button( - post = post, - can_post = can_post, - max_comments = show_comment_position, - widget_id = widget_id) }} - {% else %} - {{ add_or_show_comments_button( - post = post, - can_post = can_post, - max_comments = max_comments, - widget_id = widget_id) }} - {% endif %} - {% else %} - {{ add_or_show_comments_button( - post = post, - can_post = can_post, - max_comments = max_comments, - widget_id = widget_id) }} - {% endif %} -
      -
      - {% endspaceless %} -{%- endmacro -%} - -{%- macro reversible_sort_button(button_sort_criterium=None, asc_tooltip=None, - desc_tooltip=None, label=None, current_sort_method=None, search_state=None) -%} -{# - sort button where descending sort is default - and the search method is togglable between ascending and descending - buttons are rendered as links with id constructed as - "by_" + button_sort_criterium - class "on" is added when current_sort_method is one of - button_sort_criterium + "asc" or "desc" -#} - {% set key_name = button_sort_criterium %} - {% if current_sort_method == key_name + "-asc" %}{# "worst" first #} - {{label}} ▲ - {% elif current_sort_method == key_name + "-desc" %}{# "best first" #} - {{label}} ▼ - {% else %}{# default, when other button is active #} - {{label}} - {% endif %} - -{%- endmacro %} - -{%- macro checkbox_in_div(checkbox_field, class = 'checkbox') -%} - - {{ checkbox_field }} - {{ checkbox_field.label_tag() }} - {{ checkbox_field.errors }} - -{%- endmacro -%} - -{%- macro edit_post( - post_form, - post_type = None, - mandatory_tags = None, - edit_title = False - ) --%} -{%include "widgets/edit_post.html" %} -{%- endmacro -%} - -{%- macro tag_autocomplete_js(id = '#id_tags') -%} - var tagAc = new AutoCompleter({ - url: '{% url "get_tag_list" %}', - preloadData: true, - minChars: 1, - useCache: true, - matchInside: true, - maxCacheLength: 100, - delay: 10 - }); - tagAc.decorate($("{{ id }}")); -{%- endmacro -%} - -{%- macro answer_classes(answer, question) -%} -answer {% if answer.accepted() %}accepted-answer{% endif %} {% if answer.author_id==question.author_id %} answered-by-owner{% endif %} {% if answer.deleted %}deleted{% endif -%} -{%- endmacro -%} - -{%- macro user_score_and_badge_summary(user) -%} - {%include "widgets/user_score_and_badge_summary.html"%} -{%- endmacro -%} - -{%- macro follow_toggle(follow, name, alias, id) -%} - {# follow - boolean; name - object type name; alias - e.g. users name; id - object id #} - -{%- endmacro -%} - -{%- macro follow_user_toggle(visitor = None, subject = None) -%} - {% if visitor.is_anonymous() %} - {{ follow_toggle(True, 'user', subject.username, subject.id) }} - {% else %} - {% if visitor != subject %} - {% if visitor.is_following(subject) %} - {{ follow_toggle(False, 'user', subject.username, subject.id) }} - {% else %} - {{ follow_toggle(True, 'user', subject.username, subject.id) }} - {% endif %} - {% endif %} - {% endif %} -{%- endmacro -%} - -{%- macro user_long_score_and_badge_summary(user) -%} - {% include "widgets/user_long_score_and_badge_summary.html" %} -{%- endmacro -%} - -{%- macro user_country_flag(user) -%} - {% if user.country and user.show_country %} - {% trans 
-                    country=user.country.name 
-                    %}flag of {{country}}{% 
-                endtrans %} - {% endif %} -{%- endmacro -%} - -{%- macro user_country_name_and_flag(user) -%} - {% if user.country and user.show_country %} - {{ user.country.name }} - {{ user_country_flag(user) }} - {% endif %} -{%- endmacro -%} - -{%- macro user_full_location(user) -%} - {% if user.location %} - {{ user.location }}, - {% endif %} - {{ user_country_name_and_flag(user) }} -{%- endmacro -%} - -{%- macro user_list(users, profile_section = None) -%} -{% include "widgets/user_list.html"%} -{%- endmacro -%} - -{#todo: rename this to avatar #} -{%- macro gravatar(user, size) -%} -{% spaceless %} -{% trans username=user.username %}{{username}} gravatar image{% endtrans %} -{% endspaceless %} -{%- endmacro -%} - -{%- macro user_website_link(user, max_display_length=25) -%} - {% if user.website %} - - {{user.website|truncate(length=max_display_length, killwords=True, end='...')}} - - {% endif %} -{%- endmacro -%} - -{%- macro paginator(p, position='left', anchor='') -%}{# p is paginator context dictionary #} -{% spaceless %} - {% if p.is_paginated %} -
      - {% if p.has_previous %} - - ‹ {% trans %}previous{% endtrans %} - {% endif %} - {% if not p.in_leading_range %} - {% for num in p.pages_outside_trailing_range %} - {{ num }} - {% endfor %} - ... - {% endif %} - - {% for num in p.page_numbers %} - {% if num == p.page and p.pages != 1%} - {{ num }} - {% else %} - {{ num }} - {% endif %} - {% endfor %} - - {% if not p.in_trailing_range %} - ... - {% for num in p.pages_outside_leading_range|reverse %} - {{ num }} - {% endfor %} - {% endif %} - {% if p.has_next %} - {% trans %}next page{% endtrans %} › - {% endif %} -
      - {% endif %} -{% endspaceless %} -{%- endmacro -%} - - - -{%- macro paginator_main_page(p, position, search_state) -%} {# p is paginator context dictionary #} - {% spaceless %} - {% if p.is_paginated %} -
      - {% if p.has_previous %} - - « {% trans %}previous{% endtrans %} - {% endif %} - {% if not p.in_leading_range %} - {% for num in p.pages_outside_trailing_range %} - {{ num }} - {% endfor %} - ... - {% endif %} - - {% for num in p.page_numbers %} - {% if num == p.page and p.pages != 1%} - {{ num }} - {% else %} - {{ num }} - {% endif %} - {% endfor %} - - {% if not p.in_trailing_range %} - ... - {% for num in p.pages_outside_leading_range|reverse %} - {{ num }} - {% endfor %} - {% endif %} - {% if p.has_next %} - {% trans %}next page{% endtrans %} » - {% endif %} -
      - {% endif %} - {% endspaceless %} -{%- endmacro -%} - - -{%- macro inbox_link(user) -%} - {% if user.new_response_count > 0 or user.seen_response_count > 0 %} - - {% trans username=user.username %}responses for {{username}}{% endtrans %} 0 %} - src="{{ "/images/mail-envelope-full.png"|media }}" - title="{% trans response_count=user.new_response_count %}you have a new response{% pluralize %}you have {{response_count}} new responses{% endtrans %}" - {% elif user.seen_response_count > 0 %} - src="{{ "/images/mail-envelope-empty.png"|media }}" - title="{% trans %}no new responses yet{% endtrans %}" - {% endif %} - /> - - {% endif %} -{%- endmacro -%} - -{%- macro moderation_items_link(user, moderation_items) -%} - {% if moderation_items %} - - {% if moderation_items['new_count'] > 0 %} - 0 %} - alt="{% trans new=moderation_items['new_count'], seen=moderation_items['seen_count']%}{{new}} new flagged posts and {{seen}} previous{% endtrans %}" - title="{% trans new=moderation_items['new_count'], seen=moderation_items['seen_count']%}{{new}} new flagged posts and {{seen}} previous{% endtrans %}" - {% else %} - alt="{% trans new=moderation_items['new_count'] %}{{new}} new flagged posts{% endtrans %}" - title="{% trans new=moderation_items['new_count'] %}{{new}} new flagged posts{% endtrans %}" - {% endif %} - /> - {% elif moderation_items['seen_count'] > 0 %} - {% trans seen=moderation_items['seen_count'] %}{{seen}} flagged posts{% endtrans %} - {% endif %} - - {% endif %} -{%- endmacro -%} diff --git a/lms/askbot/skins/mitx/templates/main_page.html b/lms/askbot/skins/mitx/templates/main_page.html deleted file mode 100644 index eef655fbf6..0000000000 --- a/lms/askbot/skins/mitx/templates/main_page.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "two_column_body.html" %} -{# - this template is split into several - blocks that are included here - the blocks are within directory templates/main_page - relative to the skin directory - - there is no html markup in this file -#} - -{% block title %}{% spaceless %}{% trans %}Questions{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} - {% include "main_page/tab_bar.html" %} - {# include "main_page/headline.html" #} - {# ==== BEGIN: main_page/content.html === #} -
        - {% include "main_page/questions_loop.html" %} -
      - {# ==== END: main_page/content.html === #} - {% include "main_page/paginator.html" %} -{% endblock %} -{% block sidebar %} - {% include "main_page/sidebar.html" %} -{% endblock %} -{% block endjs %} - {% include "main_page/javascript.html" %} -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/main_page/content.html b/lms/askbot/skins/mitx/templates/main_page/content.html deleted file mode 100644 index 022d172d83..0000000000 --- a/lms/askbot/skins/mitx/templates/main_page/content.html +++ /dev/null @@ -1,3 +0,0 @@ -
        - {% include "main_page/questions_loop.html" %} -
      diff --git a/lms/askbot/skins/mitx/templates/main_page/headline.html b/lms/askbot/skins/mitx/templates/main_page/headline.html deleted file mode 100644 index 5f48e1c54a..0000000000 --- a/lms/askbot/skins/mitx/templates/main_page/headline.html +++ /dev/null @@ -1,42 +0,0 @@ -{% import "macros.html" as macros %} -{% if questions_count > 0 %} -

      - {% trans cnt=questions_count, q_num=questions_count|intcomma %}{{q_num}} question{% pluralize %}{{q_num}} questions{% endtrans %} - {% if author_name %} - {% trans %}with {{author_name}}'s contributions{% endtrans %} - {% endif %} -

      - {% if search_tags %} -
      - {% trans %}Tagged{% endtrans %} - {{ macros.tag_list_widget( - search_tags, - id = 'searchTags', - deletable = True, - make_links = False, - search_state = search_state - ) - }} -
      - {% endif %} - {% if author_name or search_tags or query %} -
      {% trans %}Search tips:{% endtrans %} - {% if reset_method_count > 1 %} - {% if author_name %} - {% trans %}reset author{% endtrans %} - {% endif %} - {% if search_tags %}{% if author_name and query %}, {% elif author_name %}{% trans %} or {% endtrans %}{% endif %} - {% trans %}reset tags{% endtrans %} - {% endif %} - {% if query %}{% trans %} or {% endtrans %} - {% trans %}start over{% endtrans %} - {% endif %} - {% else %} - {% trans %}start over{% endtrans %} - {% endif %} - {% trans %} - to expand, or dig in by adding more tags and revising the query.{% endtrans %} -
      - {% else %} -
      {% trans %}Search tip:{% endtrans %} {% trans %}add tags and a query to focus your search{% endtrans %}
      - {% endif %} -{% endif %} diff --git a/lms/askbot/skins/mitx/templates/main_page/javascript.html b/lms/askbot/skins/mitx/templates/main_page/javascript.html deleted file mode 100644 index 4c51cfd91c..0000000000 --- a/lms/askbot/skins/mitx/templates/main_page/javascript.html +++ /dev/null @@ -1,32 +0,0 @@ - - -{% if request.user.is_authenticated() %} - -{% endif %} - - diff --git a/lms/askbot/skins/mitx/templates/main_page/nothing_found.html b/lms/askbot/skins/mitx/templates/main_page/nothing_found.html deleted file mode 100644 index 3811700f98..0000000000 --- a/lms/askbot/skins/mitx/templates/main_page/nothing_found.html +++ /dev/null @@ -1,30 +0,0 @@ -{# todo: add tips to widen selection #} -{% if search_state.scope == "unanswered" %} -

      - {% trans %}There are no unanswered questions here{% endtrans %} -

      -{% endif %} -{% if search_state.scope == "favorite" %} -

      - {% trans %}No questions here. {% endtrans %} - {% trans %}Please follow some questions or follow some users.{% endtrans %} -

      -{% endif %} - -{% if search_state.query or search_state.tags or search_state.author %} - {% if reset_method_count > 1 %} - {% if search_state.author %} - {% trans %}You can expand your search by {% endtrans %}{% trans %}resetting author{% endtrans %} - {% endif %} - {% if search_state.tags %}{% if search_state.author and search_state.query %}, {% elif search_state.author %}{% trans %} or {% endtrans %}{% endif %} - {% trans %}You can expand your search by {% endtrans %}{% trans %}resetting tags{% endtrans %} - {% endif %} - {% if search_state.query %} - {% trans %}You can expand your search by {% endtrans %}{% trans %}starting over{% endtrans %} - {% endif %} - {% else %} - {% trans %}You can expand your search by {% endtrans %}{% trans %}starting over{% endtrans %} - {% endif %} - -{% endif %} - {% trans %}Ask a question{% endtrans %} diff --git a/lms/askbot/skins/mitx/templates/main_page/paginator.html b/lms/askbot/skins/mitx/templates/main_page/paginator.html deleted file mode 100644 index e7dc246a0a..0000000000 --- a/lms/askbot/skins/mitx/templates/main_page/paginator.html +++ /dev/null @@ -1,7 +0,0 @@ -{% import "macros.html" as macros %} -{% if questions_count > page_size %} -
      - {{ macros.paginator_main_page(context|setup_paginator, position='left', search_state=search_state) }} -
      -
      -{% endif %} diff --git a/lms/askbot/skins/mitx/templates/main_page/questions_loop.html b/lms/askbot/skins/mitx/templates/main_page/questions_loop.html deleted file mode 100644 index ad23b7f4bf..0000000000 --- a/lms/askbot/skins/mitx/templates/main_page/questions_loop.html +++ /dev/null @@ -1,14 +0,0 @@ -{% import "macros.html" as macros %} -{# cache 0 "questions" questions search_tags scope sort query context.page language_code #} -{% for thread in threads.object_list %} - {{ macros.question_summary(thread, thread._question_post(), search_state=search_state) }} -{% endfor %} -{% if threads.object_list|length == 0 %} - {% include "main_page/nothing_found.html" %} -{% else %} -
      - {% trans %}Did not find what you were looking for?{% endtrans %} - {% trans %}Please, post your question!{% endtrans %} -
      -{% endif %} - diff --git a/lms/askbot/skins/mitx/templates/main_page/scope_filters.html b/lms/askbot/skins/mitx/templates/main_page/scope_filters.html deleted file mode 100644 index 96ab0f0f9c..0000000000 --- a/lms/askbot/skins/mitx/templates/main_page/scope_filters.html +++ /dev/null @@ -1,16 +0,0 @@ -
      - -
      - diff --git a/lms/askbot/skins/mitx/templates/main_page/sidebar.html b/lms/askbot/skins/mitx/templates/main_page/sidebar.html deleted file mode 100644 index d6ee573ad1..0000000000 --- a/lms/askbot/skins/mitx/templates/main_page/sidebar.html +++ /dev/null @@ -1,34 +0,0 @@ -{% import "macros.html" as macros %} - -

      Course Discussion

      - -{% if request.user.is_authenticated() %} - -{% endif %} - - - - -{{ settings.SIDEBAR_MAIN_HEADER }} - -{% if contributors and settings.SIDEBAR_MAIN_SHOW_AVATARS %} - {# include "widgets/contributors.html" #} -{% endif %} - -{% if request.user.is_authenticated() and settings.SIDEBAR_MAIN_SHOW_TAG_SELECTOR %} - {% include "widgets/tag_selector.html" %} -{% endif %} - -{% if tags and settings.SIDEBAR_MAIN_SHOW_TAGS %} - {% include "widgets/related_tags.html" %} -{% endif %} - - - -{{ settings.SIDEBAR_MAIN_FOOTER }} diff --git a/lms/askbot/skins/mitx/templates/main_page/tab_bar.html b/lms/askbot/skins/mitx/templates/main_page/tab_bar.html deleted file mode 100644 index 658cb7ccdb..0000000000 --- a/lms/askbot/skins/mitx/templates/main_page/tab_bar.html +++ /dev/null @@ -1,119 +0,0 @@ -{% import "macros.html" as macros %} -{% load extra_filters_jinja %} -{% cache 0 "scope_sort_tabs" search_tags request.user scope sort query context.page language_code %} - -
      -
      - -
      - - {% if questions_count > 0 %} -

      - {% trans cnt=questions_count, q_num=questions_count|intcomma %}{{q_num}} question{% pluralize %}{{q_num}} questions{% endtrans %} - - {% if author_name %} - {% trans %}with {{author_name}}'s contributions{% endtrans %} - {% endif %} -

      - - - - {% else %} - -

      No questions found

      - - {% endif %} -
      - - - {% if questions_count > 0 %} -
      - -
      - {% endif %} - -
      - - {% if search_tags %} -
      - - - -
      - - {{ macros.tag_list_widget( search_tags, id = 'searchTags', deletable = True, make_links = False, search_state = search_state) }} -
      - - - -
      - {% endif %} - -
      - -{% endcache %} diff --git a/lms/askbot/skins/mitx/templates/meta/bottom_scripts.html b/lms/askbot/skins/mitx/templates/meta/bottom_scripts.html deleted file mode 100644 index 4e189f598c..0000000000 --- a/lms/askbot/skins/mitx/templates/meta/bottom_scripts.html +++ /dev/null @@ -1,107 +0,0 @@ -{# most, if not all javascripts should go here - this template is included at the very bottow of the - main template "base.html" -#} -
      - -
      - - - -{# - -#} - - - - -{% if settings.ENABLE_MATHJAX %} - -{% endif %} - -{% if settings.USE_CUSTOM_JS %} - -{% endif %} -{% if settings.GOOGLE_ANALYTICS_KEY %} - - -{% endif %} diff --git a/lms/askbot/skins/mitx/templates/meta/editor_data.html b/lms/askbot/skins/mitx/templates/meta/editor_data.html deleted file mode 100644 index 025be8a03e..0000000000 --- a/lms/askbot/skins/mitx/templates/meta/editor_data.html +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/lms/askbot/skins/mitx/templates/meta/html_head_javascript.html b/lms/askbot/skins/mitx/templates/meta/html_head_javascript.html deleted file mode 100644 index 2d453215e8..0000000000 --- a/lms/askbot/skins/mitx/templates/meta/html_head_javascript.html +++ /dev/null @@ -1,11 +0,0 @@ - - -{% block forejs %} -{% endblock %} -{# avoid adding javascript here so that pages load faster #} diff --git a/lms/askbot/skins/mitx/templates/meta/html_head_meta.html b/lms/askbot/skins/mitx/templates/meta/html_head_meta.html deleted file mode 100644 index 352ffb534b..0000000000 --- a/lms/askbot/skins/mitx/templates/meta/html_head_meta.html +++ /dev/null @@ -1,8 +0,0 @@ - -{% block meta_description %} - -{% endblock %} - -{% if settings.GOOGLE_SITEMAP_CODE %} - -{% endif %} diff --git a/lms/askbot/skins/mitx/templates/meta/html_head_stylesheets.html b/lms/askbot/skins/mitx/templates/meta/html_head_stylesheets.html deleted file mode 100644 index a6a3d3cd46..0000000000 --- a/lms/askbot/skins/mitx/templates/meta/html_head_stylesheets.html +++ /dev/null @@ -1,3 +0,0 @@ -{% load extra_filters_jinja %} -{{ 'application' | compressed_css }} -{{ 'course' | compressed_css }} diff --git a/lms/askbot/skins/mitx/templates/meta/mandatory_tags_js.html b/lms/askbot/skins/mitx/templates/meta/mandatory_tags_js.html deleted file mode 100644 index f04a6345b7..0000000000 --- a/lms/askbot/skins/mitx/templates/meta/mandatory_tags_js.html +++ /dev/null @@ -1,25 +0,0 @@ - diff --git a/lms/askbot/skins/mitx/templates/navigation.jinja.html b/lms/askbot/skins/mitx/templates/navigation.jinja.html deleted file mode 100644 index 686ae3a724..0000000000 --- a/lms/askbot/skins/mitx/templates/navigation.jinja.html +++ /dev/null @@ -1,27 +0,0 @@ -
      - -
      diff --git a/lms/askbot/skins/mitx/templates/question.html b/lms/askbot/skins/mitx/templates/question.html deleted file mode 100644 index b2462faf69..0000000000 --- a/lms/askbot/skins/mitx/templates/question.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "two_column_body.html" %} - -{% block title %}{% spaceless %}{{ question.get_question_title()|escape }}{% endspaceless %}{% endblock %} -{% block meta_description %} - -{% endblock %} -{% block keywords %}{{thread.tagname_meta_generator()}}{% endblock %} -{% block forestyle %} - - -{% endblock %} -{% block content %} - {# ==== BEGIN: question/content.html ==== #} - {% include "question/content.html" %} - {# ==== END: question/content.html ==== #} -{% endblock %} -{% block sidebar %} - {%include "question/sidebar.html" %} -{% endblock %} -{% block endjs %} - {%include "question/javascript.html" %} -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/question/answer_card.html b/lms/askbot/skins/mitx/templates/question/answer_card.html deleted file mode 100644 index 1af4496c99..0000000000 --- a/lms/askbot/skins/mitx/templates/question/answer_card.html +++ /dev/null @@ -1,41 +0,0 @@ -
      - - - {% if answer.old_answer_id %} - {# Make old URL anchors/hashes work #} - - {% endif %} - -
      - {# ==== START: question/answer_vote_buttons.html ==== #} - {% include "question/answer_vote_buttons.html" %} - {# ==== END: question/answer_vote_buttons.html ==== #} -
      - -
      - -
      - -
      - {{ answer.html }} - {# ==== START: question/question_comments.html ==== #} - {% include "question/answer_comments.html" %} - {# ==== END: question/question_comments.html ==== #} -
      - - {# ==== START: "question/question_author_info.html" #} -
      - - {% if answer.author.is_administrator() %}
      Staff answer
      {%endif%} - {% include "question/answer_author_info.html" %} -
      - {% include "question/answer_controls.html" %} -
      -
      - {# ==== END: "question/question_author_info.html" #} - -
      - -
      - -
      diff --git a/lms/askbot/skins/mitx/templates/question/answer_tab_bar.html b/lms/askbot/skins/mitx/templates/question/answer_tab_bar.html deleted file mode 100644 index d8a5626a4e..0000000000 --- a/lms/askbot/skins/mitx/templates/question/answer_tab_bar.html +++ /dev/null @@ -1,29 +0,0 @@ -
      -
      -

      - {% trans counter=answer_count %} - {{counter}} Answer - {% pluralize %} - {{counter}} Answers - {% endtrans %} -

      -
      - -
      diff --git a/lms/askbot/skins/mitx/templates/question/content.html b/lms/askbot/skins/mitx/templates/question/content.html deleted file mode 100644 index 33ebf3fcc0..0000000000 --- a/lms/askbot/skins/mitx/templates/question/content.html +++ /dev/null @@ -1,42 +0,0 @@ -{% import "macros.html" as macros %} - -{# ==== BEGIN: question/question_card.html ==== #} -{% include "question/question_card.html" %} -{# ==== END: question/question_card.html ==== #} -{% if thread.closed %} - {# ==== START: question/closed_question_info.html ==== #} - {% include "question/closed_question_info.html" %} - {# ==== END: question/closed_question_info.html ==== #} -{% endif %} - -{% if answers %} - - {# ==== START: question/answer_tab_bar.html ==== #} - {% include "question/answer_tab_bar.html" %} - {# ==== END: question/answer_tab_bar.html ==== #} - - - - {% for answer in answers %} - {# ==== START: question/answer_card.html ==== #} - {% include "question/answer_card.html" %} - {# ==== END: question/answer_card.html ==== #} - {% endfor %} - - {{ macros.paginator(paginator_context, anchor='#sort-top') }} - -{% else %} - {# ==== START: question/sharing_prompt_phrase.html ==== #} - {% include "question/sharing_prompt_phrase.html" %} - {# ==== END: question/sharing_prompt_phrase.html ==== #} -{% endif %} - -{# ==== START: question/new_answer_form.html ==== #} -{% include "question/new_answer_form.html" %} -{# ==== END: question/new_answer_form.html ==== #} - -{% if request.user == question.author %}{# this is outside the form on purpose #} -
      - -
      -{%endif%} diff --git a/lms/askbot/skins/mitx/templates/question/javascript.html b/lms/askbot/skins/mitx/templates/question/javascript.html deleted file mode 100644 index 96f001a952..0000000000 --- a/lms/askbot/skins/mitx/templates/question/javascript.html +++ /dev/null @@ -1,91 +0,0 @@ -{% if not thread.closed %} - - - - -{% endif %} - - - -{% include "meta/editor_data.html" %} diff --git a/lms/askbot/skins/mitx/templates/question/new_answer_form.html b/lms/askbot/skins/mitx/templates/question/new_answer_form.html deleted file mode 100644 index e36de02e51..0000000000 --- a/lms/askbot/skins/mitx/templates/question/new_answer_form.html +++ /dev/null @@ -1,59 +0,0 @@ -
      {% csrf_token %} - - {% if request.user.is_anonymous() and settings.ALLOW_POSTING_BEFORE_LOGGING_IN == False %} - {% if not thread.closed %} - {% trans %}Login/Signup to Answer{% endtrans %} - {% endif %} - {% else %} - {% if not thread.closed %} -
      -

      - {% if answers %} - {% trans %}Your answer{% endtrans %} - {% else %} - {% trans %}Be the first one to answer this question!{% endtrans %} - {% endif %} -

      -
      - - {% if request.user.is_anonymous() %} -
      {% trans %}you can answer anonymously and then login{% endtrans %}
      - {% else %} -

      - {% if request.user==question.author %} - {% trans %}answer your own question only to give an answer{% endtrans %} - {% else %} - {% trans %}please only give an answer, no discussions{% endtrans %} - {% endif %} -

      - {% endif %} - - {# ==== START: question/subscribe_by_email_prompt.html ==== #} - {# include "question/subscribe_by_email_prompt.html" #} - {# ==== END: question/subscribe_by_email_prompt.html ==== #} - - {{ macros.edit_post(answer) }} - -
      - - {% if settings.WIKI_ON %} - {{ macros.checkbox_in_div(answer.wiki) }} - {% endif %} -
      - - {% endif %} - {% endif %} -
      diff --git a/lms/askbot/skins/mitx/templates/question/question_card.html b/lms/askbot/skins/mitx/templates/question/question_card.html deleted file mode 100644 index 7a31662581..0000000000 --- a/lms/askbot/skins/mitx/templates/question/question_card.html +++ /dev/null @@ -1,47 +0,0 @@ -
      - -
      - {# ==== BEGIN: question/question_vote_buttons.html ==== #} - {% include "question/question_vote_buttons.html" %} - {# ==== END: question/question_vote_buttons.html ==== #} - {# ==== BEGIN: question/share_buttons.html ==== #} - - {# ==== END: question/share_buttons.html ==== #} -
      - -
      - -

      {{ thread.get_title(question)|escape }}

      - -
      - -
      - {% include "question/question_tags.html" %} -
      - -
      - {% include "question/question_controls.html" %} -
      - -
      - -
      - -
      - {{ question.html }} - {# ==== START: question/question_comments.html ==== #} - {% include "question/question_comments.html" %} - {# ==== END: question/question_comments.html ==== #} -
      - - {# ==== START: "question/question_author_info.html" #} -
      - {% if question.author.is_administrator() %}
      Staff question
      {%endif%} - {% include "question/question_author_info.html" %}
      - {# ==== END: "question/question_author_info.html" #} - -
      - -
      - -
      diff --git a/lms/askbot/skins/mitx/templates/question/sharing_prompt_phrase.html b/lms/askbot/skins/mitx/templates/question/sharing_prompt_phrase.html deleted file mode 100644 index ebd3e19055..0000000000 --- a/lms/askbot/skins/mitx/templates/question/sharing_prompt_phrase.html +++ /dev/null @@ -1,13 +0,0 @@ -{% set question_url=settings.APP_URL+question.get_absolute_url()|urlencode %} - diff --git a/lms/askbot/skins/mitx/templates/question/sidebar.html b/lms/askbot/skins/mitx/templates/question/sidebar.html deleted file mode 100644 index 669fc3d525..0000000000 --- a/lms/askbot/skins/mitx/templates/question/sidebar.html +++ /dev/null @@ -1,63 +0,0 @@ -{% import "macros.html" as macros %} -{{ settings.SIDEBAR_QUESTION_HEADER }} -

      Course Question

      - - - - - - - - - - - - - - - - - - - - - - - -{% if settings.SIDEBAR_QUESTION_SHOW_META %} -
      -
        -
      • - {% trans %}question asked{% endtrans %} {{question.added_at|diff_date}} -
      • -
      • - {% trans %}question was seen{% endtrans %} {{ thread.view_count|intcomma }} {% trans %}times{% endtrans %} -
      • -
      • - {% trans %}last updated{% endtrans %} {{thread.last_activity_at|diff_date}} -
      • - - - - - -
      -
      -{% endif %} - -{% if similar_threads.data and settings.SIDEBAR_QUESTION_SHOW_RELATED %} -{#% cache 1800 "related_questions" related_questions question.id language_code %#} - - - - - - - - - - -{#% endcache %#} -{% endif %} - -{{ settings.SIDEBAR_QUESTION_FOOTER }} diff --git a/lms/askbot/skins/mitx/templates/question/subscribe_by_email_prompt.html b/lms/askbot/skins/mitx/templates/question/subscribe_by_email_prompt.html deleted file mode 100644 index f6520fc3b0..0000000000 --- a/lms/askbot/skins/mitx/templates/question/subscribe_by_email_prompt.html +++ /dev/null @@ -1,23 +0,0 @@ -{% if request.user.is_authenticated() %} -

      - {{ answer.email_notify }} - - {% trans profile_url=request.user.get_profile_url() %}You can always adjust frequency of email updates from your {{profile_url}}{% endtrans %} -

      -{% else %} -

      - {{ answer.email_notify }} - -

      -{% endif %} diff --git a/lms/askbot/skins/mitx/templates/question_edit.html b/lms/askbot/skins/mitx/templates/question_edit.html deleted file mode 100644 index 07ed178ee3..0000000000 --- a/lms/askbot/skins/mitx/templates/question_edit.html +++ /dev/null @@ -1,96 +0,0 @@ -{% extends "two_column_body.html" %} -{% import "macros.html" as macros %} - -{% block title %}{% spaceless %}{% trans %}Edit question{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} - - -
      -

      {% trans %}Edit question{% endtrans %} {% trans %}back to question{% endtrans %}

      -
      - -
      {% csrf_token %} - - - {{ - macros.edit_post( - form, - post_type='question', - edit_title=True, - mandatory_tags = mandatory_tags - ) - }} -
      - {% if settings.WIKI_ON and question.wiki == False %} - {{ macros.checkbox_in_div(form.wiki) }} - {% endif %} - {% if form.can_stay_anonymous() %} - {{ macros.checkbox_in_div(form.reveal_identity) }} - {% endif %} -
      - -
      -   - - -
      -
      -{% endblock %} - -{% block sidebar %} -{% include "widgets/question_edit_tips.html" %} -{% endblock %} - -{% block endjs %} - {% include "meta/editor_data.html" %} - - {% if mandatory_tags %} - {% include "meta/mandatory_tags_js.html" %} - {% endif %} - - - - - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/question_retag.html b/lms/askbot/skins/mitx/templates/question_retag.html deleted file mode 100644 index d15813e5d3..0000000000 --- a/lms/askbot/skins/mitx/templates/question_retag.html +++ /dev/null @@ -1,68 +0,0 @@ -{% extends "two_column_body.html" %} - -{% block title %}{% spaceless %}{% trans %}Change tags{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} -

      {% trans %}Change tags{% endtrans %} [{% trans %}back{% endtrans %}]

      -
      {% csrf_token %} -

      - {{ question.thread.get_title()|escape }} -

      -
      - {{ question.html }} -
      -
      - {{ form.tags.label_tag() }}:
      - {{ form.tags }} {{ form.tags.errors }} -
      - {{ form.tags.help_text }} -
      -
      -
      -   - -
      -{% endblock %} - -{% block sidebar %} -
      -

      {% trans %}Why use and modify tags?{% endtrans %}

      -
        -
      • {% trans %}Tags help to keep the content better organized and searchable{% endtrans %}
      • -
      • - {% trans %}tag editors receive special awards from the community{% endtrans %} -
      • -
      - -
      -{% endblock %} -{% block endjs %} - - - - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/question_widget.html b/lms/askbot/skins/mitx/templates/question_widget.html deleted file mode 100644 index 9d32294bce..0000000000 --- a/lms/askbot/skins/mitx/templates/question_widget.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - {{settings.QUESTIONS_WIDGET_HEADER|safe}} -
      - -
      - {{settings.QUESTIONS_WIDGET_FOOTER|safe}} - - diff --git a/lms/askbot/skins/mitx/templates/reopen.html b/lms/askbot/skins/mitx/templates/reopen.html deleted file mode 100644 index d1ccc31315..0000000000 --- a/lms/askbot/skins/mitx/templates/reopen.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends "two_column_body.html" %} - -{% block title %}{% spaceless %}{% trans %}Reopen question{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} -

      {% trans %}Reopen question{% endtrans %}

      -

      {% trans %}Title{% endtrans %}: - - {{ question.get_question_title()|escape }} - -

      -

      {% trans %}This question has been closed by - {{closed_by_username}} -{% endtrans %} -

      -

      - {% trans %}Close reason:{% endtrans %} "{{question.thread.get_close_reason_display()}}". -

      -

      - {% trans %}When:{% endtrans %} {{question.thread.closed_at|diff_date}} -

      -

      - {% trans %}Reopen this question?{% endtrans %} -

      -
      {% csrf_token %} -
      -   - -
      -
      -{% endblock %} -{% block endjs %} - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/revisions.html b/lms/askbot/skins/mitx/templates/revisions.html deleted file mode 100644 index 07b98b5bb0..0000000000 --- a/lms/askbot/skins/mitx/templates/revisions.html +++ /dev/null @@ -1,102 +0,0 @@ -{% extends "two_column_body.html" %} -{% import "macros.html" as macros %} - -{% block title %}{% spaceless %}{% trans %}Revision history{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} -

      - {% trans %}Revision history{% endtrans %} [{% trans %}back{% endtrans %}] -

      -
      -{% for revision in revisions %} -
      -
      -
      - - - - - - - -
      - {% trans %}click to hide/show revision{% endtrans %} - - {{ revision.revision }} - {% if revision.summary %} -
      - {{ revision.summary|escape }} -
      - {% endif %} - {% if request.user|can_edit_post(post) %} - {% if post.post_type == 'answer' %} - {% trans %}edit{% endtrans %} - {% endif %} - {% if post.post_type == 'question' %} - {% trans %}edit{% endtrans %} - {% endif %} - {% endif %} -
      -
      - {% if revision.revision == 1 %} - {% set contributor_type = "original_author" %} - {% else %} - {% set contributor_type = "last_updater" %} - {% endif %} - {{ macros.post_contributor_info( - revision, - contributor_type, - False, - 0 - ) - }} -
      -
      -
      -
      -
      - {{ revision.diff }} -
      -
      -{% endfor %} -
      -{% endblock %} - -{% block endjs %} - - - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/static_page.html b/lms/askbot/skins/mitx/templates/static_page.html deleted file mode 100644 index c537e19945..0000000000 --- a/lms/askbot/skins/mitx/templates/static_page.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends "two_column_body.html" %} - -{% block title %}{% spaceless %}{{title}}{% endspaceless %}{% endblock %} -{% block content %} -
      -

      {{title}}

      - {{content}} -
      -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/subscribe_for_tags.html b/lms/askbot/skins/mitx/templates/subscribe_for_tags.html deleted file mode 100644 index b436fb840a..0000000000 --- a/lms/askbot/skins/mitx/templates/subscribe_for_tags.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "two_column_body.html" %} -{% import "macros.html" as macros %} -{% block title %}{% trans %}Subscribe for tags{% endtrans %}{% endblock %} -{% block content %} -

      {% trans %}Subscribe for tags{% endtrans %}

      -

      {% trans %}Please, subscribe for the following tags:{% endtrans %}

      -
        - {% for tag in tags %} - {{ macros.tag_widget(tag, html_tag = 'li', is_link = False) }} - {% endfor %} -
      -
      -
      {% csrf_token %} - - - -
      -
      -{% endblock %} diff --git a/lms/askbot/skins/mitx/templates/tags.html b/lms/askbot/skins/mitx/templates/tags.html deleted file mode 100644 index 4894bdb1a4..0000000000 --- a/lms/askbot/skins/mitx/templates/tags.html +++ /dev/null @@ -1,75 +0,0 @@ -{% extends "two_column_body.html" %} -{% import "macros.html" as macros %} - -{% block title %}{% spaceless %}{% trans %}Tag list{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} - -{% if stag %} -

      {% trans %}Tags, matching "{{ stag }}"{% endtrans %}

      -{% else %} -

      {% trans %}Tag list{% endtrans %}

      -{% endif %} -
      -
      - {% trans %}Sort by »{% endtrans %} - {% trans %}by name{% endtrans %} - {% trans %}by popularity{% endtrans %} -
      -
      -{% if tag_list_type == 'list' %} - {% if not tags.object_list %} - {% trans %}Nothing found{% endtrans %} - {% endif %} - {% if tags.object_list %} -
      -
        - {% for tag in tags.object_list %} -
      • - {{ macros.tag_widget( - tag = tag.name, - html_tag = 'div', - extra_content = '× ' ~ - tag.used_count|intcomma ~ '' - ) - }} -
      • - {% endfor %} -
      -
      -
      - {{macros.paginator(paginator_context)}} -
      - {% endif %} -{% else %} -
      - {% if not tags %} - {% trans %}Nothing found{% endtrans %} - {% endif %} - {{ macros.tag_cloud(tags = tags, font_sizes = font_size) }} -{% endif %} - -{% endblock %} -{% block endjs %} - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/user_profile/user.html b/lms/askbot/skins/mitx/templates/user_profile/user.html deleted file mode 100644 index c7231efe0a..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "two_column_body.html" %} - - -{% block title %}{{ page_title }}{% endblock %} - -{% block content %} - {% block usercontent %}{% endblock %} -{% endblock %} - -{% block endjs %} - - {% if request.user|can_moderate_user(view_user) %} - - {% endif %} - - {% block userjs %} - {% endblock %} -{% endblock %} - -{% block sidebar %} - -{{ settings.SIDEBAR_PROFILE_HEADER }} - -{{ settings.SIDEBAR_PROFILE_FOOTER }} - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/user_profile/user_edit.html b/lms/askbot/skins/mitx/templates/user_profile/user_edit.html deleted file mode 100644 index aa1c276e23..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user_edit.html +++ /dev/null @@ -1,120 +0,0 @@ -{% extends "one_column_body.html" %} -{% import "macros.html" as macros %} - -{% block title %}{% spaceless %}{% trans %}Edit user profile{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} -

      - {{ request.user.username }} - {% trans %}edit profile{% endtrans %} -

      - -
      -
      {% csrf_token %} -
      -
      - {{ macros.gravatar(view_user, 128) }} - {% if request.user == view_user %} -

      {% trans %}change picture{% endtrans %}

      - {% if support_custom_avatars %} -

      {% trans %}remove{% endtrans %} -

      - {% endif %} - {% endif %} -
      -
      -
      -

      {% trans %}Registered user{% endtrans %}

      - - - - - - - - - {% else %} - {{ view_user.username }} - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      {% trans %}Screen Name{% endtrans %}: - {% if settings.EDITABLE_SCREEN_NAME %} - {{ form.username }} - {{ form.username.errors }}
      - {{ form.email.label_tag() }}: - - {% if settings.EDITABLE_EMAIL %} - {{ form.email }} - - {{ form.email.errors }} - {% else %} - {{ view_user.email }} - {% trans %}(cannot be changed){% endtrans %} - {% endif %} -
      {{ form.realname.label_tag() }}:{{ form.realname }} {{ form.realname.errors }}
      {{ form.website.label_tag() }}:{{ form.website }} {{ form.website.errors }}
      {{ form.city.label_tag() }}:{{ form.city }} {{ form.city.errors }}
      {{ form.country.label_tag() }}:{{ form.country }} {{ form.country.errors }}
      {{ form.show_country.label_tag() }}:{{ form.show_country }} {{ form.show_country.errors }}
      {{ form.birthday.label_tag() }}:{{ form.birthday }} {{ form.birthday.errors }}
      {{ form.birthday.help_text }}
      -
      {{ form.about.label_tag() }}:{{ form.about }} {{ form.about.errors }}
      -
      -   - -
      -
      -
      -
      -{% endblock %} -{% block endjs %} - - {% block userjs %} - {% endblock %} -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/user_profile/user_email_subscriptions.html b/lms/askbot/skins/mitx/templates/user_profile/user_email_subscriptions.html deleted file mode 100644 index e6a18dd35c..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user_email_subscriptions.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "user_profile/user.html" %} - -{% block profilesection %} - {% trans %}subscriptions{% endtrans %} -{% endblock %} -{% block usercontent %} -

      {% trans %}Email subscription settings{% endtrans %}

      -

      {% trans %}email subscription settings info{% endtrans %}

      -
      - {% if action_status %} -

      {{action_status}}

      - {% endif %} -
      {% csrf_token %} - - {{email_feeds_form.as_table()}} -
      - - {{tag_filter_selection_form}} -
      -
      -   - -
      -
      -
      -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/user_profile/user_favorites.html b/lms/askbot/skins/mitx/templates/user_profile/user_favorites.html deleted file mode 100644 index 08c3c68859..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user_favorites.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "user_profile/user.html" %} - -{% block profilesection %} -

      {% trans %}followed questions{% endtrans %}

      -{% endblock %} -{% block usercontent %} -{% include "user_profile/users_questions.html" %} -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/user_profile/user_inbox.html b/lms/askbot/skins/mitx/templates/user_profile/user_inbox.html deleted file mode 100644 index f70f1884bc..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user_inbox.html +++ /dev/null @@ -1,131 +0,0 @@ -{% extends "user_profile/user.html" %} -{% import "macros.html" as macros %} - -{# -This template accepts a list of response list -they are a generalized form of any response and - -The following properties of response object are used: -timestamp - when it happened -user - user who gave response (database object) -response_type - type of response -response_url - link to the question -response_title - title of the question -response_snippet - abbreviated content of the response -inbox_section - forum|flags -#} -{% block profilesection %} - {% trans %}inbox{% endtrans %} -{% endblock %} -{% block usercontent %} -
      - {% set re_count = request.user.new_response_count + - request.user.seen_response_count - %} - {% if moderation_items %} - {% set flag_count = moderation_items['new_count'] + - moderation_items['seen_count'] - %} - {% else %} - {% set flag_count = 0 %} - {% endif %} - {% if re_count > 0 and flag_count > 0 %} - - {% endif %} - {% if inbox_section == 'forum' %} -
      - {% trans %}select:{% endtrans %} - {% trans %}all{% endtrans %} | - {% trans %}seen{% endtrans %} | - {% trans %}new{% endtrans %} | - {% trans %}none{% endtrans %}
      - - - -
      - {% endif %} - {% if inbox_section == 'flags' %} -
      - {% trans %}select:{% endtrans %} - {% trans %}all{% endtrans %} | - {% trans %}seen{% endtrans %} | - {% trans %}new{% endtrans %} | - {% trans %}none{% endtrans %}
      - - - -
      - {% endif %} -
      - {% for response in responses %} -
      -

      - "{{ response.response_title.strip()|escape}}" -

      - - {% if response.nested_responses %} - {%for nested_response in response.nested_responses %} - - {%endfor%} - {%endif%} -
      - {% endfor %} -
      -
      -{% endblock %} -{% block userjs %} - - -{% endblock %} diff --git a/lms/askbot/skins/mitx/templates/user_profile/user_info.html b/lms/askbot/skins/mitx/templates/user_profile/user_info.html deleted file mode 100644 index b1e5ef8ff8..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user_info.html +++ /dev/null @@ -1,90 +0,0 @@ - -{% import "macros.html" as macros %} - - - - - - - - - - - - - - - - - - - - -

      - {% trans username=view_user.username %}{{username}}’s profile{% endtrans %} -

      - - - -
      -
        -

        User stats

        - - - - - - - - - - - - - - - - - {% if view_user.real_name %} -
      • {% trans %}real name{% endtrans %} {{view_user.real_name}}
      • - {% endif %} - - - - - - - {% if view_user.website %} -
      • {% trans %}user website{% endtrans %} {{ macros.user_website_link(view_user, max_display_length = 30) }}
      • - {% endif %} - - {% if view_user.location or view_user.country %} -
      • {% trans %}location{% endtrans %} {{ macros.user_full_location(view_user) }}
      • - {% endif %} - - {% if view_user.date_of_birth %} - -
      • {% trans %}age{% endtrans %} {{view_user.date_of_birth|get_age}} {% trans %}age unit{% endtrans %}
      • - {% endif %} - - {% if votes_today_left %} -
      • {% trans %}todays unused votes{% endtrans %} {{ votes_today_left }}
      • - {% endif %} - - - - - - - -
      -
      diff --git a/lms/askbot/skins/mitx/templates/user_profile/user_moderate.html b/lms/askbot/skins/mitx/templates/user_profile/user_moderate.html deleted file mode 100644 index 048f35b414..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user_moderate.html +++ /dev/null @@ -1,93 +0,0 @@ -{% extends "user_profile/user.html" %} - -{% block profilesection %} - {% trans %}moderation{% endtrans %} -{% endblock %} -{% block usercontent %} -{% if request.user != view_user %} -

      {% trans username=view_user.username, status=view_user.get_status_display() %}{{username}}'s current status is "{{status}}"{% endtrans %} -

      - {% if user_status_changed %} -

      {% trans %}User status changed{% endtrans %}

      - {% endif %} -
      {% csrf_token %} - - - {{ change_user_status_form.as_table() }} -
      -

      -

      - -
      -{% endif %} -

      -{% if request.user == view_user %} - {% trans reputation=view_user.reputation %}Your current reputation is {{reputation}} points{% endtrans %} -{% else %} - {% trans reputation=view_user.reputation %}User's current reputation is {{reputation}} points{% endtrans %} -{% endif %} -

      -{% if user_rep_changed %} -

      {% trans %}User reputation changed{% endtrans %}

      -{% endif %} -
      {% csrf_token %} - - - {{ change_user_reputation_form.as_table() }} -
      -   - -
      -{% if request.user != view_user %} -
      -

      {% trans username=view_user.username %}Send message to {{username}}{% endtrans %}

      -

      {% trans %}An email will be sent to the user with 'reply-to' field set to your email address. Please make sure that your address is entered correctly.{% endtrans %}

      - {% if message_sent %} -

      {% trans %}Message sent{% endtrans %}

      - {% endif %} -
      {% csrf_token %} - -
      - - {% if send_message_form.subject_line.errors %} -

      {{send_message_form.subject_line.errors|join(', ')}}

      - {% endif %} - {{ send_message_form.subject_line}} -
      -
      - - {% if send_message_form.body_text.errors %} -

      {{send_message_form.body_text.errors|join(', ')}}

      - {% endif %} - {{ send_message_form.body_text}} -
      - -
      -{% endif %} -{% endblock %} -{% block endjs %} - -{% endblock %} diff --git a/lms/askbot/skins/mitx/templates/user_profile/user_network.html b/lms/askbot/skins/mitx/templates/user_profile/user_network.html deleted file mode 100644 index 1fd2e06ab9..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user_network.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "user_profile/user.html" %} -{% import "macros.html" as macros %} - -{% block profileseciton %} - {% trans %}network{% endtrans %} -{% endblock %} -{% block usercontent %} - {% if followed_users or followers %} - {% if followers %} -

      {% trans count=followers|length %}Followed by {{count}} person{% pluralize count %}Followed by {{count}} people{% endtrans %}

      - {{ macros.user_list(followers, profile_section = 'network') }} - {% endif %} - {% if followed_users %} -

      {% trans count=followed_users|length %}Following {{count}} person{% pluralize count %}Following {{count}} people{% endtrans %}

      - {{ macros.user_list(followed_users, profile_section = 'network') }} - {% endif %} - {% else %} - {% if request.user == view_user %} -

      {% trans %}Your network is empty. Would you like to follow someone? - Just visit their profiles and click "follow"{% endtrans %}

      - {% else %} -

      {% trans username = view_user.username %}{{username}}'s network is empty{% endtrans %}

      - {% endif %} - {% endif %} -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/user_profile/user_recent.html b/lms/askbot/skins/mitx/templates/user_profile/user_recent.html deleted file mode 100644 index 53c2675748..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user_recent.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends "user_profile/user.html" %} - -{% block profilesection %} -{% trans %}activity{% endtrans %} -{% endblock %} -{% block usercontent %} -
      - {% for act in activities %} -
      -
      {{ act.time|diff_date(True) }}
      -
      - {{ act.type }} -
      -
      - {% if act.is_badge %} - -  {% trans name=act.badge.name %}{{name}}{% endtrans %} - - {% if act.content_object.post_type == 'question' %} - {% set question=act.content_object %} - ({% trans %}source{% endtrans %}) - {% elif act.content_object.post_type == 'answer' %} - {% set answer=act.content_object %} - ({% trans %}source{% endtrans %}) - {% endif %} - {% else %} - {{ act.title|escape }} - {% if act.summary %}{{ act.summary|escape }}{% endif %} - {% endif %} -
      -
      -
      - {% endfor %} -
      -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/user_profile/user_reputation.html b/lms/askbot/skins/mitx/templates/user_profile/user_reputation.html deleted file mode 100644 index 0deb2b9786..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user_reputation.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "user_profile/user.html" %} - -{% block profilesection %} - {% trans %}karma{% endtrans %} -{% endblock %} -{% block usercontent %} -
      -
      -
      - {% if view_user.id == user.id %} -

      {% trans %}Your karma change log.{% endtrans %}

      - {% else %} -

      {% trans user_name=view_user.username %}{{user_name}}'s karma change log{% endtrans %}

      - {% endif %} - {% for rep in reputation %} -

      - {{ rep.positive }} - {{ rep.negative }} - {{ rep.get_explanation_snippet() }} - ({{rep.reputed_at|diff_date}}) -

      -

      - {% endfor %} -
      -
      -{% endblock %} -{% block userjs %} - - - -{% endblock %} diff --git a/lms/askbot/skins/mitx/templates/user_profile/user_stats.html b/lms/askbot/skins/mitx/templates/user_profile/user_stats.html deleted file mode 100644 index 9118502c86..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user_stats.html +++ /dev/null @@ -1,155 +0,0 @@ -{% extends "user_profile/user.html" %} -{% import "macros.html" as macros %} - -{% block profilesection %} -{% trans %}overview{% endtrans %} -{% endblock %} - -{% block sidebar%} -{% include "user_profile/user_info.html" %} -{% endblock%} - -{% block usercontent %} - -
      -
      -

      {% trans counter=question_count %}{{counter}} Question{% pluralize %}{{counter}} Questions{% endtrans %} asked by {% trans username=view_user.username %}{{username}}{% endtrans %}

      -
      -{% include "user_profile/users_questions.html" %} -
      - -
        - -
      • -

        {% trans cnt=total_votes %}{{cnt}} Vote{% pluralize %}{{cnt}} Votes {% endtrans %}

        - -
          -
        • - {{up_votes}} -
        • - -
        • - {{down_votes}} -
        • -
        - -

        {% trans counter=total_badges %}{{counter}} Badge{% pluralize %}{{counter}} Badges{% endtrans %}

        - -
        -

        View all MITx badges ➜

        -
      • - -
      • - -

        {{ top_answer_count }} {% trans counter=top_answer_count %}Answer{% pluralize %}Answers{% endtrans %}

        - -
          - {% for top_answer in top_answers %} - -
        • - - - {{ top_answer.score }} - - - - -
        • - {% endfor %} -
        -
      • - - -
      • -

        {% trans counter=user_tags|length %}{{counter}} Tag{% pluralize %}{{counter}} Tags{% endtrans %}

        - -
          - {% for tag in user_tags %} -
        • - {{ macros.tag_widget( - tag.name, - html_tag = 'div', - search_state = search_state, - extra_content = - '× ' ~ - tag.user_tag_usage_count|intcomma ~ - '' - ) - }} -
        • - {# - {% if loop.index is divisibleby 10 %} -
      - {% endif %} - #} - {% endfor %} - - - - -{% endblock %} - -{% block endjs %} -{{ super() }} - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/user_profile/user_tabs.html b/lms/askbot/skins/mitx/templates/user_profile/user_tabs.html deleted file mode 100644 index a1384be563..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user_tabs.html +++ /dev/null @@ -1,57 +0,0 @@ - -
      -
      - {% trans %}overview{% endtrans %} - {% if request.user == view_user or request.user|can_moderate_user(view_user) %} - {% trans %}inbox{% endtrans %} - {% endif %} - {% if user_follow_feature_on %} - {% trans %}network{% endtrans %} - {% endif %} - {% trans %}reputation history{% endtrans %} - {% trans %}followed questions{% endtrans %} - - {% trans %}activity{% endtrans %} - - {% if request.user == view_user or request.user|can_moderate_user(view_user) %} - {% trans %}casted votes{% endtrans %} - {% endif %} - - {% if request.user == view_user or request.user|can_moderate_user(view_user) %} - {% trans %}subscriptions{% endtrans %} - {% endif %} - - {% if request.user|can_moderate_user(view_user) %} - {% trans %}moderation{% endtrans %} - {% endif %} -
      -
      -
      - diff --git a/lms/askbot/skins/mitx/templates/user_profile/user_votes.html b/lms/askbot/skins/mitx/templates/user_profile/user_votes.html deleted file mode 100644 index b63d4c9e16..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/user_votes.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "user_profile/user.html" %} - -{% block profilesection %} -{% trans %}votes{% endtrans %} -{% endblock %} -{% block usercontent %} -
      - {% for vote in votes %} -
      -
      {{vote.voted_at|diff_date(True)}}
      -
      - {% if vote.vote==1 %} - - {% else %} - - {% endif %} -
      -
      - {% if vote.answer_id==0 %} - {{ vote.title|escape }} - {% else %} - {{ vote.title|escape}} - {% endif %} -
      -
      -
      - {% endfor %} -
      -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/user_profile/users_questions.html b/lms/askbot/skins/mitx/templates/user_profile/users_questions.html deleted file mode 100644 index c103f961a4..0000000000 --- a/lms/askbot/skins/mitx/templates/user_profile/users_questions.html +++ /dev/null @@ -1,9 +0,0 @@ - -{% import "macros.html" as macros %} - -
        - {% for question in questions %} - {{macros.question_summary(question.thread, question, extra_class='narrow', search_state=search_state)}} - {% endfor %} -
      - diff --git a/lms/askbot/skins/mitx/templates/users.html b/lms/askbot/skins/mitx/templates/users.html deleted file mode 100644 index 0502a6e5ec..0000000000 --- a/lms/askbot/skins/mitx/templates/users.html +++ /dev/null @@ -1,60 +0,0 @@ -{% extends "two_column_body.html" %} -{% import "macros.html" as macros %} - -{% block title %}{% spaceless %}{% trans %}Users{% endtrans %}{% endspaceless %}{% endblock %} -{% block content %} -

      {% trans %}Users{% endtrans %}

      - -
      -

      - {% if suser %} - {% trans %}users matching query {{suser}}:{% endtrans %} - {% endif %} - {% if not users.object_list %} - {% trans %}Nothing found.{% endtrans %} - {% endif %} -

      -{{ macros.user_list(users.object_list) }} -
      - {{ macros.paginator(paginator_context) }} -
      -{% endblock %} -{% block endjs %} - -{% endblock %} - diff --git a/lms/askbot/skins/mitx/templates/widgets/answer_edit_tips.html b/lms/askbot/skins/mitx/templates/widgets/answer_edit_tips.html deleted file mode 100644 index 5444b4c86b..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/answer_edit_tips.html +++ /dev/null @@ -1,64 +0,0 @@ - -
      -

      {% trans %}answer tips{% endtrans %}

      -
        -
      • {% trans %}please make your answer relevant to this community{% endtrans %}
      • -
      • {% trans %}try to give an answer, rather than engage into a discussion{% endtrans %}
      • -
      • {% trans %}please try to provide details{% endtrans %}
      • -
      • {% trans %}be clear and concise{% endtrans %}
      • -
      - -
      - -
      -

      {% trans %}Markdown tips{% endtrans %}

      -
        - {% if settings.MARKUP_CODE_FRIENDLY or settings.ENABLE_MATHJAX %} -
      • - {% trans %}*italic*{% endtrans %} -
      • -
      • - {% trans %}**bold**{% endtrans %} -
      • - {% else %} -
      • - {% trans %}*italic* or _italic_{% endtrans %} -
      • -
      • - {% trans %}**bold** or __bold__{% endtrans %} -
      • - {% endif %} -
      • - {% trans %}link{% endtrans %}:[{% trans %}text{% endtrans %}](http://url.com/ "{% trans %}title{% endtrans %}") - -
      • -
      • - {% trans %}image{% endtrans %}:![alt {% trans %}text{% endtrans %}](/path/img.jpg "{% trans %}title{% endtrans %}") - -
      • -
      • - {% trans %}numbered list:{% endtrans %} -
          -
        1. - Foo -
        2. -
        3. - Bar -
        4. -
        -
      • -
      • - {% trans %}basic HTML tags are also supported{% endtrans %} -
      • -
      - -
      - diff --git a/lms/askbot/skins/mitx/templates/widgets/ask_button.html b/lms/askbot/skins/mitx/templates/widgets/ask_button.html deleted file mode 100644 index 0eb9243e4d..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/ask_button.html +++ /dev/null @@ -1,6 +0,0 @@ -{% if active_tab != "ask" %} - {% if not search_state %} {# get empty SearchState() if there's none #} - {% set search_state=search_state|get_empty_search_state %} - {% endif %} - {% trans %}ask a question{% endtrans %} -{% endif %} diff --git a/lms/askbot/skins/mitx/templates/widgets/ask_form.html b/lms/askbot/skins/mitx/templates/widgets/ask_form.html deleted file mode 100644 index bab9fec321..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/ask_form.html +++ /dev/null @@ -1,49 +0,0 @@ -{% import "macros.html" as macros %} - -
      -

      Ask a question Cancel

      -
      - -
      {% csrf_token %} -
      - - - - {% if settings.EMAIL_VALIDATION %} - {% if not request.user.email_isvalid %} - {% trans email=request.user.email %}must have valid {{email}} to post, - see {{email_validation_faq_url}} - {% endtrans %} - {% endif %} - {% endif %} - - {{ form.title.errors }}
      - -
      - {{ form.title.help_text }} -
      -
      -
      - {{ - macros.edit_post( - form, - post_type = 'question', - edit_title = False, - mandatory_tags = mandatory_tags - ) - }} -
      - {% if settings.WIKI_ON %} - {{ macros.checkbox_in_div(form.wiki) }} - {% endif %} - {% if settings.ALLOW_ASK_ANONYMOUSLY %} - {{ macros.checkbox_in_div(form.ask_anonymously) }} - {% endif %} -
      - {% if not request.user.is_authenticated() %} - - {% else %} - - {% endif %} -
      diff --git a/lms/askbot/skins/mitx/templates/widgets/contributors.html b/lms/askbot/skins/mitx/templates/widgets/contributors.html deleted file mode 100644 index 12d50032f6..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/contributors.html +++ /dev/null @@ -1,10 +0,0 @@ -{% cache 600 "contributors" contributors search_tags scope sort query context.page language_code %} -
      -

      {% trans %}Contributors{% endtrans %}

      - {% spaceless %} - {% for person in contributors %} - {{ macros.gravatar(person,48) }} - {% endfor %} - {% endspaceless %} -
      -{% endcache %} diff --git a/lms/askbot/skins/mitx/templates/widgets/footer.html b/lms/askbot/skins/mitx/templates/widgets/footer.html deleted file mode 100644 index 8d7b6d7737..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/footer.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - diff --git a/lms/askbot/skins/mitx/templates/widgets/header.html b/lms/askbot/skins/mitx/templates/widgets/header.html deleted file mode 100644 index a922e7cb2b..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/header.html +++ /dev/null @@ -1,4 +0,0 @@ - -{% import "macros.html" as macros %} -{% include "navigation.jinja.html" %} -{% include "course_navigation.jinja.html" %} diff --git a/lms/askbot/skins/mitx/templates/widgets/logo.html b/lms/askbot/skins/mitx/templates/widgets/logo.html deleted file mode 100644 index 1b251432cd..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/logo.html +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/lms/askbot/skins/mitx/templates/widgets/meta_nav.html b/lms/askbot/skins/mitx/templates/widgets/meta_nav.html deleted file mode 100644 index b459b02561..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/meta_nav.html +++ /dev/null @@ -1,15 +0,0 @@ -{% trans %}tags{% endtrans %} -{% trans %}users{% endtrans %} -{% trans %}badges{% endtrans %} diff --git a/lms/askbot/skins/mitx/templates/widgets/question_edit_tips.html b/lms/askbot/skins/mitx/templates/widgets/question_edit_tips.html deleted file mode 100644 index 66efbab681..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/question_edit_tips.html +++ /dev/null @@ -1,69 +0,0 @@ - -
      -

      {% trans %}question tips{% endtrans %}

      -
        -
      • {% trans %}please ask a relevant question{% endtrans %} -
      • -
      • - {% trans %}please try provide enough details{% endtrans %} -
      • -
      • - {% trans %}be clear and concise{% endtrans %} -
      • -
      - -
      - -
      -

      {% trans %}Markdown tips{% endtrans %}

      -
        - {% if settings.MARKDUP_CODE_FRIENDLY or settings.ENABLE_MATHJAX %} -
      • - {% trans %}*italic*{% endtrans %} -
      • -
      • - {% trans %}**bold**{% endtrans %} -
      • - {% else %} -
      • - {% trans %}*italic* or _italic_{% endtrans %} -
      • -
      • - {% trans %}**bold** or __bold__{% endtrans %} -
      • - {% endif %} -
      • - {% trans %}link{% endtrans %}:[{% trans %}text{% endtrans %}](http://url.com/ "{% trans %}title{% endtrans %}") - -
      • - -
      • - {% trans %}image{% endtrans %}:![alt {% trans %}text{% endtrans %}](/path/img.jpg "{% trans %}title{% endtrans %}") - -
      • -
      • - {% trans %}numbered list:{% endtrans %} -
          -
        1. - Foo -
        2. -
        3. - Bar -
        4. -
        -
      • -
      • - {% trans %}basic HTML tags are also supported{% endtrans %} -
      • -
      - -
      - diff --git a/lms/askbot/skins/mitx/templates/widgets/question_summary.html b/lms/askbot/skins/mitx/templates/widgets/question_summary.html deleted file mode 100644 index 032010e450..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/question_summary.html +++ /dev/null @@ -1,63 +0,0 @@ -{% from "macros.html" import user_country_flag, tag_list_widget %} -
    • - -
      - -

      {{thread.get_title(question)|escape}}

      -

      {{question.summary}}…

      - - - -  {{ tag_list_widget(thread.get_tag_names(), search_state=search_state) }} - -
      - -
      -
        -
      • - {{thread.view_count}} -
        - {% trans cnt=thread.view_count %}view{% pluralize %}views{% endtrans %} -
        -
      • - -
      • - {{thread.answer_count}}{% if thread.accepted_answer_id %}{% endif %} -
        - {% trans cnt=thread.answer_count %}answer{% pluralize %}answers{% endtrans %} -
        -
      • - -
      • - {{question.score}} -
        - {% trans cnt=question.score %}vote{% pluralize %}votes{% endtrans %} -
        -
      • - -
      -
      -
    • - diff --git a/lms/askbot/skins/mitx/templates/widgets/scope_nav.html b/lms/askbot/skins/mitx/templates/widgets/scope_nav.html deleted file mode 100644 index 8e4d8f6518..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/scope_nav.html +++ /dev/null @@ -1,25 +0,0 @@ -{% if active_tab != "ask" %} - {% if not search_state %} {# get empty SearchState() if there's none #} - {% set search_state=search_state|get_empty_search_state %} - {% endif %} -
    • - {% trans %}All{% endtrans %} -
    • - -
    • - {% trans %}Unanswered{% endtrans %} -
    • - - {% if request.user.is_authenticated() %} - -
    • - {% trans %}Followed{% endtrans %} -
    • - - {% endif %} -{% else %} -
      {% trans %}Go back to {% endtrans %}
      -{% endif %} diff --git a/lms/askbot/skins/mitx/templates/widgets/secondary_header.html b/lms/askbot/skins/mitx/templates/widgets/secondary_header.html deleted file mode 100644 index bb1a57cd12..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/secondary_header.html +++ /dev/null @@ -1,21 +0,0 @@ - -
      - -
      diff --git a/lms/askbot/skins/mitx/templates/widgets/system_messages.html b/lms/askbot/skins/mitx/templates/widgets/system_messages.html deleted file mode 100644 index 10ba4a84cd..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/system_messages.html +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/lms/askbot/skins/mitx/templates/widgets/user_list.html b/lms/askbot/skins/mitx/templates/widgets/user_list.html deleted file mode 100644 index 7874946b90..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/user_list.html +++ /dev/null @@ -1,22 +0,0 @@ -{%from "macros.html" import gravatar %} -
      - - - - - -
      - {% for user in users %} -
      -
        -
      • {{ gravatar(user, 32) }}
      • -
      • {{user.username}}{{ user_country_flag(user) }}
      • -
      • {{ user_score_and_badge_summary(user) }}
      • -
      -
      - {% if loop.index is divisibleby 7 %} -
      - {% endif %} - {% endfor %} -
      -
      diff --git a/lms/askbot/skins/mitx/templates/widgets/user_long_score_and_badge_summary.html b/lms/askbot/skins/mitx/templates/widgets/user_long_score_and_badge_summary.html deleted file mode 100644 index 121ae48f06..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/user_long_score_and_badge_summary.html +++ /dev/null @@ -1,21 +0,0 @@ -{% trans %}karma:{% endtrans %} {{user.reputation}} -{%- if user.gold or user.silver or user.bronze %} -{% trans %}badges:{% endtrans %} - {% if user.gold %} - - {{user.gold}} - {% endif %} - {% if user.silver %} - - {{user.silver}} - {% endif %} - {% if user.bronze %} - - {{user.bronze}} - {%- endif -%} - -{%- endif -%} diff --git a/lms/askbot/skins/mitx/templates/widgets/user_navigation.html b/lms/askbot/skins/mitx/templates/widgets/user_navigation.html deleted file mode 100644 index 8d6dc33051..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/user_navigation.html +++ /dev/null @@ -1,17 +0,0 @@ -{% if request.user.is_authenticated() %} - {{ request.user.username }} - - {% if settings.USE_ASKBOT_LOGIN_SYSTEM %} - {% trans %}logout{% endtrans %} - {% endif %} -{% elif settings.USE_ASKBOT_LOGIN_SYSTEM %} - {% trans %}login{% endtrans %} -{% endif %} -{% if request.user.is_authenticated() and request.user.is_administrator() %} - {% trans %}settings{% endtrans %} -{% endif %} - {% trans %}help{% endtrans %} diff --git a/lms/askbot/skins/mitx/templates/widgets/user_score_and_badge_summary.html b/lms/askbot/skins/mitx/templates/widgets/user_score_and_badge_summary.html deleted file mode 100644 index 2f55b202c0..0000000000 --- a/lms/askbot/skins/mitx/templates/widgets/user_score_and_badge_summary.html +++ /dev/null @@ -1,19 +0,0 @@ -{{user.reputation}} -{% if user.gold or user.silver or user.bronze %} - - {% if user.gold %} - - {{user.gold}} - {% endif %} - {% if user.silver %} - - {{user.silver}} - {% endif %} - {% if user.bronze %} - - {{user.bronze}} - {% endif %} - -{% endif %} diff --git a/lms/askbot/skins/utils.py b/lms/askbot/skins/utils.py deleted file mode 100644 index 520fa2f7f4..0000000000 --- a/lms/askbot/skins/utils.py +++ /dev/null @@ -1,195 +0,0 @@ -"""utilities dealing with resolution of skin components - -the lookup resolution process for templates and media works as follows: -* look up item in selected skin -* if not found look in 'default' -* raise an exception -""" -import os -import logging -import urllib -from django.conf import settings as django_settings -from django.utils.datastructures import SortedDict -from askbot.utils import hasher - -class MediaNotFound(Exception): - """raised when media file is not found""" - pass - -def get_skins_from_dir(directory): - """returns sorted dict with skin data, like get_available_skins - but from a specific directory - """ - skins = SortedDict() - for item in sorted(os.listdir(directory)): - item_dir = os.path.join(directory, item) - if os.path.isdir(item_dir): - skins[item] = item_dir - return skins - -def get_available_skins(selected=None): - """selected is a name of preferred skin - if it's None, then information about all skins will be returned - otherwise, only data about selected and default skins - will be returned - - selected skin is guaranteed to be the first item in the dictionary - """ - skins = SortedDict() - if hasattr(django_settings, 'ASKBOT_EXTRA_SKINS_DIR'): - skins.update(get_skins_from_dir(django_settings.ASKBOT_EXTRA_SKINS_DIR)) - - stock_dir = os.path.normpath(os.path.dirname(__file__)) - stock_skins = get_skins_from_dir(stock_dir) - default_dir = stock_skins.pop('default') - common_dir = stock_skins.pop('common') - - skins.update(stock_skins) - if selected: - if selected in skins: - selected_dir = skins[selected] - skins.clear() - skins[selected] = selected_dir - else: - assert(selected == 'default' or selected == 'common') - skins = SortedDict() - - #re-insert default as a last item - skins['default'] = default_dir - skins['common'] = common_dir - return skins - - -def get_path_to_skin(skin): - """returns path to directory in the list of - available skin directories that contains another - directory called skin - - it is assumed that all skins are named uniquely - """ - skin_dirs = get_available_skins() - return skin_dirs.get(skin, None) - -def get_skin_choices(): - """returns a tuple for use as a set of - choices in the form""" - skin_names = list(reversed(get_available_skins().keys())) - return zip(skin_names, skin_names) - -def resolve_skin_for_media(media=None, preferred_skin = None): - #see if file exists, if not, try skin 'default' - available_skins = get_available_skins(selected = preferred_skin).items() - for skin_name, skin_dir in available_skins: - if os.path.isfile(os.path.join(skin_dir, 'media', media)): - return skin_name - raise MediaNotFound(media) - -def get_media_url(url, ignore_missing = False): - """returns url prefixed with the skin name - of the first skin that contains the file - directories are searched in this order: - askbot_settings.ASKBOT_DEFAULT_SKIN, then 'default', then 'commmon' - if file is not found - returns None - and logs an error message - - todo: move this to the skin environment class - """ - #import datetime - #before = datetime.datetime.now() - url = urllib.unquote(unicode(url)) - while url[0] == '/': url = url[1:] - - #a hack allowing urls media stored on external locations to - #just pass through unchanged - if url.startswith('http://') or url.startswith('https://'): - return url - #todo: handles case of multiple skin directories - - #if file is in upfiles directory, then give that - url_copy = url - if url_copy.startswith(django_settings.MEDIA_URL[1:]): - file_path = url_copy.replace( - django_settings.MEDIA_URL[1:], - '', - 1 - ) - file_path = os.path.join( - django_settings.MEDIA_ROOT, - file_path - ) - if os.path.isfile(file_path): - url_copy = os.path.normpath( - '///' + url_copy - ).replace( - '\\', '/' - ).replace( - '///', '/' - ) - return url_copy - elif ignore_missing == False: - logging.critical('missing media resource %s' % url) - - #2) if it does not exist in uploaded files directory - look in skins - - #purpose of this try statement is to determine - #which skin is currently used - try: - #this import statement must be hidden here - #because at startup time this branch will fail - #due to an import error - from askbot.conf import settings as askbot_settings - use_skin = askbot_settings.ASKBOT_DEFAULT_SKIN - resource_revision = askbot_settings.MEDIA_RESOURCE_REVISION - except ImportError: - use_skin = 'default' - resource_revision = None - - #determine from which skin take the media file - try: - use_skin = resolve_skin_for_media(media=url, preferred_skin = use_skin) - except MediaNotFound: - if ignore_missing == False: - log_message = 'missing media resource %s in skin %s' \ - % (url, use_skin) - logging.critical(log_message) - return None - - url = django_settings.STATIC_URL + use_skin + '/media/' + url - url = os.path.normpath(url).replace('\\', '/') - - if resource_revision: - url += '?v=%d' % resource_revision - - #after = datetime.datetime.now() - #print after - before - return url - -def update_media_revision(skin = None): - """update skin media revision number based on the contents - of the skin media directory""" - from askbot.conf import settings as askbot_settings - resource_revision = askbot_settings.MEDIA_RESOURCE_REVISION - - if skin: - if skin in get_skin_choices(): - skin_path = get_path_to_skin(skin) - else: - raise MediaNotFound('Skin %s not found' % skin) - else: - skin = 'default' - skin_path = get_path_to_skin(askbot_settings.ASKBOT_DEFAULT_SKIN) - - media_dirs = [os.path.join(skin_path, 'media'),] - - if skin != 'default': - #we have default skin as parent of the custom skin - default_skin_path = get_path_to_skin('default') - media_dirs.append(os.path.join(default_skin_path, 'media')) - - current_hash = hasher.get_hash_of_dirs(media_dirs) - - if current_hash != askbot_settings.MEDIA_RESOURCE_REVISION_HASH: - askbot_settings.update('MEDIA_RESOURCE_REVISION', resource_revision + 1) - askbot_settings.update('MEDIA_RESOURCE_REVISION_HASH', current_hash) - logging.debug('MEDIA_RESOURCE_REVISION changed') - askbot_settings.MEDIA_RESOURCE_REVISION diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index adb1ab3c0f..980fedb947 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -222,9 +222,6 @@ def get_default_tabs(user, course, active_page): link = reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id]) tabs.append(CourseTab('Discussion', link, active_page == 'discussion')) - elif settings.MITX_FEATURES.get('ENABLE_DISCUSSION'): - ## This is Askbot, which we should be retiring soon... - tabs.append(CourseTab('Discussion', reverse('questions'), active_page == 'discussion')) tabs.extend(_wiki({'name': 'Wiki', 'type': 'wiki'}, user, course, active_page)) diff --git a/lms/envs/askbotsettings.py b/lms/envs/askbotsettings.py deleted file mode 100644 index eb4840a157..0000000000 --- a/lms/envs/askbotsettings.py +++ /dev/null @@ -1,293 +0,0 @@ -""" -There are other askbot settings in common.py that covers things like where the -templates are located, etc. This file is purely for askbot forum *behavior*. -This means things like karma limits, the ability to post questions as wikis, -anonymous questions, etc. -""" - -LIVESETTINGS_OPTIONS = { - 1: { - 'DB' : False, - 'SETTINGS' : { - 'ACCESS_CONTROL' : { - 'ASKBOT_CLOSED_FORUM_MODE' : True, - }, - 'BADGES' : { - 'DISCIPLINED_BADGE_MIN_UPVOTES' : 3, - 'PEER_PRESSURE_BADGE_MIN_DOWNVOTES' : 3, - 'TEACHER_BADGE_MIN_UPVOTES' : 1, - 'NICE_ANSWER_BADGE_MIN_UPVOTES' : 2, - 'GOOD_ANSWER_BADGE_MIN_UPVOTES' : 3, - 'GREAT_ANSWER_BADGE_MIN_UPVOTES' : 5, - 'NICE_QUESTION_BADGE_MIN_UPVOTES' : 2, - 'GOOD_QUESTION_BADGE_MIN_UPVOTES' : 3, - 'GREAT_QUESTION_BADGE_MIN_UPVOTES' : 5, - 'POPULAR_QUESTION_BADGE_MIN_VIEWS' : 150, - 'NOTABLE_QUESTION_BADGE_MIN_VIEWS' : 250, - 'FAMOUS_QUESTION_BADGE_MIN_VIEWS' : 500, - 'SELF_LEARNER_BADGE_MIN_UPVOTES' : 1, - 'CIVIC_DUTY_BADGE_MIN_VOTES' : 100, - 'ENLIGHTENED_BADGE_MIN_UPVOTES' : 3, - 'ASSOCIATE_EDITOR_BADGE_MIN_EDITS' : 20, - 'COMMENTATOR_BADGE_MIN_COMMENTS' : 10, - 'ENTHUSIAST_BADGE_MIN_DAYS' : 30, - 'FAVORITE_QUESTION_BADGE_MIN_STARS' : 3, - 'GURU_BADGE_MIN_UPVOTES' : 5, - 'NECROMANCER_BADGE_MIN_DELAY' : 30, - 'NECROMANCER_BADGE_MIN_UPVOTES' : 1, - 'STELLAR_QUESTION_BADGE_MIN_STARS' : 5, - 'TAXONOMIST_BADGE_MIN_USE_COUNT' : 10, - }, - 'EMAIL' : { - 'EMAIL_SUBJECT_PREFIX' : u'[Django] ', - 'EMAIL_UNIQUE' : True, - 'EMAIL_VALIDATION' : False, - 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_M_AND_C' : u'w', - 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_ALL' : u'w', - 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_ANS' : u'w', - 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_ASK' : u'w', - 'DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE_Q_SEL' : u'w', - 'ENABLE_UNANSWERED_REMINDERS' : False, - 'DAYS_BEFORE_SENDING_UNANSWERED_REMINDER' : 1, - 'UNANSWERED_REMINDER_FREQUENCY' : 1, - 'MAX_UNANSWERED_REMINDERS' : 5, - 'ENABLE_ACCEPT_ANSWER_REMINDERS' : False, - 'DAYS_BEFORE_SENDING_ACCEPT_ANSWER_REMINDER' : 3, - 'ACCEPT_ANSWER_REMINDER_FREQUENCY' : 3, - 'MAX_ACCEPT_ANSWER_REMINDERS' : 5, - 'ANONYMOUS_USER_EMAIL' : u'anonymous@askbot.org', - 'ALLOW_ASKING_BY_EMAIL' : False, - 'REPLACE_SPACE_WITH_DASH_IN_EMAILED_TAGS' : True, - 'MAX_ALERTS_PER_EMAIL' : 7, - }, - 'EMBEDDABLE_WIDGETS' : { - 'QUESTIONS_WIDGET_CSS' : u"\nbody {\n overflow: hidden;\n}\n#container {\n width: 200px;\n height: 350px;\n}\nul {\n list-style: none;\n padding: 5px;\n margin: 5px;\n}\nli {\n border-bottom: #CCC 1px solid;\n padding-bottom: 5px;\n padding-top: 5px;\n}\nli:last-child {\n border: none;\n}\na {\n text-decoration: none;\n color: #464646;\n font-family: 'Yanone Kaffeesatz', sans-serif;\n font-size: 15px;\n}\n", - 'QUESTIONS_WIDGET_FOOTER' : u"\n\n", - 'QUESTIONS_WIDGET_HEADER' : u'', - 'QUESTIONS_WIDGET_MAX_QUESTIONS' : 7, - }, - 'EXTERNAL_KEYS' : { - 'RECAPTCHA_KEY' : u'', - 'RECAPTCHA_SECRET' : u'', - 'FACEBOOK_KEY' : u'', - 'FACEBOOK_SECRET' : u'', - 'HOW_TO_CHANGE_LDAP_PASSWORD' : u'', - 'IDENTICA_KEY' : u'', - 'IDENTICA_SECRET' : u'', - 'GOOGLE_ANALYTICS_KEY' : u'', - 'GOOGLE_SITEMAP_CODE' : u'', - 'LDAP_PROVIDER_NAME' : u'', - 'LDAP_URL' : u'', - 'LINKEDIN_KEY' : u'', - 'LINKEDIN_SECRET' : u'', - 'TWITTER_KEY' : u'', - 'TWITTER_SECRET' : u'', - 'USE_LDAP_FOR_PASSWORD_LOGIN' : False, - 'USE_RECAPTCHA' : False, - }, - 'FLATPAGES' : { - 'FORUM_ABOUT' : u'', - 'FORUM_FAQ' : u'', - 'FORUM_PRIVACY' : u'', - }, - 'FORUM_DATA_RULES' : { - 'MIN_TITLE_LENGTH' : 1, - 'MIN_QUESTION_BODY_LENGTH' : 1, - 'MIN_ANSWER_BODY_LENGTH' : 1, - 'WIKI_ON' : False, - 'ALLOW_ASK_ANONYMOUSLY' : True, - 'ALLOW_POSTING_BEFORE_LOGGING_IN' : False, - 'ALLOW_SWAPPING_QUESTION_WITH_ANSWER' : False, - 'MAX_TAG_LENGTH' : 20, - 'MIN_TITLE_LENGTH' : 1, - 'MIN_QUESTION_BODY_LENGTH' : 1, - 'MIN_ANSWER_BODY_LENGTH' : 1, - 'MANDATORY_TAGS' : u'', - 'FORCE_LOWERCASE_TAGS' : False, - 'TAG_LIST_FORMAT' : u'list', - 'USE_WILDCARD_TAGS' : False, - 'MAX_COMMENTS_TO_SHOW' : 5, - 'MAX_COMMENT_LENGTH' : 300, - 'USE_TIME_LIMIT_TO_EDIT_COMMENT' : True, - 'MINUTES_TO_EDIT_COMMENT' : 10, - 'SAVE_COMMENT_ON_ENTER' : True, - 'MIN_SEARCH_WORD_LENGTH' : 4, - 'DECOUPLE_TEXT_QUERY_FROM_SEARCH_STATE' : False, - 'MAX_TAGS_PER_POST' : 5, - 'DEFAULT_QUESTIONS_PAGE_SIZE' : u'30', - 'UNANSWERED_QUESTION_MEANING' : u'NO_ACCEPTED_ANSWERS', - - # Enabling video requires forked version of markdown - # pip uninstall markdown2 - # pip install -e git+git://github.com/andryuha/python-markdown2.git#egg=markdown2 - 'ENABLE_VIDEO_EMBEDDING' : False, - }, - 'GENERAL_SKIN_SETTINGS' : { - 'CUSTOM_CSS' : u'', - 'CUSTOM_FOOTER' : u'', - 'CUSTOM_HEADER' : u'', - 'CUSTOM_HTML_HEAD' : u'', - 'CUSTOM_JS' : u'', - 'SITE_FAVICON' : u'/images/favicon.gif', - 'SITE_LOGO_URL' : u'/images/logo.gif', - 'SHOW_LOGO' : False, - 'LOCAL_LOGIN_ICON' : u'/images/pw-login.gif', - 'ALWAYS_SHOW_ALL_UI_FUNCTIONS' : False, - 'ASKBOT_DEFAULT_SKIN' : u'mitx', - 'USE_CUSTOM_HTML_HEAD' : False, - 'FOOTER_MODE' : u'default', - 'USE_CUSTOM_CSS' : False, - 'USE_CUSTOM_JS' : False, - }, - 'LEADING_SIDEBAR' : { - 'ENABLE_LEADING_SIDEBAR' : False, - 'LEADING_SIDEBAR' : u'', - }, - 'LOGIN_PROVIDERS' : { - 'PASSWORD_REGISTER_SHOW_PROVIDER_BUTTONS' : True, - 'SIGNIN_ALWAYS_SHOW_LOCAL_LOGIN' : True, - 'SIGNIN_AOL_ENABLED' : True, - 'SIGNIN_BLOGGER_ENABLED' : True, - 'SIGNIN_CLAIMID_ENABLED' : True, - 'SIGNIN_FACEBOOK_ENABLED' : True, - 'SIGNIN_FLICKR_ENABLED' : True, - 'SIGNIN_GOOGLE_ENABLED' : True, - 'SIGNIN_IDENTI.CA_ENABLED' : True, - 'SIGNIN_LINKEDIN_ENABLED' : True, - 'SIGNIN_LIVEJOURNAL_ENABLED' : True, - 'SIGNIN_LOCAL_ENABLED' : True, - 'SIGNIN_OPENID_ENABLED' : True, - 'SIGNIN_TECHNORATI_ENABLED' : True, - 'SIGNIN_TWITTER_ENABLED' : True, - 'SIGNIN_VERISIGN_ENABLED' : True, - 'SIGNIN_VIDOOP_ENABLED' : True, - 'SIGNIN_WORDPRESS_ENABLED' : True, - 'SIGNIN_WORDPRESS_SITE_ENABLED' : False, - 'SIGNIN_YAHOO_ENABLED' : True, - 'WORDPRESS_SITE_ICON' : u'/images/logo.gif', - 'WORDPRESS_SITE_URL' : '', - }, - 'LICENSE_SETTINGS' : { - 'LICENSE_ACRONYM' : u'cc-by-sa', - 'LICENSE_LOGO_URL' : u'/images/cc-by-sa.png', - 'LICENSE_TITLE' : u'Creative Commons Attribution Share Alike 3.0', - 'LICENSE_URL' : 'http://creativecommons.org/licenses/by-sa/3.0/legalcode', - 'LICENSE_USE_LOGO' : True, - 'LICENSE_USE_URL' : True, - 'USE_LICENSE' : True, - }, - 'MARKUP' : { - 'MARKUP_CODE_FRIENDLY' : False, - 'ENABLE_MATHJAX' : True, - 'MATHJAX_BASE_URL' : u'/static/js/vendor/mathjax-MathJax-c9db6ac/', - 'ENABLE_AUTO_LINKING' : False, - 'AUTO_LINK_PATTERNS' : u'', - 'AUTO_LINK_URLS' : u'', - }, - 'MIN_REP' : { - 'MIN_REP_TO_ACCEPT_OWN_ANSWER' : 1, - 'MIN_REP_TO_ANSWER_OWN_QUESTION' : 1, - 'MIN_REP_TO_CLOSE_OTHERS_QUESTIONS' : 1200, - 'MIN_REP_TO_CLOSE_OWN_QUESTIONS' : 1, - 'MIN_REP_TO_DELETE_OTHERS_COMMENTS' : 5000, - 'MIN_REP_TO_DELETE_OTHERS_POSTS' : 10000, - 'MIN_REP_TO_EDIT_OTHERS_POSTS' : 5000, - 'MIN_REP_TO_EDIT_WIKI' : 200, - 'MIN_REP_TO_FLAG_OFFENSIVE' : 1, - 'MIN_REP_TO_HAVE_STRONG_URL' : 250, - 'MIN_REP_TO_LEAVE_COMMENTS' : 1, - 'MIN_REP_TO_LOCK_POSTS' : 10000, - 'MIN_REP_TO_REOPEN_OWN_QUESTIONS' : 1, - 'MIN_REP_TO_RETAG_OTHERS_QUESTIONS' : 100, - 'MIN_REP_TO_UPLOAD_FILES' : 1, - 'MIN_REP_TO_VIEW_OFFENSIVE_FLAGS' : 2000, - 'MIN_REP_TO_VOTE_DOWN' : 15, - 'MIN_REP_TO_VOTE_UP' : 1, - }, - 'QA_SITE_SETTINGS' : { - 'APP_COPYRIGHT' : u'Copyright Askbot, 2010-2011.', - 'APP_DESCRIPTION' : u'Open source question and answer forum written in Python and Django', - 'APP_KEYWORDS' : u'Askbot,CNPROG,forum,community', - 'APP_SHORT_NAME' : u'Askbot', - 'APP_TITLE' : u'Askbot: Open Source Q&A Forum', - 'APP_URL' : u'http://askbot.org', - 'FEEDBACK_SITE_URL' : u'', - 'ENABLE_GREETING_FOR_ANON_USER' : True, - 'GREETING_FOR_ANONYMOUS_USER' : u'First time here? Check out the FAQ!', - }, - 'REP_CHANGES' : { - 'MAX_REP_GAIN_PER_USER_PER_DAY' : 200, - 'REP_GAIN_FOR_ACCEPTING_ANSWER' : 2, - 'REP_GAIN_FOR_CANCELING_DOWNVOTE' : 1, - 'REP_GAIN_FOR_RECEIVING_ANSWER_ACCEPTANCE' : 15, - 'REP_GAIN_FOR_RECEIVING_DOWNVOTE_CANCELATION' : 2, - 'REP_GAIN_FOR_RECEIVING_UPVOTE' : 10, - 'REP_LOSS_FOR_CANCELING_ANSWER_ACCEPTANCE' : -2, - 'REP_LOSS_FOR_DOWNVOTING' : -2, - 'REP_LOSS_FOR_RECEIVING_CANCELATION_OF_ANSWER_ACCEPTANCE' : -5, - 'REP_LOSS_FOR_RECEIVING_DOWNVOTE' : -1, - 'REP_LOSS_FOR_RECEIVING_FIVE_FLAGS_PER_REVISION' : -100, - 'REP_LOSS_FOR_RECEIVING_FLAG' : -2, - 'REP_LOSS_FOR_RECEIVING_THREE_FLAGS_PER_REVISION' : -30, - 'REP_LOSS_FOR_RECEIVING_UPVOTE_CANCELATION' : -10, - }, - 'SOCIAL_SHARING' : { - 'ENABLE_SHARING_TWITTER' : False, - 'ENABLE_SHARING_FACEBOOK' : False, - 'ENABLE_SHARING_LINKEDIN' : False, - 'ENABLE_SHARING_IDENTICA' : False, - 'ENABLE_SHARING_GOOGLE' : False, - }, - 'SIDEBAR_MAIN' : { - 'SIDEBAR_MAIN_AVATAR_LIMIT' : 16, - 'SIDEBAR_MAIN_FOOTER' : u'', - 'SIDEBAR_MAIN_HEADER' : u'', - 'SIDEBAR_MAIN_SHOW_AVATARS' : True, - 'SIDEBAR_MAIN_SHOW_TAGS' : True, - 'SIDEBAR_MAIN_SHOW_TAG_SELECTOR' : True, - }, - 'SIDEBAR_PROFILE' : { - 'SIDEBAR_PROFILE_FOOTER' : u'', - 'SIDEBAR_PROFILE_HEADER' : u'', - }, - 'SIDEBAR_QUESTION' : { - 'SIDEBAR_QUESTION_FOOTER' : u'', - 'SIDEBAR_QUESTION_HEADER' : u'', - 'SIDEBAR_QUESTION_SHOW_META' : True, - 'SIDEBAR_QUESTION_SHOW_RELATED' : True, - 'SIDEBAR_QUESTION_SHOW_TAGS' : True, - }, - 'SITE_MODES' : { - 'ACTIVATE_BOOTSTRAP_MODE' : False, - }, - 'SKIN_COUNTER_SETTINGS' : { - - }, - 'SPAM_AND_MODERATION' : { - 'AKISMET_API_KEY' : u'', - 'USE_AKISMET' : False, - }, - 'USER_SETTINGS' : { - 'EDITABLE_SCREEN_NAME' : False, - 'EDITABLE_EMAIL' : False, - 'ALLOW_ADD_REMOVE_LOGIN_METHODS' : False, - 'ENABLE_GRAVATAR' : False, - 'GRAVATAR_TYPE' : u'identicon', - 'NAME_OF_ANONYMOUS_USER' : u'', - 'DEFAULT_AVATAR_URL' : u'/images/nophoto.png', - 'MIN_USERNAME_LENGTH' : 1, - 'ALLOW_ACCOUNT_RECOVERY_BY_EMAIL' : True, - }, - 'VOTE_RULES' : { - 'MAX_VOTES_PER_USER_PER_DAY' : 30, - 'MAX_FLAGS_PER_USER_PER_DAY' : 5, - 'MIN_DAYS_FOR_STAFF_TO_ACCEPT_ANSWER' : 0, - 'MIN_DAYS_TO_ANSWER_OWN_QUESTION' : 0, - 'MIN_FLAGS_TO_DELETE_POST' : 5, - 'MIN_FLAGS_TO_HIDE_POST' : 3, - 'MAX_DAYS_TO_CANCEL_VOTE' : 1, - 'VOTES_LEFT_WARNING_THRESHOLD' : 5, - }, - }, - }, -} diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 36d1a809df..16216bbe09 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -19,11 +19,10 @@ EMAIL_BACKEND = 'django_ses.SESBackend' SESSION_ENGINE = 'django.contrib.sessions.backends.cache' DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' -# Disable askbot, enable Berkeley forums -MITX_FEATURES['ENABLE_DISCUSSION'] = False +# Enable Berkeley forums MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = True -# IMPORTANT: With this enabled, the server must always be behind a proxy that +# IMPORTANT: With this enabled, the server must always be behind a proxy that # strips the header HTTP_X_FORWARDED_PROTO from client requests. Otherwise, # a user can fool our server into thinking it was an https connection. # See https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header @@ -80,4 +79,4 @@ if 'COURSE_ID' in ENV_TOKENS: ASKBOT_URL = "courses/{0}/discussions/".format(ENV_TOKENS['COURSE_ID']) PEARSON_TEST_USER = "pearsontest" -PEARSON_TEST_PASSWORD = AUTH_TOKENS.get("PEARSON_TEST_PASSWORD") \ No newline at end of file +PEARSON_TEST_PASSWORD = AUTH_TOKENS.get("PEARSON_TEST_PASSWORD") diff --git a/lms/envs/common.py b/lms/envs/common.py index 38e9940bd8..594dd854c5 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -27,15 +27,12 @@ import hashlib from collections import defaultdict import socket -import djcelery from path import path -from .askbotsettings import * # this is where LIVESETTINGS_OPTIONS comes from from .discussionsettings import * ################################### FEATURES ################################### COURSEWARE_ENABLED = True -ASKBOT_ENABLED = False GENERATE_RANDOM_USER_CREDENTIALS = False PERFSTATS = False @@ -102,14 +99,11 @@ PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/lms REPO_ROOT = PROJECT_ROOT.dirname() COMMON_ROOT = REPO_ROOT / "common" ENV_ROOT = REPO_ROOT.dirname() # virtualenv dir /mitx is in -ASKBOT_ROOT = REPO_ROOT / "askbot" COURSES_ROOT = ENV_ROOT / "data" DATA_DIR = COURSES_ROOT sys.path.append(REPO_ROOT) -sys.path.append(ASKBOT_ROOT) -sys.path.append(ASKBOT_ROOT / "askbot" / "deps") sys.path.append(PROJECT_ROOT / 'djangoapps') sys.path.append(PROJECT_ROOT / 'lib') sys.path.append(COMMON_ROOT / 'djangoapps') @@ -156,10 +150,8 @@ TEMPLATE_DIRS = ( TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.request', 'django.core.context_processors.static', - 'askbot.context.application_settings', 'django.contrib.messages.context_processors.messages', #'django.core.context_processors.i18n', - 'askbot.user_messages.context_processors.user_messages',#must be before auth 'django.contrib.auth.context_processors.auth', #this is required for admin 'django.core.context_processors.csrf', #necessary for csrf protection @@ -270,7 +262,6 @@ STATIC_ROOT = ENV_ROOT / "staticfiles" STATICFILES_DIRS = [ COMMON_ROOT / "static", PROJECT_ROOT / "static", - PROJECT_ROOT / "askbot" / "skins", ] if os.path.isdir(DATA_DIR): # Add the full course repo if there is no static directory @@ -316,35 +307,6 @@ ALLOWED_GITRELOAD_IPS = ['207.97.227.253', '50.57.128.197', '108.171.174.178'] # in the global settings.py AWS_QUERYSTRING_EXPIRE = 10 * 365 * 24 * 60 * 60 # 10 years -################################### ASKBOT ##################################### -LIVESETTINGS_OPTIONS['MITX_ROOT_URL'] = MITX_ROOT_URL -skin_settings = LIVESETTINGS_OPTIONS[1]['SETTINGS']['GENERAL_SKIN_SETTINGS'] -skin_settings['SITE_FAVICON'] = unicode(MITX_ROOT_URL) + skin_settings['SITE_FAVICON'] -skin_settings['SITE_LOGO_URL'] = unicode(MITX_ROOT_URL) + skin_settings['SITE_LOGO_URL'] -skin_settings['LOCAL_LOGIN_ICON'] = unicode(MITX_ROOT_URL) + skin_settings['LOCAL_LOGIN_ICON'] -LIVESETTINGS_OPTIONS[1]['SETTINGS']['LOGIN_PROVIDERS']['WORDPRESS_SITE_ICON'] = unicode(MITX_ROOT_URL) + LIVESETTINGS_OPTIONS[1]['SETTINGS']['LOGIN_PROVIDERS']['WORDPRESS_SITE_ICON'] -LIVESETTINGS_OPTIONS[1]['SETTINGS']['LICENSE_SETTINGS']['LICENSE_LOGO_URL'] = unicode(MITX_ROOT_URL) + LIVESETTINGS_OPTIONS[1]['SETTINGS']['LICENSE_SETTINGS']['LICENSE_LOGO_URL'] - -# ASKBOT_EXTRA_SKINS_DIR = ASKBOT_ROOT / "askbot" / "skins" -ASKBOT_EXTRA_SKINS_DIR = PROJECT_ROOT / "askbot" / "skins" -ASKBOT_ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff') -ASKBOT_MAX_UPLOAD_FILE_SIZE = 1024 * 1024 # result in bytes - -CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True -CACHE_PREFIX = SITE_ID -ASKBOT_URL = 'discussion/' -LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/' -LOGIN_URL = MITX_ROOT_URL + '/' - -ALLOW_UNICODE_SLUGS = False -ASKBOT_USE_STACKEXCHANGE_URLS = False # mimic url scheme of stackexchange -ASKBOT_CSS_DEVEL = True - -# Celery Settings -BROKER_TRANSPORT = "djkombu.transport.DatabaseTransport" -CELERY_ALWAYS_EAGER = True -djcelery.setup_loader() - ################################# SIMPLEWIKI ################################### SIMPLE_WIKI_REQUIRE_LOGIN_EDIT = True SIMPLE_WIKI_REQUIRE_LOGIN_VIEW = False @@ -380,8 +342,6 @@ TEMPLATE_LOADERS = ( # 'django.template.loaders.filesystem.Loader', # 'django.template.loaders.app_directories.Loader', - #'askbot.skins.loaders.filesystem_load_template_source', - # 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( @@ -400,13 +360,6 @@ MIDDLEWARE_CLASSES = ( 'course_wiki.course_nav.Middleware', - 'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware', - 'askbot.middleware.forum_mode.ForumModeMiddleware', - 'askbot.middleware.cancel.CancelActionMiddleware', - 'django.middleware.transaction.TransactionMiddleware', - 'askbot.middleware.view_log.ViewLogMiddleware', - 'askbot.middleware.spaceless.SpacelessMiddleware', - # 'askbot.middleware.pagesize.QuestionsPageSizeMiddleware', # 'debug_toolbar.middleware.DebugToolbarMiddleware', 'django_comment_client.utils.ViewNameMiddleware', @@ -642,18 +595,6 @@ INSTALLED_APPS = ( # For testing 'django_jasmine', - # Discussion + # Discussion forums 'django_comment_client', - - # For Askbot - 'django.contrib.sitemaps', - 'django.contrib.admin', - 'django_countries', - 'djcelery', - 'djkombu', - 'askbot', - 'askbot.deps.livesettings', - 'followit', - 'keyedcache', - 'robots' ) diff --git a/lms/envs/dev.py b/lms/envs/dev.py index f5213ba593..9d45a9ad6f 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -39,8 +39,7 @@ DATABASES = { } CACHES = { - # This is the cache used for most things. Askbot will not work without a - # functioning cache -- it relies on caching to load its settings in places. + # This is the cache used for most things. # In staging/prod envs, the sessions also live here. 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', @@ -159,16 +158,6 @@ DEBUG_TOOLBAR_PANELS = ( DEBUG_TOOLBAR_CONFIG = { 'INTERCEPT_REDIRECTS': False } -############################ FILE UPLOADS (ASKBOT) ############################# -DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' -MEDIA_ROOT = ENV_ROOT / "uploads" -MEDIA_URL = "/static/uploads/" -STATICFILES_DIRS.append(("uploads", MEDIA_ROOT)) -FILE_UPLOAD_TEMP_DIR = ENV_ROOT / "uploads" -FILE_UPLOAD_HANDLERS = ( - 'django.core.files.uploadhandler.MemoryFileUploadHandler', - 'django.core.files.uploadhandler.TemporaryFileUploadHandler', -) ########################### PIPELINE ################################# diff --git a/lms/envs/dev_ike.py b/lms/envs/dev_ike.py index 0be9146fd4..f9c7f094c5 100644 --- a/lms/envs/dev_ike.py +++ b/lms/envs/dev_ike.py @@ -44,7 +44,6 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # django 1.4 fo INSTALLED_APPS = tuple([ app for app in INSTALLED_APPS if not app.startswith('debug_toolbar') ]) MIDDLEWARE_CLASSES = tuple([ mcl for mcl in MIDDLEWARE_CLASSES if not mcl.startswith('debug_toolbar') ]) -#TEMPLATE_LOADERS = tuple([ app for app in TEMPLATE_LOADERS if not app.startswith('askbot') ]) #TEMPLATE_LOADERS = tuple([ app for app in TEMPLATE_LOADERS if not app.startswith('mitxmako') ]) TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', diff --git a/lms/envs/devgroups/courses.py b/lms/envs/devgroups/courses.py index 92b28f3575..e9ed28a09d 100644 --- a/lms/envs/devgroups/courses.py +++ b/lms/envs/devgroups/courses.py @@ -40,7 +40,3 @@ def course_db_for(course_id): 'NAME' : path_for_db(db_name) } } - -def askbot_url_for(course_id): - return "courses/{0}/discussions/".format(course_id) - diff --git a/lms/envs/devgroups/h_cs50.py b/lms/envs/devgroups/h_cs50.py index 3cd6d6f2c0..9643c33d35 100644 --- a/lms/envs/devgroups/h_cs50.py +++ b/lms/envs/devgroups/h_cs50.py @@ -1,4 +1,3 @@ from .courses import * DATABASES = course_db_for('HarvardX/CS50x/2012') -ASKBOT_URL = askbot_url_for("HarvardX/CS50x/2012") \ No newline at end of file diff --git a/lms/envs/devgroups/m_6002.py b/lms/envs/devgroups/m_6002.py index e791857d3a..411e2bcc3c 100644 --- a/lms/envs/devgroups/m_6002.py +++ b/lms/envs/devgroups/m_6002.py @@ -1,4 +1,3 @@ from .courses import * DATABASES = course_db_for('MITx/6.002x/2012_Fall') -ASKBOT_URL = askbot_url_for("MITx/6.002x/2012_Fall") diff --git a/lms/envs/devplus.py b/lms/envs/devplus.py index bb4524a1ab..5c79304c0a 100644 --- a/lms/envs/devplus.py +++ b/lms/envs/devplus.py @@ -5,7 +5,7 @@ you're running memcached. You'll want to use this to test caching and database migrations. Assumptions: -* MySQL 5.1 (version important -- askbot breaks on 5.5) +* MySQL 5.1 (version important? (askbot breaks on 5.5, but that's gone now)) Dir structure: /envroot/ diff --git a/lms/envs/edx4edx_aws.py b/lms/envs/edx4edx_aws.py index 963cd68ff7..adc2c6e1ce 100644 --- a/lms/envs/edx4edx_aws.py +++ b/lms/envs/edx4edx_aws.py @@ -25,7 +25,6 @@ COURSE_SETTINGS = {'edx4edx': {'number' : 'edX.01', STATICFILES_DIRS = [ PROJECT_ROOT / "static", - ASKBOT_ROOT / "askbot" / "skins", ("edx4edx", EDX4EDX_ROOT / "html"), ("circuits", DATA_DIR / "images"), ("handouts", DATA_DIR / "handouts"), diff --git a/lms/envs/static.py b/lms/envs/static.py index 179f9160c5..f5ec06a438 100644 --- a/lms/envs/static.py +++ b/lms/envs/static.py @@ -25,8 +25,7 @@ DATABASES = { } CACHES = { - # This is the cache used for most things. Askbot will not work without a - # functioning cache -- it relies on caching to load its settings in places. + # This is the cache used for most things. # In staging/prod envs, the sessions also live here. 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', @@ -50,12 +49,3 @@ CACHES = { # Dummy secret key for dev SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd' -############################ FILE UPLOADS (ASKBOT) ############################# -DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' -MEDIA_ROOT = ENV_ROOT / "uploads" -MEDIA_URL = "/discussion/upfiles/" -FILE_UPLOAD_TEMP_DIR = ENV_ROOT / "uploads" -FILE_UPLOAD_HANDLERS = ( - 'django.core.files.uploadhandler.MemoryFileUploadHandler', - 'django.core.files.uploadhandler.TemporaryFileUploadHandler', -) diff --git a/lms/envs/test.py b/lms/envs/test.py index e11946a47a..dc120af1b4 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -70,7 +70,6 @@ XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds STATICFILES_DIRS = [ COMMON_ROOT / "static", PROJECT_ROOT / "static", - ASKBOT_ROOT / "askbot" / "skins", ] STATICFILES_DIRS += [ (course_dir, COMMON_TEST_DATA_ROOT / course_dir) @@ -100,8 +99,7 @@ DATABASES = { } CACHES = { - # This is the cache used for most things. Askbot will not work without a - # functioning cache -- it relies on caching to load its settings in places. + # This is the cache used for most things. # In staging/prod envs, the sessions also live here. 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', @@ -130,11 +128,7 @@ MITX_FEATURES['AUTH_USE_OPENID'] = True MITX_FEATURES['AUTH_USE_OPENID_PROVIDER'] = True OPENID_PROVIDER_TRUSTED_ROOTS = ['*'] -############################ FILE UPLOADS (ASKBOT) ############################# -DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' -MEDIA_ROOT = TEST_ROOT / "uploads" -MEDIA_URL = "/static/uploads/" -STATICFILES_DIRS.append(("uploads", MEDIA_ROOT)) +############################ STATIC FILES ############################# new_staticfiles_dirs = [] # Strip out any static files that aren't in the repository root @@ -150,12 +144,6 @@ for static_dir in STATICFILES_DIRS: new_staticfiles_dirs.append(static_dir) STATICFILES_DIRS = new_staticfiles_dirs -FILE_UPLOAD_TEMP_DIR = PROJECT_ROOT / "uploads" -FILE_UPLOAD_HANDLERS = ( - 'django.core.files.uploadhandler.MemoryFileUploadHandler', - 'django.core.files.uploadhandler.TemporaryFileUploadHandler', -) - ################### Make tests faster #http://slacy.com/blog/2012/04/make-your-tests-faster-in-django-1-4/ diff --git a/lms/envs/test_ike.py b/lms/envs/test_ike.py index 8bdecb3c8f..9a340a545d 100644 --- a/lms/envs/test_ike.py +++ b/lms/envs/test_ike.py @@ -17,7 +17,6 @@ INSTALLED_APPS = [ app for app in INSTALLED_APPS - if not app.startswith('askbot') ] # Nose Test Runner @@ -52,8 +51,7 @@ DATABASES = { } CACHES = { - # This is the cache used for most things. Askbot will not work without a - # functioning cache -- it relies on caching to load its settings in places. + # This is the cache used for most things. # In staging/prod envs, the sessions also live here. 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', @@ -77,13 +75,3 @@ CACHES = { # Dummy secret key for dev SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd' -############################ FILE UPLOADS (ASKBOT) ############################# -DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' -MEDIA_ROOT = PROJECT_ROOT / "uploads" -MEDIA_URL = "/static/uploads/" -STATICFILES_DIRS.append(("uploads", MEDIA_ROOT)) -FILE_UPLOAD_TEMP_DIR = PROJECT_ROOT / "uploads" -FILE_UPLOAD_HANDLERS = ( - 'django.core.files.uploadhandler.MemoryFileUploadHandler', - 'django.core.files.uploadhandler.TemporaryFileUploadHandler', -) diff --git a/lms/static/sass/course.scss b/lms/static/sass/course.scss index 886871e4fe..924b4852c0 100644 --- a/lms/static/sass/course.scss +++ b/lms/static/sass/course.scss @@ -48,6 +48,7 @@ @import "course/instructor/instructor"; // Askbot / Discussion styles +// TODO: Get rid of askbot-specific styles. @import "course/discussion/askbot-original"; @import "course/discussion/discussion"; @import "course/discussion/sidebar"; diff --git a/lms/urls.py b/lms/urls.py index 047a828df4..4141a5e6b3 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -1,14 +1,8 @@ from django.conf import settings from django.conf.urls import patterns, include, url -from django.contrib import admin from django.conf.urls.static import static import django.contrib.auth.views -# Uncomment the next two lines to enable the admin: -if settings.DEBUG: - from django.contrib import admin - admin.autodiscover() - urlpatterns = ('', url(r'^$', 'branding.views.index', name="root"), # Main marketing page, or redirect to courseware url(r'^dashboard$', 'student.views.dashboard', name="dashboard"), @@ -246,20 +240,9 @@ if settings.QUICKEDIT: urlpatterns += (url(r'^quickedit/(?P[^/]*)$', 'dogfood.views.quickedit'),) urlpatterns += (url(r'^dogfood/(?P[^/]*)$', 'dogfood.views.df_capa_problem'),) -if settings.ASKBOT_ENABLED: - urlpatterns += (url(r'^%s' % settings.ASKBOT_URL, include('askbot.urls')), \ - url(r'^settings/', include('askbot.deps.livesettings.urls')), \ - url(r'^followit/', include('followit.urls')), \ -# url(r'^robots.txt$', include('robots.urls')), - ) - - - if settings.DEBUG: - ## Jasmine and admin - urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')), - url(r'^admin/', include(admin.site.urls)), - ) + ## Jasmine + urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),) if settings.MITX_FEATURES.get('AUTH_USE_OPENID'): urlpatterns += ( From d636d729c605cb31f9124a0209ba67eabc5912a3 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 30 Oct 2012 19:54:15 -0400 Subject: [PATCH 164/228] add migration to remove askbot columns --- .../student/migrations/0021_remove_askbot.py | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 common/djangoapps/student/migrations/0021_remove_askbot.py diff --git a/common/djangoapps/student/migrations/0021_remove_askbot.py b/common/djangoapps/student/migrations/0021_remove_askbot.py new file mode 100644 index 0000000000..b933f6cc7c --- /dev/null +++ b/common/djangoapps/student/migrations/0021_remove_askbot.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +ASKBOT_AUTH_USER_COLUMNS = ( + 'website', + 'about', + 'gold', + 'email_isvalid', + 'real_name', + 'location', + 'reputation', + 'gravatar', + 'bronze', + 'last_seen', + 'silver', + 'questions_per_page', + 'new_response_count', + 'seen_response_count', +) + + +class Migration(SchemaMigration): + + def forwards(self, orm): + "Kill the askbot" + for column in ASKBOT_AUTH_USER_COLUMNS: + db.delete_column('auth_user', column) + + def backwards(self, orm): + raise RuntimeError("Cannot reverse this migration: there's no going back to Askbot.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'student.courseenrollment': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'student.pendingemailchange': { + 'Meta': {'object_name': 'PendingEmailChange'}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.pendingnamechange': { + 'Meta': {'object_name': 'PendingNameChange'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.registration': { + 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, + 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), + 'gender': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}), + 'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'student.usertestgroup': { + 'Meta': {'object_name': 'UserTestGroup'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}) + } + } + + complete_apps = ['student'] From 22ed22d956cbd51332db89a45b4c96819d71cb23 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Tue, 30 Oct 2012 18:35:03 -0400 Subject: [PATCH 165/228] Updating the certificate app for testing Conflicts: lms/djangoapps/certificates/models.py --- .../management/commands/ungenerated_certs.py | 69 ++++++-- lms/djangoapps/certificates/models.py | 155 +++++------------ lms/djangoapps/certificates/views.py | 163 +++--------------- 3 files changed, 127 insertions(+), 260 deletions(-) diff --git a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py index 7d384103ab..947af913dd 100644 --- a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py +++ b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py @@ -1,22 +1,71 @@ from django.utils.simplejson import dumps from django.core.management.base import BaseCommand, CommandError from certificates.models import GeneratedCertificate +from courseware import grades, courses +from django.contrib.auth.models import User +from django.test.client import RequestFactory +from profilehooks import profile +import cProfile +from pprint import pprint +from capa.xqueue_interface import XQueueInterface +from capa.xqueue_interface import make_xheader +from django.conf import settings +from requests.auth import HTTPBasicAuth class Command(BaseCommand): help = """ - This command finds all GeneratedCertificate objects that do not have a - certificate generated. These come into being when a user requests a - certificate, or when grade_all_students is called (for pre-generating - certificates). - + This command finds all users that have not been graded + for a single course. It returns a json formatted list of users and their user ids """ +# @profile + def _grade(self,student, request, course): + grades.grade(student, request, course) + def handle(self, *args, **options): - users = GeneratedCertificate.objects.filter( - download_url=None) - user_output = [{'user_id':user.user_id, 'name':user.name} - for user in users] - self.stdout.write(dumps(user_output) + "\n") + factory = RequestFactory() + course_id = 'BerkeleyX/CS169.1x/2012_Fall' + course = courses.get_course_by_id(course_id) + if settings.XQUEUE_INTERFACE.get('basic_auth') is not None: + requests_auth = HTTPBasicAuth( + *settings.XQUEUE_INTERFACE['basic_auth']) + else: + requests_auth = None + + xqueue_interface = XQueueInterface( + settings.XQUEUE_INTERFACE['url'], + settings.XQUEUE_INTERFACE['django_auth'], + requests_auth, + ) + + header = make_xheader('/certificate', 'foo', 'test-pull') + print "Sending test message to queue" + xqueue_interface.send_to_queue(header, { 'test': 'foo' }) + + #enrolled_students = User.objects.filter( + # courseenrollment__course_id=course_id).prefetch_related( + # "groups").order_by('username') + #generated_certificates = GeneratedCertificate.objects.filter( + # course_id=course_id) + #request = factory.get('/') + #student = User.objects.get(username='03199618') + #print "total students: {0}".format(len(enrolled_students)) + #count = 0 + #for student in enrolled_students: + # count += 1 + # if count % 1000 == 0: + # print "{0}/{1}".format(count, len(enrolled_students)) + # grades.grade(student, request, course) + + #for student in enrolled_students: + # g = grades.grade(student, request, course) + # if g['grade'] is not None: + # print str(student) + # pprint(g) + # break + + + diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 5815db64ca..eacd5b7977 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -1,5 +1,3 @@ -from django.conf import settings as settings - from django.contrib.auth.models import User from django.db import models @@ -14,131 +12,66 @@ we save these generated certificates (for later verification), we also record the UUID so that if we regenerate the certificate it will have the same UUID. -If certificates are being generated on the fly, a GeneratedCertificate -should be created with the user, certificate_id, and enabled set -when a student requests a certificate. When the certificate has been -generated, the download_url should be set. +Certificates are generated in batches by a cron job, when a +certificate is available for download the GeneratedCertificate +table is updated with information that will be displayed +on the course overview page. -Certificates can also be pre-generated. In this case, the user, -certificate_id, and download_url are all set before the user does -anything. When the user requests the certificate, only enabled -needs to be set to true. ''' class GeneratedCertificate(models.Model): - user = models.ForeignKey(User, db_index=True) - # This is the name at the time of request - name = models.CharField(blank=True, max_length=255) + user = models.ForeignKey(User) + course_id = models.CharField(max_length=255, default=False) + certificate_id = models.CharField(max_length=32, default=False) + graded_certificate_id = models.CharField(max_length=32, default=False) - certificate_id = models.CharField(max_length=32, null=True, default=None) - graded_certificate_id = models.CharField(max_length=32, null=True, default=None) + download_url = models.CharField(max_length=128, default=False) + graded_download_url = models.CharField(max_length=128, default=False) + grade = models.CharField(max_length=5, default=False) + key = models.CharField(max_length=32, default=False) - download_url = models.CharField(max_length=128, null=True) - graded_download_url = models.CharField(max_length=128, null=True) - - grade = models.CharField(max_length=5, null=True) - - # enabled should only be true if the student has earned a grade in the course - # The student must have a grade and request a certificate for enabled to be True + # enabled should only be true if the student has earned a passing grade + # in the course. enabled = models.BooleanField(default=False) -class RevokedCertificate(models.Model): - """ - This model is for when a GeneratedCertificate must be regenerated. This model - contains all the same fields, to store a record of what the GeneratedCertificate - was before it was revoked (at which time all of it's information can change when - it is regenerated). - - GeneratedCertificate may be deleted once they are revoked, and then created again. - For this reason, the only link between a GeneratedCertificate and RevokedCertificate - is that they share the same user. - """ - ####-------------------New Fields--------------------#### - explanation = models.TextField(blank=True) - - ####---------Fields from GeneratedCertificate---------#### - user = models.ForeignKey(User, db_index=True) - # This is the name at the time of request - name = models.CharField(blank=True, max_length=255) - - certificate_id = models.CharField(max_length=32, null=True, default=None) - graded_certificate_id = models.CharField(max_length=32, null=True, default=None) - - download_url = models.CharField(max_length=128, null=True) - graded_download_url = models.CharField(max_length=128, null=True) - - grade = models.CharField(max_length=5, null=True) - - enabled = models.BooleanField(default=False) - - -def revoke_certificate(certificate, explanation): - """ - This method takes a GeneratedCertificate. It records its information from the certificate - into a RevokedCertificate, and then marks the certificate as needing regenerating. - When the new certificiate is regenerated it will have new IDs and download URLS. - - Once this method has been called, it is safe to delete the certificate, or modify the - certificate's name or grade until it has been generated again. - """ - revoked = RevokedCertificate(user=certificate.user, - name=certificate.name, - certificate_id=certificate.certificate_id, - graded_certificate_id=certificate.graded_certificate_id, - download_url=certificate.download_url, - graded_download_url=certificate.graded_download_url, - grade=certificate.grade, - enabled=certificate.enabled) - - revoked.explanation = explanation - - certificate.certificate_id = None - certificate.graded_certificate_id = None - certificate.download_url = None - certificate.graded_download_url = None - - certificate.save() - revoked.save() - - -def certificate_state_for_student(student, grade): +def certificate_state_for_student(student): ''' - This returns a dictionary with a key for state, and other information. The state is one of the - following: + This returns a dictionary with a key for state, and other information. + The state is one of the following: - unavailable - A student is not eligible for a certificate. - requestable - A student is eligible to request a certificate - generating - A student has requested a certificate, but it is not generated yet. - downloadable - The certificate has been requested and is available for download. + unavailable - A student is not eligible for a certificate. + generating - A student has requested a certificate, + but it is not generated yet. + downloadable - The certificate has been requested and is + available for download. - If the state is "downloadable", the dictionary also contains "download_url" and "graded_download_url". + If the state is "downloadable", the dictionary also contains + "download_url" and "graded_download_url". ''' - if grade: - #TODO: Remove the following after debugging - if settings.DEBUG_SURVEY: - return {'state': 'requestable'} - - try: - generated_certificate = GeneratedCertificate.objects.get(user=student) - if generated_certificate.enabled: - if generated_certificate.download_url: - return {'state': 'downloadable', - 'download_url': generated_certificate.download_url, - 'graded_download_url': generated_certificate.graded_download_url} - else: - return {'state': 'generating'} + try: + generated_certificate = GeneratedCertificate.objects.get( + user=student) + if generated_certificate.enabled: + if generated_certificate.download_url: + return { + 'state': 'downloadable', + 'download_url': + generated_certificate.download_url, + 'graded_download_url': + generated_certificate.graded_download_url + } else: - # If enabled=False, it may have been pre-generated but not yet requested - # Our output will be the same as if the GeneratedCertificate did not exist - pass - except GeneratedCertificate.DoesNotExist: + return {'state': 'generating'} + else: + # If enabled=False, there is no certificate available + # Our output will be the same as if the + # GeneratedCertificate did not exist pass - return {'state': 'requestable'} - else: - # No grade, no certificate. No exceptions - return {'state': 'unavailable'} + except GeneratedCertificate.DoesNotExist: + pass + return {'state': 'unavailable'} diff --git a/lms/djangoapps/certificates/views.py b/lms/djangoapps/certificates/views.py index 472215ab5c..d701794b6d 100644 --- a/lms/djangoapps/certificates/views.py +++ b/lms/djangoapps/certificates/views.py @@ -1,146 +1,31 @@ -import json import logging -import uuid - -from django.conf import settings -from django.contrib.auth.decorators import login_required -from django.core.mail import send_mail -from django.http import Http404, HttpResponse -from django.shortcuts import redirect - -import courseware.grades as grades -from certificates.models import GeneratedCertificate, certificate_state_for_student, revoke_certificate -from mitxmako.shortcuts import render_to_response, render_to_string -from student.models import UserProfile -#TODO: Finish migrating these changes from stable -# from student.survey_questions import exit_survey_list_for_student -# from student.views import student_took_survey, record_exit_survey +from certificates.models import GeneratedCertificate +from pprint import pprint log = logging.getLogger("mitx.certificates") -@login_required -def certificate_request(request): - ''' Attempt to send a certificate. ''' - if not settings.END_COURSE_ENABLED: - raise Http404 +def update_certificate(request): + """ + Will update GeneratedCertificate for a new certificate or + modify an existing certificate entry. + """ if request.method == "POST": - honor_code_verify = request.POST.get('cert_request_honor_code_verify', 'false') - name_verify = request.POST.get('cert_request_name_verify', 'false') - id_verify = request.POST.get('cert_request_id_verify', 'false') - error = '' - - def return_error(error): - return HttpResponse(json.dumps({'success': False, - 'error': error})) - - if honor_code_verify != 'true': - error += 'Please verify that you have followed the honor code to receive a certificate. ' - - if name_verify != 'true': - error += 'Please verify that your name is correct to receive a certificate. ' - - if id_verify != 'true': - error += 'Please certify that you understand the unique ID on the certificate. ' - - if len(error) > 0: - return return_error(error) - - survey_response = record_exit_survey(request, internal_request=True) - if not survey_response['success']: - return return_error(survey_response['error']) - - grade = None - student_gradesheet = grades.grade(request.user, request, course) - grade = student_gradesheet['grade'] - - if not grade: - return return_error('You have not earned a grade in this course. ') - - generate_certificate(request.user, grade) - - return HttpResponse(json.dumps({'success': True})) - - else: - #This is not a POST, we should render the page with the form - - student_gradesheet = grades.grade(request.user, request, course) - certificate_state = certificate_state_for_student(request.user, grade_sheet['grade']) - - if certificate_state['state'] != "requestable": - return redirect("/profile") - - user_info = UserProfile.objects.get(user=request.user) - - took_survey = student_took_survey(user_info) - if settings.DEBUG_SURVEY: - took_survey = False - survey_list = [] - if not took_survey: - survey_list = exit_survey_list_for_student(request.user) - - context = {'certificate_state': certificate_state, - 'took_survey': took_survey, - 'survey_list': survey_list, - 'name': user_info.name} - - return render_to_response('cert_request.html', context) - - -# This method should only be called if the user has a grade and has requested a certificate -def generate_certificate(user, grade): - # Make sure to see the comments in models.GeneratedCertificate to read about the valid - # states for a GeneratedCertificate object - if grade and user.is_active: - generated_certificate = None - - try: - generated_certificate = GeneratedCertificate.objects.get(user=user) - except GeneratedCertificate.DoesNotExist: - generated_certificate = GeneratedCertificate(user=user) - - generated_certificate.enabled = True - if generated_certificate.graded_download_url and (generated_certificate.grade != grade): - log.critical(u"A graded certificate has been pre-generated with the grade " - "of {gen_grade} but requested by user id {userid} with grade " - "{req_grade}! The download URLs were {graded_dl_url} and " - "{ungraded_dl_url}".format( - gen_grade=generated_certificate.grade, - req_grade=grade, - graded_dl_url=generated_certificate.graded_download_url, - ungraded_dl_url=generated_certificate.download_url, - userid=user.id)) - revoke_certificate(generated_certificate, "The grade on this certificate may be inaccurate.") - - user_name = UserProfile.objects.get(user=user).name - if generated_certificate.download_url and (generated_certificate.name != user_name): - log.critical(u"A Certificate has been pre-generated with the name of " - "{gen_name} but current name is {user_name} (user id is " - "{userid})! The download URLs were {graded_dl_url} and " - "{ungraded_dl_url}".format( - gen_name=generated_certificate.name.encode('utf-8'), - user_name=user_name.encode('utf-8'), - graded_dl_url=generated_certificate.graded_download_url, - ungraded_dl_url=generated_certificate.download_url, - userid=user.id)) - revoke_certificate(generated_certificate, "The name on this certificate may be inaccurate.") - - generated_certificate.grade = grade - generated_certificate.name = user_name - generated_certificate.save() - - certificate_id = generated_certificate.certificate_id - - log.debug("Generating certificate for " + str(user.username) + " with ID: " + str(certificate_id)) - - # TODO: If the certificate was pre-generated, send the email that it is ready to download - if certificate_state_for_student(user, grade)['state'] == "downloadable": - subject = render_to_string('emails/certificate_ready_subject.txt', {}) - subject = ''.join(subject.splitlines()) - message = render_to_string('emails/certificate_ready.txt', {}) - - res = send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email, ]) - - else: - log.warning("Asked to generate a certificate for student " + str(user.username) + " but with a grade of " + str(grade) + " and active status " + str(user.is_active)) + pprint(request) +# user = request.POST.get('user') +# try: +# generated_certificate = GeneratedCertificate.objects.get( +# key=key) +# except GeneratedCertificate.DoesNotExist: +# generated_certificate = GeneratedCertificate(user=user) +# +# enabled = request.POST.get('enabled') +# enabled = True if enabled == 'True' else False +# generated_certificate.grade = request.POST.get('grade') +# generated_certificate.download_url = request.POST.get('download_url') +# generated_certificate.graded_download_url = request.POST.get( +# 'graded_download_url') +# generated_certificate.course_id = request.POST.get('course_id') +# generated_certificate.enabled = enabled +# generated_certificate.save() From 8ccaafd758350bd8929dbb70289c99961291d848 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Tue, 30 Oct 2012 20:17:02 -0400 Subject: [PATCH 166/228] adding migration for certificate changes --- ...el_field_generatedcertificate_name__add.py | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 lms/djangoapps/certificates/migrations/0008_auto__del_revokedcertificate__del_field_generatedcertificate_name__add.py diff --git a/lms/djangoapps/certificates/migrations/0008_auto__del_revokedcertificate__del_field_generatedcertificate_name__add.py b/lms/djangoapps/certificates/migrations/0008_auto__del_revokedcertificate__del_field_generatedcertificate_name__add.py new file mode 100644 index 0000000000..cbec7214c0 --- /dev/null +++ b/lms/djangoapps/certificates/migrations/0008_auto__del_revokedcertificate__del_field_generatedcertificate_name__add.py @@ -0,0 +1,163 @@ +# -*- coding: 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): + # Deleting model 'RevokedCertificate' + db.delete_table('certificates_revokedcertificate') + + # Deleting field 'GeneratedCertificate.name' + db.delete_column('certificates_generatedcertificate', 'name') + + # Adding field 'GeneratedCertificate.course_id' + db.add_column('certificates_generatedcertificate', 'course_id', + self.gf('django.db.models.fields.CharField')(default=False, max_length=255), + keep_default=False) + + # Adding field 'GeneratedCertificate.key' + db.add_column('certificates_generatedcertificate', 'key', + self.gf('django.db.models.fields.CharField')(default=False, max_length=32), + keep_default=False) + + + # Changing field 'GeneratedCertificate.grade' + db.alter_column('certificates_generatedcertificate', 'grade', self.gf('django.db.models.fields.CharField')(max_length=5)) + + # Changing field 'GeneratedCertificate.certificate_id' + db.alter_column('certificates_generatedcertificate', 'certificate_id', self.gf('django.db.models.fields.CharField')(max_length=32)) + + # Changing field 'GeneratedCertificate.download_url' + db.alter_column('certificates_generatedcertificate', 'download_url', self.gf('django.db.models.fields.CharField')(max_length=128)) + + # Changing field 'GeneratedCertificate.graded_download_url' + db.alter_column('certificates_generatedcertificate', 'graded_download_url', self.gf('django.db.models.fields.CharField')(max_length=128)) + + # Changing field 'GeneratedCertificate.graded_certificate_id' + db.alter_column('certificates_generatedcertificate', 'graded_certificate_id', self.gf('django.db.models.fields.CharField')(max_length=32)) + + def backwards(self, orm): + # Adding model 'RevokedCertificate' + db.create_table('certificates_revokedcertificate', ( + ('grade', self.gf('django.db.models.fields.CharField')(max_length=5, null=True)), + ('certificate_id', self.gf('django.db.models.fields.CharField')(default=None, max_length=32, null=True)), + ('explanation', self.gf('django.db.models.fields.TextField')(blank=True)), + ('graded_download_url', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('enabled', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('download_url', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)), + ('graded_certificate_id', self.gf('django.db.models.fields.CharField')(default=None, max_length=32, null=True)), + )) + db.send_create_signal('certificates', ['RevokedCertificate']) + + # Adding field 'GeneratedCertificate.name' + db.add_column('certificates_generatedcertificate', 'name', + self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), + keep_default=False) + + # Deleting field 'GeneratedCertificate.course_id' + db.delete_column('certificates_generatedcertificate', 'course_id') + + # Deleting field 'GeneratedCertificate.key' + db.delete_column('certificates_generatedcertificate', 'key') + + + # Changing field 'GeneratedCertificate.grade' + db.alter_column('certificates_generatedcertificate', 'grade', self.gf('django.db.models.fields.CharField')(max_length=5, null=True)) + + # Changing field 'GeneratedCertificate.certificate_id' + db.alter_column('certificates_generatedcertificate', 'certificate_id', self.gf('django.db.models.fields.CharField')(max_length=32, null=True)) + + # Changing field 'GeneratedCertificate.download_url' + db.alter_column('certificates_generatedcertificate', 'download_url', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)) + + # Changing field 'GeneratedCertificate.graded_download_url' + db.alter_column('certificates_generatedcertificate', 'graded_download_url', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)) + + # Changing field 'GeneratedCertificate.graded_certificate_id' + db.alter_column('certificates_generatedcertificate', 'graded_certificate_id', self.gf('django.db.models.fields.CharField')(max_length=32, null=True)) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'certificates.generatedcertificate': { + 'Meta': {'object_name': 'GeneratedCertificate'}, + 'certificate_id': ('django.db.models.fields.CharField', [], {'default': 'False', 'max_length': '32'}), + 'course_id': ('django.db.models.fields.CharField', [], {'default': 'False', 'max_length': '255'}), + 'download_url': ('django.db.models.fields.CharField', [], {'default': 'False', 'max_length': '128'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'grade': ('django.db.models.fields.CharField', [], {'default': 'False', 'max_length': '5'}), + 'graded_certificate_id': ('django.db.models.fields.CharField', [], {'default': 'False', 'max_length': '32'}), + 'graded_download_url': ('django.db.models.fields.CharField', [], {'default': 'False', 'max_length': '128'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'default': 'False', 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['certificates'] \ No newline at end of file From b326b83fb65edff65b1ad4634c216d0fcddb2d07 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 16:34:51 -0400 Subject: [PATCH 167/228] Adding XQueueCertificate interface. Adding, removing and updating certs --- lms/djangoapps/certificates/queue.py | 88 ++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 lms/djangoapps/certificates/queue.py diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py new file mode 100644 index 0000000000..e7cdf31c51 --- /dev/null +++ b/lms/djangoapps/certificates/queue.py @@ -0,0 +1,88 @@ +from django.utils.simplejson import dumps +from django.core.management.base import BaseCommand, CommandError +from certificates.models import GeneratedCertificate +from certificates.models import certificate_status_for_student +from courseware import grades, courses +from django.contrib.auth.models import User +from django.test.client import RequestFactory +from pprint import pprint +from capa.xqueue_interface import XQueueInterface +from capa.xqueue_interface import make_xheader, make_hashkey +from django.conf import settings +from requests.auth import HTTPBasicAuth +from student.models import UserProfile +import json +import random +import logging + + +def add_cert_to_queue(student, course_id, xqueue_interface, request=None): + """ + Update or create a new GeneratedCertificates: + + If certificate generation is in progress this function will + return None. + + If certificate generation was completed for the user this + will add a request to delete the existing certificate. + + A new certificate request will be made if the student's + grade is not None + """ + log = logging.getLogger("mitx.certificates") + if request is None: + factory = RequestFactory() + request = factory.get('/') + + cert_status = certificate_status_for_student(student, course_id) + if cert_status['status'] == 'generating': + return None + + if cert_status['status'] == 'downloadable': + generated_certificate = GeneratedCertificate.objects.get( + user=student, course_id=course_id) + generated_certificate.status = 'unavailable' + key = generated_certificate.key + username = generated_certificate.user.username + generated_certificate.save() + + contents = { + 'remove': True, + 'verify_uuid': cert.verify_uuid, + 'download_uuid': cert.download_uuid, + 'key': cert.key, + 'username': cert.user.username + } + xheader = make_xheader('http://sandbox-jrjarvis-001.m.edx.org/certificate', key, 'test-pull') + (error, msg) = xqueue_interface.send_to_queue(header=xheader, + body=json.dumps(contents)) + + # grade the student + course = courses.get_course_by_id(course_id) + grade = grades.grade(student, request, course) + + if grade['grade'] is not None: + cert, created = GeneratedCertificate.objects.get_or_create( + user=student, course_id=course_id) + profile = UserProfile.objects.get(user=student) + + key = make_hashkey(random.random()) + + cert.status = 'generating' + cert.grade = grade['percent'] + cert.user = student + cert.course_id = course_id + cert.key = key + cert.save() + + contents = { + 'username': student.username, + 'course_id': course_id, + 'name': profile.name, + } + xheader = make_xheader('http://sandbox-jrjarvis-001.m.edx.org/update_certificate', key, 'test-pull') + (error, msg) = xqueue_interface.send_to_queue(header=xheader, + body=json.dumps(contents)) + if error: + log.critical('Unable to send message') + From 29ed95c1478c2745d3716be889221c877736da31 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 16:38:36 -0400 Subject: [PATCH 168/228] "ungenerated_certs" command update Updated for testing (for now) This will eventually be run periodically and/or server as a way to generate certificate requests from the commandline --- .../management/commands/ungenerated_certs.py | 75 +++++++------------ 1 file changed, 25 insertions(+), 50 deletions(-) diff --git a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py index 947af913dd..86bef18c76 100644 --- a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py +++ b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py @@ -1,71 +1,46 @@ from django.utils.simplejson import dumps from django.core.management.base import BaseCommand, CommandError from certificates.models import GeneratedCertificate +from certificates.models import certificate_status_for_student +from certificates.queue import XQueueCertInterface from courseware import grades, courses from django.contrib.auth.models import User from django.test.client import RequestFactory -from profilehooks import profile -import cProfile -from pprint import pprint from capa.xqueue_interface import XQueueInterface -from capa.xqueue_interface import make_xheader +from capa.xqueue_interface import make_xheader, make_hashkey from django.conf import settings from requests.auth import HTTPBasicAuth +from student.models import UserProfile +import json +import random +import logging class Command(BaseCommand): help = """ - This command finds all users that have not been graded - for a single course. - It returns a json formatted list of users and their user ids + Find all students that have need certificates + and put certificate requests on the queue """ -# @profile - def _grade(self,student, request, course): - grades.grade(student, request, course) - def handle(self, *args, **options): - factory = RequestFactory() + course_id = 'BerkeleyX/CS169.1x/2012_Fall' course = courses.get_course_by_id(course_id) - if settings.XQUEUE_INTERFACE.get('basic_auth') is not None: - requests_auth = HTTPBasicAuth( - *settings.XQUEUE_INTERFACE['basic_auth']) - else: - requests_auth = None - - xqueue_interface = XQueueInterface( - settings.XQUEUE_INTERFACE['url'], - settings.XQUEUE_INTERFACE['django_auth'], - requests_auth, - ) - - header = make_xheader('/certificate', 'foo', 'test-pull') - print "Sending test message to queue" - xqueue_interface.send_to_queue(header, { 'test': 'foo' }) - - #enrolled_students = User.objects.filter( - # courseenrollment__course_id=course_id).prefetch_related( - # "groups").order_by('username') - #generated_certificates = GeneratedCertificate.objects.filter( - # course_id=course_id) - #request = factory.get('/') - #student = User.objects.get(username='03199618') - #print "total students: {0}".format(len(enrolled_students)) - #count = 0 - #for student in enrolled_students: - # count += 1 - # if count % 1000 == 0: - # print "{0}/{1}".format(count, len(enrolled_students)) - # grades.grade(student, request, course) - - #for student in enrolled_students: - # g = grades.grade(student, request, course) - # if g['grade'] is not None: - # print str(student) - # pprint(g) - # break - + enrolled_students = User.objects.filter( + courseenrollment__course_id=course_id).prefetch_related( + "groups").order_by('username') + xq = XQueueCertInterface() + # delete all entries + for c in GeneratedCertificate.objects.all(): + c.delete() + count = 0 + for student in enrolled_students: + ret = xq.add_cert_to_queue(student, course_id) + if ret == 'generating': + print 'generating for {0}'.format(student) + count += 1 + if count > 10: + break From 416b5458a6106a2596381cedc4528b3026650585 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 16:39:21 -0400 Subject: [PATCH 169/228] Certificate model for tracking certificate statuses --- lms/djangoapps/certificates/models.py | 85 +++++++++++++-------------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index eacd5b7977..7541d31ef5 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -7,71 +7,68 @@ Certificates are created for a student and an offering of a course. When a certificate is generated, a unique ID is generated so that the certificate can be verified later. The ID is a UUID4, so that -it can't be easily guessed and so that it is unique. Even though -we save these generated certificates (for later verification), we -also record the UUID so that if we regenerate the certificate it -will have the same UUID. +it can't be easily guessed and so that it is unique. Certificates are generated in batches by a cron job, when a certificate is available for download the GeneratedCertificate table is updated with information that will be displayed on the course overview page. - ''' +class CertificateStatuses(object): + unavailable='unavailable' + generating='generating' + regenerating='regenerating' + deleting='deleting' + deleted='deleted' + downloadable='downloadable' + error='error' class GeneratedCertificate(models.Model): user = models.ForeignKey(User) - course_id = models.CharField(max_length=255, default=False) - certificate_id = models.CharField(max_length=32, default=False) - graded_certificate_id = models.CharField(max_length=32, default=False) + course_id = models.CharField(max_length=255, blank=True, default='') + verify_uuid = models.CharField(max_length=32, blank=True, default='') + download_uuid = models.CharField(max_length=32, blank=True, default='') + download_url = models.CharField(max_length=128, blank=True, default='') + grade = models.CharField(max_length=5, blank=True, default='') + key = models.CharField(max_length=32, blank=True, default='') + distinction = models.BooleanField(default=False) + status = models.CharField(max_length=32, default='unavailable') - download_url = models.CharField(max_length=128, default=False) - graded_download_url = models.CharField(max_length=128, default=False) - grade = models.CharField(max_length=5, default=False) - key = models.CharField(max_length=32, default=False) + class Meta: + unique_together= (('user', 'course_id'),) - # enabled should only be true if the student has earned a passing grade - # in the course. - enabled = models.BooleanField(default=False) - - -def certificate_state_for_student(student): +def certificate_status_for_student(student, course_id): ''' - This returns a dictionary with a key for state, and other information. - The state is one of the following: + This returns a dictionary with a key for status, and other information. + The status is one of the following: unavailable - A student is not eligible for a certificate. - generating - A student has requested a certificate, - but it is not generated yet. - downloadable - The certificate has been requested and is - available for download. + generating - A request has been made to generate a certificate, + but it has not been generated yet. + regenerating - A request has been made to regenerate a certificate, + but it has not been generated yet. + deleting - A request has been made to delete a certificate. + + deleted - The certificate has been deleted. + downloadable - The certificate is available for download. - If the state is "downloadable", the dictionary also contains - "download_url" and "graded_download_url". + If the status is "downloadable", the dictionary also contains + "download_url". ''' try: generated_certificate = GeneratedCertificate.objects.get( - user=student) - if generated_certificate.enabled: - if generated_certificate.download_url: - return { - 'state': 'downloadable', - 'download_url': - generated_certificate.download_url, - 'graded_download_url': - generated_certificate.graded_download_url - } - else: - return {'state': 'generating'} - else: - # If enabled=False, there is no certificate available - # Our output will be the same as if the - # GeneratedCertificate did not exist - pass + user=student, course_id=course_id) + if generated_certificate.status == CertificateStatuses.downloadable: + return { + 'status': CertificateStatuses.downloadable, + 'download_url': generated_certificate.download_url, + } + else: + return {'status': generated_certificate.status} except GeneratedCertificate.DoesNotExist: pass - return {'state': 'unavailable'} + return {'status': 'unavailable'} From 0ef54cdeb8a6cd8b7250a24b19700da06de07e3c Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 16:40:40 -0400 Subject: [PATCH 170/228] Certificate interface to xqueue Add, remove or update and existing certificate --- lms/djangoapps/certificates/queue.py | 238 ++++++++++++++++++++------- 1 file changed, 177 insertions(+), 61 deletions(-) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index e7cdf31c51..c254c19ebb 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -2,87 +2,203 @@ from django.utils.simplejson import dumps from django.core.management.base import BaseCommand, CommandError from certificates.models import GeneratedCertificate from certificates.models import certificate_status_for_student +from certificates.models import CertificateStatuses as status + from courseware import grades, courses from django.contrib.auth.models import User from django.test.client import RequestFactory -from pprint import pprint from capa.xqueue_interface import XQueueInterface from capa.xqueue_interface import make_xheader, make_hashkey from django.conf import settings from requests.auth import HTTPBasicAuth from student.models import UserProfile +from django.conf import settings + import json import random import logging +class XQueueCertInterface(object): -def add_cert_to_queue(student, course_id, xqueue_interface, request=None): - """ - Update or create a new GeneratedCertificates: - - If certificate generation is in progress this function will - return None. - - If certificate generation was completed for the user this - will add a request to delete the existing certificate. - - A new certificate request will be made if the student's - grade is not None - """ log = logging.getLogger("mitx.certificates") - if request is None: - factory = RequestFactory() - request = factory.get('/') - cert_status = certificate_status_for_student(student, course_id) - if cert_status['status'] == 'generating': - return None + def __init__(self, request=None): - if cert_status['status'] == 'downloadable': - generated_certificate = GeneratedCertificate.objects.get( - user=student, course_id=course_id) - generated_certificate.status = 'unavailable' - key = generated_certificate.key - username = generated_certificate.user.username - generated_certificate.save() + if settings.XQUEUE_INTERFACE.get('basic_auth') is not None: + requests_auth = HTTPBasicAuth( + *settings.XQUEUE_INTERFACE['basic_auth']) + else: + requests_auth = None - contents = { - 'remove': True, - 'verify_uuid': cert.verify_uuid, - 'download_uuid': cert.download_uuid, - 'key': cert.key, - 'username': cert.user.username - } - xheader = make_xheader('http://sandbox-jrjarvis-001.m.edx.org/certificate', key, 'test-pull') - (error, msg) = xqueue_interface.send_to_queue(header=xheader, - body=json.dumps(contents)) + if request is None: + factory = RequestFactory() + self.request = factory.get('/') + else: + self.request = request - # grade the student - course = courses.get_course_by_id(course_id) - grade = grades.grade(student, request, course) - if grade['grade'] is not None: - cert, created = GeneratedCertificate.objects.get_or_create( - user=student, course_id=course_id) - profile = UserProfile.objects.get(user=student) + self.xqueue_interface = XQueueInterface( + settings.XQUEUE_INTERFACE['url'], + settings.XQUEUE_INTERFACE['django_auth'], + requests_auth, + ) - key = make_hashkey(random.random()) - cert.status = 'generating' - cert.grade = grade['percent'] - cert.user = student - cert.course_id = course_id - cert.key = key - cert.save() + def regen_cert(self, student, course_id): + """ - contents = { - 'username': student.username, - 'course_id': course_id, - 'name': profile.name, - } - xheader = make_xheader('http://sandbox-jrjarvis-001.m.edx.org/update_certificate', key, 'test-pull') - (error, msg) = xqueue_interface.send_to_queue(header=xheader, - body=json.dumps(contents)) - if error: - log.critical('Unable to send message') + Arguments: + student - User.object + course_id - courseenrollment.course_id (string) + Removes certificate for a student, will change + the certificate status to 'regenerating'. + Will invalidate the old certificate and generate + a new one. + + When completed the certificate status will change + to 'downloadable' + + Returns the certificate status. + + """ + + VALID_STATUSES = [status.downloadable] + + cert_status = certificate_status_for_student( + student, course_id)['status'] + + if cert_status in VALID_STATUSES: + + profile = UserProfile.objects.get(user=student) + + cert = GeneratedCertificate.objects.get( + user=student, course_id=course_id) + cert.status = status.regenerating + cert.save() + + contents = { + 'action': 'regen', + 'remove_verify_uuid': cert.verify_uuid, + 'remove_download_uuid': cert.download_uuid, + 'username': cert.user.username, + 'course_id': cert.course_id, + 'name': profile.name, + } + + + key = cert.key + xheader = make_xheader('http://sandbox-jrjarvis-001.m.edx.org/certificate', key, 'test-pull') + (error, msg) = self.xqueue_interface.send_to_queue(header=xheader, + body=json.dumps(contents)) + + return cert_status + + + def remove_cert(self, student, course_id): + """ + + Arguments: + student - User.object + course_id - courseenrollment.course_id (string) + + Removes certificate for a student, will change + the certificate status to 'deleting'. + + When completed the certificate status will change + to 'deleted'. + + Returns the certificate status. + + """ + + VALID_STATUSES = [status.downloadable] + + cert_status = certificate_status_for_student( + student, course_id)['status'] + + if cert_status in VALID_STATUSES: + + cert = GeneratedCertificate.objects.get( + user=student, course_id=course_id) + username = cert.user.username + cert.status = status.deleting + cert.save() + + contents = { + 'action': 'remove', + 'remove_verify_uuid': cert.verify_uuid, + 'remove_download_uuid': cert.download_uuid, + 'username': cert.user.username, + } + + + key = cert.key + xheader = make_xheader('http://sandbox-jrjarvis-001.m.edx.org/certificate', key, 'test-pull') + (error, msg) = self.xqueue_interface.send_to_queue(header=xheader, + body=json.dumps(contents)) + + return cert_status + + + def add_cert_to_queue(self, student, course_id): + """ + + Arguments: + student - User.object + course_id - courseenrollment.course_id (string) + + Adds a new certificate request to the queue only if + the current certificate status is 'unavailable' or + 'deleted' and the student has a passing grade for + the course. + + When completed the certificate status will change + to 'downloadable'. + + If the current status is 'generating', 'regenerating' + or 'deleting' this function will return that status + + Returns 'unavailable' if the student is eligible for + a certificate but does not have a passing grade. + + """ + + VALID_STATUSES = [status.unavailable, status.deleted] + + cert_status = certificate_status_for_student( + student, course_id)['status'] + + + if cert_status in VALID_STATUSES: + # grade the student + course = courses.get_course_by_id(course_id) + grade = grades.grade(student, self.request, course) + + if grade['grade'] is not None: + cert_status = status.generating + cert, created = GeneratedCertificate.objects.get_or_create( + user=student, course_id=course_id) + profile = UserProfile.objects.get(user=student) + + key = make_hashkey(random.random()) + cert.status = cert_status + cert.grade = grade['percent'] + cert.user = student + cert.course_id = course_id + cert.key = key + cert.save() + + contents = { + 'action': 'create', + 'username': student.username, + 'course_id': course_id, + 'name': profile.name, + } + xheader = make_xheader('http://sandbox-jrjarvis-001.m.edx.org/update_certificate?{0}'.format(key), key, 'test-pull') + (error, msg) = self.xqueue_interface.send_to_queue( + header=xheader, body=json.dumps(contents)) + if error: + log.critical('Unable to send message') + + return cert_status From fceed0fc70ba70e8076545c04f9d4dcf56ffa19c Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 16:41:22 -0400 Subject: [PATCH 171/228] view for the certificate callback URL --- lms/djangoapps/certificates/views.py | 53 ++++++++++++++++++---------- lms/urls.py | 3 ++ 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/lms/djangoapps/certificates/views.py b/lms/djangoapps/certificates/views.py index d701794b6d..b71b648aec 100644 --- a/lms/djangoapps/certificates/views.py +++ b/lms/djangoapps/certificates/views.py @@ -1,10 +1,12 @@ import logging from certificates.models import GeneratedCertificate -from pprint import pprint +from django.views.decorators.csrf import csrf_exempt +from django.http import HttpResponse +import json log = logging.getLogger("mitx.certificates") - +@csrf_exempt def update_certificate(request): """ Will update GeneratedCertificate for a new certificate or @@ -12,20 +14,33 @@ def update_certificate(request): """ if request.method == "POST": - pprint(request) -# user = request.POST.get('user') -# try: -# generated_certificate = GeneratedCertificate.objects.get( -# key=key) -# except GeneratedCertificate.DoesNotExist: -# generated_certificate = GeneratedCertificate(user=user) -# -# enabled = request.POST.get('enabled') -# enabled = True if enabled == 'True' else False -# generated_certificate.grade = request.POST.get('grade') -# generated_certificate.download_url = request.POST.get('download_url') -# generated_certificate.graded_download_url = request.POST.get( -# 'graded_download_url') -# generated_certificate.course_id = request.POST.get('course_id') -# generated_certificate.enabled = enabled -# generated_certificate.save() + + xqueue_body = json.loads(request.POST.get('xqueue_body')) + xqueue_header = json.loads(request.POST.get('xqueue_header')) + + try: + cert = GeneratedCertificate.objects.get( + user__username=xqueue_body['username'], + course_id=xqueue_body['course_id'], + key=xqueue_header['lms_key']) + + except GeneratedCertificate.DoesNotExist: + log.critical('Unable to lookup certificate\n' + 'xqueue_body: {0}\n' + 'xqueue_header: {1}'.format( + xqueue_body, xqueue_header)) + + return HttpResponse(json.dumps({ + 'return_code': 1, + 'content': 'unable to lookup key'}), + mimetype='application/json') + + cert.download_uuid = xqueue_body['download_uuid'] + cert.verify_uuid = xqueue_body['download_uuid'] + cert.download_url = xqueue_body['url'] + cert.status = 'downloadable' + cert.save() + return HttpResponse(json.dumps({'return_code': 0}), + mimetype='application/json') + + diff --git a/lms/urls.py b/lms/urls.py index 4141a5e6b3..ea663aeaf8 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -4,6 +4,9 @@ from django.conf.urls.static import static import django.contrib.auth.views urlpatterns = ('', + # certificate view + + url(r'^update_certificate$', 'certificates.views.update_certificate'), url(r'^$', 'branding.views.index', name="root"), # Main marketing page, or redirect to courseware url(r'^dashboard$', 'student.views.dashboard', name="dashboard"), From 8c2e55676b32bb982c2fb39ecf0f675b61f98fa7 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 17:01:53 -0400 Subject: [PATCH 172/228] Cleanup --- lms/djangoapps/certificates/models.py | 27 ++++++++++++---------- lms/djangoapps/certificates/queue.py | 33 ++++++++++++--------------- lms/djangoapps/certificates/views.py | 5 ++-- 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 7541d31ef5..7fdea9c868 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -7,7 +7,7 @@ Certificates are created for a student and an offering of a course. When a certificate is generated, a unique ID is generated so that the certificate can be verified later. The ID is a UUID4, so that -it can't be easily guessed and so that it is unique. +it can't be easily guessed and so that it is unique. Certificates are generated in batches by a cron job, when a certificate is available for download the GeneratedCertificate @@ -16,14 +16,16 @@ on the course overview page. ''' + class CertificateStatuses(object): - unavailable='unavailable' - generating='generating' - regenerating='regenerating' - deleting='deleting' - deleted='deleted' - downloadable='downloadable' - error='error' + unavailable = 'unavailable' + generating = 'generating' + regenerating = 'regenerating' + deleting = 'deleting' + deleted = 'deleted' + downloadable = 'downloadable' + error = 'error' + class GeneratedCertificate(models.Model): user = models.ForeignKey(User) @@ -37,7 +39,8 @@ class GeneratedCertificate(models.Model): status = models.CharField(max_length=32, default='unavailable') class Meta: - unique_together= (('user', 'course_id'),) + unique_together = (('user', 'course_id'),) + def certificate_status_for_student(student, course_id): ''' @@ -48,9 +51,9 @@ def certificate_status_for_student(student, course_id): generating - A request has been made to generate a certificate, but it has not been generated yet. regenerating - A request has been made to regenerate a certificate, - but it has not been generated yet. + but it has not been generated yet. deleting - A request has been made to delete a certificate. - + deleted - The certificate has been deleted. downloadable - The certificate is available for download. @@ -67,7 +70,7 @@ def certificate_status_for_student(student, course_id): 'status': CertificateStatuses.downloadable, 'download_url': generated_certificate.download_url, } - else: + else: return {'status': generated_certificate.status} except GeneratedCertificate.DoesNotExist: pass diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index c254c19ebb..5aad41a8d4 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -1,27 +1,21 @@ -from django.utils.simplejson import dumps -from django.core.management.base import BaseCommand, CommandError from certificates.models import GeneratedCertificate from certificates.models import certificate_status_for_student from certificates.models import CertificateStatuses as status from courseware import grades, courses -from django.contrib.auth.models import User from django.test.client import RequestFactory from capa.xqueue_interface import XQueueInterface from capa.xqueue_interface import make_xheader, make_hashkey from django.conf import settings from requests.auth import HTTPBasicAuth from student.models import UserProfile -from django.conf import settings import json import random -import logging + class XQueueCertInterface(object): - log = logging.getLogger("mitx.certificates") - def __init__(self, request=None): if settings.XQUEUE_INTERFACE.get('basic_auth') is not None: @@ -36,14 +30,12 @@ class XQueueCertInterface(object): else: self.request = request - self.xqueue_interface = XQueueInterface( settings.XQUEUE_INTERFACE['url'], settings.XQUEUE_INTERFACE['django_auth'], requests_auth, ) - def regen_cert(self, student, course_id): """ @@ -86,15 +78,16 @@ class XQueueCertInterface(object): 'name': profile.name, } - key = cert.key - xheader = make_xheader('http://sandbox-jrjarvis-001.m.edx.org/certificate', key, 'test-pull') + # TODO - this needs to be read from settings + xheader = make_xheader( + 'http://sandbox-jrjarvis-001.m.edx.org/certificate', + key, 'test-pull') (error, msg) = self.xqueue_interface.send_to_queue(header=xheader, body=json.dumps(contents)) return cert_status - def remove_cert(self, student, course_id): """ @@ -121,7 +114,6 @@ class XQueueCertInterface(object): cert = GeneratedCertificate.objects.get( user=student, course_id=course_id) - username = cert.user.username cert.status = status.deleting cert.save() @@ -132,15 +124,16 @@ class XQueueCertInterface(object): 'username': cert.user.username, } - key = cert.key - xheader = make_xheader('http://sandbox-jrjarvis-001.m.edx.org/certificate', key, 'test-pull') + # TODO - this needs to be read from settings + xheader = make_xheader( + 'http://sandbox-jrjarvis-001.m.edx.org/certificate', + key, 'test-pull') (error, msg) = self.xqueue_interface.send_to_queue(header=xheader, body=json.dumps(contents)) return cert_status - def add_cert_to_queue(self, student, course_id): """ @@ -169,7 +162,6 @@ class XQueueCertInterface(object): cert_status = certificate_status_for_student( student, course_id)['status'] - if cert_status in VALID_STATUSES: # grade the student course = courses.get_course_by_id(course_id) @@ -195,10 +187,13 @@ class XQueueCertInterface(object): 'course_id': course_id, 'name': profile.name, } - xheader = make_xheader('http://sandbox-jrjarvis-001.m.edx.org/update_certificate?{0}'.format(key), key, 'test-pull') + # TODO - this needs to be read from settings + xheader = make_xheader( + 'http://sandbox-jrjarvis-001.m.edx.org/' + 'update_certificate?{0}'.format(key), key, 'test-pull') (error, msg) = self.xqueue_interface.send_to_queue( header=xheader, body=json.dumps(contents)) if error: - log.critical('Unable to send message') + raise Exception('Unable to send queue message') return cert_status diff --git a/lms/djangoapps/certificates/views.py b/lms/djangoapps/certificates/views.py index b71b648aec..450eb9e181 100644 --- a/lms/djangoapps/certificates/views.py +++ b/lms/djangoapps/certificates/views.py @@ -6,6 +6,7 @@ import json log = logging.getLogger("mitx.certificates") + @csrf_exempt def update_certificate(request): """ @@ -25,7 +26,7 @@ def update_certificate(request): key=xqueue_header['lms_key']) except GeneratedCertificate.DoesNotExist: - log.critical('Unable to lookup certificate\n' + log.critical('Unable to lookup certificate\n' 'xqueue_body: {0}\n' 'xqueue_header: {1}'.format( xqueue_body, xqueue_header)) @@ -42,5 +43,3 @@ def update_certificate(request): cert.save() return HttpResponse(json.dumps({'return_code': 0}), mimetype='application/json') - - From 84ff92d4d9f57a908f8ca8770cee36623305887b Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 17:05:09 -0400 Subject: [PATCH 173/228] new migrations for certificates --- ...icate_graded_download_url__del_field_ge.py | 95 +++++++++++++++++++ ...icate_enabled__add_field_generatedcerti.py | 81 ++++++++++++++++ ...icate_certificate_id__add_field_generat.py | 90 ++++++++++++++++++ 3 files changed, 266 insertions(+) create mode 100644 lms/djangoapps/certificates/migrations/0009_auto__del_field_generatedcertificate_graded_download_url__del_field_ge.py create mode 100644 lms/djangoapps/certificates/migrations/0010_auto__del_field_generatedcertificate_enabled__add_field_generatedcerti.py create mode 100644 lms/djangoapps/certificates/migrations/0011_auto__del_field_generatedcertificate_certificate_id__add_field_generat.py diff --git a/lms/djangoapps/certificates/migrations/0009_auto__del_field_generatedcertificate_graded_download_url__del_field_ge.py b/lms/djangoapps/certificates/migrations/0009_auto__del_field_generatedcertificate_graded_download_url__del_field_ge.py new file mode 100644 index 0000000000..40637452cc --- /dev/null +++ b/lms/djangoapps/certificates/migrations/0009_auto__del_field_generatedcertificate_graded_download_url__del_field_ge.py @@ -0,0 +1,95 @@ +# -*- coding: 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): + # Deleting field 'GeneratedCertificate.graded_download_url' + db.delete_column('certificates_generatedcertificate', 'graded_download_url') + + # Deleting field 'GeneratedCertificate.graded_certificate_id' + db.delete_column('certificates_generatedcertificate', 'graded_certificate_id') + + # Adding field 'GeneratedCertificate.distinction' + db.add_column('certificates_generatedcertificate', 'distinction', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + # Adding unique constraint on 'GeneratedCertificate', fields ['course_id', 'user'] + db.create_unique('certificates_generatedcertificate', ['course_id', 'user_id']) + + + def backwards(self, orm): + # Removing unique constraint on 'GeneratedCertificate', fields ['course_id', 'user'] + db.delete_unique('certificates_generatedcertificate', ['course_id', 'user_id']) + + # Adding field 'GeneratedCertificate.graded_download_url' + db.add_column('certificates_generatedcertificate', 'graded_download_url', + self.gf('django.db.models.fields.CharField')(default=False, max_length=128), + keep_default=False) + + # Adding field 'GeneratedCertificate.graded_certificate_id' + db.add_column('certificates_generatedcertificate', 'graded_certificate_id', + self.gf('django.db.models.fields.CharField')(default=False, max_length=32), + keep_default=False) + + # Deleting field 'GeneratedCertificate.distinction' + db.delete_column('certificates_generatedcertificate', 'distinction') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'certificates.generatedcertificate': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'GeneratedCertificate'}, + 'certificate_id': ('django.db.models.fields.CharField', [], {'default': 'False', 'max_length': '32'}), + 'course_id': ('django.db.models.fields.CharField', [], {'default': 'False', 'max_length': '255'}), + 'distinction': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'download_url': ('django.db.models.fields.CharField', [], {'default': 'False', 'max_length': '128'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'grade': ('django.db.models.fields.CharField', [], {'default': 'False', 'max_length': '5'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'default': 'False', 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['certificates'] \ No newline at end of file diff --git a/lms/djangoapps/certificates/migrations/0010_auto__del_field_generatedcertificate_enabled__add_field_generatedcerti.py b/lms/djangoapps/certificates/migrations/0010_auto__del_field_generatedcertificate_enabled__add_field_generatedcerti.py new file mode 100644 index 0000000000..5970c96f6b --- /dev/null +++ b/lms/djangoapps/certificates/migrations/0010_auto__del_field_generatedcertificate_enabled__add_field_generatedcerti.py @@ -0,0 +1,81 @@ +# -*- coding: 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): + # Deleting field 'GeneratedCertificate.enabled' + db.delete_column('certificates_generatedcertificate', 'enabled') + + # Adding field 'GeneratedCertificate.status' + db.add_column('certificates_generatedcertificate', 'status', + self.gf('django.db.models.fields.CharField')(default='unavailable', max_length=32), + keep_default=False) + + + def backwards(self, orm): + # Adding field 'GeneratedCertificate.enabled' + db.add_column('certificates_generatedcertificate', 'enabled', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + # Deleting field 'GeneratedCertificate.status' + db.delete_column('certificates_generatedcertificate', 'status') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'certificates.generatedcertificate': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'GeneratedCertificate'}, + 'certificate_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), + 'course_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}), + 'distinction': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'download_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), + 'grade': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '5', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'unavailable'", 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['certificates'] \ No newline at end of file diff --git a/lms/djangoapps/certificates/migrations/0011_auto__del_field_generatedcertificate_certificate_id__add_field_generat.py b/lms/djangoapps/certificates/migrations/0011_auto__del_field_generatedcertificate_certificate_id__add_field_generat.py new file mode 100644 index 0000000000..36d6e5d4f3 --- /dev/null +++ b/lms/djangoapps/certificates/migrations/0011_auto__del_field_generatedcertificate_certificate_id__add_field_generat.py @@ -0,0 +1,90 @@ +# -*- coding: 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): + # Deleting field 'GeneratedCertificate.certificate_id' + db.delete_column('certificates_generatedcertificate', 'certificate_id') + + # Adding field 'GeneratedCertificate.verify_uuid' + db.add_column('certificates_generatedcertificate', 'verify_uuid', + self.gf('django.db.models.fields.CharField')(default='', max_length=32, blank=True), + keep_default=False) + + # Adding field 'GeneratedCertificate.download_uuid' + db.add_column('certificates_generatedcertificate', 'download_uuid', + self.gf('django.db.models.fields.CharField')(default='', max_length=32, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Adding field 'GeneratedCertificate.certificate_id' + db.add_column('certificates_generatedcertificate', 'certificate_id', + self.gf('django.db.models.fields.CharField')(default='', max_length=32, blank=True), + keep_default=False) + + # Deleting field 'GeneratedCertificate.verify_uuid' + db.delete_column('certificates_generatedcertificate', 'verify_uuid') + + # Deleting field 'GeneratedCertificate.download_uuid' + db.delete_column('certificates_generatedcertificate', 'download_uuid') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'certificates.generatedcertificate': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'GeneratedCertificate'}, + 'course_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}), + 'distinction': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'download_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), + 'download_uuid': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), + 'grade': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '5', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'unavailable'", 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'verify_uuid': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['certificates'] \ No newline at end of file From 66c31fe21ab7f7ebd9fa2df029ff7b36ae5c5c82 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 20:51:05 -0400 Subject: [PATCH 174/228] Adding logging, reading sitename from settings --- lms/djangoapps/certificates/queue.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index 5aad41a8d4..44ccb82823 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -12,6 +12,10 @@ from student.models import UserProfile import json import random +import logging + + +logger = logging.getLogger(__name__) class XQueueCertInterface(object): @@ -79,12 +83,14 @@ class XQueueCertInterface(object): } key = cert.key - # TODO - this needs to be read from settings xheader = make_xheader( - 'http://sandbox-jrjarvis-001.m.edx.org/certificate', + 'http://{0}/certificate'.format(settings.SITE_NAME), key, 'test-pull') - (error, msg) = self.xqueue_interface.send_to_queue(header=xheader, - body=json.dumps(contents)) + (error, msg) = self.xqueue_interface.send_to_queue( + header=xheader, body=json.dumps(contents)) + if error: + logger.critical('Unable to add a request to the queue') + raise Exception('Unable to send queue message') return cert_status @@ -125,9 +131,8 @@ class XQueueCertInterface(object): } key = cert.key - # TODO - this needs to be read from settings xheader = make_xheader( - 'http://sandbox-jrjarvis-001.m.edx.org/certificate', + 'http://{0}/certificate'.format(settings.SITE_NAME), key, 'test-pull') (error, msg) = self.xqueue_interface.send_to_queue(header=xheader, body=json.dumps(contents)) @@ -187,13 +192,14 @@ class XQueueCertInterface(object): 'course_id': course_id, 'name': profile.name, } - # TODO - this needs to be read from settings xheader = make_xheader( - 'http://sandbox-jrjarvis-001.m.edx.org/' - 'update_certificate?{0}'.format(key), key, 'test-pull') + 'http://{0}/update_certificate?{1}'.format( + key, settings.SITE_NAME), key, 'test-pull') + (error, msg) = self.xqueue_interface.send_to_queue( header=xheader, body=json.dumps(contents)) if error: + logger.critical('Unable to post results to qserver') raise Exception('Unable to send queue message') return cert_status From f589adcd718ab76dc445894c31568cccf9b25a26 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 20:53:03 -0400 Subject: [PATCH 175/228] Comment about basic auth --- lms/djangoapps/certificates/queue.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index 44ccb82823..9ada836ea9 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -22,6 +22,9 @@ class XQueueCertInterface(object): def __init__(self, request=None): + # Get basic auth (username/password) for + # xqueue connection if it's in the settings + if settings.XQUEUE_INTERFACE.get('basic_auth') is not None: requests_auth = HTTPBasicAuth( *settings.XQUEUE_INTERFACE['basic_auth']) From a9b26cb87118d864ea6aeef4b552c281b34b28bc Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 21:37:02 -0400 Subject: [PATCH 176/228] Cleanup, docstrings, recording name in certificate table --- lms/djangoapps/certificates/models.py | 22 +++++++++ lms/djangoapps/certificates/queue.py | 71 +++++++++++++++++++++------ 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 7fdea9c868..ac7f8c8028 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -14,6 +14,27 @@ certificate is available for download the GeneratedCertificate table is updated with information that will be displayed on the course overview page. + +State diagram: + +[deleted,error,unavailable] [error,downloadable] + + + + + | | | + | | | + add_cert regen_cert del_cert + | | | + v v v + [generating] [regenerating] [deleting] + + + + + | | | + certificate certificate certificate + created removed,created deleted + +----------------+-------------+------->[error] + | | | + | | | + v v v + [downloadable] [downloadable] [deleted] + ''' @@ -37,6 +58,7 @@ class GeneratedCertificate(models.Model): key = models.CharField(max_length=32, blank=True, default='') distinction = models.BooleanField(default=False) status = models.CharField(max_length=32, default='unavailable') + name = models.CharField(blank=True, max_length=255) class Meta: unique_together = (('user', 'course_id'),) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index 9ada836ea9..1433fa1407 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -19,6 +19,35 @@ logger = logging.getLogger(__name__) class XQueueCertInterface(object): + """ + XQueueCertificateInterface provides an + interface to the xqueue server for + managing student certificates. + + Instantiating an object will create a new + connection to the queue server. + + See models.py for valid state transitions, + summary of methods: + + add_cert: Add a new certificate. Puts a single + request on the queue for the student/course. + Once the certificate is generated a post + will be made to the update_certificate + view which will save the certificate + download URL. + + regen_cert: Regenerate an existing certificate. + For a user that already has a certificate + this will delete the existing one and + generate a new cert. + + + del_cert: Delete an existing certificate + For a user that already has a certificate + this will delete his cert. + + """ def __init__(self, request=None): @@ -45,7 +74,6 @@ class XQueueCertInterface(object): def regen_cert(self, student, course_id): """ - Arguments: student - User.object course_id - courseenrollment.course_id (string) @@ -62,7 +90,7 @@ class XQueueCertInterface(object): """ - VALID_STATUSES = [status.downloadable] + VALID_STATUSES = [status.error, status.downloadable] cert_status = certificate_status_for_student( student, course_id)['status'] @@ -70,11 +98,16 @@ class XQueueCertInterface(object): if cert_status in VALID_STATUSES: profile = UserProfile.objects.get(user=student) + try: + cert = GeneratedCertificate.objects.get( + user=student, course_id=course_id) + except GeneratedCertificate.DoesNotExist: + logger.warning("Attempting to regenerate a certificate" + "for a user that doesn't have one") + raise - cert = GeneratedCertificate.objects.get( - user=student, course_id=course_id) cert.status = status.regenerating - cert.save() + cert.name = profile.name contents = { 'action': 'regen', @@ -94,10 +127,11 @@ class XQueueCertInterface(object): if error: logger.critical('Unable to add a request to the queue') raise Exception('Unable to send queue message') + cert.save() return cert_status - def remove_cert(self, student, course_id): + def del_cert(self, student, course_id): """ Arguments: @@ -114,17 +148,22 @@ class XQueueCertInterface(object): """ - VALID_STATUSES = [status.downloadable] + VALID_STATUSES = [status.error, status.downloadable] cert_status = certificate_status_for_student( student, course_id)['status'] if cert_status in VALID_STATUSES: - cert = GeneratedCertificate.objects.get( - user=student, course_id=course_id) + try: + cert = GeneratedCertificate.objects.get( + user=student, course_id=course_id) + except GeneratedCertificate.DoesNotExist: + logger.warning("Attempting to delete a certificate" + "for a user that doesn't have one") + raise + cert.status = status.deleting - cert.save() contents = { 'action': 'remove', @@ -140,9 +179,10 @@ class XQueueCertInterface(object): (error, msg) = self.xqueue_interface.send_to_queue(header=xheader, body=json.dumps(contents)) + cert.save() return cert_status - def add_cert_to_queue(self, student, course_id): + def add_cert(self, student, course_id): """ Arguments: @@ -150,8 +190,8 @@ class XQueueCertInterface(object): course_id - courseenrollment.course_id (string) Adds a new certificate request to the queue only if - the current certificate status is 'unavailable' or - 'deleted' and the student has a passing grade for + the current certificate status is 'unavailable', 'error' + or 'deleted' and the student has a passing grade for the course. When completed the certificate status will change @@ -165,7 +205,7 @@ class XQueueCertInterface(object): """ - VALID_STATUSES = [status.unavailable, status.deleted] + VALID_STATUSES = [status.unavailable, status.deleted, status.error] cert_status = certificate_status_for_student( student, course_id)['status'] @@ -187,7 +227,7 @@ class XQueueCertInterface(object): cert.user = student cert.course_id = course_id cert.key = key - cert.save() + cert.name = profile.name contents = { 'action': 'create', @@ -204,5 +244,6 @@ class XQueueCertInterface(object): if error: logger.critical('Unable to post results to qserver') raise Exception('Unable to send queue message') + cert.save() return cert_status From df37f111229cfd8e36e3e61d36ec2a92542abd74 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 21:40:23 -0400 Subject: [PATCH 177/228] Adding modified and created fields to GeneratedCertificates --- lms/djangoapps/certificates/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index ac7f8c8028..8f62ee85c2 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -59,6 +59,8 @@ class GeneratedCertificate(models.Model): distinction = models.BooleanField(default=False) status = models.CharField(max_length=32, default='unavailable') name = models.CharField(blank=True, max_length=255) + created_date = models.DateTimeField(auto_now_add=True) + modified_date = models.DateTimeField(auto_now=True) class Meta: unique_together = (('user', 'course_id'),) From cb1489a7cb9ac2fb2b9b6c3a9b3720d1780d13f1 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 21:51:10 -0400 Subject: [PATCH 178/228] docstring cleanup, grading for regen_cert --- lms/djangoapps/certificates/queue.py | 94 +++++++++++++--------------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index 1433fa1407..adccad0633 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -80,13 +80,11 @@ class XQueueCertInterface(object): Removes certificate for a student, will change the certificate status to 'regenerating'. - Will invalidate the old certificate and generate - a new one. - When completed the certificate status will change - to 'downloadable' + Certificate must be in the 'error' or 'downloadable' state + and the student must have a passing grade. - Returns the certificate status. + otherwise it will return the current state """ @@ -96,44 +94,49 @@ class XQueueCertInterface(object): student, course_id)['status'] if cert_status in VALID_STATUSES: + # grade the student + course = courses.get_course_by_id(course_id) + grade = grades.grade(student, self.request, course) - profile = UserProfile.objects.get(user=student) - try: - cert = GeneratedCertificate.objects.get( - user=student, course_id=course_id) - except GeneratedCertificate.DoesNotExist: - logger.warning("Attempting to regenerate a certificate" - "for a user that doesn't have one") - raise + if grade['grade'] is not None: - cert.status = status.regenerating - cert.name = profile.name + profile = UserProfile.objects.get(user=student) + try: + cert = GeneratedCertificate.objects.get( + user=student, course_id=course_id) + except GeneratedCertificate.DoesNotExist: + logger.warning("Attempting to regenerate a certificate" + "for a user that doesn't have one") + raise - contents = { - 'action': 'regen', - 'remove_verify_uuid': cert.verify_uuid, - 'remove_download_uuid': cert.download_uuid, - 'username': cert.user.username, - 'course_id': cert.course_id, - 'name': profile.name, - } + cert.status = status.regenerating + cert.name = profile.name - key = cert.key - xheader = make_xheader( - 'http://{0}/certificate'.format(settings.SITE_NAME), - key, 'test-pull') - (error, msg) = self.xqueue_interface.send_to_queue( - header=xheader, body=json.dumps(contents)) - if error: - logger.critical('Unable to add a request to the queue') - raise Exception('Unable to send queue message') - cert.save() + contents = { + 'action': 'regen', + 'remove_verify_uuid': cert.verify_uuid, + 'remove_download_uuid': cert.download_uuid, + 'username': cert.user.username, + 'course_id': cert.course_id, + 'name': profile.name, + } + + key = cert.key + xheader = make_xheader( + 'http://{0}/certificate'.format(settings.SITE_NAME), + key, 'test-pull') + (error, msg) = self.xqueue_interface.send_to_queue( + header=xheader, body=json.dumps(contents)) + if error: + logger.critical('Unable to add a request to the queue') + raise Exception('Unable to send queue message') + cert.save() return cert_status def del_cert(self, student, course_id): - """ + """ Arguments: student - User.object course_id - courseenrollment.course_id (string) @@ -141,10 +144,8 @@ class XQueueCertInterface(object): Removes certificate for a student, will change the certificate status to 'deleting'. - When completed the certificate status will change - to 'deleted'. - - Returns the certificate status. + Certificate must be in the 'error' or 'downloadable' state + otherwise it will return the current state """ @@ -189,19 +190,14 @@ class XQueueCertInterface(object): student - User.object course_id - courseenrollment.course_id (string) - Adds a new certificate request to the queue only if - the current certificate status is 'unavailable', 'error' - or 'deleted' and the student has a passing grade for - the course. + Request a new certificate for a student. + Will change the certificate status to 'deleting'. - When completed the certificate status will change - to 'downloadable'. + Certificate must be in the 'unavailable', 'error', + or 'deleted' state and the student must have + a passing grade. - If the current status is 'generating', 'regenerating' - or 'deleting' this function will return that status - - Returns 'unavailable' if the student is eligible for - a certificate but does not have a passing grade. + otherwise it will return the current state """ From 5019e2d03cda66b31ff633d272cb2001b3686d1e Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 21:51:43 -0400 Subject: [PATCH 179/228] cleanup --- .../management/commands/ungenerated_certs.py | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py index 86bef18c76..95f9a94aa4 100644 --- a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py +++ b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py @@ -1,19 +1,7 @@ -from django.utils.simplejson import dumps -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand from certificates.models import GeneratedCertificate -from certificates.models import certificate_status_for_student from certificates.queue import XQueueCertInterface -from courseware import grades, courses from django.contrib.auth.models import User -from django.test.client import RequestFactory -from capa.xqueue_interface import XQueueInterface -from capa.xqueue_interface import make_xheader, make_hashkey -from django.conf import settings -from requests.auth import HTTPBasicAuth -from student.models import UserProfile -import json -import random -import logging class Command(BaseCommand): @@ -26,19 +14,19 @@ class Command(BaseCommand): def handle(self, *args, **options): course_id = 'BerkeleyX/CS169.1x/2012_Fall' - course = courses.get_course_by_id(course_id) enrolled_students = User.objects.filter( courseenrollment__course_id=course_id).prefetch_related( "groups").order_by('username') xq = XQueueCertInterface() - # delete all entries + + # TODO (this is for debugging, remove) for c in GeneratedCertificate.objects.all(): c.delete() count = 0 for student in enrolled_students: - ret = xq.add_cert_to_queue(student, course_id) + ret = xq.add_cert(student, course_id) if ret == 'generating': print 'generating for {0}'.format(student) count += 1 From 5529eecb85d7e7107158d2057b1c9aefed9bab73 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 21:59:51 -0400 Subject: [PATCH 180/228] Separate function for send_to_xqueue --- lms/djangoapps/certificates/queue.py | 39 ++++++++++++---------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index adccad0633..273ff1e738 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -122,14 +122,7 @@ class XQueueCertInterface(object): } key = cert.key - xheader = make_xheader( - 'http://{0}/certificate'.format(settings.SITE_NAME), - key, 'test-pull') - (error, msg) = self.xqueue_interface.send_to_queue( - header=xheader, body=json.dumps(contents)) - if error: - logger.critical('Unable to add a request to the queue') - raise Exception('Unable to send queue message') + self.__send_to_xqueue(contents, key) cert.save() return cert_status @@ -174,12 +167,7 @@ class XQueueCertInterface(object): } key = cert.key - xheader = make_xheader( - 'http://{0}/certificate'.format(settings.SITE_NAME), - key, 'test-pull') - (error, msg) = self.xqueue_interface.send_to_queue(header=xheader, - body=json.dumps(contents)) - + self.__send_to_xqueue(contents, key) cert.save() return cert_status @@ -231,15 +219,22 @@ class XQueueCertInterface(object): 'course_id': course_id, 'name': profile.name, } - xheader = make_xheader( - 'http://{0}/update_certificate?{1}'.format( - key, settings.SITE_NAME), key, 'test-pull') - (error, msg) = self.xqueue_interface.send_to_queue( - header=xheader, body=json.dumps(contents)) - if error: - logger.critical('Unable to post results to qserver') - raise Exception('Unable to send queue message') + self.__send_to_xqueue(contents, key) cert.save() return cert_status + + def __send_to_xqueue(self, contents, key): + + # TODO - need to read queue name from settings + + xheader = make_xheader( + 'http://{0}/update_certificate?{1}'.format( + key, settings.SITE_NAME), key, 'test-pull') + + (error, msg) = self.xqueue_interface.send_to_queue( + header=xheader, body=json.dumps(contents)) + if error: + logger.critical('Unable to add a request to the queue') + raise Exception('Unable to send queue message') From 0481cf995f63f98eca09986c79017d9e5bc35277 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Thu, 1 Nov 2012 22:17:05 -0400 Subject: [PATCH 181/228] Updating view for different certificate statuses, error --- lms/djangoapps/certificates/views.py | 30 ++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/certificates/views.py b/lms/djangoapps/certificates/views.py index 450eb9e181..2475492524 100644 --- a/lms/djangoapps/certificates/views.py +++ b/lms/djangoapps/certificates/views.py @@ -1,10 +1,11 @@ import logging from certificates.models import GeneratedCertificate +from certificates.models import CertificateStatuses as status from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse import json -log = logging.getLogger("mitx.certificates") +logger = logging.getLogger(__name__) @csrf_exempt @@ -12,6 +13,10 @@ def update_certificate(request): """ Will update GeneratedCertificate for a new certificate or modify an existing certificate entry. + + See models.py for a state diagram of certificate states + + This view should only ever be accessed by the xqueue server """ if request.method == "POST": @@ -26,7 +31,7 @@ def update_certificate(request): key=xqueue_header['lms_key']) except GeneratedCertificate.DoesNotExist: - log.critical('Unable to lookup certificate\n' + logger.critical('Unable to lookup certificate\n' 'xqueue_body: {0}\n' 'xqueue_header: {1}'.format( xqueue_body, xqueue_header)) @@ -36,10 +41,23 @@ def update_certificate(request): 'content': 'unable to lookup key'}), mimetype='application/json') - cert.download_uuid = xqueue_body['download_uuid'] - cert.verify_uuid = xqueue_body['download_uuid'] - cert.download_url = xqueue_body['url'] - cert.status = 'downloadable' + if 'error' in xqueue_body: + cert.status = status.error + else: + if cert.state in [status.generating, status.regenerating]: + cert.download_uuid = xqueue_body['download_uuid'] + cert.verify_uuid = xqueue_body['verify_uuid'] + cert.download_url = xqueue_body['url'] + cert.status = status.downloadable + elif cert.state in [status.deleting]: + cert.status = status.deleted + else: + logger.critical('Invalid state for cert update: {0}'.format( + cert.state)) + return HttpResponse(json.dumps({ + 'return_code': 1, + 'content': 'invalid cert state'}), + mimetype='application/json') cert.save() return HttpResponse(json.dumps({'return_code': 0}), mimetype='application/json') From 507794acf5c37b2bc8779bd0e69925acdb189a3d Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Fri, 2 Nov 2012 13:35:56 -0400 Subject: [PATCH 182/228] Adding defaults to created and added fields --- lms/djangoapps/certificates/models.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 8f62ee85c2..b9e348da1d 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -1,6 +1,6 @@ from django.contrib.auth.models import User from django.db import models - +from datetime import datetime ''' Certificates are created for a student and an offering of a course. @@ -59,8 +59,10 @@ class GeneratedCertificate(models.Model): distinction = models.BooleanField(default=False) status = models.CharField(max_length=32, default='unavailable') name = models.CharField(blank=True, max_length=255) - created_date = models.DateTimeField(auto_now_add=True) - modified_date = models.DateTimeField(auto_now=True) + created_date = models.DateTimeField( + auto_now_add=True, default=datetime.now) + modified_date = models.DateTimeField( + auto_now=True, default=datetime.now) class Meta: unique_together = (('user', 'course_id'),) From 8d628ef234729c96846a9296c6a14a5acb374190 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Fri, 2 Nov 2012 13:38:44 -0400 Subject: [PATCH 183/228] unavailable->CertificateStatuses.unavailable --- lms/djangoapps/certificates/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index b9e348da1d..a1fd62041c 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -100,4 +100,4 @@ def certificate_status_for_student(student, course_id): return {'status': generated_certificate.status} except GeneratedCertificate.DoesNotExist: pass - return {'status': 'unavailable'} + return {'status': CertificateStatuses.unavailable} From 8e5d6bd416046cd9744511415082743d9b179d73 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Sun, 4 Nov 2012 18:56:30 -0500 Subject: [PATCH 184/228] Change 'remove' to 'delete' for consistency --- lms/djangoapps/certificates/queue.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index 273ff1e738..0bb238474b 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -114,8 +114,8 @@ class XQueueCertInterface(object): contents = { 'action': 'regen', - 'remove_verify_uuid': cert.verify_uuid, - 'remove_download_uuid': cert.download_uuid, + 'delete_verify_uuid': cert.verify_uuid, + 'delete_download_uuid': cert.download_uuid, 'username': cert.user.username, 'course_id': cert.course_id, 'name': profile.name, @@ -160,9 +160,9 @@ class XQueueCertInterface(object): cert.status = status.deleting contents = { - 'action': 'remove', - 'remove_verify_uuid': cert.verify_uuid, - 'remove_download_uuid': cert.download_uuid, + 'action': 'delete', + 'delete_verify_uuid': cert.verify_uuid, + 'delete_download_uuid': cert.download_uuid, 'username': cert.user.username, } From fb882d285fac59dc8fe216110edb84304b6448fc Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Mon, 5 Nov 2012 15:03:55 -0500 Subject: [PATCH 185/228] cert.state->cert.status --- lms/djangoapps/certificates/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/certificates/views.py b/lms/djangoapps/certificates/views.py index 2475492524..390dca1ef7 100644 --- a/lms/djangoapps/certificates/views.py +++ b/lms/djangoapps/certificates/views.py @@ -44,19 +44,19 @@ def update_certificate(request): if 'error' in xqueue_body: cert.status = status.error else: - if cert.state in [status.generating, status.regenerating]: + if cert.status in [status.generating, status.regenerating]: cert.download_uuid = xqueue_body['download_uuid'] cert.verify_uuid = xqueue_body['verify_uuid'] cert.download_url = xqueue_body['url'] cert.status = status.downloadable - elif cert.state in [status.deleting]: + elif cert.status in [status.deleting]: cert.status = status.deleted else: logger.critical('Invalid state for cert update: {0}'.format( - cert.state)) + cert.status)) return HttpResponse(json.dumps({ 'return_code': 1, - 'content': 'invalid cert state'}), + 'content': 'invalid cert status'}), mimetype='application/json') cert.save() return HttpResponse(json.dumps({'return_code': 0}), From 076619c81b8a5afad29225e07ca08bd516a998e9 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Mon, 5 Nov 2012 15:04:22 -0500 Subject: [PATCH 186/228] Changing private method to use a single underscore --- lms/djangoapps/certificates/queue.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index 0bb238474b..7ee9adddcf 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -122,7 +122,7 @@ class XQueueCertInterface(object): } key = cert.key - self.__send_to_xqueue(contents, key) + self._send_to_xqueue(contents, key) cert.save() return cert_status @@ -167,7 +167,7 @@ class XQueueCertInterface(object): } key = cert.key - self.__send_to_xqueue(contents, key) + self._send_to_xqueue(contents, key) cert.save() return cert_status @@ -220,18 +220,18 @@ class XQueueCertInterface(object): 'name': profile.name, } - self.__send_to_xqueue(contents, key) + self._send_to_xqueue(contents, key) cert.save() return cert_status - def __send_to_xqueue(self, contents, key): + def _send_to_xqueue(self, contents, key): # TODO - need to read queue name from settings xheader = make_xheader( 'http://{0}/update_certificate?{1}'.format( - key, settings.SITE_NAME), key, 'test-pull') + settings.SITE_NAME, key), key, 'test-pull') (error, msg) = self.xqueue_interface.send_to_queue( header=xheader, body=json.dumps(contents)) From 5d6687d89740f5ffb466c64b64c7ed475f893e86 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Mon, 5 Nov 2012 15:35:59 -0500 Subject: [PATCH 187/228] Adding error_reason --- lms/djangoapps/certificates/models.py | 1 + lms/djangoapps/certificates/views.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index a1fd62041c..d7e189ccb9 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -63,6 +63,7 @@ class GeneratedCertificate(models.Model): auto_now_add=True, default=datetime.now) modified_date = models.DateTimeField( auto_now=True, default=datetime.now) + error_reason = models.CharField(max_length=512, blank=True, default='') class Meta: unique_together = (('user', 'course_id'),) diff --git a/lms/djangoapps/certificates/views.py b/lms/djangoapps/certificates/views.py index 390dca1ef7..ae15245588 100644 --- a/lms/djangoapps/certificates/views.py +++ b/lms/djangoapps/certificates/views.py @@ -43,6 +43,20 @@ def update_certificate(request): if 'error' in xqueue_body: cert.status = status.error + if 'error_reason' in xqueue_body: + + # Hopefully we will record a meaningful error + # here if something bad happened during the + # certificate generation process + # + # example: + # (aamorm BerkeleyX/CS169.1x/2012_Fall) + # : + # HTTP error (reason=error(32, 'Broken pipe'), filename=None) : + # certificate_agent.py:175 + + + cert.error_reason = xqueue_body['error_reason'] else: if cert.status in [status.generating, status.regenerating]: cert.download_uuid = xqueue_body['download_uuid'] From be13bc602693abc3bc30df9302929a0f528e0249 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 7 Nov 2012 17:15:29 -0500 Subject: [PATCH 188/228] added in completed course status to dashboard and certificate availability states --- lms/static/sass/base/_variables.scss | 1 - lms/static/sass/multicourse/_dashboard.scss | 117 ++++++++++++++++++++ lms/templates/dashboard.html | 36 +++++- 3 files changed, 151 insertions(+), 3 deletions(-) diff --git a/lms/static/sass/base/_variables.scss b/lms/static/sass/base/_variables.scss index fad5feff3a..8d1b78a0eb 100644 --- a/lms/static/sass/base/_variables.scss +++ b/lms/static/sass/base/_variables.scss @@ -26,7 +26,6 @@ $sidebar-color: #f6f6f6; $outer-border-color: #aaa; // old variables - $light-gray: #ddd; $dark-gray: #333; $text-color: $dark-gray; diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index 53418bc0dd..ccd5650da4 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -179,6 +179,7 @@ overflow: hidden; position: relative; width: flex-grid(12); + z-index: 20; @include transition(all, 0.15s, linear); &:last-child { @@ -318,6 +319,19 @@ } } + .course-status-completed { + background: #ccc; + color: #fff; + + p { + color: #222; + + span { + font-weight: bold; + } + } + } + .enter-course { @include button(shiny, $blue); @include box-sizing(border-box); @@ -357,10 +371,113 @@ border-color: darken(rgb(200,200,200), 3%); @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6)); } + + .course-status-completed { + background: #888; + color: #fff; + } } } } + .message-status { + @include border-radius(3px); + @include box-shadow(0 1px 4px 0 rgba(0,0,0, 0.1), inset 0 -1px 0 0 rgba(255,255,255, 0.8), inset 0 1px 0 0 rgba(255,255,255, 0.8)); + display: none; + position: relative; + top: -15px; + z-index: 10; + margin: 0 0 20px 0; + padding: 15px 20px; + font-family: "Open Sans", Verdana, Geneva, sans-serif; + background: #fffcf0; + border: 1px solid #ccc; + + .message-copy { + margin: 0; + + .grade-value { + font-size: 1.4rem; + font-weight: bold; + } + } + + .actions { + @include clearfix; + list-style: none; + margin: 15px 0 0 0; + padding: 0; + + .action { + float: left; + margin:0 15px 10px 0; + + .btn, .cta { + display: inline-block; + } + + .btn { + @include button(shiny, $blue); + @include box-sizing(border-box); + @include border-radius(3px); + float: left; + font: normal 0.8rem/1.2rem $sans-serif; + letter-spacing: 1px; + padding: 6px 12px; + text-align: center; + + &.disabled { + @include button(shiny, #eee); + cursor: default !important; + + &:hover { + background: #eee; + background-image: -webkit-linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); + background-image: -moz-linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); + background-image: -ms-linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); + background-image: -o-linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); + background-image: linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); + } + } + } + + .cta { + @include button(shiny, #666); + float: left; + font: normal 0.8rem/1.2rem $sans-serif; + letter-spacing: 1px; + padding: 6px 12px; + text-align: center; + } + } + } + + &.is-shown { + display: block; + } + + &.course-status-processing { + + } + + &.course-status-certnotavailable { + // background: #fee8d6; + } + + &.course-status-certrendering { + // background: #d9e7db; + + .cta { + margin-top: 2px; + } + } + + &.course-status-certavailable { + // background: #d9e7db; + } + } + + a.unenroll { float: right; font-style: italic; diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 3fdcbb5c1c..f2b0c5239b 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -135,8 +135,8 @@

      ${get_course_about_section(course, 'university')}

      ${course.number} ${course.title}

      -
      -

      Class Starts - ${course.start_date_text}

      +
      +

      Course Completed - Nov 6, 2012

      % if course.id in show_courseware_links_for:

      View Courseware

      @@ -144,6 +144,38 @@
      + +
      +

      Final course details are being wrapped up at this time. Your final standing will be available shortly.

      +
      + +
      +

      You have received a grade of B in this course.

      + + +
      + +
      +

      You have received a grade of B in this course.

      + + +
      + +
      +

      You did not complete the necessary requirements for completion of this course.

      + + +
      + Unregister % endfor From d155869f16b99570930af0fe2cf9d510d113d867 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 8 Nov 2012 11:38:01 -0500 Subject: [PATCH 189/228] added in course-wide completion message static states and styling --- lms/static/sass/course/base/_base.scss | 74 ++++++++++++++++++++++++++ lms/templates/navigation.html | 41 ++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss index 62237fd7a9..4439b5b290 100644 --- a/lms/static/sass/course/base/_base.scss +++ b/lms/static/sass/course/base/_base.scss @@ -132,7 +132,81 @@ img { } .site-status { + display: block; +} + +.course-status { + display: none; + padding: 20px 10px; + background: #fffcf0; + border-bottom: 1px solid #999; + @include linear-gradient(#f2dfbe, #fffcf0); + @include box-shadow(0 -2px 0 #fff inset); + + .inner-wrapper { + margin: auto; + max-width: 1180px; + @include clearfix(); + + .message-copy { + display: inline-block; + margin-right: 20px; + + .grade-value { + font-size: 1.4rem; + font-weight: bold; + } + } + + .actions { + display: inline-block; + margin: 0; + padding: 0; + list-style: none; + + .action { + display: inline-block; + margin-right: 10px; + + &:last-child { + margin-right: 0; + } + + .btn { + padding: 6px 12px; + @include box-sizing(border-box); + @include button(shiny, $blue); + @include border-radius(3px); + font: normal 0.8rem/1.2rem $sans-serif; + letter-spacing: 1px; + text-align: center; + + &.disabled { + @include button(shiny, #eee); + cursor: default !important; + + &:hover { + background: #eee; + background-image: -webkit-linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); + background-image: -moz-linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); + background-image: -ms-linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); + background-image: -o-linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); + background-image: linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); + } + } + } + + .cta { + font-size: 0.8rem; + font-weight: bold; + } + } + } + } + + &.is-shown { display: block; + } } .toast-notification { diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index d574bc3f6e..dfce6d3e90 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -91,6 +91,47 @@ site_status_msg = get_site_status_msg(course_id)
      Warning: Your browser is not fully supported. We strongly recommend using Chrome or Firefox.
      % endif +% if course: +
      +
      +

      You have received a grade of B in this course.

      + + +
      +
      + +
      +
      +

      You did not complete the necessary requirements for completion of this course.

      + + +
      +
      + +
      +
      +

      You have received a grade of B in this course.

      + + +
      +
      + +
      +
      +

      Final course details are being wrapped up at this time. Your final standing will be available shortly.

      +
      +
      +% endif + %if not user.is_authenticated(): <%include file="login_modal.html" /> <%include file="signup_modal.html" /> From c6bfb67b1aea5ea4cd07bf11f00ee8c134e204c0 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 8 Nov 2012 12:08:09 -0500 Subject: [PATCH 190/228] revised no certificate state for dashboard messages and removed course message UI for future work on separate branch --- lms/static/sass/course/base/_base.scss | 74 -------------------------- lms/templates/dashboard.html | 1 - lms/templates/navigation.html | 41 -------------- 3 files changed, 116 deletions(-) diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss index 4439b5b290..6183c8a675 100644 --- a/lms/static/sass/course/base/_base.scss +++ b/lms/static/sass/course/base/_base.scss @@ -135,80 +135,6 @@ img { display: block; } -.course-status { - display: none; - padding: 20px 10px; - background: #fffcf0; - border-bottom: 1px solid #999; - @include linear-gradient(#f2dfbe, #fffcf0); - @include box-shadow(0 -2px 0 #fff inset); - - .inner-wrapper { - margin: auto; - max-width: 1180px; - @include clearfix(); - - .message-copy { - display: inline-block; - margin-right: 20px; - - .grade-value { - font-size: 1.4rem; - font-weight: bold; - } - } - - .actions { - display: inline-block; - margin: 0; - padding: 0; - list-style: none; - - .action { - display: inline-block; - margin-right: 10px; - - &:last-child { - margin-right: 0; - } - - .btn { - padding: 6px 12px; - @include box-sizing(border-box); - @include button(shiny, $blue); - @include border-radius(3px); - font: normal 0.8rem/1.2rem $sans-serif; - letter-spacing: 1px; - text-align: center; - - &.disabled { - @include button(shiny, #eee); - cursor: default !important; - - &:hover { - background: #eee; - background-image: -webkit-linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); - background-image: -moz-linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); - background-image: -ms-linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); - background-image: -o-linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); - background-image: linear-gradient(top, #EEE 0%, #C2C2C2 50%, #ABABAB 50%, #B0B0B0 100%); - } - } - } - - .cta { - font-size: 0.8rem; - font-weight: bold; - } - } - } - } - - &.is-shown { - display: block; - } -} - .toast-notification { position: fixed; top: 20px; diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index f2b0c5239b..ad2d66f8be 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -171,7 +171,6 @@

      You did not complete the necessary requirements for completion of this course.

      diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index dfce6d3e90..d574bc3f6e 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -91,47 +91,6 @@ site_status_msg = get_site_status_msg(course_id)
      Warning: Your browser is not fully supported. We strongly recommend using Chrome or Firefox.
      % endif -% if course: -
      -
      -

      You have received a grade of B in this course.

      - - -
      -
      - -
      -
      -

      You did not complete the necessary requirements for completion of this course.

      - - -
      -
      - -
      -
      -

      You have received a grade of B in this course.

      - - -
      -
      - -
      -
      -

      Final course details are being wrapped up at this time. Your final standing will be available shortly.

      -
      -
      -% endif - %if not user.is_authenticated(): <%include file="login_modal.html" /> <%include file="signup_modal.html" /> From 83b79fb87efbfaed862c72694a7324133aed10df Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Fri, 9 Nov 2012 10:57:03 -0500 Subject: [PATCH 191/228] Adding new certificate state for "notpassing" --- lms/djangoapps/certificates/models.py | 1 + lms/djangoapps/certificates/queue.py | 62 ++++++++++++++++++--------- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index d7e189ccb9..98b76d8fdc 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -45,6 +45,7 @@ class CertificateStatuses(object): deleting = 'deleting' deleted = 'deleted' downloadable = 'downloadable' + notpassing = 'notpassing' error = 'error' diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index 7ee9adddcf..6bbea61250 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -82,7 +82,12 @@ class XQueueCertInterface(object): the certificate status to 'regenerating'. Certificate must be in the 'error' or 'downloadable' state - and the student must have a passing grade. + + If the student has a passing grade a certificate + request will be put on the queue + + If the student is not passing his state will change + to status.notpassing otherwise it will return the current state @@ -98,16 +103,16 @@ class XQueueCertInterface(object): course = courses.get_course_by_id(course_id) grade = grades.grade(student, self.request, course) - if grade['grade'] is not None: + profile = UserProfile.objects.get(user=student) + try: + cert = GeneratedCertificate.objects.get( + user=student, course_id=course_id) + except GeneratedCertificate.DoesNotExist: + logger.critical("Attempting to regenerate a certificate" + "for a user that doesn't have one") + raise - profile = UserProfile.objects.get(user=student) - try: - cert = GeneratedCertificate.objects.get( - user=student, course_id=course_id) - except GeneratedCertificate.DoesNotExist: - logger.warning("Attempting to regenerate a certificate" - "for a user that doesn't have one") - raise + if grade['grade'] is not None: cert.status = status.regenerating cert.name = profile.name @@ -125,6 +130,11 @@ class XQueueCertInterface(object): self._send_to_xqueue(contents, key) cert.save() + else: + cert.status = status.notpassing + cert.name = profile.name + cert.save() + return cert_status def del_cert(self, student, course_id): @@ -182,14 +192,20 @@ class XQueueCertInterface(object): Will change the certificate status to 'deleting'. Certificate must be in the 'unavailable', 'error', - or 'deleted' state and the student must have - a passing grade. + or 'deleted' state. - otherwise it will return the current state + If a student has a passing grade a request will made + for a new cert + + If a student does not have a passing grade the status + will change to status.notpassing + + Returns the student's status """ - VALID_STATUSES = [status.unavailable, status.deleted, status.error] + VALID_STATUSES = [status.unavailable, status.deleted, status.error, + status.notpassing] cert_status = certificate_status_for_student( student, course_id)['status'] @@ -198,14 +214,14 @@ class XQueueCertInterface(object): # grade the student course = courses.get_course_by_id(course_id) grade = grades.grade(student, self.request, course) + profile = UserProfile.objects.get(user=student) + cert, created = GeneratedCertificate.objects.get_or_create( + user=student, course_id=course_id) if grade['grade'] is not None: cert_status = status.generating - cert, created = GeneratedCertificate.objects.get_or_create( - user=student, course_id=course_id) - profile = UserProfile.objects.get(user=student) - key = make_hashkey(random.random()) + cert.status = cert_status cert.grade = grade['percent'] cert.user = student @@ -222,13 +238,19 @@ class XQueueCertInterface(object): self._send_to_xqueue(contents, key) cert.save() + else: + cert_status = status.notpassing + + cert.status = cert_status + cert.user = student + cert.course_id = course_id + cert.name = profile.name + cert.save() return cert_status def _send_to_xqueue(self, contents, key): - # TODO - need to read queue name from settings - xheader = make_xheader( 'http://{0}/update_certificate?{1}'.format( settings.SITE_NAME, key), key, 'test-pull') From 01dcf291b63fbf2af2e756ed79bcf8a38925f3ac Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Fri, 9 Nov 2012 11:03:58 -0500 Subject: [PATCH 192/228] Adding setting variable for CERT_QUEUE --- lms/djangoapps/certificates/queue.py | 2 +- lms/envs/aws.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index 6bbea61250..a048e4e235 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -253,7 +253,7 @@ class XQueueCertInterface(object): xheader = make_xheader( 'http://{0}/update_certificate?{1}'.format( - settings.SITE_NAME, key), key, 'test-pull') + settings.SITE_NAME, key), key, settings.CERT_QUEUE) (error, msg) = self.xqueue_interface.send_to_queue( header=xheader, body=json.dumps(contents)) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 16216bbe09..128553f600 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -59,6 +59,7 @@ COURSE_LISTINGS = ENV_TOKENS.get('COURSE_LISTINGS', {}) SUBDOMAIN_BRANDING = ENV_TOKENS.get('SUBDOMAIN_BRANDING', {}) COMMENTS_SERVICE_URL = ENV_TOKENS.get("COMMENTS_SERVICE_URL",'') COMMENTS_SERVICE_KEY = ENV_TOKENS.get("COMMENTS_SERVICE_KEY",'') +CERT_QUEUE = ENV_TOKENS.get("CERT_QUEUE", 'test-pull') ############################## SECURE AUTH ITEMS ############################### # Secret things: passwords, access keys, etc. From cf33d85ef9dc6b59cd805c13e57000e529be5631 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 9 Nov 2012 12:02:01 -0500 Subject: [PATCH 193/228] Wire up cert status --- common/djangoapps/student/views.py | 45 ++++++----- common/lib/xmodule/xmodule/course_module.py | 10 +++ lms/djangoapps/certificates/models.py | 7 +- lms/templates/dashboard.html | 83 ++++++++++++++------- 4 files changed, 97 insertions(+), 48 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 8810c8609b..5d5dff81d2 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -29,6 +29,9 @@ from django_future.csrf import ensure_csrf_cookie, csrf_exempt from student.models import (Registration, UserProfile, PendingNameChange, PendingEmailChange, CourseEnrollment) + +from certificates.models import CertificateStatuses, certificate_status_for_student + from xmodule.course_module import CourseDescriptor from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.django import modulestore @@ -143,11 +146,15 @@ def dashboard(request): show_courseware_links_for = frozenset(course.id for course in courses if has_access(request.user, course, 'load')) + cert_statuses = [certificate_status_for_student(request.user, course.id) for course in courses] + context = {'courses': courses, 'message': message, 'staff_access': staff_access, 'errored_courses': errored_courses, - 'show_courseware_links_for' : show_courseware_links_for} + 'show_courseware_links_for' : show_courseware_links_for, + 'cert_statuses': cert_statuses, + } return render_to_response('dashboard.html', context) @@ -206,13 +213,13 @@ def change_enrollment(request): return {'success': False, 'error': 'enrollment in {} not allowed at this time' .format(course.display_name)} - - org, course_num, run=course_id.split("/") + + org, course_num, run=course_id.split("/") statsd.increment("common.student.enrollment", tags=["org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run)]) - + enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) return {'success': True} @@ -220,13 +227,13 @@ def change_enrollment(request): try: enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id) enrollment.delete() - - org, course_num, run=course_id.split("/") + + org, course_num, run=course_id.split("/") statsd.increment("common.student.unenrollment", tags=["org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run)]) - + return {'success': True} except CourseEnrollment.DoesNotExist: return {'success': False, 'error': 'You are not enrolled for this course.'} @@ -275,13 +282,13 @@ def login_user(request, error=""): log.info("Login success - {0} ({1})".format(username, email)) try_change_enrollment(request) - + statsd.increment("common.student.successful_login") - + return HttpResponse(json.dumps({'success': True})) - + log.warning("Login failed - Account not active for user {0}, resending activation".format(username)) - + reactivation_email_for_user(user) not_activated_msg = "This account has not been activated. We have " + \ "sent another activation message. Please check your " + \ @@ -483,9 +490,9 @@ def create_account(request, post_override=None): log.debug('bypassing activation email') login_user.is_active = True login_user.save() - + statsd.increment("common.student.account_created") - + js = {'success': True} return HttpResponse(json.dumps(js), mimetype="application/json") @@ -541,9 +548,9 @@ def password_reset(request): ''' Attempts to send a password reset e-mail. ''' if request.method != "POST": raise Http404 - + # By default, Django doesn't allow Users with is_active = False to reset their passwords, - # but this bites people who signed up a long time ago, never activated, and forgot their + # but this bites people who signed up a long time ago, never activated, and forgot their # password. So for their sake, we'll auto-activate a user for whome password_reset is called. try: user = User.objects.get(email=request.POST['email']) @@ -551,7 +558,7 @@ def password_reset(request): user.save() except: log.exception("Tried to auto-activate user to enable password reset, but failed.") - + form = PasswordResetForm(request.POST) if form.is_valid(): form.save(use_https = request.is_secure(), @@ -589,7 +596,7 @@ def reactivation_email_for_user(user): res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) return HttpResponse(json.dumps({'success': True})) - + @ensure_csrf_cookie def change_email_request(request): @@ -764,8 +771,8 @@ def accept_name_change_by_id(id): @ensure_csrf_cookie def accept_name_change(request): - ''' JSON: Name change process. Course staff clicks 'accept' on a given name change - + ''' JSON: Name change process. Course staff clicks 'accept' on a given name change + We used this during the prototype but now we simply record name changes instead of manually approving them. Still keeping this around in case we want to go back to this approval method. diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index fc27a692ea..46e8e145df 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -298,6 +298,16 @@ class CourseDescriptor(SequenceDescriptor): # Explicit comparison to True because we always want to return a bool. return self.metadata.get('hide_progress_tab') == True + @property + def end_of_course_survey_url(self): + """ + Pull from policy. Once we have our own survey module set up, can change this to point to an automatically + created survey for each class. + + Returns None if no url specified. + """ + return self.metadata.get('end_of_course_survey_url') + @property def title(self): return self.display_name diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 98b76d8fdc..1434510920 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -2,7 +2,7 @@ from django.contrib.auth.models import User from django.db import models from datetime import datetime -''' +""" Certificates are created for a student and an offering of a course. When a certificate is generated, a unique ID is generated so that @@ -35,8 +35,7 @@ State diagram: v v v [downloadable] [downloadable] [deleted] -''' - +""" class CertificateStatuses(object): unavailable = 'unavailable' @@ -88,6 +87,7 @@ def certificate_status_for_student(student, course_id): If the status is "downloadable", the dictionary also contains "download_url". + If the student has been graded, the dictionary also contains their grade for the course. ''' try: @@ -97,6 +97,7 @@ def certificate_status_for_student(student, course_id): return { 'status': CertificateStatuses.downloadable, 'download_url': generated_certificate.download_url, + 'grade': generated_certificate.grade, } else: return {'status': generated_certificate.status} diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index ad2d66f8be..c6c71017b3 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -2,6 +2,7 @@ from django.core.urlresolvers import reverse from courseware.courses import course_image_url, get_course_about_section from courseware.access import has_access + from certificates.models import CertificateStatuses %> <%inherit file="main.html" /> @@ -114,7 +115,7 @@ % if len(courses) > 0: - % for course in courses: + % for course, cert_status in zip(courses, cert_statuses):
      <% @@ -145,35 +146,65 @@
      -
      -

      Final course details are being wrapped up at this time. Your final standing will be available shortly.

      -
      -
      -

      You have received a grade of B in this course.

      + % if course.has_ended: + <% + passing_grade = False + cert_button = False + survey_button = False + if cert_status['status'] in [CertificateStatuses.generating, CertificateStatuses.regenerating]: + status_css_class = 'course-status-certrendering' + cert_button = True + survey_button = True + passing_grade = True + elif cert_status['status'] == CertificateStatuses.downloadable: + status_css_class = 'course-status-certavailable' + cert_button = True + survey_button = True + passing_grade = True + elif cert_status['status'] == CertificateStatuses.notpassing: + status_css_class = 'course-status-certnotavailable' + survey_button = True + else cert_status['status']: + # This is primarily the 'unavailable' state, but also 'error', 'deleted', etc. + status_css_class = 'course-status-processing' - -
      + if survey_button and not course.end_of_course_survey_url: + survey_button = False + %> +
      -
      -

      You have received a grade of B in this course.

      + % if cert_status == CertificateStatuses.unavailable: +

      Final course details are being wrapped up at this time. + Your final standing will be available shortly.

      + % elif passing_grade: +

      You have received a grade of + ${cert_status['grade']} + in this course.

      + % elif cert_status == CertificateStatuses.notpassing: +

      You did not complete the necessary requirements for + completion of this course. Your grade was ${cert_status['grade']} +

      + % endif + % if cert_button or survey_button: + + % endif +
      - -
      - -
      -

      You did not complete the necessary requirements for completion of this course.

      - - -
      + % endif Unregister From 0b69870d05a2c9bbd7bb04767ff178b0d43e4096 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 9 Nov 2012 12:02:44 -0500 Subject: [PATCH 194/228] Reverting askbot removal for now Revert "add migration to remove askbot columns" This reverts commit 9281f3c1405e7fbee932c9c9f830632e010a98e7. --- .../student/migrations/0021_remove_askbot.py | 123 ------------------ 1 file changed, 123 deletions(-) delete mode 100644 common/djangoapps/student/migrations/0021_remove_askbot.py diff --git a/common/djangoapps/student/migrations/0021_remove_askbot.py b/common/djangoapps/student/migrations/0021_remove_askbot.py deleted file mode 100644 index b933f6cc7c..0000000000 --- a/common/djangoapps/student/migrations/0021_remove_askbot.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - -ASKBOT_AUTH_USER_COLUMNS = ( - 'website', - 'about', - 'gold', - 'email_isvalid', - 'real_name', - 'location', - 'reputation', - 'gravatar', - 'bronze', - 'last_seen', - 'silver', - 'questions_per_page', - 'new_response_count', - 'seen_response_count', -) - - -class Migration(SchemaMigration): - - def forwards(self, orm): - "Kill the askbot" - for column in ASKBOT_AUTH_USER_COLUMNS: - db.delete_column('auth_user', column) - - def backwards(self, orm): - raise RuntimeError("Cannot reverse this migration: there's no going back to Askbot.") - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'student.courseenrollment': { - 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, - 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'student.pendingemailchange': { - 'Meta': {'object_name': 'PendingEmailChange'}, - 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) - }, - 'student.pendingnamechange': { - 'Meta': {'object_name': 'PendingNameChange'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) - }, - 'student.registration': { - 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"}, - 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) - }, - 'student.userprofile': { - 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, - 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), - 'gender': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), - 'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), - 'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), - 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), - 'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}), - 'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'student.usertestgroup': { - 'Meta': {'object_name': 'UserTestGroup'}, - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), - 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}) - } - } - - complete_apps = ['student'] From 2846c61d15c72543847fddf36db37057a1318823 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 9 Nov 2012 12:03:12 -0500 Subject: [PATCH 195/228] Revert "remove (almost) all references to askbot." This reverts commit 793354a0dca872780568ee1a6ebe4c775434af31. --- .gitignore | 2 + .gitmodules | 3 + .hgignore | 12 + askbot | 1 + common/djangoapps/student/models.py | 2 - common/lib/xmodule/xmodule/x_module.py | 2 +- create-dev-env.sh | 9 + doc/overview.md | 2 +- install.txt | 3 + lms/askbot/skins/README | 71 + lms/askbot/skins/__init__.py | 0 lms/askbot/skins/common/media/images/anon.png | Bin 0 -> 687 bytes .../skins/common/media/images/bigbutton.png | Bin 0 -> 263 bytes .../common/media/images/bigbuttonhover.png | Bin 0 -> 236 bytes .../media/images/blue-up-arrow-h18px.png | Bin 0 -> 593 bytes .../skins/common/media/images/box-arrow.gif | Bin 0 -> 69 bytes .../common/media/images/bullet_green.gif | Bin 0 -> 64 bytes .../skins/common/media/images/cc-88x31.png | Bin 0 -> 5460 bytes .../skins/common/media/images/cc-by-sa.png | Bin 0 -> 5083 bytes .../common/media/images/close-small-dark.png | Bin 0 -> 226 bytes .../common/media/images/close-small-hover.png | Bin 0 -> 337 bytes .../skins/common/media/images/close-small.png | Bin 0 -> 293 bytes .../common/media/images/contributorsback.png | Bin 0 -> 714 bytes lms/askbot/skins/common/media/images/dash.gif | Bin 0 -> 44 bytes .../media/images/dialog-warning-off.png | Bin 0 -> 419 bytes .../common/media/images/dialog-warning.png | Bin 0 -> 603 bytes .../media/images/djangomade124x25_grey.gif | Bin 0 -> 2035 bytes .../skins/common/media/images/dot-g.gif | Bin 0 -> 61 bytes .../skins/common/media/images/dot-list.gif | Bin 0 -> 56 bytes lms/askbot/skins/common/media/images/edit.png | Bin 0 -> 758 bytes .../media/images/expander-arrow-hide.gif | Bin 0 -> 126 bytes .../media/images/expander-arrow-show.gif | Bin 0 -> 135 bytes .../skins/common/media/images/favicon.gif | Bin 0 -> 78 bytes .../skins/common/media/images/favicon.ico | Bin 0 -> 14846 bytes .../common/media/images/feed-icon-small.png | Bin 0 -> 669 bytes .../skins/common/media/images/flags/ad.gif | Bin 0 -> 371 bytes .../skins/common/media/images/flags/ae.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/af.gif | Bin 0 -> 369 bytes .../skins/common/media/images/flags/ag.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/ai.gif | Bin 0 -> 369 bytes .../skins/common/media/images/flags/al.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/am.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/an.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/ao.gif | Bin 0 -> 244 bytes .../skins/common/media/images/flags/ar.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/as.gif | Bin 0 -> 365 bytes .../skins/common/media/images/flags/at.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/au.gif | Bin 0 -> 378 bytes .../skins/common/media/images/flags/aw.gif | Bin 0 -> 365 bytes .../skins/common/media/images/flags/ax.gif | Bin 0 -> 376 bytes .../skins/common/media/images/flags/az.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/ba.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/bb.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/bd.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/be.gif | Bin 0 -> 359 bytes .../skins/common/media/images/flags/bf.gif | Bin 0 -> 358 bytes .../skins/common/media/images/flags/bg.gif | Bin 0 -> 360 bytes .../skins/common/media/images/flags/bh.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/bi.gif | Bin 0 -> 374 bytes .../skins/common/media/images/flags/bj.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/bm.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/bn.gif | Bin 0 -> 373 bytes .../skins/common/media/images/flags/bo.gif | Bin 0 -> 359 bytes .../skins/common/media/images/flags/br.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/bs.gif | Bin 0 -> 351 bytes .../skins/common/media/images/flags/bt.gif | Bin 0 -> 377 bytes .../skins/common/media/images/flags/bv.gif | Bin 0 -> 376 bytes .../skins/common/media/images/flags/bw.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/by.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/bz.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/ca.gif | Bin 0 -> 376 bytes .../common/media/images/flags/catalonia.gif | Bin 0 -> 238 bytes .../skins/common/media/images/flags/cc.gif | Bin 0 -> 371 bytes .../skins/common/media/images/flags/cd.gif | Bin 0 -> 243 bytes .../skins/common/media/images/flags/cf.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/cg.gif | Bin 0 -> 359 bytes .../skins/common/media/images/flags/ch.gif | Bin 0 -> 332 bytes .../skins/common/media/images/flags/ci.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/ck.gif | Bin 0 -> 362 bytes .../skins/common/media/images/flags/cl.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/cm.gif | Bin 0 -> 369 bytes .../skins/common/media/images/flags/cn.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/co.gif | Bin 0 -> 353 bytes .../skins/common/media/images/flags/cr.gif | Bin 0 -> 359 bytes .../skins/common/media/images/flags/cs.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/cu.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/cv.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/cx.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/cy.gif | Bin 0 -> 365 bytes .../skins/common/media/images/flags/cz.gif | Bin 0 -> 362 bytes .../skins/common/media/images/flags/de.gif | Bin 0 -> 362 bytes .../skins/common/media/images/flags/dj.gif | Bin 0 -> 369 bytes .../skins/common/media/images/flags/dk.gif | Bin 0 -> 374 bytes .../skins/common/media/images/flags/dm.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/do.gif | Bin 0 -> 362 bytes .../skins/common/media/images/flags/dz.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/ec.gif | Bin 0 -> 362 bytes .../skins/common/media/images/flags/ee.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/eg.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/eh.gif | Bin 0 -> 359 bytes .../common/media/images/flags/england.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/er.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/es.gif | Bin 0 -> 360 bytes .../skins/common/media/images/flags/et.gif | Bin 0 -> 364 bytes .../media/images/flags/europeanunion.gif | Bin 0 -> 171 bytes .../skins/common/media/images/flags/fam.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/fi.gif | Bin 0 -> 371 bytes .../skins/common/media/images/flags/fj.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/fk.gif | Bin 0 -> 372 bytes .../skins/common/media/images/flags/fm.gif | Bin 0 -> 377 bytes .../skins/common/media/images/flags/fo.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/fr.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/ga.gif | Bin 0 -> 359 bytes .../skins/common/media/images/flags/gb.gif | Bin 0 -> 260 bytes .../skins/common/media/images/flags/gd.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/ge.gif | Bin 0 -> 379 bytes .../skins/common/media/images/flags/gf.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/gh.gif | Bin 0 -> 358 bytes .../skins/common/media/images/flags/gi.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/gl.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/gm.gif | Bin 0 -> 362 bytes .../skins/common/media/images/flags/gn.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/gp.gif | Bin 0 -> 357 bytes .../skins/common/media/images/flags/gq.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/gr.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/gs.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/gt.gif | Bin 0 -> 374 bytes .../skins/common/media/images/flags/gu.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/gw.gif | Bin 0 -> 358 bytes .../skins/common/media/images/flags/gy.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/hk.gif | Bin 0 -> 373 bytes .../skins/common/media/images/flags/hm.gif | Bin 0 -> 378 bytes .../skins/common/media/images/flags/hn.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/hr.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/ht.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/hu.gif | Bin 0 -> 357 bytes .../skins/common/media/images/flags/id.gif | Bin 0 -> 362 bytes .../skins/common/media/images/flags/ie.gif | Bin 0 -> 371 bytes .../skins/common/media/images/flags/il.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/in.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/io.gif | Bin 0 -> 373 bytes .../skins/common/media/images/flags/iq.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/ir.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/is.gif | Bin 0 -> 373 bytes .../skins/common/media/images/flags/it.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/jm.gif | Bin 0 -> 365 bytes .../skins/common/media/images/flags/jo.gif | Bin 0 -> 360 bytes .../skins/common/media/images/flags/jp.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/ke.gif | Bin 0 -> 360 bytes .../skins/common/media/images/flags/kg.gif | Bin 0 -> 373 bytes .../skins/common/media/images/flags/kh.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/ki.gif | Bin 0 -> 371 bytes .../skins/common/media/images/flags/km.gif | Bin 0 -> 358 bytes .../skins/common/media/images/flags/kn.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/kp.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/kr.gif | Bin 0 -> 385 bytes .../skins/common/media/images/flags/kw.gif | Bin 0 -> 362 bytes .../skins/common/media/images/flags/ky.gif | Bin 0 -> 373 bytes .../skins/common/media/images/flags/kz.gif | Bin 0 -> 374 bytes .../skins/common/media/images/flags/la.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/lb.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/lc.gif | Bin 0 -> 259 bytes .../skins/common/media/images/flags/li.gif | Bin 0 -> 359 bytes .../skins/common/media/images/flags/lk.gif | Bin 0 -> 377 bytes .../skins/common/media/images/flags/lr.gif | Bin 0 -> 360 bytes .../skins/common/media/images/flags/ls.gif | Bin 0 -> 369 bytes .../skins/common/media/images/flags/lt.gif | Bin 0 -> 362 bytes .../skins/common/media/images/flags/lu.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/lv.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/ly.gif | Bin 0 -> 362 bytes .../skins/common/media/images/flags/ma.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/mc.gif | Bin 0 -> 359 bytes .../skins/common/media/images/flags/md.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/me.gif | Bin 0 -> 238 bytes .../skins/common/media/images/flags/mg.gif | Bin 0 -> 372 bytes .../skins/common/media/images/flags/mh.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/mk.gif | Bin 0 -> 382 bytes .../skins/common/media/images/flags/ml.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/mm.gif | Bin 0 -> 365 bytes .../skins/common/media/images/flags/mn.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/mo.gif | Bin 0 -> 378 bytes .../skins/common/media/images/flags/mp.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/mq.gif | Bin 0 -> 379 bytes .../skins/common/media/images/flags/mr.gif | Bin 0 -> 377 bytes .../skins/common/media/images/flags/ms.gif | Bin 0 -> 371 bytes .../skins/common/media/images/flags/mt.gif | Bin 0 -> 369 bytes .../skins/common/media/images/flags/mu.gif | Bin 0 -> 358 bytes .../skins/common/media/images/flags/mv.gif | Bin 0 -> 372 bytes .../skins/common/media/images/flags/mw.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/mx.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/my.gif | Bin 0 -> 375 bytes .../skins/common/media/images/flags/mz.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/na.gif | Bin 0 -> 371 bytes .../skins/common/media/images/flags/nc.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/ne.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/nf.gif | Bin 0 -> 375 bytes .../skins/common/media/images/flags/ng.gif | Bin 0 -> 371 bytes .../skins/common/media/images/flags/ni.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/nl.gif | Bin 0 -> 360 bytes .../skins/common/media/images/flags/no.gif | Bin 0 -> 376 bytes .../skins/common/media/images/flags/np.gif | Bin 0 -> 302 bytes .../skins/common/media/images/flags/nr.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/nu.gif | Bin 0 -> 369 bytes .../skins/common/media/images/flags/nz.gif | Bin 0 -> 369 bytes .../skins/common/media/images/flags/om.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/pa.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/pe.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/pf.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/pg.gif | Bin 0 -> 360 bytes .../skins/common/media/images/flags/ph.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/pk.gif | Bin 0 -> 377 bytes .../skins/common/media/images/flags/pl.gif | Bin 0 -> 360 bytes .../skins/common/media/images/flags/pm.gif | Bin 0 -> 374 bytes .../skins/common/media/images/flags/pn.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/pr.gif | Bin 0 -> 369 bytes .../skins/common/media/images/flags/ps.gif | Bin 0 -> 358 bytes .../skins/common/media/images/flags/pt.gif | Bin 0 -> 369 bytes .../skins/common/media/images/flags/pw.gif | Bin 0 -> 374 bytes .../skins/common/media/images/flags/py.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/qa.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/re.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/ro.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/rs.gif | Bin 0 -> 238 bytes .../skins/common/media/images/flags/ru.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/rw.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/sa.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/sb.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/sc.gif | Bin 0 -> 357 bytes .../common/media/images/flags/scotland.gif | Bin 0 -> 378 bytes .../skins/common/media/images/flags/sd.gif | Bin 0 -> 355 bytes .../skins/common/media/images/flags/se.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/sg.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/sh.gif | Bin 0 -> 371 bytes .../skins/common/media/images/flags/si.gif | Bin 0 -> 362 bytes .../skins/common/media/images/flags/sj.gif | Bin 0 -> 376 bytes .../skins/common/media/images/flags/sk.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/sl.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/sm.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/sn.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/so.gif | Bin 0 -> 376 bytes .../skins/common/media/images/flags/sr.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/st.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/sv.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/sy.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/sz.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/tc.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/td.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/tf.gif | Bin 0 -> 365 bytes .../skins/common/media/images/flags/tg.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/th.gif | Bin 0 -> 360 bytes .../skins/common/media/images/flags/tj.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/tk.gif | Bin 0 -> 372 bytes .../skins/common/media/images/flags/tl.gif | Bin 0 -> 360 bytes .../skins/common/media/images/flags/tm.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/tn.gif | Bin 0 -> 375 bytes .../skins/common/media/images/flags/to.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/tr.gif | Bin 0 -> 371 bytes .../skins/common/media/images/flags/tt.gif | Bin 0 -> 377 bytes .../skins/common/media/images/flags/tv.gif | Bin 0 -> 361 bytes .../skins/common/media/images/flags/tw.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/tz.gif | Bin 0 -> 366 bytes .../skins/common/media/images/flags/ua.gif | Bin 0 -> 360 bytes .../skins/common/media/images/flags/ug.gif | Bin 0 -> 359 bytes .../skins/common/media/images/flags/um.gif | Bin 0 -> 371 bytes .../skins/common/media/images/flags/us.gif | Bin 0 -> 367 bytes .../skins/common/media/images/flags/uy.gif | Bin 0 -> 373 bytes .../skins/common/media/images/flags/uz.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/va.gif | Bin 0 -> 369 bytes .../skins/common/media/images/flags/vc.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/ve.gif | Bin 0 -> 364 bytes .../skins/common/media/images/flags/vg.gif | Bin 0 -> 368 bytes .../skins/common/media/images/flags/vi.gif | Bin 0 -> 376 bytes .../skins/common/media/images/flags/vn.gif | Bin 0 -> 370 bytes .../skins/common/media/images/flags/vu.gif | Bin 0 -> 365 bytes .../skins/common/media/images/flags/wales.gif | Bin 0 -> 372 bytes .../skins/common/media/images/flags/wf.gif | Bin 0 -> 377 bytes .../skins/common/media/images/flags/ws.gif | Bin 0 -> 365 bytes .../skins/common/media/images/flags/ye.gif | Bin 0 -> 356 bytes .../skins/common/media/images/flags/yt.gif | Bin 0 -> 382 bytes .../skins/common/media/images/flags/za.gif | Bin 0 -> 363 bytes .../skins/common/media/images/flags/zm.gif | Bin 0 -> 358 bytes .../skins/common/media/images/flags/zw.gif | Bin 0 -> 365 bytes .../skins/common/media/images/go-up-grey.png | Bin 0 -> 563 bytes .../common/media/images/go-up-orange.png | Bin 0 -> 586 bytes .../media/images/gray-up-arrow-h18px.png | Bin 0 -> 383 bytes .../skins/common/media/images/grippie.png | Bin 0 -> 162 bytes .../skins/common/media/images/indicator.gif | Bin 0 -> 2545 bytes lms/askbot/skins/common/media/images/logo.gif | Bin 0 -> 3792 bytes lms/askbot/skins/common/media/images/logo.png | Bin 0 -> 5841 bytes .../skins/common/media/images/logo1.png | Bin 0 -> 2752 bytes .../skins/common/media/images/logo2.png | Bin 0 -> 2124 bytes .../media/images/mail-envelope-empty.png | Bin 0 -> 547 bytes .../media/images/mail-envelope-full.png | Bin 0 -> 482 bytes .../skins/common/media/images/medala.gif | Bin 0 -> 801 bytes .../skins/common/media/images/medala_on.gif | Bin 0 -> 957 bytes lms/askbot/skins/common/media/images/new.gif | Bin 0 -> 635 bytes .../skins/common/media/images/nophoto.png | Bin 0 -> 696 bytes .../skins/common/media/images/openid.gif | Bin 0 -> 910 bytes .../skins/common/media/images/openid/aol.gif | Bin 0 -> 2205 bytes .../common/media/images/openid/blogger.ico | Bin 0 -> 3638 bytes .../common/media/images/openid/claimid.ico | Bin 0 -> 3638 bytes .../common/media/images/openid/facebook.gif | Bin 0 -> 2075 bytes .../common/media/images/openid/flickr.ico | Bin 0 -> 1150 bytes .../common/media/images/openid/google.gif | Bin 0 -> 1596 bytes .../media/images/openid/livejournal.ico | Bin 0 -> 5222 bytes .../common/media/images/openid/myopenid.ico | Bin 0 -> 2862 bytes .../media/images/openid/openid-inputicon.gif | Bin 0 -> 237 bytes .../common/media/images/openid/openid.gif | Bin 0 -> 740 bytes .../common/media/images/openid/technorati.ico | Bin 0 -> 2294 bytes .../common/media/images/openid/twitter.png | Bin 0 -> 3130 bytes .../common/media/images/openid/verisign.ico | Bin 0 -> 4710 bytes .../common/media/images/openid/vidoop.ico | Bin 0 -> 1406 bytes .../common/media/images/openid/wordpress.ico | Bin 0 -> 1150 bytes .../common/media/images/openid/yahoo.gif | Bin 0 -> 1510 bytes .../skins/common/media/images/print.png | Bin 0 -> 1391 bytes .../skins/common/media/images/pw-login.gif | Bin 0 -> 1818 bytes .../skins/common/media/images/quest-bg.gif | Bin 0 -> 294 bytes .../skins/common/media/images/scopearrow.png | Bin 0 -> 538 bytes .../skins/common/media/images/sprite.png | Bin 0 -> 5325 bytes .../skins/common/media/images/sprites.png | Bin 0 -> 12545 bytes .../media/images/sprites_source/sprites.svg | 732 ++ .../media/images/summary-background.png | Bin 0 -> 291 bytes .../skins/common/media/images/tag-left.png | Bin 0 -> 290 bytes .../skins/common/media/images/tag-right.png | Bin 0 -> 187 bytes .../common/media/images/vote-accepted-on.png | Bin 0 -> 1124 bytes .../common/media/images/vote-accepted.png | Bin 0 -> 1058 bytes .../media/images/vote-arrow-down-on.png | Bin 0 -> 905 bytes .../common/media/images/vote-arrow-down.png | Bin 0 -> 876 bytes .../common/media/images/vote-arrow-up-on.png | Bin 0 -> 906 bytes .../common/media/images/vote-arrow-up.png | Bin 0 -> 843 bytes .../common/media/images/vote-favorite-off.png | Bin 0 -> 930 bytes .../common/media/images/vote-favorite-on.png | Bin 0 -> 1023 bytes lms/askbot/skins/common/media/images/wiki.png | Bin 0 -> 5178 bytes .../common/media/jquery-openid/images/aol.gif | Bin 0 -> 1872 bytes .../media/jquery-openid/images/blogger-1.png | Bin 0 -> 432 bytes .../media/jquery-openid/images/blogger.ico | Bin 0 -> 3638 bytes .../media/jquery-openid/images/claimid-0.png | Bin 0 -> 629 bytes .../media/jquery-openid/images/claimid.ico | Bin 0 -> 3638 bytes .../media/jquery-openid/images/facebook.gif | Bin 0 -> 1737 bytes .../media/jquery-openid/images/flickr.ico | Bin 0 -> 1150 bytes .../media/jquery-openid/images/flickr.png | Bin 0 -> 426 bytes .../media/jquery-openid/images/google.gif | Bin 0 -> 1528 bytes .../media/jquery-openid/images/identica.png | Bin 0 -> 6601 bytes .../media/jquery-openid/images/linkedin.gif | Bin 0 -> 1530 bytes .../jquery-openid/images/livejournal-1.png | Bin 0 -> 713 bytes .../jquery-openid/images/livejournal.ico | Bin 0 -> 5222 bytes .../media/jquery-openid/images/myopenid-2.png | Bin 0 -> 511 bytes .../media/jquery-openid/images/myopenid.ico | Bin 0 -> 2862 bytes .../jquery-openid/images/openid-inputicon.gif | Bin 0 -> 237 bytes .../media/jquery-openid/images/openid.gif | Bin 0 -> 1473 bytes .../media/jquery-openid/images/openidico.png | Bin 0 -> 654 bytes .../jquery-openid/images/openidico16.png | Bin 0 -> 554 bytes .../jquery-openid/images/technorati-1.png | Bin 0 -> 606 bytes .../media/jquery-openid/images/technorati.ico | Bin 0 -> 2294 bytes .../media/jquery-openid/images/twitter.gif | Bin 0 -> 1913 bytes .../media/jquery-openid/images/verisign-2.png | Bin 0 -> 859 bytes .../media/jquery-openid/images/verisign.ico | Bin 0 -> 4710 bytes .../media/jquery-openid/images/vidoop.ico | Bin 0 -> 1406 bytes .../media/jquery-openid/images/vidoop.png | Bin 0 -> 499 bytes .../media/jquery-openid/images/wordpress.ico | Bin 0 -> 1150 bytes .../media/jquery-openid/images/wordpress.png | Bin 0 -> 566 bytes .../media/jquery-openid/images/yahoo.gif | Bin 0 -> 1607 bytes .../media/jquery-openid/jquery.openid.js | 440 ++ .../common/media/jquery-openid/openid.css | 39 + .../skins/common/media/js/autocompleter.js | 766 ++ lms/askbot/skins/common/media/js/compress.bat | 5 + lms/askbot/skins/common/media/js/editor.js | 75 + .../skins/common/media/js/excanvas.min.js | 1 + .../skins/common/media/js/flot-build.bat | 3 + .../skins/common/media/js/jquery-1.4.3.js | 6883 +++++++++++++++++ .../common/media/js/jquery-fieldselection.js | 83 + .../media/js/jquery-fieldselection.min.js | 1 + .../common/media/js/jquery.ajaxfileupload.js | 195 + .../common/media/js/jquery.animate-colors.js | 105 + .../skins/common/media/js/jquery.flot.js | 2119 +++++ .../skins/common/media/js/jquery.flot.min.js | 1 + .../skins/common/media/js/jquery.form.js | 654 ++ .../skins/common/media/js/jquery.history.js | 1 + .../skins/common/media/js/jquery.i18n.js | 133 + .../skins/common/media/js/jquery.openid.js | 176 + .../skins/common/media/js/jquery.validate.js | 1146 +++ .../common/media/js/jquery.validate.min.js | 16 + .../common/media/js/jquery.validate.pack.js | 15 + lms/askbot/skins/common/media/js/less.min.js | 16 + .../skins/common/media/js/live_search.js | 276 + .../common/media/js/live_search_new_thread.js | 82 + .../skins/common/media/js/modernizr.custom.js | 4 + .../skins/common/media/js/output-words.html | 49 + .../skins/common/media/js/output-words.js | 97 + lms/askbot/skins/common/media/js/post.js | 1952 +++++ lms/askbot/skins/common/media/js/se_hilite.js | 1 + .../skins/common/media/js/se_hilite_src.js | 273 + .../skins/common/media/js/tag_selector.js | 375 + lms/askbot/skins/common/media/js/user.js | 215 + lms/askbot/skins/common/media/js/utils.js | 493 ++ .../wmd/images/editor-toolbar-background.png | Bin 0 -> 282 bytes .../media/js/wmd/images/wmd-buttons.png | Bin 0 -> 11480 bytes .../skins/common/media/js/wmd/showdown-min.js | 1 + .../skins/common/media/js/wmd/showdown.js | 1332 ++++ .../skins/common/media/js/wmd/wmd-min.js | 1 + .../skins/common/media/js/wmd/wmd-test.html | 158 + lms/askbot/skins/common/media/js/wmd/wmd.css | 131 + lms/askbot/skins/common/media/js/wmd/wmd.js | 2438 ++++++ lms/askbot/skins/common/media/style/auth.css | 48 + .../media/style/jquery.autocomplete.css | 37 + .../skins/common/media/style/lib_style.less | 38 + .../skins/common/media/style/openid.css | 45 + .../skins/common/media/style/prettify.css | 27 + lms/askbot/skins/common/media/style/style.css | 2616 +++++++ .../authopenid/authopenid_macros.html | 58 + .../templates/authopenid/changeemail.html | 80 + .../common/templates/authopenid/complete.html | 84 + .../templates/authopenid/confirm_email.txt | 12 + .../templates/authopenid/email_validation.txt | 14 + .../common/templates/authopenid/logout.html | 31 + .../authopenid/providers_javascript.html | 55 + .../common/templates/authopenid/signin.html | 245 + .../authopenid/signup_with_password.html | 58 + .../skins/common/templates/avatar/add.html | 15 + .../skins/common/templates/avatar/change.html | 24 + .../templates/avatar/confirm_delete.html | 15 + .../skins/common/templates/debug_header.html | 27 + .../common/templates/one_column_body.html | 10 + .../question/answer_author_info.html | 6 + .../templates/question/answer_comments.html | 10 + .../templates/question/answer_controls.html | 42 + .../question/answer_vote_buttons.html | 22 + .../question/closed_question_info.html | 5 + .../question/question_author_info.html | 6 + .../templates/question/question_comments.html | 10 + .../templates/question/question_controls.html | 44 + .../templates/question/question_tags.html | 7 + .../question/question_vote_buttons.html | 1 + .../templates/question/share_buttons.html | 5 + .../common/templates/two_column_body.html | 14 + .../common/templates/widgets/edit_post.html | 63 + .../templates/widgets/related_tags.html | 21 + .../common/templates/widgets/search_bar.html | 48 + .../templates/widgets/tag_selector.html | 47 + lms/askbot/skins/loaders.py | 132 + lms/askbot/skins/mitx/media/images/accept.png | Bin 0 -> 727 bytes lms/askbot/skins/mitx/media/images/anon.png | Bin 0 -> 687 bytes .../mitx/media/images/answers-background.png | Bin 0 -> 235 bytes .../media/images/background-user-info.png | Bin 0 -> 361 bytes .../skins/mitx/media/images/bigbutton.png | Bin 0 -> 263 bytes .../mitx/media/images/bigbuttonhover.png | Bin 0 -> 236 bytes .../mitx/media/images/blue-up-arrow-h18px.png | Bin 0 -> 593 bytes .../skins/mitx/media/images/box-arrow.gif | Bin 0 -> 69 bytes .../skins/mitx/media/images/bullet_green.gif | Bin 0 -> 64 bytes .../skins/mitx/media/images/cc-88x31.png | Bin 0 -> 5460 bytes .../skins/mitx/media/images/cc-by-sa.png | Bin 0 -> 5083 bytes .../mitx/media/images/close-small-dark.png | Bin 0 -> 879 bytes .../mitx/media/images/close-small-hover.png | Bin 0 -> 337 bytes .../skins/mitx/media/images/close-small.png | Bin 0 -> 293 bytes lms/askbot/skins/mitx/media/images/close.png | Bin 0 -> 469 bytes .../mitx/media/images/comment-background.png | Bin 0 -> 250 bytes .../skins/mitx/media/images/comment.png | Bin 0 -> 606 bytes .../mitx/media/images/contributorsback.png | Bin 0 -> 714 bytes lms/askbot/skins/mitx/media/images/dash.gif | Bin 0 -> 44 bytes lms/askbot/skins/mitx/media/images/delete.png | Bin 0 -> 434 bytes .../mitx/media/images/dialog-warning-off.png | Bin 0 -> 419 bytes .../mitx/media/images/dialog-warning.png | Bin 0 -> 603 bytes .../media/images/djangomade124x25_grey.gif | Bin 0 -> 2035 bytes lms/askbot/skins/mitx/media/images/dot-g.gif | Bin 0 -> 61 bytes .../skins/mitx/media/images/dot-list.gif | Bin 0 -> 56 bytes lms/askbot/skins/mitx/media/images/edit.png | Bin 0 -> 758 bytes lms/askbot/skins/mitx/media/images/edit2.png | Bin 0 -> 498 bytes .../skins/mitx/media/images/email-sharing.png | Bin 0 -> 1095 bytes .../mitx/media/images/expander-arrow-hide.gif | Bin 0 -> 126 bytes .../mitx/media/images/expander-arrow-show.gif | Bin 0 -> 135 bytes .../mitx/media/images/facebook-sharing.png | Bin 0 -> 1085 bytes .../skins/mitx/media/images/favicon.gif | Bin 0 -> 78 bytes .../skins/mitx/media/images/favicon.ico | Bin 0 -> 14846 bytes .../mitx/media/images/feed-icon-small.png | Bin 0 -> 669 bytes lms/askbot/skins/mitx/media/images/flag.png | Bin 0 -> 515 bytes .../skins/mitx/media/images/go-up-grey.png | Bin 0 -> 563 bytes .../skins/mitx/media/images/go-up-orange.png | Bin 0 -> 586 bytes .../mitx/media/images/google-plus-sharing.png | Bin 0 -> 1161 bytes .../mitx/media/images/gray-up-arrow-h18px.png | Bin 0 -> 383 bytes .../skins/mitx/media/images/grippie.png | Bin 0 -> 162 bytes .../skins/mitx/media/images/indicator.gif | Bin 0 -> 2545 bytes lms/askbot/skins/mitx/media/images/link.png | Bin 0 -> 601 bytes lms/askbot/skins/mitx/media/images/logo.gif | Bin 0 -> 2249 bytes lms/askbot/skins/mitx/media/images/logo.png | Bin 0 -> 5841 bytes lms/askbot/skins/mitx/media/images/logo1.png | Bin 0 -> 2752 bytes lms/askbot/skins/mitx/media/images/logo2.png | Bin 0 -> 2124 bytes .../mitx/media/images/lrg/email-sharing.png | Bin 0 -> 8874 bytes .../media/images/lrg/facebook-sharing.png | Bin 0 -> 8433 bytes .../skins/mitx/media/images/lrg/facebook.png | Bin 0 -> 205 bytes .../media/images/lrg/google-plus-sharing.png | Bin 0 -> 9367 bytes .../skins/mitx/media/images/lrg/linkedin.png | Bin 0 -> 229 bytes .../mitx/media/images/lrg/twitter-sharing.png | Bin 0 -> 8867 bytes .../skins/mitx/media/images/lrg/twitter.png | Bin 0 -> 235 bytes .../mitx/media/images/lrg/youtube-sharing.png | Bin 0 -> 14387 bytes .../mitx/media/images/mail-envelope-empty.png | Bin 0 -> 547 bytes .../mitx/media/images/mail-envelope-full.png | Bin 0 -> 482 bytes lms/askbot/skins/mitx/media/images/medala.gif | Bin 0 -> 801 bytes .../skins/mitx/media/images/medala_on.gif | Bin 0 -> 957 bytes .../skins/mitx/media/images/medium-button.png | Bin 0 -> 217 bytes lms/askbot/skins/mitx/media/images/new.gif | Bin 0 -> 635 bytes .../skins/mitx/media/images/nophoto.png | Bin 0 -> 696 bytes .../skins/mitx/media/images/notification.png | Bin 0 -> 217 bytes lms/askbot/skins/mitx/media/images/openid.gif | Bin 0 -> 910 bytes lms/askbot/skins/mitx/media/images/print.png | Bin 0 -> 1391 bytes .../skins/mitx/media/images/pw-login.gif | Bin 0 -> 1818 bytes .../skins/mitx/media/images/quest-bg.gif | Bin 0 -> 294 bytes lms/askbot/skins/mitx/media/images/retag.png | Bin 0 -> 474 bytes .../skins/mitx/media/images/scopearrow.png | Bin 0 -> 538 bytes .../mitx/media/images/small-button-blue.png | Bin 0 -> 202 bytes .../mitx/media/images/small-button-cancel.png | Bin 0 -> 211 bytes .../media/images/social/email-sharing.png | Bin 0 -> 1095 bytes .../media/images/social/facebook-sharing.png | Bin 0 -> 1085 bytes .../images/social/google-plus-sharing.png | Bin 0 -> 1161 bytes .../media/images/social/twitter-sharing.png | Bin 0 -> 1065 bytes .../media/images/social/youtube-sharing.png | Bin 0 -> 1396 bytes .../skins/mitx/media/images/socialsprite.png | Bin 0 -> 3030 bytes lms/askbot/skins/mitx/media/images/sprite.png | Bin 0 -> 5325 bytes .../skins/mitx/media/images/sprites.png | Bin 0 -> 12705 bytes .../media/images/sprites_source/graphics.svg | 1291 ++++ .../media/images/sprites_source/sprites.svg | 507 ++ .../mitx/media/images/summary-background.png | Bin 0 -> 233 bytes .../skins/mitx/media/images/tag-left.png | Bin 0 -> 488 bytes .../skins/mitx/media/images/tag-right.png | Bin 0 -> 365 bytes lms/askbot/skins/mitx/media/images/tips.png | Bin 0 -> 716 bytes .../mitx/media/images/twitter-sharing.png | Bin 0 -> 1065 bytes .../mitx/media/images/view-background.png | Bin 0 -> 265 bytes .../mitx/media/images/vote-accepted-on.png | Bin 0 -> 1124 bytes .../skins/mitx/media/images/vote-accepted.png | Bin 0 -> 1058 bytes .../mitx/media/images/vote-arrow-down-new.png | Bin 0 -> 1458 bytes .../media/images/vote-arrow-down-on-new.png | Bin 0 -> 980 bytes .../mitx/media/images/vote-arrow-down-on.png | Bin 0 -> 905 bytes .../mitx/media/images/vote-arrow-down.png | Bin 0 -> 876 bytes .../mitx/media/images/vote-arrow-up-new.png | Bin 0 -> 979 bytes .../media/images/vote-arrow-up-on-new.png | Bin 0 -> 1029 bytes .../mitx/media/images/vote-arrow-up-on.png | Bin 0 -> 906 bytes .../skins/mitx/media/images/vote-arrow-up.png | Bin 0 -> 843 bytes .../mitx/media/images/vote-background.png | Bin 0 -> 225 bytes .../mitx/media/images/vote-favorite-off.png | Bin 0 -> 930 bytes .../mitx/media/images/vote-favorite-on.png | Bin 0 -> 1023 bytes lms/askbot/skins/mitx/media/images/wiki.png | Bin 0 -> 5178 bytes .../mitx/media/images/youtube-sharing.png | Bin 0 -> 1396 bytes lms/askbot/skins/mitx/media/style/auth.css | 48 + lms/askbot/skins/mitx/media/style/extra.css | 227 + .../mitx/media/style/jquery.autocomplete.css | 40 + .../skins/mitx/media/style/lib_style.less | 65 + lms/askbot/skins/mitx/media/style/openid.css | 45 + .../skins/mitx/media/style/prettify.css | 27 + lms/askbot/skins/mitx/media/style/style.css | 3165 ++++++++ lms/askbot/skins/mitx/media/style/style.less | 3351 ++++++++ lms/askbot/skins/mitx/templates/404.html | 5 + .../skins/mitx/templates/404.jinja.html | 44 + lms/askbot/skins/mitx/templates/500.html | 5 + .../skins/mitx/templates/500.jinja.html | 25 + .../skins/mitx/templates/answer_edit.html | 84 + lms/askbot/skins/mitx/templates/ask.html | 67 + lms/askbot/skins/mitx/templates/badge.html | 40 + lms/askbot/skins/mitx/templates/badges.html | 66 + lms/askbot/skins/mitx/templates/base.html | 47 + lms/askbot/skins/mitx/templates/close.html | 28 + .../templates/course_navigation.jinja.html | 16 + .../skins/mitx/templates/faq_static.html | 93 + lms/askbot/skins/mitx/templates/feedback.html | 68 + .../skins/mitx/templates/feedback_email.txt | 13 + lms/askbot/skins/mitx/templates/help.html | 33 + .../skins/mitx/templates/import_data.html | 31 + .../mitx/templates/instant_notification.html | 42 + lms/askbot/skins/mitx/templates/macros.html | 625 ++ .../skins/mitx/templates/main_page.html | 28 + .../mitx/templates/main_page/content.html | 3 + .../mitx/templates/main_page/headline.html | 42 + .../mitx/templates/main_page/javascript.html | 32 + .../templates/main_page/nothing_found.html | 30 + .../mitx/templates/main_page/paginator.html | 7 + .../templates/main_page/questions_loop.html | 14 + .../templates/main_page/scope_filters.html | 16 + .../mitx/templates/main_page/sidebar.html | 34 + .../mitx/templates/main_page/tab_bar.html | 119 + .../mitx/templates/meta/bottom_scripts.html | 107 + .../mitx/templates/meta/editor_data.html | 13 + .../templates/meta/html_head_javascript.html | 11 + .../mitx/templates/meta/html_head_meta.html | 8 + .../templates/meta/html_head_stylesheets.html | 3 + .../templates/meta/mandatory_tags_js.html | 25 + .../mitx/templates/navigation.jinja.html | 27 + lms/askbot/skins/mitx/templates/question.html | 23 + .../mitx/templates/question/answer_card.html | 41 + .../templates/question/answer_tab_bar.html | 29 + .../mitx/templates/question/content.html | 42 + .../mitx/templates/question/javascript.html | 91 + .../templates/question/new_answer_form.html | 59 + .../templates/question/question_card.html | 47 + .../question/sharing_prompt_phrase.html | 13 + .../mitx/templates/question/sidebar.html | 63 + .../question/subscribe_by_email_prompt.html | 23 + .../skins/mitx/templates/question_edit.html | 96 + .../skins/mitx/templates/question_retag.html | 68 + .../skins/mitx/templates/question_widget.html | 21 + lms/askbot/skins/mitx/templates/reopen.html | 38 + .../skins/mitx/templates/revisions.html | 102 + .../skins/mitx/templates/static_page.html | 10 + .../mitx/templates/subscribe_for_tags.html | 19 + lms/askbot/skins/mitx/templates/tags.html | 75 + .../mitx/templates/user_profile/user.html | 30 + .../templates/user_profile/user_edit.html | 120 + .../user_email_subscriptions.html | 27 + .../user_profile/user_favorites.html | 9 + .../templates/user_profile/user_inbox.html | 131 + .../templates/user_profile/user_info.html | 90 + .../templates/user_profile/user_moderate.html | 93 + .../templates/user_profile/user_network.html | 25 + .../templates/user_profile/user_recent.html | 40 + .../user_profile/user_reputation.html | 41 + .../templates/user_profile/user_stats.html | 155 + .../templates/user_profile/user_tabs.html | 57 + .../templates/user_profile/user_votes.html | 30 + .../user_profile/users_questions.html | 9 + lms/askbot/skins/mitx/templates/users.html | 60 + .../templates/widgets/answer_edit_tips.html | 64 + .../mitx/templates/widgets/ask_button.html | 6 + .../mitx/templates/widgets/ask_form.html | 49 + .../mitx/templates/widgets/contributors.html | 10 + .../skins/mitx/templates/widgets/footer.html | 38 + .../skins/mitx/templates/widgets/header.html | 4 + .../skins/mitx/templates/widgets/logo.html | 5 + .../mitx/templates/widgets/meta_nav.html | 15 + .../templates/widgets/question_edit_tips.html | 69 + .../templates/widgets/question_summary.html | 63 + .../mitx/templates/widgets/scope_nav.html | 25 + .../templates/widgets/secondary_header.html | 21 + .../templates/widgets/system_messages.html | 8 + .../mitx/templates/widgets/user_list.html | 22 + .../user_long_score_and_badge_summary.html | 21 + .../templates/widgets/user_navigation.html | 17 + .../widgets/user_score_and_badge_summary.html | 19 + lms/askbot/skins/utils.py | 195 + lms/djangoapps/courseware/tabs.py | 3 + lms/envs/askbotsettings.py | 293 + lms/envs/aws.py | 3 +- lms/envs/common.py | 61 +- lms/envs/dev.py | 13 +- lms/envs/dev_ike.py | 1 + lms/envs/devgroups/courses.py | 4 + lms/envs/devgroups/h_cs50.py | 1 + lms/envs/devgroups/m_6002.py | 1 + lms/envs/devplus.py | 2 +- lms/envs/edx4edx_aws.py | 1 + lms/envs/static.py | 12 +- lms/envs/test.py | 16 +- lms/envs/test_ike.py | 14 +- lms/static/sass/course.scss | 1 - lms/urls.py | 21 +- 651 files changed, 39089 insertions(+), 15 deletions(-) create mode 100644 .hgignore create mode 160000 askbot create mode 100644 lms/askbot/skins/README create mode 100644 lms/askbot/skins/__init__.py create mode 100644 lms/askbot/skins/common/media/images/anon.png create mode 100644 lms/askbot/skins/common/media/images/bigbutton.png create mode 100644 lms/askbot/skins/common/media/images/bigbuttonhover.png create mode 100755 lms/askbot/skins/common/media/images/blue-up-arrow-h18px.png create mode 100755 lms/askbot/skins/common/media/images/box-arrow.gif create mode 100755 lms/askbot/skins/common/media/images/bullet_green.gif create mode 100755 lms/askbot/skins/common/media/images/cc-88x31.png create mode 100644 lms/askbot/skins/common/media/images/cc-by-sa.png create mode 100755 lms/askbot/skins/common/media/images/close-small-dark.png create mode 100755 lms/askbot/skins/common/media/images/close-small-hover.png create mode 100755 lms/askbot/skins/common/media/images/close-small.png create mode 100644 lms/askbot/skins/common/media/images/contributorsback.png create mode 100755 lms/askbot/skins/common/media/images/dash.gif create mode 100644 lms/askbot/skins/common/media/images/dialog-warning-off.png create mode 100644 lms/askbot/skins/common/media/images/dialog-warning.png create mode 100755 lms/askbot/skins/common/media/images/djangomade124x25_grey.gif create mode 100755 lms/askbot/skins/common/media/images/dot-g.gif create mode 100755 lms/askbot/skins/common/media/images/dot-list.gif create mode 100755 lms/askbot/skins/common/media/images/edit.png create mode 100755 lms/askbot/skins/common/media/images/expander-arrow-hide.gif create mode 100755 lms/askbot/skins/common/media/images/expander-arrow-show.gif create mode 100644 lms/askbot/skins/common/media/images/favicon.gif create mode 100644 lms/askbot/skins/common/media/images/favicon.ico create mode 100644 lms/askbot/skins/common/media/images/feed-icon-small.png create mode 100755 lms/askbot/skins/common/media/images/flags/ad.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ae.gif create mode 100755 lms/askbot/skins/common/media/images/flags/af.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ag.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ai.gif create mode 100755 lms/askbot/skins/common/media/images/flags/al.gif create mode 100755 lms/askbot/skins/common/media/images/flags/am.gif create mode 100755 lms/askbot/skins/common/media/images/flags/an.gif create mode 100644 lms/askbot/skins/common/media/images/flags/ao.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ar.gif create mode 100755 lms/askbot/skins/common/media/images/flags/as.gif create mode 100755 lms/askbot/skins/common/media/images/flags/at.gif create mode 100755 lms/askbot/skins/common/media/images/flags/au.gif create mode 100755 lms/askbot/skins/common/media/images/flags/aw.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ax.gif create mode 100755 lms/askbot/skins/common/media/images/flags/az.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ba.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bb.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bd.gif create mode 100755 lms/askbot/skins/common/media/images/flags/be.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bf.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bg.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bh.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bi.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bj.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bm.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bn.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bo.gif create mode 100755 lms/askbot/skins/common/media/images/flags/br.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bs.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bt.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bv.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bw.gif create mode 100755 lms/askbot/skins/common/media/images/flags/by.gif create mode 100755 lms/askbot/skins/common/media/images/flags/bz.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ca.gif create mode 100644 lms/askbot/skins/common/media/images/flags/catalonia.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cc.gif create mode 100644 lms/askbot/skins/common/media/images/flags/cd.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cf.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cg.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ch.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ci.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ck.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cl.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cm.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cn.gif create mode 100755 lms/askbot/skins/common/media/images/flags/co.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cr.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cs.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cu.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cv.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cx.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cy.gif create mode 100755 lms/askbot/skins/common/media/images/flags/cz.gif create mode 100755 lms/askbot/skins/common/media/images/flags/de.gif create mode 100755 lms/askbot/skins/common/media/images/flags/dj.gif create mode 100755 lms/askbot/skins/common/media/images/flags/dk.gif create mode 100755 lms/askbot/skins/common/media/images/flags/dm.gif create mode 100755 lms/askbot/skins/common/media/images/flags/do.gif create mode 100755 lms/askbot/skins/common/media/images/flags/dz.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ec.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ee.gif create mode 100755 lms/askbot/skins/common/media/images/flags/eg.gif create mode 100755 lms/askbot/skins/common/media/images/flags/eh.gif create mode 100755 lms/askbot/skins/common/media/images/flags/england.gif create mode 100755 lms/askbot/skins/common/media/images/flags/er.gif create mode 100755 lms/askbot/skins/common/media/images/flags/es.gif create mode 100755 lms/askbot/skins/common/media/images/flags/et.gif create mode 100644 lms/askbot/skins/common/media/images/flags/europeanunion.gif create mode 100755 lms/askbot/skins/common/media/images/flags/fam.gif create mode 100755 lms/askbot/skins/common/media/images/flags/fi.gif create mode 100755 lms/askbot/skins/common/media/images/flags/fj.gif create mode 100755 lms/askbot/skins/common/media/images/flags/fk.gif create mode 100755 lms/askbot/skins/common/media/images/flags/fm.gif create mode 100755 lms/askbot/skins/common/media/images/flags/fo.gif create mode 100755 lms/askbot/skins/common/media/images/flags/fr.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ga.gif create mode 100644 lms/askbot/skins/common/media/images/flags/gb.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gd.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ge.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gf.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gh.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gi.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gl.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gm.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gn.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gp.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gq.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gr.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gs.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gt.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gu.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gw.gif create mode 100755 lms/askbot/skins/common/media/images/flags/gy.gif create mode 100755 lms/askbot/skins/common/media/images/flags/hk.gif create mode 100755 lms/askbot/skins/common/media/images/flags/hm.gif create mode 100755 lms/askbot/skins/common/media/images/flags/hn.gif create mode 100755 lms/askbot/skins/common/media/images/flags/hr.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ht.gif create mode 100755 lms/askbot/skins/common/media/images/flags/hu.gif create mode 100755 lms/askbot/skins/common/media/images/flags/id.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ie.gif create mode 100755 lms/askbot/skins/common/media/images/flags/il.gif create mode 100755 lms/askbot/skins/common/media/images/flags/in.gif create mode 100755 lms/askbot/skins/common/media/images/flags/io.gif create mode 100755 lms/askbot/skins/common/media/images/flags/iq.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ir.gif create mode 100755 lms/askbot/skins/common/media/images/flags/is.gif create mode 100755 lms/askbot/skins/common/media/images/flags/it.gif create mode 100755 lms/askbot/skins/common/media/images/flags/jm.gif create mode 100755 lms/askbot/skins/common/media/images/flags/jo.gif create mode 100755 lms/askbot/skins/common/media/images/flags/jp.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ke.gif create mode 100755 lms/askbot/skins/common/media/images/flags/kg.gif create mode 100755 lms/askbot/skins/common/media/images/flags/kh.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ki.gif create mode 100755 lms/askbot/skins/common/media/images/flags/km.gif create mode 100755 lms/askbot/skins/common/media/images/flags/kn.gif create mode 100755 lms/askbot/skins/common/media/images/flags/kp.gif create mode 100755 lms/askbot/skins/common/media/images/flags/kr.gif create mode 100755 lms/askbot/skins/common/media/images/flags/kw.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ky.gif create mode 100755 lms/askbot/skins/common/media/images/flags/kz.gif create mode 100755 lms/askbot/skins/common/media/images/flags/la.gif create mode 100755 lms/askbot/skins/common/media/images/flags/lb.gif create mode 100644 lms/askbot/skins/common/media/images/flags/lc.gif create mode 100755 lms/askbot/skins/common/media/images/flags/li.gif create mode 100755 lms/askbot/skins/common/media/images/flags/lk.gif create mode 100755 lms/askbot/skins/common/media/images/flags/lr.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ls.gif create mode 100755 lms/askbot/skins/common/media/images/flags/lt.gif create mode 100755 lms/askbot/skins/common/media/images/flags/lu.gif create mode 100755 lms/askbot/skins/common/media/images/flags/lv.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ly.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ma.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mc.gif create mode 100755 lms/askbot/skins/common/media/images/flags/md.gif create mode 100644 lms/askbot/skins/common/media/images/flags/me.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mg.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mh.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mk.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ml.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mm.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mn.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mo.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mp.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mq.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mr.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ms.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mt.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mu.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mv.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mw.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mx.gif create mode 100755 lms/askbot/skins/common/media/images/flags/my.gif create mode 100755 lms/askbot/skins/common/media/images/flags/mz.gif create mode 100755 lms/askbot/skins/common/media/images/flags/na.gif create mode 100755 lms/askbot/skins/common/media/images/flags/nc.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ne.gif create mode 100755 lms/askbot/skins/common/media/images/flags/nf.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ng.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ni.gif create mode 100755 lms/askbot/skins/common/media/images/flags/nl.gif create mode 100755 lms/askbot/skins/common/media/images/flags/no.gif create mode 100755 lms/askbot/skins/common/media/images/flags/np.gif create mode 100755 lms/askbot/skins/common/media/images/flags/nr.gif create mode 100755 lms/askbot/skins/common/media/images/flags/nu.gif create mode 100755 lms/askbot/skins/common/media/images/flags/nz.gif create mode 100755 lms/askbot/skins/common/media/images/flags/om.gif create mode 100755 lms/askbot/skins/common/media/images/flags/pa.gif create mode 100755 lms/askbot/skins/common/media/images/flags/pe.gif create mode 100755 lms/askbot/skins/common/media/images/flags/pf.gif create mode 100755 lms/askbot/skins/common/media/images/flags/pg.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ph.gif create mode 100755 lms/askbot/skins/common/media/images/flags/pk.gif create mode 100755 lms/askbot/skins/common/media/images/flags/pl.gif create mode 100755 lms/askbot/skins/common/media/images/flags/pm.gif create mode 100755 lms/askbot/skins/common/media/images/flags/pn.gif create mode 100755 lms/askbot/skins/common/media/images/flags/pr.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ps.gif create mode 100755 lms/askbot/skins/common/media/images/flags/pt.gif create mode 100755 lms/askbot/skins/common/media/images/flags/pw.gif create mode 100755 lms/askbot/skins/common/media/images/flags/py.gif create mode 100755 lms/askbot/skins/common/media/images/flags/qa.gif create mode 100755 lms/askbot/skins/common/media/images/flags/re.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ro.gif create mode 100644 lms/askbot/skins/common/media/images/flags/rs.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ru.gif create mode 100755 lms/askbot/skins/common/media/images/flags/rw.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sa.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sb.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sc.gif create mode 100755 lms/askbot/skins/common/media/images/flags/scotland.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sd.gif create mode 100755 lms/askbot/skins/common/media/images/flags/se.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sg.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sh.gif create mode 100755 lms/askbot/skins/common/media/images/flags/si.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sj.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sk.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sl.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sm.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sn.gif create mode 100755 lms/askbot/skins/common/media/images/flags/so.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sr.gif create mode 100755 lms/askbot/skins/common/media/images/flags/st.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sv.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sy.gif create mode 100755 lms/askbot/skins/common/media/images/flags/sz.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tc.gif create mode 100755 lms/askbot/skins/common/media/images/flags/td.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tf.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tg.gif create mode 100755 lms/askbot/skins/common/media/images/flags/th.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tj.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tk.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tl.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tm.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tn.gif create mode 100755 lms/askbot/skins/common/media/images/flags/to.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tr.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tt.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tv.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tw.gif create mode 100755 lms/askbot/skins/common/media/images/flags/tz.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ua.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ug.gif create mode 100755 lms/askbot/skins/common/media/images/flags/um.gif create mode 100755 lms/askbot/skins/common/media/images/flags/us.gif create mode 100755 lms/askbot/skins/common/media/images/flags/uy.gif create mode 100755 lms/askbot/skins/common/media/images/flags/uz.gif create mode 100755 lms/askbot/skins/common/media/images/flags/va.gif create mode 100755 lms/askbot/skins/common/media/images/flags/vc.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ve.gif create mode 100755 lms/askbot/skins/common/media/images/flags/vg.gif create mode 100755 lms/askbot/skins/common/media/images/flags/vi.gif create mode 100755 lms/askbot/skins/common/media/images/flags/vn.gif create mode 100755 lms/askbot/skins/common/media/images/flags/vu.gif create mode 100755 lms/askbot/skins/common/media/images/flags/wales.gif create mode 100755 lms/askbot/skins/common/media/images/flags/wf.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ws.gif create mode 100755 lms/askbot/skins/common/media/images/flags/ye.gif create mode 100755 lms/askbot/skins/common/media/images/flags/yt.gif create mode 100755 lms/askbot/skins/common/media/images/flags/za.gif create mode 100755 lms/askbot/skins/common/media/images/flags/zm.gif create mode 100755 lms/askbot/skins/common/media/images/flags/zw.gif create mode 100644 lms/askbot/skins/common/media/images/go-up-grey.png create mode 100644 lms/askbot/skins/common/media/images/go-up-orange.png create mode 100755 lms/askbot/skins/common/media/images/gray-up-arrow-h18px.png create mode 100755 lms/askbot/skins/common/media/images/grippie.png create mode 100755 lms/askbot/skins/common/media/images/indicator.gif create mode 100644 lms/askbot/skins/common/media/images/logo.gif create mode 100644 lms/askbot/skins/common/media/images/logo.png create mode 100755 lms/askbot/skins/common/media/images/logo1.png create mode 100755 lms/askbot/skins/common/media/images/logo2.png create mode 100644 lms/askbot/skins/common/media/images/mail-envelope-empty.png create mode 100644 lms/askbot/skins/common/media/images/mail-envelope-full.png create mode 100755 lms/askbot/skins/common/media/images/medala.gif create mode 100755 lms/askbot/skins/common/media/images/medala_on.gif create mode 100755 lms/askbot/skins/common/media/images/new.gif create mode 100755 lms/askbot/skins/common/media/images/nophoto.png create mode 100755 lms/askbot/skins/common/media/images/openid.gif create mode 100755 lms/askbot/skins/common/media/images/openid/aol.gif create mode 100755 lms/askbot/skins/common/media/images/openid/blogger.ico create mode 100755 lms/askbot/skins/common/media/images/openid/claimid.ico create mode 100755 lms/askbot/skins/common/media/images/openid/facebook.gif create mode 100755 lms/askbot/skins/common/media/images/openid/flickr.ico create mode 100755 lms/askbot/skins/common/media/images/openid/google.gif create mode 100755 lms/askbot/skins/common/media/images/openid/livejournal.ico create mode 100755 lms/askbot/skins/common/media/images/openid/myopenid.ico create mode 100755 lms/askbot/skins/common/media/images/openid/openid-inputicon.gif create mode 100755 lms/askbot/skins/common/media/images/openid/openid.gif create mode 100755 lms/askbot/skins/common/media/images/openid/technorati.ico create mode 100755 lms/askbot/skins/common/media/images/openid/twitter.png create mode 100755 lms/askbot/skins/common/media/images/openid/verisign.ico create mode 100755 lms/askbot/skins/common/media/images/openid/vidoop.ico create mode 100755 lms/askbot/skins/common/media/images/openid/wordpress.ico create mode 100755 lms/askbot/skins/common/media/images/openid/yahoo.gif create mode 100644 lms/askbot/skins/common/media/images/print.png create mode 100644 lms/askbot/skins/common/media/images/pw-login.gif create mode 100755 lms/askbot/skins/common/media/images/quest-bg.gif create mode 100644 lms/askbot/skins/common/media/images/scopearrow.png create mode 100644 lms/askbot/skins/common/media/images/sprite.png create mode 100644 lms/askbot/skins/common/media/images/sprites.png create mode 100644 lms/askbot/skins/common/media/images/sprites_source/sprites.svg create mode 100644 lms/askbot/skins/common/media/images/summary-background.png create mode 100644 lms/askbot/skins/common/media/images/tag-left.png create mode 100644 lms/askbot/skins/common/media/images/tag-right.png create mode 100755 lms/askbot/skins/common/media/images/vote-accepted-on.png create mode 100755 lms/askbot/skins/common/media/images/vote-accepted.png create mode 100755 lms/askbot/skins/common/media/images/vote-arrow-down-on.png create mode 100755 lms/askbot/skins/common/media/images/vote-arrow-down.png create mode 100755 lms/askbot/skins/common/media/images/vote-arrow-up-on.png create mode 100755 lms/askbot/skins/common/media/images/vote-arrow-up.png create mode 100755 lms/askbot/skins/common/media/images/vote-favorite-off.png create mode 100755 lms/askbot/skins/common/media/images/vote-favorite-on.png create mode 100644 lms/askbot/skins/common/media/images/wiki.png create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/aol.gif create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/blogger-1.png create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/blogger.ico create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/claimid-0.png create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/claimid.ico create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/facebook.gif create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/flickr.ico create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/flickr.png create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/google.gif create mode 100644 lms/askbot/skins/common/media/jquery-openid/images/identica.png create mode 100644 lms/askbot/skins/common/media/jquery-openid/images/linkedin.gif create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/livejournal-1.png create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/livejournal.ico create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/myopenid-2.png create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/myopenid.ico create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/openid-inputicon.gif create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/openid.gif create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/openidico.png create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/openidico16.png create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/technorati-1.png create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/technorati.ico create mode 100644 lms/askbot/skins/common/media/jquery-openid/images/twitter.gif create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/verisign-2.png create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/verisign.ico create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/vidoop.ico create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/vidoop.png create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/wordpress.ico create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/wordpress.png create mode 100755 lms/askbot/skins/common/media/jquery-openid/images/yahoo.gif create mode 100644 lms/askbot/skins/common/media/jquery-openid/jquery.openid.js create mode 100644 lms/askbot/skins/common/media/jquery-openid/openid.css create mode 100644 lms/askbot/skins/common/media/js/autocompleter.js create mode 100644 lms/askbot/skins/common/media/js/compress.bat create mode 100644 lms/askbot/skins/common/media/js/editor.js create mode 100644 lms/askbot/skins/common/media/js/excanvas.min.js create mode 100644 lms/askbot/skins/common/media/js/flot-build.bat create mode 100644 lms/askbot/skins/common/media/js/jquery-1.4.3.js create mode 100644 lms/askbot/skins/common/media/js/jquery-fieldselection.js create mode 100644 lms/askbot/skins/common/media/js/jquery-fieldselection.min.js create mode 100644 lms/askbot/skins/common/media/js/jquery.ajaxfileupload.js create mode 100644 lms/askbot/skins/common/media/js/jquery.animate-colors.js create mode 100644 lms/askbot/skins/common/media/js/jquery.flot.js create mode 100644 lms/askbot/skins/common/media/js/jquery.flot.min.js create mode 100644 lms/askbot/skins/common/media/js/jquery.form.js create mode 100644 lms/askbot/skins/common/media/js/jquery.history.js create mode 100644 lms/askbot/skins/common/media/js/jquery.i18n.js create mode 100644 lms/askbot/skins/common/media/js/jquery.openid.js create mode 100644 lms/askbot/skins/common/media/js/jquery.validate.js create mode 100644 lms/askbot/skins/common/media/js/jquery.validate.min.js create mode 100644 lms/askbot/skins/common/media/js/jquery.validate.pack.js create mode 100644 lms/askbot/skins/common/media/js/less.min.js create mode 100644 lms/askbot/skins/common/media/js/live_search.js create mode 100644 lms/askbot/skins/common/media/js/live_search_new_thread.js create mode 100644 lms/askbot/skins/common/media/js/modernizr.custom.js create mode 100644 lms/askbot/skins/common/media/js/output-words.html create mode 100644 lms/askbot/skins/common/media/js/output-words.js create mode 100644 lms/askbot/skins/common/media/js/post.js create mode 100644 lms/askbot/skins/common/media/js/se_hilite.js create mode 100644 lms/askbot/skins/common/media/js/se_hilite_src.js create mode 100644 lms/askbot/skins/common/media/js/tag_selector.js create mode 100644 lms/askbot/skins/common/media/js/user.js create mode 100644 lms/askbot/skins/common/media/js/utils.js create mode 100644 lms/askbot/skins/common/media/js/wmd/images/editor-toolbar-background.png create mode 100755 lms/askbot/skins/common/media/js/wmd/images/wmd-buttons.png create mode 100644 lms/askbot/skins/common/media/js/wmd/showdown-min.js create mode 100644 lms/askbot/skins/common/media/js/wmd/showdown.js create mode 100644 lms/askbot/skins/common/media/js/wmd/wmd-min.js create mode 100644 lms/askbot/skins/common/media/js/wmd/wmd-test.html create mode 100644 lms/askbot/skins/common/media/js/wmd/wmd.css create mode 100644 lms/askbot/skins/common/media/js/wmd/wmd.js create mode 100644 lms/askbot/skins/common/media/style/auth.css create mode 100644 lms/askbot/skins/common/media/style/jquery.autocomplete.css create mode 100644 lms/askbot/skins/common/media/style/lib_style.less create mode 100644 lms/askbot/skins/common/media/style/openid.css create mode 100644 lms/askbot/skins/common/media/style/prettify.css create mode 100644 lms/askbot/skins/common/media/style/style.css create mode 100644 lms/askbot/skins/common/templates/authopenid/authopenid_macros.html create mode 100644 lms/askbot/skins/common/templates/authopenid/changeemail.html create mode 100644 lms/askbot/skins/common/templates/authopenid/complete.html create mode 100644 lms/askbot/skins/common/templates/authopenid/confirm_email.txt create mode 100644 lms/askbot/skins/common/templates/authopenid/email_validation.txt create mode 100644 lms/askbot/skins/common/templates/authopenid/logout.html create mode 100644 lms/askbot/skins/common/templates/authopenid/providers_javascript.html create mode 100644 lms/askbot/skins/common/templates/authopenid/signin.html create mode 100644 lms/askbot/skins/common/templates/authopenid/signup_with_password.html create mode 100644 lms/askbot/skins/common/templates/avatar/add.html create mode 100644 lms/askbot/skins/common/templates/avatar/change.html create mode 100644 lms/askbot/skins/common/templates/avatar/confirm_delete.html create mode 100644 lms/askbot/skins/common/templates/debug_header.html create mode 100644 lms/askbot/skins/common/templates/one_column_body.html create mode 100644 lms/askbot/skins/common/templates/question/answer_author_info.html create mode 100644 lms/askbot/skins/common/templates/question/answer_comments.html create mode 100644 lms/askbot/skins/common/templates/question/answer_controls.html create mode 100644 lms/askbot/skins/common/templates/question/answer_vote_buttons.html create mode 100644 lms/askbot/skins/common/templates/question/closed_question_info.html create mode 100644 lms/askbot/skins/common/templates/question/question_author_info.html create mode 100644 lms/askbot/skins/common/templates/question/question_comments.html create mode 100644 lms/askbot/skins/common/templates/question/question_controls.html create mode 100644 lms/askbot/skins/common/templates/question/question_tags.html create mode 100644 lms/askbot/skins/common/templates/question/question_vote_buttons.html create mode 100644 lms/askbot/skins/common/templates/question/share_buttons.html create mode 100644 lms/askbot/skins/common/templates/two_column_body.html create mode 100644 lms/askbot/skins/common/templates/widgets/edit_post.html create mode 100644 lms/askbot/skins/common/templates/widgets/related_tags.html create mode 100644 lms/askbot/skins/common/templates/widgets/search_bar.html create mode 100644 lms/askbot/skins/common/templates/widgets/tag_selector.html create mode 100644 lms/askbot/skins/loaders.py create mode 100644 lms/askbot/skins/mitx/media/images/accept.png create mode 100644 lms/askbot/skins/mitx/media/images/anon.png create mode 100644 lms/askbot/skins/mitx/media/images/answers-background.png create mode 100644 lms/askbot/skins/mitx/media/images/background-user-info.png create mode 100644 lms/askbot/skins/mitx/media/images/bigbutton.png create mode 100644 lms/askbot/skins/mitx/media/images/bigbuttonhover.png create mode 100755 lms/askbot/skins/mitx/media/images/blue-up-arrow-h18px.png create mode 100755 lms/askbot/skins/mitx/media/images/box-arrow.gif create mode 100755 lms/askbot/skins/mitx/media/images/bullet_green.gif create mode 100755 lms/askbot/skins/mitx/media/images/cc-88x31.png create mode 100644 lms/askbot/skins/mitx/media/images/cc-by-sa.png create mode 100644 lms/askbot/skins/mitx/media/images/close-small-dark.png create mode 100755 lms/askbot/skins/mitx/media/images/close-small-hover.png create mode 100755 lms/askbot/skins/mitx/media/images/close-small.png create mode 100644 lms/askbot/skins/mitx/media/images/close.png create mode 100644 lms/askbot/skins/mitx/media/images/comment-background.png create mode 100644 lms/askbot/skins/mitx/media/images/comment.png create mode 100644 lms/askbot/skins/mitx/media/images/contributorsback.png create mode 100755 lms/askbot/skins/mitx/media/images/dash.gif create mode 100644 lms/askbot/skins/mitx/media/images/delete.png create mode 100644 lms/askbot/skins/mitx/media/images/dialog-warning-off.png create mode 100644 lms/askbot/skins/mitx/media/images/dialog-warning.png create mode 100755 lms/askbot/skins/mitx/media/images/djangomade124x25_grey.gif create mode 100755 lms/askbot/skins/mitx/media/images/dot-g.gif create mode 100755 lms/askbot/skins/mitx/media/images/dot-list.gif create mode 100755 lms/askbot/skins/mitx/media/images/edit.png create mode 100644 lms/askbot/skins/mitx/media/images/edit2.png create mode 100644 lms/askbot/skins/mitx/media/images/email-sharing.png create mode 100755 lms/askbot/skins/mitx/media/images/expander-arrow-hide.gif create mode 100755 lms/askbot/skins/mitx/media/images/expander-arrow-show.gif create mode 100644 lms/askbot/skins/mitx/media/images/facebook-sharing.png create mode 100644 lms/askbot/skins/mitx/media/images/favicon.gif create mode 100644 lms/askbot/skins/mitx/media/images/favicon.ico create mode 100644 lms/askbot/skins/mitx/media/images/feed-icon-small.png create mode 100644 lms/askbot/skins/mitx/media/images/flag.png create mode 100644 lms/askbot/skins/mitx/media/images/go-up-grey.png create mode 100644 lms/askbot/skins/mitx/media/images/go-up-orange.png create mode 100644 lms/askbot/skins/mitx/media/images/google-plus-sharing.png create mode 100755 lms/askbot/skins/mitx/media/images/gray-up-arrow-h18px.png create mode 100755 lms/askbot/skins/mitx/media/images/grippie.png create mode 100755 lms/askbot/skins/mitx/media/images/indicator.gif create mode 100644 lms/askbot/skins/mitx/media/images/link.png create mode 100644 lms/askbot/skins/mitx/media/images/logo.gif create mode 100644 lms/askbot/skins/mitx/media/images/logo.png create mode 100755 lms/askbot/skins/mitx/media/images/logo1.png create mode 100755 lms/askbot/skins/mitx/media/images/logo2.png create mode 100644 lms/askbot/skins/mitx/media/images/lrg/email-sharing.png create mode 100644 lms/askbot/skins/mitx/media/images/lrg/facebook-sharing.png create mode 100644 lms/askbot/skins/mitx/media/images/lrg/facebook.png create mode 100644 lms/askbot/skins/mitx/media/images/lrg/google-plus-sharing.png create mode 100644 lms/askbot/skins/mitx/media/images/lrg/linkedin.png create mode 100644 lms/askbot/skins/mitx/media/images/lrg/twitter-sharing.png create mode 100644 lms/askbot/skins/mitx/media/images/lrg/twitter.png create mode 100644 lms/askbot/skins/mitx/media/images/lrg/youtube-sharing.png create mode 100644 lms/askbot/skins/mitx/media/images/mail-envelope-empty.png create mode 100644 lms/askbot/skins/mitx/media/images/mail-envelope-full.png create mode 100755 lms/askbot/skins/mitx/media/images/medala.gif create mode 100755 lms/askbot/skins/mitx/media/images/medala_on.gif create mode 100644 lms/askbot/skins/mitx/media/images/medium-button.png create mode 100755 lms/askbot/skins/mitx/media/images/new.gif create mode 100755 lms/askbot/skins/mitx/media/images/nophoto.png create mode 100644 lms/askbot/skins/mitx/media/images/notification.png create mode 100755 lms/askbot/skins/mitx/media/images/openid.gif create mode 100644 lms/askbot/skins/mitx/media/images/print.png create mode 100644 lms/askbot/skins/mitx/media/images/pw-login.gif create mode 100755 lms/askbot/skins/mitx/media/images/quest-bg.gif create mode 100644 lms/askbot/skins/mitx/media/images/retag.png create mode 100644 lms/askbot/skins/mitx/media/images/scopearrow.png create mode 100644 lms/askbot/skins/mitx/media/images/small-button-blue.png create mode 100644 lms/askbot/skins/mitx/media/images/small-button-cancel.png create mode 100644 lms/askbot/skins/mitx/media/images/social/email-sharing.png create mode 100644 lms/askbot/skins/mitx/media/images/social/facebook-sharing.png create mode 100644 lms/askbot/skins/mitx/media/images/social/google-plus-sharing.png create mode 100644 lms/askbot/skins/mitx/media/images/social/twitter-sharing.png create mode 100644 lms/askbot/skins/mitx/media/images/social/youtube-sharing.png create mode 100644 lms/askbot/skins/mitx/media/images/socialsprite.png create mode 100644 lms/askbot/skins/mitx/media/images/sprite.png create mode 100644 lms/askbot/skins/mitx/media/images/sprites.png create mode 100644 lms/askbot/skins/mitx/media/images/sprites_source/graphics.svg create mode 100644 lms/askbot/skins/mitx/media/images/sprites_source/sprites.svg create mode 100644 lms/askbot/skins/mitx/media/images/summary-background.png create mode 100644 lms/askbot/skins/mitx/media/images/tag-left.png create mode 100644 lms/askbot/skins/mitx/media/images/tag-right.png create mode 100644 lms/askbot/skins/mitx/media/images/tips.png create mode 100644 lms/askbot/skins/mitx/media/images/twitter-sharing.png create mode 100644 lms/askbot/skins/mitx/media/images/view-background.png create mode 100755 lms/askbot/skins/mitx/media/images/vote-accepted-on.png create mode 100755 lms/askbot/skins/mitx/media/images/vote-accepted.png create mode 100644 lms/askbot/skins/mitx/media/images/vote-arrow-down-new.png create mode 100644 lms/askbot/skins/mitx/media/images/vote-arrow-down-on-new.png create mode 100755 lms/askbot/skins/mitx/media/images/vote-arrow-down-on.png create mode 100755 lms/askbot/skins/mitx/media/images/vote-arrow-down.png create mode 100644 lms/askbot/skins/mitx/media/images/vote-arrow-up-new.png create mode 100644 lms/askbot/skins/mitx/media/images/vote-arrow-up-on-new.png create mode 100755 lms/askbot/skins/mitx/media/images/vote-arrow-up-on.png create mode 100755 lms/askbot/skins/mitx/media/images/vote-arrow-up.png create mode 100644 lms/askbot/skins/mitx/media/images/vote-background.png create mode 100755 lms/askbot/skins/mitx/media/images/vote-favorite-off.png create mode 100755 lms/askbot/skins/mitx/media/images/vote-favorite-on.png create mode 100644 lms/askbot/skins/mitx/media/images/wiki.png create mode 100644 lms/askbot/skins/mitx/media/images/youtube-sharing.png create mode 100644 lms/askbot/skins/mitx/media/style/auth.css create mode 100644 lms/askbot/skins/mitx/media/style/extra.css create mode 100644 lms/askbot/skins/mitx/media/style/jquery.autocomplete.css create mode 100644 lms/askbot/skins/mitx/media/style/lib_style.less create mode 100644 lms/askbot/skins/mitx/media/style/openid.css create mode 100644 lms/askbot/skins/mitx/media/style/prettify.css create mode 100644 lms/askbot/skins/mitx/media/style/style.css create mode 100644 lms/askbot/skins/mitx/media/style/style.less create mode 100644 lms/askbot/skins/mitx/templates/404.html create mode 100644 lms/askbot/skins/mitx/templates/404.jinja.html create mode 100644 lms/askbot/skins/mitx/templates/500.html create mode 100644 lms/askbot/skins/mitx/templates/500.jinja.html create mode 100644 lms/askbot/skins/mitx/templates/answer_edit.html create mode 100644 lms/askbot/skins/mitx/templates/ask.html create mode 100644 lms/askbot/skins/mitx/templates/badge.html create mode 100644 lms/askbot/skins/mitx/templates/badges.html create mode 100644 lms/askbot/skins/mitx/templates/base.html create mode 100644 lms/askbot/skins/mitx/templates/close.html create mode 100644 lms/askbot/skins/mitx/templates/course_navigation.jinja.html create mode 100644 lms/askbot/skins/mitx/templates/faq_static.html create mode 100644 lms/askbot/skins/mitx/templates/feedback.html create mode 100644 lms/askbot/skins/mitx/templates/feedback_email.txt create mode 100644 lms/askbot/skins/mitx/templates/help.html create mode 100644 lms/askbot/skins/mitx/templates/import_data.html create mode 100644 lms/askbot/skins/mitx/templates/instant_notification.html create mode 100644 lms/askbot/skins/mitx/templates/macros.html create mode 100644 lms/askbot/skins/mitx/templates/main_page.html create mode 100644 lms/askbot/skins/mitx/templates/main_page/content.html create mode 100644 lms/askbot/skins/mitx/templates/main_page/headline.html create mode 100644 lms/askbot/skins/mitx/templates/main_page/javascript.html create mode 100644 lms/askbot/skins/mitx/templates/main_page/nothing_found.html create mode 100644 lms/askbot/skins/mitx/templates/main_page/paginator.html create mode 100644 lms/askbot/skins/mitx/templates/main_page/questions_loop.html create mode 100644 lms/askbot/skins/mitx/templates/main_page/scope_filters.html create mode 100644 lms/askbot/skins/mitx/templates/main_page/sidebar.html create mode 100644 lms/askbot/skins/mitx/templates/main_page/tab_bar.html create mode 100644 lms/askbot/skins/mitx/templates/meta/bottom_scripts.html create mode 100644 lms/askbot/skins/mitx/templates/meta/editor_data.html create mode 100644 lms/askbot/skins/mitx/templates/meta/html_head_javascript.html create mode 100644 lms/askbot/skins/mitx/templates/meta/html_head_meta.html create mode 100644 lms/askbot/skins/mitx/templates/meta/html_head_stylesheets.html create mode 100644 lms/askbot/skins/mitx/templates/meta/mandatory_tags_js.html create mode 100644 lms/askbot/skins/mitx/templates/navigation.jinja.html create mode 100644 lms/askbot/skins/mitx/templates/question.html create mode 100644 lms/askbot/skins/mitx/templates/question/answer_card.html create mode 100644 lms/askbot/skins/mitx/templates/question/answer_tab_bar.html create mode 100644 lms/askbot/skins/mitx/templates/question/content.html create mode 100644 lms/askbot/skins/mitx/templates/question/javascript.html create mode 100644 lms/askbot/skins/mitx/templates/question/new_answer_form.html create mode 100644 lms/askbot/skins/mitx/templates/question/question_card.html create mode 100644 lms/askbot/skins/mitx/templates/question/sharing_prompt_phrase.html create mode 100644 lms/askbot/skins/mitx/templates/question/sidebar.html create mode 100644 lms/askbot/skins/mitx/templates/question/subscribe_by_email_prompt.html create mode 100644 lms/askbot/skins/mitx/templates/question_edit.html create mode 100644 lms/askbot/skins/mitx/templates/question_retag.html create mode 100644 lms/askbot/skins/mitx/templates/question_widget.html create mode 100644 lms/askbot/skins/mitx/templates/reopen.html create mode 100644 lms/askbot/skins/mitx/templates/revisions.html create mode 100644 lms/askbot/skins/mitx/templates/static_page.html create mode 100644 lms/askbot/skins/mitx/templates/subscribe_for_tags.html create mode 100644 lms/askbot/skins/mitx/templates/tags.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_edit.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_email_subscriptions.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_favorites.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_inbox.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_info.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_moderate.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_network.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_recent.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_reputation.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_stats.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_tabs.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/user_votes.html create mode 100644 lms/askbot/skins/mitx/templates/user_profile/users_questions.html create mode 100644 lms/askbot/skins/mitx/templates/users.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/answer_edit_tips.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/ask_button.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/ask_form.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/contributors.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/footer.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/header.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/logo.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/meta_nav.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/question_edit_tips.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/question_summary.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/scope_nav.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/secondary_header.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/system_messages.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/user_list.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/user_long_score_and_badge_summary.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/user_navigation.html create mode 100644 lms/askbot/skins/mitx/templates/widgets/user_score_and_badge_summary.html create mode 100644 lms/askbot/skins/utils.py create mode 100644 lms/envs/askbotsettings.py diff --git a/.gitignore b/.gitignore index 1dd3b77523..81d9a57d3c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ .AppleDouble database.sqlite courseware/static/js/mathjax/* +db.newaskbot +db.oldaskbot flushdb.sh build .coverage diff --git a/.gitmodules b/.gitmodules index e69de29bb2..72ec77d0e2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "askbot"] + path = askbot + url = git@github.com:MITx/askbot-devel.git diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000000..67ea339cd8 --- /dev/null +++ b/.hgignore @@ -0,0 +1,12 @@ +syntax: glob +*.pyc +*~ +*.scssc +*.swp +*.orig +*.DS_Store +database.sqlite +courseware/static/js/mathjax/* +db.newaskbot +db.oldaskbot +flushdb.sh diff --git a/askbot b/askbot new file mode 160000 index 0000000000..e56ae38084 --- /dev/null +++ b/askbot @@ -0,0 +1 @@ +Subproject commit e56ae380846f7c6cdaeacfc58880fab103540491 diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 0eded21df1..61c2537399 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -3,8 +3,6 @@ Models for Student Information Replication Notes -TODO: Update this to be consistent with reality (no portal servers, no more askbot) - In our live deployment, we intend to run in a scenario where there is a pool of Portal servers that hold the canoncial user information and that user information is replicated to slave Course server pools. Each Course has a set of diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 2b2e709bcb..dd0df2125a 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -388,7 +388,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates): entry_point = "xmodule.v1" module_class = XModule - # Attributes for inspection of the descriptor + # Attributes for inpsection of the descriptor stores_state = False # Indicates whether the xmodule state should be # stored in a database (independent of shared state) has_score = False # This indicates whether the xmodule is a problem-type. diff --git a/create-dev-env.sh b/create-dev-env.sh index e481d3fd5e..0d7e392313 100755 --- a/create-dev-env.sh +++ b/create-dev-env.sh @@ -72,6 +72,12 @@ clone_repos() { git clone git@github.com:MITx/mitx.git fi + if [[ ! -d "$BASE/mitx/askbot/.git" ]]; then + output "Cloning askbot as a submodule of mitx" + cd "$BASE/mitx" + git submodule update --init + fi + # By default, dev environments start with a copy of 6.002x cd "$BASE" mkdir -p "$BASE/data" @@ -328,6 +334,9 @@ pip install -r mitx/pre-requirements.txt output "Installing MITx requirements" cd mitx pip install -r requirements.txt +output "Installing askbot requirements" +pip install -r askbot/askbot_requirements.txt +pip install -r askbot/askbot_requirements_dev.txt mkdir "$BASE/log" || true mkdir "$BASE/db" || true diff --git a/doc/overview.md b/doc/overview.md index f64d12920d..36e22e16eb 100644 --- a/doc/overview.md +++ b/doc/overview.md @@ -27,7 +27,7 @@ You should be familiar with the following. If you're not, go read some docs... - CMS -- Course Management System. The instructor-facing parts of the system. Allows instructors to see and modify their course, add lectures, problems, reorder things, etc. - - Forums -- this is a ruby on rails service that runs on Heroku. Contributed by berkeley folks. The LMS has a wrapper lib that talks to it. + - Askbot -- the discussion forums. We have a custom fork of this project. We're also hoping to replace it with something better later. (e.g. need support for multiple classes, etc) - Data. In the data/ dir. There is currently a single `course.xml` file that describes an entire course. Speaking of which... diff --git a/install.txt b/install.txt index 801036af6b..37a6e50986 100644 --- a/install.txt +++ b/install.txt @@ -9,6 +9,7 @@ There is also a script "create-dev-env.sh" that automates these steps. mkdir ~/mitx_all cd ~/mitx_all git clone git@github.com:MITx/mitx.git + git clone git@github.com:MITx/askbot-devel hg clone ssh://hg-content@gp.mitx.mit.edu/data 2) Install OSX dependencies (Mac users only) @@ -48,6 +49,8 @@ There is also a script "create-dev-env.sh" that automates these steps. source ~/mitx_all/python/bin/activate cd ~/mitx_all + pip install -r askbot-devel/askbot_requirements.txt + pip install -r askbot-devel/askbot_requirements_dev.txt pip install -r mitx/pre-requirements.txt pip install -r mitx/requirements.txt diff --git a/lms/askbot/skins/README b/lms/askbot/skins/README new file mode 100644 index 0000000000..3fbc8c331e --- /dev/null +++ b/lms/askbot/skins/README @@ -0,0 +1,71 @@ +============================= +Customization of Askbot skins +============================= + +The default skin at the moment is in the development, however +it is already possible to start customizing your site without +incurring much maintenance overhead. + +Current status of templates +=========================== +The two busiest templates are - the "main" page and the "question" page, +the main page is more or less complete. "Question" page will be significantly +refactored in the near future. + +How skins work in Askbot +======================== + +The skins reside in up to two directories: + +* `askbot/skins` in the source code (contains any stock skins) +* directory pointed to by a ASKBOT_EXTRA_SKINS_DIR in your settings.py + with any other skins + +Currently, the skin is selected by the site administrator in the live settings. +Also, at the moment skin default is special - it serves any resources +absent in other skins. In a way - all other skins inherit from the "default". + +Templates and media are resolved in the following way: +* check in skin named as in settings.ASKBOT_DEFAULT_SKIN +* then skin named 'default' + +How to customize a skin +======================= + +There are three options: + +* edit custom css via the settings interface - good for small tweaks + (no need to directly log in to the server) +* create a new skin in separate files (need direct access to the server + files, more maintenance overhead) +* directly modify the "default" skin (as in the previous option - need + direct access to the server, less maintenance overhead, some + knowledge of git system is required) + +The first option only allows to modify css and add custom javascript. +The latter two options allow changing the templates as well. + +If you wish to follow the second option, create a directory named the same +way as the skin you are building and start adding files with the same names +and relative locations as those in the "default" skin. + +NO NEED TO CREATE ALL TEMPLATES/MEDIA FILES AT ONCE as your skin will inherit +pieces from the "default". + +The disadvantage of thil second approach is that you will be on your own maintaining +the synchrony of your template, stylesheet and the core code. + +Third approach is the best, but it requires (the most basic) use of +git source code management software. With git you will easily merge the updates +from the development repository. + +Structure of the skin directories +================================= +Todo. + +To simplify maintenance of the css as the skin is being developed, +populate css file `media/style/extra.css` with any rules that will +override those in the `media/style/style.css` file. If you do that + +media does not have to be composed of files named the same way as in default skin +whatever media you link to from your templates - will be in operation diff --git a/lms/askbot/skins/__init__.py b/lms/askbot/skins/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/askbot/skins/common/media/images/anon.png b/lms/askbot/skins/common/media/images/anon.png new file mode 100644 index 0000000000000000000000000000000000000000..a2041590216dc4ed8f86195dcdfc66718bb47c39 GIT binary patch literal 687 zcmV;g0#N;lP)XC>R=F^NKk_!2rAkH3rS@uh-eipOd3a}2O7i~W9ICor4Muuv$VD2f=5$1#`7 z;b1V(Vb<$4@;v{01J!C(N8D^S(QdbY!&xjAEEEbltSrmOvMjv;o6Uy%{hknxMkBOZ zt%ij4dR=Ei6h(RisZ>fwCP~uWC3QF)cs`#=PqA1u#2e0mbUIDy)9D1=Zr2D7008^_ z9#mB&&yh&P5N|jKI-L$or&Hj09u9{C9FNDl1^@sUh9ULka=D8)b7$|S+wI2d^-8v& zTrU594m_F+!(gk`BI38(4MU;O>kQOtHQk|YG#ZceJx9D$D(S=(ML~|^USptAsp!O2 zRYjlA_h|ofmt9d5^m@H!IOxSCN%|)4@p#Pg(23jacAU*-M0`9R(}>d>$Y!&;w~xhQ zR6Nw5WQjzA)WhKrWLc)-p*G<6`$;{Y&uMAs4Y*t`Qm>9R#xxjYc652mk=U zbUKB}Wb#@A&x(KVzu-@Ne0-3DsA(ExG8t+URQGqWSdba8TrR1n>D#y9?FRnef=}bN VTfTAXK@I=_002ovPDHLkV1g0tCr|(Y literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/bigbutton.png b/lms/askbot/skins/common/media/images/bigbutton.png new file mode 100644 index 0000000000000000000000000000000000000000..2a7c0f05858e26e279fe52242d4d2acf7b8926c8 GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^Y(N~s0U{T6xorVbEX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4&$zvn9~6EtZ`^i_Kc&@Uqr$fxhx0DITbMW_t+i0)Y0gW5 z111hvt|`7MYYnLJpblKlqWG(SPyyip4(M3kDZRAec-nq>lf8G9ZE1RYT_qu4I z8CFZ)M+vtSM*8|Ev;Tc?xZywBZMoICUz@caF#RjdJJeUgGZp9_22WQ%mvv4FO#q;5 BUts_M literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/bigbuttonhover.png b/lms/askbot/skins/common/media/images/bigbuttonhover.png new file mode 100644 index 0000000000000000000000000000000000000000..cf4bacca69ffc4736c0c1ec25795647bf7bec72e GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^Y(T8d0V1RS{agT~Sc;uILpXq-h9ji|$mcBZh%5%G zzYfBTP8zc-fP#`Gt`Q}{`DrEPiAAXl<>lpinR(g8$%zH2dih1^v)|cB0TpF>x;Tbd z^sb$7k+;D>!0F$d~L-N zvOJSrlNlb$zAV1{^xDV5&|PgTe~DWM4f!;x4L literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/blue-up-arrow-h18px.png b/lms/askbot/skins/common/media/images/blue-up-arrow-h18px.png new file mode 100755 index 0000000000000000000000000000000000000000..e1f29e86334ce72d2d28989a133571d7bf53a94e GIT binary patch literal 593 zcmV-X0{6iqad+$V|KR(Q{q;Ok;ua!jjQtuI1z7)O( zySE1i5k-TnUMhU^!Fth&L`=(4hnCB&mew0>atrF^~@9muPo$nmJ5BNLwb>CS+ z;4aaxR`*WD1Hfmk^?b25_^vW#;gMP0a=;-lw8uKYF+TucXlk2wNsBS@}pFD ztaavBN$@-;DyO5Dqi0!d9a~=U7;O0Ayiuq>#Jyi<@Hb@35~1ra$v5dmgDzqB>^+=uOM7b5+>fY}+kwy1R`cV__*wFuQ!Bi@#VE~BJd f7UnWh{5Sjn&bP0xmAf5500000NkvXXu0mjfU!M$- literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/box-arrow.gif b/lms/askbot/skins/common/media/images/box-arrow.gif new file mode 100755 index 0000000000000000000000000000000000000000..89dcf5b3dd40fac0e6afb0b1a7ff899a059f923f GIT binary patch literal 69 zcmZ?wbhEHbWM@!dXkcJ?_wL=lfBzJJvM@6+Ff!;c00Bsbfk~#Pf92`7{EJz*0#eqW WoE7iinDij`X5pe+YFj-S8LR>Nkr%B1 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/bullet_green.gif b/lms/askbot/skins/common/media/images/bullet_green.gif new file mode 100755 index 0000000000000000000000000000000000000000..fa530910f9dc11fadaa2314f72bd98f29df39daf GIT binary patch literal 64 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 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/close-small-dark.png b/lms/askbot/skins/common/media/images/close-small-dark.png new file mode 100755 index 0000000000000000000000000000000000000000..280c1fc74e47c0e7d1c68d6f356eb22eeba7a2de GIT binary patch literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^d?3ui3?$#C89V|~1_3@Hu3NY7ShRS_jG43c?mKYe z;M6!dxb8-^+kP+MX_sAsp9}6ArM>V3CjzZ7^A^)I5Q~ t!yzC*Q0L$Zl?Eqn$0Lca5`Jx9W=NmTc;}pLNdZtBgQu&X%Q~loCIHsxKFr1QCT0q{1{MYeDE)e-c@Ne1ia=5ZCX&{{Hy&_tW!N_ZKfc zpObUM#pTWAs~3xk4w#ty`t$F>lBGXhy}DLgdpr1Q#zqRd1{MYe;g^zIfZEtgJbhi+Z*Z~+S_nO6;N1-r z;wE)e-c@Ne1ia=5Z4(qXDwR1Wb5`Fd-okU zaq{%lYd1c8`uyw9U!Xizkhgxu4WI-^NswRge+Xc>*St3pD5mS_;uunKD_NtRfsJRE z&YnFcvL4forc1XpAK{H{IrvG{>z#&4h|Ds+W&FE$GCZx(SSr0JY6i#_Pgg&ebxsLQ E08gA+X#fBK literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/contributorsback.png b/lms/askbot/skins/common/media/images/contributorsback.png new file mode 100644 index 0000000000000000000000000000000000000000..dd72838396a66be23b99c4f3dac2e22a07e5f124 GIT binary patch literal 714 zcmV;*0yX`KP)!S<-E*`}w|bpTgd0gjDDs@DtguF7w88g%!Z0L=B09Y;K@jZ4saru6dCp>< zVU79Hqm-i6Y11DL@csXA`EJFRg~QS91}(n0N%BMX0OwefOh4a zuZ{ZY&JVMm$ut$AqP%(BZqbmtt!x}dI0Os(<500io)+Vgh%eFmf9m${7ASm(Ca9_sujWopgj z`5Cv9$Xa{Q>-!%4;oxY#Fp3C2jRi%Xg9rj3Ab>)Uw*lD#JSdsa%YuAw>O?u&d)Z_qy<>haI-lg*85czODZG5RFG(T1w5NYdmisI$vV z^H$N#kv{3e0!r~?{6LbXdvkSJ?Nx&~i7%^tB7&}J%Cbc3nkb4%(gbHMv)Adt7~l7a zk{HkPkfuS`dJiZJL&m=zFY6MNQp9OOeBiYhqggHX+ZX2?WnLW3dG~OSQWq5D@6;cT zKoqOx^5hub54aycY&*}ZLn);g+>MBmm~1-7xx*dAyV7=>;oXSMF2HS|-qmWiNn0(- wqM*ogthQKd@H~$o42j~9POnE0d~RIeAJ(%PeH15L(*OVf07*qoM6N<$g6QE>TL1t6 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/dash.gif b/lms/askbot/skins/common/media/images/dash.gif new file mode 100755 index 0000000000000000000000000000000000000000..d1ddc507fe00bd654fce38ac8552793aa18c9966 GIT binary patch literal 44 wcmZ?wbhEHbWMN=rXkcLY|NsBAY10&cvM{hS{AbW%00NK<0~50k11p0y023w&(*OVf literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/dialog-warning-off.png b/lms/askbot/skins/common/media/images/dialog-warning-off.png new file mode 100644 index 0000000000000000000000000000000000000000..258e4d86c0d6da8f214fc49eb311fb88814d454e GIT binary patch literal 419 zcmV;U0bKrxP)Px#0%A)?L;(MXkIcUS000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igk;0s{aA zV|5a8|Zt%ng-9WwT z{<07*xL_f4me>5I8w|POhC{l6HvjvpVmPNqk8>77XZ1TbBEOxeG^C0&(x{>#iHf%W z05Z1B9t_y9VW0=gl<@_i9W!4?4T2O>5Y$kInb%GPsL04N`-nqe!-gP^J}gs4DnIvo z*;AKW`rL8NHFxy6WtZ8L_5f(iJ(f7(fqVLFvrV6S9yno%?ond~29hkJ1%W`2K_=>2 zC_|DMh_k(act!_|f(Q`2;RT1CJcf57H@-u>GiK?z_9dUVmXGlC`VB}Lc;@7(Wzqlu N002ovPDHLkV1fy(s`3B; literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/dialog-warning.png b/lms/askbot/skins/common/media/images/dialog-warning.png new file mode 100644 index 0000000000000000000000000000000000000000..a9e4ff3991cb0ad2a99cc25e2d13c35e52c680c8 GIT binary patch literal 603 zcmV-h0;K(kP)J2V5SdEtMnl9PhCxc2Fa&}~V}o=Tx)35=bmuS|@d;cT zbY;Loz(q+`1+A}82tI&NTm%Y*7F^UoNilyn))L6H_Q1W1%i%j`bA(=?F!-5EVN!MFB_9*XAp~_l%n771CUH65fQk#;c9J-59jBDOQ<*CorDm33|CLV znm<0~VYP}<3Z)daS`C19yN!q-BK$Z#V4_~XOaDE<2tY)*Zne1F+TuNYlS3Fj19`yz=_l|J pl>P}02k>Sh;xD3f4Z|KZe*u@?eFz-1;S&G=002ovPDHLkV1mMw_G175 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/djangomade124x25_grey.gif b/lms/askbot/skins/common/media/images/djangomade124x25_grey.gif new file mode 100755 index 0000000000000000000000000000000000000000..d34bb311615b1378a672a828c7a7916490cd882b GIT binary patch literal 2035 zcmcJOSyz(>0zk8Y5oHk(5d(s_FbFhAqNt4L16&YNCyID%iyA#v9i~-V)T#$7C{?Rq zJuXmNkX;Zk3t36XzAs-E$W8(YK|~TDELE}gFw-9~_w_#9r@MFGo{ViLN+D5@_t6k? zub_>}Y9`ZKyBS?Q+zuw^CB28p=9m6l=^h>P&(87W>M^fRs?m>(j#(W;9ErkUvGq!o zR>z=aV9@3qVhSZRzIguQM;cEA4PH>{`)!UPyVLdOr%&oW6O}KX^iRvR21>7B_K!bg zpuy#NEl_HdeI}XK;2M6dGg;OB7NH7sx`ycjiOOJXqcVp_-!Mh8P9}%M;d7*l9dn^o^Yc8JiXo6#2L{zfi^DzK#p>;|ITX6Se!BxS zS=nOw+`D&V4qvF$^jqyzo~TDG>y;|lVi{d1<;qn?yF=A))|stRQ2&y_<|)*2U7tjw zV~V8YUSW?!VYCi#Byx?(%9SZ)x_-xyo5JBc-5#D?H9GEn-bvP4>_a1?Mw^2xQ-LN+ zBe7ec(5U;2FS?oT(KkAyXb zH`<+SiGsov4vvg6M6xClb-?9rrO;i&BX+lk$mV$`rZ`gN>v8YfiAm6G<101ow4OKP zUbVqQ?BTjSuXPq%Yd5pISJ261cQQEjo#Yk@y{?1ASAq*s-`cubDGW-lki_O|%{D4etT9^j7JEC5N#je{GL^~hpbMnVVNY!b z@p%VHZ?SpC-cHTTm>h#mB$`;Qbq9D5VJ`EU_H44c4@Fu%*g{g$2m} zjre&46atBcB>!Fb`w0kg0fIg2-$QN?-a;-U9a~C|Sx_l#$$ zbyds`pGKe0LOZIh;hTXQH~7?2Jo0 zAKTtHp2*){xDdLxDX)5Bq$d9S)rRYTt~R1BZu8%arq9oL7S0X{cVZ83Md|CWuc5Ww zgHJrm4n{311;Egli>`(nMilUQIqb4)5Q?cl#}=HzL03S&F5J5ZvF3I&1iAmphxib7 z=~(%y)r?(&s;zbaOiB%VcyqB)a3LW*6`!N2T5$*vtPDAu4Lr&i{1t{hUIbJv$v&)4 zc!CfB!4I<5zFha<24-Hg2z@rDMTRf&8K%(xp|Jl;8yyb7(z29dO~7 z0?>d2d_u(#9&?g)BczhRg5UXUk{R}HU+hrKk846$t{)2uvfo5}`X_+FT_r3*q@K(Q zIFy3^Nem&SdwmdC?f@$3a69--AQ=DME>cj0|A&0oy86<nZyuyLVuwr{IGXQux+DRXY@2U6(<)cxduLt8LR@#Wa`O=WAre>^=26DJK^^h~Zenh_Qpc_eE|a>K3SC}>hGS%%baA>Rzm6~eB6s0c`oTN0US zfK?FULsI-Xr|IOSn-WhZ6RhFiUYWj6SlS#V&y{WdZMyPn->wo=`JS}ytYDdcZ9-8+ z#g^jc^%g~Q?0bcL=f~+^%9ggo{bjwMjFH13QK!~qEei?la)%F0L$*X*t*SoddGxEM zWy4mba{slN#|^%bl*nZdHaOy*)Xwd`S5&UbXN;^SSGFdf*{A-}K2y!otWP}=A9U0{ sv!XO(598i6?6_>^sSNjGbB$7Sdi(Nfq+XETwht`+l3E>&L_&7_7sn(m+5i9m literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/dot-g.gif b/lms/askbot/skins/common/media/images/dot-g.gif new file mode 100755 index 0000000000000000000000000000000000000000..5d6bb28e56377b0eeb80899222aa3290ec2a3a95 GIT binary patch literal 61 zcmZ?wbhEHb1;oomohA-cw8MruKF$f7>W?=Zo!hivofRLHNli}wd9fr@0yBS{oImf`r!T=CJ zObo<=KYvmfSa>QJq}1IQ{xNkh{QGMS5(Wq$!Uiw`?fv_wh=H9yhCxi_BLl18R|YAy zzYGlQ%?$tlTLT0TVFP~usbb(1@&ju6$nf>=e}=c;7#aR?|7H-C1iI?46+i&7;IWyL z;n$xU20l?+1~CPo=06M!Uw{_>X8zCc{xt`~yO&=Xm{>Lg1Q0F*xPh8m83e@j8ARki zGJO8U!0_!K1H&(%gWtd9WO)AQ6T^RoY6eEe8vp@>#en~UKxelz2uZ60@n?olzZn_6 z{|9LXI{!5n!^+`mIW?f>FASf4GBSJvI{OzR z1H&<(HAFtjse0v3qvG*Lj!k<6yW@2D$#+OV00*L7y zBZtO!W_$i{sO4^#Rs-O?>`EBV-VT&i$$;vm@)9CGk^eM*?-}6i0Jdne+|V2 zxHvw3_{Z?+(@RDM_T7w3%nJ!=1_6KoVp+N8l7OGV`-jpW1oz1C@m=Tm^ZxZW7V#J0 oz$F3z1Q6r@|Nj{%0RRC80Nya-9-e3d82|tP07*qoM6N<$g1t^w0{{R3 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/expander-arrow-hide.gif b/lms/askbot/skins/common/media/images/expander-arrow-hide.gif new file mode 100755 index 0000000000000000000000000000000000000000..feb6a6187c2742ea8e516244f139e7946ed757fb GIT binary patch literal 126 zcmZ?wbhEHb6k!l$Xl7tYOG_&#C^&ZP*n$NM9zTBE+}u2E+O#ug&iw!Xp8*?C{K>-1 zz`((v1Cj)p!N8&|aME-2UW?afcmGe&$YV~Jr(qS6`f6R+>scREmz$PdzB_la&4mZ% LT2lR!I2fz}g2F4- literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/expander-arrow-show.gif b/lms/askbot/skins/common/media/images/expander-arrow-show.gif new file mode 100755 index 0000000000000000000000000000000000000000..6825c56ee42f0184d66c0fe954d7fc4b6f05e850 GIT binary patch literal 135 zcmZ?wbhEHb6k!l$Xl7tYOG|5RZr;3k^RZ*c9zTA(V8Mcdf`WVZ?*0G&p8*?C{K>-1 zz`((v1Cj)p!N6i7aME-2(x+KmpRPH4k6ma{w6t6)bw%IW-N%f4X5Wr&{_cHgU%a-) Votr)GNqpOrH0{qn(_mt-1^|9hF$Dks literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/favicon.gif b/lms/askbot/skins/common/media/images/favicon.gif new file mode 100644 index 0000000000000000000000000000000000000000..d106e5da966cf18cc4fae1b00f72eeaf24ffdba4 GIT binary patch literal 78 zcmZ?wbhEHb6krfwn8*ME|NsA=X>1H+=zw@2aRw&!9=SHHla;rcwSz3*o+&L{Sv!64 c)OnoqV!K+;J)5NUt?gsS{@LgDvocr%0B>L%O8@`> literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/favicon.ico b/lms/askbot/skins/common/media/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..67203a5c9540190523f485de03bd9f39ff845dc7 GIT binary patch literal 14846 zcmeI1u}&L75Qc|@M9?HqL_$hqxuQrBX_eFzyaI0!t#|{vh|*QcNc#rJuV4zwNZc#9 zPvu9*<(oU!cRp@m7as=1p82@l|7K_AX4mV(h-}HOj7EE$x8?5}k>4V6aB$P#68ZU7 zWN)wTAJVt)1be}LM=o|me!W-wy8nmv_m65{_fN6?_sQFjIxe*5-GaJ^=x)oYjx{c_N$~? z`Q<*77FUnXd{>L$34iH2=dB}OVPot3W{Fd~xo zV1R+-gANWX7{D2k(Rn$rV1NMRg8~jL7$7M5po0So21tZU=-|MD0XpP^4h}3Bz<_+v z!GQ$>3?d(N(3b@RI3gdi0|yoi;GBHW!GQ$>1SB7HaA3gzNiYN*99S?wgM84zfdvCx zBp>uD6Lek%7(hN$3LIE4fD`gT2L~1m;Fx^S!GQ$>1S21GaA3gz3E&DH99S^GFY-aJ zs)Ej0UtAy`!hkMIU4pvYFeV>#(B+{^L8l!DI+Z$YIz>7iT6#L@+@6+D%cZ5!GUzSq z?dh#(+M1%KqoFm7y42xnsS^8dZ~Yu=zL&_KWBGF|e~#t9wp{&M|u{j~zI>t$%Wy#Dg~%j@sy-+%snxXE+R zR{|TA^m~ft|C*y<>`m3AZ*Pr_9Wyq@CKNAbY>Z9zzxw;z6G0e;pV^%lqKR4wqF7ss6h-_=6`>~)a?y)7 zLD1qs4ceYfV0F_Th!3>2TLJ3_%)kFPNr6Oppgg@0Pr$BoEu#(xlymUw1x$}X!~TmS zjd}^AmPdtRFf$6p@33Y)guB2D!@54`J_7w`p!+Diy8@M55Q0(&I}6bsRILai3)WAl z&VgIjDP<19&a=AQ!5c7o8ictF0*Ho%D1E~_FofEBNtfc3U~U{rX_$Tji<5BdIYfIQ zvIVO1Er`+Tv|GS%3W#POLNVBugxwdQ?*w=b%)SMsVC!M+bwjlr_bqn)e81sG? z;^lG3r}Uacm);hegZEN1E zRUu#QK>iansxWp{JI8vp&0T8yIfY+4gs_RSEYUh$*&qlx#HX#q6+zpro3TttG> zC(y$BWA$9YT%nahEEG#B_i=>T&#zD&z6A<_)pLqJbnnC&4^s~=00000NkvXXu0mjf DijN~n literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ad.gif b/lms/askbot/skins/common/media/images/flags/ad.gif new file mode 100755 index 0000000000000000000000000000000000000000..57b499733f149e68f5b20495eac12252c14c6fed GIT binary patch literal 371 zcmV-(0gV1fNk%w1VGsZd0M!5h)6)R7#Qm$ zB&f9J_V!($tK;oCIQ#o}?d>T0`)!t_;nc5ru)FHewutjcNcHtl$p8S!x>NI3P38aq zUUJmBrF!#PT9l#P?(ReFLPFR80Hd$vS8UWRNWPJu-?qQ(>C<7Js_4pNny+2sd`ao z&-3wbA^8LV00000EC2ui01yBR000J%z!hgm5*Uv(+Gfc)IVprF;qeFrJf2N+F-kom zzZm0UvDiGK=BS~IR4S7k*=Ep)NEEgt(hHze19UAGB0dy45+)22C@wcF1}zF9B0LL; z1uz{hI0!P52@V)93nm2x7aa(z5DEzi1{gadr!@mC85s}|uL&&}C2SuB3}XWzAP$kg RI|t4OGtmIk7}eE506T1-p$-55 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ae.gif b/lms/askbot/skins/common/media/images/flags/ae.gif new file mode 100755 index 0000000000000000000000000000000000000000..78d15b67dcba8937b13dd3a4514037af411e38b0 GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5h{#{)FXaLsM)&N)l07n4){QUF)02US&L_|bQwM{!a zJ1#CRQnykxG&K7~MF4vM{#jZ7dwc$OclrPT?f?J)bO8HGNevAR^78Wg`}@(+(fm(O z`aM1TR8;{7Oq@ zz-9buYW-7FAt514OG{zCU`Vq_{&I8u008sz^ZNSw`1ttw`T6(v_w@Ai_4W1k_V)h( z00000A^8LV00000EC2ui01yBR000JtK%dZ8dps(YfO5bj_#zRHBt&hsWTg=!;f$OB z5N%?XN|`>NnbQOA6bhMJ4yWt!dV-n+(jmd+bap#Dc{DE@EOB#nJUccj8Z|T<77l=n zju<-{6E2k;i-I;77&aiC4jdc}3nDNu2L~4y3qJ-YCo3H&F$f3>3Jt8a2E`Nn2%x z*e)ma?d|GgUfMJ*;#5wpU2^H5pXC4me0OEedwMwl0Kvb2w=Fw?004up-P^%bgn)75 znVGe-Ym9$Uj(dB5Z#G7R%u$crhciH4k<0CYeBZx4Wny4moZreMBhqK1`Xu~6I^ zV1mz)AV^<}%Vie>(<}uCucDHiBm%+1HK+W={*92I(D{i^@+;so| zRIAVK01}EvipKwjichuW0048F%I#yL(PX{m{gRegoX&i(*{xWCU7*mff}Com)BpfG zGK0ndBboN*r#gkiP?gI^k;y)Z$NId%)XLm&snlkm&w;bs?}3Qrlb!#?%J_(o@AUcq zt+48#s{g94G=IbS+~ro4$6B7xk(<4+kg5PKqAh^LT86lYsLr0T(npcU-;|;E;+Dy5 ziPBVUA^8LV00000EC2ui01yBR000J#K*Hg0xf~k4PKUvSI1#bMNU%^vEEYpaqme*< zFN$NlLYXR>EJmUrkVX_r%T=2NScL*>rZ^OIJSh?A6Z8;wq83-~oAR!(A z2_69oHYXAoGZls*A`_+$AR8SPprQjIq#_RwHv_5`p`#5VA21vo2qCHlT3w&HAg?LB(9j#x(?I|`wXLLC literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/al.gif b/lms/askbot/skins/common/media/images/flags/al.gif new file mode 100755 index 0000000000000000000000000000000000000000..c44fe0a0aec913b8667d8d4f97acb82bb5b33db5 GIT binary patch literal 370 zcmZ?wbhEHb6kyz3*}0WNQQnvVdvF@dpO9#693UzcW`aWZ|>R$Cbv9v>Zj zRafpT1H&yhiwBXux0xBPnW*0JvAA zyIK1a6Z5xF`pJP4pI&E-JAhTIwFxZ(2F);`!Bq4Wte13okgB~V^5PN-pf{h*p zAteG86(=ezEhsMpAT$_0Jer-Kp`)c3rxy$`FghC>92^n5A|eY59}El+!~wgvzV&T?|aJ3FiQL`3`h z`_}*fy1U{404(+O_3Z!vAt9Xr0Cq%0u~byK{7g*uFfjHiD)l5J`9D8TPqy~<_WMUi z;o;%v=jYws-QVBe<>lq$Dk%YNlCM|w%ym)?La`QHa4b+iq7-%^Y{1n`1ttC%gg%u`ttJf`T6<&{{Hdt z@&Et-A^8LV00000EC2ui01yBR000J!K%Y=}dKe#_4k*O=_#~nZLK!| literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ao.gif b/lms/askbot/skins/common/media/images/flags/ao.gif new file mode 100644 index 0000000000000000000000000000000000000000..8c854fa1084eb333de9df427f96983a28cf4e35c GIT binary patch literal 244 zcmZ?wbhEHb6ky+^Ctb=TzxAm zD>E~*kIgX~pw=%k)gc|Ao`~`SG%K%Jl$* z-r4N-Dzo>~)6@R`{sEio;qv?T_VyE}@Al(K)#>w#)Ax+l{F&PMqu%_G)%W`P`sMci z^YioH@%e+!^Z<D}-6A+PhVGu49(E0Y`USYuTe9rimz0Ujl`@h=fpTgCg+xq(Jb)Mb)3#0Az_4WMx z{PgtnA^8LV00000EC2ui01yBR000Jyz?WfY2pItY1_Py%s0^uSYCy%5cDc)H^#~0> z;6T7)IUHiNMCH+PZQi31q|-T(77G&2&ge>OT369R@m9Ep65 z8VE8n2!|XTKMgl9a&&fh6sf5-3_E6M39$sTGcySa3?*4x85I>Q9v%h53II$JJP*nu MA|D?L&>uknJCLTx^#A|> literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/as.gif b/lms/askbot/skins/common/media/images/flags/as.gif new file mode 100755 index 0000000000000000000000000000000000000000..d776ec2711bcd6ff8f0451188b0d3c739c309e70 GIT binary patch literal 365 zcmV-z0h0blNk%w1VGsZd0M!5hRaEwDEp<;cvh zmg&=GczFE-1N~@c`kMd%Y#p=i+=i}ivibS>_Vx7WjD6^Ue7Tf#+fG92l$7$Pp!)If z0RZgw%*BU?{gY2F>dj8UqI{sBo&W%!m2%IZQ?T#W%Cmn}&{}ieU|-a1ec)YD`Q+Tb z004VpPuE2}qKtt60O91|;+lr(@aV!rLGuFu0DJ%fyH|#HRm|hs(;*@M@Z_)f@#z2n z`4JKSA^8LV00000EC2ui01yBR000Jxz};{7Yxs`H(XG|`1)fiX)Ii8&G#!|e!F4z! zs)6NTQe0eH21#YHXnGUH=@Sw;32hB)!k1s@2oM==8+?2b6bUi{bsRGi76TO>6cZFY z5eXqWB0dHV0691*D-|Xb3m_m7ody7}H8l_gFBlaG2{fG^9$j9qB`7Nc2c1V8$jC9v LF(WoN4?zGsMO>ji literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/at.gif b/lms/askbot/skins/common/media/images/flags/at.gif new file mode 100755 index 0000000000000000000000000000000000000000..87e1217365c869c8bf2fa6a35cd597108dbe47c5 GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5h`uh6#`1tGq0P_F<`ZYEEqN4V4a`#J1_V)JrL`3v6 zGxFNCd z0RI30A^8LV00000EC2ui01yBR000Jtz@PBvEBa2zfl?s&HJ+0Rj=?aEP&Ce=+0bZe zF&it9SP&GXEujEwK_IJ7NOm)nGM`5;mGba-F8}}m0T&lN4j_AefP#bwhKP!cfr9}E z4i_7UGdTqj5pi>McNiElDkmmoW+XZ;6%``07O7V>9a<6+6ch>y78W~715Cyb$SNv3 H%s~J<(#ocW literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/au.gif b/lms/askbot/skins/common/media/images/flags/au.gif new file mode 100755 index 0000000000000000000000000000000000000000..5269c6a0e0a2c5ff0554266d9fdb854c5e8c2807 GIT binary patch literal 378 zcmV-=0fqiYNk%w1VGsZd0M!5hthUorYNpAFmH;h|$9IETa;%q+vf+)8=jrf|D@Z|H zod5tb002@WL5j)H;s5{=<7sHLz0`J#yGmoCo2knH0%eb#!f1W6HBp!Grld`4sybGg zi*}uDfwPsQ#=67WMPHtAg}5+Hlu2TrK3SXAW@_hgZ|Ka?-NVdvJ!zq$!lkm%e~Gu{ z-R7UU+uf|Qvx1nUkgSJ)r?+v3=()aYMRnfc>)>y2ws2+)GW3#LM5@q^ivc z3EsHAA^8LV00000EC2ui01yBR000J;K!R+LNDNZ5ijpy6EEc<5>(xW)0Fuy!=U5dM zz5@;dG2Kc9&Ex{W1QrQe28YWWU;)a6gaLFGEGH@g0xmx>6dxZL77-B(H3kPCM3J(k+1vUs2B&Z?{ARY}Qx>_DQ Yq5-4|5jGYZx(rTFPY>D;4ckEgI~~rLTmS$7 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/aw.gif b/lms/askbot/skins/common/media/images/flags/aw.gif new file mode 100755 index 0000000000000000000000000000000000000000..27fdb4d13906897b572573cf0364bb72d800054b GIT binary patch literal 365 zcmV-z0h0blNk%w1VGsZd0M!5hVy@I}wAfRm&H!$=eY@L?#Ns-Z#*@h7^4~=A+$(ao z+WhWz06LcW*Z@PD$p9&oS*g-$v)5g!(faFb`{`8xSE~8uOZMVC0DQavW3K>8n)&Ht z4Ti$^=34gSQsQoIPNB?(!QOVd+upGNg1_EXrq4;9%K%uj0CTkyip2m(sT+~W^x{gM z#NB4F)enlq06n7UiHUr_;Bd9rXs^>~vDg3~h5PDU27$ly-!xdL&>@k>Tc^(eGL-k@ zLtd-XA^8LV00000EC2ui01yBR000Jxz+GyU;&5;n3<@>KWGb0gD|V&_xBzzL_Kr{{KP*w!!KXxD{01P+XD|HEL{k zjt)FM4GT9E6c!df7Z*7Rs0bqiB$aC=m<|TB0ydo!6B`>H9UdMmEC|A@87C(dDk?BA L3_TCe&p`k?K;@jA literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ax.gif b/lms/askbot/skins/common/media/images/flags/ax.gif new file mode 100755 index 0000000000000000000000000000000000000000..0ceb6849f49d128216572e9fa7d9b6318090c31b GIT binary patch literal 376 zcmV-;0f+uaNk%w1VGsZd0M!5h?7K+=N~&j+%0+m&{TK{}s@VA2hVsK$H*K~jXt|NH z*!$9ZbezokXJve&((W`Y_FGi|B$ogHY}Rc6>$pGw08Ri6i#&3*p1k7uuoGU5$|z#4 z05+msio*PRbWwx8{gjVZg~j`IZy#d4OMbrm+m-ZEN$tT}^TuWQ#Bcb`Z1>`#>&s@^ z003f-#r)x&X_C?H(Rm(Qt;<;e^k`r3(rfm*VENB=?Mg!PNkdP8!|Aa!@k2cHz9{g% zPS1@1A^8LV00000EC2ui01yBR000J+z{F_SBV+;r4kr?^I1`xY3RF@Q3JwQDAdrkO zSA%E4fC)4q9Z4gBhDEontMefZI+EVYU|2L#DkujBJ^&Oa69gb0A`J}?5GFZ150yAA z86^uhDiAh8Z0J0uLubnGBOtx99ax8G$$0b#*@te literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/az.gif b/lms/askbot/skins/common/media/images/flags/az.gif new file mode 100755 index 0000000000000000000000000000000000000000..d771618498ded597d5d95979e455fa797e77c0cc GIT binary patch literal 370 zcmV-&0ge7gNk%w1VGsZd0M!5hG_KcFw{-xD#p8>NNwwNmx!VAJ0NiwR0Biu&Vqwip zOaM;+E~Zu5Yiu^ETmW~t(_38SmzV%D07tWGlK=qTdwV^wV#`ED^V-|$xVX?#Q2=DD zSH9tA!QLC9M*vu;a>n9b!Q*<$8VuqG3ax&5B2k|~b7DarE_DPRAqW#ad>9cw5)Cyk1OyBqhCC;V zAsIZ4kpK)Ih8>tVHVYdRF#s~32o@U=A*2fn9u=so3#}U!23?pSJP-}DtrkyEPzcHn J2M)?X06XGIopAsF literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/bb.gif b/lms/askbot/skins/common/media/images/flags/bb.gif new file mode 100755 index 0000000000000000000000000000000000000000..b7d08e57e3686b1fcec0bf4668fd99ffa8bcedb2 GIT binary patch literal 368 zcmV-$0gwJiNk%w1VGsZd0M!5h001id$12hmiRI literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/bd.gif b/lms/askbot/skins/common/media/images/flags/bd.gif new file mode 100755 index 0000000000000000000000000000000000000000..0fd27ecabe8d3014611684d8d81a1c6a8529fce3 GIT binary patch literal 361 zcmZ?wbhEHb6kyB6foGBf_Bm?D%#Qiu>wVkbDPVFy-ika1 zX9gfp{K*2M7<51c$WIJxZ4UD_jU#(5bnG@?xbvhkx3Yz_p}ajOznr?2L~$dpks%9PyRABpNuLt0 zm@x~7tUWs;4}+)%_iQmeRSsc=MIsFHY(l1Ubk+(BGcAg*v01u>ErV&V7pu>y)2AI7 FtO3oAcfkMv literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/be.gif b/lms/askbot/skins/common/media/images/flags/be.gif new file mode 100755 index 0000000000000000000000000000000000000000..ae09bfbe149911d7877616273c87935511426dbc GIT binary patch literal 359 zcmV-t0hsb-^(-t(N=ow_9P#e}`}=eD?6 z@&Eu-R8;%>M_5@|_xCUM_9{9$I{f@h_4Oo0MMeGT0QvJh-_Zc{?K$}JHsRv{)!_j4 zH#bjDPyPK-`S$?N+W`0XF!|^JKtDhG_F(+{W%TVI(cb{u-2kNk0Q2)e_Vz;Y@ih1G zFaQ7mA^8LV00000EC2ui01yBR000Jrz@JcfFqvks8E44}{2ovS0s(>8YJ;r6`Jhlf zn%Bs4Ikf_g7Fa+1{qEacm)!07%~Zffld)F1tmLtAR!3` z1{E!iku(k%l_WQr6#xo96D2evJ)sQ^6&o9-sB9V%5u0s6) z0RI30A^8LV00000EC2ui01yBR000Jqz@PBvD;5z*WFqlfI2VIKr2>KQL;wnNVXfZ4HdccqdBJh?EiD(F7ySJE-0a-*78Xf) z6z22hJghuTxlQu&@=LW#?(XjD_UiguTl{2X9HSg9sxA8Z`uF$uYLh+rQd8>c>iRuB za+*i>_V(W1-s}JXC88zuJ3I3L0ObGxVT&^JG&JM!<3O`O@eK`9xKaRw0015UE2S&o z002g_Mp(L7AfF)M?cXq`Ft^jU(dW_l0000&0QfXCJFq(7@8IA^8LV00000EC2ui01yBR000JsK%Y?PC_;`JO=CpZEXdlVD~0yZ`T z92^)JBqRf$3ZWz`H4^|EAvXmDIywmnC@2Ljr4kYu834K=AtfcNJR-s$#Ka@VEiF9D GK>#~f5SPFJ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/bh.gif b/lms/askbot/skins/common/media/images/flags/bh.gif new file mode 100755 index 0000000000000000000000000000000000000000..56aa72b2b645a4054500f28e6c2336aafc8ab784 GIT binary patch literal 367 zcmV-#0g(PjNk%w1VGsZd0M!5h_bn~@K|%RaQv3V+@-8m@0Ri?ZD)>7){p#xK>gx0$ zAoV3B@eK|4MMd{ZOY;B#@(>XHYH9U8KIQ-b_%=4`0092|{rpQy`$tFa0092b(EV<1 z_b@R0W@YprAM+U*_%t;7008+{SK?}vx}hKAh$0Q&m+{QUg#^78)v z{{R2~A^8LV00000EC2ui01yBR000Jzz@D&ID;Nu*&|{g&^nOqaA_v<$0=*^74@Y0EW_r?D_Tl*4*fbh-<`Z*OQ9fXJ`8Q z`T%4A_w4fHc6QubTh#;u$Lq@T#l>^Sa^>jUSiV?Nx>Lyj00372o7Cpiup3;{PE7apk*31SHeBncV- Utrs{bA<@wS)YLLEFgig1JGeZe&;S4c literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/bj.gif b/lms/askbot/skins/common/media/images/flags/bj.gif new file mode 100755 index 0000000000000000000000000000000000000000..e676116f8ea589be623ced9474ae95d26b409ae1 GIT binary patch literal 368 zcmV-$0gwJiNk%w1VGsZd0M!5hRJK(0^&~s1I(o@^_e41WQ~>+?0A9UbvXB=jF2-~a&g z85!s20Q>t#_b)H?^*>0kM)vkZSh`kXzhXnJKwQ0Ca>jG^Ha7P=F#P;XPO?isuRq-Y z004;qA^8LV00000EC2ui01yBR000J!z@PBvC>oBLj7IbMI0n!Y^zleoArQvqH#po( z-rY+S8j(uc=htJRjmzi2%;h+lJSh)=Y%T=?F?2Y16)8Lf1UeED78V2u4GJk09ThS; zFeoZ3EfYDA3Y0w^Hkq596ALU2H61+yubP|_ARrhl5j6)K0K60w7Z(c)7#Kbc5fKH; O1qjXvEG$0LK>$1EJ(0`+ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/bm.gif b/lms/askbot/skins/common/media/images/flags/bm.gif new file mode 100755 index 0000000000000000000000000000000000000000..9feb87bc9e846584ff47884f0b08b21637a36896 GIT binary patch literal 367 zcmV-#0g(PjNk%w1VGsZd0M!5h%NG}sahT%O-_0y6<%NaQJ3M;2WAotO+PA@tO>&t4 z0P)({gu!msuDRZ0W6m)#=zMvq0081^YK{N^wg3RwN=fmkrF9`q+gn@JN=nc*HQsD& z*i}_+#O2mSMaB*e$6;;MKtQ>Jn$tx^&pJBGAt5v-P1Hg{-@(h7i;IzlvBP|hgBLyM z&(`L)*me*ge$*`cb~Pfy=?di3Y#kD$!}09k(( zJ%<1QA^8LV00000EC2ui01yBR000JzKv3Z3WA!c|ijop(Mocb21BYnAHVy}d!^tTC zGQvy((QQ1Zg61*EC@wAwfnce{(!2a&q@bZxA|gBu0cr;w9Sb27FDnTkAPF`zYYQeR z3nUX1GdUF=A!=+42BjLC5}%=K10^M-9}hni4xnlStt9{eAF;DqTR9mcX(VfGvrZ8T N%oENNKhZxy06PV_l#2iW literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/bn.gif b/lms/askbot/skins/common/media/images/flags/bn.gif new file mode 100755 index 0000000000000000000000000000000000000000..b7b6b0f9199a96b4ef57eb4cac7413fe8a71793a GIT binary patch literal 373 zcmV-*0gC=dNk%w1VGsZd0M!5h*@ScBy8zjGWz@n?Mn*{IW$=0|4^4?A^E-v-e0KTa|+?tW#iFo|`==t;W?$|Hn z-uU+CRPy)t@7qi8-B9t*0QKfQiHC=(s;czoc(aE9QBhI&?M?OB0QK_MVPRqZ{{8>} z0RR90A^8LV00000EC2ui01yBR000J(K%Hr5su*RLOJf-b{C*G!V><10G8u!x`RjRA z48!Aq!7LVt3C-K%xD}|w;mjgUd>@`j!C9pMEiV!~F(eLnI5-3V03Zc_5)vU21tvBF z1epL76ape3A(I6a3@$kb11ACk6c(-oH43B%2N5$G9v%s5gG|6 TzDf;E0Ta;<4ih8SH$eb9y}PV* literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/bo.gif b/lms/askbot/skins/common/media/images/flags/bo.gif new file mode 100755 index 0000000000000000000000000000000000000000..4844f856924ae58edfb7fba44e7f5c448a2409cf GIT binary patch literal 359 zcmV-t0hs4vV(D)Xgb0J8x6j)oh?9PI!A>Dfc;*-7r+SMA+W=+{2y)j90jO;OfS+aCb+ zZ2+~@I#HDX`@xU-Yhm@MWcaUe{SE+6*-y~rdg<6v>DpD3#Q^l7S@h_CxYj>a*j4D* zPX84HA^8LV00000EC2ui01yBR000Jrz@P9(D-Mpwk#h8VI3JN{C=>`BuFOZ(dMJ2f zF@>TqYQZ?1nMNb~=yZbkhc)S3E)GY9H53#K02vt#up%M=2&W4R8yh_+f5#0kd62+7Jp F06Q~Io@M|5 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/br.gif b/lms/askbot/skins/common/media/images/flags/br.gif new file mode 100755 index 0000000000000000000000000000000000000000..8c8661626bae57026266d56824709a9283d8c7ff GIT binary patch literal 367 zcmV-#0g(PjNk%w1VGsZd0M!5h*yZ@7)EQ*1WcKzcVzE>JD*$C`>Gbp+sM%CSoJIWn zQhU35Q=?NucKT?rY5e?309XJTgBo9}UjRe^CWu`v2kCd}8kT1>3rugqR`S3bi zgXwK^s&kt8u;D^CmlU4TF*&3(uH8eG%5(PeNv*7*{qs>jm_YUIMD*z&EnxTj{8|8G z00000A^8LV00000EC2ui01yBR000JzK%YxI2Q|##o}-XBqc_nX~imf zicKPi(J(H}D*|_FGz=@5CIvz-2nvM?01#zd0|OKZ7Z5dh4=oKZ93dtH2L~iS5FP{% z8z>nK6&yE_F&%{?3?MWt20T0lI{^_97(WvuAPEUG12#Gb6t58)vRzpWG%#ai8o)?P NOBc>R(9u6Z06T&Vj4=QJ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/bs.gif b/lms/askbot/skins/common/media/images/flags/bs.gif new file mode 100755 index 0000000000000000000000000000000000000000..c0a741e5ca69ebadee7774120d87675df8f6671f GIT binary patch literal 351 zcmV-l0igazNk%w1VGsZd0M!5hMwYHDg_-ri8!;Q+I)&W*gF7+nE;!qRZKOZCw|d!C*;+}}yn+fUcmx}HB1#?JuD%K#f2#Hvf&yg>cgPCviDJJ8U&oE3tS1(vB1E9iJ}{c4-fTIVo~<2!MAGgDgFLhzJ)0iw_wz xkbjbsiy1pHWd{cv8#N#x2P!{4BvmsX9v(bA6ciyL1UCgs1p&Uk1i`{V06Udwm5Ej<$3qzlIq*j_PPM<>g?U%;q-DH zHz@pY7YC=NAd&!=j7w<4FL0hGvELK*3i%RX*Bbn0QcQj_}5dq zmwf!+Nalz{@sUyQ6#(zM0PNRa-@8`meof^k6VUOkydMcq-6}VrsEW3M&=`)TtD{ zkw~S{$joSq1J_#!yF{6IX16(Ty6p%d*8_Z7HV|nXKMG+4A`cH5A0#9j8v`5|h6FA( z4;L2(5HAQJk{AL67Ca~h3=07oI1(WyCkX-y5DY0AHwzmGpCJw>KLR=w!~rk|0|O=| X4h}vlBO@^-B_JRi*e54G+(7_4r_HFM literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/bv.gif b/lms/askbot/skins/common/media/images/flags/bv.gif new file mode 100755 index 0000000000000000000000000000000000000000..6202d1f3a2df4184878f5ed8bc23aa630d43c061 GIT binary patch literal 376 zcmV-;0f+uaNk%w1VGsZd0M!5h01vKZmEH98^zCF}`uh6sZENy;dei^_23FAb_xIv4 zFM6fq>gwv0xa;FBE9*^5?Ot5H007+=6wLqt>r_(O008MkL-O+Sq$r8H8S8KAKx4qwEzI+G%}a&g)Y3x25QbD!hlDJJ;%`1$$yu>b)6{{G_A^8LV00000EC2ui01yBR000J+K%cOO_*IUTgL1<384SHfuU06)29OoSwQzVEFmNnFct&>I;*X(EH(-}2Ut4`IvFn*G%YA0 W3NpY46Bhss8xhtKBqTiBK>$0|z?fYC literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/bw.gif b/lms/askbot/skins/common/media/images/flags/bw.gif new file mode 100755 index 0000000000000000000000000000000000000000..986ab63c2764732ffd789269068ea5b033c3ace0 GIT binary patch literal 364 zcmV-y0h9hmNk%w1VGsZd0M!5h0Ega4$@D@(LOaCqa&mL>^72YbN;5Mv0JHX7()euE z_k-U2W7PVV<^THn`u6tr0G;YpRaJ`N{r&y@`1tr`()K$$I{>coe&7CS*7_2|{`B

      v=j^b6%_x}F=0IBa4#s7NR`F`B`Pft%J z$^T%_^Z>W_Z`k~L-2TnY%?ZH$?d|PR&GwVz{*U7Ra@Y5B*!XkW{dU{^)z#IA-~0i+ z{Qv*}A^8LV00000EC2ui01yBR000Jwz*cIgnazBo(F<|8pir7d!)CkPB)!FAr6S=V zQ4A)w)4?(sMj~NSKnkb$WbUX_9{30EM~FmN1_A;W2L~J>Ap$>qe}RLAhanvniv)mz zgocP69S1)&6LofZ8wI8XGA|2hYbG@TumiFp00>)MISL9D6)qkgBD60}C?FsZ5FZ~U KB>>I$03T&+g{ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/by.gif b/lms/askbot/skins/common/media/images/flags/by.gif new file mode 100755 index 0000000000000000000000000000000000000000..43ffcd4c7168eed43697af80901f59eb800c8d2e GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5h^B5TYTU+S>093qGEvhXRp%(m0OZPA^^#B0Ij1oIEdc!h z0RI30A^8LV00000EC2ui01yBR000Jtz)u48^BIkZWKtmbxf~9RbTUaK0<@V#a5Iep z0SL*!xnx3z#r7u*kIUz@Vke-0Mi3R*j9w@d8Uj8aG?sYQ0`8CB=w=;M2%_}ui@050@}uG%YO>r938jh^H}W7}|?_-K3K zM1A#akM4Aj*@T_#<5Dho0|wkLR+Wj%A4LZh+xDUfJE%({`-8pwZ9f z{Ndf=;q&d~$@RWoci%R5=|6Golc2NI>C&_C&Srt-fT#OlgzToYz=W6VaHPjheDCQ1 z0Qmp_A^8LV00000EC2ui01yBR000J!z@KnP@c9VHqA^5jek3~#rqn@gHZc$d2LOlw zESpVnnI#fE!PYA}v>K7g;gPVUDH4&W!gwtlDlj}277uE90W2*A4kiT?2o@tVY8n9n zd<{A@5ezFM5R@U77Xb$~5iSb{2@nto0;3lk92W`(1`0B+uL1%c9Va;fHy$1_4;jiS OAV$tU1JTmcK>#~z$E7#` literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ca.gif b/lms/askbot/skins/common/media/images/flags/ca.gif new file mode 100755 index 0000000000000000000000000000000000000000..457d9662d54d976cdd5b4f4d8e480831995d9af1 GIT binary patch literal 376 zcmV-;0f+uaNk%w1VGsZd0M!5h_3Z8L?(Xu0iRy=l@p^jcX>8{c8~XbC@H|54tgHR~ z{p{o9^^lbH_4V@d^4S0Y+eAs^MMu~qDBL$X>Rw;?qo?RyWA1ZyrT_rA008Uj>+XGl z$p8S=85!+vbKXr;=1x@WC@jm%%f!UQ?PX@=VP@_yIonlP;%sf|SXl8(Q|!II=^rQF zYHQTg)b-HQ_nxBe&dtBSzw`C=^ox+{QeE3CFX-Ld^p=?QnV#ZRTln|*=)l3>GB@Ep zK=kzVA^8LV00000EC2ui01yBR000J+KotTJ`WcN5N8rKam?{;VVhR8p4!gp|!$DLS zo9SV&nKc>_P{CQD5frZ&iNaese34Sd1QT2iS{?-@5Dq;)78xNq0v=;27Bvwq0X7pl z87w#(BnJQ}ARs3T0TMkQ83qg&6gv$72??Yc9X1~r4>TSr4L=Syv$PTrxB?pmy-ESg W8WRx2Ob;9`BGn_;G8ozzK>$0p52i2x literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/catalonia.gif b/lms/askbot/skins/common/media/images/flags/catalonia.gif new file mode 100644 index 0000000000000000000000000000000000000000..73df9a0498e644d740d4b977b750e7c2f81c6777 GIT binary patch literal 238 zcmV>2R(OZZtg{&-mI@-yij3G>G+^+_kur3&)* zRQT&N?ejb0wj1j3Dd+AZ_{=f;YDez$MDjlu?7$`U=P22&66m}k?^h%Ay9L;CJOA^8LV00000EC2ui01yBR000GRASa4Yd7M;wH4xyw+omXjATbR_ z?0n>Vg8^n>fW%@nfksm(I2?ssXFBx&2Z-zhVK{ug;Ll=Fsbn%03&zS0Vzt}ua>-~u oAAx)zv9`Li2or*W1{5_m40TQqczFpa6e|sr7L^tWmZ&#vi!oWTyicM>= zhmfsIOrUA~?N;j4Rj|#BG>=sgeLO*$Wh{(RN}X%~76AYBesQ#oRG(};m|-=NS^!f3 z07L*pms~xQRYsj?Ih9>zu7hB!eoUQS=FC1!n^Iq*gHNGvceRn>%}V3YQ{vNQ?9xyE z-B^6BU(voNRhBx+#7NJ{QUF~SLYHNzx=l@-V*pPXW1vrLt#M?oaMHv-+Q~uM%1;1Y z0{{R3A^8LV00000EC2ui01yBR000J%K%Y>!DRPd2jX+TGJuCy%<)To8M1@u1)Et-! z4FCZ1kz_!)38gZ#7z|;GLzP)TCJEQ?_GmS92P!fdH9QLmFg-~ZIRFP3EF&Ey2nhiJ z1V0-WClm}eDKQ>+moFMW9109&9UUY)mH`nF5U3#^XBr-Nmk}@#6R090Vx1o}X9PQG RKTAs$&=o(^Kho1d06YCiiGu(D literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/cd.gif b/lms/askbot/skins/common/media/images/flags/cd.gif new file mode 100644 index 0000000000000000000000000000000000000000..1df717ae5cdacf3d7ea543831aaf2fdb0f94262a GIT binary patch literal 243 zcmV}Nk%w1VGsZd0K@0RL4d|2GYA-u}p90A|GB{{Rq6xZ3|^8TV$2-0BSTOZ tJ`>D_6ZdB5brBvA8-;Rze|2hu4{{fe7ZH+^gBA%3n3x5d9-W;b06Q}=W@i8Z literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/cf.gif b/lms/askbot/skins/common/media/images/flags/cf.gif new file mode 100755 index 0000000000000000000000000000000000000000..35787ca489da892f75fb431d52e85ad726b85b3d GIT binary patch literal 364 zcmV-y0h9hmNk%w1VGsZd0M!5h`b9-Fr!*Lr7?RSH`T1A%_4V}hIiuO501eXV>6HEb z{f4IZ3O(rdDk{6;F$7`+i^_{+i1YaP_?Wc%`8YY$)BsDmOGURuCY&a1$Zh20=d#-z z1~B0LYiod^_`|~h^71dM)(v`{_TJtA03+Vc&H(ZN001BW*#H3h`~AS?Tbi4q&y8{3Je{YcVx#GE#tooSGnC6XK%v(QD>^g>goG3n0|O9?HV+RO78VydITQ^T z7y_CBCy$bp0S%X$n#~#3Y`-G literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/cg.gif b/lms/askbot/skins/common/media/images/flags/cg.gif new file mode 100755 index 0000000000000000000000000000000000000000..e0a62a51cafbd00551b5258b17284e542272d4d3 GIT binary patch literal 359 zcmV-t0hsg*-+&G0P{37UEWHq-x`d#!NAM+U*-~a$r+f?z%0s8ts^UDeL zvnuz?OHkNM%JOCP%@Oy}7yJ8Qtm|UG@JH=Q0Qas=P1OMRQ%3E-0QS8pX5D6<=tSKB z006@PA^8LV00000EC2ui01yBR000Jrz@Kn9C^BR*mWpEg1)d7UWio-yN|?%x-i_`v_xJf&S^aWy{$F4GWo7tRSoJ+U`$V)sTy`awef zdwTg;So~^g^&=zw{Qdb`TlOj{_b)I0d3pQ$`~G@*?EnDy`1tzz`uzO-`T6<%{r&v_ z0RI30A^8LV00000EC2ui01E&M000JQz@PBvEBY7&!0&LV-EJ8Ts8aZ~C=NldmkO8` zznY`7-33@Eg|DWK?QA-ptl_)$cJFv@g!4^yCvZGD5e5(pFBl?vG&3Fp1SKCNgApMx e4+#J&94-$1Sv6Q#~ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ci.gif b/lms/askbot/skins/common/media/images/flags/ci.gif new file mode 100755 index 0000000000000000000000000000000000000000..844120a52b2ace1595d581a03c564d71d3f14f0c GIT binary patch literal 368 zcmV-$0gwJiNk%w1VGsZd0M!5h`m8gwt)$}Rwl0QB_q($doZhX69oGW>l24Z{ucWdQrJMgIQ&Ce0=`%{KnKS^c`}_R-{Py=9HV>Qy5;rs^761T%qz?}T6ci0NGb$-49Dtb*84?W*9y1jc3JND8 O%n%R`H#Z&*K>$0Fexktu literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ck.gif b/lms/askbot/skins/common/media/images/flags/ck.gif new file mode 100755 index 0000000000000000000000000000000000000000..2edb73994c90da240f120ea7a3143da4fd1b0723 GIT binary patch literal 362 zcmV-w0hRtoNk%w1VGsZd0M!5hixQZ�Ba-d*wp60o~!^6Yf z)83@4%IKArdyBAef1%iKb3RdjrBiL=Y-?zGr1<#wL{@{-8yPn@f!UCkyJvfopvUIJ z$^ZZWA^8LV00000EC2ui01yBR000JuKpslu@tis|!iT^lC=@zhtI;TsTrt6jVUws7 zxfdW%3j}1K2jyUJWPY9%1^~e9OqatA;@}v50|f~c6+I0lI1zjt3lu*c2@)0-4-6qQ zZHx#9EFUl_8EOZLeG3Q(5kH=xYY`fK2B#Ynssf^}rKb}c8me1eDy#>i5U)Q?PftI~ I%*;UmJAFx!?*IS* literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/cl.gif b/lms/askbot/skins/common/media/images/flags/cl.gif new file mode 100755 index 0000000000000000000000000000000000000000..cbc370e6ca757d338d851e78a257338c5088edff GIT binary patch literal 364 zcmV-y0h9hmNk%w1VGsZd0M!5hRHfwoZEXNt#rj)Y{A6Tir``HdQS$Qg>gwwG^78T% z6WQwbRFu-}008$yMC1Sf^*cNC008nXF7)l~g}mnO@9*&q4f_B9d$#5KMMd~DH1{tr z_9`m%Bqa17ANe{u`btXkGBWcS8Q%Z^=jZ48KR+|dD`anSRHa6wc(|D`gRGQiV zKd)P$+t=y#k;CU(nAH9K{@?BRcd_F4@bLKf`1SSm-2edm{QUOz_Wk|+`uh6&`}_I% z`S#~9+pog_ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/cm.gif b/lms/askbot/skins/common/media/images/flags/cm.gif new file mode 100755 index 0000000000000000000000000000000000000000..1fb102b295c86d04176b567ac795d92913dd23c8 GIT binary patch literal 369 zcmV-%0gnDhNk%w1VGsZd0M!5h_AD&?YG?LHIOgU6^Ya||J2?0EN$l+aXu@dsc60gp zR{(|p_4OoEyHoo_MMZ!}X15^;I|>4pih*93Mh&*jH7OM41RiNO4Iv>JI2af>8Vm%10~a?T4;>i= z6EOf9j}$tQ2$eiJ1pojrA_No^Cn*;Q9Uve%rZA`!B_$jw8(ceVG$bT&JtrI-J{KD+ P%ry|t&nYQB)Ik6{8V!;B literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/cn.gif b/lms/askbot/skins/common/media/images/flags/cn.gif new file mode 100755 index 0000000000000000000000000000000000000000..b052530978823707ba275dd861a188e55cc0f181 GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5hs{jD)lSsJ$0Ony~-8MD*C@0eNB6BFS@ zMa=*J+b=KPKtS3nE7mS9;ZRW6B_-onSmsz(_Rmq*ARy5V4c|&i>2Ge`MMc&hANt&Q z;Z9E4Iy&AuI^aS=)fpMW008J}YW2)Z`zbm)I8f18ZBYhGiKN`WYtZ3-}cTd005pyci#QlQT^Lg`Poy8aNCjBL@cn87O~(gocQU000aq zE(MW?BZ-TZB`6LOEgvrf12zf@7$PD(0|ui3v>+g*rl_g14!yk)z`zE>!$AN$75JQN literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/cr.gif b/lms/askbot/skins/common/media/images/flags/cr.gif new file mode 100755 index 0000000000000000000000000000000000000000..0728dd6a498c4c3e2a69652e0476a60aafe60171 GIT binary patch literal 359 zcmV-t0hs42Jdg_UT4O>Pkug0516X`S5ac002{7e7D^g z7-xgL<~uv>q@;wL%<=N_f0xSk`T3Km)a_MO?yRf(%F5v-CF3hA004OLuC0xu(fP&1 z`~Cg)zP|Rny8r-X{Qmy+xwyFi0C$nbbdJMriNM|f0Q0uB^|!X)X=nMNq3cgi<>~3? zKtSE!-{2n~@rQ+ppw81lKlj4H>!6?d$jI})zWmhGdz8rG008;?{Q3X?^Rlt?^z`$z zv-|%3A^8LV00000EC2ui01yBR000Jrz<^MQDHcD-4+0U8I3*fQ%;A9GMx+vA2kQPf z9G%V(i3rXt)vZ#iZ8oz`#}hjFY^mH~GFcG@Eerqv7ZxuqA|`u&fP#dE3Ji${jDduO zFA53(A}}>{C_X$H6ci1oG!!xh90sliX97JBw6rc5R909eB?}7)zzGFNOT`EU$jQh- F06PNatDgV> literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/cs.gif b/lms/askbot/skins/common/media/images/flags/cs.gif new file mode 100755 index 0000000000000000000000000000000000000000..101db64939d70025b6a4712466a58640ff4023b7 GIT binary patch literal 364 zcmV-y0h9hmNk%w1VGsZd0M!5hb92T1{{HrJb6{Y-^A;8W0Eb*$yLx)b`uh6#`1tnr z_U6mWOiZzCY{7nh$ogAb`htS}=;-`pWJN`<002mXgUk6+Q}k?X`aM1Sii%THw(I}^ z_(VjCiqAAOru92J^8f$<0C@5)F7xyA@?~Y^00000Wb-sM@eK{&008^@`}Zy`kC4)& zq}umOOIcXC_%t;5008wNAvrm%^>}#re0(e{q4_B(Wo5wQ$H(;b_57fq_4W1L008{_ z{PgtnA^8LV00000EC2ui01yBR000Jwz=CjC2pL1BWU_E*9sqzw12-_dGz92g%kD>pJS1OzB3BBl}#4;B_K92_4XCnq8zJs`##$jBwj KDJebAK>#~H;F-+; literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/cu.gif b/lms/askbot/skins/common/media/images/flags/cu.gif new file mode 100755 index 0000000000000000000000000000000000000000..291255ca3f7621e4af8eec0a3239a7f75d22a6a9 GIT binary patch literal 367 zcmV-#0g(PjNk%w1VGsZd0M!5h`T6<%$jA9;XxiJ|_Jq?EnA(CY< z^Mktn01D^oEiL{1{bi~BYNytGv)%LPT>u#CPn-Fj#LM*b^z!oZ@kmH@vHX0t{$Zv0 z_xJYzDDRrW$MaHBY^?fdsrf&V_C}QTL6Gu?z5f6J;cKn_Pnz}XMn-6>{zR1b-T(kg zm-P30drFx2Zm<52y217DZ~XhZ06QEjsZsy{ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/cx.gif b/lms/askbot/skins/common/media/images/flags/cx.gif new file mode 100755 index 0000000000000000000000000000000000000000..a5b43089b01283eabb0e53b270e414575d24679b GIT binary patch literal 363 zcmV-x0hInnNk%w1VGsZd0M!5hXV+)rR004|-W!KN{ zNC0jCCsdO~%|(se94IKpt*z%-a!RsRc0B{%_xkE$J z01Qw}%}f)z6QJZj005*@(oRo|A{h)Y9}6Tm7Z(8=IXf#Aehv;jf&nNIC>uCB zHYx*-9uf;62?P)j1qCfG4-7mWou8o_r4OFew3<>lq3*RJB@;{4ib^W~d`$By{bNBYx1_V)Ar{rvs$#re}e`qx+D<%^c7!WanIvNN(RvjoW z5F08akT^R6JXkUrFcA$A5j#3KI649bIW<}XAut}QI6^ox0AgcgkS(k;NlOj^1P28L L2m%5#&_Mt@EbY{X literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/cz.gif b/lms/askbot/skins/common/media/images/flags/cz.gif new file mode 100755 index 0000000000000000000000000000000000000000..0a605e581dee0f0d8500f0f0d67d7feb3f1d6f03 GIT binary patch literal 362 zcmV-w0hRtoNk%w1VGsZd0M!5h$^Za_s@~NB0d}k9<{23PM6~kq^6u{Lz0&OJ>gwd> z<@B4I-QC^sl$7NY6Yq-RdeT z=_DlWG&JlxI_New+5iCPA0Kg@*yu7c>@P2RquY_Z>Wi=7@Q;t}OH0n(^6pSjm$>Bp z{r>ANF4x!BW1ZgbnVD^*;lkDL$Ay{x{{H*>`}p|y{QUg+`T6$t_Wk|+`uh6A008&* z_x1JlA^8LV00000EC2ui01yBR000JuK+EYzG#C%aL_)!|m{hfBYlp+xc)6R8VSx;0 zwH@`E_35_VzyLAEYIQ#ymle>+5ac)U_o}coB^fX@3kwYmC=U-Y0u>X1A{QPVBpVzd z1P&hn6(<>yla-g54kG{sC!ifJmY5D07!*AP0W=a4D=85X9v&Yf6tV?9ARq$A0szX& IJeDRmk~Z$xI*esy^4v)C;Ck|#M6-%7SXfx=i6{ABV78<$X*oGRKRWhOQtvY}y1KFV zSy{b7F`I-h{MDq&xUcenaD!W0=FJ`I9v)^%Jm`WQ@8E5%t)rE4a)5z>`|+ZjoRH|x zB0oPbA^8LV00000EC2ui01yBR000Juz>Q<*Nd1n;q=UsQ6mn1oqt{z3e#_l%GHG~8 zq|GLU_}O9-iB9+V%pPzQs_d!=1Q?2`r8*oeD-bdPb$EJwet-~ygcW&veSd)vHG>2& z3JM9EA|f3fB_%fs0Vg~e85$ZOARZnZ96mHSFDC=H0};9*ya2uc7bC(W1Ox`hF9a9L I7Z*VQJGrf#Gynhq literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/dj.gif b/lms/askbot/skins/common/media/images/flags/dj.gif new file mode 100755 index 0000000000000000000000000000000000000000..212406d973881f3be2b8c1f4cc4bbed17bf29119 GIT binary patch literal 369 zcmV-%0gnDhNk%w1VGsZd0M!5hvFoud%q>TR{s5@}G|e*rmH>^h`X9+3l(+oH+5S}6 zRQ>$@iLLoMdHZFo#G1PLro;XGn3y@xIeet{jH&k|YV@ne{w>ih07~f+#S(<1_x$qm zMAJn1`uTmH^&`zAak9z{!wnf?@PVoKO4dn)tN5V5{29p^`uh9#_xJPn|6QiQzvI@8 z-;I^$mj1!P6J75BNr5eK_jKNL6v`Co_vw_h`PStA>hb+slm7rY=iT(({{H>|hXDKg z`~Uy{A^8LV00000EC2ui01yBR000J#z(h_!BsPw+N<{<{{Zt8y)B=H+Opgb`hWY(? zHxpuPFfdA^iJ{AD<1A$}dZNZG7O%|$m9SAL8X6uR6gqej5eq&jEFd5bkSicDiV+(= z9RMU37at!dG!g|qiat9t1OWjb2PYXB1U(&lngkcCtr;O90X-=-E(*dV92^o71gX6( P1I^6^(9k{8(?I|`axbfZ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/dk.gif b/lms/askbot/skins/common/media/images/flags/dk.gif new file mode 100755 index 0000000000000000000000000000000000000000..03e75bd297377de3d398b7c36a613ae283ca64e2 GIT binary patch literal 374 zcmV-+0g3)cNk%w1VGsZd0M!5h@KjXydV2Tk>-Tnb?>;`x008evN%!~n^<7=$LMcM0086-4e2E%>pnj6VPWhsG4xqk z?Kd~;D=YMBYV&4h@?Blw008hoLg*hK=NTFK`T6wp^yo4&{{H^%PEN=G0QL3t`{(EL zR8;Nl?dUZ%@mN^t&(G>PIqy3=`}_OyPfzgx0Ql_A^8LV00000EC2ui01yBR000J)K$~wk`LQOGN+ra6JbZ!Z0HqsvJ{b@UkZM2? zh|Mq8a)@ReO~=rf1j4PKqIOPqE{nosuxZtJ3Uz81BNzogC_g+rG&m(Y2L~%A5i1El zii{ACk&_V-CJ7ZD1`Y%zHyn2(BoY!QDit55GdnjL6s!^!77RTVSz9{-DGL}fFDDGX UOCb*pE-?)S)G8`HJt{!}JEF&=EC2ui literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/dm.gif b/lms/askbot/skins/common/media/images/flags/dm.gif new file mode 100755 index 0000000000000000000000000000000000000000..2f87f3ca6a52c118cdfe6bc0a6dbc623025db1a8 GIT binary patch literal 368 zcmV-$0gwJiNk%w1VGsZd0M!5h0BZng!fANOcuk|@^oCLV{QPvaE&x*iRLC-1x?DV} zJY&COmA5ucv`vA{f&J}j^z`(2cXIaSTlV2N04@MOtv~a0HZ`d=OG-l;8rvDgkm9|A-a-SpKT&M)l)gBIsMxJ{ow$M z%uC&3AK>~-_2NPAW-IDzF!Xak@og&eZ!urKUH){MK+`4=x$Z$a8m$@ z00000A^8LV00000EC2ui01yBR000J!K%Y>k(X4(2fD&*x_#9A&KyZr`0t;IL=X3_N0|+uPBp@IdNClP! zB@7l52nHh%pc@(jI1LRs9t<=FE+{4$3KSXvre`5FBqOdFuoM7T3keA#5*QF?3MwkT ON=;3M8x+#hK>#~#;)-ej literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/do.gif b/lms/askbot/skins/common/media/images/flags/do.gif new file mode 100755 index 0000000000000000000000000000000000000000..f7d0bad39e027d93dfedfed5b001b2f32ff31205 GIT binary patch literal 362 zcmV-w0hRtoNk%w1VGsZd0M!5hQ*62Y{r&1hL|}Np=Q=v}`22Z}&g@-X00372A)xo| z>*6FN=QcIe005Mv*vtR`=RQ8}^Y)FN)mwADIaaUaEG+u=_4M`i`SkPYQc~+ySNi(; z`1ttt_V(KV0C9)Q=SoWLVPU-h0K@q<#=ip))CxkzKSY=X$< zL_~g)(AnST003d>)zR>8Z~XW7^YioV^ZoB?YU)o_;9x^v5Wds;H z04ggcClfFpHk1%@3K5!|6g&tXC6x>nr9G$=00<3N8X7ksr2`WaJOH&zOd}&PFEGjo I%m_gMJNYW5asU7T literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/dz.gif b/lms/askbot/skins/common/media/images/flags/dz.gif new file mode 100755 index 0000000000000000000000000000000000000000..ed580a7cec832efa9c9aa4900ddfef45d938e132 GIT binary patch literal 370 zcmV-&0ge7gNk%w1VGsZd0M!5h+S=Oe?CkdT_Kmuu<#K4(d2Vc{iBy$ylfS9*)z#Zr zP|?xR;o{=wj)z;Dd-Cn=`TF_*Tqpob5aC-;jhKPlQ%7Q zXrzXIvX}y9HFT?xhPIz!o`89-ly9hv^Yin@$;i&m&eqn}A$d)7dtl~!bnUsgMUH7q zk#6YE%<9?K^~=la^zu%kfVpj8RGfPC@bGl1kIrskW}19HgERA^8LV00000EC2ui01yBR000J$K$LEn@kBffjgr9t05XW^U;qL^u2=y;hv3lSh3Ar3bR z2QDf#BxXJ)5gnBWDGC(?Fc?0KK06X34hJnPIVmfujXgiEHiV-zF(f2DK0iMt5*NxF Q%o`;O3nn`|B_%-sJGCdIJOBUy literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ec.gif b/lms/askbot/skins/common/media/images/flags/ec.gif new file mode 100755 index 0000000000000000000000000000000000000000..9e41e0ec8c22fba363719ef9ba536c7c168e0ac7 GIT binary patch literal 362 zcmV-w0hRtoNk%w1VGsZd0M!5h=)C~--(>ULTIq6f$N&KQ>yZEeTiZH1)+#FZ<5&06 z4<}}}?#2N0SW`xQ!*rUrS%aqSH!=C;b=Lp@_v3o@;cYr}zHF1RT#e23;dA=uW9R?? z`{;r7-%R=Def;Tb{p)l1=Y@Ks*JF*X;Y&;N&I9sFMEdEB@IpHPDWd%Cmr;etFm1b4 ziONiY#{J(5e4o4#Q;DSj0N+DH{_J>xqrTK1ALCL|{_v0TP)qgVVnTYs_hVg`w94VK z0QuJ$A^8LV00000EC2ui01yBR000Juz@Jd4P?V00Bw+bdIaP(i=@bfiD3lDK$>DfB z5=o^J2nYljANLy`m(PNV?}ej*K%b9BljwIkIsh>}H83y_4;L3BBM^=k1}P#K7%eL< z4m%bWEG!ESk&~8~nw*{sI|c*;HzXu82eAmVHZ}(`8$7xV4LK$zC@2xb5fd8|$jJoD I%*;UmJDaVaZU6uP literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ee.gif b/lms/askbot/skins/common/media/images/flags/ee.gif new file mode 100755 index 0000000000000000000000000000000000000000..9397a2d084aa397846d125405e06e31f81ca3eeb GIT binary patch literal 364 zcmV-y0h9hmNk%w1VGsZd0M!5h04K9&qux6^JMr=HUY_3K;^K64b=un6(b3TWH@g4; znaaw_QkB_ElGrLMD;O9UA|fIH7OP51N&pF@S)1KGh}1?#Mm9D!KtMoLR8%*F(lLS1 zDtpcN`1tnr_Vo1hd$8pD`}@DYzxDO?071XS#l=rgPyGD+LyOc$kJnU~+b}RN!NI}9 z!^3#4gpdSZ}D|-{0T)`T6tn^ZNSw_xJbm^6~&f zzW@LLA^8LV00000EC2ui01yBR000JwK%dY@N*DpiNMtfm_&^|+OLn^j0tDKO(QtwY z1d58ALRB;yPE0{^g~yHZIn6;Seg<2{B(Y!w7b^}B5EwsrdwqX_gAfk_hcA47fP#b% z4;Mc*Cm|s_IUPJ6BOf|FJvuD~Cp(~`q^3Os13fGSpE;pCqot>)A~**JFfa(m3kxMB KEi57;K>$0OoS+N< literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/eg.gif b/lms/askbot/skins/common/media/images/flags/eg.gif new file mode 100755 index 0000000000000000000000000000000000000000..6857c7dd57a75a6232f2ce346c8183f63a0686fb GIT binary patch literal 363 zcmV-x0hInnNk%w1VGsZd0M!5h@ZEI!`uZ&`E%IYy^Wl9wJUsGTToDoxf`WqTG&Ji; zN$_%V?nOoOYisp-df&`s?Q3gWTU+Jj<>deX?p0Owii(PgiuQJPQc_df008#(_Ve@e zL_|dLQ&VeeZ0P_1G&D4OdwXJHW7PlvOG``det`CnkoJy_`}_OyyLRnZ0j?@dkV*@n*m0QjV&>^wd7_4WMx{Pgtn>i_`#{rzTU zW&i*HA^8LV00000EC2ui01yBR000JvK%S8I`Am+-j50IgIi8oxqR|)(3=|5j=`eUZ z9U6myOC($h+v;XIRVuPl%CT5LAgWPs-_z*=Ed(zb8aX>T5`2CrEGz&3g*zLFdy4@C zH8lW+8yhKMfJ|ZDMKRz4<1t0FMbfn-)8(JO2LuNwG=Z?A-bJ z`E+!2M@L5hY5-$nWBSR-;~Eq4$V;^5%--{3K$FCm^G`jL_HFE9K2`{FDt`eI@-rZV17Px0~b_w(}-mJa;< z`~Uy|A^8LV00000EC2ui01yBR000Jrz@KnPEQ)@BBw)b|g&YovL`OkFrJW9AdpfN& z6xa?#1`*CG6>Jp&a}IEy??J05c7AP^QL9~22K zE*l#uDFU7X2@sS64Hz#k1}qsFGcyGRJp(nQ7!wm07YGQbsW~z?qzwVX!zRWf$jBl= F06Y6ZlW+h4 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/england.gif b/lms/askbot/skins/common/media/images/flags/england.gif new file mode 100755 index 0000000000000000000000000000000000000000..933a4f0b3dbe2f328d15f73895ba576aa78295ed GIT binary patch literal 367 zcmV-#0g(PjNk%w1VGsZd0M!5hm;eCXgM;eo>f(xu{r&yob#>cSRqLap(9h55o}SDo zDA{sy`}Oto^z`}j^Sl56&oMFg@$uMAP0=E`CvM@Qe^-`(Ba z<>lq!;o;-s_-l#}!9?AJ#~)l5v{hKBF&@$K#H{P_6M zRaMcyzvk!aN=?0fyu0Xnen4Z^|l!7&RRaciTXx zJp#iZacpmv+sp?hh$SZn*V3@eTC~F)cv28j}X6%!B&kQV?jG$JK3 zGAWG|6e9}@0RSrmHCa3njvx&-J3BTA0t7l*I1!H~tU^0E0;XFLC?qDUAFVk{O&S|9 N001rs&^gjU06PoNyxsr+ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/er.gif b/lms/askbot/skins/common/media/images/flags/er.gif new file mode 100755 index 0000000000000000000000000000000000000000..3d4d612c774fd0e2af39995926d21a3c0b8c33fb GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5hX~bz*eB|_YI{*XE_{357d0X_DP6BTLW{B!MZRY?0 zy6Y(^N3}eC<0QHb`Po6pE007-CF#rGn!wCTDNk;Q#VfK7| z`MFH@&O-o*0Pb8vDyb?uvO2XpU-2?D_Q6E+9UbjoW3d1LRIf<(p;ccs0961%*ok^> za3QR6snbeIineuFWX#1pUi7&-%4kxThfdREezBl<`psB=irs;JII2%5xM4o^M@sas zMlq~0A^8LV00000EC2ui01yBR000JtK#c0(DH;#RgG5=`Y*;rKU^<0Z8Iq=^z@^nt zj8NzY!d50Y90AAaWq3LYsGN-$QiMDwIS9VK0l}>&D+&Y0$O_8K H3PAun=2?lT literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/es.gif b/lms/askbot/skins/common/media/images/flags/es.gif new file mode 100755 index 0000000000000000000000000000000000000000..c27d65e5f1218537ae3dc51733ae628cca95ecc6 GIT binary patch literal 360 zcmV-u0hj(qNk%w1VGsZd0M!5h{9Ii6Q&aWzCHD3!^erv?V`Jv#0RD1w{{D9J78d>e z0Q~$+>;M4s008~{ZSwLH{{C9~`$hLlOZr4a^fNR0JU#I8LG<-I{cCIgdV2RnMC@o) z^*cM|008(lHt`J&_VzII^BMU70QB@9`1m&9008;n?pN$nQT$9y^1ZOY&ZyvwQRJse>&Z*q008{} z0RI30A^8LV00000EC2ui01yBR000Jsz@PBvEBZvphLXYfWuBSC0fQ+N0D*v{nE`mS zx5=d9p%AtT1O!r3$s!RG2{aJM3~2~0wOLu@bOIw6cnByb8!b37b9EyjgbEG|E;=?Z zDuIs=k&`(*J1`cGArB8AGa?0JWCR2(EC~r89|R?;Us@U(92^v81U*U(OvN3?B_%z} GK>$1Bgrgn+ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/et.gif b/lms/askbot/skins/common/media/images/flags/et.gif new file mode 100755 index 0000000000000000000000000000000000000000..f77995d0ab47d81355552a534b769575ffe05d15 GIT binary patch literal 364 zcmV-y0h9hmNk%w1VGsZd0M!5hcf00^#no`Bb77rgPLfgiR8&xyi)FFm_4Oy}0000< z5dHmB^z}RVMn?1V9Q^!CnaZ(wy2}7F0RH}S^jJA*ql7PcT3(xf<>df*t&~QKYx`kh zQQBLHOpS(ke6LM7b* z00000A^8LV00000EC2ui01yBR000JwK$v1klo*YvWJ>XRWip*kWqP^rIJ2K3W4&?= z22Ctxv1oofnP4pdxJ`sOVUTJ~7We?b1u;bs2o4Mwb_)*=0elcDCV_&46NiWZiw6-7 zn1d@gA0IUoHU|eZJvSvK6f7$n4IUyVIUylBARs6xB&r)51-%WwBCjVTBt9d@BMHjO KEX_X8K>#~YC5%M? literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/europeanunion.gif b/lms/askbot/skins/common/media/images/flags/europeanunion.gif new file mode 100644 index 0000000000000000000000000000000000000000..28a762a59cae1c775b52f2c26b21ad658632073b GIT binary patch literal 171 zcmZ?wbhEHb6ky8jO69TOFm!wUDVT;A)?!_C{yfy2gaWmCOrR*q$^bR9R{scg(%O@TO|giqzYsR{U*?U8O5D U#0n;zPAS`BTG1CL&&Xg60F0$Rp#T5? literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/fam.gif b/lms/askbot/skins/common/media/images/flags/fam.gif new file mode 100755 index 0000000000000000000000000000000000000000..7d528852dc3f4efe6cec2d7f25e4674816af4c7c GIT binary patch literal 370 zcmV-&0ge7gNk%w1VGsZd0M!5hRNVW-^HgZw`+DR4UETfrREzpag!n;y)AxGU`GuG5 z0Q_s4$@N_T#q;(ub86K9{dc4FD{i6i0O$Y!`2ZM9+WP>!{6yRPS>gWu04n!Og8-@Q zX3_w2;{EtOd&%=?%JpD#+5iBn@?_xt`9y#Kvi8pRX@KVc!SYYc_GI@scgXT(_%w6B z@k@E={?GMu_c?g^J9qp~h{yC7X~pN zixD#c0XPp9k`)&RESV9UC?5|y0u?$m9uW=^5k4p=Eh`))0s?pi8o?w7A0I0{4<#ZZ Q8ygeP&<_tB95+D#J95gS9{>OV literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/fi.gif b/lms/askbot/skins/common/media/images/flags/fi.gif new file mode 100755 index 0000000000000000000000000000000000000000..8d3a1918280816780d79856b678e2ebb5bb76d66 GIT binary patch literal 371 zcmV-(0gV1fNk%w1VGsZd0M!5h{r>*P&)WbiyhL-+$q-dTj%J8aF`-s|}I z_)&e-x4+G6k=`<8%JTB_Re;vK!qWHn`hcS2hNk7+;_YFI*-d)X!O7PE0Etd|&|iq! z005lz_4Q+m-1heNN_f-z`}Xr?J8L`~36s^SQy#NO#iq_xR=O^YZfY{QUg-`ug|x z_y7O@A^8LV00000EC2ui01yBR000J%K%G!05U@^_N+NMGkubX74?uZM06bo(7gP9F zQ2?M}m}$A*k1s$F%gUQnOp^O_3LOlK(r9=#2Q3gHAOk1}5E%j!27d<-3l}&82rdo_ ziW4({gOY{_4h|X_2{m(rAU=0C4g9xlX|nrTtN5Iz&ug^#l$OE(bNB!&-(s`+ z(LFiN`_)KB*mQZ%Q&iJjT~wa<0Dk;Y zc*Ul#)rlEBT(J49dzFJDNq-YNXBkUnu=&@Bj*x$;ak%+jh0f%+zRwvM=aiM^)7oiO zk9eKgA^8LV00000EC2ui01yBR000J$z(?UQdmU(uPA1c6hC&BKFOksoERcZ7B*5V$ zJKh992A~F;4XhHmNMt(DWCF6qJg|>Sk$NnA2sb1o7zsBX6(0(Vem5357#J--E-Miw z4kr#J3oHRR7Zf!EYz7P`92^W9o}r_q1S1X{4kH>F0ivW41_ZtV91sS#TV0^KA|kxQ QO&w1X&EkWpbP#0IBkZG(I zI|I#NsOdxu#!OOa0CKWjOjd$nAd$g9!fBicDjy#mKOiFk5j-s+BMm7kHWn5b9v}e@ zc>*B;6BIoe6&NBAmk1038!Vg@9-*U`4gvxi8V@g>D59he4hRSv8xIc-6AD>dprRuq Sq?g4^4GIm@(-YR$K>$0bS(5+& literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/fm.gif b/lms/askbot/skins/common/media/images/flags/fm.gif new file mode 100755 index 0000000000000000000000000000000000000000..7f8723b7da96997e52b64aa9a90b112828d0c3e1 GIT binary patch literal 377 zcmV-<0fzoZNk%w1VGsZd0M!5hah%8;X0MsK*@LOh2Uethrp>9v;gPl2JAA&LzTSzh z)RVN={QUfetkP|n#`gC1kFwRR$m6EL+`iN1mbcgdG>o~=soV?qm!{D{dLOaapBMH1q}A?D&`J_Xutgouc569)lqu+uPC6(d+B$=jZ3+&CSjG`}^|p^7i)j`uh6!_xJPj z^Z)<S_%x44Qgjl}c&lY3ENVi*#SF`jw zQJXCyqG-9^-Pb2pO=OL2x3{D1Ihy?y-rnB$m5kKX z)Yt$3w8iJV%jsr^%<5`g?(XiPwcY0C=H%q$?sjUj!{w&A-t&HP_xJazz2WW&0CJMj zs?RiQbXoVKJ@hTySuv= zN2U1q__xUC>|Rjp>+3{pyW-;FpP!#^kI|;N;Ipu>uD|3gSFo+9_7 z>~odVA^8LV00000EC2ui01yBR000Jyz@PB9K_CFBujQ zjbnlXC>J5^c-oz zY4-LpRD8_-{&QQrTlMuMUAjEtsVQ89)Jk>C0BQhQfWTjb&-wW~PP9V+MgUT`Qv3Tx`uafl z_%{9h0C&iDRDRK5zF=0lR${+dLvP7|&3P=PEM>u5ZIH=rkkx{r-tzJkag*0|me@V5 zF#v-AA^8LV00000EC2ui01yBR000Jrz@JcfDPBlOi_-G*IA0);Vj6SERFYJ)YQ<(U z8Vtsk{nc(W2xPNCX*9l22$ga*3^ZuCMA~Rr!T{VS~Ynna}=XVtbp!|Lf{JQmlm=H*ti=+u`)AVSnMi-(+*O z!o=O#-R=JV|NsC0A^8LV00000EC2ui01yBR000GnV5Ce?*=1Q51Qm2lOygk;;}L`x zMcji75JjW$iF_oI&!s{5FcJ_(MDzF@0Y^%qa!GVN)-V7Y{&)%*0&>hYxZMEDxBPL3 z5%_H+P@p93bqfgr0u>A&4+I-^1_%`h1P&1b10D|?7Z)C52rC>24jU5(0STrB78V5p KtO)}NApkqQ-f7za literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/gd.gif b/lms/askbot/skins/common/media/images/flags/gd.gif new file mode 100755 index 0000000000000000000000000000000000000000..25ea3123187325ea334cd556a8725f10d6d6f1ab GIT binary patch literal 364 zcmV-y0h9hmNk%w1VGsZd0M!5h`PfMK+EtFuT8_Lk@?HSvIRN(7DqzG={^NFipiU;F zCK;R=^~gEr002p|M#j@PRk>7Pxj^&N8T;Qyd%9}#%_{qI0R7}u^8f(%%UDdOHb$H> zrNlTjrYKjZMEl)f_-;nm-dg?PYZsyy`QAL&008*hHif@+iNkvM(=_#7H|f_=ugXT( z#UD1X3&(aoaIP6(j4a=YRrX{&i@Q&=#15ajd-KmV;m$br(m?svJpG9PimYPY008{} z0RI30A^8LV00000EC2ui01yBR000Jwz@PB9v-m7Vf(65D_-u%1R3!RX1DS*qB?w)< zlQ7Bjh$0qz?3ck literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ge.gif b/lms/askbot/skins/common/media/images/flags/ge.gif new file mode 100755 index 0000000000000000000000000000000000000000..faa7f126a7eeb2cdd66c8e7d7bae6c19288d00a7 GIT binary patch literal 379 zcmV->0fhcXNk%w1VGsZd0M!5h^J`|q!^7$}FY#Me`uh6v*x2;+^!)7X+S=Ow{{HKU zgyQ1j?^;yL%ggeamFZ|=>XMD}^78baoA-%^($doUzr5z==J?*-?}dT$VqNpPw(o#@ z@TQ^D0RZh(PwfE!<4#BNkd42;zv=1e-{0TY*Vp%_r1Pz)_oJZqq@mf@+3(57?L|NN zuB_s9ZTZg1Edqm6FfT(fIiI{QUg={r&g% z_v`EHA^8LV00000EC2ui01yBR000J&4c69K@%@B50D}3d^dxy0jRWQH6{`n86gfi4j~dT9~*=NI5-0gECQAS zEFA+jC@B(&10EO(qZ<$%7Fil-I}9EMun-Xj0Si1QE*y6rAO;Z%ATSZN1Q!k$cK{Et Z1u!cSB1{M>B@`4L9Ss570U{zn06Tokyea?y literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/gf.gif b/lms/askbot/skins/common/media/images/flags/gf.gif new file mode 100755 index 0000000000000000000000000000000000000000..43d0b80172e97a08147c64f811ea5c7775ad46fb GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5hs;a7*ui5eO@$F<*#M13od&TML>hy?y-rnB$m5kKX z)Yt$3w8iJV%jsr^%<5`g?(XiPwcY0C=H%q$?sjUj!{w&A-t&HP_xJazz2WW&0CJMj zs?RiQbXoVKJ@hTySuv= zN2U1q__xUC>|Rjp>+3{pyW-;FpP!#^kI|;N;Ipu>uD|3gSFo+9_7 z>~odVA^8LV00000EC2ui01yBR000Jyz@PB9K_CFBujQ zjbnlXC>|i_4OqFa&sJ> z9G#v)0Biu0l{WVFGxY!f<>dhXdU{*CTlOj{Xu@dxVPWe408+M7M@KpJ^*#4YO#N$X z04V@3r7r+j002h-3zZ81H~=)MG$f)V{7g(UrZOs{DioO%`8+-NH#hzL001BW{8d%` zQBnN-PxkgJ_c1a2{7PtMHDF#Z_C!QLuR!eYHu>~A^6x4Tmk;vt6k)z%_)}Bx^G5vu z0RI30A^8LV00000EC2ui01yBR000Jqz@PBvD;6I}q{Z@!I3tO~W>cvESUe3gV!gIV z1_LSun`oN=FIY^TOlTHWArNrIdW(k5@j?tT5OQ@f2MZV&4SfTDfdzI42RAY^IDP{< z4iF3!0s=JzA{{0p3JNDVB^;ZcprWLvIUpS?3_B7M7Z;tMpe3TT9>E{OA;lcWE6G6s EI~OyF2LJ#7 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/gi.gif b/lms/askbot/skins/common/media/images/flags/gi.gif new file mode 100755 index 0000000000000000000000000000000000000000..7b1984bc692fea25c149976f81b83eefe4115c6e GIT binary patch literal 370 zcmV-&0ge7gNk%w1VGsZd0M!5h`FL*WsY>!XF8KKP@?25#^YhgJ0QX)~`D|k9&&~Mc z+9?L`uh9(`||Pe>FMdMX=VCmTk~2$@8aR!Ju%|O>|Rv)6$R*3O1V8X@@io8 z_4VOMJp7c6@l;63$H)7GeDvSm>F4L}<>dDE_T2yg_4W1r{r&g%_w@Ai{QUgy@9+Np z{{R2~A^8LV00000EC2ui01yBR000J$z@D(T*>FIh#e%^wH5$F&&qvMWcs!40Fwi-o zRNvzyQ*667Z!ervAP9#U8fSxSs*eOJq)O;)I6F8H96nb(02me*7!rX6Erf?22`(!o z36CE;5Hvg;9U2lO0W&o_1s@*;F)1$qum}eMBN+r4Fcd5|5eOn82eSbb7K;}G0=y9s Q6U`F_710$p)Hgu@JB@m~0ssI2 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/gl.gif b/lms/askbot/skins/common/media/images/flags/gl.gif new file mode 100755 index 0000000000000000000000000000000000000000..ef445be003587758a9c100f3b8f4ba28d7ecf95e GIT binary patch literal 368 zcmV-$0gwJiNk%w1VGsZd0M!5h{JgyUWo7(cUi_n@_4W1b?d|w;bMrJb{QUg;Mn>xD z>gML=`kb8N;^N-k-uIJ}?-CN$*4Ffki~Hf>>;M4d008wnJM;hm`@zBTE-w9UZt)Eb z`v3s^R8{_ylk^`S{7XyU008qD8TlIRt_c0RS`uhztxb4-XQLFAy9W8Y)^` zJuWpLA`k}^93>!Tm@p=s5S}3%0SPt$Fa#4Ms2dv@Ar}GyCLJXtHYgSr2L~A$Hy6qj O%*++fAt5)@K>$0)Ft+3X literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/gm.gif b/lms/askbot/skins/common/media/images/flags/gm.gif new file mode 100755 index 0000000000000000000000000000000000000000..6847c5a8c0232d984f7c8569a6f7b89b96736963 GIT binary patch literal 362 zcmV-w0hRtoNk%w1VGsZd0M!5h5tR{Cw^Y_hNB}DU(KIvwPXGl@{oP|@Y?=D{_Vz8K zEdU7Rgsc8MtUN<^^<_5J($?(pt{=R9%q&BwrH{BE81UXb>EsQut;Yv1MHQik(Ziu729_51ku@bd5>o*}!zyZH3;^!M~Kr7~iV z`@;YLA^8LV00000EC2ui01yBR000JuK+|tHq!^E+B~N4wt|z8sdlh&I zV1nSJx|lDwTVlQ2h2E@Ig~5nrKB5=m@qk%g3kD1bE*(A|9w{Io4h}0effX4UA|ePK zeHJw|AP);O7aF2GJR7Ds7^yfqB^(e94JZltZIsglm3;X*=`$a|mSy>dB6#V>6C88zu^&&H-GWYjO`|w8m z@k>OnMEp-r_xCUT>j3rjJ^cJ-ADQ4|UZN~l|!gvlF*DuHr^e2w<21@AEEG#Yme6E6cS92*@l3n(-Of<6(3BsGdK z01J;M1r?G5BoYpb04Wa!CN?Ub7onn0QOco{QUg+`T6xtH1=3N z{{H^@`uYLs0P{sE{&r#j%>aM*bo5g+`)pYGXinn*0Nnrpnf#Fa)@c0_0R8%J@IfCP z?Ev#+Nc3n-{_Tht`2*d*sr$8G{Nri;wS@lZaLKWk0Pz5I_G$i;g8a&V@kS)|Y*74$ zcktZGA^8LV00000EC2ui01yBR000Jpz?Nlb@fbag24abz#86OyR3aG+L?TDd^W%6q zm@uZ3$pFqWmF@uoMM#8Fps=}&dL~uP_J~B{0T2at1}`QFFe@o=a)KKp1_myNJ2W$Z zf`kS<5GM&fjB|Aw1|J_R1t&jaWM*do46(5{I9Co@6BQK)yb8V)N=p>OA;k;F$Uy)* Dz3QR) literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/gq.gif b/lms/askbot/skins/common/media/images/flags/gq.gif new file mode 100755 index 0000000000000000000000000000000000000000..8b4e0cc41ecc29ca33c6780e4d3334123a4aae24 GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5hgTMYUq%n5LcID;e`TO}0hyB~y+1XT70BGp}efw#` zX^g`9);c;~yk7VB_f51-;%sd5^Yi)k_Ls{20EPenNC2q-07R`s0CxcL_3{8@0P^nc zQ?^t0`S-j40N!F^-AYQ*EG+r(xBUG4)2@!#O!e!cz8 zA0Cg&>FM|To~6)`!;9ZrTfW1<-{s$-(EY{8$%?;)^!M`gF*dl3;n zehU*4oDwT5Dk>YJ8wQIqARr_RtRW#fEiDW`2rv-0A0ILYyd@%R1*_B3)xyo}qO|0?#_IX``tI=dzRBve!R7Gq z^7i)l@$&REUCfQ5+v)A~;pFX!pW6#P!~iM0f|}g`0GExV;q>(NO>)(NgUjvj_M5HX zdz9KOSIL#A-T)c2xy9!I0E*`6^7#4u_xSts_4?!H@B%f$;^N}`{QS4X=g-&gSa!$? zG@;Vg-SYJJy~yXt%GLlFq1oT@=<4&Tu*dA~^78Ze2O+8G=k5S}K;&nLhPNH9Mi1~aOXNQ>;tg^K!o zy} OO#v0p&mq#%K>#}%6u74V literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/gs.gif b/lms/askbot/skins/common/media/images/flags/gs.gif new file mode 100755 index 0000000000000000000000000000000000000000..ccc96ec0093b08b01c9324d90d77d962f297755d GIT binary patch literal 363 zcmV-x0hInnNk%w1VGsZd0M!5h#>Uv%tF_jJjVUR4c6Frd>+Sq$P!khzY;2-MM2%x( zpgTK-N=lKOAUxya>SqWx001uP%+g(5obhdC000!HeVPCOQ{{YoFff0MVvA5vlr}Yi ziH5ZE^YlhWeEpe$=UG|P&Z7Bwao*qN_(R_cf=VV}aquXVN$HH7Mwx{F#FkLGXerQFcmx-4h0Af06z@`JsBAuD>oY$ z9Vjg)1wRl78Vm_&mjfdqDlCnkqNErD0|X=m00^E6qiU}M2fe)*w_9DUX_v2_O%y-N J%+1R|06RkFo6i6M literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/gt.gif b/lms/askbot/skins/common/media/images/flags/gt.gif new file mode 100755 index 0000000000000000000000000000000000000000..7e94d1dda0ab4a00cf6ce142d317cb66ee3eb114 GIT binary patch literal 374 zcmV-+0g3)cNk%w1VGsZd0M!5h@$~p=rThS0|Aw~zRF?YyHudc9^y=#B07&=%1Lj$o z_fwVj03`2ClKOhD{q^ z-S_wRA^8LV00000EC2ui01yBR000J)K$~D_sU42ASwW>@s04#3NTSi`bd%92gNr~Z zfy~8v>H$h6nZi2=h&C5pc?a;wK8u6^B+A?_b2%6v9~F5PAQ>A36FoaQKNuAiF)t_< z4jUv56OTVU1~DNVEGP&LBm|t0JP!sO933X66sjcvpAW7byeTLJU?VsaKe+}bCMhXC U2ulnRD=rWaFfAx3KG{J4J1s%9mH+?% literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/gu.gif b/lms/askbot/skins/common/media/images/flags/gu.gif new file mode 100755 index 0000000000000000000000000000000000000000..eafef683d5f0280f58de5bda5d17a9f6766e1b72 GIT binary patch literal 370 zcmV-&0ge7gNk%w1VGsZd0M!5hLu2wfUGmw{){dk8VSf70S6`E-|AU(R`T+pn0086w z0B(u;VSV*ndHHFC_fc>2?EnC4hWt!y^>T~)W`g^3js1zA{aktVewF-slKS)j09khQ zcaZ*7bM|(R`EZK;R(1DQbMsMf_ET{3dXxU2xQXOyYh`5VW`Ossfq&m)W}v!{w85sr zd7y5H_s4sm-DX2_jQOBSd(Kv6bdLSnyh6udh2m{-jf1Iwb*Wi)_(x~-_W%I#007$n z0R0I7A^8LV00000EC2ui01yBR000J$z@JcvDDrq3O|J)m{0z{+z+yQZFd5Ip|${z=jq{PzuC=Q~5-0B`hi(b~_At1AP$`Hv$4G4-z{77YPU! z3yTCQCpZO?00A2bJO`Q$GdVRflK}xCKBW|AIvoWt0HLlUJ{njaF$)e33=9_=AtMjH QOHB|8&mca zFyyoV_4Onfof$`~LHzt6FQ_jw zrZOL(AN*Na_Vz^e^*<4h0FKKgkj*RpcXwN^Q2P2@{7+C2mk;%Jc8zj#{Az0UZ*Tno z0RI30A^8LV00000EC2ui01yBR000Jqz@PBv%NQj{k7vRBMV_^UX8L&C3I$5Q$>F>{ ztJSDPGM!Lb?kfZym(L7Xz^4Nsa38lB#bCtdI{*L&1_l)s3N$ko78No+J$45U5FIQ7 zDJ~5v9RfZ)2L~7vmYA9iu_0t6dgAvPBml$9+5v;@M!AjQT4$jCte EJ2d&0^8f$< literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/gy.gif b/lms/askbot/skins/common/media/images/flags/gy.gif new file mode 100755 index 0000000000000000000000000000000000000000..1cb4cd71d6fd05fa1d42a8afbfc3d25d4512a43d GIT binary patch literal 367 zcmV-#0g(PjNk%w1VGsZd0M!5h0B!)EnQ->^IW?y=)#$(Z`^x}K0QyKs$J@E-_10Ck zRFQ`}hRlg$zhl}@PxkqF{&#ow_ALHaR{$~q_V%iD!*>Rh2K;Jj6P6VE`dV^nM7Byu z^YyC4*j*zfYV=QR4sr<|QTZR8-|O zG|T`1^?G{x(b3!h0Pq<)OKR)9c8u3q0?Lk55MMdyoVe2?J@L5^%W@he1Mf7)f?Kn8^N=f7&AM$Ey`@g^S z;opdmE|u59$9OzQ z34=fevWx;4&bCXs%n}pUtb;~`7QYiX(O~6jE&wA4Di#z!8VMgf1QRj@H98n79ubEd zA1Njv1PuT&lO7TZJqIhA9UnFT0xc+_G^7V5CLJRq1_1~Our~!64pwb193eOs5ef)>y2ws2+)GW3#LM5@q^ivc z3EsHAA^8LV00000EC2ui01yBR000J;K!R+LNDNZ5ijpy6EEc<5>(xW)0Fuy!=U5dM zz5@;dG2Kc9&Ex{W1QrQe28YWWU;)a6gaLFGEGH@g0xmx>6dxZL77-B(H3kPCM3J(k+1vUs2B&Z?{ARY}Qx>_DQ Yq5-4|5jGYZx(rTFPY>D;4ckEgI~~rLTmS$7 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/hn.gif b/lms/askbot/skins/common/media/images/flags/hn.gif new file mode 100755 index 0000000000000000000000000000000000000000..6c4ffe8e84324c2ea40f2a50e6e0ab391b0f77a4 GIT binary patch literal 367 zcmV-#0g(PjNk%w1VGsZd0M!5h{{H^N#KdHN(O7iOJzc|ei`VMx^A9$%dy?7!08#)1 znqGO(CP}>X_4TB);s6q%=H}*wmCI^_)BpgALu1Ik$m;m`_^!I)y2a@L0CTm$sZ_G1Q!otn%E>FI>#O60vzkrt9J5igp zzvo3;r5ixGm#E+K^YhQw@k(dLNN346Sj6My@y5{a`T6<${QQBH*1*c^!OHB#&+fCn z;s5}9A^8LV00000EC2ui01yBR000Jzz=u$%C{B*YpYpM4BA$hUL?T!W1{nonVI7n_ zGzy_+LeWAI*{yft?JAg53k15PTo8bs1moav78?c)gaLT~IUEKY4h|MJF%6N1h&d!1 zfEFD(1~DHE0cj_1DJdEnJ{K1wBR(t>0%Bw+3=A(X2e~K-6I5JY9y1~$1OzH72|p7` N8Oi__SwdDR?U1Oct<>lpbsoy$<%>4ZP`*n40r`}bR()Ttt z`anPc9HdB%(f@9$KW)_t+!_C!SY zNl5@y!TbCB`~3U-OH21xR{#*5_*`5{daY=p+i9fUY^2*!lhgI}_5A<<`OM7p^z{Ax z{r>;}A^8LV00000EC2ui01yBR000Jwz@PB9D;5n%Bn0vcIG-3yqv3GSWXym>Qd(3B zw*qIQNVKxt#Ud8s@W=@h3*!5He3wJ}EjZ|UIuvzyd2Smd0)L5ucrY(IZzKsRJpuwf z6fO}NHUuIyWi=inBNithnw=sKDmf^l7O@&23@a-G6&W}-6RQsz8acEK0L1_u9Wyfs K2q4ZNK>$19yrlgA literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ht.gif b/lms/askbot/skins/common/media/images/flags/ht.gif new file mode 100755 index 0000000000000000000000000000000000000000..059604ab20eb6f3a5063c0321ae2e8b1badc4a67 GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5h_eVDX5y0py7K@tXbA{g4008b+M)GJ`1187oMl)`K z-2el)YjMhyqv%|5)@6Cx>Od~!002^H)9y?={9H`*b7Wm(#C?w9`%*&y9K`lHCw+s| zWoyXG004!R;`1vPdW+rw0Iq6%+Dl^4TkZPe^MD(g2S=`tUA zis1bK0C;`T3l*p0EmYUr3G9Lf{dG&c}c8K5aU{L*g zZ^ZxrA^8LV00000EC2ui01yBR000JtK!i}3$piw6WM~;eG@dltVBqmwKsy01**8HYX?(2@onPECK=~2^|p?qNJv%sw5;3Kd%S7JvIiTq@}o?5e3G_9myR( H%s~J<4X=$u literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/hu.gif b/lms/askbot/skins/common/media/images/flags/hu.gif new file mode 100755 index 0000000000000000000000000000000000000000..6142d86817c12d74b84a4b19d7f60045bc0dfbd8 GIT binary patch literal 357 zcmV-r0h<0tNk%w1VGsZd0M!5hqT^01W^xjxG*;4(N1r^YimXo<#s90K5PIJ(xW@mOAQtd$j-n05|~QOiVA4FDi~I zB#R{0Dk>j`9~6NU-aAHk%VprRSYEHkW3d1LYzG;FX-Onn zhgF6J3W;1D7W4HDkIUyyCpYMsOugL>bxM^~Bsx4g3U+yWeSd*G5IBW-G<<%5f)Efq z3IsJV5ik`68XFuQBOeYoKLi1tpP{3r4h{{e0RaZLGo2KlHx08Gz!Jg?#6QNzKS2OH D+N+ap literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/id.gif b/lms/askbot/skins/common/media/images/flags/id.gif new file mode 100755 index 0000000000000000000000000000000000000000..865161b0307cfd1609732950cba4d9a6c7999aa3 GIT binary patch literal 362 zcmV-w0hRtoNk%w1VGsZd0M!5h@$vERqMhVoS?qdq@s5YO008Rh>gML=>64A?o|*Eh zrs7RQ+%+oWP)6leP3C4^%m4uEY-Gd$0Mh^f-T(mF008J=SL0Jj?SOgeXkX=AQ{hNG z;Nal={QUj>{p@pV112#cW>!lRp)AA@TZ~kuBz>JZt9nj?3$I| zJu=`!IOdLs@QQ=*jD^)03-0dj`1ttt_V(S~-QNQM`}_O#_4WGt`tfFwS*DGCZG0RagtD+mZL2{IlY9UVA2 zJTyKvIXgW+J0LigmzkTLpFceuJRLfinw*|L002K6KBcCovp+vJIXwU~#0CZj2Ou0A IHqAi*JCOOXTL1t6 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ie.gif b/lms/askbot/skins/common/media/images/flags/ie.gif new file mode 100755 index 0000000000000000000000000000000000000000..506ad285906c72324b51a6d2e9e067177444dbb0 GIT binary patch literal 371 zcmV-(0gV1fNk%w1VGsZd0M!5h?e*;dhXCjR0OsfB^nWvY$$IE^k-~a$hv`X}aJLBWy`k7kwdM5alQDVPh_<}cg z$alWJu+Y%Z_Ixbx`0)4d=;7bnSiD&Ei9hrp06?xk0FeOr`1sub0Qvd(_V)Jr`uhC* z{P*|wA^8LV00000EC2ui01yBR000J%KwMxL7#Ihqnay^&U;>3`%h&1nd_5FOr4m3_ z3Qf#v=xtaOSj1Zhhho0+ZbSH_)`1o+Gy=$TG9L~JHC_*VB>+7;IX^OkASMD03nL{S z0E|C83<)406eN_FHxrFK6$~H;2Lz%k9ybsZn-!@C85trZ5?~q#w6zQb1R^3j4H6O@ R94;>}78WifBs$hX06RAOp?v@V literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/il.gif b/lms/askbot/skins/common/media/images/flags/il.gif new file mode 100755 index 0000000000000000000000000000000000000000..c8483ae52f057c8131c67501b466bd5bdd3e46fd GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5h&d$z)rTd+o`NG1&dY<`ek@k(P{eGhQTZQw1l=tW7 z=jG+)m$UxY*Vn(lzst+Z)6>(tySwr6@#Ev;)z#I-#l^k7z2V{E$H&KTkNS$B`(=yv z-{0Tv?)r0>_jQ{1{rvrQj`wtw`-Y$OgPHn>pZZ~l^#@1kYm52m>Gq1J`T!;000706 ztNQZt^z`-h>FN0U`}_F#_}kmt`T6FNFb z{r>*`A^8LV00000EC2ui01yBR000Jyz@AX3SS%(N&TQ78Ku(Nk)L>Al0tW)&!|MIM zKc9~mtI=dOoDJapig}vSIlaR@6mAehL=tRNI6FE!7y?`uh6v^7Hz%001}u^YiprrdUp*P5>YP`}_P! zpGo!Je*W#Gp|je6zJUGUZGV!-`~3Uou>kk60Pv&$_RcK)-)V!4z4y>D`P4o9*i7{F z_5Z&BA^8LV00000EC2ui01yBR000Jvz@JbEDVj_+h2nO5{65f+!y(IMG6aDD_4?|E8002HHJ`#B&EQtsfIthX(C54AHEF=^& zGZsFBkrE6O1_lEI5h(#GEe#DFDIlMsq^78<9UTEH3^5uS8ylgc5v2jQ3?arM$S2Am J%qz}806T|Gt@i)` literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/io.gif b/lms/askbot/skins/common/media/images/flags/io.gif new file mode 100755 index 0000000000000000000000000000000000000000..de7e7ab385eecd5e55ff60cc67e06604c2ca7ead GIT binary patch literal 373 zcmV-*0gC=dNk%w1VGsZd0M!5hk9Dg60*GdAx`nK8)1|q)z}>aD+IFN@_tw~um%5oQ zUH}I&%uGl6=jQ+bZe3-z{imduwur24O~l6HsI1iay1I#n!|l>Z002&Gn@c|@i`(4n z^4;S5oS8~etev6F)Ya=)V6#I@sovo3?bO?TsAAjG&gr(ks>Ph* z!_4l?IpEIRTSuAQ$wl4Gdcnoo*L{uP;PFveu(G9OaGPHBprQ3&U-$C&tbwT4*z2iH zbD59BA^8LV00000EC2ui01yBR000J(KpG&RP!KhNfWxs^I$)d~V#R5E3ZO!S6`4_V z6-cK8$?*h&N9lkYV0?&1C5Jd!D3kT!5c&K_Dg*>6Efx~ T6b2F>xK2wL)Ya4r*g*h0BUh+0 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/iq.gif b/lms/askbot/skins/common/media/images/flags/iq.gif new file mode 100755 index 0000000000000000000000000000000000000000..c34fe3c44ac029c96bbc44f4fd1a814b8235902e GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5h>hlr5TwF#*M)d#ytK6*S_2zwh zeZJzt{9|KdV`Mx$JnH}e6ciNp`uTy#gZE5K{cCIcMMeL6diyvyTU%SS;kN#Ec2ZJP z{fdhD{rvodg~H~=oYJ26adPhV@ia6vz2m)XX>5&)jg`-q_vX5cu>1kC^SPodc962ANhPDA|fCG0R(s%EC>rXEG#VxED|S$cs&RS z2`(fg0G*SC0x~8hIynjoHZ?CLB@GHcKMe*B4kH4$5V^Smunia(8yg10v?I2>u*u5H Hut5Mj5z(S@ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ir.gif b/lms/askbot/skins/common/media/images/flags/ir.gif new file mode 100755 index 0000000000000000000000000000000000000000..156040fc578e6a7a1bc2399c40523cfa7f41e945 GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5h>Hq-wmX^8Wx%8{60E7U`<;$nnr{(44*Y4L!v`WnA z%<_bU{AFhI#l`X!754V_V!mSfy1D>&0Q566089W}yj=SA_It{E0Av9A)YSS!MEYA> zQn*oT!fQOPJaWZz`L(wAP*C$NE%!u3^(7_sJ3I0K0Q&m+^Yioe%+28d0Qdj^`J$q; z-?XpZu|K0fcgT10rKMB0Q&+lI_wn*Es4)09H~Bm~{N?5Q`}_6v_1ypf{QUd?i2(HU z^!@$)A^8LV00000EC2ui01yBR000Jyz@AV@AV`i9j8aLsTwt)zr_+HzDl=Jv(z{%- z98MzP@ziQtfdV05BU%cV!cd^Ue)u1cX90m|4-Yy%ItO`sIRPvY0fIg#g?NZ3CkqQS zgD5CI2Mah68K5931EdWx4Fd}+3>q3YHWMNwBo4L?6FaOM8zH_UBPAshvLZVs$R+^G M%PP()JJCS^JK%<H%{6AP`uh4~io)bW zLI40qah1w#lgUwhyjX(1-Y6#{SfxyOxh`R>^78WUd3Y&Zs6KA9003rQhr$2=S>ro8 zL2b=SW9KbF%;ngZTLPLvXX(003%_#`gC1`}_Mq zZ?Urg0OwX!>R49iO-$wGMR}ioeJSROX0EIFs6(2AHF(e8b8#)k^3NAl< z1uP_igM|PT05Svy84m~wCp8x$2NM$(B?|-|4>LR)J*%x277-{5CRr*xFFg-AI1)4^ TC@l+20RuM!8r2Ke*g*h0+u4{_ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/it.gif b/lms/askbot/skins/common/media/images/flags/it.gif new file mode 100755 index 0000000000000000000000000000000000000000..d79e90e99e9cd5e9603313cd6a840bdf23abd8f2 GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5h_e@Lxg#hB>;@;lg<^TZl6BGGWRrK`q^*uf8008nX zE?Bx*09OF<4GsSO{#?CW^8f%tuR>I|RRDbed&+wNWdKyURP`k#^BEaWwod?d0P`|3 z-~a#si2%aF!uK&TNwi7yA0PHBD|yIybjEb$<>dfx005By^foqFyjac6&GsxT$jHb` zvrG6lH#(|1Xu@dvTwL=tHTFbAV83D8+uQZ^_1ypf{r&y>`}_R-{PydN zd@J3P04Wauo4O>(*?IKSABDqIg2{K~!vN>OBme4q?$1tHSy<-VS(SxFl8t()nOR+_ z82|tPA^8LV00000EC2ui01yBR000JxKu0YFC>9gQ*XA2tx8hJl#0*Mp>0Sg0!8Ut&I4@2FfukLorfVND;ysdus$jd6&pV<1`G%tV`Ly8Bp4YsKNA-K63NLT L%*+xME({S;F)=14CLS9-epOaZVqkDu>-d|2;g6S>^(yPXxw#C% z48dE2V|K+PCnx*(_+)2iU%PfqW03}fJ3~oHiL0w?aB%Q%7Z<77(tm)Ur-#9U;cr+N zgEqsbPoFMcy!f+k;)|T5KXd2*NlS|e4?lnYyy;5Qf3s%Y4fa}6U9C7@@t2F|p9vH8 z#KcUQ9m|lwur<@(V3|SEzNA0?{$IX+wODSE5FK4vS^4?%=N~_QeETpBlj-E-sawYi(g7&(0-fEUvB4T4f<;-Nn_YF|o2; zPR>G=U0K|KnT=D;P)JBiLV{D4T~W`7mzRf!dD(JPQ(0DSeSPjD!ot!gWKYRDGFSru D;P{a* literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/jp.gif b/lms/askbot/skins/common/media/images/flags/jp.gif new file mode 100755 index 0000000000000000000000000000000000000000..444c1d05c59ed6a43e63fc7cc7b809f47422ccca GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5h`8zxI_V)Pr`0wxU)6>)K?d|L9>-R4&_%=4><>lAc z*XQTw_9`mr>FM3w-PP6A+uPgwMn>P?-}p5(_A4v+G&KBGRoU6u`8+(~%`awbW*4FfWef0J9{i>?b(b4r)RrXt3`~3U#lau|syY;)f{r&#= zmX-Riul(uh`+0f%et-M>`}Fkm@$vD`&(Hb!`TF|$_xJbw{QUj>{qpki^Yiom{{Hp# z_5c6>A^8LV00000EC2ui01yBR000JyK%Y>^ zZ-qQvx?SNNBcTYxnl_dwY83H$CD#LiO|W?MqSQ008AJGra%+Sy)(L zKtS_hW#AVdH?TYMU14m)ZE#IZ=jh_q*T3uM=grE>+tbJ_q%`yK@!;9cQBhGDp&(aW zTmS$7A^8LV00000EC2ui01yBR000Jsz@KnPEDA};in72gNLW1HQS$WNE|=TG!hQU1 zqmx*N_}3(3k0 GK>$0)Ly?03 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/kg.gif b/lms/askbot/skins/common/media/images/flags/kg.gif new file mode 100755 index 0000000000000000000000000000000000000000..72a4d412c805708b5f27c5a38622a46e86a14cad GIT binary patch literal 373 zcmV-*0gC=dNk%w1VGsZd0M!5h_@ztrYismlWAm3S>^nQ`D=X?MALtqy`N~7;BO~Mh z0Q7}9_QNy!)z^Sy}N#MfiJr@?Bl&Ha7HBRP8Y_^G{Frd3otEG4X{f_I*qA zRZ;RtLgy?j^jT5wKsN4QBk(~w_MSWUl1B1aSopv?^tUSXQ&aVKO6@otQ-kt?Kxm(j!%+|jWD|880;3=hGc0v?6DBMW3lJ+U92_?)1pojJg)9p* zFDNT9Iw}edk_CkpGc_D22{8%@3J9SS4-8^tW;PMGBO^TlAXYzF9ykXF1UWe*BmqiH NOc~A@0nyPx06Qe4k+A>( literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ki.gif b/lms/askbot/skins/common/media/images/flags/ki.gif new file mode 100755 index 0000000000000000000000000000000000000000..4a0751a221904a25343137a7a4a5aef71e112f5e GIT binary patch literal 371 zcmV-(0gV1fNk%w1VGsZd0M!5h@rQbxtlas-yX9F&_U_%w)9PDz%0^j|=-Axod}+hX z=jr9+wZiA)NjCP*$?R@k%m4sugwX&3cIsqPe3aPF*X>qgj_Sf#^yO0uE0F15PK%z~ z-Z&)b-BpmK;Bbl4VSUc>-FyH5SHA!N*4*%_xZmPJFEmV*2Fo_ z*3sn7((l~9Q)#JVahYm_&5fD6=@ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/km.gif b/lms/askbot/skins/common/media/images/flags/km.gif new file mode 100755 index 0000000000000000000000000000000000000000..5859595e809b6e8a98704cc408a7267920ad3e80 GIT binary patch literal 358 zcmV-s0h#_sNk%w1VGsZd0M!5h01oTx)d2YTQ~LV)mxr|gYXJ84_5A#D>GSE^G#2{$ zYS`=8Xr1@rNGhkd+*cC>-6gY0NwH0 z003D4A^8LV00000EC2ui01yBR000Jqz+1paEE)sIKtVU7P!3SVWh$sDAC{b~gnq9G%<3I~Ib&TxZo_Wz4GoNldUwcoQc+PVr+09H^hX}@W4 z$7M{kOuDfv^id$~I{@0++TY*cX=h@foQj5lTxDTNZ)#U`Z$igy0G*nShJt3@0001q z00000A^8LV00000EC2ui01yBR000J$K#gzcix?9~4dBO&K%OUyg~VzZYz-9Z=Ys^k zsf(+i7|j@BxE&= zfD;Y}2OkwG5e67L6dfQd5*3^uo+`r_g2Jw5vA>G4cV05t6HJ39bN@c<6qBz6D!#KiMNMeIXE04C?|QdIlO%H{9> z07CBU008xJbKL0q_H}mli;Lpz{`=nD`qR_j>ip&>-Kkf?qp>#egEhb6n~@L!P)=#kB|46ne}gQ`^d<%&G+_& zgz!;PA^8LV00000EC2ui01yBR000Jyz#3&pF&GI*f<(fR;5N{WXj&?LJ_tlX2FuMr zJ5OM&P-K258UXfsX&j7@#S%%e28qiW#EbECBM)_V2?7lO9Udbj9wZI{c6b7Yh&vTN z6(I}{gp)QdhzAE6438<20yZcuh#(3-4<{E55iTtQCMJ44S6EsF1uDV|#5YPz91su~ M%quGs&?`XzJM&4QW&i*H literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/kr.gif b/lms/askbot/skins/common/media/images/flags/kr.gif new file mode 100755 index 0000000000000000000000000000000000000000..1cddbe75b3087d8abe0be4845b8339b490d1f713 GIT binary patch literal 385 zcmV-{0e=2RNk%w1VGsZd0M!5hbeiF&rli!=)A@*pe0+RUYQ_6SMf>yfXlG~P=jHkN z`NYJ;-sIx-_4V1=+U@f4{Mgv(>FJ7!ipSLL%F4>_4Co$*W>Bw*WcaQ;os%!>*ww5^7Q%Z@$l2x+34={uCTDs*VScizN*CV-{0TH zd6Uc0&-1ah(9q5D_4W1i^R=DBqpaE5+}zL8(a+V>{{H^`{r&Xx^z!oZ_xJbw{QU9v z_5c6>A^8LV00000EC2ui01yBR000J_K%Y?9={z1K%}De~FnqtCW1-9CdcEC-qToG$ z5ecD|gFqAx1i`@z`EU*i>M$@E20IjD(Ez=15iu`z6%_*-4mu4O9XMA#GlePzEj&6I z4IU3y5i%#DGxmY1OX%fq5vlW92*%oB|R(z2m(A46FV>*2oyUO8a)aN fC=on74Gj?iC_BeXO$-bnAQ&bdDK-`sEMJYmMn>ov z8Bb47;^E>l>J20u+s5T1;X@Z*S%*UzWbssOwF!_RG6&GQb!{>9vG+m7@|Vx4Va?&x;h|s8^>EksaoG6! z)kD0~h_BI+aM9+z*OX+^^?1j!Y1aFT%dCRUyWhV6pVFt_$~foGMu({bf!E<)&gZJh zcmu!xA^8LV00000EC2ui01yBR000J)z~48ZQ0cPx# literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/la.gif b/lms/askbot/skins/common/media/images/flags/la.gif new file mode 100755 index 0000000000000000000000000000000000000000..d14cf4d82c62f716ee124e1b2afbf8bd79a41c0f GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5h@;N!BW|MW1+gp6p`T6;WLw!AE%7&og%+>Dva&z|p z05o36ggkg`i`%h(sQO}J`dwX}Ta4QP0A+*K004**002pC%;o9xEm*|-Yio;0fSXi= zWQ5rMdV2LlMC||ou5z0I7PwV;)8qgE_fk^#O-=Y#SMe_|03*I%f!Fu<_ic#Qe>8A$ zjoVOl&P#I9?eO<~n&4@O+E8@RvUs17PJ)$HiA-?KpJS82kg@FV_MTsmO#nY_15fb) z0Q~>}A^8LV00000EC2ui01yBR000Jyz@KoaC>DLl&*#@^b`pt9=J9}-NTp9NN27s2 zfxs>j*=%Zo3hK~m$R+{=Vr8it29&j%!+{Y97c&NfA`~G9G8_W}e*`KM3kC}=hA|u| z0|Ppa7ZwW(7&n!eA}<_r1D_Ze78E=VIAb<78U+luEG#t@3RNaoB_19C#3#lQNIps* MBh4cc(9uBvJEn@1%m4rY literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/lb.gif b/lms/askbot/skins/common/media/images/flags/lb.gif new file mode 100755 index 0000000000000000000000000000000000000000..003d83af5e0ac0bb96a30dfa4932a43915e3fba6 GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5hD%NwCGBEU>P{CMGEg1KS zeDQ2t^78WW^z_5R!lKNh@M2NS003L0TlxC>(*OYVfpqR(PCb)7+W-Kx0092|{q0*y z=0r06{r$uM0O>_Cz5oD2mPARKO855m_4W1hdTsvx{`2?sg}Q{x-^=AcF8%xb?N~@C}y*Yt0X(b7Px_hsW==K zScqkVAP_zl5J1Dut#+tdU@{^ZNDhF>)amse5+XJyb$5AuelQX`AtEVucLe}_JvR~( zAr~Pu4F(Jbl9e|TCjl214*@(3p9K^p8!TlnA1VR@3Arsk6jxbevSu$FDk{865gr~O M%o)xZKG8t{JI9i#CjbBd literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/lc.gif b/lms/askbot/skins/common/media/images/flags/lc.gif new file mode 100644 index 0000000000000000000000000000000000000000..f5fe5bffd2017b1a599d94b4fd5745098fb0e845 GIT binary patch literal 259 zcmV+e0sQ_)Nk%w1VGsZd0K@-TAu z@%Gn5Hf!~fxAkFQm2{o+Dq`bplkGn{JYR_JahUI(!uQRgGWYE*V~z4EUfn8R;k)Ab zD`W7m!s?EOgeqX$A^8LV00000EC2ui01yBR000GmAO?&;VKN1yG8_X0a8NbdhBOVL zLocom1j1wRpil(7Kn@{Dcsz*+MWH~xbchE*A|YTR3d}%LsOSWU0Jk&1U?vZ;S@1|W zEEY#%!l%dubPWa<7X||W695z$1q}iN3LOWN2OR~85g80V2n(K{3K61=DjXaTs2Uj` J8Lg`!06T6AWa9t; literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/li.gif b/lms/askbot/skins/common/media/images/flags/li.gif new file mode 100755 index 0000000000000000000000000000000000000000..713c58e1df193b0256be462a3437c95af926e600 GIT binary patch literal 359 zcmV-t0hssmzVQaqZh z!vGnld1aW&008t@L+xl)DF~@ zSCHT;5Aa4W>|sowk+<${TPX`+X;O^vO*e2`laHREMR3Hv!KX%Z$msw8@N{DP;A8aM zX8-^TA^8LV00000EC2ui01yBR000Jrz=d$QAQ+E?OlA>Ms8Tt_X2aohIv2n)#4`Cr zE5OHtlNd0c?Wre<+{WcI#k160vSM8vL!sa@Gcf~$0}O^XEi@n?3_m*-BO?wDH8n3K zB?1BvH4O(AFbD|=0R;sUnh-7l6$d1uq^78wE+_@9JTfv5FrlykrMCye4aLR{70Jp$ F06Pnflbiqm literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/lk.gif b/lms/askbot/skins/common/media/images/flags/lk.gif new file mode 100755 index 0000000000000000000000000000000000000000..1b3ee7f572ddb8199166f65e8803233cdaebca1b GIT binary patch literal 377 zcmV-<0fzoZNk%w1VGsZd0M!5h{+C$v(@WyB7rACo`;AEGyd>$jN1;4ZaHnYW)E}!% zW?Y+G^2j5eHA(i>Q0~Vy_uOQ;Wiqo`Z25*h^Up)ccqB}aNb$@)o-{hmhC;)6bf-mJ z#BUqfm@L|xL{*nm?Y}d(V|1=kX0cgR>%uPi*;JV=LjIy<_SHC}K|J~2a`o9;{gO~b zj6~k2Kv9%W%7Rquz-MNmW!9KB$9p@^f)K4vPsoC6u24#^QeDi1F3^N5-J&1%*HqJz zNBP-TA^8LV00000EC2ui01yBR000J-z@P9}>Ny;N0EKe7JRUy;;&f8QG@+10A{zKQ zKiL8RfM{-9MQ$?~?ZV@-L}7q=JQ>`fQW67L5OW47AT~1*dp!#uECeee3Md&M1qnJ0 z6%Gq81Oz<_8$Jx93>YRH0Us6w6Al{(12i2GJf^5GEfW&~BLfY;CKwSZ9274Wx&baD X2f!vN5j9Ls4Oz$U literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/lr.gif b/lms/askbot/skins/common/media/images/flags/lr.gif new file mode 100755 index 0000000000000000000000000000000000000000..435af9e506f70a8cc09022ae3029373facb3ee0c GIT binary patch literal 360 zcmV-u0hj(qNk%w1VGsZd0M!5h;Ami@006B30N!R@(LFP>wA0{cU&H_a-eX$bURAaL z0L}mag@?Gl00885ZR2ri;caExT2b3qO`HG#003CoRZC`RrPfSB{{H^`{r&Xx^zZNW z_xJk9006lF0M}Vh++0%OY-8tyeW0Po+gVT3L^<{6<@f99*ic8`Z)VtERnRakXhcp5e|E(;u`37noK1Oy)wCUthEk~66;OHB$20>uHx6B9o_ GK>$0mai$so literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ls.gif b/lms/askbot/skins/common/media/images/flags/ls.gif new file mode 100755 index 0000000000000000000000000000000000000000..427ae957e508a6c90df90c68f2320b119bf372e7 GIT binary patch literal 369 zcmV-%0gnDhNk%w1VGsZd0M!5h<>loTn-%~u03Dnr01s_coyhC#>_wKu-`(3QhPvwU z^F*&lB9T_py_GPCz4`h1JF7bYL;y65zx(<2`1bNAe4D>{miq3 z-_Wu>lENi^wBggYHmEi;rZV;K<^25nD1x_as?K<^(Hx*0%H8JxF@pd>jnU!i)xevM zzS{Bh_0hVG_wwpkqRl_4Nx00`OQv)D`};(Zy>FefA)7Sq=GVrmdHehO>FMeG{QT|h z?fUxqA^8LV00000EC2ui01yBR000J#K%dZOFz|SQBw{&J`93faXQa{edb{7FRUr5% zuMMY))hZ4N8^O@PSTMEn1Jy~8Yyw1swb43i7#0Q1BE`QJsoDir+URq$bA z_uD)C=X3hyYxl|k{nP;W-&O5BJ%l;7{1gcNpq%=3ZTkB9`1tq$YVH6;-d(c!gv9#*g7BZ7tSf@AJVX6BGjjJwH7-dJBDjfrEu6Jso$1Tgt5T@ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/lv.gif b/lms/askbot/skins/common/media/images/flags/lv.gif new file mode 100755 index 0000000000000000000000000000000000000000..17e71b7eb6bb2dc4f4e63837cd70be048d08f1a1 GIT binary patch literal 363 zcmV-x0hInnNk%w1VGsZd0M!5hu0lhpG&H(YRP5Q=oE#jaGBUzzYjgkrX8-{F{QR*- zMp^&>@!{dhdwRxlbG1=Xtv)`;o}RKwN~JC?>B`FV_4P>r0O{7&zFb^^008>>`uOXBfMEz^Yiom{{EO06y@dR=+o2W&(EqnJnGli#(8&0BdeM3Si~wcH zWdK_MOTJ4`!BBF|a&F9S0A&DS#$i#vQ6;V=O}|YrvoJcgI$_CS08#)|!&QRRg8)SU z089W*z)n}gS2eUXe$#$x%4$KkK_IIjJ+wVHwKqAlIWMsNmIg8Xv}C?!dXPQLsh_4fYX3GxH~nqHaE66Nxn%-yh~lg zT>zN?A^8LV00000EC2ui01yBR000Juz@Jb!7$g#cB9w9}<=Yc$F ziVG}|X;27;)=uKZg;V4P1KlcBD2pHH0x!=EIXV?6G7AF;eR61lgA)Z912JSBb1oJi zjUyr)2@W%q1ePBsDib3BB%BW&JfWAQrT_pY8vP*D9* zQS~Jy`a(kaR8;@^dKPd4Gs57O6~vv^*%l3008@8Ve~pW`bI|jL`3=k0QLX? z@&Ew-Sy}1;0RMY?^dBGd85#F5F#Kw2{AOnQU0vb;0Q^f!`$|dqJU#O=GW}Fk-v9vl zIy(M&dHO#;`B+%Ga_{m6nG9Rd>Ax1Hw1McAs2WLHYgY` z6f`t43y=joCK@A_FD-PPAq530EE*&kr2`0$sSyzdEE`%}2@)6r6c;-x1~NTMO->#j N6%{5XEIrae06XmGiQoVL literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mc.gif b/lms/askbot/skins/common/media/images/flags/mc.gif new file mode 100755 index 0000000000000000000000000000000000000000..02a7c8e1bdcd38bf6ce52dc5662d1d75026b67d1 GIT binary patch literal 359 zcmV-t0hs(J z@$vEgU0w3>^YuSJ>gwwBW@i0#bo***^dKP5&(G)Q=lpMP`cF^&aB=!vT=-K{`Bzu= zL`3{$W&C$|$H&LX%gOm?XZ!p6`1tts_4UQY#rF30`T6<%008sz^ZNSw_xJbo^z`!b z^8Ww;A^8LV00000EC2ui01yBR000Jrz@PA!E9NE>gVJy~{5DXBD0LdS3M$qL<=k{S zh{`3QSTr7xNTLfClg;P|r6tKZ!sGHeO(A{i3=A(KRWbzt00RRFArc$`kTW|tG(0yo zJvu%;8Xy5Pl9ZO1nm#%iCILH>m6w@592`D27@?x0nI1l~7`*}l78V2qB^ogqHZV3p F06Uswuu=d3 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/md.gif b/lms/askbot/skins/common/media/images/flags/md.gif new file mode 100755 index 0000000000000000000000000000000000000000..e4b8a7e3f67097d65193a435018d68d1a0798af8 GIT binary patch literal 367 zcmV-#0g(PjNk%w1VGsZd0M!5h-B3dS0I{c(RP^B~OJ~w#ecS!+UE*X}+e$s_#sL2E zdxMwa_T@3wGbO120GfwGY=qpl008>wOY_(!ev{(-?r8k%RQufk<-GvrZe-g=IChNQ z;8;xg=tTDAJ=7~4{L%o!0032S)?azq+CVawgFOHMpV&Jsp#T6{b=dgkK=$QK zj7na3)ioRq>SWf5>VEgl*qCmap|6ni3lGcpehgbe_P8#y9A z5-l$}B@8DGl9ZPO9V!o<1E3!WC;<>Y1TQKK6B9hCAFHij1uix=H7g!8Xlbn`$R-I1 N7|j?CDA6cE06R$cn05dF literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/me.gif b/lms/askbot/skins/common/media/images/flags/me.gif new file mode 100644 index 0000000000000000000000000000000000000000..a260453c2f73f32c07a75de3dcc5d990fceff33a GIT binary patch literal 238 zcmVjVnU0|@E6KJ?8$=pz#6vOEuFvF)}# z;jSy>oHOJA2LItkA^8LV00000EC2ui01yBS000GR;3tkw=|CuST?pX5upkRc``kCB zQ&LacI7(;X=?w-y04{*ZbTFMGvZmApHX4mndfx o1nz;Q5ovVj2NQP^Qb%-vf-^D)jE#*G36YTzlo14%m;@mJJHTpU4*&oF literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mg.gif b/lms/askbot/skins/common/media/images/flags/mg.gif new file mode 100755 index 0000000000000000000000000000000000000000..a91b577d138eeaa5d0f9bc8a00cee9ddb3305062 GIT binary patch literal 372 zcmV-)0gL`eNk%w1VGsZd0M!5hO0r5Eo*epARO$c#>gwt}tUXz}TK#Kl05$;iBqaAt zOy=h1HL5k@;^Nxc+S5HKFQqT^007W49sB?Q3zZ81D*ymT0R3iW`$tFmK|=XFJoq;@ z_c1Z{D=Q?TB>YTFGo~^sq$(7d6#4o2?d|Q~-`_T{HrGcp{9s`hq!%KpBKAZ?+fqQA z008=2T<2$9^*=xTaB&cq57$mREw3&6{QLd<{Vbv^{)L76`}_U<{rdX){QUgy@9+Np z{{R2~A^8LV00000EC2ui01yBR000J&z@D&2ni!3dWMnYt{eC_lE_OIvP%eQ$(S$}1 zf)6I6*>F1CUeu?%h{@(oVl^N|bvD$vd`=`_0|ab5JTELWCnpaN5)v~l1Ufu5IR*s` zG&CwIDJdov1Uxx8I|d98Aszt%A~GNd3p_hJBLJwXtTGf36$_*zBL)Bf1(ysU5H}SK S4J8gG7SR?6)D_kUK>$0+p_E1d literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mh.gif b/lms/askbot/skins/common/media/images/flags/mh.gif new file mode 100755 index 0000000000000000000000000000000000000000..92f5f485c3bb6e33dbf40656bda07e3742c071a3 GIT binary patch literal 370 zcmV-&0ge7gNk%w1VGsZd0M!5hxy0R=s?qhQZg-KxskYMq0BsmTlZ|k9L}{`3+q?h( zR7+~BwRBrkbh12StM=p2001wAo5ymG!dYjT_kl4(W29SsxmkI%WrDf@02nq~q5l5< zG+3T%hPy{i_^kXs|~^Yw2A8*Wm7JgR#$@g7wX=EKr+wc$#u) zga8S9A^8LV00000EC2ui01yBR000J$z@Jc9C>9k+r0W*}m?8s-MAX4pF1;1)7uak9 zmqsJxaa3M2*zHH~2F9V9!xy6n9;ZTt0KkM6H4_YWC?6#`1p{Fr2sH;XcON(r7Z3#! zV+eu*2`K;_JR=$z1tNb3oFoArAO^0Z1sFX#I{*?IAi1t192p8MEd&yO3mz2}4jLT2 QHYx>35Yf^Z)YL%$J4-p13IG5A literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mk.gif b/lms/askbot/skins/common/media/images/flags/mk.gif new file mode 100755 index 0000000000000000000000000000000000000000..7aeb8311b2b6a4e18b278d1774459e57335c1b76 GIT binary patch literal 382 zcmV-^0fGKUNk%w1VGsZd0M!5h{8?7{*f{@h0RED8_Bl8G+;I4bGyD4h^Z)?$ye#Pe z0Qq@4_{>KBb#?o_NBKub`-VsR###JvPyA(N_xCySnG)&h0Qash_NpfT`gijf82*1* z_ADp->r?wvO!Pn?_H8-zM>O_oC+#-?`<6rdZCLqcNA`9q-v9vgEga~^0QF4({Wk#o z;{ftO6#bV`_Bbo@S`*#f0Q2S={CH;j@M8XmS@vBk`Mx>y*dOuA4)WtH`maL$zf}6u zUHXiT c9uEu*1koW0J17S$NebF3Dhepy-!ee}J1K9L&j0`b literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ml.gif b/lms/askbot/skins/common/media/images/flags/ml.gif new file mode 100755 index 0000000000000000000000000000000000000000..53d6f490c1777de4f9d44195d5e188cf2a35db0e GIT binary patch literal 363 zcmV-x0hInnNk%w1VGsZd0M!5h?Da!Rv`Xjz0BXW&`22I|=m7luY4kBM_en|lS6BS} z0QD>^^Bf!ihyeE3D0<0y>GL=BBqUL`QS9|j^wb<&y<7l#0CL82I;uJVXaM*3MDi08 z`T0Bg`a$^kHT69`Sh`sNR{-%14e|g0MX^Qu`$zZpFZT8-{QOMy^(0idRNw#r{ryqn zqX6&tQ}py6!4O&4|n0B#;M4t{?E78eo$BmjCQCk_k@ z8#yZ$1CTlp9+W8*m>UP110*^a5TY*>8zLg78C)PL6EPYZH9ci!KNcAoAt5&f$OSDc JD?iRb06S!MmRbM+ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mm.gif b/lms/askbot/skins/common/media/images/flags/mm.gif new file mode 100755 index 0000000000000000000000000000000000000000..9e0a2756d2ebf850087f556cf77289009985f3a1 GIT binary patch literal 365 zcmV-z0h0blNk%w1VGsZd0M!5h`9nkXB_-Y6`1CwI`dwZ000610?d1Rf_%=58OH1ki z0Qx>Y@=a6#0LOiO zmFiJU*Kly;czNVTM%}@|@=HtGMLW(_Sl(S&*Yq|v`$$OfLrQFA+Hq~)c5>d`008X( z0002PA^8LV00000EC2ui01yBR000Jxz{c+1@_u`KpBFCl2H5} z6@dU{a^V0ggeKDchQ~7wS}h4+4h`5NWvInMX&QAM0|z8LHaaI9AvAOW9Up~0Dhv!c zIRh+$kRK2?2OAz|3nh<{5D+3G8z2Cs2oZyx7Z(#F1z8qb2^ataH#Z^^6Fo~!J_-s4 L%p)T`&_Mt@&VP;h literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mn.gif b/lms/askbot/skins/common/media/images/flags/mn.gif new file mode 100755 index 0000000000000000000000000000000000000000..dff8ea5a63f0a3ec96dfd0dcb95a0a7abed8083c GIT binary patch literal 368 zcmV-$0gwJiNk%w1VGsZd0M!5hK|%Ta+GzgCTz7N&^f58_NlE{EdHYpV^(-tZD)v!P z{qr0g`)FH4IPLW$B>d4${Aft?00000>He%%?EnB(N%8z!Tl!r+X=(iC008n66ZJhk zFfaB$J@rRN`ZYE9@eK_C0Q>*|);l}-OicXw001N;_22*iT~hJ;*hC&4^z}A2WnlLn z8SehbdH=P0{@ro=abhYY@cxH(Jvj0tAMZ;`_b@B*`m#j+-ds&V?)h6){ex89008{} z0RI30A^8LV00000EC2ui01yBR000J!z@PBvlZ1{TNn}Z({0uLHVKBg0S(d}0;4}yr z7LX O63r4KCnr7AK>#~`43F&q literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mo.gif b/lms/askbot/skins/common/media/images/flags/mo.gif new file mode 100755 index 0000000000000000000000000000000000000000..66cf5b4f05dc02126365be46762113446f171a8b GIT binary patch literal 378 zcmV-=0fqiYNk%w1VGsZd0M!5htJ5t>eK)_ z0QC0sQngY=u|?YK+Iq=)am8`~g8*H=UAEn}m(P~~SpZMAPXI{(M6N^tbO3P0Y)Y|8 zF{LnGyi7l?KRT*93zZ81EC3pu8d|!738K-oN;DY5a``m@aiMCmJs`=!Yv%|;$A$NP0yis$eeV4+6o4SOY!gi9p zM{BlIcBwmInnGuu001C@l!%F>i0sgicaElSjJR!vwDiBKYoDF;_+o*uV2ZQ2#p3<#r26otkD|Afrn#QA zrT{I0A^8LV00000EC2ui01yBR000J!z@Jc9DGrW@Wby}S0t_sN1J>IRN~QzTvf;G^ zw9^DZx#}tgQ1uJ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mq.gif b/lms/askbot/skins/common/media/images/flags/mq.gif new file mode 100755 index 0000000000000000000000000000000000000000..570bc5dd18a8cd881e03c9a56c3ebcc514191fa0 GIT binary patch literal 379 zcmV->0fhcXNk%w1VGsZd0M!5hTXLl}RGv_4tisCRo~+NNwAAM6@??9o{QUgT*5k_3 z<+j1v03U?2z1I2p`2YfQ=H})A05XA<#O&_!U3RgBn8lT)&AGzb003F^_4Q9`sk+79 z)!F8Zoy5)5<#~_6003wJ07b~q;?UUXLSCYGjK03e-o3`%_xJh$02lxgeZ$M$SaGn5 zoykaLr%7h0`uh5Eh`rk0=-}n>)!poAfw|q{?_YSayv5t;>FMF-?bh4q`}_O$_V)e# z{rC6xA^8LV00000EC2ui01yBR000J)s+bJnQ06YHGuI>N; literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mr.gif b/lms/askbot/skins/common/media/images/flags/mr.gif new file mode 100755 index 0000000000000000000000000000000000000000..f52fcf0933bdd5a8e30a1d2f7d76b97f728c9ed0 GIT binary patch literal 377 zcmV-<0fzoZNk%w1VGsZd0M!5hfXF0h!e{`M05z#K0E7ThwNXT_L_@GK0BHc&>@Y2) zEdV_LGo>>Cc>sFJdH_lQaKv#rmpNj-VqLyn`ujyqt4;6rEkm+HBcUUs*-C4>Yd)?% z09ybVn;9mPCQ7wRAB!JPwogj4N;;}KSh-nLxKwt?b`+Wv04D%Dt~*`4T`rj}g2^NRIXC!;d4wT zq*5W1F>D3{g^24V?FtnNg*G@SFop-gq>J@F1{Wy|3@{!T8w&&wB^5F}G!YRm2{||& zBZ?3qA2SIyEiE+(ARQ+I3k?Au7aI{Q78U@dCpQc~s;wylD;^%SH#Z7D2v=bRA_it7 X0}KiZC=E*-91qqD3qRT@3qb%oq~mv! literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ms.gif b/lms/askbot/skins/common/media/images/flags/ms.gif new file mode 100755 index 0000000000000000000000000000000000000000..5e5a67aa882715433cef156d6586341b5f50c8a4 GIT binary patch literal 371 zcmV-(0gV1fNk%w1VGsZd0M!5hX)9;rcXyqRRA+R#002$RY&qox!(I;tiatq!836tfEpCMGHrtpi#hprWH0 R%*0Gi1Ps#B9@W)B06X1&k5K>s literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mt.gif b/lms/askbot/skins/common/media/images/flags/mt.gif new file mode 100755 index 0000000000000000000000000000000000000000..45c709f2bc68af31bf62940c2765474f1798639a GIT binary patch literal 369 zcmV-%0gnDhNk%w1VGsZd0M!5h{9|M0=H}JY)8gXd+}zyq6BG0D^7T48^Z)?t008nX zF844n^(7_xQ&aH`4fjn=^*%o4008+yLjL^y_A4v&A0PNPHuW|(^BEc9008;`0Qo#U z^D;8u008*-`1kks_4W1d@9*vH?fF+%y|}p4$;rvc$olW^`dnQ3OH1=LHSgWs@$c{U z>gxP*a`^4->+9?M^78um`S?ag`~3U+@bLHR?EUri-2edj`T6_%`}+F&@$vEg{{H;@ z{Qdp?A^8LV00000EC2ui01yBR000J#Kpu-nB(5|Xgc6ZRfMkMSulKvT007je@kkg1 zq!RTPYe+Vh1tsO2n8vwT33!M%Q3az80A1@CO2Pzy23OFzU0RsaB P1SJs>85tco)Ik6{yXCVQ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mu.gif b/lms/askbot/skins/common/media/images/flags/mu.gif new file mode 100755 index 0000000000000000000000000000000000000000..081ab4533607939fa2bb70838fc34e35024272aa GIT binary patch literal 358 zcmV-s0h#_sNk%w1VGsZd0M!5hXLpgiSZP$WRO4r7SZaqkShP8&IRH}tsWeF@M5grg z9I{ASQfj;R_bo=PMwrei`}|AYQ&U4-vj8yw`};!q`8v1&07+!I$^ZcJ@)M-iJ^lS{ z02xc0(JTNK0F%ocb%UJr^fY*j%c=kX=5TR}#tlqfeL7RE=z4lqal!oj0001oFQ6~@ z_%-$QB#@o3T5!GL;Q+~dh!2YZVYvWbx?nF&tVsX>hX4Qo07T(lUZ~kbx!`7w$P=H_ zG{XP@A^8LV00000EC2ui01yBR000JqK+JDAv>1=0ttvyIpt@ zV8r6K62V{$#zi7IootDOBv($Q)aydQ2AAhJFCc;dgan2JCMEzLDjN_G6ciOL4Gk^| z3K=0WjgXR*mY5luF*H0o8X7(w4-X$7JtZX%GY2&_0=W*m2E8o59tXn16UD|J$R0re EJA^uk@&Et; literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mv.gif b/lms/askbot/skins/common/media/images/flags/mv.gif new file mode 100755 index 0000000000000000000000000000000000000000..46b63875b1fb72f602ec068d935042455e4ed795 GIT binary patch literal 372 zcmV-)0gL`eNk%w1VGsZd0M!5huG&JNjG2bX7 zUf*8qWL@xAR&wZd>Hq*(-dlR?e&j4S;mhrjc|>04A$;8)`S z0OtSzA^8LV00000EC2ui01yBR000J&KoICh^cj7XWaYvHNuH9y;BY_?2%g5`;v{rB z7+xY_k&Q@zMWCA#i9Uv10T*cjAc5Ziqk1!D0yZ}hCL0QQ10E z4K5!A3<5L;Aw4e-84i~V69ok&Ius@_FEym52oPo@8ZjFiD-RDoF%A`09$5$1RfRXqB literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mw.gif b/lms/askbot/skins/common/media/images/flags/mw.gif new file mode 100755 index 0000000000000000000000000000000000000000..ad045a09c124c010eeb1ce5adf28a3b5f28fba4c GIT binary patch literal 364 zcmZ?wbhEHb6ky*$`1tskt};Cv73Hj{sjsj9l!1YTh2fi-*-vZhJO+lp3=FYLT>tp{JFRo-FgKeO z8=H10ZDC~Ol9G~$h4%Iv?WGsUM7y{oF853{H?QBG)m2&PGtDq@UsAD?lfhC0<;BVj z#tcB9_>%=hG3bB@ke?XXIuFb*@UWWOb|OASveA--DS+GID~IwG@e>Cgz7aB;c;eS# z$*AZytAk!GPFv(pDNbC$H~Z|;EgM3##FdN7*m-%?YU)H;8jH)@I%?}#SX7mz%uO{7 zB_u34I2ac(TB@+>@f!)t3kcX68#8aR;bOHC5s{SS=hqfqZDS*_m6cWMxa>(;yE9x| Ic8(0z0Cd)M=l}o! literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mx.gif b/lms/askbot/skins/common/media/images/flags/mx.gif new file mode 100755 index 0000000000000000000000000000000000000000..ddc75d04d8f2b05b315d7960abebe0604fd9e1fa GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5h=W1?MqI7z=lG`;q09yu2oow7kOSk|4gMO=WUGP6008&*_j9z3-aSIuFE!ygI_=U{QTe5#NOoIjeTn0 zPgCSzWb*Frqq3jR&(FZHllS@f`1JJRN>BCo_B@ne!esC^{avh*CKMIRzCIsgCw literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/my.gif b/lms/askbot/skins/common/media/images/flags/my.gif new file mode 100755 index 0000000000000000000000000000000000000000..fc7d5236148edbed734df8a666731eabfe88968e GIT binary patch literal 375 zcmV--0f_!bNk%w1VGsZd0M!5h^ycRK@$vKMn%&Wc>gwrCP4ndB-bGE;007*krQl6hwg3PR2bl8W;?|v= z+c`t-!oudp#sC1t-m0qLEG(0d(AyjwOiSF{&w|6PC5LxQv8}`L$Hm>dyYA)gZEfLq zb;9-U_3P8q_ut>$)TQIvp5Md6@#pS4JK6i^<@fFF57%;0^5q7T=u^K8V V9UTu35D+*ZAQl!qJsTTA06XF)t_T1C literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/mz.gif b/lms/askbot/skins/common/media/images/flags/mz.gif new file mode 100755 index 0000000000000000000000000000000000000000..7d635082a690908e3fb17ac14c490b6e39eab441 GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5hzn{ItTgr0 zDoM0SRJv4k$#wbKH`<7D=i|-KQCR?l0Qb{IX~JncI6L;oUi8m7`$Jo91@Pl4&nF6HJ%NMG!|np7M&mJFnd`S z;Dk7V)%HGxlDY(F!(afG;L$pKA~KIBQ>mCt9zFsFHx?imJbfl7Cn_oxf;$!uh#4y@ zBqTa1DJwod8WO{DxC$Yl79744z!$n; MATw7ID zxc~r<05kw8qA8o7*lKFS`8zusoEwLP!}w-x>*4D>t32W0@A!#{^3v7p>GAk@cW`jU z004T%z`N>543qa92rdRbTTX? z0zpAR?ol+JQ4KNphQ}Za=9GS^I)opnFrH;D5NQ`CF)Iu|8ww8=E(w7cu10t>=AFvDtISC1?JS!^|BqSmt R4Zlq`It0=T)Ya5M06TOHp8)^> literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/nc.gif b/lms/askbot/skins/common/media/images/flags/nc.gif new file mode 100755 index 0000000000000000000000000000000000000000..b1e91b9a80f4e4d69a3225e1dce5faaf099e0499 GIT binary patch literal 364 zcmV-y0h9hmNk%w1VGsZd0M!5h`1UqumfigBbxln;GlW^1nnM5xtQvMkiKydqb4~mE zOZ6p91xLs897bA=+xIhAq;jj9Wutth<3o&Q{rp)beNdsHPy72s04BQtEdXStf&etz302BcAJz(Vk0GY1JkF)9aD^ih>H220taUFf^?pvR{s9m6a^}#yc z;&5DEKu~{b{JDF^sD}N%gIJMx)7Mb*^IoXOQ@7N4B>**Zp5W5ZnnsanRD^C$gw_B{ z%K!iXA^8LV00000EC2ui01yBR000JwK%daZU~G<dD>FPV6FU$1Tl#-7C literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ne.gif b/lms/askbot/skins/common/media/images/flags/ne.gif new file mode 100755 index 0000000000000000000000000000000000000000..ff4eaf074e9ffd097b3820ad9c56fdda7e336fd1 GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5hRJT99`H4pOc{Bh|001ul0BivKq-omi z-5Z!3)z#HJsyzPw{?X;pCZ8tq^78(xbS|PU_V)Jd1_1LS0R5U>_T}39IRHtqN&A&l z`OdXry<+|C;`!;_)#%mw`uh2@hWqQ`L#{(Jr8EBT<^QjG{fh{R2`<`I?)V<&E-~N_a_1VeylTP*H)-|X#p~s>8{QUIv z^!4@iA^8LV00000EC2ui01yBR000Jyz<_WV$QUh1OT`j6IVI49ltyc9a+Sc~a=C3# zC<0-%DijD6oXlkMct-Hg2ZPaJUO$Jv*Q<#J9268kK0gc*dp#*GWg>$<5r&9-DIpvK zF*Ae_5k3qs0yd&H3Z*O{AP)~9JO~{m4Gkp|5D*IsD!CH}t{nis8XB+-JF~k8$Os0@ M1_#Xt(9l5uJ4gkx9{>OV literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/nf.gif b/lms/askbot/skins/common/media/images/flags/nf.gif new file mode 100755 index 0000000000000000000000000000000000000000..c83424c2c3c02352efee0e63f4011be0129e2fc5 GIT binary patch literal 375 zcmV--0f_!bNk%w1VGsZd0M!5he5-s+lT5?Q!}a#{VxD6FD*%M8gf@OQw9T}|+Qs_% z`tkAcHlUu^q!s_no*yY$Qd@Ws`T^ev2YNBd%taZ)b&HerT01^PY(z-K&Gc9#3 z6Kxa8-N{OgN<@uBSejT3X$?GtJVJ&-Q zlAGcJh$&rKcY$4i6R$4Gs_pAt4c_1a2L504oCv VJZi&CO)EV$Is!d94Ngu$06RnPl%)Uw literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ng.gif b/lms/askbot/skins/common/media/images/flags/ng.gif new file mode 100755 index 0000000000000000000000000000000000000000..bdde7cb3bf708d1ee0bbea86c0b0d407d708b691 GIT binary patch literal 371 zcmV-(0gV1fNk%w1VGsZd0M!5hddYe=r#ABU@?5=K?(Xhw#BBh20CL82GN&^2`t<-e z004ymFQhN^_4V)c@9gsI09gP~wovNu>HuT_Pqj~3yIBpE4FF0204xAXvq}<|5&&!f z{r&wVp(RDJMO3y_RJl|+syRQcKP;pyAD$l>n;8_E6ab3=`}_Mzv`N<1*4f$FT)A9C zuSCbw$K2-J=kDiNxmX~dAa}@jV83Blyjbh&>*(+3y~n*~z-IXP`276*`T6{#XcoB}a*Jd(-OcsFXEWzRIb^=!HL%Ey) zv_XZsL-`yb#3*xtia&y~kLOT#TJ2d4L=l*IIu9cqG9n5K2Otp{0&_Y)4;>^WAqoZs z5h)%5fImGFlO_R{1)3rbk39`HB_0>NbZe0G=U#X^llC=&nx18Q8b z*~KC;8kJau1Zed*od$yt1k#~x9JS8t$N%+uDLOef3=BLzJO+J#I3g%8B!)g7iF}M4 zA}T5*haMh21`-c&avcH!cQqOsHZcbgWF;*H5)~B)2s1MU83b5b3m9YyB`#>ZOHCgj M%mL0B(9l5uJD$t2%m4rY literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/nl.gif b/lms/askbot/skins/common/media/images/flags/nl.gif new file mode 100755 index 0000000000000000000000000000000000000000..c1c8f46d0c328cc5ceae99f0ccd7c5047ae93d83 GIT binary patch literal 360 zcmV-u0hj(qNk%w1VGsZd0M!5h00DIV{{A#>vH&5E`eJhySf}*>0P^zk`uh6#`1tnr z_WpW_T8hK{U28>qx;k>TDrThkJd0frAGJ zJ|Y7!3?LsJ4jT^wArKH68x{Z(4k@EDry2zX92_AYs|=l=p{@d?5V!!t7{$dF$R5fb GK>$0vUY#BQ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/no.gif b/lms/askbot/skins/common/media/images/flags/no.gif new file mode 100755 index 0000000000000000000000000000000000000000..6202d1f3a2df4184878f5ed8bc23aa630d43c061 GIT binary patch literal 376 zcmV-;0f+uaNk%w1VGsZd0M!5h01vKZmEH98^zCF}`uh6sZENy;dei^_23FAb_xIv4 zFM6fq>gwv0xa;FBE9*^5?Ot5H007+=6wLqt>r_(O008MkL-O+Sq$r8H8S8KAKx4qwEzI+G%}a&g)Y3x25QbD!hlDJJ;%`1$$yu>b)6{{G_A^8LV00000EC2ui01yBR000J+K%cOO_*IUTgL1<384SHfuU06)29OoSwQzVEFmNnFct&>I;*X(EH(-}2Ut4`IvFn*G%YA0 W3NpY46Bhss8xhtKBqTiBK>$0|z?fYC literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/np.gif b/lms/askbot/skins/common/media/images/flags/np.gif new file mode 100755 index 0000000000000000000000000000000000000000..1096893a70f475c9407efff0afe534deec81af54 GIT binary patch literal 302 zcmV+}0nz?PNk%w1VF>^W0M$PL2MRYH7*)Xl0KxzO+yDR~9Bd&n)-ENh5gT<88gd8> zINksN8Wle0ZEZ6t#ugrb`|Il{H_R+VwCn%?>V17GBAM6#0QS?<`sU^xC!PigHt+xd zCm)0Ch=}A}T_iHP>VksM008;i+zJmq3lK*N5Jdd&@D>s_`QYFZ9C)t)039us#{dBO z!001*8$=^#$@~*BhC%Fp|N&D*RKe$V2vVE zgga)E<;rVZf~hZEjT=pYS$?HpgsSP$e0P?%02^IScAi~k7044wcNL72H z0022XXp~BFoJMb%ex18}nYmYeqi&C}YKyLAhpKdxwBY9LVuYq;h^v#U#7=ji-oaQ@ zdZdr2zb99Pc9gTbfdKK{n%}h@`}_Og!(2;rp6JhZNN}2do3^;e)?K91Plxa2sJDOF&HKr zE*oYG26Qh94+}pUBPui}0%>%h4*&+EBoQY%3kj96S*)B^MPQa-6ojOH2jJ KKh4g~K>$0!fQ&2v literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/nu.gif b/lms/askbot/skins/common/media/images/flags/nu.gif new file mode 100755 index 0000000000000000000000000000000000000000..618210a755d0faa72b11a29fb07ab7c0c66d155d GIT binary patch literal 369 zcmV-%0gnDhNk%w1VGsZd0M!5h+S2WMhw1sIrumhX?dC~rblC3cP2|Wt?9@x}*;M`1 z)ryDQ^6gRe@>%BGJ^1xp-@!HY@K#%S?&{$}^-DrUv-L;mT4>Cr~&;6Cf*M)U7i=ifm6#>L0A-h+C{{`!3V%V7S%zWsM^{ephu zSXt%a_3Y$C{qs^*R?64m{PyBoZe+BAlET4@;6XMqvHP^M=Hf~8>sa&d zQ~cj?A^8LV00000EC2ui01yBR000J#z`|+~z!VA;U1FBWBrXVvOyNQSO1Hr1@n}RC z2#ew)s0@ArOK-&37#JYRc#vY5Tbs6l(D6uB05B#yHU&Hedm;oLF#-!U7y&0OG>8Nu zI2s2T99>>M43?NVFPj_al}cTuV|24eRGOEZ#pJ@uA~}%ps;HHCopL&7HAET>aRJP@1%w*VbZ zFoFSP7(X&DldJGJkX{u6q0)13Hkd(Sz)3JBEFB#ZI~E%=5qkkH2{kf4EiEMt78pMP z6cjuf2_GpS4;&d97zG8H000=B9-$(nrl=RHoe&(arKc1C3JMnm2?$vWU7;Bp8W94f P!3a*z2MN;C(m?<_p3#U7 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/om.gif b/lms/askbot/skins/common/media/images/flags/om.gif new file mode 100755 index 0000000000000000000000000000000000000000..2b8c77501d4fcb4eebfadfd58e5fceaff2e50afe GIT binary patch literal 364 zcmV-y0h9hmNk%w1VGsZd0M!5h+*(=yPyqV+`uOgo=i4(8_OGpsVE0083R;zYDW-rnBYR8;o%_AaR|(JCs|MMXTaJpTIn z{{H^hN=ni@JJvuz)jK;Xt11+s6fdnWB&Z}Gr5_oi8PPU2(?37XD=Ppk0NPJa{P*`X zuQW@zOVl+r@9*%@FfcihdMqNJv%4i~Eo0CzVo6B8OTEf5zMDiOxU0m%x> KKg}vCK>$0WIGL^h literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/pa.gif b/lms/askbot/skins/common/media/images/flags/pa.gif new file mode 100755 index 0000000000000000000000000000000000000000..d518b2f9780dcab0072328d4007ce28bd7f3f064 GIT binary patch literal 367 zcmV-#0g(PjNk%w1VGsZd0M!5h^IBT&h=}R{0Ar=~hr0RfM@IlS=<;%M{QUg!czAZM z_3I=gfVTM3($e)~WA1BfZL0Qrv-s`p?a$86_V)JiOiW3Y@$B^e^3>G&`uYG^^3?zU z07&itDB=Jb-d>{h-v9tqob>K7G3_fW@jN{5H#S+H^-r1e^YioXK|$}w$MMU{*Vor) zsQ8`8`Wt`s@9*&B@cr{qQt*C$08jDgGBJ3u_qf;l_4W1r{r&#_{>#hD^78WZ^z`xZ z@&Et-A^8LV00000EC2ui01yBR000JzK%Y29|%qH<0z*6k@`pQ(=)3n?2fE?^81HVQKd2L}%iGaDKVEdw7W zAtw|RI6WvlJq?qVm=+eCJplncC=)ahmzk!WD+M(wJUbkT5D){Fs2CVLxf}+^9UUGX NEDa446FbsD06SJcu!#Tw literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/pe.gif b/lms/askbot/skins/common/media/images/flags/pe.gif new file mode 100755 index 0000000000000000000000000000000000000000..3bc7639057b36907b31b465d608bbabe017e9409 GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5h^*ufEE-v|0Rq+iC{{H^*5D@!CMe+au{#jY>0094c zd;WKK_5c9-U0wAhCFTGC^BEcX008O$0O9}u`bkOiGBV!)0QB_q{7+B)R8;pdG4?Af z@)Q*MKR@&zAO3lH{N&{RTwMKNVfRi>_`}_O+{QUOz_WJtz`T6;rJ6A>nr9g8|N7Z4l;6&9Wb9V>vN7Z;}k0Tv7*TLOTX7cLb60X0ia2{Ohc$P*Sd H%s~J<+RLIo literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/pf.gif b/lms/askbot/skins/common/media/images/flags/pf.gif new file mode 100755 index 0000000000000000000000000000000000000000..849297a57045d279a8edf2657314cfc5e9496ac5 GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5h>*-{-007)PJmXVS~=I5Zl$?gej>%Xy+eIWCjc!MB6xayJ1RFUH8(khJb4FhJ0u`7 z4h}XWKRg{jdX*#+5+xH8C7m4|JSh(@WMvQ#At43^Gcy(x4^~()3>6gu0vj6^yGct+ M3C+zE&=f%cJBfF#l>h($ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/pg.gif b/lms/askbot/skins/common/media/images/flags/pg.gif new file mode 100755 index 0000000000000000000000000000000000000000..2d20b078561ed92f92a9034f24d382e33db894df GIT binary patch literal 360 zcmV-u0hj(qNk%w1VGsZd0M!5h^8x~$QBin!c>Z;D^(rXy7Z#Wu9A#x?Ha0fs004Ph zT=`X1C@3gIL`2jvG5BRRQ4kPjH8u1yGF)9HO zv9Yn<008_?gxH1m2HZvX)Hyes-+Tp<$^*n4~Y zb7k{F8o|NAG%qjt$~^l^NBc)d^tK;HOG}WDkoKx8jeC3eyf@NOQT28u^@Jt;00930 z00000A^8LV00000EC2ui01yBR000JsKuE&p>lu%LWHRx?u?EnA#nKX~7K;i~FbQB6 z9@(apdl0lg?nI&U%r-HT=|;$GCm_ltka;{vDLW(>TQ3?H95oRR5(GRnE`(`%4FU}a z2nGZNCj}Br2Nw+u6bLX33=ks)o*h324ICk+s2?9I1^^PS2NO02hohLLi>^P%$jQkl GK>#}|p@q}{ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ph.gif b/lms/askbot/skins/common/media/images/flags/ph.gif new file mode 100755 index 0000000000000000000000000000000000000000..12b380acd38aba726f63d337660206bafba34339 GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5h?EnD%{dn;U3-t8p0002=^z{G$ZTCh-Wg>ZUa>f1q zSo9nmG&HJ0L9qE$RQ>(_X=%a$080J+A(&zsEx%BMH{QSvA zMzSCvpZcn;`DJGM(aU*x$@=p0`}~dn|2OaN8v6Qct*w`)jh10xzMr4kva;e>SGjk0 z#@zq_A^8LV00000EC2ui01yBR000JtK%8etG#HPOWNo#R`4S0;R5$8{D3QVA!5f9H ztwx8zn6x4VOGc7PP{QM~8J!6PI0c`WJejyUSt<$&EFw2K7Z(VN2p0qe8UZvMGZiEy zCMOFE8yF~Q8WJ%umzkRzohVu&F9)TXoErilC^jn<4!H-rA0HSPARs>#0L8||D9I>4 H%s~JUbKv57`IX+(7_4In0sJ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/pl.gif b/lms/askbot/skins/common/media/images/flags/pl.gif new file mode 100755 index 0000000000000000000000000000000000000000..bf10646366e6e2de00d04ca75e60c5cbc662e5d6 GIT binary patch literal 360 zcmV-u0hj(qNk%w1VGsZd0M!5h{qFAkZf^BEJN8OS^Z)?o*4FYaF8Ekk`~U#>U|{hL z4gLK6@(>XEMMd{DHTN$s_9`mw008wQB<27B;s5~jA0PQTI_dxb`$|dkGc)rU8Q%Z^ z_4W1oKR^BP@$wWD`dwZ3Pfz$$RQyj*_vz{TWo7T-;q^m9{d9En=H~tH@&1mE>Dk%q z-QD@?>-+8P`)FwVYHID@-|^$)`}_O&`1sub0Qvd(_V)Jr`uhC*{P*|w{r&y_@$vrt z{{R2~A^8LV00000EC2ui01yBR000Jsz@D(@Q2_ui7b%sRnPk1+&qw9)bUIq@R@=3r zRG&4w?c#Qg~;C6SYrmzfL-1{(x~hKLO(0s$0JO|I|& literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/pm.gif b/lms/askbot/skins/common/media/images/flags/pm.gif new file mode 100755 index 0000000000000000000000000000000000000000..99bf6fdb60bb4e92ef55622efaddf8f322b76e48 GIT binary patch literal 374 zcmV-+0g3)cNk%w1VGsZd0M!5h?t^XN;o`WXG33)!zpqdL042z~a)Y9Q@o8aKS-8iy zR!d8tYGZTO$7JFF03%=p(9_j;c(7Mkr+;*4Kti<4xJ3W}MK(9A003k|M4)nLSdxl; zXl0UNTWQn z(|4Hw08db)Oii@bWpU9>T+&u#e}KN)p=apm=EA8Rl9AwEI2nzYq!I&Itw5<1trek03<;E_85kuJ zje$hLSrAGv%L6nzWCw$di0olVERfYFghFxTA_O`l3U`I>`Zo5O=hzI051K~ z(R_<>S8lfe08IC$s7zz1X_}18YJZxl>x+b1RQb4~o>;?C9eRBNk8W`r_Lc|lsEEJ&qabh%J)=l-If*U#vAcDdW0t59c{qL|M? zT9{XUUdpxCcdTA|sdZm*-y1o{004QLj>=kgbga1}^|Q9!qO@64x&Q9&duhKhOrw8Y zt2YSqof5S0td1JtF5IyBnu)pA`1x=TU`vH2oDw`$OXSl NPt6Sp(9sD&06UU^mbL%@ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/pr.gif b/lms/askbot/skins/common/media/images/flags/pr.gif new file mode 100755 index 0000000000000000000000000000000000000000..6d5d5896709989000cb51a3d13f77593ba68938d GIT binary patch literal 369 zcmV-%0gnDhNk%w1VGsZd0M!5h+}!(XWcK5CcKP}F&Nw-JdHlEl0Hy!|%vx>p^74iN z0QvRx*i%&8Y;0Ux{?kK4@$K#Q_V$_p06{_eFCVq(Yu0Mu1g#KQf}GBQg``_)WL z_44un0Q<-&DE#^O`uqI!^z_+XUZh^7#2p=wj{eU)Jl9!S007rWM*H^l^=@hX`1ki- zVE%t==D}z#U02f4{@KaN_4oMMzrScv;No#|s%WaWd$?3l z`~U#|A^8LV00000EC2ui01yBR000J#K-9rPG#HPDgYxhz^kg!@Zr6ZSDxwQR(rme2 zpD=4dj;J`4=E~6mfIx!*i)D^bIwf1lq2VDI3>yy*CMOad7$5*XDHAgU86F-aBqjikL05|~sR#yLddH^c` z{ZCN+Z*TJQ^7)RD(9qEN`T6wq^*EA)g`sSy@VtOm+tkj;Px--7*}^>iaB*p(T-NCNQ;|jW zJT~WeT<}3KL6J1>-QDecPGFy5^ED~-XH4SISMItt`%_2oxIILUMEXKP>S-$G(A3=k z002z@A^8LV00000EC2ui01yBR000J#z@Ko)DRMMOjm7f&d`6>2>T;P(6xay!GgBQl zCjo8;3K<64-zZ?S5o!WK`Ru?zevQCJR_YOQEp!DBcMTLKGBGPIH!lMY1%nU^2owhZ zE+h;LkC7Q6lL!(l04NCwJdgz$83rPhIu#W#12n1`9UTL&GXVh<6cHF04|u})Ik6{x-5+a literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/pw.gif b/lms/askbot/skins/common/media/images/flags/pw.gif new file mode 100755 index 0000000000000000000000000000000000000000..5854510fa9ee2e9b218f8a783b331e7200512ea3 GIT binary patch literal 374 zcmV-+0g3)cNk%w1VGsZd0M!5h^!Fw-sr3MY{Vk;Q_V_gbF5UoT@rBE{09@{2zWe?A z6ic)BJF4{nIpTWB|5CR2L9O^bt@&)j{Y|y{amD@sd;0)z_FcaI{`?&O`~-Kxq5wnZ z3zhL$y8c+Y`2bGqII8$1q4gi0^hL4z6q)ifrt=w`^LNPpXTbb>#4U`KTifeJ-R)Blm+~v5?p?h4JgnuO)sBnL$6UMmr`nlDu=d2_VNb0k217Wg2cg`?cj|ZmKYK8v+zND;a@583Z(jCJYUV z6BB@fFESz~BmoT-5(^_W3KB;M4z`ua|7-SPAJ^YioT?DFsO`}f(|_b)H>_4?=C-GiCy>VSFs z`}^nV@a^yU_SDh&($MzI$oTW~>UneN>hN%Z%JcO4_xJq#OicCl_5A$&{Qv;;^z;A# zr~d!|A^8LV00000EC2ui01yBR000Jvz@PBPD;5_>Bnb4&I5UUC<|OCLdiK6o5=c`StaMQcHnNM|M6p zWG5hOF)4pdM*IBxg;GuM?(A0$1?lJKmTG14@bHdbTJ`hu_4V_2K|7&%bewZ-a5ph} zML&HFMX?<>TYwrht5fRZny~H2eGe{r&y=`uh3#`S{{H{} z{{R30A^8LV00000EC2ui01yBR000JwK+JB45oZ=7$k?u1o-Ww zQyM@4(hv@tLCrhu@o#}@Z;vhj literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/re.gif b/lms/askbot/skins/common/media/images/flags/re.gif new file mode 100755 index 0000000000000000000000000000000000000000..43d0b80172e97a08147c64f811ea5c7775ad46fb GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5hs;a7*ui5eO@$F<*#M13od&TML>hy?y-rnB$m5kKX z)Yt$3w8iJV%jsr^%<5`g?(XiPwcY0C=H%q$?sjUj!{w&A-t&HP_xJazz2WW&0CJMj zs?RiQbXoVKJ@hTySuv= zN2U1q__xUC>|Rjp>+3{pyW-;FpP!#^kI|;N;Ipu>uD|3gSFo+9_7 z>~odVA^8LV00000EC2ui01yBR000Jyz@PB9K_CFBujQ zjbnlXC>KmW0Po!Z_V!r){ZaeyVEp`L^%SwzVzog`06(D@Hu4#=r=dt008Ib=jvcp(q3Wp^>_62^zga4?W3f@001?Bw(;@t z09Bas^77^7<@NRTA^8LV00000EC2ui01yBR000GRU?hr0X)Y)lAuKz+Qz8REwn9)a z3{2qvH3I_2s8o7CU+tiIJTMH#r9drkID!MydHHk*)hkxpd<-a*fmJ;IxIYX6fnl+9 oHXFsqV|f#H1b27}3LPGLZyy^Sj2(oG9t{qY5)vMln3*8}JCP7%O#lD@ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ru.gif b/lms/askbot/skins/common/media/images/flags/ru.gif new file mode 100755 index 0000000000000000000000000000000000000000..b525c4623312f57d837c23d28d18fdb97e730de0 GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5hjg9#7^8NHJEf^T{&pl|Ioc{d${KqF&^A;8`F!xne z{o~~C`deH4WMnEU_Jf1>A|mzr`ug|x_xMmy`aL~#bpGDn>h|{bm6iO`M{w)_0QW>h z^*cNB002r#{lgqh;ebFwadRwpd3${j11fPZgLry;eggw3CNVS_ z83Y73HwOnFA0#9003CJ#W_foV%^7cdX_ehGj%=Gw9;mAnu^D_W7 z=LduTA^8LV00000EC2ui01yBR000Jtz@P9pnGBADj&jBP5uOB&)MP5yJRaD{S?GRB zB?Jr8;PDz;f)^~lU}}|$ZZsi$z6db}lgY?r7d{683JNV8000iJBqS#%E)EM3G&BOc61@h$D8VWb55>g`$jB!TWms0>s z00000A^8LV00000EC2ui01yBR000J$K%Y>EKqQWnf)CD0m(r zJv}=CC>sq86eKMmKM5HN0VyXIG8hmB79;=-r5XV=5-tr23^5`E000U<6IWRmn=~jA QAiKj#Oh3}o)IUK0JE%R1wg3PC literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/sb.gif b/lms/askbot/skins/common/media/images/flags/sb.gif new file mode 100755 index 0000000000000000000000000000000000000000..8f5ff837fe4c9c0280ee8d3e4601725c86099fa2 GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5h=F3Yvg*&9A>&CB&R#+8hXWlG(Eab^=%ELz-avT5v zyNiqG`1t&CbK*mbL#DGuKtJ0_O5Mem009BWB6cEdY~WH-+ftKLkAI^^Mb;E;6k1!{ zM@QcdX$~}jG$nT>dwb=-sE7aw0OQhQkyik>oq}+jK-IWxrkAL$qP5}FUE|~U005uT z()GW-L*&F?zQt&@weYW*0H3Wt=*&N!p6W|W*j0}*l49~5{KF%~rz9CJDkgb4`&KMEBV z5ji0M2se=l8XlDu5k4n9ke?bIq6#US2qp#w6q2hQ20sJ?BQO^iI1Ua00UjQ+KN2zo MB0tX0(9S^sJJi99umAu6 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/sc.gif b/lms/askbot/skins/common/media/images/flags/sc.gif new file mode 100755 index 0000000000000000000000000000000000000000..31b47677e0d51f2958f3c833b36345c5267a064d GIT binary patch literal 357 zcmV-r0h<0tNk%w1VGsZd0M!5h2apH&la}uR0MF>s_*7N=`bz*a003M7rPrqb0EaoI zIrb?j`8z!JwYB?;NBfyY^z`)o{Zjn*Y1r9l^YQX}ddm4=V2_TNgi;OnPEP#%0OaK4 zbacsIU%dVOaWA7U`anSVHa1>f!S^sQ1pzMr0D1hDWy#3^`hA7#=imFXXM@Otsi~>p z;A$(CCHOizmzk*S_48)CX7}dp=g-ge!ov2}*Y-q2?(pvRb#?ejNwe9raB#%d+kE-$ z079-pA^8LV00000EC2ui01yBR000Jpz=TkE(inY$q%u)yvOG|U$zmZ8G6JTk(~3k2 z4iFhbdcSh@gVtvZd#003=tu-+(Vym_VDgS_JY{{BRK z#{dC{7+JJus@d}L@_Du4_4V}tNweYM;Z2p&#oX?Y!Q=J%`%;3+HgmrK6qrSn%`J4o zTAtQNjn7k_)YH_|01A&Cbio{b#432i057Wh`}<*!&`WE;M}o<3uiYPj#+}LNC4<8N zRi#s**d+4_rI1Ud8VwT%$LkG9 zm4tY8nRFDDE~G&S9B8pp0*6unx)cKn6nJnh2qh&xJ6#P1AvOyE1uh;Z9fVyYFDfDf zizyQd1(H1w5FQL6m=Q226ofb%8vqavGYlIG84o^$6$T?A3_1?K8U!AL79M3a8Uq*u Y0KWo4EDInq86Y4F3j_o$A0I&gJ6ofk%>V!Z literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/sd.gif b/lms/askbot/skins/common/media/images/flags/sd.gif new file mode 100755 index 0000000000000000000000000000000000000000..53ae214fa16b78b4c491add45bb56f70f4339a6a GIT binary patch literal 355 zcmV-p0i6CvNk%w1VGsZd0M!5h1#HJ(<>gwuk#BF=edi-N!6ciNu?(O$XO#N$X`a(kedwNQ>O6&jt z{qFAb^Yh~3;yXJ#`}_O$=H}qw;50NePq$C|R#shMU3kfO_)}B;QBl3Uy^z0(K}10S zJpkv&$8M}|T*6#$l*FC5{(%MR9h?+zy#V#mJ^0c!5^0?raiBAOsQ@Hw?3(~Ng{wV@t_^9H00~z5)<8pxux6sb zC3&I%07L!TQTx|M_RcGNu*&}3S@+N{SedzCpS}Pvbp6~^001HU*-HQ!WBl4rd9KO! z&oceN000wR_ts18p#W2swf zr~pHOA^8LV00000EC2ui01yBR000Jzz@PBvYBWry$wc+SU^>50h!qI%7K=?uN0Kq1 zp^`#$@-P4zOT}jZbaE^OuAH)^*9MNJ;|h>HDIXaF4RtgN26(1d0tj zD?NgQhLi+5EeR$IXOV0i5eErYSWN=oisUF9Ss@MUJ~LPFcm&)v_@;nUOTF){LZc=^lA?&Rb7yuAGG?)~!e@L^%` z=jZDQ3G?ac_3P{FK|$z6Md(;q?O0g*`}_F#`1SSm_V)JY008;<`SbJh`uh6!_xJSl z^z!oZA^8LV00000EC2ui01yBR000Jwz(~#|gcu=>WTV2&ohS?dgCG!qSRUGhBgs)x ziPXgez=SfElBUyL6)uU>2@^xBKQak~p(0!+baoma9}XK400{|KSz8@43Jx$1Vk+U%X literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/sh.gif b/lms/askbot/skins/common/media/images/flags/sh.gif new file mode 100755 index 0000000000000000000000000000000000000000..dcc7f3bcffadfaf36ba17812e55f66c0fd10b466 GIT binary patch literal 371 zcmV-(0gV1fNk%w1VGsZd0M!5h_N1uI(B(o`u8Et=MqRQrOsen)40MCV002uoQmg%E zX3wuVOJTLTqDpFg!TAVRxPV;gJFGlwVI~X=wWJAX}C5{ufeNT&#^$$v_ar?f;Ug7psm@myx(P! zWdHy(A^8LV00000EC2ui01yBR000J%K-8gNz!YRF(PnvRUIc(ZBoY|$9F7SolL;XV z5RXD3rdbFAK|*3dU;q#@egr`|)&KyA^78WT?(X_qTl{2XO;pbGvA6pA`b1CB_xJbtQd3)F)A~I< z_V)JmJ3I6M0H-Hi^E5P~9aZrS4Xrh3_V@fiOw9lQw=O)%;Q#>0Z|2P=x>$^&uhg?)LaJG~nR$FF?&d0028l&mS(t0};cjWSy2-qo!Y)jgsTt008y% z_5c6>A^8LV00000EC2ui01yBR000JuK%YoL4$SBGv IJgwv0xa;FBE9*^5?Ot5H007+=6wLqt>r_(O008MkL-O+Sq$r8H8S8KAKx4qwEzI+G%}a&g)Y3x25QbD!hlDJJ;%`1$$yu>b)6{{G_A^8LV00000EC2ui01yBR000J+K%cOO_*IUTgL1<384SHfuU06)29OoSwQzVEFmNnFct&>I;*X(EH(-}2Ut4`IvFn*G%YA0 W3NpY46Bhss8xhtKBqTiBK>$0|z?fYC literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/sk.gif b/lms/askbot/skins/common/media/images/flags/sk.gif new file mode 100755 index 0000000000000000000000000000000000000000..1b3f22baf9e1af04495b4044dc6b7082552f9f04 GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5h`uh6xD=YPOdZqvX{D+0t!`l4v^Zot(_3Z9SW7g^b z0Q5IE_(n!VT+#pmrFx3pcYD)3Sk(MvVrF*P?c?P085#OpTj6_*S##Zknd|h6koi(m zRc+XWli>j%%lG&9@&EwPLR!iyM)3^|P-oZu-rnH=0Ew95{q5}bo}$h+P+f4<@N0AS zkd^rF@P>86_y7RMBR}x1v*+F4{msnghLqs5!}93rthes`;^B~*;cITr_*h!q008~< z_5c6>A^8LV00000EC2ui01yBR000JtK%Y=(D7st-#O+3*_GVcXs8?y!r_n-4Gk-Fc4=#Te}NH$bp|bk^45 zii+iD;b=58%TiL+K0eR@0GIRg^8ma6dU@StW!5@6&j0|30G$A!pz2=UUR>K;EiKXe z`}++Iz&EA^8LV00000EC2ui01yBR000Jvz@Jcf8yPJ~3t{;~I1vDVMq@A>4l4($+36fq z*;*vB3j}JYfb4S7K_IqV^zksnEP{c)*J~*h3kwW9K0F(F1AKmff`lT5d5V95gFYf6 zJ~S*T92^uBFC-ZmI5QeZm$h@{>+^E9`isQ<0BQGNtM+BD z`T6tyd%OBwsPt;F_y8o|RipCt_4NQOzN}z?zWa{2fmx~eYOK=n z`TtdoNKc~mezW1D%+i#`_hp^acDelkQ}0NV)r7$OjkmFi!1VU^_V@Sq{r&y%_y7L> z{{R2~A^8LV00000EC2ui01yBR000Jzz*cWaDj7}4g0j=;{eHeEkN2o<7K>dID&=uD z*Q{f8YI^(6iMbM-wv`};>!xl~57L0G(5cgT0(005WKJoorCRkl0k*Z}1EMqs~T@AfLO-Z1$1G@jH!!{k8x@@4$=Y5V$M{rzqD_cn3Gae~M%jLtP!xmeu* z004miA^8LV00000EC2ui01yBR000Jwz@Jd4Dyjg0fO0VSIi4hs2WP9@5?Dmj@sf-L z0u2cBU>y`%r#BQHKXPY?mO&bYE*jU&v}!3J4IcvxI(GyhG$(Ed?D5n;Qou76u|287HNu3mdkp7F|7T6}=P`W@kPY5ycT4$SElk KB_%%3K>$0(ftB|F literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/so.gif b/lms/askbot/skins/common/media/images/flags/so.gif new file mode 100755 index 0000000000000000000000000000000000000000..f1961694ab98384142d534dd682287dda3b37892 GIT binary patch literal 376 zcmV-;0f+uaNk%w1VGsZd0M!5hl*#@8Nai7+^!WgD_KCv# z;_diGo%D9P{RM&j*y;L4obv!~_CcBQdA$Bvrt<)H`AVPkb+`F!wEI@1@_D)WeZ2Zl zqV*7m{vnb7aJ2UTUhjIi_I$hgX|VTGqViFs_KU>*akTis-TJ=T_p;Uaki_}8+WS?f z`4fr%A^8LV00000EC2ui01yBR000J+z}Ii+YJ3(8lSU+w#9|GA6oA3pHWtQgg@Ofs zfJlVoQ7BTBgh2p#1YBXVqB5EI){qu05S34QARs#zHZ3O!1Roe>DJeM_89ERMCm{!k zH)m-)92^@Rl_LTK3ma$^1``Z27a<`d4kZI778Df+26-nPuMQ3>10oD8TsRLj2?qiK WB`P%o8ci_;1p(Fs*aO-GK>$0Av#T!v literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/sr.gif b/lms/askbot/skins/common/media/images/flags/sr.gif new file mode 100755 index 0000000000000000000000000000000000000000..0f7499ad954a455a4742c4b53c3c8c2e3f558275 GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5h{bgnVP5}D-`e(st?DFjO`Str%RRD(o^B5TW%|-w+ z0Q^cy0Ac`p%6sVV>GJmT{aIT3Mn(X20A0Oa^)fU4cX#$nOZ6fm`SUsZtWrp_N%*!h z`N%jstUdKBEA>4+GNv>A008Cy0AapkRJc|5`}p-kKHmTUHK;eq*~>w$LQb_%{7+Bv zE->^rIR0*M_(4MUMo9KZL+$zT`hr0CQBe6^UiQK&`q)4Bd@xY9P*}QJaK&(S$aT}@ z)&7c%A^8LV00000EC2ui01yBR000Jtz=LpjkQgP5Wb)8xHhMhXU@)5ONV>pGz~z>{ zg~DU}@P&S_Gyssv7;2Un%q9v|AO_YVfx~eq9v2rMbuW1f5i}SWIe`}lgds0_5ilGZ z8#jUo2rhLMHh48~GA1q*Eglvp1Yltg1hEdYvndi(RwOGL8Y&$fC?FsTNJ#<5#|p~J H3PAunnPZt? literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/st.gif b/lms/askbot/skins/common/media/images/flags/st.gif new file mode 100755 index 0000000000000000000000000000000000000000..4f1e6e092b35de465031930db0bd661ab5e1665e GIT binary patch literal 367 zcmV-#0g(PjNk%w1VGsZd0M!5h>+(1NYXDigRr~u!tf@8tDgf~S0D8)J08#+;^(Ksp zB|57#OS4D$XG}AuF87sH=sZ2EN%iwBqCt&9G(Z63I zC5FqgSU4ElO6-wZMcx3wWCBs~eiaGrF&MB2Fc=Ug2_hGG5)BOlA}2Tm2?{tFhCd$? zIurvi8iWZb87&xx9}N@{adQwI9UL4QJ3lusV`dIBBO^T|Bo00XHCI_-wYClgAxa3# N%RbIN1qD7q06RBYi{tlp#qV34i@{^(4g`4a4_Vxe(w>w%R&?NpnbSvS+W;xo_4W1q{QUIv z^!@$)A^8LV00000EC2ui01yBR000Jvz@2cXP<%eE(}~0673_xJ^z5$PLO3 JD9tEA06WZouv!2B literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/sy.gif b/lms/askbot/skins/common/media/images/flags/sy.gif new file mode 100755 index 0000000000000000000000000000000000000000..dc8bd50948773d5698d49ad4a214a9847fb09d5b GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5hWMpLfMMeL6dRtpt^Z54G*4F-Zc2ZJP^Z)?t00918 zUPMGh78VvQEiIkZpY;3oG&D3mK0f*Y06IE44Gj(V_xJnz`}+F&^78WW@$u2o(WTp` z>GSJcx?cTJQTkk5jLwen`S9-U?)+n8{cCGWOH1}FEaC9s&(F{IF){czH~3Ri`Bzu@ zOicY$RQ_36At538JU#tzar^xH`1tts_4WMx{Q3F${Qv;=_V)Ah^Yrxe{r&y^{{H^} z00000A^8LV00000EC2ui01yBR000JtK%dZuE8-*)gc6ZR_ykacK}I5t6d%^AIv6{aadLEag`pTaC_exT5fcy%4KOea3=|X%Kdu7<2L~k!u(Gqfy~)bU Hy+Hsw(&wg* literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/sz.gif b/lms/askbot/skins/common/media/images/flags/sz.gif new file mode 100755 index 0000000000000000000000000000000000000000..f37aaf801198a2afc9700c33f0c71a126358ab67 GIT binary patch literal 363 zcmV-x0hInnNk%w1VGsZd0M!5hP)SKIa=JS-G|P)m+04jKi^iE37kQ1f3|ghfuCK+v zy!pv|^0!#~)RX`(j+wmEt2H#&Qvmh7WXN`P^N|9w&fx$>ou$Ot>ZUoCxzDmpOx&n- z^y}vvWU!60$?dE|gssJ`%HF0bDZg7<^}%lLu}-6#n1TQRIDEP7vQx7|K$AK;o2bX* z;NF9Bke|TT+?ruTgT&>+xd1(Px`0BOMMh>}V!~KdkBf<+owlM;QM^-3UXKTY(pY#G zq_9N7D3ciXq~*0T001yqc9~yusj-_rQfPT=jJ;2Ft~+I=VT8GruG0Vj6f#_( zdw8NeVyW?`rYlf>GFOCig^kv&x08maHejIu0AXf`y3(>idm}~m>CH-UuJFrVP;aPR zfVDtqs@>Gy^A^8LV00000EC2ui01yBR000Jyzy!yDLKy;yl5)Xh8V)Z}?$F4XOqf=oV1d{G z0L4$C=&@XpP$;5;1P-1Wz{5ZgI=t0`Mu12fItUCIA|n?rFewi+E^asl95DnKBP0$d z0s=c78YLALDg-VUBpU-eGCmU_Z7Ng}vMDgWn^XPTyO-%p+miX?J_wA48NJ?dc&%gix@ZxCd zMo8@7TkYa*6+^k?Eic>2A?eLf=srK{&{XT*SMTVa!K(nxmjLERNb%Wh04=j+gw*um zi08WiA^8LV00000EC2ui01yBR000J!Kp14mlPQjh-($IiB%X)c>4ZZ009e4oIgo%p z6bgy7E44BK0asE1lL(H*nU>}yygkp`F#~&-k$9M literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/tf.gif b/lms/askbot/skins/common/media/images/flags/tf.gif new file mode 100755 index 0000000000000000000000000000000000000000..51a43250963e5862d4cbf19d34a4cbb53cfb3b7b GIT binary patch literal 365 zcmV-z0h0blNk%w1VGsZd0M!5h001pZU8DVNZ2$pje2KbYajtcNv-vqX000$MWuyQA zSN#0^Phg}#QkuNN)^2>YiIv1WPMJ1Il|E3Ic7(Y4K|ufjMvR!oMOdHvM@Lv_sQdr` zI!%}SOiW#EtT9BAMOK~SF!)>sd0a|`}_NDe6vbhpg~idEkTe^V4~jO=Tc*&YI(B&33vYf z{{8*^A^8LV00000EC2ui01yBR000Jxz@E_e`)!T`h0?<8T`s<;U^+XESST65Ns0bS zoeU#@c||l^$c*?4SID=BWCs{P9>2vRQ#oL691>-#1P%=opahkdnGGB!Ar3kQ2nrN4H9V6kE(tj+Aru_B3QSA@ L%m4__&_Mt@20fAt literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/tg.gif b/lms/askbot/skins/common/media/images/flags/tg.gif new file mode 100755 index 0000000000000000000000000000000000000000..ca6b4e7744dd89a6bc12c61900830fd7054c6554 GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5h0Dk~jxmMc47642D^2ro9r#5J^H0#z#ZN_We$SKvh z1^_Vt{aagHt0hmR8T(UH`bkOte0=iOL;iJk+L8d~%`E_80QS)<{@mQ|(KO@FI{Ml@ z``SeQwYL9-g-WnS9hn^fasXnzU@@aE_0A*s)iq(TFFUb2>)d9meE|F2N#~saMXo|n zv`+re&_Ap@`{PF{pez2HoBPE8{N!r=R=!E6)`2{7IR*wd4GlR1J|18X5D+H?BQZQ6AO#C~ zZj2B*CWQ{41_LuRDjXXdb9E#HtT+@3CgjLrD? z`1bbp?EnCCbHrO*xc`EI{B(5u{QUZAYCAitgoVla`ug@&Rbpehnw!=D0DAxcF696K z_gGjgETMOJ#PUT&N=mXpMX`H)#`N^`Mn3S?zx3nKxuvndD=H4<1_92Nz?A0IqRO~o6>FfcsJ GK>#~&ADH3* literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/tj.gif b/lms/askbot/skins/common/media/images/flags/tj.gif new file mode 100755 index 0000000000000000000000000000000000000000..2fe38d4ab9213a814453896b2edaa13d69751c4a GIT binary patch literal 361 zcmV-v0hazpNk%w1VGsZd0M!5h_u-uK^77qdV?>HXQj=5ENlDQ(G~a7$J7MMc=y z*!ktE^wo6Ybaedu{MuGlEqW~$Y!=;ITmTvX_St@!008;<`7(VnK88ND007xiQltO? z4Q37a-i@sQ0K5PI_xJbu`ufw;)Arw&*G*0K(P18O9$lJU`10Lkpk(>!wmO44SeIDz z)_6;fOZCrPBXuLrEG+ligEfFPE_^QZ_4U&{J^TCn_V)Jk^Yi`v{q*$o{{H^I008y% z^#A|>A^8LV00000EC2ui01yBR000Jtz@D)8DQ1oy&t_TaA)bxs;UEx59vV^;A?adC ziG6m&a23ORXuHz5Eca|}BhgiIU*A&0~jkaG!QT*5fL05G(W2+ECd8D1Ff$Y5D>Z(Kgr6= H%RvA;T1}p6 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/tk.gif b/lms/askbot/skins/common/media/images/flags/tk.gif new file mode 100755 index 0000000000000000000000000000000000000000..3d3a727fde64fe5afc971a13eb412b6adfd2de77 GIT binary patch literal 372 zcmV-)0gL`eNk%w1VGsZd0M!5hN`-D*b=Cj?i+Ymfv9e^Ks+7aKP_MM8m!g|0N64?R zUCYB)=IQr2RLcMWrT_qYR&LfzWz>w7qG^1}WO~}ArDQl%(d*n!it#^at|=Q zZiC(|ShZE6J+iZYf{ukkUC;mkv==zTQfkCSU%jWNdN54M-O^Zli@vzFe|m$Bf05cC zKgQqLd|RYYmX>1A$Y06EdDhKt*wAI;+=h{!fsvezOXSZ9d5~1|~QjkCxAr=?}AQdfnA{-Di2t6bg7A1rRBMkzH z5h^z*7#1fTFFY`n510}=F*ybm9RMBx8z-d{AtWXXHUtV4t{brr4io_p69qIE7djXK S4JQe}OH2X})YH_|K>$0GgO)@9 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/tl.gif b/lms/askbot/skins/common/media/images/flags/tl.gif new file mode 100755 index 0000000000000000000000000000000000000000..df22d5823900f670fcccfd571052d18619ac8441 GIT binary patch literal 360 zcmZ?wbhEHb6kyP#$Sy@>RA3l7_!0_GMJ0c?LPjd1n28O@c+234TJ_!r|1d5xP{p_&*rmg)+Pw%_F z{*R!bpJ8E3mMs3r%Ue}d<>lq|)5i7z(7Ml|KR4UVotE^o%=&L|@UQzpKYuy=d}{sO z%3#)_`2U;?{~D`4UAO<47Fk&6`p3uTUu)Fn)iu9jVtjmp{>1q7PmBEd)8_B4n!7;T z89+esCkvR?0g)g-F|f5d96If;6W2FcrTOFLj|puWtx6N`*vN7iKMA@Tq#3o);1Gk! zt$2+^+MGp?KS>5w{;})xP+D+G#6U)hgPFUGuU&~<+gh@*xuvYa!kAGsNW&kJvCUQg1b!{qiMqQS3Jb+e7ief^9TsnVMQIBd(jAkHiJ^(TR z(sE@CVl9uho*Qa9Vwrf(YFx==Uu2qj%x!0^001v`OLnM=#amRnCmGavY_-n5b*G3S zZa=@&$l{@y!Pd!uu9CN4UekYdMu%TVh+)lTQ_*Qug|L;uV_;{VecYOo-imP5g?<1z z00000A^8LV00000EC2ui01yBR000JzK%XyF3Tz&o$6@(v{(J@k;l+wEU>?vUfyq1# z5grMLF;p6p3&K?SsvI25s^)5p5DN+Iw>gn=A{h(-03H?#5G@vbEF3Z+H5L>jhlw8- zKMx8b11ORR1&A#l0tlH5Ap Nvp)evPSMdp06X`pgsA`k literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/tn.gif b/lms/askbot/skins/common/media/images/flags/tn.gif new file mode 100755 index 0000000000000000000000000000000000000000..917d4288c9424fb3a01546189e8765091127b7c9 GIT binary patch literal 375 zcmV--0f_!bNk%w1VGsZd0M!5h^fNQ{JUsPWT=Xq1{dRWt>+9?Q0Q3L=|9X1*Qc~pr z0R6SK_S@U{OH1`5Bl@wi^BNlVw6yxGtNvYG_C!SbJw5tFMDi08`GbS`R8{_Qa{X>@ z`fhIV4GsHYVfp|7{#jZ4YHIvuX8K)S`$|dR008_?Px|TU^sB4=R8;miH~8o0^Ho*( zy}kXx!S`rq{Iat5WMuTAq4(C-{omjDSXlOaeEFQ5_>`0RcX#~n@BLF#`FMEU008{} z0RI30A^8LV00000EC2ui01yBR000J*z@PBvEBZd4gYsZ7{18wAL9+>oJfm8s;Y3U( zAQw%id+=0%0rv@BEC>ls3-=m0C<-3|lzQJz7#IvO4h{i%8apO12`4!e5d{Jc5Qjb+ zCL|9WH!C+b3<3}rDheMVBo-19F#!S*C@dEj03{zH78Ml_GX(`Q0v8<}10@GoS_usl V6ai>y13gPk)C$%mB|X|f06P+8o0b3o literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/to.gif b/lms/askbot/skins/common/media/images/flags/to.gif new file mode 100755 index 0000000000000000000000000000000000000000..d7ed4d11641ae1c1c9001dddc7653973045b6c66 GIT binary patch literal 367 zcmZ?wbhEHb6ky>fLOY4)K-jAT5ud1p)Lqh-N z<$Y05_{huq&Bf)rm)AQ6hOc&Z&lnhfCnkQevifdd@YBlbmy62}GqYa|4Br?SJ~1%7 z0?N9%eUX>{VPf)GQu0q$)}OSrU$L=|fZ|@BKW%J38yVeaVED_x@XOx*|NsAg{`~n1 zM1TMOtEu_@`}h0zAK$-!|L@|(pAiv2vCpQaKdo)Pn3?@cNciLJ^B1TvIQZYce|H%e zK7RbD_>+YJ1av?I$WIJxJqP9oNK9U8B-xv`xzV9c`%w3V)w)iLPIk0$6fCf0Sn%{x zDVLg0>W3fS(so96m->bCaJE0W{(5JbjsdHARt1lskS4bcV_I`7r<{UFCpV*Ny4ZwC zOcpAViZist;M4#va<950ObGx_e)FmLqqyT zM*dx0{(5@(JU#Lg6ZbJO{&sf!etz;UF7zNE`B72*o}T$sRr+CJ{&8~sZf@}n4f;Sp z^(7_x00913S@`7S`8zuLadGn*8T@K${AOnQU0w7aANV&n^D;8v008zZEd1o;_x1Jn zx3~T2>G@b#`=X-vjEwX)Hu%`s^z&1=FloSdBO001^NHuL}h<^cioeSP;!OZ6ioDk>^tV`KjF^ZP_ZT3TBBo16KQlm2vd zQ&Us+EG+!)?)5!A_ck^}L`3*fQWzN-`Bql);^Oy3Mf+i4`Tzj@SXlgKX5Ih*`dwX2 zOicSqN&HVw{OjxS-rnQE!T%*C_Li3YQ&ax6wf=c|{pIEQ?d|mA;@i8s^@fK100930 z00000A^8LV00000EC2ui01yBR000J-K-lQ_8{twt6zaizG#WDtV}M{-Yys7YMG{FG zKARG4D<~>zYaIqU7$YMsF$OCSTPa-< XCNu;KlNBY$Oi$Jj5HdtWJ3#FMOnWx-8hvD?$HWv97lqg?>qvO8n2**KTU!;U!T=C}U-{`~y>{r&xE00960 z{{R30A^8LV00000EC2ui01yBR000Jtz@D%N0$Glhi}FHC^hQvP20;Y?WUfab;q&Rf zJl;-1o)8F=miIuiK_H&WhBLrK7z&=lQ8K_Q3v?Ja1t@t83_Cjq3xRbZA9o!B0uCt# zfPym-A}|9T5-B+`a)OW|5e1(TV`XP&4Y9E&7gi4sCMGN@0W~-jz&}d~#2FbYFBU(` H%RvA;*H)jd literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/tw.gif b/lms/askbot/skins/common/media/images/flags/tw.gif new file mode 100755 index 0000000000000000000000000000000000000000..cacfd9b7aacab39f8975b3567d65ab0ac0e62813 GIT binary patch literal 367 zcmV-#0g(PjNk%w1VGsZd0M!5hT!hm2HZ}K4OZjVVgrwg97pwU^Jz|N}_C!RuxaX3R z+42(;bClWlEiLvcD)KHa_)=2!B_;a+0Qpu|03onbeb4kDAby|S@eK`4c+2`gK<)ql z^*%o4008@8Ve~pW_b@Q~MMd@i0P+9;>Hq-Z008;<{h68F^dBGd85#U$X8K)S`8qmR zfzkU)N%Jx?-v9u8o!jg|OYt^3?MF~|nc4IrvLjctHMir)KbL%AFn40IVgYk3wN9R)E505}yH88dhhC@CKu z5(7LgAsQ@&lqm}~Ndugopp^>?FEA1+ou3VPsu&m#Fd|zMT>=OQ93M9?4-Y;}PERBx N9UUblFh0^j06S#9jNSkM literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/tz.gif b/lms/askbot/skins/common/media/images/flags/tz.gif new file mode 100755 index 0000000000000000000000000000000000000000..82b52ca29807dcb6dfcc632cb22c853b52f41fd5 GIT binary patch literal 366 zcmV-!0g?VkNk%w1VGsZd0M!5hH>mW~=twm*GgMGB0EGY-n(~~{hJ}v++UjFly<5V? zGv((-l#VL^EZzV~%ve}h(#}Hwc>n-7;(Ez?&fNe~wNi4%b5yxgZpTgG-b62?@`{K< z4wmr%MCfkBZd6o4RI$8w$akH|0C{vt=I~w5=1NXZPUY=4K&|$_y#TwnJ(Sm4U%>#R z-b=3FN5SD+laE&K?OZ=UJoWQQ)zW5VWMWgT08zM1*yTZKziEAYJTs!-;OJm(ZLC>U;-H(TEIYv2%*H~(po?UXafo*1`Y%kZg3lCbT|PZ01^=%eGngK zf+q$A1(7}oGmB>oI4>Za8~_azG?xN`0R}s$92yNjH4hIkJS06RC;$K&#J5Zb6gCbW M9TLtB&=NraJ0=2<1^@s6 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ua.gif b/lms/askbot/skins/common/media/images/flags/ua.gif new file mode 100755 index 0000000000000000000000000000000000000000..5d6cd83f597ea6681e2afe62af7745ce3f6ca463 GIT binary patch literal 360 zcmV-u0hj(qNk%w1VGsZd0M!5h{QPM7`C0160QB@60C@aYxYzmlJl?hdD5Lg9vioVm z{QLY%`};)m^E6Sn{NlX;a>o7uX!Y{)6MD)207L0ZveWnXMD_JMYr)=3v-k4yE?vI< z^3?!hz1%{t`SI}$`rZKc+5kMR`2d3cOSb$stN8f%H23!}_Vz0E^(0if{aL*JF{$?e zSMNTn&v3-x`1npkuF)W$^Z;G)E~obZk;jVA=Rd9W{ow#%zWV@AnRm$kTDtn~&j47v z{s4vlA^8LV00000EC2ui01yBR000Jsz@P9p8WE0!h;r6yjV90?1fr#}mt*h$* literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ug.gif b/lms/askbot/skins/common/media/images/flags/ug.gif new file mode 100755 index 0000000000000000000000000000000000000000..58b731ad5c6cad47c4f3fdea21763c6537010a5a GIT binary patch literal 359 zcmV-t0hsTZ9Ldc$_uEAD^(*~SQiX+mt4&QrL`0l3 zGwJ{U^I|pg^BMNoHu(4V#K~8fCvvH7Y_<0hzdd<35^L84=NJ^lmZDq z05Tq%0|OAB8lf5xE;BI!8yhJFtPQRsBLycJ9UT-DI5;7@480~MJ{iIb#Kk?wKFK~o F06WatnQZ_7 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/um.gif b/lms/askbot/skins/common/media/images/flags/um.gif new file mode 100755 index 0000000000000000000000000000000000000000..3b4c84839342c61a9e9b079291cb4b021824019f GIT binary patch literal 371 zcmV-(0gV1fNk%w1VGsZd0M!5hkhb2ayyfgnOtAm})YR1Z`T5EK0NdKzxytP7LqnAS z0PXGV={h=h008FZ=2U#n*E%}(_V)1b@a|n*^!4@I0RgAGged` z<}EGM003Em)7m2=;wUJ5wB7vt{G+(wrMcqs^YdVp+5Z0ie}8|_(9rL1Z}Oj?(HgwPoCbr1xqqpGZDk@KR$ya{R?(XjL^78EL z>|2A?A^8LV00000EC2ui01yBR000J%z@PBv**zY*3#h{Ebbcp9RCFu2A`wcjCqexX ztyU~xA&n*zL4qMLd_}Q>B{BkVc|Ofz=0OC=W&j~-3>h2=13MxC1tb&z5*T z4=-gnf*2+Y7aSlT5DF;;StSw@lN$~W2L~`0D=KMghB-DLEG!TWGO2e9c^fnb1Sv2~ R6;2%n&;tVs3Juml06QBhscHZK literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/us.gif b/lms/askbot/skins/common/media/images/flags/us.gif new file mode 100755 index 0000000000000000000000000000000000000000..8f198f73a777a6af8d3c8e0b9b2ce48168a216dd GIT binary patch literal 367 zcmV-#0g(PjNk%w1VGsZd0M!5hV`H}Z`ug_v_3`oX{6FMd)+t~b6RQz~i+ha=mD=V72{QPBRx?@WGIy&a&mbT%E8IW!TtSPICuH}{{8#^`tkAa18?Ei{{8*o z++%0NA^8LV00000EC2ui01yBR000Jzz@PA!N&$|l2Vx0<{BkH=DIlQ+0->w`_Cs+v zo`Ju(0i zIDb4K9W8?qA0G<`A^`#$8+MVC77acxDm7+kX%V0WxGEYA0Wn&rsXHq)79ulC2u)2S N7!(u-2O82r06XTKt3Ch# literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/uy.gif b/lms/askbot/skins/common/media/images/flags/uy.gif new file mode 100755 index 0000000000000000000000000000000000000000..12848c74131363a1a78d279e0df008422b1707fe GIT binary patch literal 373 zcmV-*0gC=dNk%w1VGsZd0M!5h@%H;fi__%fSEy~E0ryX58U@tnfv?(_I+q}t_4Qnix#T&7(5uVr_4MOio7(N|?EpEfD0jtmtKZ}4@0!2k z>gwrJmf57n=+oBR&ePWO^Pb4g)56Hk?(TV^#OU(!mFe&FgRg5)ut8It(x{3=C)|Cn^R8 zDg-qzb9Hxk1y?s44;Ky<6@`X~i5M6GED8Wh1F_g*4pwM4d#!!qcQlV_X0LQl(QglQgWX)=o~N zs#9y#)#_SWs;*me^YineLtc1zyPZ5(07n2WkS>`vQk5}H003XTz0sdUUgF~Kjw3G;uVrPhOQB1;a)vsXI@#Fj+1c!~v)2uO4Y|45_4W1q{QUIv z^Z)<=A^8LV00000EC2ui01yBR000JwK$NBv)N2g z55eGigLPuDf_n42)IJ{$$6^VMOeY0P^?M!uJv}ZA3=IGPJU%=XEr5cAhKN2OivkCM zgN28QARs;|ZgnGfc`Pghs3$)p5)=?N86^n`2(>yoC94t<5f~UW6t1wbxg-n63jxXj KKh4fRK>$0~?46DP literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/va.gif b/lms/askbot/skins/common/media/images/flags/va.gif new file mode 100755 index 0000000000000000000000000000000000000000..2bd74468d6b328a625c2c3a44f46dee979ec4b97 GIT binary patch literal 369 zcmV-%0gnDhNk%w1VGsZd0M!5h`uh6&>2vnuTm0>e`|5i3<6-H{0{iTR`si-*^77r?-Sy;d*4Eb3)6(nbtn}eki6e!)2#sX_4N1m_xa~( z`}*d=)eem$>`1bABvjFSO z2hY#XA^8LV00000EC2ui01yBR000J#K$XGgGkA`!O2uM9IDMt)U~m|q09e3Bt~bosFgUFR4!2l23cLIxVa!9Aw3uv PA|fvk5D^hS)Ik6{@a)B^ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/vc.gif b/lms/askbot/skins/common/media/images/flags/vc.gif new file mode 100755 index 0000000000000000000000000000000000000000..48213816af17a104ca3d031312588ff1ec97afc9 GIT binary patch literal 370 zcmV-&0ge7gNk%w1VGsZd0M!5h<+T9!_k92)04bs=7nv8M*+f@Y(Rq2~>hd=H{8IAs zTTHS{089Y!^dz?4HpJ#eOR_ES@J;{#v|wP=X1y~pr7`sM9q#fd=I$2!{A2t3MU~Gd z_V`Ed??(I909LnE-|so}_C)>tZ~fZiZsGOyJ7i?uJ*qu|g5K=G0Qvbm4wMe~ z_%#3k#{dA4_Rs+I@l$)qMrLQ$x7{S@@E=65LZjC@?Db3Z^kDb*E{4l1{QOLw(vPW{0^y9Kt#=06`#+-d6>;| zi3CrhSs^L|8+SHQByb!KqJuGP8#FZp1Qh}jB?crND=iQZ4Hg&#ETR%8B&RJpJsTFTEU}{}Y;GJOAtokgXrmG@ QFDlL|0MP&f)B-^OJ8QU=H2?qr literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ve.gif b/lms/askbot/skins/common/media/images/flags/ve.gif new file mode 100755 index 0000000000000000000000000000000000000000..19ce6c1466f493ff8702f776364295095e8f6f2a GIT binary patch literal 364 zcmV-y0h9hmNk%w1VGsZd0M!5h=;-$J0s{U0cl`WmJUr7F7|H$S0Q4v*`BYTv008{_ zTlhvs`};-f!2lv6%=Y#wa&qIDndM4K+Vd6`{NDhECxQ3(PE}Ri`(a{T2v_<&J?Ogt znn#xPJ3I2$0Q502j53Gv^7#M&jY5_H_S*or)BybaO!@ge`1m&f0KWbGRHdcr_xCa3 z008~{QMuY_eHnPH(uwZR0O8{CgM{9$YN{kh863OF{$woXuOh6r26)05&!b4g?~2dwnYeHxV<01%`Nh0E;(* zjs*obA{jLt96386AQcrbs4yTt0T>G!9UmVD2NSjv0Us@`7zn-#z#Xx&2R{MF3CYSW K%q>69K>$0TIGSMq literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/vg.gif b/lms/askbot/skins/common/media/images/flags/vg.gif new file mode 100755 index 0000000000000000000000000000000000000000..1fc0f96eed0e8e31729bd61b0dafaafd9e2c76ea GIT binary patch literal 368 zcmV-$0gwJiNk%w1VGsZd0M!5hv2~Z&tGEgRk^lftJ3OnlUwTx6Q{u$XEiI(VevkkF zGOEL%N=ve1W4!Y{Hb9*F{CK-*|gnUAlM>PJ3Ra zQc|_7tXl7(omPZrGc~4?bEzUCpn;xH001DLzMfcEx#f|R*P5w3Jg?<~fnSSWex`4n zmS&=zZ-88%gAql>zg<5+t~ffW%@r4ygT39(-^jveQ&YZDNvvFvN#bZ{VO6h+dcDrEeN++Tb~mIuCKQa OP0b3=3MA6fK>#~4#*32x literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/vi.gif b/lms/askbot/skins/common/media/images/flags/vi.gif new file mode 100755 index 0000000000000000000000000000000000000000..66f9e746b6aedf561e504119ba5827873e95414b GIT binary patch literal 376 zcmV-;0f+uaNk%w1VGsZd0M!5hy}ix*`QYQ*DC_I%kJ8lA zu=VVWcW~0y*4KiTN%-~CE2lM>r2|&>G$uO z{QUH*s^DBk+3d~G_xJhG&(HAaYxnl?>(w6k^1$rhM)K@)=;-H*f8e~%ro6nm?(Xi+ z&MV&AUhe2I=jYAIzf$h**5u`u*46Ut<)i%ezGP6{lCw>moZY{_ZT0I`%gf91^78!r z{PFSeA^8LV00000EC2ui01yBR000J+z@JdaDzXMB6k!?pQ33(m!nfJ@D5b|^QE-Yb z3kv6y;RHk&h)c)vEi9H@YBFIIUM&c$!2l^P7#R#V9v%<^E;Sth3^Wx!J|ZIr9|sT$ zIU0)z5)vQ|7zUIFCKU+;i){u55I8e2I0ywK8a+KB13s%PDku;n2stZ4Jv=C@s~{jF WG7=RPw#7#~Vbius< literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/vn.gif b/lms/askbot/skins/common/media/images/flags/vn.gif new file mode 100755 index 0000000000000000000000000000000000000000..f1e20c9412cd1d7b33b1daf3b2c178896dc8b1e3 GIT binary patch literal 370 zcmV-&0ge7gNk%w1VGsZd0M!5h^*lW6008s=0RMV=Z$Q(|r8}|tomt>1Gt7B0Re2K6Fw*Y-)IWQI#0Cy89EhP>g9}zJa7Aqu$J`^n` z8yPPVGc}PU4JQN~A($C15i~qIk_`Xkh5lViL;|4Z{t5e0n`vI=`<^?c9IDRa7k-0NcGH0H6RuK|iOTd$6W&mU?-! zOiSd=P_uOaRjpM3Jpk3mb|N4h&A)S7bXb)D0LPU8)vEyM(@)TMcNoVRGchg`#uNPj z00000A^8LV00000EC2ui01yBR000JxKojm~^BIpmfKvHWkytv8v_M5zHGp5J;<=O% z8VF=S#$dIb%H+}#lLr_KBrBUR46a8i<>2usGbukmG#xn)1_A;Kj2jy#0xv%+BXnFaf>|z!4DyxIZ^PF(}Fj L%n1w6&p`k?=S6^; literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/wales.gif b/lms/askbot/skins/common/media/images/flags/wales.gif new file mode 100755 index 0000000000000000000000000000000000000000..901d17507dfa6428fee33367dfa321144dcfcece GIT binary patch literal 372 zcmV-)0gL`eNk%w1VGsZd0M!5h>W+{~y+8f^{g1|Rx0K-U-R{uKzCkbU z_V)7G*vVT_dvPE6`1pF2JmiFbAX}WbY>sxB#C-6A(&>Vx9`KL0eoJ z0!9a7i!BnMtsrrUg&Yiss6i5?1vSA;vYJ64A1xyQ9w0j^4<`&49UXBU4+I%1DLw-V z5(Eqp5e^O;B^o9-7?uhd2N66I8xtllBPa!>mjO94HykV^8Yns_9JB*93knJ^2RJev SIUK$0m_LeLF literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/wf.gif b/lms/askbot/skins/common/media/images/flags/wf.gif new file mode 100755 index 0000000000000000000000000000000000000000..eaa954b13694ab9e3aa3f77ed03e372f47abd722 GIT binary patch literal 377 zcmV-<0fzoZNk%w1VGsZd0M!5h&PhqoR8+<_HQRP}!7eW1m6hRjw5!6hZfK0Vrvjcot`l>h*P004CW0M%h( zi~s=2L`1g_56L<@oB#mVW@gMmL3{uJ+HY^zYHHJ6UC~ZX!!|aLcO;jB`)Sd~r&_Kx8K1!xK#3pNWCBR3HR1_nAiHUcgS6c#Nx zBM>Aw00#^o0t74)76dOS8W1I&p8_>05(_OaGbD|`4_aIa038Sw X5-2MK!%I#a)EOBg8rc^Y8bJU%oBE4P literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/ws.gif b/lms/askbot/skins/common/media/images/flags/ws.gif new file mode 100755 index 0000000000000000000000000000000000000000..a51f939ede562216b0b37b393dcb489ed82968bb GIT binary patch literal 365 zcmV-z0h0blNk%w1VGsZd0M!5h_e)FtZEa>|qxn2NNlBFUL`1#4*76e*_bn}YdarbA zqV_5(@-8m;Qd0FLCHnvX`Bqo-ARzG#4FCW$`anSL008wqKIQ-b`(a^OS)BAbI`=Rz z`$a`eL5KDL08vMZ@&Eue^dBGiHa7S)H1io5{AOnQU0vb;0QovPoSnn_ zN=X0!Cr3Mj^D;8u008thHm$JGc6PAP(c*D&sd;gy{7+AtqssF&H9|rNfACB_)6>)b zz`*{@%=M?I_^+?*JS?uRuJrZw_IPddab@*&Y4P##wY9alxVZO(dHnqR^z`)g_4V%n z00000A^8LV00000EC2ui01yBR000JoK%dZ;DH=6M&EeqlcyuF)gl4ND5GjL$>A7Z6 zphjcGV8pf}SF3fwU`VABOrdy{Ouf&Cv)hFR0|W#GJv==V6nlMtfrEtEGj2CI139h2?+qF8yg7~KM(>I76BC%549b)tgR3cyaK+k0kXM2#Kp$PK>#~Y Covxz* literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/yt.gif b/lms/askbot/skins/common/media/images/flags/yt.gif new file mode 100755 index 0000000000000000000000000000000000000000..a2267c0546ff372e5c0717dc7c79518a2e6c7cce GIT binary patch literal 382 zcmV-^0fGKUNk%w1VGsZd0M!5hN=dQI%E{&B<^JgC);3%v*F7z?w%N6`wAR(t?(XjVva@bK{F;^OD$=jrF?_44w&xw`uM`}+3w z^6&5S^Yi|{zx4F<;o{-`_xIb`>SAQR@9*%~*VpLl>iGEh`uX_%`1j_muE@v7-B3cn zy}HE5;qK(*Vp_BO`S`A^t?KFN>FDP9@9**P@!CQ!^Y8Kg{{Hgv^7i)j-QC^K&(HPs z_5c6>A^8LV00000EC2ui01yBR000J?K%WqU!EqFd*XuH~2tdByZxHc#3fMxVc9QU- zxkV_HifvGv+HU9d<#Gsw#?`2QOoGoY;Z6hsHX{Io3N0BcJ|!JF2OJV4Fd`xVGc*<% z9}hMQJrfc(ECdV$GbRoW9S55l6FLekHw+9FATkiCBnvw$I|?cWz!ftNGdevG8X6WB c5*={}Iy^iS#tKbOJS7hY2LU|?J>fwBJ6lxJZ~y=R literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/za.gif b/lms/askbot/skins/common/media/images/flags/za.gif new file mode 100755 index 0000000000000000000000000000000000000000..ede52589199b0360eb5d92c3aa174dd9b4eda473 GIT binary patch literal 363 zcmV-x0hInnNk%w1VGsZd0M!5hF)_^b006PF<;vvPpxUzcF*1+H0QR@I0052rWoA>f zX{nC@YQJ{?X8`#~NcpCw=<04pMbkU0T}ZKL6cofkuVd!hTKQL3laW?hxOaNUkaBW! z=I!PinK(0|O8_|l0EH1asaij)VRO4@DWOa=rdQd~%iPpd$-#c2o?ddgU1ML%_`SbN zzjys_a44KW>GFq!yIRD9Dg=(mWWb=&5I7H)D?zi00;(L8Us2Jz!9{=2n`J#0yj4Z J%+1b006TO7llA}r literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/flags/zm.gif b/lms/askbot/skins/common/media/images/flags/zm.gif new file mode 100755 index 0000000000000000000000000000000000000000..b2851d2b405c7ffe20eadff1b0bccbb7fe8b62e3 GIT binary patch literal 358 zcmV-s0h#_sNk%w1VGsZd0M!5h12q8d85sa)0AZ|Qe7$@CSO9UhacQw>07n3Xz=QyN z09mM6ZnbVK7Z+x*X59b)mC2R!C@AMFC6309Ri;(#I5h2qJzc9^6%-K4004-?hyZf{ z?0+_7u4Cl@0C~K6QlwILxpq#XPLarw=%i(BvTfa;TRAi_=t4Y6pGlmsb?BK_fWLrh zvuo)zF7Aj!c&=#iibv>(IcTqFdAWIBu3d}7i{vC6S+r(QqEP3IKjWfbC?X(Ns8<7l z0{{R3A^8LV00000EC2ui01yBR000JqK%da3C~h`_WMPT$L7t<@#HLB8NITOC;A9a# zAp@i3z}0jzSQGLMj~$)Mf|8p76eF;h9Ir%pJTV>$6K-S!axQ;7FCH5G^(%_TIQaKBkgjm`_4Vd*NT%Co08;=cC@9!j zI{p1s{QLW%S4PJ=83BXlXiPMy_R-c=~E;drwE0IS|{@$n7s@9&mlPWSgoJ3Ku1){pJQz1QK_^1WV( z003VA0P?x1qSjyW@A1!4Hs9R<+)N^;rAxq3MCs`O@AB}RTvqYy=#=S< z9u8+f4xDa0TB@-xB)W-I0RP{6BNP%#KZtT7E1^c L%M#Ad5Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipb} z1`0Te$~fl$00FZ}L_t(2&!v$uiyA=`$KRXXVHZ!qMORR6L3S%y1y8Q976R#P!d;zk zpu4?a!j#8>u}P64g-uAx3&9Vtuv>+It%b@cYId38j+ra&Og!<@%T6PLHNhY`)0Fwk;!Brj$_F= z2hKU>^ZCT2l;v`HUaeL?tBBY)O|v|mP9c>_-Rrup zP9_u3G!1OqMoQ`ZcDq%x*{r=@uluSH;!_kwU&1g<(&_YVyWIvUB~VI1(=-^5$7+%! zuMqJB5r3Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipb} z1_=O(<;!SGu3>lMEqv97#7Y6X5~F%6cJS8Hby(8dDQB&S74ttW zA{6X*J!7AYzPSfag;7slpsK#t)v6z4(2T}g-`>qi^4$CN7YmOziVHuC!SK`mvD=V- z3!WZ=R69^B-jU79`Wc}R6goC!Y97VT$zDxsFzTxu@Y}>f0lv#DB#k`8vz~w06jN71 z+kX0A_5T2|2e1e7U4Z?+0J+5ugyY;uo+nTMJTSxzqHO`Mx#p}sn9-R1&bH(?zB}EX YpD4ec72Ga}h5!Hn07*qoM6N<$f&)YOMF0Q* literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/gray-up-arrow-h18px.png b/lms/askbot/skins/common/media/images/gray-up-arrow-h18px.png new file mode 100755 index 0000000000000000000000000000000000000000..78767445ec3e38fab840f8177c249e38379abb06 GIT binary patch literal 383 zcmV-_0f7FAP)Bj>^LJ3~~S>i&IO88=qln{V-q zOM40!3-puuch|@DVB6cUq=Rp^(V|(yIunMk|nMY zCBgY=CFO}lsSJ)O`AMk?p1FzXsX?iUDV2pMQ*D5X*aCb)TzBu@{r~^}iVZzqfg(&L zL4Lvi8J=!8@B;EgJY5_^DsCku9AK6xWM-3k!OUU6z#qV1KKathOF(%BPgg&ebxsLQ E0Ke)mAOHXW literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/indicator.gif b/lms/askbot/skins/common/media/images/indicator.gif new file mode 100755 index 0000000000000000000000000000000000000000..1c72ebb554be018511ae972c3f2361dff02dce02 GIT binary patch literal 2545 zcma*pX;2es8VB%~zPr=ibVMCx-JQ^BhLDAsK)^**h(ZDp9YGuzZ%~j!}+w%FI;|aC7){7CdVvG)P{bng1y9Te*f}~*`1kQl$jwb z$tlW~rRS!X?#xfm_&6tTdp_`cjgYwbRFLNdoJCN$S-yhg`ZnC-yvedRSmOh%;Y`Gl6bY$Z-}#C=#F4%9!I1b zWQ~f+9P?;vhCxWwlwl=lrWG|7IYo;{jjmzJ5R9?f>n%-d@>kLINUc z4wM5dAO;kq<$}Dk{2-u0$I6@2N}&cUx9nmV1dYc8jfC}%=F9WCg^OQK9C6poh#2!A z3^EU*UFZvS^)?bu3T?J;@Ahb~%I?+@4!l5!*TjC}GIslNan-RCrrd~PdHYnNLJk+m&`$Y+NV(e>CCu%R#_8GqY4cv#j`#uRWdsg9DxWy(?oOvgCU}&@jy%c!H&-Q zqXJxajAtmQRoRa9V-RFXXh-bK*;Fum{BjpkYQGX~i@OZ^Dx0n&H}kvGKqQ?w(6iGXu_g08T|_hp#ZvFzIwKF*a=oMJ~3UGAjZ?g}GOxm44td zXoyYrU*I=y*vHv89hkYH(v5R#wc)BC3dZJKb3K)f>zaM3%JP(mpecViP0eKKYf3zy z->jx_mc?mCtPEvCQ?uppk?eLJt}_IR7giW%Jr)RyI!+E-voIs*lXI*z`GQc_&D#X( z{6G};HPYj6O|$lXxBJeDaweqa{4L=tOZCjTI^&UOxXg})LRG_cr^B9Rqt(i5ORbQX zq`_xCRsH>xEYY%&*Nyi#{S_JZNlTm#K56`RI%7^amom;*h90Si&g1CfaFV3D|a!`3Y-GKKbL*KSbl z>I96`TR@CqPJl(>QqB~RvK~-U)`e`l4LIqj+IU^~yyIe*|BRVB>4Bup%j{tLdKz4j zY^<8P8m~GRGz*yv0&-RJE+-keJ+%m3wNeopzsltWd->eWmBVwUr)pX` zK~CD<;~Z*Uy3W`3+MrEYxm5qYQ!z%YI;y7DTG`UVH0;@{M{!B&id_}3DBQ?zsotuR zEGLdRx25nLm%-wjlnEi;-aN_1S7???rO~WgA67jjr&(vRa3y$u#kqJbeKnw z{!T!1li9>M+sJ6AUe+*9d}2uGjhzd z|L1Rtp8uTGYyZoQ*`DS^m2dw-X{a)l+3m?ncvn^+O>)hdd3(hMtlhkRGns{<8c0I! zDDjpmwtj?@!6kA|iu3q+Ai;@JR+ zfk+ln&YFC{4bhK6IxVgLs4W%^8Lk`qzWU*L>yq0A3;l}{!wKZ!ue)C)SKI)9dl1hl zhIRLV@8E}rwvE{gX(}$f6x*k)_`*Ijt1=EU-Ls6-(phomeQBgtUs z5Xz~Cd*nE)Ac!0i4ep}Z1AugMB(&F?)#CU{Qc{Sp^vKsdL}vRB30H+Bbzrn`M##H3 z{W8dc_mDroEE+p8_}mnJtzZ4!RNe)zhB)Ds;S57nYSJxtek>^~&(7B+N5MPf2+2xx z5Dl&4X|c@f{Kd|z1r+N|$DmsoVp*3yOdxT^J^-VAk)Z@$4^XrPrFP-Co+MXZ+KJ(W z{JNYvraLLWA;&tRhIKOvhW|HC|L-dLvAUF(MG0(Nl?4tB{RzN7I(}Cb%hwN{crFC8 zji#aJElKvDFV+&VI1V?oUMA>*kto0^;3W8FQBSZ|{ z$v~TqE=(8DZa^i$^oht&h};P1N&wMXorKh*Z68gPV&ouy>%f36Oqkwemyeas$Qbz# zV?7Jy%o7KY6^I=P@eCji%W`o5sf(5hySYo9$l4e2`(hIV_?=H-#R6}0$WVA|*(K@3 z=5?@RlcLh(meW%A4)hGzcvEpm(_w?>zhL*i&s9$2>r zAtk{8Cia|+Y+V!uX9BtpXoF%lswuRKsM!pSs!?yhlCy!269K0|b M?FSZn2B>%I-}ej|s{jB1 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/logo.gif b/lms/askbot/skins/common/media/images/logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..ac4ceda66e823ad3e73cddf5bcb4c8ac6882b3e4 GIT binary patch literal 3792 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 W^f}hD@z|-|$J&n``$k2;s{a5|29cWp literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/logo.png b/lms/askbot/skins/common/media/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..10559161a3e8ef09566d6291bac225281203ca96 GIT binary patch literal 5841 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 bsql9I+IY{C+hEHK00000NkvXXu0mjf6)5?5 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/logo1.png b/lms/askbot/skins/common/media/images/logo1.png new file mode 100755 index 0000000000000000000000000000000000000000..d79a627174b08ee90776540abad2e76f28909652 GIT binary patch literal 2752 zcmcguX* z1E?4wgA2<ibF5B?oZ9u^18n1*~xFBuz3-<%yx3)#_ftUE+Qo`d)qPK67?2S(r z0zbj_1R*mtDSopsN*FIl+1W1i27nS38t4}z)g^qLp7y;lhYt7a0jSl3L8?S)fQbgp z9GIgd0|RA`d?7_d00faod_JKJpnwt1uSD7Z2a|-B1yhe(GKu=epgmLHL<<`k8h-xr z#Rdb>9oZ&!^VeQMo#r!{pg-pxO=%%wiP-5se;)6v3??meND?n)RA!O@$<(3FLWQ+$ zOH6FdOV()2Oid#y4ULJnFZpl4eYEx}KeR}+M$Gk5S zqnm}0W#xsXD4uMIn&y?8n~Sx|d}IA$=JVCuRjV$XJ}sknP^i=`V|K{Uo}IS~d2w;U zw>#2*{<@gaLWBV!hwNSpj+t{B4+|*Cyj*>2K%9^omNa`xrFm7gG6I0)*hvDZW~2n9%+MzzWk{Tzj9=GEY?tX{kpHp1&$#)S9m54a>eAI8%Q-XV);&;*9apqP&PXYp>>%+b;E;=0xznd$s;o zNiW9QI{j^mKNAYvs+E&f1KTijaz9V7?^FoLb3{g=Tb7$1e){vBP^z+Zns=jU$thc( zw2kd%$grJDyMNT!)Sct9dhWuKlacvPp`- z8Y8&LnwaoBc`{&GJVY{O#mCp`P@%=Q3Yaf}s2B6C79AD5YR#P~V>m6)bOTOOJli=W zPNp*HEQD667cH}?Hg{}+gdgp}93YjUmp*9c`gj8_aBZ0sW8avY zX6NiSFgF7CK79K0^heY)4c1V_P-sdc?>r6RHTL$J{dk^GMMm!9(tmz0dWka-lQA#W zXdf`;=ExOJv+V3vx`;W^wrWq(KyLp>!+{ofPxJF9|Ku2~4KPy`&%r!MfevTFv?hRH zP^Pv`=qS9h326iT7wKo$i=g2*B^lbLre~#DL(I(n28>2Gg^v4`z>bmBWVf432BE}! z=%dyMYS3f}wFCnuO`6FNZ&g-=WBwi8-KIYYS~6hF|43W|sCKWJaqiix73<%aT>M#5 z?iKC8)_XL&u;F5WRi;7`VW01Uz?7QJJz;?DfTU)e2~pZhZ!ORA0JzhpA(Hd7nVDJl ztq(E=8|M-c?lGfz@4u1lb$BMrC2Om_dxO4-tz2^b_1KuqlAiJtXpSQ6`8qPSH$8nC zMZpy}rL*@|27`gFMY^W63My8YCbaRwy~}3>~>!x8!nLwhyfOZXVGBycPfMA^MtwF)p}uJ zB~ORB*G&5!yTZ<$OqwMMHLF_PJ&jGd-SXLoYICY?CVF(&Vw#m#{b%s+m#mJxx&FG3 z8EpzW20O+=4wK)eCMQ3=(LXxwIZs6*uopv5ROAkrE?rPBaxA;pgO}{nULCe zHva>TDb#W^s+&*5L4WaxR#7-B1|!KkpC9{0e>FF=@ssguLF9d|wA!E~;qk9j!Nb{V z724{=IXkjH?b#LE~)Ia~*I(j^ z&q7!0`jgh#H!;;L(ylgBpXh9_2S(;HM+P(Zgj)SipDbK@^n^bC=|1xLztn?+(cTs; zL9LTzC`o)-7zuSAeKT~#fv@{a-vaYz1u)j9(QBzS8#fk8^g^SuEFbTf!OCZm@e1Hz zk~G1k6;|w?^B68VecJwAmXgBAYOPakiL>pz(w!`at=gC8Z#gHN?r?plOG7Gru6NLxkY;S=fF2z|YbO3%qJzr|R*`t+_ElUU&&^^{glpQP5mHpLzxP@1#e01HOQOj08Vo z)cfHL0^U{i&=SYTLGdn3&Ju3SY#4p@t=L7hVjDBl94p&Pms&GXe$dl$W59QduVdfz zK_DwVt5H0(psqg{TB3EPB_MT+v zLYBDN^U+I)`GZ+Th^mq2lakVw_C)=`4cD`4R&i(Ib*)VL&w?yA@5bAw@19`~{B3xY kse_>K@2>xA`@bf7AU)z?-rE%2I=lr1p}dH|I)5bN->?*N_5c6? literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/logo2.png b/lms/askbot/skins/common/media/images/logo2.png new file mode 100755 index 0000000000000000000000000000000000000000..bd3cccd9f47793f86864cd068621ab07198b5ff0 GIT binary patch literal 2124 zcmcIm`&Uy}7Cw1j9(f?ogb*T6 zR7xF)f`B{(Do|8Luizj$s9*~MRt2>X1!+Z~1*!Hr|H7>K;heqp+UuPCt?ygs+owPh z9z-=}83O>QA;AHWSUia>UkH!w{6lRrED-njZSe!3sdpXe0oyJ}doah{1$l3>n$0jEa145Xm{l=`0 z1LOGW4g>)b$0!*Rr7zHbv%EOo9Z|$Fo71*7zJWCy1avV4C^u25yk)w+l|hG~+NP%M z5{YQYU@)jZ|6}ppWJ?dj(b2ICDyt7~YcEeqbnFp!&dzSWetl`j4p9%R#_ovkDqA); zH&^*-g-KjDYY(ZuwsyF`U!+y!<>j?5(BX=hvuhN^Uk??}&Q{n9Y4Zjg#C7RoPy$Wi z{*X1KD~4_Y4Z$7tC2Omw9FAC!5Acv66G_Q!o6uY7#pImab!WWHb~w?Z0Yl``%a$|q z+>?+r?Ts74s#CZ+-0m|}TfQ#NUh)?>(Z$J0GLxDv2Bn)2Z1H?F;I?WNsG~%EmS7Pjk|D)ZWt7A)huHmXOer^gBJH%1K9jp$`#$4x zb~w%Q?@pblQ{_46k=OZ}+xH(tNCYC#!ac#N_JOZWKf7U{DDkgy2`d$SnL*NS*Lyk0 z%meWH?Wzzz0i!T%m4v~F`SGnry4R#TIUJ1vJ3A0IipV;YRq&)YZGt}EW(eud{ zE;wK6z~y^1FP?Bu(p|Q0-uZ9VP{;H9U`&*-j-@vuhP{16BhsxE$`{EVKiAp{q~8uq zr@u2SsG*&O$?=ivqQiihvx^*$fSkYwVxu7!GvA=hX{$Q^|2i*Q5IBWivcFZ#_XbQb<4Md2}~BtK?7vNsOe@; z8A7rc8k5Y~=#YTZ36{>eteYjoWt1`AYnuXw~+sOqb zwxMU@^F4f%7!EBh=q1a|wAiZ_D+JOhUX!JJa2*$iS7#3pi1YLFdk4gKV7j*>o*xr) z>u|a52rtb9z` zL5qYJFB}IWKha&}+a4CAY57e4)62=pAG~t$p>#RjdtqsQ+_oJzW`~!f=cC-qY`D zk$bnEefjm5qa|4Mzj(KDJvR>pqy+7to)KGbH~EW!4A`pNaZ zoo-AVwbdl!!x09)Z|CpRy=ESskd{ID`jXDLfL?hW9X-=U$GpHyAgw3%zFZB{#O zNiG-P)g6G3eOSK97L?xGE4MfIYy#f`>zXaygC;%Ei~H&H6ZB;yzeOe+$gdv&7;t@C zrZ*)T@!lqtLa&6cr>4Y#2wypjY&gET2c|!nXb&Nw67mG0^=3cjK+>=HMrGJ-oxxOB?TeD9Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2igV( z0XG0dpY@gi00E;(L_t(I%hi)TYZOrwhM$>9alx7F&XUDO2?RA0W(zkdA|lbhWTUNr zAWhgJtssVwGE0&rh5n8uT@qHrh*@-)xgY0R?C$6kHa6m^4iD$P_kFqN!hc@@!0T5p zKNLTT7f}=uMN33cNW0y_Ifru&YhB$tCpc$k+_^UZ4EA35%Jbq zZ>{xO>&Dz*Z`X$lE9av#!Z1Wcu+~zRB~?|SwZ<5OF$QZbB0?C3oR7``2my>S^f$K{ zot~hSA_xLR z^(9LF3T9?;8k(oV|a+!BB`rtvh7 z`+eTOd)r{nJDzMmZ7^-jOh80xSQG`*+bOM9O5W?${QLBVn=fBkZY|diL}cz@9^v}x znopAn_wU_hBj0G;mUdD;PcFH04~O30G{nU|KrxJu4SyQWn7HM_53`7 l|F*wBX#d3~e*E{J=_j4B6J&6NXg>e|002ovPDHLkV1lFs>mvXF literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/mail-envelope-full.png b/lms/askbot/skins/common/media/images/mail-envelope-full.png new file mode 100644 index 0000000000000000000000000000000000000000..2277e919779eeb21478ab71d5598346dfa701e54 GIT binary patch literal 482 zcmV<80UiE{P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2igV( z0X8WgHe9;^00Cl2L_t(I%hi)TP6IIzg+Dvaf{V=p1rel=lr9AlEe(g@3N&1Snu~A+ zBnqfNxks`~gd!9QlC0Mr3hxpEqM<>IG_v*l{N~4F`1hs&E;^mNw-8R0QW(GhS*wK? zp#r=JpY3O;eV2zgUR?khR+kfZNH0(_Zge$8RSSj*^6|?$1gj z3iw)}v0;*MGaheX6>z+@wSoCYAbbKBBD{t`M{5SnO8|2bX5Q1$nykjc*Akw}lBxF` z8bg13dm~x~JUd5TRZx~3Si3F(pqS5N`g?mnPTjt>?AP3euK>=|^u>wn{9+RC|Nb+* Y1G^5%Mvgs9zyJUM07*qoM6N<$f>X1_mjD0& literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/medala.gif b/lms/askbot/skins/common/media/images/medala.gif new file mode 100755 index 0000000000000000000000000000000000000000..93dd1a3960a9668a92ed55a81402592f296c9333 GIT binary patch literal 801 zcmV++1K#{cNk%w1VJrb40K@oE*q;kn@I-k&}bV{vSuh^`1%k6r<;IMd1E+ZG{w0g~MyWjA*d`_?1 z@A$la&+q&HfPsR8goTEOh-(y!jE#0PICFAzz_X{% zpFo2O9ZIyQ(W6L{DqYI7sne%Wqe>k@wW`&tShH%~%C)Q4uV7~+8cVjU*|TU7DOk(4 zt=qS7(BTJr4xw7TUm@^~T z%(=7Y&!9t#9!C>oFt6t5zwd>cgV;`7JySDAyxO3~?&AYen-@tu`Sa-0t6$H)z5Dm@NG7S| zk}d||0+o_zM{=bwNED(Iku7HVjkApih7e}UBq literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/medala_on.gif b/lms/askbot/skins/common/media/images/medala_on.gif new file mode 100755 index 0000000000000000000000000000000000000000..a18f9e8562941254941a446efad3e6edcb651d9c GIT binary patch literal 957 zcmV;u148^qNk%w1VJrb40K@E)v5}!; z41e42_PDkdcy;l$Dm3n3(BTJqC>oFt6n{%wd>cgW6KueptkMXxO3~?&AYen-@t5f($n3;DZoGDB*+&RA}La7-p#9h8%Y2;fElG zDB_4DmT2OMD5j|5iU_pm;)^iGDC3MY)@b96IOeG1jy(400+o_zM{=bwNED(Iku7Ha6Bh$gD&q69SR=%bKED(R$@R%+>`m}aW!rkr-_ z>8GHED(a{Mlxpg!sHUpws;su^>Z`EED(kGY)@tjmxaO+st^xGw>#x8DE9|hu7HjOW z$R?}ovdlK??6c5DEA6xbRBP=zwb*8>?Y7)@>+QGThAZy4+ZYo f#w+i<^ww+dz4+#<@4o!@>+in+2Q2WwApih7u&Gpl literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/new.gif b/lms/askbot/skins/common/media/images/new.gif new file mode 100755 index 0000000000000000000000000000000000000000..8a220b531225397b6a304918e4d96f6196ef40a8 GIT binary patch literal 635 zcmZ?wbhEHblwuHIc*el+@5|9!VKPrU6JO0wzv{&J_sfTWzkfci3%c&b^ZDfVC#}&> zyOaNZ`}}-T?z6u1zh6H-?MZpollt+%s=r@9J?->=+L`dQ-S^kCd)M4KzTZCopv3i7 zg#7c#dEf6|d^NB3$Neksx6gmOcH+A&vz~S(UH9g@mudETVb^R-1c%-`NuBSmGx$*^t}v|N3{XpZ=QLxvghB=@852m{z+->`t|DZla{DoFCJX;;C#~TaW_r#|NsA29T^4|DE?#tJ3t3Sg5rdM{YC?W zNN`JQTYE>a2m^CyU;l)Olls+yCQqLcG;PM53A6e*r27K<7?(+e+V$BkQ0bc=>K)=6 z>S?AMvXf0cM9U_`CsbZ({cMkrfKVO*0bx^n7M2ip!4S?%Tu1x7I766NnV3TOd0ax> zT^02BokLcHZqTpCs*6-yL$WVW?v z$*r&`a5&o0$QRlWP#C1jBFZZwJ5}J(3P%A63#oz=VNXx?YB3g7+`6(-c+I>yOH7zf SWil{ucTQ5<#GoL+U=08@?DshU literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/nophoto.png b/lms/askbot/skins/common/media/images/nophoto.png new file mode 100755 index 0000000000000000000000000000000000000000..2daf0ffd4333c90aafd71479510144bcdcb16c79 GIT binary patch literal 696 zcmV;p0!RIcP)FMR=<>BGs+}zyR+1b_A)zQ(>&CSip$;tWo`Nqb^+obycyPRT{LUB1#`*2@<8tPF=iD3b$&izilao_AJ$Uu| z?T7EI;9G2Ni9S*S|C71nPeS~@YXtLGZ#Xme_@@>8G%0p2sX6212og-Y1w$?e z!WtwvbPJl0Aa)A^G6(_^hxshQF(4>p2|9paHcPMx2&P29rJ}2$!H> zp6f%wU$+bD9?CDf;J*mCI1D_PzD=;|;mna;B%E*;B%w%FK=_dsy!3SVseTRsf;O0PA5l@7S3xZ$It;!i#Ky zUwupv47dxD@P=FMLIa$I+=l%s0_x#ZiV;DS09Qn^j9pByOm?A=ICBWn)?Lr=Eg7SN z03L<9;aJ866<{Oli{Qt&ARUsBlRXUURcV2;m^$aahm^dfY4!Hqk3O{)q1D z1TF3(2qBkcAppN3{)S!D3NGw#J)8~}C44zdYXvndi+CRUT0sljYWS7$P%CI+|Ayli em8wo4&)6FRqy5_K=O4cS0000${>eZ`v@813YY~Q2faPbmo5s9gt%|d4ho>o57Mp#$&^RgUuZ5eg+dZJYZnp<>ZnO zn8@79#>ir^py2~!6AQbMoJWI#lOrpaM#=_*i%myaxx_eX7A$CT;APg}>iKX$rJbGI pjAPFPVa3)-5*81xR0c3Hu?omAG~5VaYGz`Q2)lD?YqkS}H2_zIYl#2= literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/openid/aol.gif b/lms/askbot/skins/common/media/images/openid/aol.gif new file mode 100755 index 0000000000000000000000000000000000000000..decc4f12362124c74e1e0b78ad4e5d132621ab23 GIT binary patch literal 2205 zcmV;O2x9j~Nk%w1VNn1b0QUd@0a~U0{{97Cr-7}~`~3at@%IK^rUhN5{r>*;`TQ|@ zxAXV<(c$m-`uz9${QUj?tjXaAU#9_Cqyt>0`uzP6Wve@Yx&8hA@b&ra^Y|liumW18 z16!pEV5rI5>-6~hXr0Mflf!YN%x>+Qkd8p6#`TXYZ_T}#Ooxt1~X|1!)YIhOpD5#NM*a<0W#j0a&CWZm>^{ zzyn;Rz}D!)*y=NTw$$VApTXTqiN5vt`#gcU#o6jVg1UmP(x}JZzt!kmmBs1t_qNaF zEOxXxf4M<~yV&LMwa?_c(&n4K+mpH2M25T;XRHups^01ID|NGbsn3_Y+VS@KWSYp* z;_ryE)EsK9fUMC>i@&bQ;tOD?Mu)vGc(qZE!4hSw$lL2DbFy2M#IVcb=I-{7w%6?P z_}%F8#@g%I=JCVW>A~0O1zM$Emc;;Aq|e~)=kN9^bhB5I!`tZcY@o~4Dwe zHhs4lW~<`t^hk)l_4)e+UZ(8x_}b_3h_Ter;O(~1GFN6(4WBES(L=`_W7sA;8&5u>hkx|;qKw=^yu*R z4rHnaUZ-7_#k9}l^!NJ*UZyBu2wvd-h_@b-M1#8yVW@Sa&Gh*C zz}DxCwAK`8te?W&k+|6D@b~`z|2%=Z24AP1z}wd3@G*F`*5vQL)#tg==7q1*{{H`_ z#^61Iy8r+GA^8LV00000EC2ui08s!P000R80RIUbNU)&6g9sBUT*$DY!-o+6Jy`H% zQ^boHGYUk|VhNoVyflg&nXrK{5cMRHfHRGxOOgqmlo8oERHm?$HPJbz#x1Z zpeoL(7W@hZxJ5+J9eE5BP*LH>O&${ zj5jX`S&`8IL!Cx*Fz7_k)nS8ApeE%1x(B4-jBvRPK#9X9Oc*K+HhF5Leg{%TGv{D8K;`L_xwpaHxaF z0b^+Mfl?X7amfU05OYC(Eiz<;2RbaUiytGQ0x5J^bziwedW^uQh}2wH>_T<*uvM9pjgf(kHL(TyFV3}K8NEHsip6(+=y z&mT8*M^FIdM4?9wB6I;j1ZWKZ^G^UCv;lw?B2a+P3?fKiKopBK>LNoIcyNRiBZxr* z3skJY1QoA5aVRDuT=9ep_zX!<2JM_N0V+YPFa`jiSn&@6Y-j*L7D^;UixL1NBE%5F zIz+$0D>L=cJ;YWUy}E(fIl&_o8nn81SpJFsxj2TRoOLkeItpmjn7WRMLXFdR_93ql-= z1sX|!ae@q8)L_8?$rNzmK!~tV!vO{eV8H9GyWWQlALOvc;zfv35CtA!F~I>nUE0WC6>_LJDwEf?Ad!6)ETg5hf{+1h8Trx)^{dte^urh{6Tzy$WAVijduPI(q;0nNaEbGS@D4BQ7Jci(whr)Rj z2q%0XEZ+#_Ct;QoHXnrD7mcRRG|JxzlR06vA#8Vq`IeoTkwqW`2*{qEdy|l0s_pcpGac^C{l1+0Klhw_ z&&T`@fCw(|_68p%Za@V6lF2M~`8?nwT2qqAT-dS*v`8aE#9vMiv)K%5uiT>`Cu)M8Smh5Zv%QKMzFEtB5sZL;q!re*wb?jBb3)yUxCP$DlBcT zL~7f2Xc#phq4g__Pfa4yRFB|+YGk+9pkrbfljbS-=}#g^UkO?BX(*bj5Y=)P6(&8R zO&8EjIa=wQK6IBb$~`nSj@zR~h?^=9$j(D&k8U|fv8T{Z@q#Iibl^PYK8+B{A42a? zn&ISkg6Y&SAlPq3IwwFtymf2cujzUWIlJo?t9T&*_MRU`vwqzc+-Li7Ie}Z~G zw--mnb2~J18Ml|a^Y~h=%%9s4;%rO&*gS5p3SVHc(|ZoU<~DkwMeO*)pdrB+_7<^=v$j_Y602 z`C%7w+v^bCUxP0O8_{GQ#m)y;vDe(lIiw=?CYvtf$V4M<5;OZIN3pf@5@38@&kv2aagW&h{ct;C2I}#);Rf;YA$}%~j}S*U5O+@yBM+JNXeK_&iEC2g zVcl>ivG!|}cK^irokINm*mxIr#*BEg_ZQ@KT);bqU!m-;Md5=V(Ka!J&7D8s_+S%f zwxX{Zm2{3EVkhehN@9O3O&g~f%MTkGaEszaP>gr!-qHPa(2tuSAtncKmY?DrX8gCX zw~BMRcDRG{ov~M8tU)~Wgk$}8ag_SW2Q;^IUBX+vH&E1l6;o7K6S3YvHIh(0Buyvb zPq7*ES+A(4dj?Z{#(CB^!dlL7#>=UHD5w@T&X-e-SU-uNe5_A=N#7-g&KplPQnh`H zPpS5~)B`tB4GvL#c2V7C$7}`uQ3W`%9{-rZU1zsNPt=!gocjbl$bG*$0T$*uM_#og zqCg|Oj)j|ToOkqD9r=oShaixip)P)7lk0PiqG(xWk-CUfrb~sQT(xGwfh@bnZt)8{ zOS#N-`c;zh{GQn97)jX{(Yl`-r8``9*&;k6ch&`=V2(|Hv9Q?Y!17y%ns+Deleo>c zXxB?L3fq2>yTn;9_Ox^x=H#ziXLYde*Z6o(?-#8oR9PIR^3 z@paatZ5n(3g?_+M-|dv&)6MY$9@Z~3eSe{3dw@NFY^CLT4(AuNdxNbG`TGi;zmHH_ zSiDu1l`l%qOH$kZ{2ccSrA6|bw77#IFRiuQojuoJeX89Ogm8^qV@=JLQbqwetb-Qw+Rg0ZKx z&@oVwYk{zTlDlPmt@`@>gqOWSUYhOj^{u(ngOha>|?_PGQs<+b3)a1g<-&k^{S8%1h$J}*`wztCBw7}OfP?3F+ zx}UAgE>4j0^Z00huWN#^P-~*o+2@|D%bKdmk)p&^Z>3FWpl*hPmxpaxO{r>*^{r&3g^y}{QvcA^&`utmTsG+aT<>~RIv(NVT z`dxOZnyJau+UV8W=vs5AIaif$g|m8&xL|mzI9Ha?*XCe(t6FoXft9>`kh$96>;C@# z=j!sh#oF=n_nfQBkD$XkS(mT8)w90W`T6@dRg{;f$iKG0|8^T^QRhnm2|&EM7A>5!qsI$4%mbf-I5mPlls z$SB4TVtT7ycB#|a=rU51xWw9-sL89i z(n)2W=j-y--09cc>DS%rdyu)Cs><{B`Ju4Qjh@49g|hDO^}EK~dXKnYc&bxvqfcs~ zPimqoOpejm=F-~dch+4I98VJ@Ad5N^-O1=?C$GIVlOYcaH&31>#Gvky1T6k8 ziI9>}$}k#&1aaV#k&~HkVjA>Ni}1-ALH^X7g3wDVMhx42p^-3zFGdr>{P}}0nBh() zQ+&Yj_sGEtC{9v4!QeCs7}ZYbLV2PZ!;@{5(yUo^Ep-=xT4<+!GBhd%)x@V1(7dY$ zli|e;x|GU}&KlATNQC)RW{;a~Qea~L*oW%bl|LfzM8I*SAGLi>0OXdiYZ)0oD=NA0;?e(m_y^K!7e(_>)B@sen*VB(K|;J z;Q||($kIn4;0ypl4t>-S3JpnY<3$=NxKl(oq>Qq{7C!8dNgbK+K*j(AkU>Kvb=0s! zARZvXTnJ~JVgmss;IYv`U6h~*6>vQuM?3-$A`TU3R6z&;&1B&ZBQIn@$Qv#gGKMd9 zNHPWw)!<@i^F zkW_BOA%Q=nA4{0yNA2%5#gj6X79!bLy&P%(i8R0KhQ33|Ze zj|Ok}Gsr!L_`}03U?j9t6w0L1E*sk@0ZAVnNaLnII^>|)C;T=d!X^G#Ax8_0R3V8E zl#H>)GOFyNnF1*IGr|%kTu}@aihL;wA`4(5gFkFQBJu~CRj>duDkBsH2JHIN$u@|5 zfkhT}5Tcn5^JsPl5SsM=&8eIT7^H}xB?~M@+bUFBMfc8pJwdwjAuwgf&OR%CSahM z26#XX{?G+}HV}YBB!VF4+r9Wh!XGIp1P)5^3=c%-A5dWe2?B5h9F#y4 zD_{U7W^fAzFdzebFo6j2V1^USVFmb*0z1fH251Z+3}D$dVm%x8e+Ke`=wY_TR8Nr~50fe7M}lw@Sx z)|sz}w0Z6jHaQ_AqDOitt<&SSea)PHxo<4{I(gaj_r1;9XTGktbGBaB{saBT0srj_ z{;~R31Lym74}$(bu>NSIe?gpv&bzYY@H?IKd literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/openid/google.gif b/lms/askbot/skins/common/media/images/openid/google.gif new file mode 100755 index 0000000000000000000000000000000000000000..1b6cd07bd8b9f27175e0a03be3b5849e0f5479c7 GIT binary patch literal 1596 zcmc(e`#aNn0DwQcU}GKMxn#>^B<6OWQKZt-cb3xBiE^HYQf=ZKSu)*APi@FdDnu7l zbE$~JN>R#|+lE{hiz(BRl~d+uOl9Xe=bt$55AUzjP!`6aB*XlLd;L`$MA0-=oh;_@%=DSKzTW5!kkg02oxN<-;QgzZXZ6@(M)5 zb-u1@<^vWqN`U%R3CtOJuojJt|Ec%yo>_yZ%MV%ajx`i>ysy4d0^mKeNIQ#t@2y*KMj}jE8i*m zl#eQZ7!1Pfn72wr)$QwY6gabm(O6$Lylw3Pd(s%KbU5crNh3{eeZxI4(dJKB1MUAN>n;QZCCkQs_tGtJY-^iC3ja3x9v_uTP?vFVW;}s># zx;OS=;!{FWer7!3y~sj6t|9~xWcTkC-QJ+z*` z{$kU`Oy-`7hp(oZDFCEubef~t-M)K#*_S^K7T>g7T^A0(oK7oIN_s0RehD8}s1*i- z0sK!m=+Xa`J^uB-PXLSoRAEU$)j~-M#?mcPT3yt{hn6u4gKCOf&J$L|DbuOJmz+s9 z2lJ2J?sjAok}A_-F35;rl&1P$i@IJDD5Bmxks6yO#s#L4K20(2&;kgjBXbWq$Y}&> zCc!g3Y<~;U$CQlDX2nU|3Dx218gg&b%2GehXilrp4#P1y;weqVUjQptQlB1c;VO7e z@*D^wqyiQ&MP@@Eiijj!C6&AUPa6q7&lm^MToqj2ny^P9>WX^Ttd-e`;&k}6HltTz=X|A>@zzv6@ z)2r$ZZK19Hq1s>pg(Wg*yF5bqcpgSu2*Z=3Y48rhsMseSGm6D#RWnKSz*rI2&e%)U zhJ)uMj-*j*Cq6a|SR^kuis=outMPl7vPf8@-OF`fK(;t_tEV6%zOcxe#-=20Mq7|L zi=0@wEQshFjSRBhL&)v46Z=EWNGr^Y7(cjs9|q}uirjP>UklMq_Cn}BpIZ=OxlIZN zrqgmZ9Daq-5mP(tkggoG%l1Mp5`!YX9P+{9a@vc{UA@ux9w!5<$J7Qsxz)kjp9U>O zdMGwqZ?ce!P4?kRO}wMe2<(jAw{y`J9zkIp$bd;9jpjy5e7h8h$r8hU6K*}g{&bHR zc4&^sGRyCwh1g@leXv&EK^8KfI43fLjtGZ4%_yBJq-Q6Ii74-1r$U?ItVA?Z^o7I@ z6Gx_VGVMkkya7VbaJCw^O!RGWg$&GOOC(&Bu>?}_7>IP{%ivHGBI}%WA6YI2+3M9H z%iJLj*4d~I--NgTJ(9t@qodNU?AaV`YHlKR%UVir5C+OujBse~Mp}pFlaUEl5;MVK zxnv+FK^_1to@fbJn^<{1g0s+?k E17=sO82|tP literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/openid/livejournal.ico b/lms/askbot/skins/common/media/images/openid/livejournal.ico new file mode 100755 index 0000000000000000000000000000000000000000..f3d21ec5e8f629b77c77615982cef929802fbde4 GIT binary patch literal 5222 zcmdT{3s6+o89vBPQIq*$1QbbvAP7Q0Tpmk!l@)pHf>>S> z1vbL6JXf&Eq~nt$W+Y}?NhYbg%X2}TX`y3UlPS(H)68fPIsMMP%d)ZvOQtiO{(Js= z?m7Sef9JdZJ&(JD*bxy~wQ3b%;$cU~E2vFPBC#hV6@5-lg8iE%gba#Un|TxR-jjq} zb3h#KnTHdU;W5$jSK%T=Pj@H?K_Lo-P~nPOqSb0qGXv!dp_JW0@nc==!i$LGD~{u9 zt~8U?B2Fd~qvpC~M^KA`gqqWJF*i|=E@%ujnuad%Of!pb5`P(QC7kR#SP2OTzQ<#W zlv%8aIKD~feX!6nCX`OuQ922|4;{CsbQG#}pj5FEW=E(mRL;B7LWEq0KANYW^3Y=B z_y|vmH;?ldeDaRw*8J(~hC!qOw?4H%Oa?Y1JnqbNZuG{_4-W8zh5g(k>k33gQDw%FD{ne3XB@ z!rC7_g|Gdhw<&R;HMK*r`+|J;KQGI_{V?zIn&?P3mLE&uo!9m>GUrS3pvy(U5A^kP z_$(c0u8#D!b_R{U=43cu$bYTlSjYsNjhYF)s`QQZ*3Ky}xtxmjy4jX?u^{kLVeo`r zO$S;h4E5!2dRRN72QywCwpkgq;m=hECwm&HvGsd>T}g=BENAdX&yl^G;15ZD@oDXe za5~)ny+KtH;%e1SDcr|-+q7@ZKZGT0tS19Bm$;2T93LRj8^{4y675bB zwk%H%+bJ)|KhfTK`uexou|aVjGo#E;Z%!+~BizWLXGzMgwI!$9Yp#x{^ivH+c7UV2 ztG$yM@p8QPk{Hi9qZjA|_iy0IrBz^V2FDGCt7!c=X2YVB&%j+s%GRZq6}$VIQ&2P#@0_gE zNAUFwKQH_3h_#F3ZUXmN_F>UpAnQ~Ky-mq~NZp*1ER$dEt(TrWr;qctLx_#Sm^+j7oj?A#I7DC$ zaD!T6s6Sj3{L6HyM1HcisHVO0oT2v1d(D5PMI|R99BoZLz$4vBrg+7t)3*@)h!oupL+wwg_YH%9vj*4aIjHXba4{8xkuc);A<= z{dZS2HK=KK!_1Gt{0T^;r%|h@D@A zwEWI*^|gxqvzS+eyR}HSKjd>V18&q9+tQP_E(>?D2|U7;$hbAy$_UJGbI$ekhw(yN zQ(t@t+Lp`(@GUljJClQ+q->vynK|C(jk-g{A&xDnJEp9_2N%N}naK~`m>>4s;pgp< z9QJ=jEdleAH`nptjkB2FdOtgL+Y^|;w&WZ>d7<84e)0JS>Jtat2~yqPk^=D3P?xq0 z@f7|6Jern%&D$w>p*GAGyl#LeY+CZk`Lq2)w{GZkg^@2rdyw`F-&0YZ?HMmk^)y+7 zCvI74%WRnjCpgYpE3*5H!##ZipIx~q^<9GpuZi7A2hXs1Zk~!pO}{2J>4l}(OBYOw zFj0B(Bt6hCeb>`pT-SE@^z{x5U2Ln3-?S|8w?F!!Jf|8`_0G@Mi{w$ z=X|SDy7MOoto~1%!)>|A@LPpZ)J;^~NDC9o;+|ify5q^Z+~}r~bWFtv8#b;5N6F%T zh9Q2R{a&r|U)2@;IgyF6UTmI3tzX1!nST@^QP$V_qZVtm#9wTzwZCbi{LlWU{0Anp BEK2|Y literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/openid/myopenid.ico b/lms/askbot/skins/common/media/images/openid/myopenid.ico new file mode 100755 index 0000000000000000000000000000000000000000..ceb06e6a3f0d88fb97cf10475a3062fb0edab33e GIT binary patch literal 2862 zcmeHJJxc>Y5S@6ZrZbS)owhjA&;jnXfl*X745tmMB3MrBW$)n`RP3b417qd6J6bL?UM{+)q(3xlv0Ik%T7j-l59$8$m>#ard~jq(8yIciUu?j(!>?&WKIe?G5c$>` zV+r!H1WS-ha)=*H^!uZEl>O1BP8%xc=fconk~~4DvKCYWn4`b_pUnoZ788}%mc>uh z9HV;s)r}P~NWtqf_p5&HGjTJoZI|S8n)uf0l05g}rdhqaIBpnv^myAWbI*7E=&N3x z95xI+0zR;x-|08c&;6Ul#XdjZARV+no-wSN`};1(+>)AIisPc*P@H*_1Kd%ys#()H z>NUmL)tL6ccj9UxPF`{LG^Rc9JypwV>?^N0yvK~LBc9f{#^OA9dQUv#8Tz7oxfa(K u#=%<%_2}PpAb{bmUKcqz}))c5uC(7v?)v4a2P)ZNa- z@$&T2)z|&~{r~^}A^8LV00000EC2ui01yBW000GQ;3tk`X`bk)Wk@<6#nZYULKH{p zEx|?+kif!I0vIL|#ZMubBmjWH2OtmxIFVa~6JQ7!1CK!f5W#StOTv&C3=E8h2vI1s n+#cd5;2fT3B_0kF0v!+!GARoV78n&7dMN`JIW(4+BOw4gP{MS* literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/openid/openid.gif b/lms/askbot/skins/common/media/images/openid/openid.gif new file mode 100755 index 0000000000000000000000000000000000000000..c718b0e6f37012db6c9c10d9d21c4dea0d0c01bc GIT binary patch literal 740 zcmZ?wbhEHb^k$G`xXQrrKS$vIw-5i{JotZf!T-qt{~ulWe{RG7ST3!;kh({9A)7tlU&$%5*g$f^Ar># z6BXsynVh) zJ?osJf(s)D10z?npvQi179P(djPk7Va&pI%NfVlv<{V@&c*Q1S?C^%corz71;ll+1P9`Sd{~>qE3OQZcJ(5}z4)|O#`p@vJ zr9;Fsp~HjOs*vv^`}=;ISs7Cf1bMkUpV%Pi{6*$qgF{F5!;-XxTBi=%eG5gPtV^!wdin~r1kkVO&vN=8Ce+Xv z$*|MCqlY(ACp$%vPrNeV;U*4lo)c<`?yngdvl5)QWEV6rHbi?!)@MOEE;CkPT#L!!@77y7u K{UjJ!7_0#i2pVPp literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/openid/technorati.ico b/lms/askbot/skins/common/media/images/openid/technorati.ico new file mode 100755 index 0000000000000000000000000000000000000000..fa1083c116527de7cdbf5897976aae8807fce878 GIT binary patch literal 2294 zcmeH}ze^lZ5XZl}d+sirdNzWcsg4x+2g0RLIAWPjuoD!tN}VeB7eq*vB1MXHX)Xi{ zAytZyMi86WScxK7SO|(gE|<@|cRuzut2sy&xMBC*%tj+A zE!c_l2H#`zaX;dYlru_mk^31KdcB@L9&W3#wIFp`YJOeP`OSr1{CK6O-`2Es@?E#T zy1MFK>-Ep~I=Vd7%e_s#J@}-Zvwh`X=4ClX_h=7BXW;)k1Ifb@+v7M5AZI7aYkiNm z$KjX;Qm=X2(~a>=Zn(6-IG8mv>sbkaBEroVrCFBdHr=3XM61uF&u!`5GI1u;Qf<+zMxU!sr1q{f@m!ic#&7x4 zjcDZqv{&NU85cGO2UiL7I^;$4kVpqJ@-E;pjlK(>l3v2Y;e5IS@q=)?(5+CX;*7LwCwnlxopJBahPURBFV+~&J eFF>coTp;}_7zqbJAQuX63TM^f{)_Klzq8-HK65Pq literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/openid/twitter.png b/lms/askbot/skins/common/media/images/openid/twitter.png new file mode 100755 index 0000000000000000000000000000000000000000..9a6552d18444ac98fdc776e6c58b794dd4452772 GIT binary patch literal 3130 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 UY}!*r@&Et;07*qoM6N<$f<-Fwq5uE@ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/openid/verisign.ico b/lms/askbot/skins/common/media/images/openid/verisign.ico new file mode 100755 index 0000000000000000000000000000000000000000..3953af931987b0e66c122b338dc352502564eafd GIT binary patch literal 4710 zcmeHJc~n&A75@Q)fTGMB7?4e2SOip*O~H*}8$=cdnTIhNBRWh_BW_>?5imwU5pfq4 zQ4m>QjBTZ+CRIeErNk zREwwe2CX!zp{LmSu(HSiXS0Z~ELfW_hmFlL(pSOSItLB|b75<{0z+Ju!Pzbsp04@u z^IM5gez^!5o)68~JR~M1V)|=3XwNQ8?a>Ya%3;dK~By}6c*$o zzc7z#uYNYpl*u)SjcUZ3@pYK4t;Vd>gP50g5b0^P$e34y71<3~mHQrY z^v!hsAEG!C)5bMuezljdfeI&}&sTTWohhE|ksJA(uJTd-&Q zMO2ly;NXEa9IkGqoEFsAT)_K>+aNTx;izy9$B(q(h|rGX$9{>EKmIk&oIQ<;7cbz` z_Vf7oOgqlCbl~T$U*gkC?YPwb87^PGj4!TTMMqaVuHU?hTbV;iCum zZRbOL{qcZs;#f*}wrlz{`0xBe*(I;Sz zyL{rRJv~s#3{DZZtH%`9aNO9+=O$@yE9B}Ppw5xAUn^OeZyC|qoIL(nKmn&nYY>eL zKrf+{E*!VX7~r9ZuM^b@Gr+V$Li3f}Ym!#NDK|=*KfCmKlHM?d^N_SO`K0ZlR@aaD z`$@HgRh&}Pnx=3wq}{WbyQe^@gROXtjgE@>Qu-jEKP!rl1Z1YS(s35^4X=At_Fe%L7)Z2wJ zI=BcM$Zt(2XjtYnSb8HX6)sr~HNO7Fi=k7(q*OSR;1tv-=GQtZrfwBAkrlHYyo$=k zI88HxY21*dylU_ihyn*o`a+siMM#3?z2La9_bI5tty|5m@_$Kv=M-r`DWnsuDXI|SI(OnaFs5Rj zX)H<$bK-%v#3C6u8ZzR^Dh7HHqo*OR6YUU|4ygcb%!+EsW(iDpCu}sLU`Ao=$b{)2 zqYUx~O12syWYaUKC^j&d+|rIZmK*4)3nD=!V^Y|D)97J|C?PnK9W{j|5sikK_<-~j zB9Ma7re4g!anVfBnN3BRq6!uR)Iqc*BNYKUJ~~i(NLEqh;Ss1(xyk6vj3QL-oI(}E ztJRXtBFNMQ1%(&^QRrgWCG@7zAyK_e%IJBC60Uv0=A*yw|Kz_F$NuLuvWqkMvw2)i zo$zJE=5ZUFK~Q{S_xsb>KZ5B3WHsh5CkCG#~pZKK$?_l$V}GW#u_kRkqUX z`UyTd@da8=wc+%sYiMmfjdN#zfeRNd;rz$faN&FhZrr$v&dv^;YrTU@mu}(8l`dTS zyp!h7d$`$gpJq>*C%Yct?%liirkiHU``!4q`ysx0_@`GhR*Wg zL+$LnVne>U0!F8BBDJgQ#SO&zJnpsR{N}Sv)RGX5ladO5OX7h<2Rs-#nHmcwuE~3rz z2>~NUOyad;2ZtE}qZs8M)E9^dp|1)1sQ>G$Z=aRsiI#u(rfHU-9}N`$^h{weM0ms) zh?4E1IgP=fR~iiIb_PS5COyWGoN9OA+h%v^2+Yx zGJaHiGz3ONU|59!7au1(GXnz`A0HnV&|@H)n}q>L0s$9;ARnmIVdDT(f_z;3d|=N* zDLy_ze4rMf1_3@kMlc035Cr)7nOM2`_ymE1g8cmaK$@2aOo2_OfcXEPfdPgOHZw3Z X0PU5rU|_JTL0}LcBo2~?sfE!1%eOAT literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/openid/wordpress.ico b/lms/askbot/skins/common/media/images/openid/wordpress.ico new file mode 100755 index 0000000000000000000000000000000000000000..31b7d2c2b77c039342854190a90a8d8436992b47 GIT binary patch literal 1150 zcmaKqJxT*n6op^J!coBi!TOa}allq&O3PWo1c^ULbO>qe21^@jt1Q7KOpyh+ge)Q1 zK|J4huRH_#zwbc3UEA;R9AO!H&d%roag-+O5 zO!N95T@?S*G}Sgw{mN!=VmhZ5Rzf4>LLtlrZUAWLgGPY+oVrG5xo^yW`xZU-Q@wk~@ zYxS@D)yH#t7kvCm!TtK*mFh-7(+TYB+*i5pa!nc|Jf3SU=XN{`fjrfux$lA7e}n0{ zlfCqFi>Z(M`wVVG>yKK1*9rZ1`iw}=W^iIUOLNU?<8e1$!1FrglCO_u)5YTUvK?eu yI~YIj#-~XLO;hd+o2#A2S^08*UB1qS`QCUE52vFxJ9}scm-~xFcCkL1j=lhFMR`a7 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/openid/yahoo.gif b/lms/askbot/skins/common/media/images/openid/yahoo.gif new file mode 100755 index 0000000000000000000000000000000000000000..0f0eb8efe7eb64bfb49b71bcfed741c1193fae55 GIT binary patch literal 1510 zcmW+!eN5I>6h2Lvn6<^!)Xd5hDlfLZ?*XcrP9QW)@-4ktMwaM%)M8CbQ_0*l24$96 zFCxc~J~6$k=GUBkF2{?49T|aFsb(Ko&Z+fB)4rCSet7>lyXT(mKF@icXKLw`+wYh+ z8-~IXfYE>VhrW~BSNh&w=(_*{5C8)RfPw)q2!_Bg7y+YT0W5+gunbnfDmVa#;0PRp z6L1P1z$179&)@~TLI4N|fgmshfuPafpa@E!3@V^e6Ja9)AuxhOGcER$-0QkIY5^5?}#|%n$=&Pz;G-F(O9Af>;zw zVp*(+RdFB=#gRA`C*o8*h)3}xp2drJl>ib@0!d&AB0)t#6h%ptMMYHmf)Ke8NfPmj zhEWs@s6jQPhSi7~RSRlSEvaR-qE^*`I#fsMSe>X-^`IWrlX_M!>Qw`1Knp*qp;Bn<`OCEbSmdY9b-PCpKTi3^Piz?f8 zy?dyt^GwNvp40E_er`*8*@3T<(}!)EUr^Op+Wlj^>bA2Zu5HOanSSmqx^=>0X=Kzc=shR~H^k^D_Zi zm0opINA;Grd+-0WzzmB`9htoIiPY)gdyAhf=<2`k$K9pb0|sBWeR^%q^M`Ymzy0{v zAGS1YzH-Xo)hi!8cI)gxUElrt`{^%}>1j`mD86O+Bh%J=eAmCXEE%zRZiAWIIC00W zAKEkSsqYxqQb6nD8wb6-Eq;Dd#o_vaK4;Y0sB zlrb|aKY!qiCqsAY>3-`Dw8pcK>`aU~%LVGIDmHY4O%6#(r zGy7)b{WdzUcIZF3+Y{BVyg1;&!k_|&&Wb)U7@G{hd*@p61{K8lY zrQfzUZ=akxn9jU!Wo>@vjsXJ~W_|iavS?$;@p-quF|A^H-qD(SYOemd(}6TtKSPDj(q#+`%nLkS978;g&z*8QU&dA7SiPQ{aIOUr9b<4dOt34TkisQA&nbLY;9Ccc(3tE@kX znE6UyUl+T&^ThS)X`LSZvHSkK+^(b)C2gpd8>Yw9YOz%K{?MI3b8>FYng zg7JWavpq*sgNo;g6r<+f&-XN&Sv-%4pIH6yyXyw`2g(l?RLOZPm@;X(+iPCEdC$MT zd94@`6*bGYIIYa@^}jFd8?H?{n|Afkp+oJzpI3Fg`MvM??|IKF<-EQw52?i2W)YJWX>apJ^D?$6>Lmqjk`V}C23|M_GcQ|q-^FJHc#ZujfQtp_>8oo%osN*4%%ep{Ml2kwIxe$mgJ+jD9T! zJbP!(oLSND@-~N6QTWG^k0OZ@LT-ZRqF#Nq@?3oI*<#;5d3#5Rwv^3^$1=*cmEX7# zq3$WQ#LQK5#w;tlt?dh91G06mW=+jn+T>?)Okz!`MY^Gy>+6>NVgFs!IeS$UGfgg7 zEeyN;>`eFOIn~c&=al;N$j^Qu;N#;XVmy zzozET@ulpPJd+a?#`RV3j#D! zq7O@~VBF34pn+`yQ>Fk*@OdE_h3Tia?tWSI`t7`&>zW5XefqR`?b@}0RaMV|7n+%y zOFut9fBDps%atN?=d=E2KKDwZt`|B)KRxu_RSD1xPR$85mgV8XD>v zS%esxTNzqd8CmEWm|Gbb7*5=z1JeLfX$8>*)L>|!Yha{nWDsIxY-MC-Wn>J|@H!7D^o*=hRq6(LFI@k$Von#$*Fn8sSFt>$tTND5Q literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/pw-login.gif b/lms/askbot/skins/common/media/images/pw-login.gif new file mode 100644 index 0000000000000000000000000000000000000000..f88b1bcf0bdf05b8dd91b0927333fed55b6ae6df GIT binary patch literal 1818 zcmW+#eN>ZG7{6nHsJTrP6%if5F=J}s)iKnudGH`AFc3lrln?O=g2yHze&9g^mFy@B zwYZf9;qjx?R?{8I10k45z6dmOd^;OTsN^(00)pG~?*8|j=Xri#&-<=hAFK#SjztU6 zFhY!f9AlI*!Wd=@@fF80jxvrg4l@q%P0BG!86}KjMiD!#~P#gyS&BA)Xd0N;yh6iaCmS70@J@peH4G%=wVQ;fhph=N&)2t}A8um^d7OxT7vz(0rphoBn3Ks2a?QP2Uz zfDO=qh4|$7Hq`JiA&3tW`~fXg7!!;jSFj5zph*xW2y%p^AppI^VkOew`7_b2v@h9S^p$#=W1Q&xK!5`2z|+BIvso;brluynUa!;Xl9G}lA|ljkwMwOuN~IEs1U`MQ z5k~>8Xwv+^+bO{<>4N^ zcH7Oy#GiI|zZNvfMIFZ6-s+>Pb~vexyISvM-@52`!G9NWIB>t-v&OoOkU@`V;<|WO5O6hdrrp3 zM_B^15^wddy7w}(rB-w^-SB4P`EC1NI|%l-i@Qu2Yo1i@uRG9PSyLGF@%*q+>Bi5F zPwu`W7k#_-Q19LBti!R10hwJ7MxLAZbi^GRy-xp`B8pe!$>O)Bu54Wxt{ofNe&1MR zT_Fp)|JbAA=j@zzN56cwbjka#Hq;N=-i`?^n*Y?+Z_cRQEJoiMto6?~SG?0THjbPZ zS&le5Ts|9hMcdmFRkq!y7Xn|vRjCo*2$l>~AYnZlsdGD9n{Ows{ z*~9F&sCwA4yd%e@IVds6oGU!78ea5Xr+hde>a;?$zD=Wel;oTu*B|vhts1x*Eb<>s zEiVeV+9y=2{!D1OB=_@NSETIhxlrWR;kH(z63;4`6FnGusqf;XUC;W|BXR9I`AF)S zqLh5a#b+zqbQKomKOeA#8LQiE`5O7~N50L<3CC~ds79~Wd86^t1oZ9e~pFZiglv!LAH{BE(v)BCcmysWr%~D>?^fvus*>8H0-kRDuH|3~)lMfpB z%M>r)eU~;WqqYRO-k$XJjJ}8zVM&Sa&@ZYLlu&c)>k!hlBw)`kMFbLGXx zc1ur~v`cGhkiXTi_o1@4I!@HL?2i@hRox|4pEYH^(Hd#9=M`hhV4HQ{-|?Et`NdVT zi(jRPo*6V(ivKLn-Spz1`}x4zv!lmuKHZ+LP<7@sYJ#2mO+~|9FO?4mAAWwRL}|Wb zdNB4%@!!{KvwEI*^}lslVan37#A|NNeJ4`d%Y1Y6!T@u`2?w`A_q@#D7~5sH^+RQf zOt-Zj#ji?MKaG5IbozbOndu>c*Zstz*FrR5D2?u^t@&Z*Mrqx)GP&>Nn!*BZ!KZEO LF3fY4pjH0^IY)F; literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/quest-bg.gif b/lms/askbot/skins/common/media/images/quest-bg.gif new file mode 100755 index 0000000000000000000000000000000000000000..b754023882679555b5d31862f777e5f8b1877bcf GIT binary patch literal 294 zcmb79KQF^k6n#yEq+=^$FjT|RNGC(FR2r3bG9We##9%NQ3>uLbk@yBARv*JGzJSqW zdH4Uj&-?iX&Pnb)_vW6Q-0jh@w{~0$JK-|~h%skR?y5pa2$0O&vznEh|N+RO!h?sK@|HmRv z{h3y(TKyEJ2mNOMtn<{HAAUcKPwGpL&s#5zxz_#Zs$8wV{*2!`hmD)b^4H{i;qK%8 aqJF!3xj$$f?HydV+s)ZpZKm7Z3>&}3(53|d literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/scopearrow.png b/lms/askbot/skins/common/media/images/scopearrow.png new file mode 100644 index 0000000000000000000000000000000000000000..73dc674452aa75bc0968f1e3118235401955aa92 GIT binary patch literal 538 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 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/sprites.png b/lms/askbot/skins/common/media/images/sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..e7244673e65066fa2773aeed011c3b2bfb9b6fad GIT binary patch literal 12545 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 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/sprites_source/sprites.svg b/lms/askbot/skins/common/media/images/sprites_source/sprites.svg new file mode 100644 index 0000000000..34898e3037 --- /dev/null +++ b/lms/askbot/skins/common/media/images/sprites_source/sprites.svg @@ -0,0 +1,732 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + X + + + X + + + X + + + + + + + + + + + + + + + + + + + + + + + + + ASK A QUESTION + ASK A QUESTION + + + + + + + + + + + + + diff --git a/lms/askbot/skins/common/media/images/summary-background.png b/lms/askbot/skins/common/media/images/summary-background.png new file mode 100644 index 0000000000000000000000000000000000000000..58c3855abb22e956124b2fc203be49aa3b88d91d GIT binary patch literal 291 zcmeAS@N?(olHy`uVBq!ia0vp^vOpZg!3HE<&c)saQY^(zo*^7SP{WbZ0pxQQctjQh z)n5l;MkkHg6+l7B64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1SRc)B=- zWHi3L>dn`nAi#3*Si%3_n+{FYdul#m#yZP}{1Zn)w3dd*&UqnHk)OtsgP^z{Tpgjtk-RyYfFx9Iji@kIQYWaV&<1F VVkcJcD}p@F;OXk;vd$@?2>=#9ZoU8j literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/tag-left.png b/lms/askbot/skins/common/media/images/tag-left.png new file mode 100644 index 0000000000000000000000000000000000000000..5a9d8a0d384fbda62d62f47e6a74aec6738e17fd GIT binary patch literal 290 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRt!3HF+tk*dLq!^2X+?^QKos)S9vL>4nJ za0`PlBg3pY5H=O_S;MX3|5l&%}hY1ZSiz*46!)9c8Vcai-Caa{T5c;q9zlk zbrXKeD~1ca6?J6Ycq)v|bW_Himqrim)Bf>K6mUuiKWy+}NeaKjaY4a_I;U?le07sy z+?fQVcs>Mr1&X*98J;&TjXl13tJvC7u83)>j5$fqW8PH0*H?U%wPv%#0n73kFVr6t za@Dg(WPiLTUp1Tkq<79NHHqX+!4ooPF%?KYT`Rif+Lzk%yVm`b3kjXd+o|@<>B%4Y gExF#;OSdz|=j@LC__$zopr0ADm{LjV8( literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/tag-right.png b/lms/askbot/skins/common/media/images/tag-right.png new file mode 100644 index 0000000000000000000000000000000000000000..871664c301341cc62d22dd6008ede26e68675c1a GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^XMk9cg9%8=J-8AAq!^2X+?^QKos)S9vL>4nJ za0`PlBg3pY5H=O_S;MX3>JE!dq1}Wg?v3-978hhy}e{8c))>!;h@TA)qkyt zK$TzCH)-t_%6s?r9s9L2XF53)Zv=FVdQ&MBb@02|9XApigX literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/vote-accepted-on.png b/lms/askbot/skins/common/media/images/vote-accepted-on.png new file mode 100755 index 0000000000000000000000000000000000000000..2026f3bcc50e2738bdb6c21f32ffb2a82d088e11 GIT binary patch literal 1124 zcmV-q1e^PbP)(98f962%urEyvrobTSI2iWDonJaRXmcCZ2&_7W|s$8;q zX78?p{;d3j=Jt6u zRf1NRn45~bKOeQloe<4k4F*a0^w988Pn>Cnf)+6fhvK8hS8!=&g-Tr5fO=!taU4$~ zOcn}~1dq~+P+Mn_{a3^epPXDNqV_QrDaOtjsGVmiri&cVy-8sMq#8b+xcl4la{1KX zd)90%K-Mw{%5_9>cd}5B8H|=?=ws;;*H`e>Y)`)g+x+z483yu`hh? z)vUt~N$7kH$t{g&Q6XMdNY^x24}P89!Jn~piFLVw3}%z-ZxONXwNtVCms$I=Hpt<1 z;>5`C2ui916R`wY2}KWfU9>D9Dg;Y<_4cY;b#?q^Z}exPXQRCee0Tq%=HACy;vCVa z)oQ5K>nMeJcUM{?j$?G%ZU0GpPJBRg?!=6hb@~7WeF-jmZ%z`A5yxGcdRvZYk@$u9 qnpoR$QRL8vDfll$;(wNZ1Q-D2C|}A%ZKM4F00002jDb`!&=tG_0s>$|K>*|`prJ6sL73(3+qcd@u04=FA87F=kXo2eKmZ_sFf2$; zPWA&zCIel<#Ldmk0CYVA$YsbuiEN3WpdiEV-@jQtefpFN6jJ#4^XDXHW@fmf00M{! z+3>`~M022FsXRP9Oq`sYV1r>6fM^&7@nIMgTp$C5g@r+ma0TLs@87?}%>)P_xCIFb z3Bo|lB^(?aU=1+CkRuHnMz)lXj}Igk4Pmg5nWK zgM9!HKul2A3d156+4UepK$wMv1;uh~hS${8fRioA43Jqsb)q0~fB<5G=1>+`+J?D) z%9JT^&HerTa5l0y4Aj-tf${>ww{PE&GbGq-fB*uAJJ7~2AO*0T3i2vCz-BNA)YjHA z05ccZaO6}4^z~N|8z6v~pcXs;rCV4KfV@~$RYfo!V1|GF`V}PzL1I7)9)r{X1Q0AA zT!9r*AO#=|po~*pU5(RlP?-Pt@dK;cg2@4U%(nbkq83~4GrM%MlNq) zt_Q^*$WcIgHIVxmBnA*bjL2RCmZUYn2r>s2cJMME1VFhHRs;jfeUu1=7iz%L6XYu( z0EK-K5dWAvcP;}!05PGJEj>WnAAr0L$}BL~0gGY=V7brm?%g}EuaFBtSO$Xz??)hR z2I3zuIe-8{v0&c3c_1f%lFyZoA3rjH3O+6_F0kui`2-depv(ty1ju)w_yQXE94J)^ z#Ltn<1PCBTY{dew=mzBvKWLT74YCM?K^B1kDEN?45GX8n0Pzwa{)%kQf&~j000M{+ zU%3+(7sm^vtby1Jh*f}C5Gn;S=spmi2jcxe{2E;^$d?NjE@S`*AVy+~WpV*P05LJp c7ytwq03Nl7Z}J%VEdT%j07*qoM6N<$f`!tbz5oCK literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/vote-arrow-down-on.png b/lms/askbot/skins/common/media/images/vote-arrow-down-on.png new file mode 100755 index 0000000000000000000000000000000000000000..048dbb44dcf2aa3669386737e4e34503659cfa70 GIT binary patch literal 905 zcmV;419tq0P)-y5{XDqL?wtw8c7i~ zN0Q6^?9Pnu+qv8Oi^-+Pz_+tI^UZtT@2q2t0Un+PKF$My_TW0l0Sr+3C>}Z+0c92j zQ&9RPZchib4JRZRISTw*?j`s&D(yi!4W!bYK|2l~#3&Yl)q=eKB*_hofA~`;xN^ut zQH^pphu&R)Hdf>y>Qg&-^&xZ21q}p#FHqmiA?Xcc?0sNEuv}tToE&x%yyA;O9n_c& ztgi)$?=eay3oI<;;0F@w=OKY#QG@aB?kYGJj-3UDF8m+(%RAuS)fP`Uv|~x*NmL4j z!2}6hSuY1_FXH@Eno4SYa2fqafZMOBtL=P?a&g3-4|twf6~U}kNf0P6+^mhu1MQ&W zQ|)VwXZi;$cy;)KStHP5wXSdC?sR7rWK_$yj`F9K-3tj+pzGE&CT^qA=z+D=Rv$@J zbvtq)#Yq4@9@OYr{|tt^${b4@Tb$voWYw^jsXJl~1l){yZ6Z~mTapgVXlRrfvI@o5R%kNt_@+gec_sFS>sC)ftV7EML$Cz1 z{=+$y6i-HJNDispz>1H>a&r@96Rxcq37>Q;^OE=LI@u)mx5b(Kd2vsyAI4-kummT= zg6^eUr{s{$(8UZkHS&32F)wd(AU!Q7(Rdg%m8GwFfuZFFfcG!1F;4WvoH_`{s8d}AU+B-V@FC#${&~nKmaj< zELga3A;YgoU<-h@djJ(F zfE2(qfH25qFg7-fEDzJi!NCC%R{)s_G8-U(SfCczf+Ro+KtNAVkHN^u2xqW7e*BnW z&z?PC3($iL1VFy|@#BXrkhujQfWSWZ{rk5XND$^Ce1?O7tgI|UVqzj#J%|QjXJ=fy@M{1qdLp4}jidfk}eE+_`fx3{6f>#t=jHft;KiJOrSj0yY~UfM6D&CG|15)01!ZM3y{4xbLLD8!_w2!F~pGdzzl|Am>fU= z!Gj1S080fK85v~z06FP`06+k-pxX?ygxFk+ZUJ&W0Reyjg8Kl3VH&1PnSx) z)Ie%~{`?8YATeO(f|~^k4S)aw`v6o-fJ#FqCMIw$Jbn5!!8)eg7&Qmi00000xCZ&X`T>s1rrok967{t#$kYGS7Vd|fw?Kji~ z;lV5VHnRYeM`{4BF0c$^a34H4Y$j8XY%{~vP4Ur0f;38Hd%(7f1L*It^x#ox6Y^@F zBDnIA8Ir>UzFOH#7SIa-feR)47XkUfbW{ysEx$_Za+(2mrv@e0$jB+NG1p<%zK%|C zDhyl)$6NrHqq@G{IrJPRPyB_8o%g~qnsOblfcn? zud345g@qRO!2EE%0Z?uLjv-MxAZ&$ojSA9F#F=yV|e*{B8pP@ zPpy`qnVS0ErfIu&a2$lel&veGH2NtLOlegf9(x9-G9&7J^nDl1oLg=Hr16Fr(Oro+ zQbLGVpQ1pj;IRb_B#w0GLze=Va%%)w6bBhuOzm9OdN+s8=o$#_ccLp*OIu$G;h>rLx z;mgpUhOxf69+e^?1Ywq=jh2GNqFYu`xESK?pEsfZhXwuLEj*_r99^_vuKcEHdY&#l z0Wa8SHm?w7E{Z_H{N^6JFTqUk){$jh)SV7SR)<^84Pbk)5p-5xEIhKMBS92|x<@3= zZ)!HBX})$JOk*s=%QA0e+p{3c*vl0e=9sUD))!9#$+3Q`u!ebIIb1s(-e@F=*U1b% z9-vyObQ;pB6zoI_#Ud071$xZ|IC*EJ6cLci_@% literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/vote-arrow-up.png b/lms/askbot/skins/common/media/images/vote-arrow-up.png new file mode 100755 index 0000000000000000000000000000000000000000..6e9a51c7df2da30fd2e56359a93f4712077ee62d GIT binary patch literal 843 zcmV-R1GM~!P)Ze18CM!Al?Dub8&GoOqw)_0U&_j1|%mZ z3j?V-AXWlNa&mGq04*cZdym|9R2`JR?`}glckWT;thzab!KYu)chAIdN z2{HWr`xlHsmVhvfpOlou(AU?;;Ns$fQw<0U3k!q96hLNz%mxS`umwN?TMiBmxCT(X zz%UHx=;(lHFE1~+7_vH;K9D6Kvw*ZMNDV*$!7NY%c@N!iY!;y#j4TgxAjmABm>Nh7 zAb^;l8VIHqU?k#|$KnXEnE(OAf-H%y2t6WU0F?aD!Pc!?8Fud6iD4l?05Re7HZlf< zD?Z@u?ajc%#Do!S00G2;&E;unX$-o$x?py0ZZ5;*$&)b*1c~GFC9-({0mOnW3}J@j z3pQ+)=H=yK1UEncF%grAhy|b^LI(f=#DX0Dq!^ZylS5Px0R#}iRD>&trc9ZFHD1uo z0tg@`LIz>;HohQ2&zJxK#DXjV0<5g8D4IZp7&efdosDoMP(O0;g2Z7C0|+1%sG)yA zIS*DU-nemtK|@0WtN>JWV*^vCPQ{suK*a^n%pV{zfB=G7a0gg^Yk+0$lVq#(#)YQ~)1`j9~!(0t4szGKx0I>lA2$l~H18vm+Dfss78yF*}3LYLF zh6M{2fGtH24p`ms^XE@^(165%^kI-ZKmfrk*aj4E1sbd%ARqvC0Z0=pj6Z+=Ot?w| zS`LaokmG=6fn2{0Bn}Wjj4(qI5)wdFCb(AP=H>>wniTNm%NGV#&7yzfB V!E{DAJ$nEE002ovPDHLkV1i?NM1}wW literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/vote-favorite-off.png b/lms/askbot/skins/common/media/images/vote-favorite-off.png new file mode 100755 index 0000000000000000000000000000000000000000..c1bef0745ea9acd1c405ac11e0d5555e8b12d7e3 GIT binary patch literal 930 zcmV;T16}-yP)Q zl~7{r#<-|Zx+tX^7v1(nLEMz+FA(Vu=&lr@i%_r>io5Ej3qgvws#2;kN-aKOFk(}p z#$w4mvd&mnLCfqGB`LW%tTQXT~$@pwWz4*(Z$7u`~3X;?Ck6f-uOMADC8`%H49ug9F8Y$ zw_8+IRf)E?w)=F~D6T0AgF(40thghvTO@bbp`oEcsi>%U%r*rBP;F6Pqt;fdRTLK& z>)!xC_VxAkhJ(#!6RoYS59m#UO~C*#0w5d#&H*ynH51s~l9G~3PA->Ap>jP%V%H7; z1$bs+Zcu>R9*^fGmE?+(-EJ4{?d|vr-^j>_mM?&1XlTeop!aHPYwM^Sb=B3?ZURv7 zN9E<^df;4gaBv`wkB@cT+uMthZf$OE#_0YhP1;0PS65oCtgL+9(a~Wg;GAGQS@Lv2 zkN}h`N1!eQU0hu3oSU1o(cP$Wa&nTu!eDWr*}!96BogVHo}P|wZ*Lo!n{BzQY=b5*FE0apfWSv{QAJ{HZLNQP zem;yw6#{1^;uGuZ>*=Yfskc2nJ!4n)Ad9ee={G;^@9!H;&ZqE2X`<2Sx8B~~uQ}N? z!#RA{$lSzt+5M1{!p76PmY3|<5RSAL&84NK2CzIy@&xf8fJSJCngW4N5yyPM&Jfr$uh-k@bUF=s0dG-KZhD`{`3D>TQ-Os; z#w#l;;f;+A00-HDHurrf6dIy?@=((>&<&onoKqxC$uC;-AG8mSL?SV4mQhBw zj*gB7W@ctSFDxuPr@!!0KgLYy`Z|>GFQSP5EPn+U0OR;~TRRy%rT_o{07*qoM6N<$ Ef@zSrumAu6 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/vote-favorite-on.png b/lms/askbot/skins/common/media/images/vote-favorite-on.png new file mode 100755 index 0000000000000000000000000000000000000000..1f9c14ab0813a1aec1f66b12c3eebe7232be3d8d GIT binary patch literal 1023 zcmVZd<5{Le6mt|)_<`GX($dlRu2ZJkuE zjf4$Qewch0=TDhyPWI*W3&@@n;Mrh z;ON^;+o1J6x+lSyC4yAc5*r#_RRe6^%F}2A*z}DOqn}Ca7lL#(u})I%7x#^k&|B0; z`f()bM-h)+;pZ4u$ALJ}P;Wvb3dM-H#z&Yj5-&MXQ!CKC#TXEuPEEBSJ=cPnS)ej| z2r5#+g@;jQ$TU_*6Ij=36b6CwjWTT^%5_m~{H>S28)%4W9OsGaYMC6L=yK8ysOFtZ z$a|Cc`qnVdXsKFY-eyhS#>TZMyvv&Gk-Z`#?>^Zjhh+Uaj*dLD2Glrl0XhE%M&6JK zdqZ9sxMdI+<1Dr0Qu()0dB28}pC5!-B*l|clf(rIB=P3C6)a-E6moW+nx^tEqNfk1 zhaN6W(@j2{T4UmW|9SG(9X_!#|lMVmx<&Y38Ta_F{^dJ0jyZ z^KE3Yfqc<E7fl_u~mXc`B_ShF#sjOqSPbOw6(spucpG!yqP5!xrMP zXa--)`><8~qDbZjaUY(43Ik6LqKX$#+j`!7QB^cQueWs7N thq=s2qJz(mO1EpR@jr*ce;$7X7ywPEpB^Im6SDvS002ovPDHLkV1mKF-a7yQ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/images/wiki.png b/lms/askbot/skins/common/media/images/wiki.png new file mode 100644 index 0000000000000000000000000000000000000000..06d487f3e2a76ed2783f6c3f95b56bd2e2249680 GIT binary patch literal 5178 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 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/aol.gif b/lms/askbot/skins/common/media/jquery-openid/images/aol.gif new file mode 100755 index 0000000000000000000000000000000000000000..24d1e152c952b30b093fae1e9b4df6e9dea44750 GIT binary patch literal 1872 zcmW+#dr*^C7QZem!?cCi4Za2uSG(A?)lR)_AP=$#uGO}Lw%Ju{QB#pyu#UnCq9Qxh zSm;zBV61Sh5h~)M#VTZrfPfD~qP-9-R)T~G8z`6r9em~{s&r0&j6SaruC9Kr2SKyr%~ZsOJ<0K1{#bAT&Bc;E@BM#iVcCZ0`9O;106 z@of6}9jmLOZv<^PGh<`1M|ANYHx_$sKKqiUM6~VvoVe0O373{88CUGD2t8Q6=9{`t z_4Tpeo05u~v_Ji-t7toO)Arxyo|@LaYxnzawma^2KXwd1J$bd`$J^aKgJZ7Ii5Hwt zKY#w>1^n_qPkPP(qv%c0fQ0`9^5$rU_xv@N3a@Lu#Y>}2mwu?vn7R1UR7!Cg|Skive}}#-GINZ|~Mf_BpM!sTonH zF6$Q<+np6T{vYg(=)T?Bc(L$m>O|MwAFGG@WP3CnKkuGn_P?BMdr(%Np758NZ(h5 zc%#^9{pV&`lEaqVnzHL*ox#7h?Af}X0zN#{u}EK3Djljy%>SiqG-${sXGQb2>r<8o zC=HgH_EGXl;Z@_Tyn>g5mj~1ynz9b?1sT4ZgHy*HwGoY-C$AW8Oa*(2;gXH#BF)Ih&6N)gkb)&<*= zB6P}}>uS+wuO_NRUwdX3H6Qpxc8ss$vQLj$Ig}&LKG>njk6zzt(gw;0yCQVKQ~h?I z(9vz$-{ebf{GO9Y4Mv0X@hjVOvWGcF-$bw83RS+-y7)}Gcdp+i`O1Nm&HE=U`XED9a*`>tcdCx*t; z;fkqzRp@BGN3(PSCG<vDUMnh5bXB)n6!F#l<5@f6 z9HZw?4>CtwO74QZA0n(RT3^|gN7YXG+VES=>ov9YXIEOf)T)rap86)Gquk$I@ThQ~ zD4g9fo%lIo#j0J>l4SYujgxQA$(OmV*|WZzC>PJa;VeU* z;$zH;2cFAR(v7PxsFW6_9r{ORP}19^=a_x0?aFUwBkOugTe~Ex2BXR{%Rg(L?ACp? zD0Dj35E=T?muit*Eaq|9?UsJ6NNMY-%#n;yS&ZmiQ%#OC7Yi-ib z4X+*ObNWTL7zTTcmx3p2@NDI{#%?bEM*4(?oKCLKl2qDH?JXQaXl_Wfez789>xxbD z<2=)gYDT&au-E_Rsu4#8TMk=alXy=RrLFq5VJy45F;AZzb1}u_Rkz<|Q^WH8 N!$&m7)`lS@|35eW&(i<^ literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/blogger-1.png b/lms/askbot/skins/common/media/jquery-openid/images/blogger-1.png new file mode 100755 index 0000000000000000000000000000000000000000..8b360ea5620915124dc45ea465a9f2411730a925 GIT binary patch literal 432 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*VRoSU95jv*GOlT#X+>N9=*OL{Q)Z2o6xWB>${ z7E9g~a4RxB%)s5|;lj_{?8?N<+|JB}<)%Hfk6edRIx|)U-=MyYvG@3L2ePrxsR2Oqt zBxw2I!og0~3Z95SriB-Uh0Z(^<`&3W(C}!9Pi0zD;fb^77(DfzpVpq9FbC*K)e_f; zlH{V)#FA9q6d=I>^q-}!frYMtWr%^Hm7$51se!J6iIsuDpB2g>Fb%o+DVb@N$QsOa gjf_K#jI2zIfJz}6>YoYA12r&sy85}Sb4q9e0RGj5MF0Q* literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/blogger.ico b/lms/askbot/skins/common/media/jquery-openid/images/blogger.ico new file mode 100755 index 0000000000000000000000000000000000000000..1b9730b01c3e60a396afa4193986b7ce800784a7 GIT binary patch literal 3638 zcmeHKJ5Iwu5Ph~|?1U(aasn#_qUHo}jTDrKiXtUdlmp}lu2BjK4iLGb^Z_VRaDbGQ zV`g{OIJ=vVkdP%LkF;y=%$twL>zy$WAVijduPI(q;0nNaEbGS@D4BQ7Jci(whr)Rj z2q%0XEZ+#_Ct;QoHXnrD7mcRRG|JxzlR06vA#8Vq`Ijl@shxhCs9J>F=Pk_Z-FQz1P;kqy49I?9vmhesu(DDd2TIQ)X`OMtv zn36eGX(BoIuReV`=lQ(PMGs~LG0b10`Y_|q?3EjYCVKU(mu83%PtE7$VCeoT#ik(R zI_G=Ly}Z;TPEX~szaC%nFS05~vowSpGdW`wz5QNNiS763 z)A|{|X4mk|Pb``6HD(c4gZ06~PM`I2P38&B*lpoil+UKHq(V!B*N4IXtYRdaF@vLl zmV&|Rr42{^DojjF{P2k7k-;lA^VR%bjPq6nUi{3FY3ZQZzmogqF?nyszfZWEm-L2B z-JibT!wa5rZ$|dN(=9gsk6^s8>ePX{)JfmC{wZV@OTAD2$6>+F!Ka<9w$43N|KZD> zPKm$&mc^Jhy;?nm8U4s*&rmI|-^laso@xK@w* zQ%73r<~Q4ldN041`4(R&vsZcBa-Fqj;u&0Ae(<|~YnlAQHvQkZs|?d;eGo36IJ3*} zU)cm;w5gW3MwBEMr6!i7>ZSk*1|tIlOI-sCT?5My14AoA6Dv~#T>}#<1A{*+ltW+| qa`RI%(<+fQnCTiBhZq@InHT|;LNwGr6P5>RVDNPHb6Mw<&;$Sg?fAn0 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/claimid.ico b/lms/askbot/skins/common/media/jquery-openid/images/claimid.ico new file mode 100755 index 0000000000000000000000000000000000000000..2b80f49183c7c36fee4c4f9f0a82d4fd9950fcd5 GIT binary patch literal 3638 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 O!#>o_*!YQVNcKOTTaAJM literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/flickr.ico b/lms/askbot/skins/common/media/jquery-openid/images/flickr.ico new file mode 100755 index 0000000000000000000000000000000000000000..11f6e07f68446a858eb793ece7bd44dad957a8e3 GIT binary patch literal 1150 zcmeH_Jr06E5QQJn&W6N<+R_e7ID~KjOJm{@{ApYu)=)sD$dVm%x8e+Ke`=wY_TR8Nr~50fe7M}lw@Sx z)|sz}w0Z6jHaQ_AqDOitt<&SSea)PHxo<4{I(gaj_r1;9XTGktbGBaB{saBT0srj_ z{;~R31Lym74}$(bu>NSIe?gpv&bzYY@H?IKd literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/flickr.png b/lms/askbot/skins/common/media/jquery-openid/images/flickr.png new file mode 100755 index 0000000000000000000000000000000000000000..142405a6e6a4d5739f00b18dd6ff5693f6db1109 GIT binary patch literal 426 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*VRoXegrjv*GO-%i}<$K)u`w*TgY2f{yOO759% zIVQj{S8?O+?FWuNikNpOhMmz%p*o8@@35=;(PrbQ5pQe$i2HdeZ*?nLqyz z*gx6rmBaFdf&I+MMb|f6OiX%kEqhthHhX5F*-XuQFM7Gz#J$x2c%k9W|0p|=FU=Ef zNL2hRo-L$jZzr9ZTC>}H~WqJD0<%)F#> zzDalYrg(pGEJF+otqe`9Obv7mOsotH{;W_AfoaIiPsvQH kMAl%YYh)Z^WMpMx1XK#qQ2$I=9;kuA)78&qol`;+0FfD;b^rhX literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/google.gif b/lms/askbot/skins/common/media/jquery-openid/images/google.gif new file mode 100755 index 0000000000000000000000000000000000000000..653953658f06dfdc9cdb2a4d46cf1d2d600803f9 GIT binary patch literal 1528 zcmW+#c~q2D6o12_Bie{5;4%_8R4Q89i&yW@BMA}`*e#JsWmGN=nZ5? zh>OQKr<@bcG3Q8BjB!ReBb;H*kZ4lIDdm)KiaAAMmM~5zCxjEs2@Lxp>&m=Gj_1b;va6~+W3$QA5@3TP6934$CUX$Syx!8imBZXrs@17yNB!~vSXFgOI& z00yFA6J&u7AO>uJM&d+b8ro39LvS$&68r%zR2YemL*(kbw`e*~C}MVAMa(*b7O6Y4 z6RXZR#1Whh1(H*=xQSDT0Cqyd=KxoN@W2yLA^$VF6~}#|eqXn*{7_*7tAEyN>vatB zrazwkQr7satEaEuF*wY7`s|M3!HJO*+xyz>4tvR!lD+qgO-*&24-5?02L>FyJ+|kU zMphJP`|Z6idb;cDEPuCHt)*8wYAae(*EJNKzL^kx)gArkaCCHZz^Cu;aFc*hG)rg@ z{{)h9B$3J^FXY`eO2^MkG+j7$=UY!j)K8Jc$4mE5#Otl5;{1CVe&OHA7hgP4o-;ir zJ3w{NTE0Cn&pe~7s7<{o|Kgg5%Js|C*?-lX5A-frt=^b+Yt$~Essx-oU$-EEq_0*d z|065<SCD@H}b6w5t|=Jg(gZO%0ed%?Xf9L3!eOzI=i(rd{)i!qZ=DbCOy1TIVJ6j z3i-pv`h`h{724}$`K-B#SS#!3KBw}uj{EXpg1KLrnQQZMxw>Y&R5nF<^5<2qe!A4W z?}PTCPVW8oxUtISF%|88G5WGB9qKGq1kNe(K{t<$E`0F8*1ke}VM~U#uhS^qJ>kz< zHuIJC@ew}m4KI$Wq~>PBw(ed2GGDW8WSXH`>b5M-Ax&8#8%#{e`rLABm1k0On!*sK z+Z(U<%qYl7OLx!8Ss9u#q|6L{)UTR`ybJw}WBN8nRL4knDw?K0E^yJxf+nB1qj=ew zlK!5@nAYm0mKud#ZCSCSEo{iIpjNrW@^0q)D0Yi48ju%{*y1LUyf(Vq8m|vAM#LY$ zEpji*SLj;&h}`IT#skVCue+7G=tB7u?Qr2oOEp1V>derjMBCWt$2B_wLWjC8RqKXh zE#V*H{T@H)f^3H_eUt4fDV~AsySf+VxLCSQo~$%z^iyL;^-i6yZFWUr@AT%6Q#Y9I zPgYrz^P`me_k@=z22I){8n-iw;Da9$mivMUVDTVaT>cjizP#m?{5Svx3j+jg?Aklq#>rFWq%tblBY4FU_&> O8(&%sYeV%~6#PFV+wk@P literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/identica.png b/lms/askbot/skins/common/media/jquery-openid/images/identica.png new file mode 100644 index 0000000000000000000000000000000000000000..2b607db163d6159412c694e31036a361db75f3c9 GIT binary patch literal 6601 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

      7g;&>oLSQWk&pYg5F)WLI?J1k&X3ih{ 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 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/livejournal.ico b/lms/askbot/skins/common/media/jquery-openid/images/livejournal.ico new file mode 100755 index 0000000000000000000000000000000000000000..f3d21ec5e8f629b77c77615982cef929802fbde4 GIT binary patch literal 5222 zcmdT{3s6+o89vBPQIq*$1QbbvAP7Q0Tpmk!l@)pHf>>S> z1vbL6JXf&Eq~nt$W+Y}?NhYbg%X2}TX`y3UlPS(H)68fPIsMMP%d)ZvOQtiO{(Js= z?m7Sef9JdZJ&(JD*bxy~wQ3b%;$cU~E2vFPBC#hV6@5-lg8iE%gba#Un|TxR-jjq} zb3h#KnTHdU;W5$jSK%T=Pj@H?K_Lo-P~nPOqSb0qGXv!dp_JW0@nc==!i$LGD~{u9 zt~8U?B2Fd~qvpC~M^KA`gqqWJF*i|=E@%ujnuad%Of!pb5`P(QC7kR#SP2OTzQ<#W zlv%8aIKD~feX!6nCX`OuQ922|4;{CsbQG#}pj5FEW=E(mRL;B7LWEq0KANYW^3Y=B z_y|vmH;?ldeDaRw*8J(~hC!qOw?4H%Oa?Y1JnqbNZuG{_4-W8zh5g(k>k33gQDw%FD{ne3XB@ z!rC7_g|Gdhw<&R;HMK*r`+|J;KQGI_{V?zIn&?P3mLE&uo!9m>GUrS3pvy(U5A^kP z_$(c0u8#D!b_R{U=43cu$bYTlSjYsNjhYF)s`QQZ*3Ky}xtxmjy4jX?u^{kLVeo`r zO$S;h4E5!2dRRN72QywCwpkgq;m=hECwm&HvGsd>T}g=BENAdX&yl^G;15ZD@oDXe za5~)ny+KtH;%e1SDcr|-+q7@ZKZGT0tS19Bm$;2T93LRj8^{4y675bB zwk%H%+bJ)|KhfTK`uexou|aVjGo#E;Z%!+~BizWLXGzMgwI!$9Yp#x{^ivH+c7UV2 ztG$yM@p8QPk{Hi9qZjA|_iy0IrBz^V2FDGCt7!c=X2YVB&%j+s%GRZq6}$VIQ&2P#@0_gE zNAUFwKQH_3h_#F3ZUXmN_F>UpAnQ~Ky-mq~NZp*1ER$dEt(TrWr;qctLx_#Sm^+j7oj?A#I7DC$ zaD!T6s6Sj3{L6HyM1HcisHVO0oT2v1d(D5PMI|R99BoZLz$4vBrg+7t)3*@)h!oupL+wwg_YH%9vj*4aIjHXba4{8xkuc);A<= z{dZS2HK=KK!_1Gt{0T^;r%|h@D@A zwEWI*^|gxqvzS+eyR}HSKjd>V18&q9+tQP_E(>?D2|U7;$hbAy$_UJGbI$ekhw(yN zQ(t@t+Lp`(@GUljJClQ+q->vynK|C(jk-g{A&xDnJEp9_2N%N}naK~`m>>4s;pgp< z9QJ=jEdleAH`nptjkB2FdOtgL+Y^|;w&WZ>d7<84e)0JS>Jtat2~yqPk^=D3P?xq0 z@f7|6Jern%&D$w>p*GAGyl#LeY+CZk`Lq2)w{GZkg^@2rdyw`F-&0YZ?HMmk^)y+7 zCvI74%WRnjCpgYpE3*5H!##ZipIx~q^<9GpuZi7A2hXs1Zk~!pO}{2J>4l}(OBYOw zFj0B(Bt6hCeb>`pT-SE@^z{x5U2Ln3-?S|8w?F!!Jf|8`_0G@Mi{w$ z=X|SDy7MOoto~1%!)>|A@LPpZ)J;^~NDC9o;+|ify5q^Z+~}r~bWFtv8#b;5N6F%T zh9Q2R{a&r|U)2@;IgyF6UTmI3tzX1!nST@^QP$V_qZVtm#9wTzwZCbi{LlWU{0Anp BEK2|Y literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/myopenid-2.png b/lms/askbot/skins/common/media/jquery-openid/images/myopenid-2.png new file mode 100755 index 0000000000000000000000000000000000000000..f64fb8e81b1adfbdf56a27de64731491f9cb82d2 GIT binary patch literal 511 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*U87#KA?T^vI!PA4ZU5HLs*?m4T~osf_a(WfS} zvm)v`SIO2h5hl-#3=Gb+7M(XRF!+<6knkWmA%Wwf`PRDfmtQUhpLjKMgGGYSl&}b! zwWmuyZqbuuV`Hl{v3$dJByFmt^UT=p<)yYQo%-{xeeGy$T*&Rq8zz20<$prqO>>R6 zE$12RG-|oJ88!xw#@LoR68=nUA^bgP7O;gK1jMIbk>J*@O-}%8c!i(EIWI z{rz}FN5!-nDf^l~AEq;E{tD!I@bFpcygPA6JI*)UIkoM-yj|S^p637m742qhFYP_hAF>%3ImO7yz6kU8N?5ex!R_zs z>o0dpe>}!^&mjdE692ya)mHQlOyXqlj0-XqdN%7fFaT9cTq8=7i&7IyQgu^+1OqUD zEOiYmbPX&+3=FLdO{`1}bPY_bfZ?@5IRvI5H$NpatrA&-nXZv>h>?+%i4jmKL__^E RVR@hi22WQ%mvv4FO#r_Lz6k&T literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/myopenid.ico b/lms/askbot/skins/common/media/jquery-openid/images/myopenid.ico new file mode 100755 index 0000000000000000000000000000000000000000..ceb06e6a3f0d88fb97cf10475a3062fb0edab33e GIT binary patch literal 2862 zcmeHJJxc>Y5S@6ZrZbS)owhjA&;jnXfl*X745tmMB3MrBW$)n`RP3b417qd6J6bL?UM{+)q(3xlv0Ik%T7j-l59$8$m>#ard~jq(8yIciUu?j(!>?&WKIe?G5c$>` zV+r!H1WS-ha)=*H^!uZEl>O1BP8%xc=fconk~~4DvKCYWn4`b_pUnoZ788}%mc>uh z9HV;s)r}P~NWtqf_p5&HGjTJoZI|S8n)uf0l05g}rdhqaIBpnv^myAWbI*7E=&N3x z95xI+0zR;x-|08c&;6Ul#XdjZARV+no-wSN`};1(+>)AIisPc*P@H*_1Kd%ys#()H z>NUmL)tL6ccj9UxPF`{LG^Rc9JypwV>?^N0yvK~LBc9f{#^OA9dQUv#8Tz7oxfa(K u#=%<%_2}PpAb{bmUKcqz}))c5uC(7v?)v4a2P)ZNa- z@$&T2)z|&~{r~^}A^8LV00000EC2ui01yBW000GQ;3tk`X`bk)Wk@<6#nZYULKH{p zEx|?+kif!I0vIL|#ZMubBmjWH2OtmxIFVa~6JQ7!1CK!f5W#StOTv&C3=E8h2vI1s n+#cd5;2fT3B_0kF0v!+!GARoV78n&7dMN`JIW(4+BOw4gP{MS* literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/openid.gif b/lms/askbot/skins/common/media/jquery-openid/images/openid.gif new file mode 100755 index 0000000000000000000000000000000000000000..19eb7c6f6849c2a5ff61efb3fb9980b6935a4d78 GIT binary patch literal 1473 zcmW+#Yf#ix6hFHw0+uWZN{J633Nz928j!}gz7jM?q9K7rMTES$}eFf5s@ z!P|m}uoZ}iuLToalt+M9BTxxs98l0$trS5FSZ?R;{jziBob!8~|1J*wIOxMQk?=P3 z0|@!J5KIUv1Q7yc#kt^2a4I+v9AuMm!I)rFFd`VpS;_@vf>J?=pdeQX7la8y1tEd} zA_?FEm;fq(2*3id5gM^@8K*(=$j3ORoDNL_Dd7~PD4ZpX z6UqtU1W+Tca$p=N2f_haQ8j|0XjF-^u!qEmjnIgN%ODNO8XskhFb2{e(qcs^ql8hA zc2Na331x&Z0_Yw^;Vc8nfG|LNmh_9Y~DW2#r`Elap<% z@exWv28sTV7Arz10n8Qcq6%zMAQUi1Od10~T{wi^WHG-jN z+(cQZ1BnqEp+TNVPGcKue1I3jAkiPvVnslH95Pq?y=BvWLK(CDDst8yv`pQeom{oY zA&+2pD3R=<L~%pK<7J->#eUWqzdnP99JIy!?EgdYp@|Qn^k~ zpH&y0GuP20u4qH(`4g7!XRf#Urar%BW^=Un4tJ9&%aZ%cqP(ns9}md&y4bCqx23xA zSj37ib{5?5yp`5gmSAe#T=;o`ZPdshefzr$lGhqHJ-+0h-@=@VYEl~1x@wh2?3i-s zP?#^c3?^L6I6Y%-xou#}#5qr|?ss%FJ81M*uQjC&gI}q^;IF)%=ay!&`k%8j2ix|q zEK_Q9jk^^3rg81(Rv)rKs)13a-8pIM&Ox^2iliR{YvLGFK}K%dR#7DABh?b+qU5nmYn6uikz{5MVWNhu7tQC#U9X~WIi}GG@Cc$k*0O(R5 z=w`db?>T&BbV`?lqU=SRDz0_hgw(j3?ujR-2RFw@1lbNX1hlwEt_)x`x9+gC7Z0>w z=j9*oZ#BKwm1NwSv8XpDAUir%v2pqM-tht2@bPX+<@V6-@IUpH%)vSGKuc!HH#92a z{X+u{g}c_|9tm`sRWs>Z--+!SrD_x_Omg10G<&P_n7~O{t!!t7%B5W!UF;e<`;^7A zwQ54v?L|R8fxV#xo)x!)Jy@}#|N6aBL-;4@g|DuU&R#a|@&~^2T^3rO)%s>^=&76X Pb74>Yv=aWQ2Il+^c-q-* literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/openidico.png b/lms/askbot/skins/common/media/jquery-openid/images/openidico.png new file mode 100755 index 0000000000000000000000000000000000000000..ab622669dfd9a90d93d01eb3e371b7cafe6c655e GIT binary patch literal 654 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%3?x6Bmj*I0Fp32Dgt!8^n_%GkWH}&$@vzfy z1rWt>E_T&OqG&47xJU{SOk_34Y=jC7*0n{c5V6N|Jm8Y}^&-pOTo@1JE@fGk2Mm|U zk|4ie21aHU7FITPP98pf0dWZ#894<7C1n*|6AMc#7k5uz|L};|rUGc>Lt$+fQGx%PFW>%kX-!fgBRjqK{jeiYax&1X*6P`ZiOyxhY@aaR2pHCO>zir$)LCtXD8x_6F mSx4_%OzwSX@y%uX6P_|}hZ#5T25SNR#^CAd=d#Wzp$Py+&RzQe literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/openidico16.png b/lms/askbot/skins/common/media/jquery-openid/images/openidico16.png new file mode 100755 index 0000000000000000000000000000000000000000..ad718ac5a62bcc6e995e934064f095ead4b6e7c2 GIT binary patch literal 554 zcmV+_0@eMAP)&P@Tx$zK zg`~UL-C8ZQ2-|28!5~Wx5)m0L>Jb1(5jV;Nr?PiaL(OqwOY$%cI)+e zAGk59mne!(`M>};2%H0!rya0}IAG0BlH^dWR(m2M_f_=)@IgeLT5I34aqs`2{fRh^ s=d86?fHUW}g5FWL{!;FK`&|V13)hOgVr~+r0{{R307*qoM6N<$g5FK`NWB;egxu81uWJVQJd%?yFbuOKe63n-+pJq8^_inN|!<5KX zFN*sfuYBh^N#*37U5;*-g&BHG&qzF5KIPbyTdo@Gp3RZ!`IWi(>*ra1sXp?qirOVN z+Itk=U-XFkoPOfbN^#}~I=N0}kqkSk`viJ^ao?*q-!qH#kL$)M@5?;K9~EEDvaZD#)>=N%EREX+1lcxA4c zbH1=*x3k7Nr4-imOyRkvhk35~Crs+v$HZ_e=WoT`Y_=s6ikx`d{>Ip})TM+aN6+|d zsKDGyYt<-~H<*?|Py7&ZkT|u3=hh%dXrI`@B$d-ImSy<^r20u_tJy z{YrS?&d+&*_oKPcrceK*|8w1xcc@~H-P%ytZ}Ltj+A zE!c_l2H#`zaX;dYlru_mk^31KdcB@L9&W3#wIFp`YJOeP`OSr1{CK6O-`2Es@?E#T zy1MFK>-Ep~I=Vd7%e_s#J@}-Zvwh`X=4ClX_h=7BXW;)k1Ifb@+v7M5AZI7aYkiNm z$KjX;Qm=X2(~a>=Zn(6-IG8mv>sbkaBEroVrCFBdHr=3XM61uF&u!`5GI1u;Qf<+zMxU!sr1q{f@m!ic#&7x4 zjcDZqv{&NU85cGO2UiL7I^;$4kVpqJ@-E;pjlK(>l3v2Y;e5IS@q=)?(5+CX;*7LwCwnlxopJBahPURBFV+~&J eFF>coTp;}_7zqbJAQuX63TM^f{)_Klzq8-HK65Pq literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/twitter.gif b/lms/askbot/skins/common/media/jquery-openid/images/twitter.gif new file mode 100644 index 0000000000000000000000000000000000000000..173cace1cb2481dc6cacc7416fa59321a4410a36 GIT binary patch literal 1913 zcmW+#YgAKL7CwieY5}VyZ8R~_kH`^bsN@- z0`_c0-pGs)ejj3GXwlu^Pc$0%kL@kK%zA&hX0U`7z%%TdM=#&L|pj6?h= zrVJB?IfgOA2#^?}3=xJnhA=~r05(7a77Rl-;`i`DDCH={lp=mDWQ0({QGzK!JPRmt zgmN6^Fy#o9;&TX& z5KIW-^}$-mIF4|baEO-+ETD)vf-%7e)B`E#C5R&k69n?W513=G+-g#Ilc@z1m-xz8wvVgEo2yT7=f=K7g#`%LzqM0BRCBP0550n*fhJgGo3@$EQ$ynx z^VD?T@c6U-q2aNK2VDc5gQH!B;ht9}gK5IEi3yvT_kHH~fcgK%KKPA&NSU~Wt^J+t zc*4^3O!b158rYL@)-%bhGpuQp8LVv7#>_L!YO~G2OSet4X)BwVv0AMbiv>O`v%Dq% zq3C_!fcSp`IWZ(~@>pMXsV>9G?cE*CWqI|d76>=yt}oC3_3R>?pldEyHz_^C&vsG?mZHrtNlXkpWwbmdTC?YtiwreA{UR4Z**yA zXyg3QnuBAgP*dHem!z6ev$bfU$Gwu68|m`5zyEXf;lXB-;TW1L%IT@{Pw`EPmmGMZ z7fiY@3cdA3S54b?pC6Lis;2t$WbrLaj+3t1(+-X@lc+?u>iTTt##J3MwEmR%)}v4E z$Pdhu8{V|!ddbt5x;EVM-`-dATKV{sDp@a%|d0=^KFgF;31__KJ4XuDE@ES=FfMTMiatfwSc6I_AFZx$ z`C)L!+|a$BADY`M{!fZL^Js5ds!-fp=^zd0F9`QSL#_MnpGeJ?w}>a21?`eijY{Dq zefD4d$${!?IlIR+F)q1Bf6j2(8C@P8FV6kB-UaDB9zXmpa>}vm*hp=RLbdJr(++Xk z9j09(2`g&M$;Q!nR>)i>eT6p^l2?`5ebL7DIadREGh?=i98ZTArJ7=Y zEICT;BoXzHz`k}`%->ZY6^Bx5b{m*0w$QIvTTy4<#Wv)qx-X1INyD48D z=AhOVX(IK0iN{`4;dagVf`+nl@498J%r|IM2JxWAq>!j8#)nyBH5u>yG30q?@s6tQYb&!BNPUYFqjnu}4Qi?IUKVKFdUBO`Ul__R4NEGOeYexKlMWI*z(Qb8~sc6rc@?X|CNB54|ZsR=#7X%AjKAE5F=8`_Ln|Pru zL2=UTJ#)9XvP;hE3cFY4<(x=(XZ-H2kg++(H4$@H_ES!_WY12g{Yz&uFYcT8-Stb|<&5Y3>zr@ZxnH$}pZG_^rd^?h%A+s->D@i- zxH|f4-;lq)bvU8-lzp7vQ|J9=eC$^**NV=BFTC>bOTRxif3_(z-L=!h*QfS={%C>r uT=JyDv42%|i!|!_FUMwk?f*{g(YL-y{A@&#O4aX}o122CRw@JnB>5ZB-r*ns literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/verisign-2.png b/lms/askbot/skins/common/media/jquery-openid/images/verisign-2.png new file mode 100755 index 0000000000000000000000000000000000000000..c14670084ad7edd1b0e9d0795e6528cdef94476b GIT binary patch literal 859 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*U87?@f-T^vI!PEVa2>oGY|}cueE8LQ-r;?xW+qEnNYQEJ<;m0d)a9Q_@mBVkN|!>Qw5|?oEj@ zJHq?c^=`&1?qzR(mMiWxyVlEZV}5@A=jwBo|BFqWIFe>RcdoZ*x}TP)x54RF-iE~s z8DC#e@LbN^5O848Yiql$Ta`ELV`DH#n{(vi8smtFhy%ax$(8JV*Rc1K+L@U%85qvq z7pmEuXL6g%@aFaAyM}BT%H@tx|9dyM>Obn2cTOoZWK*|KKKZ)!@yE$)SOq+4T&ZVtR9A6KuU$Y6Q?$#Qaa}_p@ ziv*gC<@l8sFRJ+A!F1kJ==pE9CPrpHc5R{FIYm32n)aUJnmlO|N5i&BO52{sK0kbA zOM=7O&ft=;y9;$r`wVX1A7Ex4E?i21Paf zk!EFRxSVGEDCsc+!>?d^$k<~9c|4`!9(VT;faV{nLzlk55Tah2J(E1ez6oEP5T-aWN3KX2YP1Ks=A zYy(#2_0JYBF1~uJRbbBS84GlMPM>=wiOhxKu z#nY^gWqq5n3nWU#buPR-G_mk(7c;x?hvJ{?`$|fat4cU-KApNFrdB^?r^)|yoAa;r zUc8nrk(b}TNXJ>ox==$^@_Bx!qMqv0{kMOz|Izv_r~7BpAzFVdQ&MBb@03r5qo&W#< literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/verisign.ico b/lms/askbot/skins/common/media/jquery-openid/images/verisign.ico new file mode 100755 index 0000000000000000000000000000000000000000..3953af931987b0e66c122b338dc352502564eafd GIT binary patch literal 4710 zcmeHJc~n&A75@Q)fTGMB7?4e2SOip*O~H*}8$=cdnTIhNBRWh_BW_>?5imwU5pfq4 zQ4m>QjBTZ+CRIeErNk zREwwe2CX!zp{LmSu(HSiXS0Z~ELfW_hmFlL(pSOSItLB|b75<{0z+Ju!Pzbsp04@u z^IM5gez^!5o)68~JR~M1V)|=3XwNQ8?a>Ya%3;dK~By}6c*$o zzc7z#uYNYpl*u)SjcUZ3@pYK4t;Vd>gP50g5b0^P$e34y71<3~mHQrY z^v!hsAEG!C)5bMuezljdfeI&}&sTTWohhE|ksJA(uJTd-&Q zMO2ly;NXEa9IkGqoEFsAT)_K>+aNTx;izy9$B(q(h|rGX$9{>EKmIk&oIQ<;7cbz` z_Vf7oOgqlCbl~T$U*gkC?YPwb87^PGj4!TTMMqaVuHU?hTbV;iCum zZRbOL{qcZs;#f*}wrlz{`0xBe*(I;Sz zyL{rRJv~s#3{DZZtH%`9aNO9+=O$@yE9B}Ppw5xAUn^OeZyC|qoIL(nKmn&nYY>eL zKrf+{E*!VX7~r9ZuM^b@Gr+V$Li3f}Ym!#NDK|=*KfCmKlHM?d^N_SO`K0ZlR@aaD z`$@HgRh&}Pnx=3wq}{WbyQe^@gROXtjgE@>Qu-jEKP!rl1Z1YS(s35^4X=At_Fe%L7)Z2wJ zI=BcM$Zt(2XjtYnSb8HX6)sr~HNO7Fi=k7(q*OSR;1tv-=GQtZrfwBAkrlHYyo$=k zI88HxY21*dylU_ihyn*o`a+siMM#3?z2La9_bI5tty|5m@_$Kv=M-r`DWnsuDXI|SI(OnaFs5Rj zX)H<$bK-%v#3C6u8ZzR^Dh7HHqo*OR6YUU|4ygcb%!+EsW(iDpCu}sLU`Ao=$b{)2 zqYUx~O12syWYaUKC^j&d+|rIZmK*4)3nD=!V^Y|D)97J|C?PnK9W{j|5sikK_<-~j zB9Ma7re4g!anVfBnN3BRq6!uR)Iqc*BNYKUJ~~i(NLEqh;Ss1(xyk6vj3QL-oI(}E ztJRXtBFNMQ1%(&^QRrgWCG@7zAyK_e%IJBC60Uv0=A*yw|Kz_F$NuLuvWqkMvw2)i zo$zJE=5ZUFK~Q{S_xsb>KZ5B3WHsh5CkCG#~pZKK$?_l$V}GW#u_kRkqUX z`UyTd@da8=wc+%sYiMmfjdN#zfeRNd;rz$faN&FhZrr$v&dv^;YrTU@mu}(8l`dTS zyp!h7d$`$gpJq>*C%Yct?%liirkiHU``!4q`ysx0_@`GhR*Wg zL+$LnVne>U0!F8BBDJgQ#SO&zJnpsR{N}Sv)RGX5ladO5OX7h<2Rs-#nHmcwuE~3rz z2>~NUOyad;2ZtE}qZs8M)E9^dp|1)1sQ>G$Z=aRsiI#u(rfHU-9}N`$^h{weM0ms) zh?4E1IgP=fR~iiIb_PS5COyWGoN9OA+h%v^2+Yx zGJaHiGz3ONU|59!7au1(GXnz`A0HnV&|@H)n}q>L0s$9;ARnmIVdDT(f_z;3d|=N* zDLy_ze4rMf1_3@kMlc035Cr)7nOM2`_ymE1g8cmaK$@2aOo2_OfcXEPfdPgOHZw3Z X0PU5rU|_JTL0}LcBo2~?sfE!1%eOAT literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/vidoop.png b/lms/askbot/skins/common/media/jquery-openid/images/vidoop.png new file mode 100755 index 0000000000000000000000000000000000000000..032c9e9897bb71bb0a603534bfac1a442be1cf20 GIT binary patch literal 499 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*U87#L+dT^vI!PM@7*>(3M@;Cgqe21^@jt1Q7KOpyh+ge)Q1 zK|J4huRH_#zwbc3UEA;R9AO!H&d%roag-+O5 zO!N95T@?S*G}Sgw{mN!=VmhZ5Rzf4>LLtlrZUAWLgGPY+oVrG5xo^yW`xZU-Q@wk~@ zYxS@D)yH#t7kvCm!TtK*mFh-7(+TYB+*i5pa!nc|Jf3SU=XN{`fjrfux$lA7e}n0{ zlfCqFi>Z(M`wVVG>yKK1*9rZ1`iw}=W^iIUOLNU?<8e1$!1FrglCO_u)5YTUvK?eu yI~YIj#-~XLO;hd+o2#A2S^08*UB1qS`QCUE52vFxJ9}scm-~xFcCkL1j=lhFMR`a7 literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/images/wordpress.png b/lms/askbot/skins/common/media/jquery-openid/images/wordpress.png new file mode 100755 index 0000000000000000000000000000000000000000..ee29f0cf1acf19c7dfe7407b582422511d8d20c9 GIT binary patch literal 566 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*U87#QO{T^vI!POqJOFt^!3z;%8gR{`e|W&z#@ zTsh$k`!={g;#w5)pddwmoA!p_Mo&iVU?$Z7C%!+f+Dl}VQdLz&Yw`xl0Z#EMLLB^eIW!H4}F&{V=c{y`~F=GJ7WcP!sgnTVS9uyb*-2UJa zyX8gU^EHyJ5)F#0Z>a>7vTsvVNHXe-51kX5%U84V#``+8kTv3J@u{J|FE}JUSQ0Bg zlc#vvo$#l^NBfjkE2}kMVfyAM?Qu71W8A0etJe-(wf?BN$j{t_Xf0)0$=*^XiS;GhnchwTth?3-@)Wnih z-4r0fU}RumscT@NYhW2-U}$A%Vr6QeYhYq!VDM*!atKUAZhlH;S|zduGhHL&5F;Zi Z6CjKRe&~e!ri$?{9tmeb%mvl%u6+ z5+TN)z!+tWFoqdJyb=T^P$m#2U?w2mqynRqQNk!@6!BRiFhUt2j9^9(U&R8$lwra! zW*7kyLjpsTA;J)52nxUkXuyJHm`0q3KPm`B5U?O1P74*K0wn^)0!3T}Gzk?55eOCt z;-X*{QvnkJV*w-32&)330wMy!0s>Z04PYP|RDvw%0Wn|$G+@Cp;)Zw)k5Gy!Mcg0I zLPZEAm=eV8f(mG2LNTTofqM`IvlJ1EFhyVw@&K8z4RL^f5CINBHGqL=Pzj@;1Bd|| zpaBc<fJXd8d>YzN!$WW}2on4OEmRoskAvrGxHoSaPRL_6T!qgX zg663kvg50UIQS6^4mpxRG{1>K2M0Dl!{-23g7Cl-P>oLL-cQX;>Gb;9xf%UztLClx zM5U*<4v zECQow1!zG0KY`2`5}AqpsxHakfX{DX0H9xe+dw!RlZBbcs zgvCT^O+#R2dv}k6EYP=II%!?oAZ<;H5__*LQ<(?76txGqZ%W4JJzbX1qbW!1lXuAU zji@kIwm3TAfM(#z)~FkMM+!6NwKbb$`k7p5->DF*Hk^uVlID8K?0)Ey7EX6Q4Bafr z69;5klvycn{TWj#^+_A+j$bqv(|ua@?O=QEeoKig*66<<&Gw}@4~7OsOf&||)$3y) zX3iP!yL>9;(tNLjNFI_=WLlWo<8WxasV`7H(40}~*!)DDepeJ{rA+$pdUjAjHF5HG z8sJ6lUX-}@>_b!RaatrONJGnI%r_YonD;%SnJjSA$h6ZFN!E-;QnML{AOC+~b8yK#fNA*1NN?s-W;ZoRkUleia#3- zw@6w&{@B4&6B=Q%vs3;2hgR7> zc#$8~RB7onIgm9!!&O>xxWJVJE0Jk@{NcjffJ+0gju7uoZS(bxY2LI))f literal 0 HcmV?d00001 diff --git a/lms/askbot/skins/common/media/jquery-openid/jquery.openid.js b/lms/askbot/skins/common/media/jquery-openid/jquery.openid.js new file mode 100644 index 0000000000..249413b919 --- /dev/null +++ b/lms/askbot/skins/common/media/jquery-openid/jquery.openid.js @@ -0,0 +1,440 @@ +$.fn.authenticator = function() { + var signin_page = $(this); + var signin_form = $('#signin-form'); + var openid_login_token_input = $('input[name=openid_login_token]'); + var openid_login_token_input_fields = $('#openid-fs'); + var provider_name_input = $('input[name=login_provider_name]'); + var email_input_fields = $('#email-input-fs'); + var account_recovery_heading = $('#account-recovery-heading'); + var account_recovery_hint = $('#account-recovery-form>.hint'); + var account_recovery_link = $('#account-recovery-form>.hint>span.link'); + var account_recovery_text_span = $('#account-recovery-form>.hint>span.text'); + var password_input_fields = $('#password-fs'); + var existing_login_methods_div = $('#existing-login-methods'); + var openid_submit_button = $('input[name=openid_login_with_extra_token]'); + var existing_login_methods = {}; + + var account_recovery_question_text = account_recovery_heading.html(); + var account_recovery_prompt_text = account_recovery_text_span.html(); + + var setup_click_handler = function(elements, handler_function){ + elements.unbind('click').click(handler_function); + }; + + var setup_enter_key_handler = function(elements, handler_function){ + elements.each( + function(index, element){ + $(element).unbind('keypress').keypress( + function(e){ + if ((e.which && e.which == 13)||(e.keyCode && e.keyCode == 13)){ + if (handler_function){ + return handler_function(); + } + else { + element.click(); + return false; + } + } + } + ); + } + ); + }; + + var setup_event_handlers = function(elements, handler_function){ + setup_click_handler(elements, handler_function); + setup_enter_key_handler(elements); + }; + + var get_provider_name = function(row_el){ + var row = $(row_el); + var name_span = row.find('.ab-provider-name'); + return provider_name = $.trim(name_span.html()); + }; + + var read_existing_login_methods = function(){ + $('.ab-provider-row').each( + function(i, provider_row){ + var provider_name = get_provider_name(provider_row); + existing_login_methods[provider_name] = true; + } + ); + }; + + var setup_login_method_deleters = function(){ + $('.ab-provider-row').each( + function(i, provider_row){ + var provider_name = get_provider_name(provider_row); + var remove_button = $( + provider_row + ).find('button'); + remove_button.click( + function(){ + var message = interpolate(gettext('Are you sure you want to remove your %s login?'), [provider_name]); + if (confirm(message)){ + $.ajax({ + type: 'POST', + url: authUrl + 'delete_login_method/',//url!!! + data: {provider_name: provider_name}, + success: function(data, text_status, xhr){ + $(provider_row).remove(); + delete existing_login_methods[provider_name]; + provider_count -=1; + if (provider_count < 0){ + provider_count === 0; + } + if (provider_count === 0){ + $('#ab-existing-login-methods').remove(); + $('#ab-show-login-methods').remove(); + $('h1').html( + gettext("Please add one or more login methods.") + ); + $('#login-intro').html( + gettext("You don\'t have a method to log in right now, please add one or more by clicking any of the icons below.") + ); + existing_login_methods = null; + } + } + }); + } + } + ); + } + ); + } + + var submit_login_with_password = function(){ + var username = $('#id_username'); + var password = $('#id_password'); + + if (username.val().length < 1){ + username.focus(); + return false; + } + if (password.val().length < 1){ + password.focus(); + return false; + } + return true; + }; + + var submit_change_password = function(){ + var newpass = $('#id_new_password'); + var newpass_retyped = $('#id_new_password_retyped'); + if (newpass.val().length < 1){ + newpass.focus(); + return false + } + if (newpass_retyped.val().length < 1){ + newpass_retyped.focus(); + return false; + } + if (newpass.val() !== newpass_retyped.val()){ + newpass_retyped.after( + '' + + gettext('passwords do not match') + + '' + ); + newpass.val('').focus(); + newpass_retyped.val(''); + return false; + } + return true; + }; + + //validator, may be extended to check url for openid + var submit_with_extra_openid_token = function() { + if (openid_login_token_input.val().length < 1) { + openid_login_token_input.focus(); + return false; + } + return true; + }; + + var insert_login_list_enabler = function(){ + var enabler = $('#login-list-enabler'); + if (enabler.is('p#login-list-enabler')){ + enabler.show(); + } + else { + enabler = $( + '

      ' + + gettext('Show/change current login methods') + + '

      '); + setup_event_handlers( + enabler, + function(){ + if (askbot['settings']['signin_always_show_local_login'] === false){ + password_input_fields.hide(); + } + openid_login_token_input_fields.hide(); + enabler.hide(); + existing_login_methods_div.show(); + } + ); + existing_login_methods_div.after(enabler); + } + }; + + var reset_password_input_fields = function(){ + if (userIsAuthenticated){ + $('#id_new_password').val(''); + $('#id_new_password_retyped').val(''); + } + else { + $('#id_username').val(''); + $('#id_password').val(''); + } + }; + + var reset_form = function(){ + openid_login_token_input_fields.hide(); + if (askbot['settings']['signin_always_show_local_login'] === false){ + password_input_fields.hide(); + } + reset_password_input_fields(); + if (userIsAuthenticated === false){ + email_input_fields.hide(); + account_recovery_heading.hide(); + account_recovery_link.show(); + account_recovery_hint.show(); + $('#account-recovery-form>p.hint').css('margin-top','10px'); + account_recovery_text_span.html(account_recovery_question_text).show(); + } + else { + if (existing_login_methods !== null){ + existing_login_methods_div.hide(); + insert_login_list_enabler(); + } + } + }; + + var reset_form_and_errors = function(){ + reset_form(); + $('.error').remove(); + } + + var set_provider_name = function(element){ + var provider_name = element.attr('name'); + provider_name_input.val(provider_name); + }; + + var show_openid_input_fields = function(provider_name){ + reset_form_and_errors(); + var token_name = extra_token_name[provider_name] + if (userIsAuthenticated){ + $('#openid-heading').html( + interpolate(gettext('Please enter your %s, then proceed'), [token_name]) + ); + var button_text = gettext('Connect your %(provider_name)s account to %(site)s'); + var data = { + provider_name: provider_name, + site: siteName + } + button_text = interpolate(button_text, data, true); + openid_submit_button.val(button_text); + } + else { + $('#openid-heading>span').html(token_name); + } + openid_login_token_input_fields.show(); + openid_login_token_input.focus(); + }; + + var start_simple_login = function() { + //$('#openid_form .providers td').removeClass('highlight'); + //$li.addClass('highlight'); + set_provider_name($(this)); + signin_form.submit(); + return true; + }; + + var start_login_with_extra_openid_token = function() { + show_openid_input_fields($(this).attr('name')); + set_provider_name($(this)); + + setup_enter_key_handler( + openid_login_token_input, + function(){ + openid_submit_button.click(); + return false; + } + ); + + setup_event_handlers( + openid_submit_button, + function(){ + signin_form.unbind( + 'submit' + ).submit( + submit_with_extra_openid_token + ); + } + ); + return false; + }; + + var start_facebook_login = function(){ + set_provider_name($(this)); + if (typeof FB != 'undefined'){ + FB.getLoginStatus(function(response){ + if (response.authResponse){ + signin_form.submit(); + } + else { + if (FB.getAuthResponse()){ + signin_form.submit(); + } + FB.login(); + } + }); + } + return false; + }; + + var start_password_login_or_change = function(){ + //called upon clicking on one of the password login buttons + reset_form_and_errors(); + set_provider_name($(this)); + var provider_name = $(this).attr('name'); + return setup_password_login_or_change(provider_name); + }; + + var init_always_visible_password_login = function(){ + reset_form(); + //will break wordpress and ldap + provider_name_input.val('local'); + setup_password_login_or_change('local'); + }; + + var setup_password_login_or_change = function(provider_name){ + var token_name = extra_token_name[provider_name] + var password_action_input = $('input[name=password_action]'); + if (userIsAuthenticated === true){ + var password_button = $('input[name=change_password]'); + var submit_action = submit_change_password; + if (provider_name === 'local'){ + var provider_cleaned_name = siteName; + } + else { + var provider_cleaned_name = provider_name; + } + if (existing_login_methods && existing_login_methods[provider_name]){ + var password_heading_text = interpolate(gettext('Change your %s password'), [provider_cleaned_name]) + var password_button_text = gettext('Change password') + } + else { + var password_heading_text = interpolate(gettext('Create a password for %s'), [provider_cleaned_name]) + var password_button_text = gettext('Create password') + } + $('#password-heading').html( + password_heading_text + ) + password_button.val(password_button_text); + password_action_input.val('change_password'); + var focus_input = $('#id_new_password'); + var submittable_input = $('#id_new_password_retyped'); + } + else{ + $('#password-heading>span').html(token_name); + var password_button = $('input[name=login_with_password]'); + var submit_action = submit_login_with_password; + var create_pw_link = $('a.create-password-account') + if (create_pw_link.length > 0){ + create_pw_link.html(gettext('Create a password-protected account')); + var url = create_pw_link.attr('href'); + if (url.indexOf('?') !== -1){ + url = url.replace(/\?.*$/,'?login_provider=' + provider_name); + } + else{ + url += '?login_provider=' + provider_name; + } + create_pw_link.attr('href', url); + } + password_action_input.val('login'); + var focus_input = $('#id_username'); + var submittable_input = $('#id_password'); + } + password_input_fields.show(); + focus_input.focus(); + + var submit_password_login = function(){ + signin_form.unbind('submit').submit(submit_action); + }; + + setup_enter_key_handler( + submittable_input, + function() { + password_button.click(); + return false; + } + ); + setup_event_handlers(password_button, submit_password_login); + return false; + }; + + var start_account_recovery = function(){ + reset_form_and_errors(); + account_recovery_hint.hide(); + account_recovery_heading.css('margin-bottom', '0px'); + account_recovery_heading.html(account_recovery_prompt_text).show(); + email_input_fields.show(); + $('#id_email').focus(); + }; + + var clear_password_fields = function(){ + $('#id_password').val(''); + $('#id_new_password').val(''); + $('#id_new_password_retyped').val(''); + }; + + var setup_default_handlers = function(){ + setup_event_handlers( + signin_page.find('input.openid-direct'), + start_simple_login + ); + + setup_event_handlers( + signin_page.find('input.openid-username'), + start_login_with_extra_openid_token + ); + + setup_event_handlers( + signin_page.find('input.openid-generic'), + start_login_with_extra_openid_token + ); + + setup_event_handlers( + signin_page.find('input.facebook'), + start_facebook_login + ); + + setup_event_handlers( + signin_page.find('input.oauth'), + start_simple_login + ); + + setup_event_handlers( + signin_page.find('input.password'), + start_password_login_or_change + ); + setup_event_handlers( + signin_page.find('input.wordpress_site'), + start_password_login_or_change + ); + + setup_event_handlers(account_recovery_link, start_account_recovery); + + if (userIsAuthenticated){ + read_existing_login_methods(); + setup_login_method_deleters(); + } + }; + + setup_default_handlers(); + if (askbot['settings']['signin_always_show_local_login'] === true){ + init_always_visible_password_login(); + } + clear_password_fields(); + return this; +}; diff --git a/lms/askbot/skins/common/media/jquery-openid/openid.css b/lms/askbot/skins/common/media/jquery-openid/openid.css new file mode 100644 index 0000000000..0028722442 --- /dev/null +++ b/lms/askbot/skins/common/media/jquery-openid/openid.css @@ -0,0 +1,39 @@ +div#login-icons {margin:10px 0 0 0;padding:10px;border:#eee 1px solid;} +ul.login-icons {width: 450px; margin:0;padding:0;text-align:left; list-style-type:none; display:block;} +ul.login-icons li {display:inline;} +ul.large input {height: 40px; width: 90px;border:1px solid #ccc;margin:0 5px 5px 0;} +.openid-signin h2 {margin-top:15px;} +.openid-signin h2#account-recovery-heading {margin-bottom:2px;} +#account-recovery-form p.hint a {color:#1b79bd; text-decoration: none;} +#account-recovery-form p.hint a:hover {text-decoration: underline;} +.openid-signin fieldset { border-style:none;margin:0;padding:0;} +.openid-signin p {margin:0;padding:0}; +.openid-signin p.hint {color: #555;} +.openid-signin #password-fs label {width:100px;margin-top:5px;text-align:left;} +.openid-signin #password-fs .hint {margin-bottom:5px} +#password-fs a {padding-left:5px;} +/*#signin-form #account-recovery-form input {cursor:pointer;} +#signin-form #account-recovery-form input.text {cursor:default;}*/ + +table.login { text-align: right;} + +.openid-signin .submit-b { + cursor: pointer; /*letter-spacing:1px;*/ + margin: 0 0 2px 0; + vertical-align: middle; +} + +.openid-signin .highlight { -moz-border-radius:4px; -webkit-border-radius:4px; background-color: #FD6} + +ul.providers { + display: block; +} + +.openid-signin th { + color: #555; + font-weight: normal; +} + +.openid-signin .ab-provider-name { + font-weight: bold; +} diff --git a/lms/askbot/skins/common/media/js/autocompleter.js b/lms/askbot/skins/common/media/js/autocompleter.js new file mode 100644 index 0000000000..a7c5431592 --- /dev/null +++ b/lms/askbot/skins/common/media/js/autocompleter.js @@ -0,0 +1,766 @@ +/** + * AutoCompleter Object, refactored closure style from + * jQuery autocomplete plugin + * @param {Object=} options Settings + * @constructor + */ +var AutoCompleter = function(options) { + + /** + * Default options for autocomplete plugin + */ + var defaults = { + autocompleteMultiple: true, + multipleSeparator: ' ',//a single character + inputClass: 'acInput', + loadingClass: 'acLoading', + resultsClass: 'acResults', + selectClass: 'acSelect', + queryParamName: 'q', + limitParamName: 'limit', + extraParams: {}, + lineSeparator: '\n', + cellSeparator: '|', + minChars: 2, + maxItemsToShow: 10, + delay: 400, + useCache: true, + maxCacheLength: 10, + matchSubset: true, + matchCase: false, + matchInside: true, + mustMatch: false, + preloadData: false, + selectFirst: false, + stopCharRegex: /\s+/, + selectOnly: false, + formatItem: null, // TBD + onItemSelect: false, + autoFill: false, + filterResults: true, + sortResults: true, + sortFunction: false, + onNoMatch: false + }; + + /** + * Options dictionary + * @type Object + * @private + */ + this.options = $.extend({}, defaults, options); + + /** + * Cached data + * @type Object + * @private + */ + this.cacheData_ = {}; + + /** + * Number of cached data items + * @type number + * @private + */ + this.cacheLength_ = 0; + + /** + * Class name to mark selected item + * @type string + * @private + */ + this.selectClass_ = 'jquery-autocomplete-selected-item'; + + /** + * Handler to activation timeout + * @type ?number + * @private + */ + this.keyTimeout_ = null; + + /** + * Last key pressed in the input field (store for behavior) + * @type ?number + * @private + */ + this.lastKeyPressed_ = null; + + /** + * Last value processed by the autocompleter + * @type ?string + * @private + */ + this.lastProcessedValue_ = null; + + /** + * Last value selected by the user + * @type ?string + * @private + */ + this.lastSelectedValue_ = null; + + /** + * Is this autocompleter active? + * @type boolean + * @private + */ + this.active_ = false; + + /** + * Is it OK to finish on blur? + * @type boolean + * @private + */ + this.finishOnBlur_ = true; + + this.options.minChars = parseInt(this.options.minChars, 10); + if (isNaN(this.options.minChars) || this.options.minChars < 1) { + this.options.minChars = 2; + } + + this.options.maxItemsToShow = parseInt(this.options.maxItemsToShow, 10); + if (isNaN(this.options.maxItemsToShow) || this.options.maxItemsToShow < 1) { + this.options.maxItemsToShow = 10; + } + + this.options.maxCacheLength = parseInt(this.options.maxCacheLength, 10); + if (isNaN(this.options.maxCacheLength) || this.options.maxCacheLength < 1) { + this.options.maxCacheLength = 10; + } + + if (this.options['preloadData'] === true){ + this.fetchRemoteData('', function(){}); + } +}; +inherits(AutoCompleter, WrappedElement); + +AutoCompleter.prototype.decorate = function(element){ + + /** + * Init DOM elements repository + */ + this._element = element; + + /** + * Switch off the native autocomplete + */ + this._element.attr('autocomplete', 'off'); + + /** + * Create DOM element to hold results + */ + this._results = $('
      ').hide(); + if (this.options.resultsClass) { + this._results.addClass(this.options.resultsClass); + } + this._results.css({ + position: 'absolute' + }); + $('body').append(this._results); + + this.setEventHandlers(); +}; + +AutoCompleter.prototype.setEventHandlers = function(){ + /** + * Shortcut to self + */ + var self = this; + + /** + * Attach keyboard monitoring to $elem + */ + self._element.keydown(function(e) { + self.lastKeyPressed_ = e.keyCode; + switch(self.lastKeyPressed_) { + + case 38: // up + e.preventDefault(); + if (self.active_) { + self.focusPrev(); + } else { + self.activate(); + } + return false; + break; + + case 40: // down + e.preventDefault(); + if (self.active_) { + self.focusNext(); + } else { + self.activate(); + } + return false; + break; + + case 9: // tab + case 13: // return + if (self.active_) { + e.preventDefault(); + self.selectCurrent(); + return false; + } + break; + + case 27: // escape + if (self.active_) { + e.preventDefault(); + self.finish(); + return false; + } + break; + + default: + self.activate(); + + } + }); + self._element.blur(function() { + if (self.finishOnBlur_) { + setTimeout(function() { self.finish(); }, 200); + } + }); +}; + +AutoCompleter.prototype.position = function() { + var offset = this._element.offset(); + this._results.css({ + top: offset.top + this._element.outerHeight(), + left: offset.left + }); +}; + +AutoCompleter.prototype.cacheRead = function(filter) { + var filterLength, searchLength, search, maxPos, pos; + if (this.options.useCache) { + filter = String(filter); + filterLength = filter.length; + if (this.options.matchSubset) { + searchLength = 1; + } else { + searchLength = filterLength; + } + while (searchLength <= filterLength) { + if (this.options.matchInside) { + maxPos = filterLength - searchLength; + } else { + maxPos = 0; + } + pos = 0; + while (pos <= maxPos) { + search = filter.substr(0, searchLength); + if (this.cacheData_[search] !== undefined) { + return this.cacheData_[search]; + } + pos++; + } + searchLength++; + } + } + return false; +}; + +AutoCompleter.prototype.cacheWrite = function(filter, data) { + if (this.options.useCache) { + if (this.cacheLength_ >= this.options.maxCacheLength) { + this.cacheFlush(); + } + filter = String(filter); + if (this.cacheData_[filter] !== undefined) { + this.cacheLength_++; + } + return this.cacheData_[filter] = data; + } + return false; +}; + +AutoCompleter.prototype.cacheFlush = function() { + this.cacheData_ = {}; + this.cacheLength_ = 0; +}; + +AutoCompleter.prototype.callHook = function(hook, data) { + var f = this.options[hook]; + if (f && $.isFunction(f)) { + return f(data, this); + } + return false; +}; + +AutoCompleter.prototype.activate = function() { + var self = this; + var activateNow = function() { + self.activateNow(); + }; + var delay = parseInt(this.options.delay, 10); + if (isNaN(delay) || delay <= 0) { + delay = 250; + } + if (this.keyTimeout_) { + clearTimeout(this.keyTimeout_); + } + this.keyTimeout_ = setTimeout(activateNow, delay); +}; + +AutoCompleter.prototype.activateNow = function() { + var value = this.getValue(); + if (value !== this.lastProcessedValue_ && value !== this.lastSelectedValue_) { + if (value.length >= this.options.minChars) { + this.active_ = true; + this.lastProcessedValue_ = value; + this.fetchData(value); + } + } +}; + +AutoCompleter.prototype.fetchData = function(value) { + if (this.options.data) { + this.filterAndShowResults(this.options.data, value); + } else { + var self = this; + this.fetchRemoteData(value, function(remoteData) { + self.filterAndShowResults(remoteData, value); + }); + } +}; + +AutoCompleter.prototype.fetchRemoteData = function(filter, callback) { + var data = this.cacheRead(filter); + if (data) { + callback(data); + } else { + var self = this; + if (this._element){ + this._element.addClass(this.options.loadingClass); + } + var ajaxCallback = function(data) { + var parsed = false; + if (data !== false) { + parsed = self.parseRemoteData(data); + self.options.data = parsed;//cache data forever - E.F. + self.cacheWrite(filter, parsed); + } + if (self._element){ + self._element.removeClass(self.options.loadingClass); + } + callback(parsed); + }; + $.ajax({ + url: this.makeUrl(filter), + success: ajaxCallback, + error: function() { + ajaxCallback(false); + } + }); + } +}; + +AutoCompleter.prototype.setOption = function(name, value){ + this.options[name] = value; +}; + +AutoCompleter.prototype.setExtraParam = function(name, value) { + var index = $.trim(String(name)); + if (index) { + if (!this.options.extraParams) { + this.options.extraParams = {}; + } + if (this.options.extraParams[index] !== value) { + this.options.extraParams[index] = value; + this.cacheFlush(); + } + } +}; + +AutoCompleter.prototype.makeUrl = function(param) { + var self = this; + var url = this.options.url; + var params = $.extend({}, this.options.extraParams); + // If options.queryParamName === false, append query to url + // instead of using a GET parameter + if (this.options.queryParamName === false) { + url += encodeURIComponent(param); + } else { + params[this.options.queryParamName] = param; + } + + if (this.options.limitParamName && this.options.maxItemsToShow) { + params[this.options.limitParamName] = this.options.maxItemsToShow; + } + + var urlAppend = []; + $.each(params, function(index, value) { + urlAppend.push(self.makeUrlParam(index, value)); + }); + if (urlAppend.length) { + url += url.indexOf('?') == -1 ? '?' : '&'; + url += urlAppend.join('&'); + } + return url; +}; + +AutoCompleter.prototype.makeUrlParam = function(name, value) { + return String(name) + '=' + encodeURIComponent(value); +}; + +/** + * Sanitize CR and LF, then split into lines + */ +AutoCompleter.prototype.splitText = function(text) { + return String(text).replace(/(\r\n|\r|\n)/g, '\n').split(this.options.lineSeparator); +}; + +AutoCompleter.prototype.parseRemoteData = function(remoteData) { + var value, lines, i, j, data; + var results = []; + var lines = this.splitText(remoteData); + for (i = 0; i < lines.length; i++) { + var line = lines[i].split(this.options.cellSeparator); + data = []; + for (j = 0; j < line.length; j++) { + data.push(unescape(line[j])); + } + value = data.shift(); + results.push({ value: unescape(value), data: data }); + } + return results; +}; + +AutoCompleter.prototype.filterAndShowResults = function(results, filter) { + this.showResults(this.filterResults(results, filter), filter); +}; + +AutoCompleter.prototype.filterResults = function(results, filter) { + + var filtered = []; + var value, data, i, result, type, include; + var regex, pattern, testValue; + + for (i = 0; i < results.length; i++) { + result = results[i]; + type = typeof result; + if (type === 'string') { + value = result; + data = {}; + } else if ($.isArray(result)) { + value = result[0]; + data = result.slice(1); + } else if (type === 'object') { + value = result.value; + data = result.data; + } + value = String(value); + if (value > '') { + if (typeof data !== 'object') { + data = {}; + } + if (this.options.filterResults) { + pattern = String(filter); + testValue = String(value); + if (!this.options.matchCase) { + pattern = pattern.toLowerCase(); + testValue = testValue.toLowerCase(); + } + include = testValue.indexOf(pattern); + if (this.options.matchInside) { + include = include > -1; + } else { + include = include === 0; + } + } else { + include = true; + } + if (include) { + filtered.push({ value: value, data: data }); + } + } + } + + if (this.options.sortResults) { + filtered = this.sortResults(filtered, filter); + } + + if (this.options.maxItemsToShow > 0 && this.options.maxItemsToShow < filtered.length) { + filtered.length = this.options.maxItemsToShow; + } + + return filtered; + +}; + +AutoCompleter.prototype.sortResults = function(results, filter) { + var self = this; + var sortFunction = this.options.sortFunction; + if (!$.isFunction(sortFunction)) { + sortFunction = function(a, b, f) { + return self.sortValueAlpha(a, b, f); + }; + } + results.sort(function(a, b) { + return sortFunction(a, b, filter); + }); + return results; +}; + +AutoCompleter.prototype.sortValueAlpha = function(a, b, filter) { + a = String(a.value); + b = String(b.value); + if (!this.options.matchCase) { + a = a.toLowerCase(); + b = b.toLowerCase(); + } + if (a > b) { + return 1; + } + if (a < b) { + return -1; + } + return 0; +}; + +AutoCompleter.prototype.showResults = function(results, filter) { + var self = this; + var $ul = $('
        '); + var i, result, $li, extraWidth, first = false, $first = false; + var numResults = results.length; + for (i = 0; i < numResults; i++) { + result = results[i]; + $li = $('
      • ' + this.showResult(result.value, result.data) + '
      • '); + $li.data('value', result.value); + $li.data('data', result.data); + $li.click(function() { + var $this = $(this); + self.selectItem($this); + }).mousedown(function() { + self.finishOnBlur_ = false; + }).mouseup(function() { + self.finishOnBlur_ = true; + }); + $ul.append($li); + if (first === false) { + first = String(result.value); + $first = $li; + $li.addClass(this.options.firstItemClass); + } + if (i == numResults - 1) { + $li.addClass(this.options.lastItemClass); + } + } + + // Alway recalculate position before showing since window size or + // input element location may have changed. This fixes #14 + this.position(); + + this._results.html($ul).show(); + extraWidth = this._results.outerWidth() - this._results.width(); + this._results.width(this._element.outerWidth() - extraWidth); + $('li', this._results).hover( + function() { self.focusItem(this); }, + function() { /* void */ } + ); + if (this.autoFill(first, filter)) { + this.focusItem($first); + } +}; + +AutoCompleter.prototype.showResult = function(value, data) { + if ($.isFunction(this.options.showResult)) { + return this.options.showResult(value, data); + } else { + return value; + } +}; + +AutoCompleter.prototype.autoFill = function(value, filter) { + var lcValue, lcFilter, valueLength, filterLength; + if (this.options.autoFill && this.lastKeyPressed_ != 8) { + lcValue = String(value).toLowerCase(); + lcFilter = String(filter).toLowerCase(); + valueLength = value.length; + filterLength = filter.length; + if (lcValue.substr(0, filterLength) === lcFilter) { + this._element.val(value); + this.selectRange(filterLength, valueLength); + return true; + } + } + return false; +}; + +AutoCompleter.prototype.focusNext = function() { + this.focusMove(+1); +}; + +AutoCompleter.prototype.focusPrev = function() { + this.focusMove(-1); +}; + +AutoCompleter.prototype.focusMove = function(modifier) { + var i, $items = $('li', this._results); + modifier = parseInt(modifier, 10); + for (var i = 0; i < $items.length; i++) { + if ($($items[i]).hasClass(this.selectClass_)) { + this.focusItem(i + modifier); + return; + } + } + this.focusItem(0); +}; + +AutoCompleter.prototype.focusItem = function(item) { + var $item, $items = $('li', this._results); + if ($items.length) { + $items.removeClass(this.selectClass_).removeClass(this.options.selectClass); + if (typeof item === 'number') { + item = parseInt(item, 10); + if (item < 0) { + item = 0; + } else if (item >= $items.length) { + item = $items.length - 1; + } + $item = $($items[item]); + } else { + $item = $(item); + } + if ($item) { + $item.addClass(this.selectClass_).addClass(this.options.selectClass); + } + } +}; + +AutoCompleter.prototype.selectCurrent = function() { + var $item = $('li.' + this.selectClass_, this._results); + if ($item.length == 1) { + this.selectItem($item); + } else { + this.finish(); + } +}; + +AutoCompleter.prototype.selectItem = function($li) { + var value = $li.data('value'); + var data = $li.data('data'); + var displayValue = this.displayValue(value, data); + this.lastProcessedValue_ = displayValue; + this.lastSelectedValue_ = displayValue; + + this.setValue(displayValue); + + this.setCaret(displayValue.length); + this.callHook('onItemSelect', { value: value, data: data }); + this.finish(); +}; + +/** + * @return {boolean} true if the symbol matches something that is + * considered content and false otherwise + * @param {string} symbol - a single char string + */ +AutoCompleter.prototype.isContentChar = function(symbol){ + if (symbol.match(this.options['stopCharRegex'])){ + return false; + } else if (symbol === this.options['multipleSeparator']){ + return false; + } else { + return true; + } +}; + +/** + * takes value from the input box + * and saves _selection_start and _selection_end coordinates + * respects settings autocompleteMultiple and + * multipleSeparator + * @return {string} the current word in the + * autocompletable word + */ +AutoCompleter.prototype.getValue = function(){ + var sel = this._element.getSelection(); + var text = this._element.val(); + var pos = sel.start;//estimated start + //find real start + var start = pos; + for (cpos = pos; cpos >= 0; cpos = cpos - 1){ + if (cpos === text.length){ + continue; + } + var symbol = text.charAt(cpos); + if (!this.isContentChar(symbol)){ + break; + } + start = cpos; + } + //find real end + var end = pos; + for (cpos = pos; cpos < text.length; cpos = cpos + 1){ + if (cpos === 0){ + continue; + } + var symbol = text.charAt(cpos); + if (!this.isContentChar(symbol)){ + break; + } + end = cpos; + } + this._selection_start = start; + this._selection_end = end; + return text.substring(start, end); +} + +/** + * sets value of the input box + * by replacing the previous selection + * with the value from the autocompleter + */ +AutoCompleter.prototype.setValue = function(val){ + var prefix = this._element.val().substring(0, this._selection_start); + var postfix = this._element.val().substring(this._selection_end + 1); + this._element.val(prefix + val + postfix); +}; + +AutoCompleter.prototype.displayValue = function(value, data) { + if ($.isFunction(this.options.displayValue)) { + return this.options.displayValue(value, data); + } else { + return value; + } +}; + +AutoCompleter.prototype.finish = function() { + if (this.keyTimeout_) { + clearTimeout(this.keyTimeout_); + } + if (this._element.val() !== this.lastSelectedValue_) { + if (this.options.mustMatch) { + this._element.val(''); + } + this.callHook('onNoMatch'); + } + this._results.hide(); + this.lastKeyPressed_ = null; + this.lastProcessedValue_ = null; + if (this.active_) { + this.callHook('onFinish'); + } + this.active_ = false; +}; + +AutoCompleter.prototype.selectRange = function(start, end) { + var input = this._element.get(0); + if (input.setSelectionRange) { + input.focus(); + input.setSelectionRange(start, end); + } else if (this.createTextRange) { + var range = this.createTextRange(); + range.collapse(true); + range.moveEnd('character', end); + range.moveStart('character', start); + range.select(); + } +}; + +AutoCompleter.prototype.setCaret = function(pos) { + this.selectRange(pos, pos); +}; + diff --git a/lms/askbot/skins/common/media/js/compress.bat b/lms/askbot/skins/common/media/js/compress.bat new file mode 100644 index 0000000000..53d72588d2 --- /dev/null +++ b/lms/askbot/skins/common/media/js/compress.bat @@ -0,0 +1,5 @@ +#java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 wmd\wmd.js -o wmd\wmd-min.js +#java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 wmd\showdown.js -o wmd\showdown-min.js +#java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 post.js -o post.pack.js +java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 se_hilite_src.js -o se_hilite.js +pause diff --git a/lms/askbot/skins/common/media/js/editor.js b/lms/askbot/skins/common/media/js/editor.js new file mode 100644 index 0000000000..2d1f567079 --- /dev/null +++ b/lms/askbot/skins/common/media/js/editor.js @@ -0,0 +1,75 @@ +/* + jQuery TextAreaResizer plugin + Created on 17th January 2008 by Ryan O'Dell + Version 1.0.4 +*/(function($){var textarea,staticOffset;var iLastMousePos=0;var iMin=32;var grip;$.fn.TextAreaResizer=function(){return this.each(function(){textarea=$(this).addClass('processed'),staticOffset=null;$(this).wrap('
        ').parent().append($('
        ').bind("mousedown",{el:this},startDrag));var grippie=$('div.grippie',$(this).parent())[0];grippie.style.marginRight=(grippie.offsetWidth-$(this)[0].offsetWidth)+'px'})};function startDrag(e){textarea=$(e.data.el);textarea.blur();iLastMousePos=mousePosition(e).y;staticOffset=textarea.height()-iLastMousePos;textarea.css('opacity',0.25);$(document).mousemove(performDrag).mouseup(endDrag);return false}function performDrag(e){var iThisMousePos=mousePosition(e).y;var iMousePos=staticOffset+iThisMousePos;if(iLastMousePos>=(iThisMousePos)){iMousePos-=5}iLastMousePos=iThisMousePos;iMousePos=Math.max(iMin,iMousePos);textarea.height(iMousePos+'px');if(iMousePosoptions.captureLength&&elTxt.toUpperCase()!=timer.text)||(override&&elTxt.length>options.captureLength)){timer.text=elTxt.toUpperCase();timer.cb(elTxt)}};function watchElement(elem){if(elem.type.toUpperCase()=="TEXT"||elem.nodeName.toUpperCase()=="TEXTAREA"){var timer={timer:null,text:jQuery(elem).val().toUpperCase(),cb:options.callback,el:elem,wait:options.wait};if(options.highlight){jQuery(elem).focus(function(){this.select()})}var startWatch=function(evt){var timerWait=timer.wait;var overrideBool=false;if(evt.keyCode==13&&this.type.toUpperCase()=="TEXT"){timerWait=1;overrideBool=true}var timerCallbackFx=function(){checkElement(timer,overrideBool)};clearTimeout(timer.timer);timer.timer=setTimeout(timerCallbackFx,timerWait)};jQuery(elem).keydown(startWatch)}};return this.each(function(index){watchElement(this)})}})(jQuery); +/* +Ajax upload +*/jQuery.extend({createUploadIframe:function(d,b){var a="jUploadFrame"+d;if(window.ActiveXObject){var c=document.createElement('