Merge branch 'master' into feature/abarrett/annotatable_xmodule
1
.gitignore
vendored
@@ -27,4 +27,5 @@ lms/lib/comment_client/python
|
||||
nosetests.xml
|
||||
cover_html/
|
||||
.idea/
|
||||
.redcar/
|
||||
chromedriver.log
|
||||
BIN
.redcar/lucene/segments.gen
Normal file
1
.redcar/lucene_last_updated
Normal file
@@ -0,0 +1 @@
|
||||
1360614836
|
||||
1
.redcar/redcar.lock
Normal file
@@ -0,0 +1 @@
|
||||
10664: Locked by 10664 at Mon Feb 11 14:22:22 -0500 2013
|
||||
4
.redcar/storage/cursor_saver.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
cursor_positions: []
|
||||
|
||||
files_to_retain: 0
|
||||
1
.redcar/tags.REMOVED.git-id
Normal file
@@ -0,0 +1 @@
|
||||
ce76efcea5f0a5b2238364f81d54f1d393853a1a
|
||||
@@ -5,7 +5,7 @@ from django.test.utils import override_settings
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from path import path
|
||||
from tempfile import mkdtemp
|
||||
from tempdir import mkdtemp_clean
|
||||
import json
|
||||
from fs.osfs import OSFS
|
||||
import copy
|
||||
@@ -194,7 +194,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
import_from_xml(ms, 'common/test/data/', ['full'])
|
||||
location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
|
||||
|
||||
root_dir = path(mkdtemp())
|
||||
root_dir = path(mkdtemp_clean())
|
||||
|
||||
print 'Exporting to tempdir = {0}'.format(root_dir)
|
||||
|
||||
@@ -264,6 +264,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf')
|
||||
|
||||
|
||||
|
||||
class ContentStoreTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the CMS ContentStore application.
|
||||
@@ -421,6 +422,64 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
self.assertIn('markdown', problem.metadata, "markdown is missing from metadata")
|
||||
self.assertNotIn('markdown', problem.editable_metadata_fields, "Markdown slipped into the editable metadata fields")
|
||||
|
||||
def test_import_metadata_with_attempts_empty_string(self):
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['simple'])
|
||||
ms = modulestore('direct')
|
||||
did_load_item = False
|
||||
try:
|
||||
ms.get_item(Location(['i4x', 'edX', 'simple', 'problem', 'ps01-simple', None]))
|
||||
did_load_item = True
|
||||
except ItemNotFoundError:
|
||||
pass
|
||||
|
||||
# make sure we found the item (e.g. it didn't error while loading)
|
||||
self.assertTrue(did_load_item)
|
||||
|
||||
def test_metadata_inheritance(self):
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||
|
||||
ms = modulestore('direct')
|
||||
course = ms.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))
|
||||
|
||||
verticals = ms.get_items(['i4x', 'edX', 'full', 'vertical', None, None])
|
||||
|
||||
# let's assert on the metadata_inheritance on an existing vertical
|
||||
for vertical in verticals:
|
||||
self.assertIn('xqa_key', vertical.metadata)
|
||||
self.assertEqual(course.metadata['xqa_key'], vertical.metadata['xqa_key'])
|
||||
|
||||
self.assertGreater(len(verticals), 0)
|
||||
|
||||
new_component_location = Location('i4x', 'edX', 'full', 'html', 'new_component')
|
||||
source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Blank_HTML_Page')
|
||||
|
||||
# crate a new module and add it as a child to a vertical
|
||||
ms.clone_item(source_template_location, new_component_location)
|
||||
parent = verticals[0]
|
||||
ms.update_children(parent.location, parent.definition.get('children', []) + [new_component_location.url()])
|
||||
|
||||
# flush the cache
|
||||
ms.get_cached_metadata_inheritance_tree(new_component_location, -1)
|
||||
new_module = ms.get_item(new_component_location)
|
||||
|
||||
# check for grace period definition which should be defined at the course level
|
||||
self.assertIn('graceperiod', new_module.metadata)
|
||||
|
||||
self.assertEqual(course.metadata['graceperiod'], new_module.metadata['graceperiod'])
|
||||
|
||||
#
|
||||
# now let's define an override at the leaf node level
|
||||
#
|
||||
new_module.metadata['graceperiod'] = '1 day'
|
||||
ms.update_metadata(new_module.location, new_module.metadata)
|
||||
|
||||
# flush the cache and refetch
|
||||
ms.get_cached_metadata_inheritance_tree(new_component_location, -1)
|
||||
new_module = ms.get_item(new_component_location)
|
||||
|
||||
self.assertIn('graceperiod', new_module.metadata)
|
||||
self.assertEqual('1 day', new_module.metadata['graceperiod'])
|
||||
|
||||
|
||||
class TemplateTestCase(ModuleStoreTestCase):
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.test.client import Client
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from path import path
|
||||
from tempfile import mkdtemp
|
||||
import json
|
||||
from fs.osfs import OSFS
|
||||
import copy
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import json
|
||||
import copy
|
||||
from time import time
|
||||
from uuid import uuid4
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
|
||||
@@ -20,13 +20,12 @@ class ModuleStoreTestCase(TestCase):
|
||||
def _pre_setup(self):
|
||||
super(ModuleStoreTestCase, self)._pre_setup()
|
||||
|
||||
# Use the current seconds since epoch to differentiate
|
||||
# Use a uuid to differentiate
|
||||
# the mongo collections on jenkins.
|
||||
sec_since_epoch = '%s' % int(time() * 100)
|
||||
self.orig_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
|
||||
self.test_MODULESTORE = self.orig_MODULESTORE
|
||||
self.test_MODULESTORE['default']['OPTIONS']['collection'] = 'modulestore_%s' % sec_since_epoch
|
||||
self.test_MODULESTORE['direct']['OPTIONS']['collection'] = 'modulestore_%s' % sec_since_epoch
|
||||
self.test_MODULESTORE['default']['OPTIONS']['collection'] = 'modulestore_%s' % uuid4().hex
|
||||
self.test_MODULESTORE['direct']['OPTIONS']['collection'] = 'modulestore_%s' % uuid4().hex
|
||||
settings.MODULESTORE = self.test_MODULESTORE
|
||||
|
||||
# Flush and initialize the module store
|
||||
|
||||
@@ -20,7 +20,6 @@ Longer TODO:
|
||||
"""
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
import os.path
|
||||
import os
|
||||
import lms.envs.common
|
||||
@@ -59,7 +58,8 @@ sys.path.append(COMMON_ROOT / 'lib')
|
||||
|
||||
############################# WEB CONFIGURATION #############################
|
||||
# This is where we stick our compiled template files.
|
||||
MAKO_MODULE_DIR = tempfile.mkdtemp('mako')
|
||||
from tempdir import mkdtemp_clean
|
||||
MAKO_MODULE_DIR = mkdtemp_clean('mako')
|
||||
MAKO_TEMPLATES = {}
|
||||
MAKO_TEMPLATES['main'] = [
|
||||
PROJECT_ROOT / 'templates',
|
||||
|
||||
@@ -68,10 +68,10 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
|
||||
save_videosource: function(newsource) {
|
||||
// newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string
|
||||
// returns the videosource for the preview which iss the key whose speed is closest to 1
|
||||
if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.set({'intro_video': null});
|
||||
if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.save({'intro_video': null});
|
||||
// TODO remove all whitespace w/in string
|
||||
else {
|
||||
if (this.get('intro_video') !== newsource) this.set('intro_video', newsource);
|
||||
if (this.get('intro_video') !== newsource) this.save('intro_video', newsource);
|
||||
}
|
||||
|
||||
return this.videosourceSample();
|
||||
|
||||
@@ -498,6 +498,7 @@ input.courseware-unit-search-input {
|
||||
}
|
||||
|
||||
&.new-section {
|
||||
|
||||
header {
|
||||
height: auto;
|
||||
@include clearfix();
|
||||
@@ -506,6 +507,15 @@ input.courseware-unit-search-input {
|
||||
.expand-collapse-icon {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.item-details {
|
||||
padding: 25px 0 0 0;
|
||||
|
||||
.section-name {
|
||||
float: none;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ forums, and to the cohort admin views.
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import Http404
|
||||
import logging
|
||||
import random
|
||||
|
||||
from courseware import courses
|
||||
from student.models import get_user_by_username_or_email
|
||||
@@ -64,7 +65,23 @@ def is_commentable_cohorted(course_id, commentable_id):
|
||||
ans))
|
||||
return ans
|
||||
|
||||
|
||||
def get_cohorted_commentables(course_id):
|
||||
"""
|
||||
Given a course_id return a list of strings representing cohorted commentables
|
||||
"""
|
||||
|
||||
course = courses.get_course_by_id(course_id)
|
||||
|
||||
if not course.is_cohorted:
|
||||
# this is the easy case :)
|
||||
ans = []
|
||||
else:
|
||||
ans = course.cohorted_discussions
|
||||
|
||||
return ans
|
||||
|
||||
|
||||
def get_cohort(user, course_id):
|
||||
"""
|
||||
Given a django User and a course_id, return the user's cohort in that
|
||||
@@ -96,9 +113,30 @@ def get_cohort(user, course_id):
|
||||
group_type=CourseUserGroup.COHORT,
|
||||
users__id=user.id)
|
||||
except CourseUserGroup.DoesNotExist:
|
||||
# TODO: add auto-cohorting logic here once we know what that will be.
|
||||
# Didn't find the group. We'll go on to create one if needed.
|
||||
pass
|
||||
|
||||
if not course.auto_cohort:
|
||||
return None
|
||||
|
||||
choices = course.auto_cohort_groups
|
||||
if len(choices) == 0:
|
||||
# Nowhere to put user
|
||||
log.warning("Course %s is auto-cohorted, but there are no"
|
||||
" auto_cohort_groups specified",
|
||||
course_id)
|
||||
return None
|
||||
|
||||
# Put user in a random group, creating it if needed
|
||||
group_name = random.choice(choices)
|
||||
group, created = CourseUserGroup.objects.get_or_create(
|
||||
course_id=course_id,
|
||||
group_type=CourseUserGroup.COHORT,
|
||||
name=group_name)
|
||||
|
||||
user.course_groups.add(group)
|
||||
return group
|
||||
|
||||
|
||||
def get_course_cohorts(course_id):
|
||||
"""
|
||||
|
||||
@@ -47,7 +47,10 @@ class TestCohorts(django.test.TestCase):
|
||||
|
||||
@staticmethod
|
||||
def config_course_cohorts(course, discussions,
|
||||
cohorted, cohorted_discussions=None):
|
||||
cohorted,
|
||||
cohorted_discussions=None,
|
||||
auto_cohort=None,
|
||||
auto_cohort_groups=None):
|
||||
"""
|
||||
Given a course with no discussion set up, add the discussions and set
|
||||
the cohort config appropriately.
|
||||
@@ -59,6 +62,9 @@ class TestCohorts(django.test.TestCase):
|
||||
cohorted: bool.
|
||||
cohorted_discussions: optional list of topic names. If specified,
|
||||
converts them to use the same ids as topic names.
|
||||
auto_cohort: optional bool.
|
||||
auto_cohort_groups: optional list of strings
|
||||
(names of groups to put students into).
|
||||
|
||||
Returns:
|
||||
Nothing -- modifies course in place.
|
||||
@@ -76,6 +82,12 @@ class TestCohorts(django.test.TestCase):
|
||||
if cohorted_discussions is not None:
|
||||
d["cohorted_discussions"] = [to_id(name)
|
||||
for name in cohorted_discussions]
|
||||
|
||||
if auto_cohort is not None:
|
||||
d["auto_cohort"] = auto_cohort
|
||||
if auto_cohort_groups is not None:
|
||||
d["auto_cohort_groups"] = auto_cohort_groups
|
||||
|
||||
course.metadata["cohort_config"] = d
|
||||
|
||||
|
||||
@@ -89,12 +101,9 @@ class TestCohorts(django.test.TestCase):
|
||||
|
||||
|
||||
def test_get_cohort(self):
|
||||
# Need to fix this, but after we're testing on staging. (Looks like
|
||||
# problem is that when get_cohort internally tries to look up the
|
||||
# course.id, it fails, even though we loaded it through the modulestore.
|
||||
|
||||
# Proper fix: give all tests a standard modulestore that uses the test
|
||||
# dir.
|
||||
"""
|
||||
Make sure get_cohort() does the right thing when the course is cohorted
|
||||
"""
|
||||
course = modulestore().get_course("edX/toy/2012_Fall")
|
||||
self.assertEqual(course.id, "edX/toy/2012_Fall")
|
||||
self.assertFalse(course.is_cohorted)
|
||||
@@ -122,6 +131,54 @@ class TestCohorts(django.test.TestCase):
|
||||
self.assertEquals(get_cohort(other_user, course.id), None,
|
||||
"other_user shouldn't have a cohort")
|
||||
|
||||
def test_auto_cohorting(self):
|
||||
"""
|
||||
Make sure get_cohort() does the right thing when the course is auto_cohorted
|
||||
"""
|
||||
course = modulestore().get_course("edX/toy/2012_Fall")
|
||||
self.assertEqual(course.id, "edX/toy/2012_Fall")
|
||||
self.assertFalse(course.is_cohorted)
|
||||
|
||||
user1 = User.objects.create(username="test", email="a@b.com")
|
||||
user2 = User.objects.create(username="test2", email="a2@b.com")
|
||||
user3 = User.objects.create(username="test3", email="a3@b.com")
|
||||
|
||||
cohort = CourseUserGroup.objects.create(name="TestCohort",
|
||||
course_id=course.id,
|
||||
group_type=CourseUserGroup.COHORT)
|
||||
|
||||
# user1 manually added to a cohort
|
||||
cohort.users.add(user1)
|
||||
|
||||
# Make the course auto cohorted...
|
||||
self.config_course_cohorts(course, [], cohorted=True,
|
||||
auto_cohort=True,
|
||||
auto_cohort_groups=["AutoGroup"])
|
||||
|
||||
self.assertEquals(get_cohort(user1, course.id).id, cohort.id,
|
||||
"user1 should stay put")
|
||||
|
||||
self.assertEquals(get_cohort(user2, course.id).name, "AutoGroup",
|
||||
"user2 should be auto-cohorted")
|
||||
|
||||
# Now make the group list empty
|
||||
self.config_course_cohorts(course, [], cohorted=True,
|
||||
auto_cohort=True,
|
||||
auto_cohort_groups=[])
|
||||
|
||||
self.assertEquals(get_cohort(user3, course.id), None,
|
||||
"No groups->no auto-cohorting")
|
||||
|
||||
# Now make it different
|
||||
self.config_course_cohorts(course, [], cohorted=True,
|
||||
auto_cohort=True,
|
||||
auto_cohort_groups=["OtherGroup"])
|
||||
|
||||
self.assertEquals(get_cohort(user3, course.id).name, "OtherGroup",
|
||||
"New list->new group")
|
||||
self.assertEquals(get_cohort(user2, course.id).name, "AutoGroup",
|
||||
"user2 should still be in originally placed cohort")
|
||||
|
||||
|
||||
def test_get_course_cohorts(self):
|
||||
course1_id = 'a/b/c'
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.template.loaders.app_directories import Loader as AppDirectoriesLoad
|
||||
from mitxmako.template import Template
|
||||
import mitxmako.middleware
|
||||
|
||||
import tempdir
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -30,7 +31,7 @@ class MakoLoader(object):
|
||||
|
||||
if module_directory is None:
|
||||
log.warning("For more caching of mako templates, set the MAKO_MODULE_DIR in settings!")
|
||||
module_directory = tempfile.mkdtemp()
|
||||
module_directory = tempdir.mkdtemp_clean()
|
||||
|
||||
self.module_directory = module_directory
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
from mako.lookup import TemplateLookup
|
||||
import tempfile
|
||||
import tempdir
|
||||
from django.template import RequestContext
|
||||
from django.conf import settings
|
||||
|
||||
@@ -29,7 +29,7 @@ class MakoMiddleware(object):
|
||||
module_directory = getattr(settings, 'MAKO_MODULE_DIR', None)
|
||||
|
||||
if module_directory is None:
|
||||
module_directory = tempfile.mkdtemp()
|
||||
module_directory = tempdir.mkdtemp_clean()
|
||||
|
||||
for location in template_locations:
|
||||
lookup[location] = TemplateLookup(directories=template_locations[location],
|
||||
|
||||
@@ -7,6 +7,7 @@ import logging
|
||||
import os
|
||||
from tempfile import mkdtemp
|
||||
import cStringIO
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from django.test import TestCase
|
||||
@@ -143,23 +144,18 @@ class PearsonTestCase(TestCase):
|
||||
'''
|
||||
Base class for tests running Pearson-related commands
|
||||
'''
|
||||
import_dir = mkdtemp(prefix="import")
|
||||
export_dir = mkdtemp(prefix="export")
|
||||
|
||||
def assertErrorContains(self, error_message, expected):
|
||||
self.assertTrue(error_message.find(expected) >= 0, 'error message "{}" did not contain "{}"'.format(error_message, expected))
|
||||
|
||||
def setUp(self):
|
||||
self.import_dir = mkdtemp(prefix="import")
|
||||
self.addCleanup(shutil.rmtree, self.import_dir)
|
||||
self.export_dir = mkdtemp(prefix="export")
|
||||
self.addCleanup(shutil.rmtree, self.export_dir)
|
||||
|
||||
def tearDown(self):
|
||||
def delete_temp_dir(dirname):
|
||||
if os.path.exists(dirname):
|
||||
for filename in os.listdir(dirname):
|
||||
os.remove(os.path.join(dirname, filename))
|
||||
os.rmdir(dirname)
|
||||
|
||||
# clean up after any test data was dumped to temp directory
|
||||
delete_temp_dir(self.import_dir)
|
||||
delete_temp_dir(self.export_dir)
|
||||
|
||||
pass
|
||||
# and clean up the database:
|
||||
# TestCenterUser.objects.all().delete()
|
||||
# TestCenterRegistration.objects.all().delete()
|
||||
|
||||
@@ -111,7 +111,7 @@ class DragAndDrop(object):
|
||||
Returns: bool.
|
||||
'''
|
||||
for draggable in self.excess_draggables:
|
||||
if not self.excess_draggables[draggable]:
|
||||
if self.excess_draggables[draggable]:
|
||||
return False # user answer has more draggables than correct answer
|
||||
|
||||
# Number of draggables in user_groups may be differ that in
|
||||
@@ -304,8 +304,13 @@ class DragAndDrop(object):
|
||||
|
||||
user_answer = json.loads(user_answer)
|
||||
|
||||
# check if we have draggables that are not in correct answer:
|
||||
self.excess_draggables = {}
|
||||
# This dictionary will hold a key for each draggable the user placed on
|
||||
# the image. The value is True if that draggable is not mentioned in any
|
||||
# correct_answer entries. If the draggable is mentioned in at least one
|
||||
# correct_answer entry, the value is False.
|
||||
# default to consider every user answer excess until proven otherwise.
|
||||
self.excess_draggables = dict((users_draggable.keys()[0],True)
|
||||
for users_draggable in user_answer['draggables'])
|
||||
|
||||
# create identical data structures from user answer and correct answer
|
||||
for i in xrange(0, len(correct_answer)):
|
||||
@@ -322,11 +327,8 @@ class DragAndDrop(object):
|
||||
self.user_groups[groupname].append(draggable_name)
|
||||
self.user_positions[groupname]['user'].append(
|
||||
draggable_dict[draggable_name])
|
||||
self.excess_draggables[draggable_name] = True
|
||||
else:
|
||||
self.excess_draggables[draggable_name] = \
|
||||
self.excess_draggables.get(draggable_name, False)
|
||||
|
||||
# proved that this is not excess
|
||||
self.excess_draggables[draggable_name] = False
|
||||
|
||||
def grade(user_input, correct_answer):
|
||||
""" Creates DragAndDrop instance from user_input and correct_answer and
|
||||
|
||||
@@ -46,6 +46,18 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
|
||||
correct_answer = {'1': 't1', 'name_with_icon': 't2'}
|
||||
self.assertTrue(draganddrop.grade(user_input, correct_answer))
|
||||
|
||||
def test_expect_no_actions_wrong(self):
|
||||
user_input = '{"draggables": [{"1": "t1"}, \
|
||||
{"name_with_icon": "t2"}]}'
|
||||
correct_answer = []
|
||||
self.assertFalse(draganddrop.grade(user_input, correct_answer))
|
||||
|
||||
def test_expect_no_actions_right(self):
|
||||
user_input = '{"draggables": []}'
|
||||
correct_answer = []
|
||||
self.assertTrue(draganddrop.grade(user_input, correct_answer))
|
||||
|
||||
|
||||
def test_targets_false(self):
|
||||
user_input = '{"draggables": [{"1": "t1"}, \
|
||||
{"name_with_icon": "t2"}]}'
|
||||
|
||||
17
common/lib/tempdir.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Make temporary directories nicely."""
|
||||
|
||||
import atexit
|
||||
import os.path
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
def mkdtemp_clean(suffix="", prefix="tmp", dir=None):
|
||||
"""Just like mkdtemp, but the directory will be deleted when the process ends."""
|
||||
the_dir = tempfile.mkdtemp(suffix=suffix, prefix=prefix, dir=dir)
|
||||
atexit.register(cleanup_tempdir, the_dir)
|
||||
return the_dir
|
||||
|
||||
def cleanup_tempdir(the_dir):
|
||||
"""Called on process exit to remove a temp directory."""
|
||||
if os.path.exists(the_dir):
|
||||
shutil.rmtree(the_dir)
|
||||
@@ -429,6 +429,11 @@ class CapaModule(XModule):
|
||||
# used by conditional module
|
||||
return self.attempts > 0
|
||||
|
||||
def is_correct(self):
|
||||
"""True if full points"""
|
||||
d = self.get_score()
|
||||
return d['score'] == d['total']
|
||||
|
||||
def answer_available(self):
|
||||
'''
|
||||
Is the user allowed to see an answer?
|
||||
@@ -449,6 +454,9 @@ class CapaModule(XModule):
|
||||
return self.lcp.done
|
||||
elif self.show_answer == 'closed':
|
||||
return self.closed()
|
||||
elif self.show_answer == 'finished':
|
||||
return self.closed() or self.is_correct()
|
||||
|
||||
elif self.show_answer == 'past_due':
|
||||
return self.is_past_due()
|
||||
elif self.show_answer == 'always':
|
||||
|
||||
@@ -108,11 +108,13 @@ class CombinedOpenEndedModule(XModule):
|
||||
instance_state = {}
|
||||
|
||||
self.version = self.metadata.get('version', DEFAULT_VERSION)
|
||||
version_error_string = "Version of combined open ended module {0} is not correct. Going with version {1}"
|
||||
if not isinstance(self.version, basestring):
|
||||
try:
|
||||
self.version = str(self.version)
|
||||
except:
|
||||
log.error("Version {0} is not correct. Going with version {1}".format(self.version, DEFAULT_VERSION))
|
||||
#This is a dev_facing_error
|
||||
log.info(version_error_string.format(self.version, DEFAULT_VERSION))
|
||||
self.version = DEFAULT_VERSION
|
||||
|
||||
versions = [i[0] for i in VERSION_TUPLES]
|
||||
@@ -122,7 +124,8 @@ class CombinedOpenEndedModule(XModule):
|
||||
try:
|
||||
version_index = versions.index(self.version)
|
||||
except:
|
||||
log.error("Version {0} is not correct. Going with version {1}".format(self.version, DEFAULT_VERSION))
|
||||
#This is a dev_facing_error
|
||||
log.error(version_error_string.format(self.version, DEFAULT_VERSION))
|
||||
self.version = DEFAULT_VERSION
|
||||
version_index = versions.index(self.version)
|
||||
|
||||
@@ -205,4 +208,4 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
|
||||
for child in ['task']:
|
||||
add_child(child)
|
||||
|
||||
return elt
|
||||
return elt
|
||||
|
||||
@@ -352,6 +352,13 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
"""
|
||||
return self.metadata.get('tabs')
|
||||
|
||||
@property
|
||||
def pdf_textbooks(self):
|
||||
"""
|
||||
Return the pdf_textbooks config, as a python object, or None if not specified.
|
||||
"""
|
||||
return self.metadata.get('pdf_textbooks')
|
||||
|
||||
@tabs.setter
|
||||
def tabs(self, value):
|
||||
self.metadata['tabs'] = value
|
||||
@@ -371,6 +378,28 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
|
||||
return bool(config.get("cohorted"))
|
||||
|
||||
@property
|
||||
def auto_cohort(self):
|
||||
"""
|
||||
Return whether the course is auto-cohorted.
|
||||
"""
|
||||
if not self.is_cohorted:
|
||||
return False
|
||||
|
||||
return bool(self.metadata.get("cohort_config", {}).get(
|
||||
"auto_cohort", False))
|
||||
|
||||
@property
|
||||
def auto_cohort_groups(self):
|
||||
"""
|
||||
Return the list of groups to put students into. Returns [] if not
|
||||
specified. Returns specified list even if is_cohorted and/or auto_cohort are
|
||||
false.
|
||||
"""
|
||||
return self.metadata.get("cohort_config", {}).get(
|
||||
"auto_cohort_groups", [])
|
||||
|
||||
|
||||
@property
|
||||
def top_level_discussion_topic_ids(self):
|
||||
"""
|
||||
@@ -707,7 +736,7 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
def get_test_center_exam(self, exam_series_code):
|
||||
exams = [exam for exam in self.test_center_exams if exam.exam_series_code == exam_series_code]
|
||||
return exams[0] if len(exams) == 1 else None
|
||||
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self.display_name
|
||||
|
||||
@@ -1,6 +1,61 @@
|
||||
class @Rubric
|
||||
constructor: () ->
|
||||
|
||||
@initialize: (location) ->
|
||||
$('.rubric').data("location", location)
|
||||
$('input[class="score-selection"]').change @tracking_callback
|
||||
# set up the hotkeys
|
||||
$(window).unbind('keydown', @keypress_callback)
|
||||
$(window).keydown @keypress_callback
|
||||
# display the 'current' carat
|
||||
@categories = $('.rubric-category')
|
||||
@category = $(@categories.first())
|
||||
@category.prepend('> ')
|
||||
@category_index = 0
|
||||
|
||||
|
||||
@keypress_callback: (event) =>
|
||||
# don't try to do this when user is typing in a text input
|
||||
if $(event.target).is('input, textarea')
|
||||
return
|
||||
# for when we select via top row
|
||||
if event.which >= 48 and event.which <= 57
|
||||
selected = event.which - 48
|
||||
# for when we select via numpad
|
||||
else if event.which >= 96 and event.which <= 105
|
||||
selected = event.which - 96
|
||||
# we don't want to do anything since we haven't pressed a number
|
||||
else
|
||||
return
|
||||
|
||||
# if we actually have a current category (not past the end)
|
||||
if(@category_index <= @categories.length)
|
||||
# find the valid selections for this category
|
||||
inputs = $("input[name='score-selection-#{@category_index}']")
|
||||
max_score = inputs.length - 1
|
||||
|
||||
if selected > max_score or selected < 0
|
||||
return
|
||||
inputs.filter("input[value=#{selected}]").click()
|
||||
|
||||
# move to the next category
|
||||
old_category_text = @category.html().substring(5)
|
||||
@category.html(old_category_text)
|
||||
@category_index++
|
||||
@category = $(@categories[@category_index])
|
||||
@category.prepend('> ')
|
||||
|
||||
@tracking_callback: (event) ->
|
||||
target_selection = $(event.target).val()
|
||||
# chop off the beginning of the name so that we can get the number of the category
|
||||
category = $(event.target).data("category")
|
||||
location = $('.rubric').data('location')
|
||||
# probably want the original problem location as well
|
||||
|
||||
data = {location: location, selection: target_selection, category: category}
|
||||
Logger.log 'rubric_select', data
|
||||
|
||||
|
||||
# finds the scores for each rubric category
|
||||
@get_score_list: () =>
|
||||
# find the number of categories:
|
||||
@@ -34,6 +89,7 @@ class @CombinedOpenEnded
|
||||
constructor: (element) ->
|
||||
@element=element
|
||||
@reinitialize(element)
|
||||
$(window).keydown @keydown_handler
|
||||
|
||||
reinitialize: (element) ->
|
||||
@wrapper=$(element).find('section.xmodule_CombinedOpenEndedModule')
|
||||
@@ -45,6 +101,9 @@ class @CombinedOpenEnded
|
||||
@task_count = @el.data('task-count')
|
||||
@task_number = @el.data('task-number')
|
||||
@accept_file_upload = @el.data('accept-file-upload')
|
||||
@location = @el.data('location')
|
||||
# set up handlers for click tracking
|
||||
Rubric.initialize(@location)
|
||||
|
||||
@allow_reset = @el.data('allow_reset')
|
||||
@reset_button = @$('.reset-button')
|
||||
@@ -89,6 +148,8 @@ class @CombinedOpenEnded
|
||||
@can_upload_files = false
|
||||
@open_ended_child= @$('.open-ended-child')
|
||||
|
||||
@out_of_sync_message = 'The problem state got out of sync. Try reloading the page.'
|
||||
|
||||
if @task_number>1
|
||||
@prompt_hide()
|
||||
else if @task_number==1 and @child_state!='initial'
|
||||
@@ -116,6 +177,9 @@ class @CombinedOpenEnded
|
||||
@submit_evaluation_button = $('.submit-evaluation-button')
|
||||
@submit_evaluation_button.click @message_post
|
||||
Collapsible.setCollapsibles(@results_container)
|
||||
# make sure we still have click tracking
|
||||
$('.evaluation-response a').click @log_feedback_click
|
||||
$('input[name="evaluation-score"]').change @log_feedback_selection
|
||||
|
||||
show_results: (event) =>
|
||||
status_item = $(event.target).parent()
|
||||
@@ -153,7 +217,6 @@ class @CombinedOpenEnded
|
||||
@legend_container= $('.legend-container')
|
||||
|
||||
message_post: (event)=>
|
||||
Logger.log 'message_post', @answers
|
||||
external_grader_message=$(event.target).parent().parent().parent()
|
||||
evaluation_scoring = $(event.target).parent()
|
||||
|
||||
@@ -182,6 +245,7 @@ class @CombinedOpenEnded
|
||||
$('section.evaluation').slideToggle()
|
||||
@message_wrapper.html(response.message_html)
|
||||
|
||||
|
||||
$.ajaxWithPrefix("#{@ajax_url}/save_post_assessment", settings)
|
||||
|
||||
|
||||
@@ -283,6 +347,7 @@ class @CombinedOpenEnded
|
||||
if response.success
|
||||
@rubric_wrapper.html(response.rubric_html)
|
||||
@rubric_wrapper.show()
|
||||
Rubric.initialize(@location)
|
||||
@answer_area.html(response.student_response)
|
||||
@child_state = 'assessing'
|
||||
@find_assessment_elements()
|
||||
@@ -293,7 +358,12 @@ class @CombinedOpenEnded
|
||||
$.ajaxWithPrefix("#{@ajax_url}/save_answer",settings)
|
||||
|
||||
else
|
||||
@errors_area.html('Problem state got out of sync. Try reloading the page.')
|
||||
@errors_area.html(@out_of_sync_message)
|
||||
|
||||
keydown_handler: (e) =>
|
||||
# only do anything when the key pressed is the 'enter' key
|
||||
if e.which == 13 && @child_state == 'assessing' && Rubric.check_complete()
|
||||
@save_assessment(e)
|
||||
|
||||
save_assessment: (event) =>
|
||||
event.preventDefault()
|
||||
@@ -315,7 +385,7 @@ class @CombinedOpenEnded
|
||||
else
|
||||
@errors_area.html(response.error)
|
||||
else
|
||||
@errors_area.html('Problem state got out of sync. Try reloading the page.')
|
||||
@errors_area.html(@out_of_sync_message)
|
||||
|
||||
save_hint: (event) =>
|
||||
event.preventDefault()
|
||||
@@ -330,7 +400,7 @@ class @CombinedOpenEnded
|
||||
else
|
||||
@errors_area.html(response.error)
|
||||
else
|
||||
@errors_area.html('Problem state got out of sync. Try reloading the page.')
|
||||
@errors_area.html(@out_of_sync_message)
|
||||
|
||||
skip_post_assessment: =>
|
||||
if @child_state == 'post_assessment'
|
||||
@@ -342,7 +412,7 @@ class @CombinedOpenEnded
|
||||
else
|
||||
@errors_area.html(response.error)
|
||||
else
|
||||
@errors_area.html('Problem state got out of sync. Try reloading the page.')
|
||||
@errors_area.html(@out_of_sync_message)
|
||||
|
||||
reset: (event) =>
|
||||
event.preventDefault()
|
||||
@@ -362,7 +432,7 @@ class @CombinedOpenEnded
|
||||
else
|
||||
@errors_area.html(response.error)
|
||||
else
|
||||
@errors_area.html('Problem state got out of sync. Try reloading the page.')
|
||||
@errors_area.html(@out_of_sync_message)
|
||||
|
||||
next_problem: =>
|
||||
if @child_state == 'done'
|
||||
@@ -385,7 +455,7 @@ class @CombinedOpenEnded
|
||||
else
|
||||
@errors_area.html(response.error)
|
||||
else
|
||||
@errors_area.html('Problem state got out of sync. Try reloading the page.')
|
||||
@errors_area.html(@out_of_sync_message)
|
||||
|
||||
gentle_alert: (msg) =>
|
||||
if @el.find('.open-ended-alert').length
|
||||
@@ -404,7 +474,7 @@ class @CombinedOpenEnded
|
||||
$.postWithPrefix "#{@ajax_url}/check_for_score", (response) =>
|
||||
if response.state == "done" or response.state=="post_assessment"
|
||||
delete window.queuePollerID
|
||||
location.reload()
|
||||
@reload()
|
||||
else
|
||||
window.queuePollerID = window.setTimeout(@poll, 10000)
|
||||
|
||||
@@ -438,7 +508,9 @@ class @CombinedOpenEnded
|
||||
@prompt_container.toggleClass('open')
|
||||
if @question_header.text() == "(Hide)"
|
||||
new_text = "(Show)"
|
||||
Logger.log 'oe_hide_question', {location: @location}
|
||||
else
|
||||
Logger.log 'oe_show_question', {location: @location}
|
||||
new_text = "(Hide)"
|
||||
@question_header.text(new_text)
|
||||
|
||||
@@ -454,4 +526,16 @@ class @CombinedOpenEnded
|
||||
@prompt_container.toggleClass('open')
|
||||
@question_header.text("(Show)")
|
||||
|
||||
log_feedback_click: (event) ->
|
||||
link_text = $(event.target).html()
|
||||
if link_text == 'See full feedback'
|
||||
Logger.log 'oe_show_full_feedback', {}
|
||||
else if link_text == 'Respond to Feedback'
|
||||
Logger.log 'oe_show_respond_to_feedback', {}
|
||||
else
|
||||
generated_event_type = link_text.toLowerCase().replace(" ","_")
|
||||
Logger.log "oe_" + generated_event_type, {}
|
||||
|
||||
log_feedback_selection: (event) ->
|
||||
target_selection = $(event.target).val()
|
||||
Logger.log 'oe_feedback_response_selected', {value: target_selection}
|
||||
|
||||
@@ -175,6 +175,7 @@ class @PeerGradingProblem
|
||||
@prompt_container = $('.prompt-container')
|
||||
@rubric_container = $('.rubric-container')
|
||||
@flag_student_container = $('.flag-student-container')
|
||||
@answer_unknown_container = $('.answer-unknown-container')
|
||||
@calibration_panel = $('.calibration-panel')
|
||||
@grading_panel = $('.grading-panel')
|
||||
@content_panel = $('.content-panel')
|
||||
@@ -208,6 +209,10 @@ class @PeerGradingProblem
|
||||
@interstitial_page_button = $('.interstitial-page-button')
|
||||
@calibration_interstitial_page_button = $('.calibration-interstitial-page-button')
|
||||
@flag_student_checkbox = $('.flag-checkbox')
|
||||
@answer_unknown_checkbox = $('.answer-unknown-checkbox')
|
||||
|
||||
$(window).keydown @keydown_handler
|
||||
|
||||
@collapse_question()
|
||||
|
||||
Collapsible.setCollapsibles(@content_panel)
|
||||
@@ -249,9 +254,6 @@ class @PeerGradingProblem
|
||||
fetch_submission_essay: () =>
|
||||
@backend.post('get_next_submission', {location: @location}, @render_submission)
|
||||
|
||||
gentle_alert: (msg) =>
|
||||
@grading_message.fadeIn()
|
||||
@grading_message.html("<p>" + msg + "</p>")
|
||||
|
||||
construct_data: () ->
|
||||
data =
|
||||
@@ -262,6 +264,7 @@ class @PeerGradingProblem
|
||||
submission_key: @submission_key_input.val()
|
||||
feedback: @feedback_area.val()
|
||||
submission_flagged: @flag_student_checkbox.is(':checked')
|
||||
answer_unknown: @answer_unknown_checkbox.is(':checked')
|
||||
return data
|
||||
|
||||
|
||||
@@ -334,6 +337,14 @@ class @PeerGradingProblem
|
||||
@show_submit_button()
|
||||
@grade = Rubric.get_total_score()
|
||||
|
||||
keydown_handler: (event) =>
|
||||
if event.which == 13 && @submit_button.is(':visible')
|
||||
if @calibration
|
||||
@submit_calibration_essay()
|
||||
else
|
||||
@submit_grade()
|
||||
|
||||
|
||||
|
||||
|
||||
##########
|
||||
@@ -360,6 +371,8 @@ class @PeerGradingProblem
|
||||
@calibration_panel.find('.grading-text').hide()
|
||||
@grading_panel.find('.grading-text').hide()
|
||||
@flag_student_container.hide()
|
||||
@answer_unknown_container.hide()
|
||||
|
||||
@feedback_area.val("")
|
||||
|
||||
@submit_button.unbind('click')
|
||||
@@ -388,6 +401,7 @@ class @PeerGradingProblem
|
||||
@calibration_panel.find('.grading-text').show()
|
||||
@grading_panel.find('.grading-text').show()
|
||||
@flag_student_container.show()
|
||||
@answer_unknown_container.show()
|
||||
@feedback_area.val("")
|
||||
|
||||
@submit_button.unbind('click')
|
||||
@@ -420,6 +434,7 @@ class @PeerGradingProblem
|
||||
@submit_button.hide()
|
||||
@action_button.hide()
|
||||
@calibration_feedback_panel.hide()
|
||||
Rubric.initialize(@location)
|
||||
|
||||
|
||||
render_calibration_feedback: (response) =>
|
||||
@@ -466,11 +481,17 @@ class @PeerGradingProblem
|
||||
# And now hook up an event handler again
|
||||
$("input[class='score-selection']").change @graded_callback
|
||||
|
||||
gentle_alert: (msg) =>
|
||||
@grading_message.fadeIn()
|
||||
@grading_message.html("<p>" + msg + "</p>")
|
||||
|
||||
collapse_question: () =>
|
||||
@prompt_container.slideToggle()
|
||||
@prompt_container.toggleClass('open')
|
||||
if @question_header.text() == "(Hide)"
|
||||
Logger.log 'peer_grading_hide_question', {location: @location}
|
||||
new_text = "(Show)"
|
||||
else
|
||||
Logger.log 'peer_grading_show_question', {location: @location}
|
||||
new_text = "(Hide)"
|
||||
@question_header.text(new_text)
|
||||
|
||||
@@ -44,5 +44,6 @@ class MakoModuleDescriptor(XModuleDescriptor):
|
||||
# cdodge: encapsulate a means to expose "editable" metadata fields (i.e. not internal system metadata)
|
||||
@property
|
||||
def editable_metadata_fields(self):
|
||||
subset = [name for name in self.metadata.keys() if name not in self.system_metadata_fields]
|
||||
subset = [name for name in self.metadata.keys() if name not in self.system_metadata_fields and
|
||||
name not in self._inherited_metadata]
|
||||
return subset
|
||||
|
||||
@@ -23,6 +23,15 @@ URL_RE = re.compile("""
|
||||
(@(?P<revision>[^/]+))?
|
||||
""", re.VERBOSE)
|
||||
|
||||
MISSING_SLASH_URL_RE = re.compile("""
|
||||
(?P<tag>[^:]+):/
|
||||
(?P<org>[^/]+)/
|
||||
(?P<course>[^/]+)/
|
||||
(?P<category>[^/]+)/
|
||||
(?P<name>[^@]+)
|
||||
(@(?P<revision>[^/]+))?
|
||||
""", re.VERBOSE)
|
||||
|
||||
# TODO (cpennington): We should decide whether we want to expand the
|
||||
# list of valid characters in a location
|
||||
INVALID_CHARS = re.compile(r"[^\w.-]")
|
||||
@@ -164,12 +173,16 @@ class Location(_LocationBase):
|
||||
if isinstance(location, basestring):
|
||||
match = URL_RE.match(location)
|
||||
if match is None:
|
||||
log.debug('location is instance of %s but no URL match' % basestring)
|
||||
raise InvalidLocationError(location)
|
||||
else:
|
||||
groups = match.groupdict()
|
||||
check_dict(groups)
|
||||
return _LocationBase.__new__(_cls, **groups)
|
||||
# cdodge:
|
||||
# check for a dropped slash near the i4x:// element of the location string. This can happen with some
|
||||
# redirects (e.g. edx.org -> www.edx.org which I think happens in Nginx)
|
||||
match = MISSING_SLASH_URL_RE.match(location)
|
||||
if match is None:
|
||||
log.debug('location is instance of %s but no URL match' % basestring)
|
||||
raise InvalidLocationError(location)
|
||||
groups = match.groupdict()
|
||||
check_dict(groups)
|
||||
return _LocationBase.__new__(_cls, **groups)
|
||||
elif isinstance(location, (list, tuple)):
|
||||
if len(location) not in (5, 6):
|
||||
log.debug('location has wrong length')
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import pymongo
|
||||
import sys
|
||||
import logging
|
||||
import copy
|
||||
|
||||
from bson.son import SON
|
||||
from fs.osfs import OSFS
|
||||
from itertools import repeat
|
||||
from path import path
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from importlib import import_module
|
||||
from xmodule.errortracker import null_error_tracker, exc_info_to_str
|
||||
@@ -27,9 +29,11 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
"""
|
||||
A system that has a cache of module json that it will use to load modules
|
||||
from, with a backup of calling to the underlying modulestore for more data
|
||||
TODO (cdodge) when the 'split module store' work has been completed we can remove all
|
||||
references to metadata_inheritance_tree
|
||||
"""
|
||||
def __init__(self, modulestore, module_data, default_class, resources_fs,
|
||||
error_tracker, render_template):
|
||||
error_tracker, render_template, metadata_inheritance_tree = None):
|
||||
"""
|
||||
modulestore: the module store that can be used to retrieve additional modules
|
||||
|
||||
@@ -54,6 +58,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
# cdodge: other Systems have a course_id attribute defined. To keep things consistent, let's
|
||||
# define an attribute here as well, even though it's None
|
||||
self.course_id = None
|
||||
self.metadata_inheritance_tree = metadata_inheritance_tree
|
||||
|
||||
def load_item(self, location):
|
||||
location = Location(location)
|
||||
@@ -61,11 +66,13 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
if json_data is None:
|
||||
return self.modulestore.get_item(location)
|
||||
else:
|
||||
# TODO (vshnayder): metadata inheritance is somewhat broken because mongo, doesn't
|
||||
# always load an entire course. We're punting on this until after launch, and then
|
||||
# will build a proper course policy framework.
|
||||
# load the module and apply the inherited metadata
|
||||
try:
|
||||
return XModuleDescriptor.load_from_json(json_data, self, self.default_class)
|
||||
module = XModuleDescriptor.load_from_json(json_data, self, self.default_class)
|
||||
if self.metadata_inheritance_tree is not None:
|
||||
metadata_to_inherit = self.metadata_inheritance_tree.get('parent_metadata', {}).get(location.url(),{})
|
||||
module.inherit_metadata(metadata_to_inherit)
|
||||
return module
|
||||
except:
|
||||
return ErrorDescriptor.from_json(
|
||||
json_data,
|
||||
@@ -142,6 +149,82 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
self.fs_root = path(fs_root)
|
||||
self.error_tracker = error_tracker
|
||||
self.render_template = render_template
|
||||
self.metadata_inheritance_cache = {}
|
||||
|
||||
def get_metadata_inheritance_tree(self, location):
|
||||
'''
|
||||
TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
|
||||
'''
|
||||
|
||||
# get all collections in the course, this query should not return any leaf nodes
|
||||
query = { '_id.org' : location.org,
|
||||
'_id.course' : location.course,
|
||||
'_id.revision' : None,
|
||||
'definition.children':{'$ne': []}
|
||||
}
|
||||
# we just want the Location, children, and metadata
|
||||
record_filter = {'_id':1,'definition.children':1,'metadata':1}
|
||||
|
||||
# call out to the DB
|
||||
resultset = self.collection.find(query, record_filter)
|
||||
|
||||
results_by_url = {}
|
||||
root = None
|
||||
|
||||
# now go through the results and order them by the location url
|
||||
for result in resultset:
|
||||
location = Location(result['_id'])
|
||||
results_by_url[location.url()] = result
|
||||
if location.category == 'course':
|
||||
root = location.url()
|
||||
|
||||
# now traverse the tree and compute down the inherited metadata
|
||||
metadata_to_inherit = {}
|
||||
def _compute_inherited_metadata(url):
|
||||
my_metadata = results_by_url[url]['metadata']
|
||||
for key in my_metadata.keys():
|
||||
if key not in XModuleDescriptor.inheritable_metadata:
|
||||
del my_metadata[key]
|
||||
results_by_url[url]['metadata'] = my_metadata
|
||||
|
||||
# go through all the children and recurse, but only if we have
|
||||
# in the result set. Remember results will not contain leaf nodes
|
||||
for child in results_by_url[url].get('definition',{}).get('children',[]):
|
||||
if child in results_by_url:
|
||||
new_child_metadata = copy.deepcopy(my_metadata)
|
||||
new_child_metadata.update(results_by_url[child]['metadata'])
|
||||
results_by_url[child]['metadata'] = new_child_metadata
|
||||
metadata_to_inherit[child] = new_child_metadata
|
||||
_compute_inherited_metadata(child)
|
||||
else:
|
||||
# this is likely a leaf node, so let's record what metadata we need to inherit
|
||||
metadata_to_inherit[child] = my_metadata
|
||||
|
||||
if root is not None:
|
||||
_compute_inherited_metadata(root)
|
||||
|
||||
cache = {'parent_metadata': metadata_to_inherit,
|
||||
'timestamp' : datetime.now()}
|
||||
|
||||
return cache
|
||||
|
||||
def get_cached_metadata_inheritance_tree(self, location, max_age_allowed):
|
||||
'''
|
||||
TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
|
||||
'''
|
||||
cache_name = '{0}/{1}'.format(location.org, location.course)
|
||||
cache = self.metadata_inheritance_cache.get(cache_name,{'parent_metadata': {},
|
||||
'timestamp': datetime.now() - timedelta(hours=1)})
|
||||
age = (datetime.now() - cache['timestamp'])
|
||||
|
||||
if age.seconds >= max_age_allowed:
|
||||
logging.debug('loading entire inheritance tree for {0}'.format(cache_name))
|
||||
cache = self.get_metadata_inheritance_tree(location)
|
||||
self.metadata_inheritance_cache[cache_name] = cache
|
||||
|
||||
return cache
|
||||
|
||||
|
||||
|
||||
def _clean_item_data(self, item):
|
||||
"""
|
||||
@@ -196,6 +279,8 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
|
||||
resource_fs = OSFS(root)
|
||||
|
||||
# TODO (cdodge): When the 'split module store' work has been completed, we should remove
|
||||
# the 'metadata_inheritance_tree' parameter
|
||||
system = CachingDescriptorSystem(
|
||||
self,
|
||||
data_cache,
|
||||
@@ -203,6 +288,7 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
resource_fs,
|
||||
self.error_tracker,
|
||||
self.render_template,
|
||||
metadata_inheritance_tree = self.get_cached_metadata_inheritance_tree(Location(item['location']), 60)
|
||||
)
|
||||
return system.load_item(item['location'])
|
||||
|
||||
@@ -261,11 +347,11 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
descendents of the queried modules for more efficient results later
|
||||
in the request. The depth is counted in the number of
|
||||
calls to get_children() to cache. None indicates to cache all descendents.
|
||||
|
||||
"""
|
||||
location = Location.ensure_fully_specified(location)
|
||||
item = self._find_one(location)
|
||||
return self._load_items([item], depth)[0]
|
||||
module = self._load_items([item], depth)[0]
|
||||
return module
|
||||
|
||||
def get_instance(self, course_id, location, depth=0):
|
||||
"""
|
||||
@@ -285,7 +371,8 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
sort=[('revision', pymongo.ASCENDING)],
|
||||
)
|
||||
|
||||
return self._load_items(list(items), depth)
|
||||
modules = self._load_items(list(items), depth)
|
||||
return modules
|
||||
|
||||
def clone_item(self, source, location):
|
||||
"""
|
||||
@@ -313,7 +400,7 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
raise DuplicateItemError(location)
|
||||
|
||||
|
||||
def get_course_for_item(self, location):
|
||||
def get_course_for_item(self, location, depth=0):
|
||||
'''
|
||||
VS[compat]
|
||||
cdodge: for a given Xmodule, return the course that it belongs to
|
||||
@@ -327,7 +414,7 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
# know the 'name' parameter in this context, so we have
|
||||
# to assume there's only one item in this query even though we are not specifying a name
|
||||
course_search_location = ['i4x', location.org, location.course, 'course', None]
|
||||
courses = self.get_items(course_search_location)
|
||||
courses = self.get_items(course_search_location, depth=depth)
|
||||
|
||||
# make sure we found exactly one match on this above course search
|
||||
found_cnt = len(courses)
|
||||
|
||||
@@ -2,8 +2,7 @@ import json
|
||||
import logging
|
||||
from lxml import etree
|
||||
from lxml.html import rewrite_links
|
||||
|
||||
|
||||
from xmodule.timeinfo import TimeInfo
|
||||
from xmodule.capa_module import only_one, ComplexEncoder
|
||||
from xmodule.editing_module import EditingDescriptor
|
||||
from xmodule.html_checker import check_html
|
||||
@@ -14,16 +13,13 @@ from xmodule.xml_module import XmlDescriptor
|
||||
import self_assessment_module
|
||||
import open_ended_module
|
||||
from combined_open_ended_rubric import CombinedOpenEndedRubric, GRADER_TYPE_IMAGE_DICT, HUMAN_GRADER_TYPE, LEGEND_LIST
|
||||
import dateutil
|
||||
import dateutil.parser
|
||||
from xmodule.timeparse import parse_timedelta
|
||||
|
||||
log = logging.getLogger("mitx.courseware")
|
||||
|
||||
# Set the default number of max attempts. Should be 1 for production
|
||||
# Set higher for debugging/testing
|
||||
# attempts specified in xml definition overrides this.
|
||||
MAX_ATTEMPTS = 10000
|
||||
MAX_ATTEMPTS = 1
|
||||
|
||||
# Set maximum available number of points.
|
||||
# Overriden by max_score specified in xml.
|
||||
@@ -48,6 +44,10 @@ HUMAN_TASK_TYPE = {
|
||||
'openended' : "edX Assessment",
|
||||
}
|
||||
|
||||
#Default value that controls whether or not to skip basic spelling checks in the controller
|
||||
#Metadata overrides this
|
||||
SKIP_BASIC_CHECKS = False
|
||||
|
||||
class CombinedOpenEndedV1Module():
|
||||
"""
|
||||
This is a module that encapsulates all open ended grading (self assessment, peer assessment, etc).
|
||||
@@ -146,28 +146,17 @@ class CombinedOpenEndedV1Module():
|
||||
self.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS))
|
||||
self.is_scored = self.metadata.get('is_graded', IS_SCORED) in TRUE_DICT
|
||||
self.accept_file_upload = self.metadata.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT
|
||||
self.skip_basic_checks = self.metadata.get('skip_spelling_checks', SKIP_BASIC_CHECKS)
|
||||
|
||||
display_due_date_string = self.metadata.get('due', None)
|
||||
if display_due_date_string is not None:
|
||||
try:
|
||||
self.display_due_date = dateutil.parser.parse(display_due_date_string)
|
||||
except ValueError:
|
||||
log.error("Could not parse due date {0} for location {1}".format(display_due_date_string, location))
|
||||
raise
|
||||
else:
|
||||
self.display_due_date = None
|
||||
|
||||
|
||||
grace_period_string = self.metadata.get('graceperiod', None)
|
||||
if grace_period_string is not None and self.display_due_date:
|
||||
try:
|
||||
self.grace_period = parse_timedelta(grace_period_string)
|
||||
self.close_date = self.display_due_date + self.grace_period
|
||||
except:
|
||||
log.error("Error parsing the grace period {0} for location {1}".format(grace_period_string, location))
|
||||
raise
|
||||
else:
|
||||
self.grace_period = None
|
||||
self.close_date = self.display_due_date
|
||||
try:
|
||||
self.timeinfo = TimeInfo(display_due_date_string, grace_period_string)
|
||||
except:
|
||||
log.error("Error parsing due date information in location {0}".format(location))
|
||||
raise
|
||||
self.display_due_date = self.timeinfo.display_due_date
|
||||
|
||||
# Used for progress / grading. Currently get credit just for
|
||||
# completion (doesn't matter if you self-assessed correct/incorrect).
|
||||
@@ -185,8 +174,9 @@ class CombinedOpenEndedV1Module():
|
||||
'rubric': definition['rubric'],
|
||||
'display_name': self.display_name,
|
||||
'accept_file_upload': self.accept_file_upload,
|
||||
'close_date' : self.close_date,
|
||||
'close_date' : self.timeinfo.close_date,
|
||||
's3_interface' : self.system.s3_interface,
|
||||
'skip_basic_checks' : self.skip_basic_checks,
|
||||
}
|
||||
|
||||
self.task_xml = definition['task_xml']
|
||||
@@ -340,6 +330,7 @@ class CombinedOpenEndedV1Module():
|
||||
'status': self.get_status(False),
|
||||
'display_name': self.display_name,
|
||||
'accept_file_upload': self.accept_file_upload,
|
||||
'location': self.location,
|
||||
'legend_list' : LEGEND_LIST,
|
||||
}
|
||||
|
||||
@@ -656,7 +647,10 @@ class CombinedOpenEndedV1Module():
|
||||
if self.attempts > self.max_attempts:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Too many attempts.'
|
||||
#This is a student_facing_error
|
||||
'error': ('You have attempted this question {0} times. '
|
||||
'You are only allowed to attempt it {1} times.').format(
|
||||
self.attempts, self.max_attempts)
|
||||
}
|
||||
self.state = self.INITIAL
|
||||
self.allow_reset = False
|
||||
@@ -795,7 +789,8 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor):
|
||||
expected_children = ['task', 'rubric', 'prompt']
|
||||
for child in expected_children:
|
||||
if len(xml_object.xpath(child)) == 0:
|
||||
raise ValueError("Combined Open Ended definition must include at least one '{0}' tag".format(child))
|
||||
#This is a staff_facing_error
|
||||
raise ValueError("Combined Open Ended definition must include at least one '{0}' tag. Contact the learning sciences group for assistance.".format(child))
|
||||
|
||||
def parse_task(k):
|
||||
"""Assumes that xml_object has child k"""
|
||||
@@ -820,4 +815,4 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor):
|
||||
for child in ['task']:
|
||||
add_child(child)
|
||||
|
||||
return elt
|
||||
return elt
|
||||
|
||||
@@ -4,7 +4,6 @@ from lxml import etree
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
GRADER_TYPE_IMAGE_DICT = {
|
||||
'8B' : '/static/images/random_grading_icon.png',
|
||||
'SA' : '/static/images/self_assessment_icon.png',
|
||||
'PE' : '/static/images/peer_grading_icon.png',
|
||||
'ML' : '/static/images/ml_grading_icon.png',
|
||||
@@ -13,7 +12,6 @@ GRADER_TYPE_IMAGE_DICT = {
|
||||
}
|
||||
|
||||
HUMAN_GRADER_TYPE = {
|
||||
'8B' : 'Magic-8-Ball-Assessment',
|
||||
'SA' : 'Self-Assessment',
|
||||
'PE' : 'Peer-Assessment',
|
||||
'IN' : 'Instructor-Assessment',
|
||||
@@ -71,8 +69,9 @@ class CombinedOpenEndedRubric(object):
|
||||
})
|
||||
success = True
|
||||
except:
|
||||
error_message = "[render_rubric] Could not parse the rubric with xml: {0}".format(rubric_xml)
|
||||
log.error(error_message)
|
||||
#This is a staff_facing_error
|
||||
error_message = "[render_rubric] Could not parse the rubric with xml: {0}. Contact the learning sciences group for assistance.".format(rubric_xml)
|
||||
log.exception(error_message)
|
||||
raise RubricParsingError(error_message)
|
||||
return {'success' : success, 'html' : html, 'rubric_scores' : rubric_scores}
|
||||
|
||||
@@ -81,7 +80,8 @@ class CombinedOpenEndedRubric(object):
|
||||
success = rubric_dict['success']
|
||||
rubric_feedback = rubric_dict['html']
|
||||
if not success:
|
||||
error_message = "Could not parse rubric : {0} for location {1}".format(rubric_string, location.url())
|
||||
#This is a staff_facing_error
|
||||
error_message = "Could not parse rubric : {0} for location {1}. Contact the learning sciences group for assistance.".format(rubric_string, location.url())
|
||||
log.error(error_message)
|
||||
raise RubricParsingError(error_message)
|
||||
|
||||
@@ -90,13 +90,15 @@ class CombinedOpenEndedRubric(object):
|
||||
for category in rubric_categories:
|
||||
total = total + len(category['options']) - 1
|
||||
if len(category['options']) > (max_score_allowed + 1):
|
||||
error_message = "Number of score points in rubric {0} higher than the max allowed, which is {1}".format(
|
||||
#This is a staff_facing_error
|
||||
error_message = "Number of score points in rubric {0} higher than the max allowed, which is {1}. Contact the learning sciences group for assistance.".format(
|
||||
len(category['options']), max_score_allowed)
|
||||
log.error(error_message)
|
||||
raise RubricParsingError(error_message)
|
||||
|
||||
if total != max_score:
|
||||
error_msg = "The max score {0} for problem {1} does not match the total number of points in the rubric {2}".format(
|
||||
#This is a staff_facing_error
|
||||
error_msg = "The max score {0} for problem {1} does not match the total number of points in the rubric {2}. Contact the learning sciences group for assistance.".format(
|
||||
max_score, location, total)
|
||||
log.error(error_msg)
|
||||
raise RubricParsingError(error_msg)
|
||||
@@ -118,7 +120,8 @@ class CombinedOpenEndedRubric(object):
|
||||
categories = []
|
||||
for category in element:
|
||||
if category.tag != 'category':
|
||||
raise RubricParsingError("[extract_categories] Expected a <category> tag: got {0} instead".format(category.tag))
|
||||
#This is a staff_facing_error
|
||||
raise RubricParsingError("[extract_categories] Expected a <category> tag: got {0} instead. Contact the learning sciences group for assistance.".format(category.tag))
|
||||
else:
|
||||
categories.append(self.extract_category(category))
|
||||
return categories
|
||||
@@ -144,12 +147,14 @@ class CombinedOpenEndedRubric(object):
|
||||
self.has_score = True
|
||||
# if we are missing the score tag and we are expecting one
|
||||
elif self.has_score:
|
||||
raise RubricParsingError("[extract_category] Category {0} is missing a score".format(descriptionxml.text))
|
||||
#This is a staff_facing_error
|
||||
raise RubricParsingError("[extract_category] Category {0} is missing a score. Contact the learning sciences group for assistance.".format(descriptionxml.text))
|
||||
|
||||
|
||||
# parse description
|
||||
if descriptionxml.tag != 'description':
|
||||
raise RubricParsingError("[extract_category]: expected description tag, got {0} instead".format(descriptionxml.tag))
|
||||
#This is a staff_facing_error
|
||||
raise RubricParsingError("[extract_category]: expected description tag, got {0} instead. Contact the learning sciences group for assistance.".format(descriptionxml.tag))
|
||||
|
||||
description = descriptionxml.text
|
||||
|
||||
@@ -159,7 +164,8 @@ class CombinedOpenEndedRubric(object):
|
||||
# parse options
|
||||
for option in optionsxml:
|
||||
if option.tag != 'option':
|
||||
raise RubricParsingError("[extract_category]: expected option tag, got {0} instead".format(option.tag))
|
||||
#This is a staff_facing_error
|
||||
raise RubricParsingError("[extract_category]: expected option tag, got {0} instead. Contact the learning sciences group for assistance.".format(option.tag))
|
||||
else:
|
||||
pointstr = option.get("points")
|
||||
if pointstr:
|
||||
@@ -168,7 +174,8 @@ class CombinedOpenEndedRubric(object):
|
||||
try:
|
||||
points = int(pointstr)
|
||||
except ValueError:
|
||||
raise RubricParsingError("[extract_category]: expected points to have int, got {0} instead".format(pointstr))
|
||||
#This is a staff_facing_error
|
||||
raise RubricParsingError("[extract_category]: expected points to have int, got {0} instead. Contact the learning sciences group for assistance.".format(pointstr))
|
||||
elif autonumbering:
|
||||
# use the generated one if we're in the right mode
|
||||
points = cur_points
|
||||
@@ -200,7 +207,6 @@ class CombinedOpenEndedRubric(object):
|
||||
for grader_type in tuple[3]:
|
||||
rubric_categories[i]['options'][j]['grader_types'].append(grader_type)
|
||||
|
||||
log.debug(rubric_categories)
|
||||
html = self.system.render_template('open_ended_combined_rubric.html',
|
||||
{'categories': rubric_categories,
|
||||
'has_score': True,
|
||||
@@ -219,13 +225,15 @@ class CombinedOpenEndedRubric(object):
|
||||
Validates a set of options. This can and should be extended to filter out other bad edge cases
|
||||
'''
|
||||
if len(options) == 0:
|
||||
raise RubricParsingError("[extract_category]: no options associated with this category")
|
||||
#This is a staff_facing_error
|
||||
raise RubricParsingError("[extract_category]: no options associated with this category. Contact the learning sciences group for assistance.")
|
||||
if len(options) == 1:
|
||||
return
|
||||
prev = options[0]['points']
|
||||
for option in options[1:]:
|
||||
if prev == option['points']:
|
||||
raise RubricParsingError("[extract_category]: found duplicate point values between two different options")
|
||||
#This is a staff_facing_error
|
||||
raise RubricParsingError("[extract_category]: found duplicate point values between two different options. Contact the learning sciences group for assistance.")
|
||||
else:
|
||||
prev = option['points']
|
||||
|
||||
@@ -241,11 +249,14 @@ class CombinedOpenEndedRubric(object):
|
||||
"""
|
||||
success = False
|
||||
if len(scores)==0:
|
||||
log.error("Score length is 0.")
|
||||
#This is a dev_facing_error
|
||||
log.error("Score length is 0 when trying to reformat rubric scores for rendering.")
|
||||
return success, ""
|
||||
|
||||
if len(scores) != len(score_types) or len(feedback_types) != len(scores):
|
||||
log.error("Length mismatches.")
|
||||
#This is a dev_facing_error
|
||||
log.error("Length mismatches when trying to reformat rubric scores for rendering. "
|
||||
"Scores: {0}, Score Types: {1} Feedback Types: {2}".format(scores, score_types, feedback_types))
|
||||
return success, ""
|
||||
|
||||
score_lists = []
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import logging
|
||||
from xmodule.open_ended_grading_classes.grading_service_module import GradingService
|
||||
|
||||
from xmodule.x_module import ModuleSystem
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
from grading_service_module import GradingService
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -11,8 +8,8 @@ class ControllerQueryService(GradingService):
|
||||
"""
|
||||
Interface to staff grading backend.
|
||||
"""
|
||||
def __init__(self, config):
|
||||
config['system'] = ModuleSystem(None, None, None, render_to_string, None)
|
||||
def __init__(self, config, system):
|
||||
config['system'] = system
|
||||
super(ControllerQueryService, self).__init__(config)
|
||||
self.url = config['url'] + config['grading_controller']
|
||||
self.login_url = self.url + '/login/'
|
||||
@@ -77,3 +74,16 @@ class ControllerQueryService(GradingService):
|
||||
|
||||
response = self.post(self.take_action_on_flags_url, params)
|
||||
return response
|
||||
|
||||
def convert_seconds_to_human_readable(seconds):
|
||||
if seconds < 60:
|
||||
human_string = "{0} seconds".format(seconds)
|
||||
elif seconds < 60 * 60:
|
||||
human_string = "{0} minutes".format(round(seconds/60,1))
|
||||
elif seconds < (24*60*60):
|
||||
human_string = "{0} hours".format(round(seconds/(60*60),1))
|
||||
else:
|
||||
human_string = "{0} days".format(round(seconds/(60*60*24),1))
|
||||
|
||||
eta_string = "{0}".format(human_string)
|
||||
return eta_string
|
||||
@@ -51,6 +51,8 @@ class GradingService(object):
|
||||
r = self._try_with_login(op)
|
||||
except (RequestException, ConnectionError, HTTPError) as err:
|
||||
# reraise as promised GradingServiceError, but preserve stacktrace.
|
||||
#This is a dev_facing_error
|
||||
log.error("Problem posting data to the grading controller. URL: {0}, data: {1}".format(url, data))
|
||||
raise GradingServiceError, str(err), sys.exc_info()[2]
|
||||
|
||||
return r.text
|
||||
@@ -67,6 +69,8 @@ class GradingService(object):
|
||||
r = self._try_with_login(op)
|
||||
except (RequestException, ConnectionError, HTTPError) as err:
|
||||
# reraise as promised GradingServiceError, but preserve stacktrace.
|
||||
#This is a dev_facing_error
|
||||
log.error("Problem getting data from the grading controller. URL: {0}, params: {1}".format(url, params))
|
||||
raise GradingServiceError, str(err), sys.exc_info()[2]
|
||||
|
||||
return r.text
|
||||
@@ -119,11 +123,13 @@ class GradingService(object):
|
||||
return response_json
|
||||
# if we can't parse the rubric into HTML,
|
||||
except etree.XMLSyntaxError, RubricParsingError:
|
||||
#This is a dev_facing_error
|
||||
log.exception("Cannot parse rubric string. Raw string: {0}"
|
||||
.format(rubric))
|
||||
return {'success': False,
|
||||
'error': 'Error displaying submission'}
|
||||
except ValueError:
|
||||
#This is a dev_facing_error
|
||||
log.exception("Error parsing response: {0}".format(response))
|
||||
return {'success': False,
|
||||
'error': "Error displaying submission"}
|
||||
|
||||
@@ -251,8 +251,9 @@ def upload_to_s3(file_to_upload, keyname, s3_interface):
|
||||
|
||||
return True, public_url
|
||||
except:
|
||||
error_message = "Could not connect to S3."
|
||||
log.exception(error_message)
|
||||
#This is a dev_facing_error
|
||||
error_message = "Could not connect to S3 to upload peer grading image. Trying to utilize bucket: {0}".format(bucketname.lower())
|
||||
log.error(error_message)
|
||||
return False, error_message
|
||||
|
||||
|
||||
|
||||
@@ -59,12 +59,14 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
self.submission_id = None
|
||||
self.grader_id = None
|
||||
|
||||
error_message = "No {0} found in problem xml for open ended problem. Contact the learning sciences group for assistance."
|
||||
if oeparam is None:
|
||||
raise ValueError("No oeparam found in problem xml.")
|
||||
#This is a staff_facing_error
|
||||
raise ValueError(error_message.format('oeparam'))
|
||||
if self.prompt is None:
|
||||
raise ValueError("No prompt found in problem xml.")
|
||||
raise ValueError(error_message.format('prompt'))
|
||||
if self.rubric is None:
|
||||
raise ValueError("No rubric found in problem xml.")
|
||||
raise ValueError(error_message.format('rubric'))
|
||||
|
||||
self._parse(oeparam, self.prompt, self.rubric, system)
|
||||
|
||||
@@ -73,6 +75,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
self.send_to_grader(self.latest_answer(), system)
|
||||
self.created = False
|
||||
|
||||
|
||||
def _parse(self, oeparam, prompt, rubric, system):
|
||||
'''
|
||||
Parse OpenEndedResponse XML:
|
||||
@@ -98,19 +101,21 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
# __init__ adds it (easiest way to get problem location into
|
||||
# response types)
|
||||
except TypeError, ValueError:
|
||||
log.exception("Grader payload %r is not a json object!", grader_payload)
|
||||
#This is a dev_facing_error
|
||||
log.exception("Grader payload from external open ended grading server is not a json object! Object: {0}".format(grader_payload))
|
||||
|
||||
self.initial_display = find_with_default(oeparam, 'initial_display', '')
|
||||
self.answer = find_with_default(oeparam, 'answer_display', 'No answer given.')
|
||||
|
||||
parsed_grader_payload.update({
|
||||
'location': system.location.url(),
|
||||
'location': self.location_string,
|
||||
'course_id': system.course_id,
|
||||
'prompt': prompt_string,
|
||||
'rubric': rubric_string,
|
||||
'initial_display': self.initial_display,
|
||||
'answer': self.answer,
|
||||
'problem_id': self.display_name
|
||||
'problem_id': self.display_name,
|
||||
'skip_basic_checks': self.skip_basic_checks,
|
||||
})
|
||||
updated_grader_payload = json.dumps(parsed_grader_payload)
|
||||
|
||||
@@ -133,24 +138,27 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
"""
|
||||
|
||||
event_info = dict()
|
||||
event_info['problem_id'] = system.location.url()
|
||||
event_info['problem_id'] = self.location_string
|
||||
event_info['student_id'] = system.anonymous_student_id
|
||||
event_info['survey_responses'] = get
|
||||
|
||||
survey_responses = event_info['survey_responses']
|
||||
for tag in ['feedback', 'submission_id', 'grader_id', 'score']:
|
||||
if tag not in survey_responses:
|
||||
return {'success': False, 'msg': "Could not find needed tag {0}".format(tag)}
|
||||
#This is a student_facing_error
|
||||
return {'success': False, 'msg': "Could not find needed tag {0} in the survey responses. Please try submitting again.".format(tag)}
|
||||
try:
|
||||
submission_id = int(survey_responses['submission_id'])
|
||||
grader_id = int(survey_responses['grader_id'])
|
||||
feedback = str(survey_responses['feedback'].encode('ascii', 'ignore'))
|
||||
score = int(survey_responses['score'])
|
||||
except:
|
||||
#This is a dev_facing_error
|
||||
error_message = ("Could not parse submission id, grader id, "
|
||||
"or feedback from message_post ajax call. Here is the message data: {0}".format(
|
||||
survey_responses))
|
||||
log.exception(error_message)
|
||||
#This is a student_facing_error
|
||||
return {'success': False, 'msg': "There was an error saving your feedback. Please contact course staff."}
|
||||
|
||||
qinterface = system.xqueue['interface']
|
||||
@@ -187,6 +195,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
|
||||
self.state = self.DONE
|
||||
|
||||
#This is a student_facing_message
|
||||
return {'success': success, 'msg': "Successfully submitted your feedback."}
|
||||
|
||||
def send_to_grader(self, submission, system):
|
||||
@@ -336,18 +345,22 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
|
||||
for tag in ['success', 'feedback', 'submission_id', 'grader_id']:
|
||||
if tag not in response_items:
|
||||
return format_feedback('errors', 'Error getting feedback')
|
||||
#This is a student_facing_error
|
||||
return format_feedback('errors', 'Error getting feedback from grader.')
|
||||
|
||||
feedback_items = response_items['feedback']
|
||||
try:
|
||||
feedback = json.loads(feedback_items)
|
||||
except (TypeError, ValueError):
|
||||
log.exception("feedback_items have invalid json %r", feedback_items)
|
||||
return format_feedback('errors', 'Could not parse feedback')
|
||||
#This is a dev_facing_error
|
||||
log.exception("feedback_items from external open ended grader have invalid json {0}".format(feedback_items))
|
||||
#This is a student_facing_error
|
||||
return format_feedback('errors', 'Error getting feedback from grader.')
|
||||
|
||||
if response_items['success']:
|
||||
if len(feedback) == 0:
|
||||
return format_feedback('errors', 'No feedback available')
|
||||
#This is a student_facing_error
|
||||
return format_feedback('errors', 'No feedback available from grader.')
|
||||
|
||||
for tag in do_not_render:
|
||||
if tag in feedback:
|
||||
@@ -356,6 +369,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
feedback_lst = sorted(feedback.items(), key=get_priority)
|
||||
feedback_list_part1 = u"\n".join(format_feedback(k, v) for k, v in feedback_lst)
|
||||
else:
|
||||
#This is a student_facing_error
|
||||
feedback_list_part1 = format_feedback('errors', response_items['feedback'])
|
||||
|
||||
feedback_list_part2 = (u"\n".join([format_feedback_hidden(feedback_type, value)
|
||||
@@ -431,14 +445,16 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
try:
|
||||
score_result = json.loads(score_msg)
|
||||
except (TypeError, ValueError):
|
||||
error_message = ("External grader message should be a JSON-serialized dict."
|
||||
#This is a dev_facing_error
|
||||
error_message = ("External open ended grader message should be a JSON-serialized dict."
|
||||
" Received score_msg = {0}".format(score_msg))
|
||||
log.error(error_message)
|
||||
fail['feedback'] = error_message
|
||||
return fail
|
||||
|
||||
if not isinstance(score_result, dict):
|
||||
error_message = ("External grader message should be a JSON-serialized dict."
|
||||
#This is a dev_facing_error
|
||||
error_message = ("External open ended grader message should be a JSON-serialized dict."
|
||||
" Received score_result = {0}".format(score_result))
|
||||
log.error(error_message)
|
||||
fail['feedback'] = error_message
|
||||
@@ -446,7 +462,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
|
||||
for tag in ['score', 'feedback', 'grader_type', 'success', 'grader_id', 'submission_id']:
|
||||
if tag not in score_result:
|
||||
error_message = ("External grader message is missing required tag: {0}"
|
||||
#This is a dev_facing_error
|
||||
error_message = ("External open ended grader message is missing required tag: {0}"
|
||||
.format(tag))
|
||||
log.error(error_message)
|
||||
fail['feedback'] = error_message
|
||||
@@ -563,7 +580,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
}
|
||||
|
||||
if dispatch not in handlers:
|
||||
return 'Error'
|
||||
#This is a dev_facing_error
|
||||
log.error("Cannot find {0} in handlers in handle_ajax function for open_ended_module.py".format(dispatch))
|
||||
#This is a dev_facing_error
|
||||
return json.dumps({'error': 'Error handling action. Please try again.', 'success' : False})
|
||||
|
||||
before = self.get_progress()
|
||||
d = handlers[dispatch](get, system)
|
||||
@@ -604,15 +624,21 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
success, get = self.append_image_to_student_answer(get)
|
||||
error_message = ""
|
||||
if success:
|
||||
get['student_answer'] = OpenEndedModule.sanitize_html(get['student_answer'])
|
||||
self.new_history_entry(get['student_answer'])
|
||||
self.send_to_grader(get['student_answer'], system)
|
||||
self.change_state(self.ASSESSING)
|
||||
success, allowed_to_submit, error_message = self.check_if_student_can_submit()
|
||||
if allowed_to_submit:
|
||||
get['student_answer'] = OpenEndedModule.sanitize_html(get['student_answer'])
|
||||
self.new_history_entry(get['student_answer'])
|
||||
self.send_to_grader(get['student_answer'], system)
|
||||
self.change_state(self.ASSESSING)
|
||||
else:
|
||||
#Error message already defined
|
||||
success = False
|
||||
else:
|
||||
#This is a student_facing_error
|
||||
error_message = "There was a problem saving the image in your submission. Please try a different image, or try pasting a link to an image into the answer box."
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'success': success,
|
||||
'error': error_message,
|
||||
'student_response': get['student_answer']
|
||||
}
|
||||
@@ -637,17 +663,21 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
Output: Rendered HTML
|
||||
"""
|
||||
#set context variables and render template
|
||||
eta_string = None
|
||||
if self.state != self.INITIAL:
|
||||
latest = self.latest_answer()
|
||||
previous_answer = latest if latest is not None else self.initial_display
|
||||
post_assessment = self.latest_post_assessment(system)
|
||||
score = self.latest_score()
|
||||
correct = 'correct' if self.is_submission_correct(score) else 'incorrect'
|
||||
if self.state == self.ASSESSING:
|
||||
eta_string = self.get_eta()
|
||||
else:
|
||||
post_assessment = ""
|
||||
correct = ""
|
||||
previous_answer = self.initial_display
|
||||
|
||||
|
||||
context = {
|
||||
'prompt': self.prompt,
|
||||
'previous_answer': previous_answer,
|
||||
@@ -660,6 +690,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
'child_type': 'openended',
|
||||
'correct': correct,
|
||||
'accept_file_upload': self.accept_file_upload,
|
||||
'eta_message' : eta_string,
|
||||
}
|
||||
html = system.render_template('open_ended.html', context)
|
||||
return html
|
||||
@@ -689,7 +720,8 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
|
||||
"""
|
||||
for child in ['openendedparam']:
|
||||
if len(xml_object.xpath(child)) != 1:
|
||||
raise ValueError("Open Ended definition must include exactly one '{0}' tag".format(child))
|
||||
#This is a staff_facing_error
|
||||
raise ValueError("Open Ended definition must include exactly one '{0}' tag. Contact the learning sciences group for assistance.".format(child))
|
||||
|
||||
def parse(k):
|
||||
"""Assumes that xml_object has child k"""
|
||||
|
||||
@@ -22,6 +22,8 @@ from xmodule.stringify import stringify_children
|
||||
from xmodule.xml_module import XmlDescriptor
|
||||
from xmodule.modulestore import Location
|
||||
from capa.util import *
|
||||
from peer_grading_service import PeerGradingService
|
||||
import controller_query_service
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
@@ -99,10 +101,21 @@ class OpenEndedChild(object):
|
||||
self.accept_file_upload = static_data['accept_file_upload']
|
||||
self.close_date = static_data['close_date']
|
||||
self.s3_interface = static_data['s3_interface']
|
||||
self.skip_basic_checks = static_data['skip_basic_checks']
|
||||
|
||||
# Used for progress / grading. Currently get credit just for
|
||||
# completion (doesn't matter if you self-assessed correct/incorrect).
|
||||
self._max_score = static_data['max_score']
|
||||
self.peer_gs = PeerGradingService(system.open_ended_grading_interface, system)
|
||||
self.controller_qs = controller_query_service.ControllerQueryService(system.open_ended_grading_interface,system)
|
||||
|
||||
self.system = system
|
||||
|
||||
self.location_string = location
|
||||
try:
|
||||
self.location_string = self.location_string.url()
|
||||
except:
|
||||
pass
|
||||
|
||||
self.setup_response(system, location, definition, descriptor)
|
||||
|
||||
@@ -126,12 +139,14 @@ class OpenEndedChild(object):
|
||||
if self.closed():
|
||||
return True, {
|
||||
'success': False,
|
||||
'error': 'This problem is now closed.'
|
||||
#This is a student_facing_error
|
||||
'error': 'The problem close date has passed, and this problem is now closed.'
|
||||
}
|
||||
elif self.attempts > self.max_attempts:
|
||||
return True, {
|
||||
'success': False,
|
||||
'error': 'Too many attempts.'
|
||||
#This is a student_facing_error
|
||||
'error': 'You have attempted this problem {0} times. You are allowed {1} attempts.'.format(self.attempts, self.max_attempts)
|
||||
}
|
||||
else:
|
||||
return False, {}
|
||||
@@ -250,7 +265,8 @@ class OpenEndedChild(object):
|
||||
try:
|
||||
return Progress(self.get_score()['score'], self._max_score)
|
||||
except Exception as err:
|
||||
log.exception("Got bad progress")
|
||||
#This is a dev_facing_error
|
||||
log.exception("Got bad progress from open ended child module. Max Score: {1}".format(self._max_score))
|
||||
return None
|
||||
return None
|
||||
|
||||
@@ -258,10 +274,12 @@ class OpenEndedChild(object):
|
||||
"""
|
||||
return dict out-of-sync error message, and also log.
|
||||
"""
|
||||
log.warning("Assessment module state out sync. state: %r, get: %r. %s",
|
||||
#This is a dev_facing_error
|
||||
log.warning("Open ended child state out sync. state: %r, get: %r. %s",
|
||||
self.state, get, msg)
|
||||
#This is a student_facing_error
|
||||
return {'success': False,
|
||||
'error': 'The problem state got out-of-sync'}
|
||||
'error': 'The problem state got out-of-sync. Please try reloading the page.'}
|
||||
|
||||
def get_html(self):
|
||||
"""
|
||||
@@ -339,6 +357,10 @@ class OpenEndedChild(object):
|
||||
if get_data['can_upload_files'] in ['true', '1']:
|
||||
has_file_to_upload = True
|
||||
file = get_data['student_file'][0]
|
||||
if self.system.track_fuction:
|
||||
self.system.track_function('open_ended_image_upload', {'filename': file.name})
|
||||
else:
|
||||
log.info("No tracking function found when uploading image.")
|
||||
uploaded_to_s3, image_ok, s3_public_url = self.upload_image_to_s3(file)
|
||||
if uploaded_to_s3:
|
||||
image_tag = self.generate_image_tag_from_url(s3_public_url, file.name)
|
||||
@@ -407,3 +429,55 @@ class OpenEndedChild(object):
|
||||
success = True
|
||||
|
||||
return success, string
|
||||
|
||||
def check_if_student_can_submit(self):
|
||||
location = self.location_string
|
||||
|
||||
student_id = self.system.anonymous_student_id
|
||||
success = False
|
||||
allowed_to_submit = True
|
||||
response = {}
|
||||
#This is a student_facing_error
|
||||
error_string = ("You need to peer grade {0} more in order to make another submission. "
|
||||
"You have graded {1}, and {2} are required. You have made {3} successful peer grading submissions.")
|
||||
try:
|
||||
response = self.peer_gs.get_data_for_location(self.location_string, student_id)
|
||||
count_graded = response['count_graded']
|
||||
count_required = response['count_required']
|
||||
student_sub_count = response['student_sub_count']
|
||||
success = True
|
||||
except:
|
||||
#This is a dev_facing_error
|
||||
log.error("Could not contact external open ended graders for location {0} and student {1}".format(self.location_string,student_id))
|
||||
#This is a student_facing_error
|
||||
error_message = "Could not contact the graders. Please notify course staff."
|
||||
return success, allowed_to_submit, error_message
|
||||
if count_graded>=count_required:
|
||||
return success, allowed_to_submit, ""
|
||||
else:
|
||||
allowed_to_submit = False
|
||||
#This is a student_facing_error
|
||||
error_message = error_string.format(count_required-count_graded, count_graded, count_required, student_sub_count)
|
||||
return success, allowed_to_submit, error_message
|
||||
|
||||
def get_eta(self):
|
||||
response = self.controller_qs.check_for_eta(self.location_string)
|
||||
try:
|
||||
response = json.loads(response)
|
||||
except:
|
||||
pass
|
||||
|
||||
success = response['success']
|
||||
if isinstance(success, basestring):
|
||||
success = (success.lower()=="true")
|
||||
|
||||
if success:
|
||||
eta = controller_query_service.convert_seconds_to_human_readable(response['eta'])
|
||||
eta_string = "Please check back for your response in at most {0}.".format(eta)
|
||||
else:
|
||||
eta_string = ""
|
||||
|
||||
return eta_string
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ class PeerGradingService(GradingService):
|
||||
self.system = system
|
||||
|
||||
def get_data_for_location(self, problem_location, student_id):
|
||||
response = self.get(self.get_data_for_location_url,
|
||||
{'location': problem_location, 'student_id': student_id})
|
||||
params = {'location': problem_location, 'student_id': student_id}
|
||||
response = self.get(self.get_data_for_location_url, params)
|
||||
return self.try_to_decode(response)
|
||||
|
||||
def get_next_submission(self, problem_location, grader_id):
|
||||
@@ -106,7 +106,7 @@ class MockPeerGradingService(object):
|
||||
'max_score': 4})
|
||||
|
||||
def save_grade(self, location, grader_id, submission_id,
|
||||
score, feedback, submission_key):
|
||||
score, feedback, submission_key, rubric_scores, submission_flagged):
|
||||
return json.dumps({'success': True})
|
||||
|
||||
def is_student_calibrated(self, problem_location, grader_id):
|
||||
@@ -122,7 +122,8 @@ class MockPeerGradingService(object):
|
||||
'max_score': 4})
|
||||
|
||||
def save_calibration_essay(self, problem_location, grader_id,
|
||||
calibration_essay_id, submission_key, score, feedback):
|
||||
calibration_essay_id, submission_key, score,
|
||||
feedback, rubric_scores):
|
||||
return {'success': True, 'actual_score': 2}
|
||||
|
||||
def get_problem_list(self, course_id, grader_id):
|
||||
|
||||
@@ -90,7 +90,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
}
|
||||
|
||||
if dispatch not in handlers:
|
||||
return 'Error'
|
||||
#This is a dev_facing_error
|
||||
log.error("Cannot find {0} in handlers in handle_ajax function for open_ended_module.py".format(dispatch))
|
||||
#This is a dev_facing_error
|
||||
return json.dumps({'error': 'Error handling action. Please try again.', 'success' : False})
|
||||
|
||||
before = self.get_progress()
|
||||
d = handlers[dispatch](get, system)
|
||||
@@ -123,7 +126,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
elif self.state in (self.POST_ASSESSMENT, self.DONE):
|
||||
context['read_only'] = True
|
||||
else:
|
||||
raise ValueError("Illegal state '%r'" % self.state)
|
||||
#This is a dev_facing_error
|
||||
raise ValueError("Self assessment module is in an illegal state '{0}'".format(self.state))
|
||||
|
||||
return system.render_template('self_assessment_rubric.html', context)
|
||||
|
||||
@@ -148,7 +152,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
elif self.state == self.DONE:
|
||||
context['read_only'] = True
|
||||
else:
|
||||
raise ValueError("Illegal state '%r'" % self.state)
|
||||
#This is a dev_facing_error
|
||||
raise ValueError("Self Assessment module is in an illegal state '{0}'".format(self.state))
|
||||
|
||||
return system.render_template('self_assessment_hint.html', context)
|
||||
|
||||
@@ -177,10 +182,16 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
# add new history element with answer and empty score and hint.
|
||||
success, get = self.append_image_to_student_answer(get)
|
||||
if success:
|
||||
get['student_answer'] = SelfAssessmentModule.sanitize_html(get['student_answer'])
|
||||
self.new_history_entry(get['student_answer'])
|
||||
self.change_state(self.ASSESSING)
|
||||
success, allowed_to_submit, error_message = self.check_if_student_can_submit()
|
||||
if allowed_to_submit:
|
||||
get['student_answer'] = SelfAssessmentModule.sanitize_html(get['student_answer'])
|
||||
self.new_history_entry(get['student_answer'])
|
||||
self.change_state(self.ASSESSING)
|
||||
else:
|
||||
#Error message already defined
|
||||
success = False
|
||||
else:
|
||||
#This is a student_facing_error
|
||||
error_message = "There was a problem saving the image in your submission. Please try a different image, or try pasting a link to an image into the answer box."
|
||||
|
||||
return {
|
||||
@@ -214,7 +225,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
for i in xrange(0,len(score_list)):
|
||||
score_list[i] = int(score_list[i])
|
||||
except ValueError:
|
||||
return {'success': False, 'error': "Non-integer score value, or no score list"}
|
||||
#This is a dev_facing_error
|
||||
log.error("Non-integer score value passed to save_assessment ,or no score list present.")
|
||||
#This is a student_facing_error
|
||||
return {'success': False, 'error': "Error saving your score. Please notify course staff."}
|
||||
|
||||
#Record score as assessment and rubric scores as post assessment
|
||||
self.record_latest_score(score)
|
||||
@@ -256,6 +270,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
try:
|
||||
rubric_scores = json.loads(latest_post_assessment)
|
||||
except:
|
||||
#This is a dev_facing_error
|
||||
log.error("Cannot parse rubric scores in self assessment module from {0}".format(latest_post_assessment))
|
||||
rubric_scores = []
|
||||
return [rubric_scores]
|
||||
@@ -287,7 +302,8 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
|
||||
expected_children = []
|
||||
for child in expected_children:
|
||||
if len(xml_object.xpath(child)) != 1:
|
||||
raise ValueError("Self assessment definition must include exactly one '{0}' tag".format(child))
|
||||
#This is a staff_facing_error
|
||||
raise ValueError("Self assessment definition must include exactly one '{0}' tag. Contact the learning sciences group for assistance.".format(child))
|
||||
|
||||
def parse(k):
|
||||
"""Assumes that xml_object has child k"""
|
||||
|
||||
@@ -3,6 +3,7 @@ import logging
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from datetime import datetime
|
||||
from pkg_resources import resource_string
|
||||
from .capa_module import ComplexEncoder
|
||||
from .editing_module import EditingDescriptor
|
||||
@@ -10,6 +11,8 @@ from .stringify import stringify_children
|
||||
from .x_module import XModule
|
||||
from .xml_module import XmlDescriptor
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from timeinfo import TimeInfo
|
||||
|
||||
from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError
|
||||
|
||||
@@ -21,6 +24,8 @@ TRUE_DICT = [True, "True", "true", "TRUE"]
|
||||
MAX_SCORE = 1
|
||||
IS_GRADED = True
|
||||
|
||||
EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please notify course staff."
|
||||
|
||||
|
||||
class PeerGradingModule(XModule):
|
||||
_VERSION = 1
|
||||
@@ -50,6 +55,7 @@ class PeerGradingModule(XModule):
|
||||
self.system = system
|
||||
self.peer_gs = PeerGradingService(self.system.open_ended_grading_interface, self.system)
|
||||
|
||||
|
||||
self.use_for_single_location = self.metadata.get('use_for_single_location', USE_FOR_SINGLE_LOCATION)
|
||||
if isinstance(self.use_for_single_location, basestring):
|
||||
self.use_for_single_location = (self.use_for_single_location in TRUE_DICT)
|
||||
@@ -58,10 +64,28 @@ class PeerGradingModule(XModule):
|
||||
if isinstance(self.is_graded, basestring):
|
||||
self.is_graded = (self.is_graded in TRUE_DICT)
|
||||
|
||||
display_due_date_string = self.metadata.get('due', None)
|
||||
grace_period_string = self.metadata.get('graceperiod', None)
|
||||
|
||||
try:
|
||||
self.timeinfo = TimeInfo(display_due_date_string, grace_period_string)
|
||||
except:
|
||||
log.error("Error parsing due date information in location {0}".format(location))
|
||||
raise
|
||||
|
||||
self.display_due_date = self.timeinfo.display_due_date
|
||||
|
||||
self.link_to_location = self.metadata.get('link_to_location', USE_FOR_SINGLE_LOCATION)
|
||||
if self.use_for_single_location == True:
|
||||
#This will raise an exception if the location is invalid
|
||||
link_to_location_object = Location(self.link_to_location)
|
||||
try:
|
||||
self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location)
|
||||
except:
|
||||
log.error("Linked location {0} for peer grading module {1} does not exist".format(
|
||||
self.link_to_location, self.location))
|
||||
raise
|
||||
due_date = self.linked_problem.metadata.get('peer_grading_due', None)
|
||||
if due_date:
|
||||
self.metadata['due'] = due_date
|
||||
|
||||
self.ajax_url = self.system.ajax_url
|
||||
if not self.ajax_url.endswith("/"):
|
||||
@@ -73,6 +97,15 @@ class PeerGradingModule(XModule):
|
||||
#This could result in an exception, but not wrapping in a try catch block so it moves up the stack
|
||||
self.max_grade = int(self.max_grade)
|
||||
|
||||
def closed(self):
|
||||
return self._closed(self.timeinfo)
|
||||
|
||||
def _closed(self, timeinfo):
|
||||
if timeinfo.close_date is not None and datetime.utcnow() > timeinfo.close_date:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _err_response(self, msg):
|
||||
"""
|
||||
Return a HttpResponse with a json dump with success=False, and the given error message.
|
||||
@@ -92,6 +125,8 @@ class PeerGradingModule(XModule):
|
||||
Needs to be implemented by inheritors. Renders the HTML that students see.
|
||||
@return:
|
||||
"""
|
||||
if self.closed():
|
||||
return self.peer_grading_closed()
|
||||
if not self.use_for_single_location:
|
||||
return self.peer_grading()
|
||||
else:
|
||||
@@ -112,7 +147,10 @@ class PeerGradingModule(XModule):
|
||||
}
|
||||
|
||||
if dispatch not in handlers:
|
||||
return 'Error'
|
||||
#This is a dev_facing_error
|
||||
log.error("Cannot find {0} in handlers in handle_ajax function for open_ended_module.py".format(dispatch))
|
||||
#This is a dev_facing_error
|
||||
return json.dumps({'error': 'Error handling action. Please try again.', 'success' : False})
|
||||
|
||||
d = handlers[dispatch](get)
|
||||
|
||||
@@ -130,6 +168,7 @@ class PeerGradingModule(XModule):
|
||||
count_required = response['count_required']
|
||||
success = True
|
||||
except GradingServiceError:
|
||||
#This is a dev_facing_error
|
||||
log.exception("Error getting location data from controller for location {0}, student {1}"
|
||||
.format(location, student_id))
|
||||
|
||||
@@ -155,6 +194,7 @@ class PeerGradingModule(XModule):
|
||||
count_graded = response['count_graded']
|
||||
count_required = response['count_required']
|
||||
if count_required > 0 and count_graded >= count_required:
|
||||
#Ensures that once a student receives a final score for peer grading, that it does not change.
|
||||
self.student_data_for_location = response
|
||||
|
||||
score_dict = {
|
||||
@@ -204,10 +244,12 @@ class PeerGradingModule(XModule):
|
||||
response = self.peer_gs.get_next_submission(location, grader_id)
|
||||
return response
|
||||
except GradingServiceError:
|
||||
#This is a dev_facing_error
|
||||
log.exception("Error getting next submission. server url: {0} location: {1}, grader_id: {2}"
|
||||
.format(self.peer_gs.url, location, grader_id))
|
||||
#This is a student_facing_error
|
||||
return {'success': False,
|
||||
'error': 'Could not connect to grading service'}
|
||||
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR}
|
||||
|
||||
def save_grade(self, get):
|
||||
"""
|
||||
@@ -244,14 +286,16 @@ class PeerGradingModule(XModule):
|
||||
score, feedback, submission_key, rubric_scores, submission_flagged)
|
||||
return response
|
||||
except GradingServiceError:
|
||||
log.exception("""Error saving grade. server url: {0}, location: {1}, submission_id:{2},
|
||||
#This is a dev_facing_error
|
||||
log.exception("""Error saving grade to open ended grading service. server url: {0}, location: {1}, submission_id:{2},
|
||||
submission_key: {3}, score: {4}"""
|
||||
.format(self.peer_gs.url,
|
||||
location, submission_id, submission_key, score)
|
||||
)
|
||||
#This is a student_facing_error
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Could not connect to grading service'
|
||||
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR
|
||||
}
|
||||
|
||||
def is_student_calibrated(self, get):
|
||||
@@ -284,11 +328,13 @@ class PeerGradingModule(XModule):
|
||||
response = self.peer_gs.is_student_calibrated(location, grader_id)
|
||||
return response
|
||||
except GradingServiceError:
|
||||
log.exception("Error from grading service. server url: {0}, grader_id: {0}, location: {1}"
|
||||
#This is a dev_facing_error
|
||||
log.exception("Error from open ended grading service. server url: {0}, grader_id: {0}, location: {1}"
|
||||
.format(self.peer_gs.url, grader_id, location))
|
||||
#This is a student_facing_error
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Could not connect to grading service'
|
||||
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR
|
||||
}
|
||||
|
||||
def show_calibration_essay(self, get):
|
||||
@@ -327,16 +373,20 @@ class PeerGradingModule(XModule):
|
||||
response = self.peer_gs.show_calibration_essay(location, grader_id)
|
||||
return response
|
||||
except GradingServiceError:
|
||||
log.exception("Error from grading service. server url: {0}, location: {0}"
|
||||
#This is a dev_facing_error
|
||||
log.exception("Error from open ended grading service. server url: {0}, location: {0}"
|
||||
.format(self.peer_gs.url, location))
|
||||
#This is a student_facing_error
|
||||
return {'success': False,
|
||||
'error': 'Could not connect to grading service'}
|
||||
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR}
|
||||
# if we can't parse the rubric into HTML,
|
||||
except etree.XMLSyntaxError:
|
||||
#This is a dev_facing_error
|
||||
log.exception("Cannot parse rubric string. Raw string: {0}"
|
||||
.format(rubric))
|
||||
#This is a student_facing_error
|
||||
return {'success': False,
|
||||
'error': 'Error displaying submission'}
|
||||
'error': 'Error displaying submission. Please notify course staff.'}
|
||||
|
||||
|
||||
def save_calibration_essay(self, get):
|
||||
@@ -375,8 +425,20 @@ class PeerGradingModule(XModule):
|
||||
submission_key, score, feedback, rubric_scores)
|
||||
return response
|
||||
except GradingServiceError:
|
||||
#This is a dev_facing_error
|
||||
log.exception("Error saving calibration grade, location: {0}, submission_id: {1}, submission_key: {2}, grader_id: {3}".format(location, submission_id, submission_key, grader_id))
|
||||
return self._err_response('Could not connect to grading service')
|
||||
#This is a student_facing_error
|
||||
return self._err_response('There was an error saving your score. Please notify course staff.')
|
||||
|
||||
def peer_grading_closed(self):
|
||||
'''
|
||||
Show the Peer grading closed template
|
||||
'''
|
||||
html = self.system.render_template('peer_grading/peer_grading_closed.html', {
|
||||
'use_for_single_location': self.use_for_single_location
|
||||
})
|
||||
return html
|
||||
|
||||
|
||||
def peer_grading(self, get=None):
|
||||
'''
|
||||
@@ -397,13 +459,49 @@ class PeerGradingModule(XModule):
|
||||
problem_list = problem_list_dict['problem_list']
|
||||
|
||||
except GradingServiceError:
|
||||
error_text = "Error occured while contacting the grading service"
|
||||
#This is a student_facing_error
|
||||
error_text = EXTERNAL_GRADER_NO_CONTACT_ERROR
|
||||
success = False
|
||||
# catch error if if the json loads fails
|
||||
except ValueError:
|
||||
error_text = "Could not get problem list"
|
||||
#This is a student_facing_error
|
||||
error_text = "Could not get list of problems to peer grade. Please notify course staff."
|
||||
success = False
|
||||
|
||||
|
||||
def _find_corresponding_module_for_location(location):
|
||||
'''
|
||||
find the peer grading module that links to the given location
|
||||
'''
|
||||
try:
|
||||
return modulestore().get_instance(self.system.course_id, location)
|
||||
except:
|
||||
# the linked problem doesn't exist
|
||||
log.error("Problem {0} does not exist in this course".format(location))
|
||||
raise
|
||||
|
||||
|
||||
for problem in problem_list:
|
||||
problem_location = problem['location']
|
||||
descriptor = _find_corresponding_module_for_location(problem_location)
|
||||
if descriptor:
|
||||
problem['due'] = descriptor.metadata.get('peer_grading_due', None)
|
||||
grace_period_string = descriptor.metadata.get('graceperiod', None)
|
||||
try:
|
||||
problem_timeinfo = TimeInfo(problem['due'], grace_period_string)
|
||||
except:
|
||||
log.error("Malformed due date or grace period string for location {0}".format(problem_location))
|
||||
raise
|
||||
if self._closed(problem_timeinfo):
|
||||
problem['closed'] = True
|
||||
else:
|
||||
problem['closed'] = False
|
||||
else:
|
||||
# if we can't find the due date, assume that it doesn't have one
|
||||
problem['due'] = None
|
||||
problem['closed'] = False
|
||||
|
||||
|
||||
ajax_url = self.ajax_url
|
||||
html = self.system.render_template('peer_grading/peer_grading.html', {
|
||||
'course_id': self.system.course_id,
|
||||
@@ -425,6 +523,8 @@ class PeerGradingModule(XModule):
|
||||
if get == None or get.get('location') == None:
|
||||
if not self.use_for_single_location:
|
||||
#This is an error case, because it must be set to use a single location to be called without get parameters
|
||||
#This is a dev_facing_error
|
||||
log.error("Peer grading problem in peer_grading_module called with no get parameters, but use_for_single_location is False.")
|
||||
return {'html': "", 'success': False}
|
||||
problem_location = self.link_to_location
|
||||
|
||||
@@ -489,7 +589,8 @@ class PeerGradingDescriptor(XmlDescriptor, EditingDescriptor):
|
||||
expected_children = []
|
||||
for child in expected_children:
|
||||
if len(xml_object.xpath(child)) == 0:
|
||||
raise ValueError("Peer grading definition must include at least one '{0}' tag".format(child))
|
||||
#This is a staff_facing_error
|
||||
raise ValueError("Peer grading definition must include at least one '{0}' tag. Contact the learning sciences group for assistance.".format(child))
|
||||
|
||||
def parse_task(k):
|
||||
"""Assumes that xml_object has child k"""
|
||||
|
||||
@@ -19,6 +19,15 @@ import xmodule
|
||||
from xmodule.x_module import ModuleSystem
|
||||
from mock import Mock
|
||||
|
||||
open_ended_grading_interface = {
|
||||
'url': 'http://sandbox-grader-001.m.edx.org/peer_grading',
|
||||
'username': 'incorrect_user',
|
||||
'password': 'incorrect_pass',
|
||||
'staff_grading' : 'staff_grading',
|
||||
'peer_grading' : 'peer_grading',
|
||||
'grading_controller' : 'grading_controller'
|
||||
}
|
||||
|
||||
test_system = ModuleSystem(
|
||||
ajax_url='courses/course_id/modx/a_location',
|
||||
track_function=Mock(),
|
||||
@@ -31,7 +40,8 @@ test_system = ModuleSystem(
|
||||
debug=True,
|
||||
xqueue={'interface': None, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 10},
|
||||
node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
|
||||
anonymous_student_id='student'
|
||||
anonymous_student_id='student',
|
||||
open_ended_grading_interface= open_ended_grading_interface
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ class CapaFactory(object):
|
||||
force_save_button=None,
|
||||
attempts=None,
|
||||
problem_state=None,
|
||||
correct=False
|
||||
):
|
||||
"""
|
||||
All parameters are optional, and are added to the created problem if specified.
|
||||
@@ -58,6 +59,7 @@ class CapaFactory(object):
|
||||
module.
|
||||
|
||||
attempts: also added to instance state. Will be converted to an int.
|
||||
correct: if True, the problem will be initialized to be answered correctly.
|
||||
"""
|
||||
definition = {'data': CapaFactory.sample_problem_xml, }
|
||||
location = Location(["i4x", "edX", "capa_test", "problem",
|
||||
@@ -81,10 +83,19 @@ class CapaFactory(object):
|
||||
instance_state_dict = {}
|
||||
if problem_state is not None:
|
||||
instance_state_dict = problem_state
|
||||
|
||||
if attempts is not None:
|
||||
# converting to int here because I keep putting "0" and "1" in the tests
|
||||
# since everything else is a string.
|
||||
instance_state_dict['attempts'] = int(attempts)
|
||||
|
||||
if correct:
|
||||
# TODO: make this actually set an answer of 3.14, and mark it correct
|
||||
#instance_state_dict['student_answers'] = {}
|
||||
#instance_state_dict['correct_map'] = {}
|
||||
pass
|
||||
|
||||
|
||||
if len(instance_state_dict) > 0:
|
||||
instance_state = json.dumps(instance_state_dict)
|
||||
else:
|
||||
@@ -94,13 +105,16 @@ class CapaFactory(object):
|
||||
definition, descriptor,
|
||||
instance_state, None, metadata=metadata)
|
||||
|
||||
if correct:
|
||||
# TODO: probably better to actually set the internal state properly, but...
|
||||
module.get_score = lambda: {'score': 1, 'total': 1}
|
||||
|
||||
return module
|
||||
|
||||
|
||||
|
||||
class CapaModuleTest(unittest.TestCase):
|
||||
|
||||
|
||||
def setUp(self):
|
||||
now = datetime.datetime.now()
|
||||
day_delta = datetime.timedelta(days=1)
|
||||
@@ -120,6 +134,18 @@ class CapaModuleTest(unittest.TestCase):
|
||||
self.assertNotEqual(module.url_name, other_module.url_name,
|
||||
"Factory should be creating unique names for each problem")
|
||||
|
||||
|
||||
def test_correct(self):
|
||||
"""
|
||||
Check that the factory creates correct and incorrect problems properly.
|
||||
"""
|
||||
module = CapaFactory.create()
|
||||
self.assertEqual(module.get_score()['score'], 0)
|
||||
|
||||
other_module = CapaFactory.create(correct=True)
|
||||
self.assertEqual(other_module.get_score()['score'], 1)
|
||||
|
||||
|
||||
def test_showanswer_default(self):
|
||||
"""
|
||||
Make sure the show answer logic does the right thing.
|
||||
@@ -178,7 +204,7 @@ class CapaModuleTest(unittest.TestCase):
|
||||
for everyone--e.g. after due date + grace period.
|
||||
"""
|
||||
|
||||
# can see after attempts used up, even with due date in the future
|
||||
# can't see after attempts used up, even with due date in the future
|
||||
used_all_attempts = CapaFactory.create(showanswer='past_due',
|
||||
max_attempts="1",
|
||||
attempts="1",
|
||||
@@ -209,3 +235,50 @@ class CapaModuleTest(unittest.TestCase):
|
||||
due=self.yesterday_str,
|
||||
graceperiod=self.two_day_delta_str)
|
||||
self.assertFalse(still_in_grace.answer_available())
|
||||
|
||||
def test_showanswer_finished(self):
|
||||
"""
|
||||
With showanswer="finished" should show answer after the problem is closed,
|
||||
or after the answer is correct.
|
||||
"""
|
||||
|
||||
# can see after attempts used up, even with due date in the future
|
||||
used_all_attempts = CapaFactory.create(showanswer='finished',
|
||||
max_attempts="1",
|
||||
attempts="1",
|
||||
due=self.tomorrow_str)
|
||||
self.assertTrue(used_all_attempts.answer_available())
|
||||
|
||||
|
||||
# can see after due date
|
||||
past_due_date = CapaFactory.create(showanswer='finished',
|
||||
max_attempts="1",
|
||||
attempts="0",
|
||||
due=self.yesterday_str)
|
||||
self.assertTrue(past_due_date.answer_available())
|
||||
|
||||
|
||||
# can't see because attempts left and wrong
|
||||
attempts_left_open = CapaFactory.create(showanswer='finished',
|
||||
max_attempts="1",
|
||||
attempts="0",
|
||||
due=self.tomorrow_str)
|
||||
self.assertFalse(attempts_left_open.answer_available())
|
||||
|
||||
# _can_ see because attempts left and right
|
||||
correct_ans = CapaFactory.create(showanswer='finished',
|
||||
max_attempts="1",
|
||||
attempts="0",
|
||||
due=self.tomorrow_str,
|
||||
correct=True)
|
||||
self.assertTrue(correct_ans.answer_available())
|
||||
|
||||
|
||||
# Can see even though grace period hasn't expired, because have no more
|
||||
# attempts.
|
||||
still_in_grace = CapaFactory.create(showanswer='finished',
|
||||
max_attempts="1",
|
||||
attempts="1",
|
||||
due=self.yesterday_str,
|
||||
graceperiod=self.two_day_delta_str)
|
||||
self.assertTrue(still_in_grace.answer_available())
|
||||
|
||||
@@ -48,6 +48,7 @@ class OpenEndedChildTest(unittest.TestCase):
|
||||
'close_date': None,
|
||||
's3_interface' : "",
|
||||
'open_ended_grading_interface' : {},
|
||||
'skip_basic_checks' : False,
|
||||
}
|
||||
definition = Mock()
|
||||
descriptor = Mock()
|
||||
@@ -167,6 +168,7 @@ class OpenEndedModuleTest(unittest.TestCase):
|
||||
'close_date': None,
|
||||
's3_interface' : test_util_open_ended.S3_INTERFACE,
|
||||
'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
|
||||
'skip_basic_checks' : False,
|
||||
}
|
||||
|
||||
oeparam = etree.XML('''
|
||||
@@ -301,6 +303,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
|
||||
'close_date' : "",
|
||||
's3_interface' : test_util_open_ended.S3_INTERFACE,
|
||||
'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
|
||||
'skip_basic_checks' : False,
|
||||
}
|
||||
|
||||
oeparam = etree.XML('''
|
||||
|
||||
@@ -4,7 +4,7 @@ from fs.osfs import OSFS
|
||||
from nose.tools import assert_equals, assert_true
|
||||
from path import path
|
||||
from tempfile import mkdtemp
|
||||
from shutil import copytree
|
||||
import shutil
|
||||
|
||||
from xmodule.modulestore.xml import XMLModuleStore
|
||||
|
||||
@@ -46,11 +46,11 @@ class RoundTripTestCase(unittest.TestCase):
|
||||
Thus we make sure that export and import work properly.
|
||||
'''
|
||||
def check_export_roundtrip(self, data_dir, course_dir):
|
||||
root_dir = path(mkdtemp())
|
||||
root_dir = path(self.temp_dir)
|
||||
print "Copying test course to temp dir {0}".format(root_dir)
|
||||
|
||||
data_dir = path(data_dir)
|
||||
copytree(data_dir / course_dir, root_dir / course_dir)
|
||||
shutil.copytree(data_dir / course_dir, root_dir / course_dir)
|
||||
|
||||
print "Starting import"
|
||||
initial_import = XMLModuleStore(root_dir, course_dirs=[course_dir])
|
||||
@@ -108,6 +108,8 @@ class RoundTripTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.maxDiff = None
|
||||
self.temp_dir = mkdtemp()
|
||||
self.addCleanup(shutil.rmtree, self.temp_dir)
|
||||
|
||||
def test_toy_roundtrip(self):
|
||||
self.check_export_roundtrip(DATA_DIR, "toy")
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import json
|
||||
from mock import Mock
|
||||
from mock import Mock, MagicMock
|
||||
import unittest
|
||||
|
||||
from xmodule.open_ended_grading_classes.self_assessment_module import SelfAssessmentModule
|
||||
from xmodule.modulestore import Location
|
||||
from lxml import etree
|
||||
from nose.plugins.skip import SkipTest
|
||||
|
||||
from . import test_system
|
||||
|
||||
@@ -51,6 +50,7 @@ class SelfAssessmentTest(unittest.TestCase):
|
||||
'close_date': None,
|
||||
's3_interface' : test_util_open_ended.S3_INTERFACE,
|
||||
'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
|
||||
'skip_basic_checks' : False,
|
||||
}
|
||||
|
||||
self.module = SelfAssessmentModule(test_system, self.location,
|
||||
@@ -63,13 +63,29 @@ class SelfAssessmentTest(unittest.TestCase):
|
||||
self.assertTrue("This is sample prompt text" in html)
|
||||
|
||||
def test_self_assessment_flow(self):
|
||||
raise SkipTest()
|
||||
responses = {'assessment': '0', 'score_list[]': ['0', '0']}
|
||||
def get_fake_item(name):
|
||||
return responses[name]
|
||||
|
||||
def get_data_for_location(self,location,student):
|
||||
return {
|
||||
'count_graded' : 0,
|
||||
'count_required' : 0,
|
||||
'student_sub_count': 0,
|
||||
}
|
||||
|
||||
mock_query_dict = MagicMock()
|
||||
mock_query_dict.__getitem__.side_effect = get_fake_item
|
||||
mock_query_dict.getlist = get_fake_item
|
||||
|
||||
self.module.peer_gs.get_data_for_location = get_data_for_location
|
||||
|
||||
self.assertEqual(self.module.get_score()['score'], 0)
|
||||
|
||||
self.module.save_answer({'student_answer': "I am an answer"}, test_system)
|
||||
self.assertEqual(self.module.state, self.module.ASSESSING)
|
||||
|
||||
self.module.save_assessment({'assessment': '0'}, test_system)
|
||||
self.module.save_assessment(mock_query_dict, test_system)
|
||||
self.assertEqual(self.module.state, self.module.DONE)
|
||||
|
||||
|
||||
@@ -79,5 +95,6 @@ class SelfAssessmentTest(unittest.TestCase):
|
||||
|
||||
# if we now assess as right, skip the REQUEST_HINT state
|
||||
self.module.save_answer({'student_answer': 'answer 4'}, test_system)
|
||||
self.module.save_assessment({'assessment': '1'}, test_system)
|
||||
responses['assessment'] = '1'
|
||||
self.module.save_assessment(mock_query_dict, test_system)
|
||||
self.assertEqual(self.module.state, self.module.DONE)
|
||||
|
||||
39
common/lib/xmodule/xmodule/timeinfo.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import dateutil
|
||||
import dateutil.parser
|
||||
import datetime
|
||||
from timeparse import parse_timedelta
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class TimeInfo(object):
|
||||
"""
|
||||
This is a simple object that calculates and stores datetime information for an XModule
|
||||
based on the due date string and the grace period string
|
||||
|
||||
So far it parses out three different pieces of time information:
|
||||
self.display_due_date - the 'official' due date that gets displayed to students
|
||||
self.grace_period - the length of the grace period
|
||||
self.close_date - the real due date
|
||||
|
||||
"""
|
||||
def __init__(self, display_due_date_string, grace_period_string):
|
||||
if display_due_date_string is not None:
|
||||
try:
|
||||
self.display_due_date = dateutil.parser.parse(display_due_date_string)
|
||||
except ValueError:
|
||||
log.error("Could not parse due date {0}".format(display_due_date_string))
|
||||
raise
|
||||
else:
|
||||
self.display_due_date = None
|
||||
|
||||
if grace_period_string is not None and self.display_due_date:
|
||||
try:
|
||||
self.grace_period = parse_timedelta(grace_period_string)
|
||||
self.close_date = self.display_due_date + self.grace_period
|
||||
except:
|
||||
log.error("Error parsing the grace period {0}".format(grace_period_string))
|
||||
raise
|
||||
else:
|
||||
self.grace_period = None
|
||||
self.close_date = self.display_due_date
|
||||
@@ -411,7 +411,6 @@ class ResourceTemplates(object):
|
||||
|
||||
return templates
|
||||
|
||||
|
||||
class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
|
||||
"""
|
||||
An XModuleDescriptor is a specification for an element of a course. This
|
||||
@@ -585,11 +584,11 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
|
||||
def inherit_metadata(self, metadata):
|
||||
"""
|
||||
Updates this module with metadata inherited from a containing module.
|
||||
Only metadata specified in self.inheritable_metadata will
|
||||
Only metadata specified in inheritable_metadata will
|
||||
be inherited
|
||||
"""
|
||||
# Set all inheritable metadata from kwargs that are
|
||||
# in self.inheritable_metadata and aren't already set in metadata
|
||||
# in inheritable_metadata and aren't already set in metadata
|
||||
for attr in self.inheritable_metadata:
|
||||
if attr not in self.metadata and attr in metadata:
|
||||
self._inherited_metadata.add(attr)
|
||||
|
||||
@@ -128,8 +128,7 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
'graded': bool_map,
|
||||
'hide_progress_tab': bool_map,
|
||||
'allow_anonymous': bool_map,
|
||||
'allow_anonymous_to_peers': bool_map,
|
||||
'weight': int_map
|
||||
'allow_anonymous_to_peers': bool_map
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ if Backbone?
|
||||
url = DiscussionUtil.urlFor 'threads'
|
||||
when 'followed'
|
||||
url = DiscussionUtil.urlFor 'followed_threads', options.user_id
|
||||
if options['group_id']
|
||||
data['group_id'] = options['group_id']
|
||||
data['sort_key'] = sort_options.sort_key || 'date'
|
||||
data['sort_order'] = sort_options.sort_order || 'desc'
|
||||
DiscussionUtil.safeAjax
|
||||
|
||||
@@ -70,10 +70,21 @@ if Backbone?
|
||||
DiscussionUtil.loadRoles(response.roles)
|
||||
allow_anonymous = response.allow_anonymous
|
||||
allow_anonymous_to_peers = response.allow_anonymous_to_peers
|
||||
cohorts = response.cohorts
|
||||
# $elem.html("Hide Discussion")
|
||||
@discussion = new Discussion()
|
||||
@discussion.reset(response.discussion_data, {silent: false})
|
||||
$discussion = $(Mustache.render $("script#_inline_discussion").html(), {'threads':response.discussion_data, 'discussionId': discussionId, 'allow_anonymous_to_peers': allow_anonymous_to_peers, 'allow_anonymous': allow_anonymous})
|
||||
|
||||
#use same discussion template but different thread templated
|
||||
#determined in the coffeescript based on whether or not there's a
|
||||
#group id
|
||||
|
||||
if response.is_cohorted
|
||||
source = "script#_inline_discussion_cohorted"
|
||||
else
|
||||
source = "script#_inline_discussion"
|
||||
|
||||
$discussion = $(Mustache.render $(source).html(), {'threads':response.discussion_data, 'discussionId': discussionId, 'allow_anonymous_to_peers': allow_anonymous_to_peers, 'allow_anonymous': allow_anonymous, 'cohorts':cohorts})
|
||||
if @$('section.discussion').length
|
||||
@$('section.discussion').replaceWith($discussion)
|
||||
else
|
||||
|
||||
@@ -9,6 +9,7 @@ if Backbone?
|
||||
"click .browse-topic-drop-search-input": "ignoreClick"
|
||||
"click .post-list .list-item a": "threadSelected"
|
||||
"click .post-list .more-pages a": "loadMorePages"
|
||||
"change .cohort-options": "chooseCohort"
|
||||
'keyup .browse-topic-drop-search-input': DiscussionFilter.filterDrop
|
||||
|
||||
initialize: ->
|
||||
@@ -128,10 +129,20 @@ if Backbone?
|
||||
switch @mode
|
||||
when 'search'
|
||||
options.search_text = @current_search
|
||||
if @group_id
|
||||
options.group_id = @group_id
|
||||
when 'followed'
|
||||
options.user_id = window.user.id
|
||||
options.group_id = "all"
|
||||
when 'commentables'
|
||||
options.commentable_ids = @discussionIds
|
||||
if @group_id
|
||||
options.group_id = @group_id
|
||||
when 'all'
|
||||
if @group_id
|
||||
options.group_id = @group_id
|
||||
|
||||
|
||||
@collection.retrieveAnotherPage(@mode, options, {sort_key: @sortBy})
|
||||
|
||||
renderThread: (thread) =>
|
||||
@@ -263,13 +274,25 @@ if Backbone?
|
||||
if discussionId == "#all"
|
||||
@discussionIds = ""
|
||||
@$(".post-search-field").val("")
|
||||
@$('.cohort').show()
|
||||
@retrieveAllThreads()
|
||||
else if discussionId == "#following"
|
||||
@retrieveFollowed(event)
|
||||
@$('.cohort').hide()
|
||||
else
|
||||
discussionIds = _.map item.find(".board-name[data-discussion_id]"), (board) -> $(board).data("discussion_id").id
|
||||
@retrieveDiscussions(discussionIds)
|
||||
|
||||
|
||||
if $(event.target).attr('cohorted') == "True"
|
||||
@retrieveDiscussions(discussionIds, "function(){$('.cohort').show();}")
|
||||
else
|
||||
@retrieveDiscussions(discussionIds, "function(){$('.cohort').hide();}")
|
||||
|
||||
chooseCohort: (event) ->
|
||||
@group_id = @$('.cohort-options :selected').val()
|
||||
@collection.current_page = 0
|
||||
@collection.reset()
|
||||
@loadMorePages(event)
|
||||
|
||||
retrieveDiscussion: (discussion_id, callback=null) ->
|
||||
url = DiscussionUtil.urlFor("retrieve_discussion", discussion_id)
|
||||
DiscussionUtil.safeAjax
|
||||
|
||||
@@ -16,7 +16,10 @@ if Backbone?
|
||||
@$delegateElement = @$local
|
||||
|
||||
render: ->
|
||||
@template = DiscussionUtil.getTemplate("_inline_thread")
|
||||
if @model.has('group_id')
|
||||
@template = DiscussionUtil.getTemplate("_inline_thread_cohorted")
|
||||
else
|
||||
@template = DiscussionUtil.getTemplate("_inline_thread")
|
||||
|
||||
if not @model.has('abbreviatedBody')
|
||||
@abbreviateBody()
|
||||
|
||||
@@ -25,6 +25,7 @@ if Backbone?
|
||||
event.preventDefault()
|
||||
title = @$(".new-post-title").val()
|
||||
body = @$(".new-post-body").find(".wmd-input").val()
|
||||
group = @$(".new-post-group option:selected").attr("value")
|
||||
|
||||
# TODO tags: commenting out til we know what to do with them
|
||||
#tags = @$(".new-post-tags").val()
|
||||
@@ -45,6 +46,7 @@ if Backbone?
|
||||
data:
|
||||
title: title
|
||||
body: body
|
||||
group_id: group
|
||||
|
||||
# TODO tags: commenting out til we know what to do with them
|
||||
#tags: tags
|
||||
|
||||
@@ -14,8 +14,14 @@ if Backbone?
|
||||
@setSelectedTopic()
|
||||
|
||||
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
|
||||
|
||||
@$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
|
||||
|
||||
|
||||
if @$($(".topic_menu li a")[0]).attr('cohorted') != "True"
|
||||
$('.choose-cohort').hide();
|
||||
|
||||
|
||||
|
||||
events:
|
||||
"submit .new-post-form": "createPost"
|
||||
"click .topic_dropdown_button": "toggleTopicDropdown"
|
||||
@@ -65,6 +71,11 @@ if Backbone?
|
||||
@topicText = @getFullTopicName($target)
|
||||
@topicId = $target.data('discussion_id')
|
||||
@setSelectedTopic()
|
||||
if $target.attr('cohorted') == "True"
|
||||
$('.choose-cohort').show();
|
||||
else
|
||||
$('.choose-cohort').hide();
|
||||
|
||||
|
||||
setSelectedTopic: ->
|
||||
@dropdownButton.html(@fitName(@topicText) + ' <span class="drop-arrow">▾</span>')
|
||||
@@ -116,6 +127,7 @@ if Backbone?
|
||||
title = @$(".new-post-title").val()
|
||||
body = @$(".new-post-body").find(".wmd-input").val()
|
||||
tags = @$(".new-post-tags").val()
|
||||
group = @$(".new-post-group option:selected").attr("value")
|
||||
|
||||
anonymous = false || @$("input.discussion-anonymous").is(":checked")
|
||||
anonymous_to_peers = false || @$("input.discussion-anonymous-to-peers").is(":checked")
|
||||
@@ -137,6 +149,7 @@ if Backbone?
|
||||
anonymous: anonymous
|
||||
anonymous_to_peers: anonymous_to_peers
|
||||
auto_subscribe: follow
|
||||
group_id: group
|
||||
error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors"))
|
||||
success: (response, textStatus) =>
|
||||
# TODO: Move this out of the callback, this makes it feel sluggish
|
||||
|
||||
763
common/static/css/pdfviewer.css
Normal file
@@ -0,0 +1,763 @@
|
||||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
background-color: #404040;
|
||||
background-image: url(vendor/pdfjs/images/texture.png);
|
||||
}
|
||||
|
||||
body,
|
||||
input,
|
||||
button,
|
||||
select {
|
||||
font: message-box;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#viewerContainer.presentationControls {
|
||||
cursor: default;
|
||||
}
|
||||
*/
|
||||
|
||||
/* outer/inner center provides horizontal center */
|
||||
.outerCenter {
|
||||
float: right;
|
||||
position: relative;
|
||||
right: 50%;
|
||||
}
|
||||
|
||||
.innerCenter {
|
||||
float: right;
|
||||
position: relative;
|
||||
right: -50%;
|
||||
}
|
||||
|
||||
#outerContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#mainContainer {
|
||||
/* position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;*/
|
||||
-webkit-transition-duration: 200ms;
|
||||
-webkit-transition-timing-function: ease;
|
||||
-moz-transition-duration: 200ms;
|
||||
-moz-transition-timing-function: ease;
|
||||
-ms-transition-duration: 200ms;
|
||||
-ms-transition-timing-function: ease;
|
||||
-o-transition-duration: 200ms;
|
||||
-o-transition-timing-function: ease;
|
||||
transition-duration: 200ms;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
#viewerContainer {
|
||||
/* overflow: auto; */
|
||||
box-shadow: inset 1px 0 0 hsla(0,0%,100%,.05);
|
||||
/* position: absolute;
|
||||
top: 32px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0; */
|
||||
/* switch to using these instead: */
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
/* position: absolute; */
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 32px;
|
||||
z-index: 9999;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#toolbarContainer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
#toolbarViewer {
|
||||
position: relative;
|
||||
height: 32px;
|
||||
background-image: url(vendor/pdfjs/images/texture.png),
|
||||
-webkit-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
|
||||
background-image: url(vendor/pdfjs/images/texture.png),
|
||||
-moz-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
|
||||
background-image: url(vendor/pdfjs/images/texture.png),
|
||||
-ms-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
|
||||
background-image: url(vendor/pdfjs/images/texture.png),
|
||||
-o-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
|
||||
background-image: url(vendor/pdfjs/images/texture.png),
|
||||
linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
|
||||
box-shadow: inset 1px 0 0 hsla(0,0%,100%,.08),
|
||||
inset 0 1px 1px hsla(0,0%,0%,.15),
|
||||
inset 0 -1px 0 hsla(0,0%,100%,.05),
|
||||
0 1px 0 hsla(0,0%,0%,.15),
|
||||
0 1px 1px hsla(0,0%,0%,.1);
|
||||
}
|
||||
|
||||
#toolbarViewerLeft {
|
||||
margin-left: -1px;
|
||||
/* position: absolute; */
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#toolbarViewerRight {
|
||||
/* position: absolute; */
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#toolbarViewerLeft > *,
|
||||
#toolbarViewerMiddle > *,
|
||||
#toolbarViewerRight > * {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.splitToolbarButton {
|
||||
margin: 3px 2px 4px 0;
|
||||
display: inline-block;
|
||||
}
|
||||
.splitToolbarButton > .toolbarButton {
|
||||
border-radius: 0;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
border: 0 none;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
width: 32px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.toolbarButton > span {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toolbarButton[disabled] {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.toolbarButton.group {
|
||||
margin-right:0;
|
||||
}
|
||||
|
||||
.splitToolbarButton.toggled .toolbarButton {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.splitToolbarButton:hover > .toolbarButton,
|
||||
.splitToolbarButton:focus > .toolbarButton,
|
||||
.splitToolbarButton.toggled > .toolbarButton,
|
||||
.toolbarButton.textButton {
|
||||
background-color: hsla(0,0%,0%,.12);
|
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-clip: padding-box;
|
||||
border: 1px solid hsla(0,0%,0%,.35);
|
||||
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||
0 0 1px hsla(0,0%,100%,.15) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.05);
|
||||
-webkit-transition-property: background-color, border-color, box-shadow;
|
||||
-webkit-transition-duration: 150ms;
|
||||
-webkit-transition-timing-function: ease;
|
||||
-moz-transition-property: background-color, border-color, box-shadow;
|
||||
-moz-transition-duration: 150ms;
|
||||
-moz-transition-timing-function: ease;
|
||||
-ms-transition-property: background-color, border-color, box-shadow;
|
||||
-ms-transition-duration: 150ms;
|
||||
-ms-transition-timing-function: ease;
|
||||
-o-transition-property: background-color, border-color, box-shadow;
|
||||
-o-transition-duration: 150ms;
|
||||
-o-transition-timing-function: ease;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: ease;
|
||||
|
||||
}
|
||||
.splitToolbarButton > .toolbarButton:hover,
|
||||
.splitToolbarButton > .toolbarButton:focus,
|
||||
.dropdownToolbarButton:hover,
|
||||
.toolbarButton.textButton:hover,
|
||||
.toolbarButton.textButton:focus {
|
||||
background-color: hsla(0,0%,0%,.2);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||
0 0 1px hsla(0,0%,100%,.15) inset,
|
||||
0 0 1px hsla(0,0%,0%,.05);
|
||||
z-index: 199;
|
||||
}
|
||||
.splitToolbarButton > .toolbarButton:first-child {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
margin-right: -1px;
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
.splitToolbarButton > .toolbarButton:last-child {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
margin-left: -1px;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
border-left-color: transparent;
|
||||
}
|
||||
.splitToolbarButtonSeparator {
|
||||
padding: 8px 0;
|
||||
width: 1px;
|
||||
background-color: hsla(0,0%,00%,.5);
|
||||
z-index: 99;
|
||||
box-shadow: 0 0 0 1px hsla(0,0%,100%,.08);
|
||||
display: inline-block;
|
||||
margin: 5px 0;
|
||||
float:left;
|
||||
}
|
||||
}
|
||||
.splitToolbarButton:hover > .splitToolbarButtonSeparator,
|
||||
.splitToolbarButton.toggled > .splitToolbarButtonSeparator {
|
||||
padding: 12px 0;
|
||||
margin: 1px 0;
|
||||
box-shadow: 0 0 0 1px hsla(0,0%,100%,.03);
|
||||
-webkit-transition-property: padding;
|
||||
-webkit-transition-duration: 10ms;
|
||||
-webkit-transition-timing-function: ease;
|
||||
-moz-transition-property: padding;
|
||||
-moz-transition-duration: 10ms;
|
||||
-moz-transition-timing-function: ease;
|
||||
-ms-transition-property: padding;
|
||||
-ms-transition-duration: 10ms;
|
||||
-ms-transition-timing-function: ease;
|
||||
-o-transition-property: padding;
|
||||
-o-transition-duration: 10ms;
|
||||
-o-transition-timing-function: ease;
|
||||
transition-property: padding;
|
||||
transition-duration: 10ms;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
.toolbarButton,
|
||||
.dropdownToolbarButton {
|
||||
min-width: 16px;
|
||||
padding: 2px 6px 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
color: hsl(0,0%,95%);
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select:none;
|
||||
-ms-user-select:none;
|
||||
/* Opera does not support user-select, use <... unselectable="on"> instead */
|
||||
cursor: default;
|
||||
-webkit-transition-property: background-color, border-color, box-shadow;
|
||||
-webkit-transition-duration: 150ms;
|
||||
-webkit-transition-timing-function: ease;
|
||||
-moz-transition-property: background-color, border-color, box-shadow;
|
||||
-moz-transition-duration: 150ms;
|
||||
-moz-transition-timing-function: ease;
|
||||
-ms-transition-property: background-color, border-color, box-shadow;
|
||||
-ms-transition-duration: 150ms;
|
||||
-ms-transition-timing-function: ease;
|
||||
-o-transition-property: background-color, border-color, box-shadow;
|
||||
-o-transition-duration: 150ms;
|
||||
-o-transition-timing-function: ease;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
margin: 3px 2px 4px 0;
|
||||
}
|
||||
|
||||
.toolbarButton:hover,
|
||||
.toolbarButton:focus,
|
||||
.dropdownToolbarButton {
|
||||
background-color: hsla(0,0%,0%,.12);
|
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-clip: padding-box;
|
||||
border: 1px solid hsla(0,0%,0%,.35);
|
||||
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||
0 0 1px hsla(0,0%,100%,.15) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.05);
|
||||
}
|
||||
|
||||
.toolbarButton:hover:active,
|
||||
.dropdownToolbarButton:hover:active {
|
||||
background-color: hsla(0,0%,0%,.2);
|
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.4) hsla(0,0%,0%,.45);
|
||||
box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
|
||||
0 0 1px hsla(0,0%,0%,.2) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.05);
|
||||
-webkit-transition-property: background-color, border-color, box-shadow;
|
||||
-webkit-transition-duration: 10ms;
|
||||
-webkit-transition-timing-function: linear;
|
||||
-moz-transition-property: background-color, border-color, box-shadow;
|
||||
-moz-transition-duration: 10ms;
|
||||
-moz-transition-timing-function: linear;
|
||||
-ms-transition-property: background-color, border-color, box-shadow;
|
||||
-ms-transition-duration: 10ms;
|
||||
-ms-transition-timing-function: linear;
|
||||
-o-transition-property: background-color, border-color, box-shadow;
|
||||
-o-transition-duration: 10ms;
|
||||
-o-transition-timing-function: linear;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 10ms;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.toolbarButton.toggled,
|
||||
.splitToolbarButton.toggled > .toolbarButton.toggled {
|
||||
background-color: hsla(0,0%,0%,.3);
|
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.45) hsla(0,0%,0%,.5);
|
||||
box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
|
||||
0 0 1px hsla(0,0%,0%,.2) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.05);
|
||||
-webkit-transition-property: background-color, border-color, box-shadow;
|
||||
-webkit-transition-duration: 10ms;
|
||||
-webkit-transition-timing-function: linear;
|
||||
-moz-transition-property: background-color, border-color, box-shadow;
|
||||
-moz-transition-duration: 10ms;
|
||||
-moz-transition-timing-function: linear;
|
||||
-ms-transition-property: background-color, border-color, box-shadow;
|
||||
-ms-transition-duration: 10ms;
|
||||
-ms-transition-timing-function: linear;
|
||||
-o-transition-property: background-color, border-color, box-shadow;
|
||||
-o-transition-duration: 10ms;
|
||||
-o-transition-timing-function: linear;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 10ms;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.toolbarButton.toggled:hover:active,
|
||||
.splitToolbarButton.toggled > .toolbarButton.toggled:hover:active {
|
||||
background-color: hsla(0,0%,0%,.4);
|
||||
border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.5) hsla(0,0%,0%,.55);
|
||||
box-shadow: 0 1px 1px hsla(0,0%,0%,.2) inset,
|
||||
0 0 1px hsla(0,0%,0%,.3) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.05);
|
||||
}
|
||||
|
||||
.dropdownToolbarButton {
|
||||
max-width: 120px;
|
||||
padding: 3px 2px 2px;
|
||||
overflow: hidden;
|
||||
background: url(vendor/pdfjs/images/toolbarButton-menuArrows.png) no-repeat;
|
||||
background-position: 95%;
|
||||
}
|
||||
|
||||
.dropdownToolbarButton > select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none; /* in the future this might matter, see bugzilla bug #649849 */
|
||||
min-width: 140px;
|
||||
font-size: 12px;
|
||||
color: hsl(0,0%,95%);
|
||||
margin:0;
|
||||
padding:0;
|
||||
border:none;
|
||||
background: rgba(0,0,0,0); /* Opera does not support 'transparent' <select> background */
|
||||
}
|
||||
|
||||
.dropdownToolbarButton > select > option {
|
||||
background: hsl(0,0%,24%);
|
||||
}
|
||||
|
||||
#customScaleOption {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#pageWidthOption {
|
||||
border-bottom: 1px rgba(255, 255, 255, .5) solid;
|
||||
}
|
||||
|
||||
.splitToolbarButton:first-child,
|
||||
.toolbarButton:first-child,
|
||||
.splitToolbarButton:last-child,
|
||||
.toolbarButton:last-child {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.toolbarButtonSpacer {
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.toolbarButtonFlexibleSpacer {
|
||||
-webkit-box-flex: 1;
|
||||
-moz-box-flex: 1;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.toolbarButton#sidebarToggle::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/toolbarButton-sidebarToggle.png);
|
||||
}
|
||||
|
||||
.toolbarButton.findPrevious::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/findbarButton-previous.png);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.toolbarButton.findNext::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/findbarButton-next.png);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.toolbarButton.pageUp::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/toolbarButton-pageUp.png);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.toolbarButton.pageDown::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/toolbarButton-pageDown.png);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.toolbarButton.zoomOut::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/toolbarButton-zoomOut.png);
|
||||
}
|
||||
|
||||
.toolbarButton.zoomIn::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/toolbarButton-zoomIn.png);
|
||||
}
|
||||
|
||||
.toolbarButton.fullscreen::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/toolbarButton-fullscreen.png);
|
||||
}
|
||||
|
||||
.toolbarButton.print::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/toolbarButton-print.png);
|
||||
}
|
||||
|
||||
.toolbarButton.openFile::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/toolbarButton-openFile.png);
|
||||
}
|
||||
|
||||
.toolbarButton.download::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/toolbarButton-download.png);
|
||||
}
|
||||
|
||||
.toolbarButton.bookmark {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
margin-top: 3px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.toolbarButton.bookmark::before {
|
||||
content: url(vendor/pdfjs/images/toolbarButton-bookmark.png);
|
||||
}
|
||||
|
||||
#viewThumbnail.toolbarButton::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/toolbarButton-viewThumbnail.png);
|
||||
}
|
||||
|
||||
#viewOutline.toolbarButton::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/toolbarButton-viewOutline.png);
|
||||
}
|
||||
|
||||
#viewFind.toolbarButton::before {
|
||||
display: inline-block;
|
||||
content: url(vendor/pdfjs/images/toolbarButton-search.png);
|
||||
}
|
||||
|
||||
|
||||
.toolbarField {
|
||||
padding: 3px 6px;
|
||||
margin: 4px 0 4px 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
background-color: hsla(0,0%,100%,.09);
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-clip: padding-box;
|
||||
border: 1px solid hsla(0,0%,0%,.35);
|
||||
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,0%,.05) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.05);
|
||||
color: hsl(0,0%,95%);
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
outline-style: none;
|
||||
-moz-transition-property: background-color, border-color, box-shadow;
|
||||
-moz-transition-duration: 150ms;
|
||||
-moz-transition-timing-function: ease;
|
||||
}
|
||||
|
||||
.toolbarField[type=checkbox] {
|
||||
display: inline-block;
|
||||
margin: 8px 0px;
|
||||
}
|
||||
|
||||
.toolbarField.pageNumber {
|
||||
min-width: 16px;
|
||||
text-align: right;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.toolbarField.pageNumber::-webkit-inner-spin-button,
|
||||
.toolbarField.pageNumber::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.toolbarField:hover {
|
||||
background-color: hsla(0,0%,100%,.11);
|
||||
border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.43) hsla(0,0%,0%,.45);
|
||||
}
|
||||
|
||||
.toolbarField:focus {
|
||||
background-color: hsla(0,0%,100%,.15);
|
||||
border-color: hsla(204,100%,65%,.8) hsla(204,100%,65%,.85) hsla(204,100%,65%,.9);
|
||||
}
|
||||
|
||||
.toolbarLabel {
|
||||
min-width: 16px;
|
||||
padding: 3px 6px 3px 2px;
|
||||
margin: 4px 2px 4px 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
color: hsl(0,0%,85%);
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
text-align: left;
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select:none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
canvas {
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pdfpage {
|
||||
direction: ltr;
|
||||
width: 816px;
|
||||
height: 1056px;
|
||||
margin: 10px auto;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-box-shadow: 0px 4px 10px #000;
|
||||
-moz-box-shadow: 0px 4px 10px #000;
|
||||
box-shadow: 0px 4px 10px #000;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.pdfpage > a {
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.pdfpage > a:hover {
|
||||
opacity: 0.2;
|
||||
background: #ff0;
|
||||
-webkit-box-shadow: 0px 2px 10px #ff0;
|
||||
-moz-box-shadow: 0px 2px 10px #ff0;
|
||||
box-shadow: 0px 2px 10px #ff0;
|
||||
}
|
||||
|
||||
.loadingIcon {
|
||||
/* position: absolute; */
|
||||
display: block;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url('vendor/pdfjs/images/loading-icon.gif') center no-repeat;
|
||||
}
|
||||
|
||||
#loadingBox {
|
||||
/* position: absolute; */
|
||||
top: 50%;
|
||||
margin-top: -25px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
color: #ddd;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#loadingBar {
|
||||
display: inline-block;
|
||||
clear: both;
|
||||
margin: 0px;
|
||||
margin-top: 5px;
|
||||
line-height: 0;
|
||||
border-radius: 2px;
|
||||
width: 200px;
|
||||
height: 25px;
|
||||
|
||||
background-color: hsla(0,0%,0%,.3);
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
border: 1px solid #000;
|
||||
box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
|
||||
0 0 1px hsla(0,0%,0%,.2) inset,
|
||||
0 0 1px 1px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
#loadingBar .progress {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
|
||||
background: #666;
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2b2b2), color-stop(100%,#898989));
|
||||
background: -webkit-linear-gradient(top, #b2b2b2 0%,#898989 100%);
|
||||
background: -moz-linear-gradient(top, #b2b2b2 0%,#898989 100%);
|
||||
background: -ms-linear-gradient(top, #b2b2b2 0%,#898989 100%);
|
||||
background: -o-linear-gradient(top, #b2b2b2 0%,#898989 100%);
|
||||
background: linear-gradient(top, #b2b2b2 0%,#898989 100%);
|
||||
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#loadingBar .progress.full {
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
#loadingBar .progress.indeterminate {
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
background-image: -moz-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040);
|
||||
background-image: -webkit-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040);
|
||||
background-image: -ms-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040);
|
||||
background-image: -o-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040);
|
||||
background-size: 75px 25px;
|
||||
-moz-animation: progressIndeterminate 1s linear infinite;
|
||||
-webkit-animation: progressIndeterminate 1s linear infinite;
|
||||
}
|
||||
|
||||
@-moz-keyframes progressIndeterminate {
|
||||
from { background-position: 0px 0px; }
|
||||
to { background-position: 75px 0px; }
|
||||
}
|
||||
|
||||
@-webkit-keyframes progressIndeterminate {
|
||||
from { background-position: 0px 0px; }
|
||||
to { background-position: 75px 0px; }
|
||||
}
|
||||
|
||||
/* TODO: file FF bug to support ::-moz-selection:window-inactive
|
||||
so we can override the opaque grey background when the window is inactive;
|
||||
see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */
|
||||
::selection { background:rgba(0,0,255,0.3); }
|
||||
::-moz-selection { background:rgba(0,0,255,0.3); }
|
||||
|
||||
.clearBoth {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media all and (max-width: 950px) {
|
||||
#outerContainer.sidebarMoving .outerCenter,
|
||||
#outerContainer.sidebarOpen .outerCenter {
|
||||
float: left;
|
||||
left: 180px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media all and (max-width: 770px) {
|
||||
.outerCenter {
|
||||
float: left;
|
||||
left: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 600px) {
|
||||
#toolbarViewerRight {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 500px) {
|
||||
#scaleSelectContainer, #pageNumberLabel {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
10
common/static/css/vendor/pdfjs/images/annotation-check.svg
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40">
|
||||
<path
|
||||
d="M 1.5006714,23.536225 6.8925879,18.994244 14.585721,26.037937 34.019683,4.5410479 38.499329,9.2235032 14.585721,35.458952 z"
|
||||
id="path4"
|
||||
style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.25402856;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 392 B |
15
common/static/css/vendor/pdfjs/images/annotation-comment.svg
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="40"
|
||||
width="40">
|
||||
<rect
|
||||
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
width="33.76017"
|
||||
height="33.76017"
|
||||
x="3.119915"
|
||||
y="3.119915" />
|
||||
<path
|
||||
d="m 20.677967,8.54499 c -7.342801,0 -13.295293,4.954293 -13.295293,11.065751 0,2.088793 0.3647173,3.484376 1.575539,5.150563 L 6.0267418,31.45501 13.560595,29.011117 c 2.221262,1.387962 4.125932,1.665377 7.117372,1.665377 7.3428,0 13.295291,-4.954295 13.295291,-11.065753 0,-6.111458 -5.952491,-11.065751 -13.295291,-11.065751 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.93031836;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 860 B |
25
common/static/css/vendor/pdfjs/images/annotation-help.svg
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40">
|
||||
<g
|
||||
transform="translate(0,-60)"
|
||||
id="layer1">
|
||||
<rect
|
||||
width="36.460953"
|
||||
height="34.805603"
|
||||
x="1.7695236"
|
||||
y="62.597198"
|
||||
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.30826771;stroke-opacity:1" />
|
||||
<g
|
||||
transform="matrix(0.88763677,0,0,0.88763677,2.2472646,8.9890584)">
|
||||
<path
|
||||
d="M 20,64.526342 C 11.454135,64.526342 4.5263421,71.454135 4.5263421,80 4.5263421,88.545865 11.454135,95.473658 20,95.473658 28.545865,95.473658 35.473658,88.545865 35.473658,80 35.473658,71.454135 28.545865,64.526342 20,64.526342 z m -0.408738,9.488564 c 3.527079,0 6.393832,2.84061 6.393832,6.335441 0,3.494831 -2.866753,6.335441 -6.393832,6.335441 -3.527079,0 -6.393832,-2.84061 -6.393832,-6.335441 0,-3.494831 2.866753,-6.335441 6.393832,-6.335441 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.02768445;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 7.2335209,71.819938 4.9702591,4.161823 c -1.679956,2.581606 -1.443939,6.069592 0.159325,8.677725 l -5.1263071,3.424463 c 0.67516,1.231452 3.0166401,3.547686 4.2331971,4.194757 l 3.907728,-4.567277 c 2.541952,1.45975 5.730694,1.392161 8.438683,-0.12614 l 3.469517,6.108336 c 1.129779,-0.44367 4.742234,-3.449633 5.416358,-5.003859 l -5.46204,-4.415541 c 1.44319,-2.424098 1.651175,-5.267515 0.557303,-7.748623 l 5.903195,-3.833951 C 33.14257,71.704996 30.616217,69.018606 29.02952,67.99296 l -4.118813,4.981678 C 22.411934,71.205099 18.900853,70.937534 16.041319,72.32916 l -3.595408,-5.322091 c -1.345962,0.579488 -4.1293881,2.921233 -5.2123901,4.812869 z m 8.1010311,3.426672 c 2.75284,-2.446266 6.769149,-2.144694 9.048998,0.420874 2.279848,2.56557 2.113919,6.596919 -0.638924,9.043185 -2.752841,2.446267 -6.775754,2.13726 -9.055604,-0.428308 -2.279851,-2.565568 -2.107313,-6.589485 0.64553,-9.035751 z"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
9
common/static/css/vendor/pdfjs/images/annotation-insert.svg
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64">
|
||||
<path
|
||||
d="M 32.003143,1.4044602 57.432701,62.632577 6.5672991,62.627924 z"
|
||||
style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:1.00493038;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 385 B |
10
common/static/css/vendor/pdfjs/images/annotation-key.svg
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64">
|
||||
<path
|
||||
d="M 25.470843,9.4933766 C 25.30219,12.141818 30.139101,14.445969 34.704831,13.529144 40.62635,12.541995 41.398833,7.3856498 35.97505,5.777863 31.400921,4.1549155 25.157674,6.5445892 25.470843,9.4933766 z M 4.5246282,17.652051 C 4.068249,11.832873 9.2742983,5.9270407 18.437379,3.0977088 29.751911,-0.87185184 45.495663,1.4008022 53.603953,7.1104009 c 9.275765,6.1889221 7.158128,16.2079421 -3.171076,21.5939521 -1.784316,1.635815 -6.380222,1.21421 -7.068351,3.186186 -1.04003,0.972427 -1.288046,2.050158 -1.232864,3.168203 1.015111,2.000108 -3.831548,1.633216 -3.270553,3.759574 0.589477,5.264544 -0.179276,10.53738 -0.362842,15.806257 -0.492006,2.184998 1.163456,4.574232 -0.734888,6.610642 -2.482919,2.325184 -7.30604,2.189143 -9.193497,-0.274767 -2.733688,-1.740626 -8.254447,-3.615254 -6.104247,-6.339626 3.468112,-1.708686 -2.116197,-3.449897 0.431242,-5.080274 5.058402,-1.39256 -2.393215,-2.304318 -0.146889,-4.334645 3.069198,-0.977415 2.056986,-2.518352 -0.219121,-3.540397 1.876567,-1.807151 1.484149,-4.868919 -2.565455,-5.942205 0.150866,-1.805474 2.905737,-4.136876 -1.679967,-5.20493 C 10.260902,27.882167 4.6872697,22.95045 4.5245945,17.652051 z"
|
||||
id="path604"
|
||||
style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.72665179;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
10
common/static/css/vendor/pdfjs/images/annotation-newparagraph.svg
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64">
|
||||
<path
|
||||
d="M 32.003143,10.913072 57.432701,53.086929 6.567299,53.083723 z"
|
||||
id="path2985"
|
||||
style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:0.83403099;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 403 B |
41
common/static/css/vendor/pdfjs/images/annotation-note.svg
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40">
|
||||
<rect
|
||||
width="36.075428"
|
||||
height="31.096582"
|
||||
x="1.962286"
|
||||
y="4.4517088"
|
||||
id="rect4"
|
||||
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.23004246;stroke-opacity:1" />
|
||||
<rect
|
||||
width="27.96859"
|
||||
height="1.5012145"
|
||||
x="6.0157046"
|
||||
y="10.285"
|
||||
id="rect6"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
width="27.96859"
|
||||
height="0.85783684"
|
||||
x="6.0157056"
|
||||
y="23.21689"
|
||||
id="rect8"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
width="27.96859"
|
||||
height="0.85783684"
|
||||
x="5.8130345"
|
||||
y="28.964394"
|
||||
id="rect10"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
width="27.96859"
|
||||
height="0.85783684"
|
||||
x="6.0157046"
|
||||
y="17.426493"
|
||||
id="rect12"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1018 B |
15
common/static/css/vendor/pdfjs/images/annotation-paragraph.svg
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40">
|
||||
<rect
|
||||
width="33.76017"
|
||||
height="33.76017"
|
||||
x="3.119915"
|
||||
y="3.119915"
|
||||
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 17.692678,34.50206 0,-16.182224 c -1.930515,-0.103225 -3.455824,-0.730383 -4.57593,-1.881473 -1.12011,-1.151067 -1.680164,-2.619596 -1.680164,-4.405591 0,-1.992435 0.621995,-3.5796849 1.865988,-4.7617553 1.243989,-1.1820288 3.06352,-1.7730536 5.458598,-1.7730764 l 9.802246,0 0,2.6789711 -2.229895,0 0,26.3251486 -2.632515,0 0,-26.3251486 -3.45324,0 0,26.3251486 z"
|
||||
style="font-size:29.42051125px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.07795751;stroke-opacity:1;font-family:Arial;-inkscape-font-specification:Arial" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
common/static/css/vendor/pdfjs/images/findbarButton-next-rtl.png
vendored
Normal file
|
After Width: | Height: | Size: 371 B |
BIN
common/static/css/vendor/pdfjs/images/findbarButton-next.png
vendored
Normal file
|
After Width: | Height: | Size: 381 B |
BIN
common/static/css/vendor/pdfjs/images/findbarButton-previous-rtl.png
vendored
Normal file
|
After Width: | Height: | Size: 381 B |
BIN
common/static/css/vendor/pdfjs/images/findbarButton-previous.png
vendored
Normal file
|
After Width: | Height: | Size: 371 B |
BIN
common/static/css/vendor/pdfjs/images/loading-icon.gif
vendored
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
common/static/css/vendor/pdfjs/images/loading-small.png
vendored
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
common/static/css/vendor/pdfjs/images/texture.png
vendored
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-bookmark.png
vendored
Normal file
|
After Width: | Height: | Size: 244 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-download.png
vendored
Normal file
|
After Width: | Height: | Size: 512 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-fullscreen.png
vendored
Normal file
|
After Width: | Height: | Size: 491 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-menuArrows.png
vendored
Normal file
|
After Width: | Height: | Size: 237 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-openFile.png
vendored
Normal file
|
After Width: | Height: | Size: 417 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-pageDown-rtl.png
vendored
Normal file
|
After Width: | Height: | Size: 558 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-pageDown.png
vendored
Normal file
|
After Width: | Height: | Size: 353 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-pageUp-rtl.png
vendored
Normal file
|
After Width: | Height: | Size: 426 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-pageUp.png
vendored
Normal file
|
After Width: | Height: | Size: 344 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-print.png
vendored
Normal file
|
After Width: | Height: | Size: 474 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-search.png
vendored
Normal file
|
After Width: | Height: | Size: 503 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle.png
vendored
Normal file
|
After Width: | Height: | Size: 349 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline.png
vendored
Normal file
|
After Width: | Height: | Size: 300 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-viewThumbnail.png
vendored
Normal file
|
After Width: | Height: | Size: 211 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-zoomIn.png
vendored
Normal file
|
After Width: | Height: | Size: 228 B |
BIN
common/static/css/vendor/pdfjs/images/toolbarButton-zoomOut.png
vendored
Normal file
|
After Width: | Height: | Size: 143 B |
1431
common/static/css/vendor/pdfjs/viewer.css
vendored
Normal file
@@ -0,0 +1,1431 @@
|
||||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
background-color: #404040;
|
||||
background-image: url(images/texture.png);
|
||||
}
|
||||
|
||||
body,
|
||||
input,
|
||||
button,
|
||||
select {
|
||||
font: message-box;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#viewerContainer:-webkit-full-screen {
|
||||
top: 0px;
|
||||
border-top: 2px solid transparent;
|
||||
background-color: #404040;
|
||||
background-image: url(images/texture.png);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
#viewerContainer:-moz-full-screen {
|
||||
top: 0px;
|
||||
border-top: 2px solid transparent;
|
||||
background-color: #404040;
|
||||
background-image: url(images/texture.png);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
#viewerContainer:fullscreen {
|
||||
top: 0px;
|
||||
border-top: 2px solid transparent;
|
||||
background-color: #404040;
|
||||
background-image: url(images/texture.png);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
|
||||
:-webkit-full-screen .page {
|
||||
margin-bottom: 100%;
|
||||
}
|
||||
|
||||
:-moz-full-screen .page {
|
||||
margin-bottom: 100%;
|
||||
}
|
||||
|
||||
:fullscreen .page {
|
||||
margin-bottom: 100%;
|
||||
}
|
||||
|
||||
#viewerContainer.presentationControls {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* outer/inner center provides horizontal center */
|
||||
html[dir='ltr'] .outerCenter {
|
||||
float: right;
|
||||
position: relative;
|
||||
right: 50%;
|
||||
}
|
||||
html[dir='rtl'] .outerCenter {
|
||||
float: left;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
}
|
||||
html[dir='ltr'] .innerCenter {
|
||||
float: right;
|
||||
position: relative;
|
||||
right: -50%;
|
||||
}
|
||||
html[dir='rtl'] .innerCenter {
|
||||
float: left;
|
||||
position: relative;
|
||||
left: -50%;
|
||||
}
|
||||
|
||||
#outerContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#sidebarContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 200px;
|
||||
visibility: hidden;
|
||||
-webkit-transition-duration: 200ms;
|
||||
-webkit-transition-timing-function: ease;
|
||||
-moz-transition-duration: 200ms;
|
||||
-moz-transition-timing-function: ease;
|
||||
-ms-transition-duration: 200ms;
|
||||
-ms-transition-timing-function: ease;
|
||||
-o-transition-duration: 200ms;
|
||||
-o-transition-timing-function: ease;
|
||||
transition-duration: 200ms;
|
||||
transition-timing-function: ease;
|
||||
|
||||
}
|
||||
html[dir='ltr'] #sidebarContainer {
|
||||
-webkit-transition-property: left;
|
||||
-moz-transition-property: left;
|
||||
-ms-transition-property: left;
|
||||
-o-transition-property: left;
|
||||
transition-property: left;
|
||||
left: -200px;
|
||||
}
|
||||
html[dir='rtl'] #sidebarContainer {
|
||||
-webkit-transition-property: right;
|
||||
-ms-transition-property: right;
|
||||
-o-transition-property: right;
|
||||
transition-property: right;
|
||||
right: -200px;
|
||||
}
|
||||
|
||||
#outerContainer.sidebarMoving > #sidebarContainer,
|
||||
#outerContainer.sidebarOpen > #sidebarContainer {
|
||||
visibility: visible;
|
||||
}
|
||||
html[dir='ltr'] #outerContainer.sidebarOpen > #sidebarContainer {
|
||||
left: 0px;
|
||||
}
|
||||
html[dir='rtl'] #outerContainer.sidebarOpen > #sidebarContainer {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
#mainContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
-webkit-transition-duration: 200ms;
|
||||
-webkit-transition-timing-function: ease;
|
||||
-moz-transition-duration: 200ms;
|
||||
-moz-transition-timing-function: ease;
|
||||
-ms-transition-duration: 200ms;
|
||||
-ms-transition-timing-function: ease;
|
||||
-o-transition-duration: 200ms;
|
||||
-o-transition-timing-function: ease;
|
||||
transition-duration: 200ms;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer {
|
||||
-webkit-transition-property: left;
|
||||
-moz-transition-property: left;
|
||||
-ms-transition-property: left;
|
||||
-o-transition-property: left;
|
||||
transition-property: left;
|
||||
left: 200px;
|
||||
}
|
||||
html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer {
|
||||
-webkit-transition-property: right;
|
||||
-moz-transition-property: right;
|
||||
-ms-transition-property: right;
|
||||
-o-transition-property: right;
|
||||
transition-property: right;
|
||||
right: 200px;
|
||||
}
|
||||
|
||||
#sidebarContent {
|
||||
top: 32px;
|
||||
bottom: 0;
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
width: 200px;
|
||||
|
||||
background-color: hsla(0,0%,0%,.1);
|
||||
box-shadow: inset -1px 0 0 hsla(0,0%,0%,.25);
|
||||
}
|
||||
html[dir='ltr'] #sidebarContent {
|
||||
left: 0;
|
||||
}
|
||||
html[dir='rtl'] #sidebarContent {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#viewerContainer {
|
||||
overflow: auto;
|
||||
box-shadow: inset 1px 0 0 hsla(0,0%,100%,.05);
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 32px;
|
||||
z-index: 9999;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#toolbarContainer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#toolbarSidebar {
|
||||
width: 200px;
|
||||
height: 32px;
|
||||
background-image: url(images/texture.png),
|
||||
-webkit-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
|
||||
background-image: url(images/texture.png),
|
||||
-moz-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
|
||||
background-image: url(images/texture.png),
|
||||
-ms-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
|
||||
background-image: url(images/texture.png),
|
||||
-o-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
|
||||
background-image: url(images/texture.png),
|
||||
linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25),
|
||||
|
||||
inset 0 -1px 0 hsla(0,0%,100%,.05),
|
||||
0 1px 0 hsla(0,0%,0%,.15),
|
||||
0 0 1px hsla(0,0%,0%,.1);
|
||||
}
|
||||
|
||||
#toolbarViewer, .findbar {
|
||||
position: relative;
|
||||
height: 32px;
|
||||
background-image: url(images/texture.png),
|
||||
-webkit-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
|
||||
background-image: url(images/texture.png),
|
||||
-moz-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
|
||||
background-image: url(images/texture.png),
|
||||
-ms-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
|
||||
background-image: url(images/texture.png),
|
||||
-o-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
|
||||
background-image: url(images/texture.png),
|
||||
linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
|
||||
box-shadow: inset 1px 0 0 hsla(0,0%,100%,.08),
|
||||
inset 0 1px 1px hsla(0,0%,0%,.15),
|
||||
inset 0 -1px 0 hsla(0,0%,100%,.05),
|
||||
0 1px 0 hsla(0,0%,0%,.15),
|
||||
0 1px 1px hsla(0,0%,0%,.1);
|
||||
}
|
||||
|
||||
.findbar {
|
||||
top: 32px;
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
height: 32px;
|
||||
|
||||
min-width: 16px;
|
||||
padding: 0px 6px 0px 6px;
|
||||
margin: 4px 2px 4px 2px;
|
||||
color: hsl(0,0%,85%);
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
text-align: left;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
html[dir='ltr'] .findbar {
|
||||
left: 68px;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .findbar {
|
||||
right: 68px;
|
||||
}
|
||||
|
||||
.findbar label {
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select:none;
|
||||
}
|
||||
|
||||
#findInput[data-status="pending"] {
|
||||
background-image: url(images/loading-small.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: right;
|
||||
}
|
||||
|
||||
.doorHanger {
|
||||
border: 1px solid hsla(0,0%,0%,.5);
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.doorHanger:after, .doorHanger:before {
|
||||
bottom: 100%;
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.doorHanger:after {
|
||||
border-bottom-color: hsla(0,0%,32%,.99);
|
||||
border-width: 8px;
|
||||
}
|
||||
.doorHanger:before {
|
||||
border-bottom-color: hsla(0,0%,0%,.5);
|
||||
border-width: 9px;
|
||||
}
|
||||
|
||||
html[dir='ltr'] .doorHanger:after {
|
||||
left: 16px;
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
html[dir='ltr'] .doorHanger:before {
|
||||
left: 16px;
|
||||
margin-left: -9px;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .doorHanger:after {
|
||||
right: 16px;
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .doorHanger:before {
|
||||
right: 16px;
|
||||
margin-right: -9px;
|
||||
}
|
||||
|
||||
#findMsg {
|
||||
font-style: italic;
|
||||
color: #A6B7D0;
|
||||
}
|
||||
|
||||
.notFound {
|
||||
background-color: rgb(255, 137, 153);
|
||||
}
|
||||
|
||||
html[dir='ltr'] #toolbarViewerLeft {
|
||||
margin-left: -1px;
|
||||
}
|
||||
html[dir='rtl'] #toolbarViewerRight {
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
|
||||
html[dir='ltr'] #toolbarViewerLeft,
|
||||
html[dir='rtl'] #toolbarViewerRight {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
html[dir='ltr'] #toolbarViewerRight,
|
||||
html[dir='rtl'] #toolbarViewerLeft {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
html[dir='ltr'] #toolbarViewerLeft > *,
|
||||
html[dir='ltr'] #toolbarViewerMiddle > *,
|
||||
html[dir='ltr'] #toolbarViewerRight > *,
|
||||
html[dir='ltr'] .findbar > * {
|
||||
float: left;
|
||||
}
|
||||
html[dir='rtl'] #toolbarViewerLeft > *,
|
||||
html[dir='rtl'] #toolbarViewerMiddle > *,
|
||||
html[dir='rtl'] #toolbarViewerRight > *,
|
||||
html[dir='rtl'] .findbar > * {
|
||||
float: right;
|
||||
}
|
||||
|
||||
html[dir='ltr'] .splitToolbarButton {
|
||||
margin: 3px 2px 4px 0;
|
||||
display: inline-block;
|
||||
}
|
||||
html[dir='rtl'] .splitToolbarButton {
|
||||
margin: 3px 0 4px 2px;
|
||||
display: inline-block;
|
||||
}
|
||||
html[dir='ltr'] .splitToolbarButton > .toolbarButton {
|
||||
border-radius: 0;
|
||||
float: left;
|
||||
}
|
||||
html[dir='rtl'] .splitToolbarButton > .toolbarButton {
|
||||
border-radius: 0;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
border: 0 none;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
width: 32px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.toolbarButton > span {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toolbarButton[disabled] {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.toolbarButton.group {
|
||||
margin-right:0;
|
||||
}
|
||||
|
||||
.splitToolbarButton.toggled .toolbarButton {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.splitToolbarButton:hover > .toolbarButton,
|
||||
.splitToolbarButton:focus > .toolbarButton,
|
||||
.splitToolbarButton.toggled > .toolbarButton,
|
||||
.toolbarButton.textButton {
|
||||
background-color: hsla(0,0%,0%,.12);
|
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-clip: padding-box;
|
||||
border: 1px solid hsla(0,0%,0%,.35);
|
||||
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||
0 0 1px hsla(0,0%,100%,.15) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.05);
|
||||
-webkit-transition-property: background-color, border-color, box-shadow;
|
||||
-webkit-transition-duration: 150ms;
|
||||
-webkit-transition-timing-function: ease;
|
||||
-moz-transition-property: background-color, border-color, box-shadow;
|
||||
-moz-transition-duration: 150ms;
|
||||
-moz-transition-timing-function: ease;
|
||||
-ms-transition-property: background-color, border-color, box-shadow;
|
||||
-ms-transition-duration: 150ms;
|
||||
-ms-transition-timing-function: ease;
|
||||
-o-transition-property: background-color, border-color, box-shadow;
|
||||
-o-transition-duration: 150ms;
|
||||
-o-transition-timing-function: ease;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: ease;
|
||||
|
||||
}
|
||||
.splitToolbarButton > .toolbarButton:hover,
|
||||
.splitToolbarButton > .toolbarButton:focus,
|
||||
.dropdownToolbarButton:hover,
|
||||
.toolbarButton.textButton:hover,
|
||||
.toolbarButton.textButton:focus {
|
||||
background-color: hsla(0,0%,0%,.2);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||
0 0 1px hsla(0,0%,100%,.15) inset,
|
||||
0 0 1px hsla(0,0%,0%,.05);
|
||||
z-index: 199;
|
||||
}
|
||||
html[dir='ltr'] .splitToolbarButton > .toolbarButton:first-child,
|
||||
html[dir='rtl'] .splitToolbarButton > .toolbarButton:last-child {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
margin-right: -1px;
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
html[dir='ltr'] .splitToolbarButton > .toolbarButton:last-child,
|
||||
html[dir='rtl'] .splitToolbarButton > .toolbarButton:first-child {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
margin-left: -1px;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
border-left-color: transparent;
|
||||
}
|
||||
.splitToolbarButtonSeparator {
|
||||
padding: 8px 0;
|
||||
width: 1px;
|
||||
background-color: hsla(0,0%,00%,.5);
|
||||
z-index: 99;
|
||||
box-shadow: 0 0 0 1px hsla(0,0%,100%,.08);
|
||||
display: inline-block;
|
||||
margin: 5px 0;
|
||||
}
|
||||
html[dir='ltr'] .splitToolbarButtonSeparator {
|
||||
float:left;
|
||||
}
|
||||
html[dir='rtl'] .splitToolbarButtonSeparator {
|
||||
float:right;
|
||||
}
|
||||
.splitToolbarButton:hover > .splitToolbarButtonSeparator,
|
||||
.splitToolbarButton.toggled > .splitToolbarButtonSeparator {
|
||||
padding: 12px 0;
|
||||
margin: 1px 0;
|
||||
box-shadow: 0 0 0 1px hsla(0,0%,100%,.03);
|
||||
-webkit-transition-property: padding;
|
||||
-webkit-transition-duration: 10ms;
|
||||
-webkit-transition-timing-function: ease;
|
||||
-moz-transition-property: padding;
|
||||
-moz-transition-duration: 10ms;
|
||||
-moz-transition-timing-function: ease;
|
||||
-ms-transition-property: padding;
|
||||
-ms-transition-duration: 10ms;
|
||||
-ms-transition-timing-function: ease;
|
||||
-o-transition-property: padding;
|
||||
-o-transition-duration: 10ms;
|
||||
-o-transition-timing-function: ease;
|
||||
transition-property: padding;
|
||||
transition-duration: 10ms;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
.toolbarButton,
|
||||
.dropdownToolbarButton {
|
||||
min-width: 16px;
|
||||
padding: 2px 6px 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
color: hsl(0,0%,95%);
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select:none;
|
||||
-ms-user-select:none;
|
||||
/* Opera does not support user-select, use <... unselectable="on"> instead */
|
||||
cursor: default;
|
||||
-webkit-transition-property: background-color, border-color, box-shadow;
|
||||
-webkit-transition-duration: 150ms;
|
||||
-webkit-transition-timing-function: ease;
|
||||
-moz-transition-property: background-color, border-color, box-shadow;
|
||||
-moz-transition-duration: 150ms;
|
||||
-moz-transition-timing-function: ease;
|
||||
-ms-transition-property: background-color, border-color, box-shadow;
|
||||
-ms-transition-duration: 150ms;
|
||||
-ms-transition-timing-function: ease;
|
||||
-o-transition-property: background-color, border-color, box-shadow;
|
||||
-o-transition-duration: 150ms;
|
||||
-o-transition-timing-function: ease;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
html[dir='ltr'] .toolbarButton,
|
||||
html[dir='ltr'] .dropdownToolbarButton {
|
||||
margin: 3px 2px 4px 0;
|
||||
}
|
||||
html[dir='rtl'] .toolbarButton,
|
||||
html[dir='rtl'] .dropdownToolbarButton {
|
||||
margin: 3px 0 4px 2px;
|
||||
}
|
||||
|
||||
.toolbarButton:hover,
|
||||
.toolbarButton:focus,
|
||||
.dropdownToolbarButton {
|
||||
background-color: hsla(0,0%,0%,.12);
|
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-clip: padding-box;
|
||||
border: 1px solid hsla(0,0%,0%,.35);
|
||||
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||
0 0 1px hsla(0,0%,100%,.15) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.05);
|
||||
}
|
||||
|
||||
.toolbarButton:hover:active,
|
||||
.dropdownToolbarButton:hover:active {
|
||||
background-color: hsla(0,0%,0%,.2);
|
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.4) hsla(0,0%,0%,.45);
|
||||
box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
|
||||
0 0 1px hsla(0,0%,0%,.2) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.05);
|
||||
-webkit-transition-property: background-color, border-color, box-shadow;
|
||||
-webkit-transition-duration: 10ms;
|
||||
-webkit-transition-timing-function: linear;
|
||||
-moz-transition-property: background-color, border-color, box-shadow;
|
||||
-moz-transition-duration: 10ms;
|
||||
-moz-transition-timing-function: linear;
|
||||
-ms-transition-property: background-color, border-color, box-shadow;
|
||||
-ms-transition-duration: 10ms;
|
||||
-ms-transition-timing-function: linear;
|
||||
-o-transition-property: background-color, border-color, box-shadow;
|
||||
-o-transition-duration: 10ms;
|
||||
-o-transition-timing-function: linear;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 10ms;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.toolbarButton.toggled,
|
||||
.splitToolbarButton.toggled > .toolbarButton.toggled {
|
||||
background-color: hsla(0,0%,0%,.3);
|
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.45) hsla(0,0%,0%,.5);
|
||||
box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
|
||||
0 0 1px hsla(0,0%,0%,.2) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.05);
|
||||
-webkit-transition-property: background-color, border-color, box-shadow;
|
||||
-webkit-transition-duration: 10ms;
|
||||
-webkit-transition-timing-function: linear;
|
||||
-moz-transition-property: background-color, border-color, box-shadow;
|
||||
-moz-transition-duration: 10ms;
|
||||
-moz-transition-timing-function: linear;
|
||||
-ms-transition-property: background-color, border-color, box-shadow;
|
||||
-ms-transition-duration: 10ms;
|
||||
-ms-transition-timing-function: linear;
|
||||
-o-transition-property: background-color, border-color, box-shadow;
|
||||
-o-transition-duration: 10ms;
|
||||
-o-transition-timing-function: linear;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 10ms;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
.toolbarButton.toggled:hover:active,
|
||||
.splitToolbarButton.toggled > .toolbarButton.toggled:hover:active {
|
||||
background-color: hsla(0,0%,0%,.4);
|
||||
border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.5) hsla(0,0%,0%,.55);
|
||||
box-shadow: 0 1px 1px hsla(0,0%,0%,.2) inset,
|
||||
0 0 1px hsla(0,0%,0%,.3) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.05);
|
||||
}
|
||||
|
||||
.dropdownToolbarButton {
|
||||
max-width: 120px;
|
||||
padding: 3px 2px 2px;
|
||||
overflow: hidden;
|
||||
background: url(images/toolbarButton-menuArrows.png) no-repeat;
|
||||
}
|
||||
html[dir='ltr'] .dropdownToolbarButton {
|
||||
background-position: 95%;
|
||||
}
|
||||
html[dir='rtl'] .dropdownToolbarButton {
|
||||
background-position: 5%;
|
||||
}
|
||||
|
||||
.dropdownToolbarButton > select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none; /* in the future this might matter, see bugzilla bug #649849 */
|
||||
min-width: 140px;
|
||||
font-size: 12px;
|
||||
color: hsl(0,0%,95%);
|
||||
margin:0;
|
||||
padding:0;
|
||||
border:none;
|
||||
background: rgba(0,0,0,0); /* Opera does not support 'transparent' <select> background */
|
||||
}
|
||||
|
||||
.dropdownToolbarButton > select > option {
|
||||
background: hsl(0,0%,24%);
|
||||
}
|
||||
|
||||
#customScaleOption {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#pageWidthOption {
|
||||
border-bottom: 1px rgba(255, 255, 255, .5) solid;
|
||||
}
|
||||
|
||||
html[dir='ltr'] .splitToolbarButton:first-child,
|
||||
html[dir='ltr'] .toolbarButton:first-child,
|
||||
html[dir='rtl'] .splitToolbarButton:last-child,
|
||||
html[dir='rtl'] .toolbarButton:last-child {
|
||||
margin-left: 4px;
|
||||
}
|
||||
html[dir='ltr'] .splitToolbarButton:last-child,
|
||||
html[dir='ltr'] .toolbarButton:last-child,
|
||||
html[dir='rtl'] .splitToolbarButton:first-child,
|
||||
html[dir='rtl'] .toolbarButton:first-child {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.toolbarButtonSpacer {
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.toolbarButtonFlexibleSpacer {
|
||||
-webkit-box-flex: 1;
|
||||
-moz-box-flex: 1;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.toolbarButton#sidebarToggle::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-sidebarToggle.png);
|
||||
}
|
||||
|
||||
html[dir='ltr'] .toolbarButton.findPrevious::before {
|
||||
display: inline-block;
|
||||
content: url(images/findbarButton-previous.png);
|
||||
}
|
||||
|
||||
html[dir='rtl'] .toolbarButton.findPrevious::before {
|
||||
display: inline-block;
|
||||
content: url(images/findbarButton-previous-rtl.png);
|
||||
}
|
||||
|
||||
html[dir='ltr'] .toolbarButton.findNext::before {
|
||||
display: inline-block;
|
||||
content: url(images/findbarButton-next.png);
|
||||
}
|
||||
|
||||
html[dir='rtl'] .toolbarButton.findNext::before {
|
||||
display: inline-block;
|
||||
content: url(images/findbarButton-next-rtl.png);
|
||||
}
|
||||
|
||||
html[dir='ltr'] .toolbarButton.pageUp::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-pageUp.png);
|
||||
}
|
||||
|
||||
html[dir='rtl'] .toolbarButton.pageUp::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-pageUp-rtl.png);
|
||||
}
|
||||
|
||||
html[dir='ltr'] .toolbarButton.pageDown::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-pageDown.png);
|
||||
}
|
||||
|
||||
html[dir='rtl'] .toolbarButton.pageDown::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-pageDown-rtl.png);
|
||||
}
|
||||
|
||||
.toolbarButton.zoomOut::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-zoomOut.png);
|
||||
}
|
||||
|
||||
.toolbarButton.zoomIn::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-zoomIn.png);
|
||||
}
|
||||
|
||||
.toolbarButton.fullscreen::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-fullscreen.png);
|
||||
}
|
||||
|
||||
.toolbarButton.print::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-print.png);
|
||||
}
|
||||
|
||||
.toolbarButton.openFile::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-openFile.png);
|
||||
}
|
||||
|
||||
.toolbarButton.download::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-download.png);
|
||||
}
|
||||
|
||||
.toolbarButton.bookmark {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
margin-top: 3px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.toolbarButton.bookmark::before {
|
||||
content: url(images/toolbarButton-bookmark.png);
|
||||
}
|
||||
|
||||
#viewThumbnail.toolbarButton::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-viewThumbnail.png);
|
||||
}
|
||||
|
||||
#viewOutline.toolbarButton::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-viewOutline.png);
|
||||
}
|
||||
|
||||
#viewFind.toolbarButton::before {
|
||||
display: inline-block;
|
||||
content: url(images/toolbarButton-search.png);
|
||||
}
|
||||
|
||||
|
||||
.toolbarField {
|
||||
padding: 3px 6px;
|
||||
margin: 4px 0 4px 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
background-color: hsla(0,0%,100%,.09);
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-clip: padding-box;
|
||||
border: 1px solid hsla(0,0%,0%,.35);
|
||||
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,0%,.05) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.05);
|
||||
color: hsl(0,0%,95%);
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
outline-style: none;
|
||||
-moz-transition-property: background-color, border-color, box-shadow;
|
||||
-moz-transition-duration: 150ms;
|
||||
-moz-transition-timing-function: ease;
|
||||
}
|
||||
|
||||
.toolbarField[type=checkbox] {
|
||||
display: inline-block;
|
||||
margin: 8px 0px;
|
||||
}
|
||||
|
||||
.toolbarField.pageNumber {
|
||||
min-width: 16px;
|
||||
text-align: right;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.toolbarField.pageNumber::-webkit-inner-spin-button,
|
||||
.toolbarField.pageNumber::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.toolbarField:hover {
|
||||
background-color: hsla(0,0%,100%,.11);
|
||||
border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.43) hsla(0,0%,0%,.45);
|
||||
}
|
||||
|
||||
.toolbarField:focus {
|
||||
background-color: hsla(0,0%,100%,.15);
|
||||
border-color: hsla(204,100%,65%,.8) hsla(204,100%,65%,.85) hsla(204,100%,65%,.9);
|
||||
}
|
||||
|
||||
.toolbarLabel {
|
||||
min-width: 16px;
|
||||
padding: 3px 6px 3px 2px;
|
||||
margin: 4px 2px 4px 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
color: hsl(0,0%,85%);
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
text-align: left;
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select:none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#thumbnailView {
|
||||
position: absolute;
|
||||
width: 120px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
padding: 10px 40px 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
margin-bottom: 15px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.thumbnail:not([data-loaded]) {
|
||||
border: 1px dashed rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.thumbnailImage {
|
||||
-moz-transition-duration: 150ms;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
opacity: 0.8;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.thumbnailSelectionRing {
|
||||
border-radius: 2px;
|
||||
padding: 7px;
|
||||
-moz-transition-duration: 150ms;
|
||||
}
|
||||
|
||||
a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage,
|
||||
.thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage {
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
a:focus > .thumbnail > .thumbnailSelectionRing,
|
||||
.thumbnail:hover > .thumbnailSelectionRing {
|
||||
background-color: hsla(0,0%,100%,.15);
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-clip: padding-box;
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||
0 0 1px hsla(0,0%,100%,.2) inset,
|
||||
0 0 1px hsla(0,0%,0%,.2);
|
||||
color: hsla(0,0%,100%,.9);
|
||||
}
|
||||
|
||||
.thumbnail.selected > .thumbnailSelectionRing > .thumbnailImage {
|
||||
box-shadow: 0 0 0 1px hsla(0,0%,0%,.5);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.thumbnail.selected > .thumbnailSelectionRing {
|
||||
background-color: hsla(0,0%,100%,.3);
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-clip: padding-box;
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||
0 0 1px hsla(0,0%,100%,.1) inset,
|
||||
0 0 1px hsla(0,0%,0%,.2);
|
||||
color: hsla(0,0%,100%,1);
|
||||
}
|
||||
|
||||
#outlineView {
|
||||
position: absolute;
|
||||
width: 192px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
padding: 4px 4px 0;
|
||||
overflow: auto;
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select:none;
|
||||
}
|
||||
|
||||
html[dir='ltr'] .outlineItem > .outlineItems {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .outlineItem > .outlineItems {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.outlineItem > a {
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
min-width: 95%;
|
||||
height: auto;
|
||||
margin-bottom: 1px;
|
||||
border-radius: 2px;
|
||||
color: hsla(0,0%,100%,.8);
|
||||
font-size: 13px;
|
||||
line-height: 15px;
|
||||
-moz-user-select:none;
|
||||
cursor: default;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
html[dir='ltr'] .outlineItem > a {
|
||||
padding: 2px 0 5px 10px;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .outlineItem > a {
|
||||
padding: 2px 10px 5px 0;
|
||||
}
|
||||
|
||||
.outlineItem > a:hover {
|
||||
background-color: hsla(0,0%,100%,.02);
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-clip: padding-box;
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||
0 0 1px hsla(0,0%,100%,.2) inset,
|
||||
0 0 1px hsla(0,0%,0%,.2);
|
||||
color: hsla(0,0%,100%,.9);
|
||||
}
|
||||
|
||||
.outlineItem.selected {
|
||||
background-color: hsla(0,0%,100%,.08);
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-clip: padding-box;
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||
0 0 1px hsla(0,0%,100%,.1) inset,
|
||||
0 0 1px hsla(0,0%,0%,.2);
|
||||
color: hsla(0,0%,100%,1);
|
||||
}
|
||||
|
||||
.noOutline,
|
||||
.noResults {
|
||||
font-size: 12px;
|
||||
color: hsla(0,0%,100%,.8);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#findScrollView {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
#sidebarControls {
|
||||
position:absolute;
|
||||
width: 180px;
|
||||
height: 32px;
|
||||
left: 15px;
|
||||
bottom: 35px;
|
||||
}
|
||||
|
||||
canvas {
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.page {
|
||||
direction: ltr;
|
||||
width: 816px;
|
||||
height: 1056px;
|
||||
margin: 10px auto;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-box-shadow: 0px 4px 10px #000;
|
||||
-moz-box-shadow: 0px 4px 10px #000;
|
||||
box-shadow: 0px 4px 10px #000;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.page > a {
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.page > a:hover {
|
||||
opacity: 0.2;
|
||||
background: #ff0;
|
||||
-webkit-box-shadow: 0px 2px 10px #ff0;
|
||||
-moz-box-shadow: 0px 2px 10px #ff0;
|
||||
box-shadow: 0px 2px 10px #ff0;
|
||||
}
|
||||
|
||||
.loadingIcon {
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url('images/loading-icon.gif') center no-repeat;
|
||||
}
|
||||
|
||||
#loadingBox {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -25px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
color: #ddd;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#loadingBar {
|
||||
display: inline-block;
|
||||
clear: both;
|
||||
margin: 0px;
|
||||
margin-top: 5px;
|
||||
line-height: 0;
|
||||
border-radius: 2px;
|
||||
width: 200px;
|
||||
height: 25px;
|
||||
|
||||
background-color: hsla(0,0%,0%,.3);
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||
border: 1px solid #000;
|
||||
box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
|
||||
0 0 1px hsla(0,0%,0%,.2) inset,
|
||||
0 0 1px 1px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
#loadingBar .progress {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
|
||||
background: #666;
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2b2b2), color-stop(100%,#898989));
|
||||
background: -webkit-linear-gradient(top, #b2b2b2 0%,#898989 100%);
|
||||
background: -moz-linear-gradient(top, #b2b2b2 0%,#898989 100%);
|
||||
background: -ms-linear-gradient(top, #b2b2b2 0%,#898989 100%);
|
||||
background: -o-linear-gradient(top, #b2b2b2 0%,#898989 100%);
|
||||
background: linear-gradient(top, #b2b2b2 0%,#898989 100%);
|
||||
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#loadingBar .progress.full {
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
#loadingBar .progress.indeterminate {
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
background-image: -moz-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040);
|
||||
background-image: -webkit-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040);
|
||||
background-image: -ms-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040);
|
||||
background-image: -o-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040);
|
||||
background-size: 75px 25px;
|
||||
-moz-animation: progressIndeterminate 1s linear infinite;
|
||||
-webkit-animation: progressIndeterminate 1s linear infinite;
|
||||
}
|
||||
|
||||
@-moz-keyframes progressIndeterminate {
|
||||
from { background-position: 0px 0px; }
|
||||
to { background-position: 75px 0px; }
|
||||
}
|
||||
|
||||
@-webkit-keyframes progressIndeterminate {
|
||||
from { background-position: 0px 0px; }
|
||||
to { background-position: 75px 0px; }
|
||||
}
|
||||
|
||||
.textLayer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
color: #000;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.textLayer > div {
|
||||
color: transparent;
|
||||
position: absolute;
|
||||
line-height:1.3;
|
||||
white-space:pre;
|
||||
}
|
||||
|
||||
.textLayer .highlight {
|
||||
margin: -1px;
|
||||
padding: 1px;
|
||||
|
||||
background-color: rgba(180, 0, 170, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.textLayer .highlight.begin {
|
||||
border-radius: 4px 0px 0px 4px;
|
||||
}
|
||||
|
||||
.textLayer .highlight.end {
|
||||
border-radius: 0px 4px 4px 0px;
|
||||
}
|
||||
|
||||
.textLayer .highlight.middle {
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.textLayer .highlight.selected {
|
||||
background-color: rgba(0, 100, 0, 0.2);
|
||||
}
|
||||
|
||||
/* TODO: file FF bug to support ::-moz-selection:window-inactive
|
||||
so we can override the opaque grey background when the window is inactive;
|
||||
see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */
|
||||
::selection { background:rgba(0,0,255,0.3); }
|
||||
::-moz-selection { background:rgba(0,0,255,0.3); }
|
||||
|
||||
.annotText > div {
|
||||
z-index: 200;
|
||||
position: absolute;
|
||||
padding: 0.6em;
|
||||
max-width: 20em;
|
||||
background-color: #FFFF99;
|
||||
-webkit-box-shadow: 0px 2px 10px #333;
|
||||
-moz-box-shadow: 0px 2px 10px #333;
|
||||
box-shadow: 0px 2px 10px #333;
|
||||
border-radius: 7px;
|
||||
-moz-border-radius: 7px;
|
||||
}
|
||||
|
||||
.annotText > img {
|
||||
position: absolute;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.annotText > img:hover {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.annotText > div > h1 {
|
||||
font-size: 1.2em;
|
||||
border-bottom: 1px solid #000000;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#errorWrapper {
|
||||
background: none repeat scroll 0 0 #FF5555;
|
||||
color: white;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 32px;
|
||||
z-index: 1000;
|
||||
padding: 3px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
#errorMessageLeft {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#errorMessageRight {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#errorMoreInfo {
|
||||
background-color: #FFFFFF;
|
||||
color: black;
|
||||
padding: 3px;
|
||||
margin: 3px;
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
.clearBoth {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.fileInput {
|
||||
background: white;
|
||||
color: black;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#PDFBug {
|
||||
background: none repeat scroll 0 0 white;
|
||||
border: 1px solid #666666;
|
||||
position: fixed;
|
||||
top: 32px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
font-size: 10px;
|
||||
padding: 0;
|
||||
width: 300px;
|
||||
}
|
||||
#PDFBug .controls {
|
||||
background:#EEEEEE;
|
||||
border-bottom: 1px solid #666666;
|
||||
padding: 3px;
|
||||
}
|
||||
#PDFBug .panels {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 27px;
|
||||
}
|
||||
#PDFBug button.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
.debuggerShowText {
|
||||
background: none repeat scroll 0 0 yellow;
|
||||
color: blue;
|
||||
opacity: 0.3;
|
||||
}
|
||||
.debuggerHideText:hover {
|
||||
background: none repeat scroll 0 0 yellow;
|
||||
opacity: 0.3;
|
||||
}
|
||||
#PDFBug .stats {
|
||||
font-family: courier;
|
||||
font-size: 10px;
|
||||
white-space: pre;
|
||||
}
|
||||
#PDFBug .stats .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
#PDFBug table {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
#viewer.textLayer-visible .textLayer > div,
|
||||
#viewer.textLayer-hover .textLayer > div:hover {
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#viewer.textLayer-shadow .textLayer > div {
|
||||
background-color: rgba(255,255,255, .6);
|
||||
color: black;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#printContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
/* Rules for browsers that don't support mozPrintCallback. */
|
||||
#sidebarContainer, .toolbar, #loadingBox, #errorWrapper, .textLayer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mainContainer, #viewerContainer, .page, .page canvas {
|
||||
position: static;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
float: left;
|
||||
display: none;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.page[data-loaded] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Rules for browsers that support mozPrintCallback */
|
||||
body[data-mozPrintCallback] #outerContainer {
|
||||
display: none;
|
||||
}
|
||||
body[data-mozPrintCallback] #printContainer {
|
||||
display: block;
|
||||
}
|
||||
#printContainer canvas {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 950px) {
|
||||
html[dir='ltr'] #outerContainer.sidebarMoving .outerCenter,
|
||||
html[dir='ltr'] #outerContainer.sidebarOpen .outerCenter {
|
||||
float: left;
|
||||
left: 180px;
|
||||
}
|
||||
html[dir='rtl'] #outerContainer.sidebarMoving .outerCenter,
|
||||
html[dir='rtl'] #outerContainer.sidebarOpen .outerCenter {
|
||||
float: right;
|
||||
right: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 770px) {
|
||||
#sidebarContainer {
|
||||
top: 33px;
|
||||
z-index: 100;
|
||||
}
|
||||
#sidebarContent {
|
||||
top: 32px;
|
||||
background-color: hsla(0,0%,0%,.7);
|
||||
}
|
||||
|
||||
html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer {
|
||||
left: 0px;
|
||||
}
|
||||
html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
html[dir='ltr'] .outerCenter {
|
||||
float: left;
|
||||
left: 180px;
|
||||
}
|
||||
html[dir='rtl'] .outerCenter {
|
||||
float: right;
|
||||
right: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 600px) {
|
||||
#toolbarViewerRight, #findbar, #viewFind {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 500px) {
|
||||
#scaleSelectContainer, #pageNumberLabel {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ var CohortManager = (function ($) {
|
||||
$(".remove", tr).html('<a href="#">remove</a>')
|
||||
.click(function() {
|
||||
remove_user_from_cohort(item.username, current_cohort_id, tr);
|
||||
return false;
|
||||
});
|
||||
|
||||
detail_users.append(tr);
|
||||
@@ -217,6 +218,7 @@ var CohortManager = (function ($) {
|
||||
show_cohorts_button.click(function() {
|
||||
state = state_summary;
|
||||
render();
|
||||
return false;
|
||||
});
|
||||
|
||||
add_cohort_input.change(function() {
|
||||
@@ -231,12 +233,14 @@ var CohortManager = (function ($) {
|
||||
var add_url = url + '/add';
|
||||
data = {'name': add_cohort_input.val()}
|
||||
$.post(add_url, data).done(added_cohort);
|
||||
return false;
|
||||
});
|
||||
|
||||
add_members_button.click(function() {
|
||||
var add_url = detail_url + '/add';
|
||||
data = {'users': users_area.val()}
|
||||
$.post(add_url, data).done(added_users);
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
|
||||
367
common/static/js/pdfviewer.js
Normal file
@@ -0,0 +1,367 @@
|
||||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Modified (and JQuerified) from PDF-JS sample code (viewer.js)
|
||||
*/
|
||||
/* globals: PDFJS as defined in pdf.js. Also assumes that jquery is included. */
|
||||
|
||||
//
|
||||
// Disable workers to avoid yet another cross-origin issue (workers need the URL of
|
||||
// the script to be loaded, and currently do not allow cross-origin scripts)
|
||||
//
|
||||
PDFJS.disableWorker = true;
|
||||
|
||||
(function($) {
|
||||
$.fn.PDFViewer = function(options) {
|
||||
var pdfViewer = this;
|
||||
|
||||
var pdfDocument = null;
|
||||
var urlToLoad = null;
|
||||
if (options.url) {
|
||||
urlToLoad = options.url;
|
||||
}
|
||||
var chapterUrls = null;
|
||||
if (options.chapters) {
|
||||
chapterUrls = options.chapters;
|
||||
}
|
||||
var chapterToLoad = 1;
|
||||
if (options.chapterNum) {
|
||||
// TODO: this should only be specified if there are
|
||||
// chapters, and it should be in-bounds.
|
||||
chapterToLoad = options.chapterNum;
|
||||
}
|
||||
var pageToLoad = 1;
|
||||
if (options.pageNum) {
|
||||
pageToLoad = options.pageNum;
|
||||
}
|
||||
|
||||
var chapterNum = 1;
|
||||
var pageNum = 1;
|
||||
|
||||
var viewerElement = document.getElementById('viewer');
|
||||
var ANNOT_MIN_SIZE = 10;
|
||||
var DEFAULT_SCALE_DELTA = 1.1;
|
||||
var UNKNOWN_SCALE = 0;
|
||||
var MIN_SCALE = 0.25;
|
||||
var MAX_SCALE = 4.0;
|
||||
|
||||
var currentScale = UNKNOWN_SCALE;
|
||||
var currentScaleValue = "0";
|
||||
var DEFAULT_SCALE_VALUE = "1";
|
||||
|
||||
var setupText = function setupText(textdiv, content, viewport) {
|
||||
|
||||
function getPageNumberFromDest(dest) {
|
||||
var destPage = 1;
|
||||
if (dest instanceof Array) {
|
||||
var destRef = dest[0];
|
||||
if (destRef instanceof Object) {
|
||||
// we would need to look this up in the
|
||||
// list of all pages that have been loaded,
|
||||
// but we're trying to not have to load all the pages
|
||||
// right now.
|
||||
// destPage = this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'];
|
||||
} else {
|
||||
destPage = (destRef + 1);
|
||||
}
|
||||
}
|
||||
return destPage;
|
||||
}
|
||||
|
||||
function bindLink(link, dest) {
|
||||
// get page number from dest:
|
||||
destPage = getPageNumberFromDest(dest);
|
||||
link.href = '#page=' + destPage;
|
||||
link.onclick = function pageViewSetupLinksOnclick() {
|
||||
if (dest && dest instanceof Array )
|
||||
renderPage(destPage);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
function createElementWithStyle(tagName, item, rect) {
|
||||
if (!rect) {
|
||||
rect = viewport.convertToViewportRectangle(item.rect);
|
||||
rect = PDFJS.Util.normalizeRect(rect);
|
||||
}
|
||||
var element = document.createElement(tagName);
|
||||
element.style.left = Math.floor(rect[0]) + 'px';
|
||||
element.style.top = Math.floor(rect[1]) + 'px';
|
||||
element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
|
||||
element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
|
||||
// BW: my additions here, but should use css:
|
||||
// TODO: move these to css
|
||||
element.style.position = 'absolute';
|
||||
element.style.cursor = 'auto';
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
function createTextAnnotation(item) {
|
||||
var container = document.createElement('section');
|
||||
container.className = 'annotText';
|
||||
var rect = viewport.convertToViewportRectangle(item.rect);
|
||||
rect = PDFJS.Util.normalizeRect(rect);
|
||||
// sanity check because of OOo-generated PDFs
|
||||
if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) {
|
||||
rect[3] = rect[1] + ANNOT_MIN_SIZE;
|
||||
}
|
||||
if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
|
||||
rect[2] = rect[0] + (rect[3] - rect[1]);
|
||||
// make it square
|
||||
}
|
||||
var image = createElementWithStyle('img', item, rect);
|
||||
var iconName = item.name;
|
||||
}
|
||||
|
||||
|
||||
content.getAnnotations().then(function(items) {
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
switch (item.type) {
|
||||
case 'Link':
|
||||
var link = createElementWithStyle('a', item);
|
||||
link.href = item.url || '';
|
||||
if (!item.url)
|
||||
bindLink(link, ('dest' in item) ? item.dest : null);
|
||||
textdiv.appendChild(link);
|
||||
break;
|
||||
case 'Text':
|
||||
var textAnnotation = createTextAnnotation(item);
|
||||
if (textAnnotation)
|
||||
textdiv.appendChild(textAnnotation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Get page info from document, resize canvas accordingly, and render page
|
||||
//
|
||||
renderPage = function(num) {
|
||||
// don't try to render a page that cannot be rendered
|
||||
if (num < 1 || num > pdfDocument.numPages) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update logging:
|
||||
log_event("book", { "type" : "gotopage", "old" : pageNum, "new" : num });
|
||||
|
||||
parentElement = viewerElement;
|
||||
while (parentElement.hasChildNodes())
|
||||
parentElement.removeChild(parentElement.lastChild);
|
||||
|
||||
// Using promise to fetch the page
|
||||
pdfDocument.getPage(num).then(function(page) {
|
||||
var viewport = page.getViewport(currentScale);
|
||||
|
||||
var pageDisplayWidth = viewport.width;
|
||||
var pageDisplayHeight = viewport.height;
|
||||
|
||||
var pageDivHolder = document.createElement('div');
|
||||
pageDivHolder.className = 'pdfpage';
|
||||
pageDivHolder.style.width = pageDisplayWidth + 'px';
|
||||
pageDivHolder.style.height = pageDisplayHeight + 'px';
|
||||
parentElement.appendChild(pageDivHolder);
|
||||
|
||||
// Prepare canvas using PDF page dimensions
|
||||
var canvas = document.createElement('canvas');
|
||||
var context = canvas.getContext('2d');
|
||||
canvas.width = pageDisplayWidth;
|
||||
canvas.height = pageDisplayHeight;
|
||||
pageDivHolder.appendChild(canvas);
|
||||
|
||||
// Render PDF page into canvas context
|
||||
var renderContext = {
|
||||
canvasContext : context,
|
||||
viewport : viewport
|
||||
};
|
||||
page.render(renderContext);
|
||||
|
||||
// Prepare and populate text elements layer
|
||||
setupText(pageDivHolder, page, viewport);
|
||||
|
||||
});
|
||||
pageNum = num;
|
||||
|
||||
// Update page counters
|
||||
document.getElementById('numPages').textContent = 'of ' + pdfDocument.numPages;
|
||||
$("#pageNumber").max = pdfDocument.numPages;
|
||||
$("#pageNumber").val(pageNum);
|
||||
}
|
||||
|
||||
// Go to previous page
|
||||
prevPage = function prev_page() {
|
||||
if (pageNum <= 1)
|
||||
return;
|
||||
renderPage(pageNum - 1);
|
||||
log_event("book", { "type" : "prevpage", "new" : pageNum });
|
||||
}
|
||||
|
||||
// Go to next page
|
||||
nextPage = function next_page() {
|
||||
if (pageNum >= pdfDocument.numPages)
|
||||
return;
|
||||
renderPage(pageNum + 1);
|
||||
log_event("book", { "type" : "nextpage", "new" : pageNum });
|
||||
}
|
||||
|
||||
selectScaleOption = function(value) {
|
||||
var options = $('#scaleSelect options');
|
||||
var predefinedValueFound = false;
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var option = options[i];
|
||||
if (option.value != value) {
|
||||
option.selected = false;
|
||||
continue;
|
||||
}
|
||||
option.selected = true;
|
||||
predefinedValueFound = true;
|
||||
}
|
||||
return predefinedValueFound;
|
||||
}
|
||||
|
||||
setScale = function pdfViewSetScale(val, resetAutoSettings, noScroll) {
|
||||
if (val == currentScale)
|
||||
return;
|
||||
currentScale = val;
|
||||
var customScaleOption = $('#customScaleOption')[0];
|
||||
customScaleOption.selected = false
|
||||
var predefinedValueFound = selectScaleOption('' + currentScale);
|
||||
if (!predefinedValueFound) {
|
||||
customScaleOption.textContent = Math.round(currentScale * 10000) / 100 + '%';
|
||||
customScaleOption.selected = true;
|
||||
}
|
||||
$('#zoom_in').disabled = (currentScale === MAX_SCALE);
|
||||
$('#zoom_out').disabled = (currentScale === MIN_SCALE);
|
||||
|
||||
// Just call renderPage once the scale
|
||||
// has been changed. If we were saving information about
|
||||
// the rendering of other pages, we would need
|
||||
// to reset those as well.
|
||||
renderPage(pageNum);
|
||||
};
|
||||
|
||||
parseScale = function pdfViewParseScale(value, resetAutoSettings, noScroll) {
|
||||
// we shouldn't be choosing the 'custom' value -- it's only for display.
|
||||
// Check, just in case.
|
||||
if ('custom' == value)
|
||||
return;
|
||||
|
||||
var scale = parseFloat(value);
|
||||
if (scale) {
|
||||
currentScaleValue = value;
|
||||
setScale(scale, true, noScroll);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
zoomIn = function pdfViewZoomIn() {
|
||||
var newScale = (currentScale * DEFAULT_SCALE_DELTA).toFixed(2);
|
||||
newScale = Math.min(MAX_SCALE, newScale);
|
||||
parseScale(newScale, true);
|
||||
};
|
||||
|
||||
zoomOut = function pdfViewZoomOut() {
|
||||
var newScale = (currentScale / DEFAULT_SCALE_DELTA).toFixed(2);
|
||||
newScale = Math.max(MIN_SCALE, newScale);
|
||||
parseScale(newScale, true);
|
||||
};
|
||||
|
||||
//
|
||||
// Asynchronously download PDF as an ArrayBuffer
|
||||
//
|
||||
loadUrl = function pdfViewLoadUrl(url, page) {
|
||||
PDFJS.getDocument(url).then(
|
||||
function getDocument(_pdfDocument) {
|
||||
pdfDocument = _pdfDocument;
|
||||
pageNum = page;
|
||||
// if the scale has not been set before, set it now.
|
||||
// Otherwise, don't change the current scale,
|
||||
// but make sure it gets refreshed.
|
||||
if (currentScale == UNKNOWN_SCALE) {
|
||||
parseScale(DEFAULT_SCALE_VALUE);
|
||||
} else {
|
||||
var preservedScale = currentScale;
|
||||
currentScale = UNKNOWN_SCALE;
|
||||
parseScale(preservedScale);
|
||||
}
|
||||
},
|
||||
function getDocumentError(message, exception) {
|
||||
// placeholder: don't expect errors :)
|
||||
},
|
||||
function getDocumentProgress(progressData) {
|
||||
// placeholder: not yet ready to display loading progress
|
||||
});
|
||||
};
|
||||
|
||||
loadChapterUrl = function pdfViewLoadChapterUrl(chapterNum, pageVal) {
|
||||
if (chapterNum < 1 || chapterNum > chapterUrls.length) {
|
||||
return;
|
||||
}
|
||||
var chapterUrl = chapterUrls[chapterNum-1];
|
||||
loadUrl(chapterUrl, pageVal);
|
||||
}
|
||||
|
||||
$("#previous").click(function(event) {
|
||||
prevPage();
|
||||
});
|
||||
|
||||
$("#next").click(function(event) {
|
||||
nextPage();
|
||||
});
|
||||
|
||||
$('#zoom_in').click(function(event) {
|
||||
zoomIn();
|
||||
});
|
||||
$('#zoom_out').click(function(event) {
|
||||
zoomOut();
|
||||
});
|
||||
|
||||
$('#scaleSelect').change(function(event) {
|
||||
parseScale(this.value);
|
||||
});
|
||||
|
||||
|
||||
$('#pageNumber').change(function(event) {
|
||||
var newPageVal = parseInt(this.value);
|
||||
if (newPageVal) {
|
||||
renderPage(newPageVal);
|
||||
}
|
||||
});
|
||||
|
||||
// define navigation links for chapters:
|
||||
if (chapterUrls != null) {
|
||||
var loadChapterUrlHelper = function(i) {
|
||||
return function(event) {
|
||||
// when opening a new chapter, always open the first page:
|
||||
loadChapterUrl(i, 1);
|
||||
};
|
||||
};
|
||||
for (var index = 1; index <= chapterUrls.length; index += 1) {
|
||||
$("#pdfchapter-" + index).click(loadChapterUrlHelper(index));
|
||||
}
|
||||
}
|
||||
|
||||
// finally, load the appropriate url/page
|
||||
if (urlToLoad != null) {
|
||||
loadUrl(urlToLoad, pageToLoad);
|
||||
} else {
|
||||
loadChapterUrl(chapterToLoad, pageToLoad);
|
||||
}
|
||||
|
||||
return pdfViewer;
|
||||
}
|
||||
})(jQuery);
|
||||
430
common/static/js/vendor/pdfjs/compatibility.js
vendored
Normal file
@@ -0,0 +1,430 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* globals VBArray */
|
||||
|
||||
'use strict';
|
||||
|
||||
// Checking if the typed arrays are supported
|
||||
(function checkTypedArrayCompatibility() {
|
||||
if (typeof Uint8Array !== 'undefined') {
|
||||
// some mobile versions do not support subarray (e.g. safari 5 / iOS)
|
||||
if (typeof Uint8Array.prototype.subarray === 'undefined') {
|
||||
Uint8Array.prototype.subarray = function subarray(start, end) {
|
||||
return new Uint8Array(this.slice(start, end));
|
||||
};
|
||||
Float32Array.prototype.subarray = function subarray(start, end) {
|
||||
return new Float32Array(this.slice(start, end));
|
||||
};
|
||||
}
|
||||
|
||||
// some mobile version might not support Float64Array
|
||||
if (typeof Float64Array === 'undefined')
|
||||
window.Float64Array = Float32Array;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function subarray(start, end) {
|
||||
return new TypedArray(this.slice(start, end));
|
||||
}
|
||||
|
||||
function setArrayOffset(array, offset) {
|
||||
if (arguments.length < 2)
|
||||
offset = 0;
|
||||
for (var i = 0, n = array.length; i < n; ++i, ++offset)
|
||||
this[offset] = array[i] & 0xFF;
|
||||
}
|
||||
|
||||
function TypedArray(arg1) {
|
||||
var result;
|
||||
if (typeof arg1 === 'number') {
|
||||
result = [];
|
||||
for (var i = 0; i < arg1; ++i)
|
||||
result[i] = 0;
|
||||
} else
|
||||
result = arg1.slice(0);
|
||||
|
||||
result.subarray = subarray;
|
||||
result.buffer = result;
|
||||
result.byteLength = result.length;
|
||||
result.set = setArrayOffset;
|
||||
|
||||
if (typeof arg1 === 'object' && arg1.buffer)
|
||||
result.buffer = arg1.buffer;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
window.Uint8Array = TypedArray;
|
||||
|
||||
// we don't need support for set, byteLength for 32-bit array
|
||||
// so we can use the TypedArray as well
|
||||
window.Uint32Array = TypedArray;
|
||||
window.Int32Array = TypedArray;
|
||||
window.Uint16Array = TypedArray;
|
||||
window.Float32Array = TypedArray;
|
||||
window.Float64Array = TypedArray;
|
||||
})();
|
||||
|
||||
// Object.create() ?
|
||||
(function checkObjectCreateCompatibility() {
|
||||
if (typeof Object.create !== 'undefined')
|
||||
return;
|
||||
|
||||
Object.create = function objectCreate(proto) {
|
||||
function Constructor() {}
|
||||
Constructor.prototype = proto;
|
||||
return new Constructor();
|
||||
};
|
||||
})();
|
||||
|
||||
// Object.defineProperty() ?
|
||||
(function checkObjectDefinePropertyCompatibility() {
|
||||
if (typeof Object.defineProperty !== 'undefined') {
|
||||
var definePropertyPossible = true;
|
||||
try {
|
||||
// some browsers (e.g. safari) cannot use defineProperty() on DOM objects
|
||||
// and thus the native version is not sufficient
|
||||
Object.defineProperty(new Image(), 'id', { value: 'test' });
|
||||
// ... another test for android gb browser for non-DOM objects
|
||||
var Test = function Test() {};
|
||||
Test.prototype = { get id() { } };
|
||||
Object.defineProperty(new Test(), 'id',
|
||||
{ value: '', configurable: true, enumerable: true, writable: false });
|
||||
} catch (e) {
|
||||
definePropertyPossible = false;
|
||||
}
|
||||
if (definePropertyPossible) return;
|
||||
}
|
||||
|
||||
Object.defineProperty = function objectDefineProperty(obj, name, def) {
|
||||
delete obj[name];
|
||||
if ('get' in def)
|
||||
obj.__defineGetter__(name, def['get']);
|
||||
if ('set' in def)
|
||||
obj.__defineSetter__(name, def['set']);
|
||||
if ('value' in def) {
|
||||
obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
|
||||
this.__defineGetter__(name, function objectDefinePropertyGetter() {
|
||||
return value;
|
||||
});
|
||||
return value;
|
||||
});
|
||||
obj[name] = def.value;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
// Object.keys() ?
|
||||
(function checkObjectKeysCompatibility() {
|
||||
if (typeof Object.keys !== 'undefined')
|
||||
return;
|
||||
|
||||
Object.keys = function objectKeys(obj) {
|
||||
var result = [];
|
||||
for (var i in obj) {
|
||||
if (obj.hasOwnProperty(i))
|
||||
result.push(i);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
|
||||
// No readAsArrayBuffer ?
|
||||
(function checkFileReaderReadAsArrayBuffer() {
|
||||
if (typeof FileReader === 'undefined')
|
||||
return; // FileReader is not implemented
|
||||
var frPrototype = FileReader.prototype;
|
||||
// Older versions of Firefox might not have readAsArrayBuffer
|
||||
if ('readAsArrayBuffer' in frPrototype)
|
||||
return; // readAsArrayBuffer is implemented
|
||||
Object.defineProperty(frPrototype, 'readAsArrayBuffer', {
|
||||
value: function fileReaderReadAsArrayBuffer(blob) {
|
||||
var fileReader = new FileReader();
|
||||
var originalReader = this;
|
||||
fileReader.onload = function fileReaderOnload(evt) {
|
||||
var data = evt.target.result;
|
||||
var buffer = new ArrayBuffer(data.length);
|
||||
var uint8Array = new Uint8Array(buffer);
|
||||
|
||||
for (var i = 0, ii = data.length; i < ii; i++)
|
||||
uint8Array[i] = data.charCodeAt(i);
|
||||
|
||||
Object.defineProperty(originalReader, 'result', {
|
||||
value: buffer,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
var event = document.createEvent('HTMLEvents');
|
||||
event.initEvent('load', false, false);
|
||||
originalReader.dispatchEvent(event);
|
||||
};
|
||||
fileReader.readAsBinaryString(blob);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// No XMLHttpRequest.response ?
|
||||
(function checkXMLHttpRequestResponseCompatibility() {
|
||||
var xhrPrototype = XMLHttpRequest.prototype;
|
||||
if (!('overrideMimeType' in xhrPrototype)) {
|
||||
// IE10 might have response, but not overrideMimeType
|
||||
Object.defineProperty(xhrPrototype, 'overrideMimeType', {
|
||||
value: function xmlHttpRequestOverrideMimeType(mimeType) {}
|
||||
});
|
||||
}
|
||||
if ('response' in xhrPrototype ||
|
||||
'mozResponseArrayBuffer' in xhrPrototype ||
|
||||
'mozResponse' in xhrPrototype ||
|
||||
'responseArrayBuffer' in xhrPrototype)
|
||||
return;
|
||||
// IE9 ?
|
||||
if (typeof VBArray !== 'undefined') {
|
||||
Object.defineProperty(xhrPrototype, 'response', {
|
||||
get: function xmlHttpRequestResponseGet() {
|
||||
return new Uint8Array(new VBArray(this.responseBody).toArray());
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// other browsers
|
||||
function responseTypeSetter() {
|
||||
// will be only called to set "arraybuffer"
|
||||
this.overrideMimeType('text/plain; charset=x-user-defined');
|
||||
}
|
||||
if (typeof xhrPrototype.overrideMimeType === 'function') {
|
||||
Object.defineProperty(xhrPrototype, 'responseType',
|
||||
{ set: responseTypeSetter });
|
||||
}
|
||||
function responseGetter() {
|
||||
var text = this.responseText;
|
||||
var i, n = text.length;
|
||||
var result = new Uint8Array(n);
|
||||
for (i = 0; i < n; ++i)
|
||||
result[i] = text.charCodeAt(i) & 0xFF;
|
||||
return result;
|
||||
}
|
||||
Object.defineProperty(xhrPrototype, 'response', { get: responseGetter });
|
||||
})();
|
||||
|
||||
// window.btoa (base64 encode function) ?
|
||||
(function checkWindowBtoaCompatibility() {
|
||||
if ('btoa' in window)
|
||||
return;
|
||||
|
||||
var digits =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
|
||||
window.btoa = function windowBtoa(chars) {
|
||||
var buffer = '';
|
||||
var i, n;
|
||||
for (i = 0, n = chars.length; i < n; i += 3) {
|
||||
var b1 = chars.charCodeAt(i) & 0xFF;
|
||||
var b2 = chars.charCodeAt(i + 1) & 0xFF;
|
||||
var b3 = chars.charCodeAt(i + 2) & 0xFF;
|
||||
var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
|
||||
var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
|
||||
var d4 = i + 2 < n ? (b3 & 0x3F) : 64;
|
||||
buffer += (digits.charAt(d1) + digits.charAt(d2) +
|
||||
digits.charAt(d3) + digits.charAt(d4));
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
})();
|
||||
|
||||
// Function.prototype.bind ?
|
||||
(function checkFunctionPrototypeBindCompatibility() {
|
||||
if (typeof Function.prototype.bind !== 'undefined')
|
||||
return;
|
||||
|
||||
Function.prototype.bind = function functionPrototypeBind(obj) {
|
||||
var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
|
||||
var bound = function functionPrototypeBindBound() {
|
||||
var args = Array.prototype.concat.apply(headArgs, arguments);
|
||||
return fn.apply(obj, args);
|
||||
};
|
||||
return bound;
|
||||
};
|
||||
})();
|
||||
|
||||
// IE9/10 text/html data URI
|
||||
(function checkDataURICompatibility() {
|
||||
if (!('documentMode' in document) ||
|
||||
document.documentMode !== 9 && document.documentMode !== 10)
|
||||
return;
|
||||
// overriding the src property
|
||||
var originalSrcDescriptor = Object.getOwnPropertyDescriptor(
|
||||
HTMLIFrameElement.prototype, 'src');
|
||||
Object.defineProperty(HTMLIFrameElement.prototype, 'src', {
|
||||
get: function htmlIFrameElementPrototypeSrcGet() { return this.$src; },
|
||||
set: function htmlIFrameElementPrototypeSrcSet(src) {
|
||||
this.$src = src;
|
||||
if (src.substr(0, 14) != 'data:text/html') {
|
||||
originalSrcDescriptor.set.call(this, src);
|
||||
return;
|
||||
}
|
||||
// for text/html, using blank document and then
|
||||
// document's open, write, and close operations
|
||||
originalSrcDescriptor.set.call(this, 'about:blank');
|
||||
setTimeout((function htmlIFrameElementPrototypeSrcOpenWriteClose() {
|
||||
var doc = this.contentDocument;
|
||||
doc.open('text/html');
|
||||
doc.write(src.substr(src.indexOf(',') + 1));
|
||||
doc.close();
|
||||
}).bind(this), 0);
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
})();
|
||||
|
||||
// HTMLElement dataset property
|
||||
(function checkDatasetProperty() {
|
||||
var div = document.createElement('div');
|
||||
if ('dataset' in div)
|
||||
return; // dataset property exists
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'dataset', {
|
||||
get: function() {
|
||||
if (this._dataset)
|
||||
return this._dataset;
|
||||
|
||||
var dataset = {};
|
||||
for (var j = 0, jj = this.attributes.length; j < jj; j++) {
|
||||
var attribute = this.attributes[j];
|
||||
if (attribute.name.substring(0, 5) != 'data-')
|
||||
continue;
|
||||
var key = attribute.name.substring(5).replace(/\-([a-z])/g,
|
||||
function(all, ch) { return ch.toUpperCase(); });
|
||||
dataset[key] = attribute.value;
|
||||
}
|
||||
|
||||
Object.defineProperty(this, '_dataset', {
|
||||
value: dataset,
|
||||
writable: false,
|
||||
enumerable: false
|
||||
});
|
||||
return dataset;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
})();
|
||||
|
||||
// HTMLElement classList property
|
||||
(function checkClassListProperty() {
|
||||
var div = document.createElement('div');
|
||||
if ('classList' in div)
|
||||
return; // classList property exists
|
||||
|
||||
function changeList(element, itemName, add, remove) {
|
||||
var s = element.className || '';
|
||||
var list = s.split(/\s+/g);
|
||||
if (list[0] === '') list.shift();
|
||||
var index = list.indexOf(itemName);
|
||||
if (index < 0 && add)
|
||||
list.push(itemName);
|
||||
if (index >= 0 && remove)
|
||||
list.splice(index, 1);
|
||||
element.className = list.join(' ');
|
||||
}
|
||||
|
||||
var classListPrototype = {
|
||||
add: function(name) {
|
||||
changeList(this.element, name, true, false);
|
||||
},
|
||||
remove: function(name) {
|
||||
changeList(this.element, name, false, true);
|
||||
},
|
||||
toggle: function(name) {
|
||||
changeList(this.element, name, true, true);
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'classList', {
|
||||
get: function() {
|
||||
if (this._classList)
|
||||
return this._classList;
|
||||
|
||||
var classList = Object.create(classListPrototype, {
|
||||
element: {
|
||||
value: this,
|
||||
writable: false,
|
||||
enumerable: true
|
||||
}
|
||||
});
|
||||
Object.defineProperty(this, '_classList', {
|
||||
value: classList,
|
||||
writable: false,
|
||||
enumerable: false
|
||||
});
|
||||
return classList;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
})();
|
||||
|
||||
// Check console compatability
|
||||
(function checkConsoleCompatibility() {
|
||||
if (!('console' in window)) {
|
||||
window.console = {
|
||||
log: function() {},
|
||||
error: function() {}
|
||||
};
|
||||
} else if (!('bind' in console.log)) {
|
||||
// native functions in IE9 might not have bind
|
||||
console.log = (function(fn) {
|
||||
return function(msg) { return fn(msg); };
|
||||
})(console.log);
|
||||
console.error = (function(fn) {
|
||||
return function(msg) { return fn(msg); };
|
||||
})(console.error);
|
||||
}
|
||||
})();
|
||||
|
||||
// Check onclick compatibility in Opera
|
||||
(function checkOnClickCompatibility() {
|
||||
// workaround for reported Opera bug DSK-354448:
|
||||
// onclick fires on disabled buttons with opaque content
|
||||
function ignoreIfTargetDisabled(event) {
|
||||
if (isDisabled(event.target)) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
function isDisabled(node) {
|
||||
return node.disabled || (node.parentNode && isDisabled(node.parentNode));
|
||||
}
|
||||
if (navigator.userAgent.indexOf('Opera') != -1) {
|
||||
// use browser detection since we cannot feature-check this bug
|
||||
document.addEventListener('click', ignoreIfTargetDisabled, true);
|
||||
}
|
||||
})();
|
||||
|
||||
// Checks if navigator.language is supported
|
||||
(function checkNavigatorLanguage() {
|
||||
if ('language' in navigator)
|
||||
return;
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
get: function navigatorLanguage() {
|
||||
var language = navigator.userLanguage || 'en-US';
|
||||
return language.substring(0, 2).toLowerCase() +
|
||||
language.substring(2).toUpperCase();
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
})();
|
||||
491
common/static/js/vendor/pdfjs/debugger.js
vendored
Normal file
@@ -0,0 +1,491 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* globals PDFJS */
|
||||
|
||||
'use strict';
|
||||
|
||||
var FontInspector = (function FontInspectorClosure() {
|
||||
var fonts;
|
||||
var panelWidth = 300;
|
||||
var active = false;
|
||||
var fontAttribute = 'data-font-name';
|
||||
function removeSelection() {
|
||||
var divs = document.querySelectorAll('div[' + fontAttribute + ']');
|
||||
for (var i = 0, ii = divs.length; i < ii; ++i) {
|
||||
var div = divs[i];
|
||||
div.className = '';
|
||||
}
|
||||
}
|
||||
function resetSelection() {
|
||||
var divs = document.querySelectorAll('div[' + fontAttribute + ']');
|
||||
for (var i = 0, ii = divs.length; i < ii; ++i) {
|
||||
var div = divs[i];
|
||||
div.className = 'debuggerHideText';
|
||||
}
|
||||
}
|
||||
function selectFont(fontName, show) {
|
||||
var divs = document.querySelectorAll('div[' + fontAttribute + '=' +
|
||||
fontName + ']');
|
||||
for (var i = 0, ii = divs.length; i < ii; ++i) {
|
||||
var div = divs[i];
|
||||
div.className = show ? 'debuggerShowText' : 'debuggerHideText';
|
||||
}
|
||||
}
|
||||
function textLayerClick(e) {
|
||||
if (!e.target.dataset.fontName || e.target.tagName != 'DIV')
|
||||
return;
|
||||
var fontName = e.target.dataset.fontName;
|
||||
var selects = document.getElementsByTagName('input');
|
||||
for (var i = 0; i < selects.length; ++i) {
|
||||
var select = selects[i];
|
||||
if (select.dataset.fontName != fontName) continue;
|
||||
select.checked = !select.checked;
|
||||
selectFont(fontName, select.checked);
|
||||
select.scrollIntoView();
|
||||
}
|
||||
}
|
||||
return {
|
||||
// Properties/functions needed by PDFBug.
|
||||
id: 'FontInspector',
|
||||
name: 'Font Inspector',
|
||||
panel: null,
|
||||
manager: null,
|
||||
init: function init() {
|
||||
var panel = this.panel;
|
||||
panel.setAttribute('style', 'padding: 5px;');
|
||||
var tmp = document.createElement('button');
|
||||
tmp.addEventListener('click', resetSelection);
|
||||
tmp.textContent = 'Refresh';
|
||||
panel.appendChild(tmp);
|
||||
|
||||
fonts = document.createElement('div');
|
||||
panel.appendChild(fonts);
|
||||
},
|
||||
enabled: false,
|
||||
get active() {
|
||||
return active;
|
||||
},
|
||||
set active(value) {
|
||||
active = value;
|
||||
if (active) {
|
||||
document.body.addEventListener('click', textLayerClick, true);
|
||||
resetSelection();
|
||||
} else {
|
||||
document.body.removeEventListener('click', textLayerClick, true);
|
||||
removeSelection();
|
||||
}
|
||||
},
|
||||
// FontInspector specific functions.
|
||||
fontAdded: function fontAdded(fontObj, url) {
|
||||
function properties(obj, list) {
|
||||
var moreInfo = document.createElement('table');
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var tr = document.createElement('tr');
|
||||
var td1 = document.createElement('td');
|
||||
td1.textContent = list[i];
|
||||
tr.appendChild(td1);
|
||||
var td2 = document.createElement('td');
|
||||
td2.textContent = obj[list[i]].toString();
|
||||
tr.appendChild(td2);
|
||||
moreInfo.appendChild(tr);
|
||||
}
|
||||
return moreInfo;
|
||||
}
|
||||
var moreInfo = properties(fontObj, ['name', 'type']);
|
||||
var m = /url\(['"]?([^\)"']+)/.exec(url);
|
||||
var fontName = fontObj.loadedName;
|
||||
var font = document.createElement('div');
|
||||
var name = document.createElement('span');
|
||||
name.textContent = fontName;
|
||||
var download = document.createElement('a');
|
||||
download.href = m[1];
|
||||
download.textContent = 'Download';
|
||||
var logIt = document.createElement('a');
|
||||
logIt.href = '';
|
||||
logIt.textContent = 'Log';
|
||||
logIt.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
console.log(fontObj);
|
||||
});
|
||||
var select = document.createElement('input');
|
||||
select.setAttribute('type', 'checkbox');
|
||||
select.dataset.fontName = fontName;
|
||||
select.addEventListener('click', (function(select, fontName) {
|
||||
return (function() {
|
||||
selectFont(fontName, select.checked);
|
||||
});
|
||||
})(select, fontName));
|
||||
font.appendChild(select);
|
||||
font.appendChild(name);
|
||||
font.appendChild(document.createTextNode(' '));
|
||||
font.appendChild(download);
|
||||
font.appendChild(document.createTextNode(' '));
|
||||
font.appendChild(logIt);
|
||||
font.appendChild(moreInfo);
|
||||
fonts.appendChild(font);
|
||||
// Somewhat of a hack, should probably add a hook for when the text layer
|
||||
// is done rendering.
|
||||
setTimeout(function() {
|
||||
if (this.active)
|
||||
resetSelection();
|
||||
}.bind(this), 2000);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
// Manages all the page steppers.
|
||||
var StepperManager = (function StepperManagerClosure() {
|
||||
var steppers = [];
|
||||
var stepperDiv = null;
|
||||
var stepperControls = null;
|
||||
var stepperChooser = null;
|
||||
var breakPoints = {};
|
||||
return {
|
||||
// Properties/functions needed by PDFBug.
|
||||
id: 'Stepper',
|
||||
name: 'Stepper',
|
||||
panel: null,
|
||||
manager: null,
|
||||
init: function init() {
|
||||
var self = this;
|
||||
this.panel.setAttribute('style', 'padding: 5px;');
|
||||
stepperControls = document.createElement('div');
|
||||
stepperChooser = document.createElement('select');
|
||||
stepperChooser.addEventListener('change', function(event) {
|
||||
self.selectStepper(this.value);
|
||||
});
|
||||
stepperControls.appendChild(stepperChooser);
|
||||
stepperDiv = document.createElement('div');
|
||||
this.panel.appendChild(stepperControls);
|
||||
this.panel.appendChild(stepperDiv);
|
||||
if (sessionStorage.getItem('pdfjsBreakPoints'))
|
||||
breakPoints = JSON.parse(sessionStorage.getItem('pdfjsBreakPoints'));
|
||||
},
|
||||
enabled: false,
|
||||
active: false,
|
||||
// Stepper specific functions.
|
||||
create: function create(pageIndex) {
|
||||
var debug = document.createElement('div');
|
||||
debug.id = 'stepper' + pageIndex;
|
||||
debug.setAttribute('hidden', true);
|
||||
debug.className = 'stepper';
|
||||
stepperDiv.appendChild(debug);
|
||||
var b = document.createElement('option');
|
||||
b.textContent = 'Page ' + (pageIndex + 1);
|
||||
b.value = pageIndex;
|
||||
stepperChooser.appendChild(b);
|
||||
var initBreakPoints = breakPoints[pageIndex] || [];
|
||||
var stepper = new Stepper(debug, pageIndex, initBreakPoints);
|
||||
steppers.push(stepper);
|
||||
if (steppers.length === 1)
|
||||
this.selectStepper(pageIndex, false);
|
||||
return stepper;
|
||||
},
|
||||
selectStepper: function selectStepper(pageIndex, selectPanel) {
|
||||
if (selectPanel)
|
||||
this.manager.selectPanel(1);
|
||||
for (var i = 0; i < steppers.length; ++i) {
|
||||
var stepper = steppers[i];
|
||||
if (stepper.pageIndex == pageIndex)
|
||||
stepper.panel.removeAttribute('hidden');
|
||||
else
|
||||
stepper.panel.setAttribute('hidden', true);
|
||||
}
|
||||
var options = stepperChooser.options;
|
||||
for (var i = 0; i < options.length; ++i) {
|
||||
var option = options[i];
|
||||
option.selected = option.value == pageIndex;
|
||||
}
|
||||
},
|
||||
saveBreakPoints: function saveBreakPoints(pageIndex, bps) {
|
||||
breakPoints[pageIndex] = bps;
|
||||
sessionStorage.setItem('pdfjsBreakPoints', JSON.stringify(breakPoints));
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
// The stepper for each page's IRQueue.
|
||||
var Stepper = (function StepperClosure() {
|
||||
function Stepper(panel, pageIndex, initialBreakPoints) {
|
||||
this.panel = panel;
|
||||
this.len = 0;
|
||||
this.breakPoint = 0;
|
||||
this.nextBreakPoint = null;
|
||||
this.pageIndex = pageIndex;
|
||||
this.breakPoints = initialBreakPoints;
|
||||
this.currentIdx = -1;
|
||||
}
|
||||
Stepper.prototype = {
|
||||
init: function init(IRQueue) {
|
||||
// Shorter way to create element and optionally set textContent.
|
||||
function c(tag, textContent) {
|
||||
var d = document.createElement(tag);
|
||||
if (textContent)
|
||||
d.textContent = textContent;
|
||||
return d;
|
||||
}
|
||||
var panel = this.panel;
|
||||
this.len = IRQueue.fnArray.length;
|
||||
var content = c('div', 'c=continue, s=step');
|
||||
var table = c('table');
|
||||
content.appendChild(table);
|
||||
table.cellSpacing = 0;
|
||||
var headerRow = c('tr');
|
||||
table.appendChild(headerRow);
|
||||
headerRow.appendChild(c('th', 'Break'));
|
||||
headerRow.appendChild(c('th', 'Idx'));
|
||||
headerRow.appendChild(c('th', 'fn'));
|
||||
headerRow.appendChild(c('th', 'args'));
|
||||
|
||||
var self = this;
|
||||
for (var i = 0; i < IRQueue.fnArray.length; i++) {
|
||||
var line = c('tr');
|
||||
line.className = 'line';
|
||||
line.dataset.idx = i;
|
||||
table.appendChild(line);
|
||||
var checked = this.breakPoints.indexOf(i) != -1;
|
||||
var args = IRQueue.argsArray[i] ? IRQueue.argsArray[i] : [];
|
||||
|
||||
var breakCell = c('td');
|
||||
var cbox = c('input');
|
||||
cbox.type = 'checkbox';
|
||||
cbox.className = 'points';
|
||||
cbox.checked = checked;
|
||||
cbox.onclick = (function(x) {
|
||||
return function() {
|
||||
if (this.checked)
|
||||
self.breakPoints.push(x);
|
||||
else
|
||||
self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
|
||||
StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
|
||||
};
|
||||
})(i);
|
||||
|
||||
breakCell.appendChild(cbox);
|
||||
line.appendChild(breakCell);
|
||||
line.appendChild(c('td', i.toString()));
|
||||
line.appendChild(c('td', IRQueue.fnArray[i]));
|
||||
line.appendChild(c('td', args.join(', ')));
|
||||
}
|
||||
panel.appendChild(content);
|
||||
var self = this;
|
||||
},
|
||||
getNextBreakPoint: function getNextBreakPoint() {
|
||||
this.breakPoints.sort(function(a, b) { return a - b; });
|
||||
for (var i = 0; i < this.breakPoints.length; i++) {
|
||||
if (this.breakPoints[i] > this.currentIdx)
|
||||
return this.breakPoints[i];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
breakIt: function breakIt(idx, callback) {
|
||||
StepperManager.selectStepper(this.pageIndex, true);
|
||||
var self = this;
|
||||
var dom = document;
|
||||
self.currentIdx = idx;
|
||||
var listener = function(e) {
|
||||
switch (e.keyCode) {
|
||||
case 83: // step
|
||||
dom.removeEventListener('keydown', listener, false);
|
||||
self.nextBreakPoint = self.currentIdx + 1;
|
||||
self.goTo(-1);
|
||||
callback();
|
||||
break;
|
||||
case 67: // continue
|
||||
dom.removeEventListener('keydown', listener, false);
|
||||
var breakPoint = self.getNextBreakPoint();
|
||||
self.nextBreakPoint = breakPoint;
|
||||
self.goTo(-1);
|
||||
callback();
|
||||
break;
|
||||
}
|
||||
};
|
||||
dom.addEventListener('keydown', listener, false);
|
||||
self.goTo(idx);
|
||||
},
|
||||
goTo: function goTo(idx) {
|
||||
var allRows = this.panel.getElementsByClassName('line');
|
||||
for (var x = 0, xx = allRows.length; x < xx; ++x) {
|
||||
var row = allRows[x];
|
||||
if (row.dataset.idx == idx) {
|
||||
row.style.backgroundColor = 'rgb(251,250,207)';
|
||||
row.scrollIntoView();
|
||||
} else {
|
||||
row.style.backgroundColor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return Stepper;
|
||||
})();
|
||||
|
||||
var Stats = (function Stats() {
|
||||
var stats = [];
|
||||
function clear(node) {
|
||||
while (node.hasChildNodes())
|
||||
node.removeChild(node.lastChild);
|
||||
}
|
||||
function getStatIndex(pageNumber) {
|
||||
for (var i = 0, ii = stats.length; i < ii; ++i)
|
||||
if (stats[i].pageNumber === pageNumber)
|
||||
return i;
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
// Properties/functions needed by PDFBug.
|
||||
id: 'Stats',
|
||||
name: 'Stats',
|
||||
panel: null,
|
||||
manager: null,
|
||||
init: function init() {
|
||||
this.panel.setAttribute('style', 'padding: 5px;');
|
||||
PDFJS.enableStats = true;
|
||||
},
|
||||
enabled: false,
|
||||
active: false,
|
||||
// Stats specific functions.
|
||||
add: function(pageNumber, stat) {
|
||||
if (!stat)
|
||||
return;
|
||||
var statsIndex = getStatIndex(pageNumber);
|
||||
if (statsIndex !== false) {
|
||||
var b = stats[statsIndex];
|
||||
this.panel.removeChild(b.div);
|
||||
stats.splice(statsIndex, 1);
|
||||
}
|
||||
var wrapper = document.createElement('div');
|
||||
wrapper.className = 'stats';
|
||||
var title = document.createElement('div');
|
||||
title.className = 'title';
|
||||
title.textContent = 'Page: ' + pageNumber;
|
||||
var statsDiv = document.createElement('div');
|
||||
statsDiv.textContent = stat.toString();
|
||||
wrapper.appendChild(title);
|
||||
wrapper.appendChild(statsDiv);
|
||||
stats.push({ pageNumber: pageNumber, div: wrapper });
|
||||
stats.sort(function(a, b) { return a.pageNumber - b.pageNumber; });
|
||||
clear(this.panel);
|
||||
for (var i = 0, ii = stats.length; i < ii; ++i)
|
||||
this.panel.appendChild(stats[i].div);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
// Manages all the debugging tools.
|
||||
var PDFBug = (function PDFBugClosure() {
|
||||
var panelWidth = 300;
|
||||
var buttons = [];
|
||||
var activePanel = null;
|
||||
|
||||
return {
|
||||
tools: [
|
||||
FontInspector,
|
||||
StepperManager,
|
||||
Stats
|
||||
],
|
||||
enable: function(ids) {
|
||||
var all = false, tools = this.tools;
|
||||
if (ids.length === 1 && ids[0] === 'all')
|
||||
all = true;
|
||||
for (var i = 0; i < tools.length; ++i) {
|
||||
var tool = tools[i];
|
||||
if (all || ids.indexOf(tool.id) !== -1)
|
||||
tool.enabled = true;
|
||||
}
|
||||
if (!all) {
|
||||
// Sort the tools by the order they are enabled.
|
||||
tools.sort(function(a, b) {
|
||||
var indexA = ids.indexOf(a.id);
|
||||
indexA = indexA < 0 ? tools.length : indexA;
|
||||
var indexB = ids.indexOf(b.id);
|
||||
indexB = indexB < 0 ? tools.length : indexB;
|
||||
return indexA - indexB;
|
||||
});
|
||||
}
|
||||
},
|
||||
init: function init() {
|
||||
/*
|
||||
* Basic Layout:
|
||||
* PDFBug
|
||||
* Controls
|
||||
* Panels
|
||||
* Panel
|
||||
* Panel
|
||||
* ...
|
||||
*/
|
||||
var ui = document.createElement('div');
|
||||
ui.id = 'PDFBug';
|
||||
|
||||
var controls = document.createElement('div');
|
||||
controls.setAttribute('class', 'controls');
|
||||
ui.appendChild(controls);
|
||||
|
||||
var panels = document.createElement('div');
|
||||
panels.setAttribute('class', 'panels');
|
||||
ui.appendChild(panels);
|
||||
|
||||
var container = document.getElementById('viewerContainer');
|
||||
container.appendChild(ui);
|
||||
container.style.right = panelWidth + 'px';
|
||||
|
||||
// Initialize all the debugging tools.
|
||||
var tools = this.tools;
|
||||
var self = this;
|
||||
for (var i = 0; i < tools.length; ++i) {
|
||||
var tool = tools[i];
|
||||
var panel = document.createElement('div');
|
||||
var panelButton = document.createElement('button');
|
||||
panelButton.textContent = tool.name;
|
||||
panelButton.addEventListener('click', (function(selected) {
|
||||
return function(event) {
|
||||
event.preventDefault();
|
||||
self.selectPanel(selected);
|
||||
};
|
||||
})(i));
|
||||
controls.appendChild(panelButton);
|
||||
panels.appendChild(panel);
|
||||
tool.panel = panel;
|
||||
tool.manager = this;
|
||||
if (tool.enabled)
|
||||
tool.init();
|
||||
else
|
||||
panel.textContent = tool.name + ' is disabled. To enable add ' +
|
||||
' "' + tool.id + '" to the pdfBug parameter ' +
|
||||
'and refresh (seperate multiple by commas).';
|
||||
buttons.push(panelButton);
|
||||
}
|
||||
this.selectPanel(0);
|
||||
},
|
||||
selectPanel: function selectPanel(index) {
|
||||
if (index === activePanel)
|
||||
return;
|
||||
activePanel = index;
|
||||
var tools = this.tools;
|
||||
for (var j = 0; j < tools.length; ++j) {
|
||||
if (j == index) {
|
||||
buttons[j].setAttribute('class', 'active');
|
||||
tools[j].active = true;
|
||||
tools[j].panel.removeAttribute('hidden');
|
||||
} else {
|
||||
buttons[j].setAttribute('class', '');
|
||||
tools[j].active = false;
|
||||
tools[j].panel.setAttribute('hidden', 'true');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
922
common/static/js/vendor/pdfjs/l10n.js
vendored
Normal file
@@ -0,0 +1,922 @@
|
||||
/** Copyright (c) 2011-2012 Fabien Cazenave, Mozilla.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
/*
|
||||
Additional modifications for PDF.js project:
|
||||
- Disables language initialization on page loading;
|
||||
- Adds fallback argument to the getL10nData;
|
||||
- Removes consoleLog and simplifies consoleWarn;
|
||||
- Removes window._ assignment.
|
||||
*/
|
||||
/*jshint browser: true, devel: true, es5: true, globalstrict: true */
|
||||
'use strict';
|
||||
|
||||
document.webL10n = (function(window, document, undefined) {
|
||||
var gL10nData = {};
|
||||
var gTextData = '';
|
||||
var gTextProp = 'textContent';
|
||||
var gLanguage = '';
|
||||
var gMacros = {};
|
||||
var gReadyState = 'loading';
|
||||
|
||||
// read-only setting -- we recommend to load l10n resources synchronously
|
||||
var gAsyncResourceLoading = true;
|
||||
|
||||
// debug helpers
|
||||
function consoleWarn(message) {
|
||||
console.log('[l10n] ' + message);
|
||||
};
|
||||
|
||||
/**
|
||||
* DOM helpers for the so-called "HTML API".
|
||||
*
|
||||
* These functions are written for modern browsers. For old versions of IE,
|
||||
* they're overridden in the 'startup' section at the end of this file.
|
||||
*/
|
||||
|
||||
function getL10nResourceLinks() {
|
||||
return document.querySelectorAll('link[type="application/l10n"]');
|
||||
}
|
||||
|
||||
function getTranslatableChildren(element) {
|
||||
return element ? element.querySelectorAll('*[data-l10n-id]') : [];
|
||||
}
|
||||
|
||||
function getL10nAttributes(element) {
|
||||
if (!element)
|
||||
return {};
|
||||
|
||||
var l10nId = element.getAttribute('data-l10n-id');
|
||||
var l10nArgs = element.getAttribute('data-l10n-args');
|
||||
var args = {};
|
||||
if (l10nArgs) {
|
||||
try {
|
||||
args = JSON.parse(l10nArgs);
|
||||
} catch (e) {
|
||||
consoleWarn('could not parse arguments for #' + l10nId);
|
||||
}
|
||||
}
|
||||
return { id: l10nId, args: args };
|
||||
}
|
||||
|
||||
function fireL10nReadyEvent(lang) {
|
||||
var evtObject = document.createEvent('Event');
|
||||
evtObject.initEvent('localized', false, false);
|
||||
evtObject.language = lang;
|
||||
window.dispatchEvent(evtObject);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* l10n resource parser:
|
||||
* - reads (async XHR) the l10n resource matching `lang';
|
||||
* - imports linked resources (synchronously) when specified;
|
||||
* - parses the text data (fills `gL10nData' and `gTextData');
|
||||
* - triggers success/failure callbacks when done.
|
||||
*
|
||||
* @param {string} href
|
||||
* URL of the l10n resource to parse.
|
||||
*
|
||||
* @param {string} lang
|
||||
* locale (language) to parse.
|
||||
*
|
||||
* @param {Function} successCallback
|
||||
* triggered when the l10n resource has been successully parsed.
|
||||
*
|
||||
* @param {Function} failureCallback
|
||||
* triggered when the an error has occured.
|
||||
*
|
||||
* @return {void}
|
||||
* uses the following global variables: gL10nData, gTextData, gTextProp.
|
||||
*/
|
||||
|
||||
function parseResource(href, lang, successCallback, failureCallback) {
|
||||
var baseURL = href.replace(/\/[^\/]*$/, '/');
|
||||
|
||||
// handle escaped characters (backslashes) in a string
|
||||
function evalString(text) {
|
||||
if (text.lastIndexOf('\\') < 0)
|
||||
return text;
|
||||
return text.replace(/\\\\/g, '\\')
|
||||
.replace(/\\n/g, '\n')
|
||||
.replace(/\\r/g, '\r')
|
||||
.replace(/\\t/g, '\t')
|
||||
.replace(/\\b/g, '\b')
|
||||
.replace(/\\f/g, '\f')
|
||||
.replace(/\\{/g, '{')
|
||||
.replace(/\\}/g, '}')
|
||||
.replace(/\\"/g, '"')
|
||||
.replace(/\\'/g, "'");
|
||||
}
|
||||
|
||||
// parse *.properties text data into an l10n dictionary
|
||||
function parseProperties(text) {
|
||||
var dictionary = [];
|
||||
|
||||
// token expressions
|
||||
var reBlank = /^\s*|\s*$/;
|
||||
var reComment = /^\s*#|^\s*$/;
|
||||
var reSection = /^\s*\[(.*)\]\s*$/;
|
||||
var reImport = /^\s*@import\s+url\((.*)\)\s*$/i;
|
||||
var reSplit = /^([^=\s]*)\s*=\s*(.+)$/; // TODO: escape EOLs with '\'
|
||||
|
||||
// parse the *.properties file into an associative array
|
||||
function parseRawLines(rawText, extendedSyntax) {
|
||||
var entries = rawText.replace(reBlank, '').split(/[\r\n]+/);
|
||||
var currentLang = '*';
|
||||
var genericLang = lang.replace(/-[a-z]+$/i, '');
|
||||
var skipLang = false;
|
||||
var match = '';
|
||||
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var line = entries[i];
|
||||
|
||||
// comment or blank line?
|
||||
if (reComment.test(line))
|
||||
continue;
|
||||
|
||||
// the extended syntax supports [lang] sections and @import rules
|
||||
if (extendedSyntax) {
|
||||
if (reSection.test(line)) { // section start?
|
||||
match = reSection.exec(line);
|
||||
currentLang = match[1];
|
||||
skipLang = (currentLang !== '*') &&
|
||||
(currentLang !== lang) && (currentLang !== genericLang);
|
||||
continue;
|
||||
} else if (skipLang) {
|
||||
continue;
|
||||
}
|
||||
if (reImport.test(line)) { // @import rule?
|
||||
match = reImport.exec(line);
|
||||
loadImport(baseURL + match[1]); // load the resource synchronously
|
||||
}
|
||||
}
|
||||
|
||||
// key-value pair
|
||||
var tmp = line.match(reSplit);
|
||||
if (tmp && tmp.length == 3)
|
||||
dictionary[tmp[1]] = evalString(tmp[2]);
|
||||
}
|
||||
}
|
||||
|
||||
// import another *.properties file
|
||||
function loadImport(url) {
|
||||
loadResource(url, function(content) {
|
||||
parseRawLines(content, false); // don't allow recursive imports
|
||||
}, false, false); // load synchronously
|
||||
}
|
||||
|
||||
// fill the dictionary
|
||||
parseRawLines(text, true);
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
// load the specified resource file
|
||||
function loadResource(url, onSuccess, onFailure, asynchronous) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, asynchronous);
|
||||
if (xhr.overrideMimeType) {
|
||||
xhr.overrideMimeType('text/plain; charset=utf-8');
|
||||
}
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200 || xhr.status === 0) {
|
||||
if (onSuccess)
|
||||
onSuccess(xhr.responseText);
|
||||
} else {
|
||||
if (onFailure)
|
||||
onFailure();
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
// load and parse l10n data (warning: global variables are used here)
|
||||
loadResource(href, function(response) {
|
||||
gTextData += response; // mostly for debug
|
||||
|
||||
// parse *.properties text data into an l10n dictionary
|
||||
var data = parseProperties(response);
|
||||
|
||||
// find attribute descriptions, if any
|
||||
for (var key in data) {
|
||||
var id, prop, index = key.lastIndexOf('.');
|
||||
if (index > 0) { // an attribute has been specified
|
||||
id = key.substring(0, index);
|
||||
prop = key.substr(index + 1);
|
||||
} else { // no attribute: assuming text content by default
|
||||
id = key;
|
||||
prop = gTextProp;
|
||||
}
|
||||
if (!gL10nData[id]) {
|
||||
gL10nData[id] = {};
|
||||
}
|
||||
gL10nData[id][prop] = data[key];
|
||||
}
|
||||
|
||||
// trigger callback
|
||||
if (successCallback)
|
||||
successCallback();
|
||||
}, failureCallback, gAsyncResourceLoading);
|
||||
};
|
||||
|
||||
// load and parse all resources for the specified locale
|
||||
function loadLocale(lang, callback) {
|
||||
clear();
|
||||
gLanguage = lang;
|
||||
|
||||
// check all <link type="application/l10n" href="..." /> nodes
|
||||
// and load the resource files
|
||||
var langLinks = getL10nResourceLinks();
|
||||
var langCount = langLinks.length;
|
||||
if (langCount == 0) {
|
||||
consoleWarn('no resource to load, early way out');
|
||||
fireL10nReadyEvent(lang);
|
||||
gReadyState = 'complete';
|
||||
return;
|
||||
}
|
||||
|
||||
// start the callback when all resources are loaded
|
||||
var onResourceLoaded = null;
|
||||
var gResourceCount = 0;
|
||||
onResourceLoaded = function() {
|
||||
gResourceCount++;
|
||||
if (gResourceCount >= langCount) {
|
||||
if (callback) // execute the [optional] callback
|
||||
callback();
|
||||
fireL10nReadyEvent(lang);
|
||||
gReadyState = 'complete';
|
||||
}
|
||||
};
|
||||
|
||||
// load all resource files
|
||||
function l10nResourceLink(link) {
|
||||
var href = link.href;
|
||||
var type = link.type;
|
||||
this.load = function(lang, callback) {
|
||||
var applied = lang;
|
||||
parseResource(href, lang, callback, function() {
|
||||
consoleWarn(href + ' not found.');
|
||||
applied = '';
|
||||
});
|
||||
return applied; // return lang if found, an empty string if not found
|
||||
};
|
||||
}
|
||||
|
||||
for (var i = 0; i < langCount; i++) {
|
||||
var resource = new l10nResourceLink(langLinks[i]);
|
||||
var rv = resource.load(lang, onResourceLoaded);
|
||||
if (rv != lang) { // lang not found, used default resource instead
|
||||
consoleWarn('"' + lang + '" resource not found');
|
||||
gLanguage = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clear all l10n data
|
||||
function clear() {
|
||||
gL10nData = {};
|
||||
gTextData = '';
|
||||
gLanguage = '';
|
||||
// TODO: clear all non predefined macros.
|
||||
// There's no such macro /yet/ but we're planning to have some...
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get rules for plural forms (shared with JetPack), see:
|
||||
* http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
|
||||
* https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p
|
||||
*
|
||||
* @param {string} lang
|
||||
* locale (language) used.
|
||||
*
|
||||
* @return {Function}
|
||||
* returns a function that gives the plural form name for a given integer:
|
||||
* var fun = getPluralRules('en');
|
||||
* fun(1) -> 'one'
|
||||
* fun(0) -> 'other'
|
||||
* fun(1000) -> 'other'.
|
||||
*/
|
||||
|
||||
function getPluralRules(lang) {
|
||||
var locales2rules = {
|
||||
'af': 3,
|
||||
'ak': 4,
|
||||
'am': 4,
|
||||
'ar': 1,
|
||||
'asa': 3,
|
||||
'az': 0,
|
||||
'be': 11,
|
||||
'bem': 3,
|
||||
'bez': 3,
|
||||
'bg': 3,
|
||||
'bh': 4,
|
||||
'bm': 0,
|
||||
'bn': 3,
|
||||
'bo': 0,
|
||||
'br': 20,
|
||||
'brx': 3,
|
||||
'bs': 11,
|
||||
'ca': 3,
|
||||
'cgg': 3,
|
||||
'chr': 3,
|
||||
'cs': 12,
|
||||
'cy': 17,
|
||||
'da': 3,
|
||||
'de': 3,
|
||||
'dv': 3,
|
||||
'dz': 0,
|
||||
'ee': 3,
|
||||
'el': 3,
|
||||
'en': 3,
|
||||
'eo': 3,
|
||||
'es': 3,
|
||||
'et': 3,
|
||||
'eu': 3,
|
||||
'fa': 0,
|
||||
'ff': 5,
|
||||
'fi': 3,
|
||||
'fil': 4,
|
||||
'fo': 3,
|
||||
'fr': 5,
|
||||
'fur': 3,
|
||||
'fy': 3,
|
||||
'ga': 8,
|
||||
'gd': 24,
|
||||
'gl': 3,
|
||||
'gsw': 3,
|
||||
'gu': 3,
|
||||
'guw': 4,
|
||||
'gv': 23,
|
||||
'ha': 3,
|
||||
'haw': 3,
|
||||
'he': 2,
|
||||
'hi': 4,
|
||||
'hr': 11,
|
||||
'hu': 0,
|
||||
'id': 0,
|
||||
'ig': 0,
|
||||
'ii': 0,
|
||||
'is': 3,
|
||||
'it': 3,
|
||||
'iu': 7,
|
||||
'ja': 0,
|
||||
'jmc': 3,
|
||||
'jv': 0,
|
||||
'ka': 0,
|
||||
'kab': 5,
|
||||
'kaj': 3,
|
||||
'kcg': 3,
|
||||
'kde': 0,
|
||||
'kea': 0,
|
||||
'kk': 3,
|
||||
'kl': 3,
|
||||
'km': 0,
|
||||
'kn': 0,
|
||||
'ko': 0,
|
||||
'ksb': 3,
|
||||
'ksh': 21,
|
||||
'ku': 3,
|
||||
'kw': 7,
|
||||
'lag': 18,
|
||||
'lb': 3,
|
||||
'lg': 3,
|
||||
'ln': 4,
|
||||
'lo': 0,
|
||||
'lt': 10,
|
||||
'lv': 6,
|
||||
'mas': 3,
|
||||
'mg': 4,
|
||||
'mk': 16,
|
||||
'ml': 3,
|
||||
'mn': 3,
|
||||
'mo': 9,
|
||||
'mr': 3,
|
||||
'ms': 0,
|
||||
'mt': 15,
|
||||
'my': 0,
|
||||
'nah': 3,
|
||||
'naq': 7,
|
||||
'nb': 3,
|
||||
'nd': 3,
|
||||
'ne': 3,
|
||||
'nl': 3,
|
||||
'nn': 3,
|
||||
'no': 3,
|
||||
'nr': 3,
|
||||
'nso': 4,
|
||||
'ny': 3,
|
||||
'nyn': 3,
|
||||
'om': 3,
|
||||
'or': 3,
|
||||
'pa': 3,
|
||||
'pap': 3,
|
||||
'pl': 13,
|
||||
'ps': 3,
|
||||
'pt': 3,
|
||||
'rm': 3,
|
||||
'ro': 9,
|
||||
'rof': 3,
|
||||
'ru': 11,
|
||||
'rwk': 3,
|
||||
'sah': 0,
|
||||
'saq': 3,
|
||||
'se': 7,
|
||||
'seh': 3,
|
||||
'ses': 0,
|
||||
'sg': 0,
|
||||
'sh': 11,
|
||||
'shi': 19,
|
||||
'sk': 12,
|
||||
'sl': 14,
|
||||
'sma': 7,
|
||||
'smi': 7,
|
||||
'smj': 7,
|
||||
'smn': 7,
|
||||
'sms': 7,
|
||||
'sn': 3,
|
||||
'so': 3,
|
||||
'sq': 3,
|
||||
'sr': 11,
|
||||
'ss': 3,
|
||||
'ssy': 3,
|
||||
'st': 3,
|
||||
'sv': 3,
|
||||
'sw': 3,
|
||||
'syr': 3,
|
||||
'ta': 3,
|
||||
'te': 3,
|
||||
'teo': 3,
|
||||
'th': 0,
|
||||
'ti': 4,
|
||||
'tig': 3,
|
||||
'tk': 3,
|
||||
'tl': 4,
|
||||
'tn': 3,
|
||||
'to': 0,
|
||||
'tr': 0,
|
||||
'ts': 3,
|
||||
'tzm': 22,
|
||||
'uk': 11,
|
||||
'ur': 3,
|
||||
've': 3,
|
||||
'vi': 0,
|
||||
'vun': 3,
|
||||
'wa': 4,
|
||||
'wae': 3,
|
||||
'wo': 0,
|
||||
'xh': 3,
|
||||
'xog': 3,
|
||||
'yo': 0,
|
||||
'zh': 0,
|
||||
'zu': 3
|
||||
};
|
||||
|
||||
// utility functions for plural rules methods
|
||||
function isIn(n, list) {
|
||||
return list.indexOf(n) !== -1;
|
||||
}
|
||||
function isBetween(n, start, end) {
|
||||
return start <= n && n <= end;
|
||||
}
|
||||
|
||||
// list of all plural rules methods:
|
||||
// map an integer to the plural form name to use
|
||||
var pluralRules = {
|
||||
'0': function(n) {
|
||||
return 'other';
|
||||
},
|
||||
'1': function(n) {
|
||||
if ((isBetween((n % 100), 3, 10)))
|
||||
return 'few';
|
||||
if (n === 0)
|
||||
return 'zero';
|
||||
if ((isBetween((n % 100), 11, 99)))
|
||||
return 'many';
|
||||
if (n == 2)
|
||||
return 'two';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'2': function(n) {
|
||||
if (n !== 0 && (n % 10) === 0)
|
||||
return 'many';
|
||||
if (n == 2)
|
||||
return 'two';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'3': function(n) {
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'4': function(n) {
|
||||
if ((isBetween(n, 0, 1)))
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'5': function(n) {
|
||||
if ((isBetween(n, 0, 2)) && n != 2)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'6': function(n) {
|
||||
if (n === 0)
|
||||
return 'zero';
|
||||
if ((n % 10) == 1 && (n % 100) != 11)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'7': function(n) {
|
||||
if (n == 2)
|
||||
return 'two';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'8': function(n) {
|
||||
if ((isBetween(n, 3, 6)))
|
||||
return 'few';
|
||||
if ((isBetween(n, 7, 10)))
|
||||
return 'many';
|
||||
if (n == 2)
|
||||
return 'two';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'9': function(n) {
|
||||
if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19)))
|
||||
return 'few';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'10': function(n) {
|
||||
if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19)))
|
||||
return 'few';
|
||||
if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19)))
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'11': function(n) {
|
||||
if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
|
||||
return 'few';
|
||||
if ((n % 10) === 0 ||
|
||||
(isBetween((n % 10), 5, 9)) ||
|
||||
(isBetween((n % 100), 11, 14)))
|
||||
return 'many';
|
||||
if ((n % 10) == 1 && (n % 100) != 11)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'12': function(n) {
|
||||
if ((isBetween(n, 2, 4)))
|
||||
return 'few';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'13': function(n) {
|
||||
if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
|
||||
return 'few';
|
||||
if (n != 1 && (isBetween((n % 10), 0, 1)) ||
|
||||
(isBetween((n % 10), 5, 9)) ||
|
||||
(isBetween((n % 100), 12, 14)))
|
||||
return 'many';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'14': function(n) {
|
||||
if ((isBetween((n % 100), 3, 4)))
|
||||
return 'few';
|
||||
if ((n % 100) == 2)
|
||||
return 'two';
|
||||
if ((n % 100) == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'15': function(n) {
|
||||
if (n === 0 || (isBetween((n % 100), 2, 10)))
|
||||
return 'few';
|
||||
if ((isBetween((n % 100), 11, 19)))
|
||||
return 'many';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'16': function(n) {
|
||||
if ((n % 10) == 1 && n != 11)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'17': function(n) {
|
||||
if (n == 3)
|
||||
return 'few';
|
||||
if (n === 0)
|
||||
return 'zero';
|
||||
if (n == 6)
|
||||
return 'many';
|
||||
if (n == 2)
|
||||
return 'two';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'18': function(n) {
|
||||
if (n === 0)
|
||||
return 'zero';
|
||||
if ((isBetween(n, 0, 2)) && n !== 0 && n != 2)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'19': function(n) {
|
||||
if ((isBetween(n, 2, 10)))
|
||||
return 'few';
|
||||
if ((isBetween(n, 0, 1)))
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'20': function(n) {
|
||||
if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(
|
||||
isBetween((n % 100), 10, 19) ||
|
||||
isBetween((n % 100), 70, 79) ||
|
||||
isBetween((n % 100), 90, 99)
|
||||
))
|
||||
return 'few';
|
||||
if ((n % 1000000) === 0 && n !== 0)
|
||||
return 'many';
|
||||
if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92]))
|
||||
return 'two';
|
||||
if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91]))
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'21': function(n) {
|
||||
if (n === 0)
|
||||
return 'zero';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'22': function(n) {
|
||||
if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99)))
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'23': function(n) {
|
||||
if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'24': function(n) {
|
||||
if ((isBetween(n, 3, 10) || isBetween(n, 13, 19)))
|
||||
return 'few';
|
||||
if (isIn(n, [2, 12]))
|
||||
return 'two';
|
||||
if (isIn(n, [1, 11]))
|
||||
return 'one';
|
||||
return 'other';
|
||||
}
|
||||
};
|
||||
|
||||
// return a function that gives the plural form name for a given integer
|
||||
var index = locales2rules[lang.replace(/-.*$/, '')];
|
||||
if (!(index in pluralRules)) {
|
||||
consoleWarn('plural form unknown for [' + lang + ']');
|
||||
return function() { return 'other'; };
|
||||
}
|
||||
return pluralRules[index];
|
||||
}
|
||||
|
||||
// pre-defined 'plural' macro
|
||||
gMacros.plural = function(str, param, key, prop) {
|
||||
var n = parseFloat(param);
|
||||
if (isNaN(n))
|
||||
return str;
|
||||
|
||||
// TODO: support other properties (l20n still doesn't...)
|
||||
if (prop != gTextProp)
|
||||
return str;
|
||||
|
||||
// initialize _pluralRules
|
||||
if (!gMacros._pluralRules)
|
||||
gMacros._pluralRules = getPluralRules(gLanguage);
|
||||
var index = '[' + gMacros._pluralRules(n) + ']';
|
||||
|
||||
// try to find a [zero|one|two] key if it's defined
|
||||
if (n === 0 && (key + '[zero]') in gL10nData) {
|
||||
str = gL10nData[key + '[zero]'][prop];
|
||||
} else if (n == 1 && (key + '[one]') in gL10nData) {
|
||||
str = gL10nData[key + '[one]'][prop];
|
||||
} else if (n == 2 && (key + '[two]') in gL10nData) {
|
||||
str = gL10nData[key + '[two]'][prop];
|
||||
} else if ((key + index) in gL10nData) {
|
||||
str = gL10nData[key + index][prop];
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* l10n dictionary functions
|
||||
*/
|
||||
|
||||
// fetch an l10n object, warn if not found, apply `args' if possible
|
||||
function getL10nData(key, args, fallback) {
|
||||
var data = gL10nData[key];
|
||||
if (!data) {
|
||||
consoleWarn('#' + key + ' missing for [' + gLanguage + ']');
|
||||
if (!fallback) {
|
||||
return null;
|
||||
}
|
||||
data = fallback;
|
||||
}
|
||||
|
||||
/** This is where l10n expressions should be processed.
|
||||
* The plan is to support C-style expressions from the l20n project;
|
||||
* until then, only two kinds of simple expressions are supported:
|
||||
* {[ index ]} and {{ arguments }}.
|
||||
*/
|
||||
var rv = {};
|
||||
for (var prop in data) {
|
||||
var str = data[prop];
|
||||
str = substIndexes(str, args, key, prop);
|
||||
str = substArguments(str, args);
|
||||
rv[prop] = str;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// replace {[macros]} with their values
|
||||
function substIndexes(str, args, key, prop) {
|
||||
var reIndex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)\s*\]\}/;
|
||||
var reMatch = reIndex.exec(str);
|
||||
if (!reMatch || !reMatch.length)
|
||||
return str;
|
||||
|
||||
// an index/macro has been found
|
||||
// Note: at the moment, only one parameter is supported
|
||||
var macroName = reMatch[1];
|
||||
var paramName = reMatch[2];
|
||||
var param;
|
||||
if (args && paramName in args) {
|
||||
param = args[paramName];
|
||||
} else if (paramName in gL10nData) {
|
||||
param = gL10nData[paramName];
|
||||
}
|
||||
|
||||
// there's no macro parser yet: it has to be defined in gMacros
|
||||
if (macroName in gMacros) {
|
||||
var macro = gMacros[macroName];
|
||||
str = macro(str, param, key, prop);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// replace {{arguments}} with their values
|
||||
function substArguments(str, args) {
|
||||
var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/;
|
||||
var match = reArgs.exec(str);
|
||||
while (match) {
|
||||
if (!match || match.length < 2)
|
||||
return str; // argument key not found
|
||||
|
||||
var arg = match[1];
|
||||
var sub = '';
|
||||
if (arg in args) {
|
||||
sub = args[arg];
|
||||
} else if (arg in gL10nData) {
|
||||
sub = gL10nData[arg][gTextProp];
|
||||
} else {
|
||||
consoleWarn('could not find argument {{' + arg + '}}');
|
||||
return str;
|
||||
}
|
||||
|
||||
str = str.substring(0, match.index) + sub +
|
||||
str.substr(match.index + match[0].length);
|
||||
match = reArgs.exec(str);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// translate an HTML element
|
||||
function translateElement(element) {
|
||||
var l10n = getL10nAttributes(element);
|
||||
if (!l10n.id)
|
||||
return;
|
||||
|
||||
// get the related l10n object
|
||||
var data = getL10nData(l10n.id, l10n.args);
|
||||
if (!data) {
|
||||
consoleWarn('#' + l10n.id + ' missing for [' + gLanguage + ']');
|
||||
return;
|
||||
}
|
||||
|
||||
// translate element (TODO: security checks?)
|
||||
// for the node content, replace the content of the first child textNode
|
||||
// and clear other child textNodes
|
||||
if (data[gTextProp]) { // XXX
|
||||
if (element.children.length === 0) {
|
||||
element[gTextProp] = data[gTextProp];
|
||||
} else {
|
||||
var children = element.childNodes,
|
||||
found = false;
|
||||
for (var i = 0, l = children.length; i < l; i++) {
|
||||
if (children[i].nodeType === 3 &&
|
||||
/\S/.test(children[i].textContent)) { // XXX
|
||||
// using nodeValue seems cross-browser
|
||||
if (found) {
|
||||
children[i].nodeValue = '';
|
||||
} else {
|
||||
children[i].nodeValue = data[gTextProp];
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
consoleWarn('unexpected error, could not translate element content');
|
||||
}
|
||||
}
|
||||
delete data[gTextProp];
|
||||
}
|
||||
|
||||
for (var k in data) {
|
||||
element[k] = data[k];
|
||||
}
|
||||
}
|
||||
|
||||
// translate an HTML subtree
|
||||
function translateFragment(element) {
|
||||
element = element || document.documentElement;
|
||||
|
||||
// check all translatable children (= w/ a `data-l10n-id' attribute)
|
||||
var children = getTranslatableChildren(element);
|
||||
var elementCount = children.length;
|
||||
for (var i = 0; i < elementCount; i++) {
|
||||
translateElement(children[i]);
|
||||
}
|
||||
|
||||
// translate element itself if necessary
|
||||
translateElement(element);
|
||||
}
|
||||
|
||||
// cross-browser API (sorry, oldIE doesn't support getters & setters)
|
||||
return {
|
||||
// get a localized string
|
||||
get: function(key, args, fallback) {
|
||||
var data = getL10nData(key, args, {textContent: fallback});
|
||||
if (data) { // XXX double-check this
|
||||
return 'textContent' in data ? data.textContent : '';
|
||||
}
|
||||
return '{{' + key + '}}';
|
||||
},
|
||||
|
||||
// debug
|
||||
getData: function() { return gL10nData; },
|
||||
getText: function() { return gTextData; },
|
||||
|
||||
// get|set the document language
|
||||
getLanguage: function() { return gLanguage; },
|
||||
setLanguage: function(lang) { loadLocale(lang, translateFragment); },
|
||||
|
||||
// get the direction (ltr|rtl) of the current language
|
||||
getDirection: function() {
|
||||
// http://www.w3.org/International/questions/qa-scripts
|
||||
// Arabic, Hebrew, Farsi, Pashto, Urdu
|
||||
var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];
|
||||
return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr';
|
||||
},
|
||||
|
||||
// translate an element or document fragment
|
||||
translate: translateFragment,
|
||||
|
||||
// this can be used to prevent race conditions
|
||||
getReadyState: function() { return gReadyState; }
|
||||
};
|
||||
|
||||
}) (window, document);
|
||||
111
common/static/js/vendor/pdfjs/locale/ar/viewer.properties
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
# Copyright 2012 Mozilla Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Main toolbar buttons (tooltips and alt text for images)
|
||||
previous.title=الصفحة السابقة
|
||||
previous_label=السابق
|
||||
next.title=الصفحة التاليه
|
||||
next_label=التالي
|
||||
|
||||
# LOCALIZATION NOTE (page_label, page_of):
|
||||
# These strings are concatenated to form the "Page: X of Y" string.
|
||||
# Do not translate "{{pageCount}}", it will be substituted with a number
|
||||
# representing the total number of pages.
|
||||
page_label=الصفحة:
|
||||
page_of=من {{pageCount}}
|
||||
|
||||
zoom_out.title=تصغير
|
||||
zoom_out_label=تصغير
|
||||
zoom_in.title=تكبير
|
||||
zoom_in_label=تكبير
|
||||
zoom.title=التكبير
|
||||
print.title=طباعة
|
||||
print_label=طباعة
|
||||
fullscreen.title=ملء الشاشة
|
||||
fullscreen_label=ملء الشاشة
|
||||
open_file.title=فتح الملف
|
||||
open_file_label=فتح
|
||||
download.title=تحميل
|
||||
download_label=تحميل
|
||||
bookmark.title=المشهد الحالي (نسخ أو فتح في نافذة جديدة)
|
||||
bookmark_label=المشهد الحالي
|
||||
|
||||
# Tooltips and alt text for side panel toolbar buttons
|
||||
# (the _label strings are alt text for the buttons, the .title strings are
|
||||
# tooltips)
|
||||
toggle_slider.title=تبديل الزلاق
|
||||
toggle_slider_label=تبديل الزلاق
|
||||
outline.title=إظهار ملخص المستند
|
||||
outline_label=ملخص المستند
|
||||
thumbs.title=إظهار الصور المصغرة
|
||||
thumbs_label=الصور المصغرة
|
||||
findbar.title=البحث في المستند
|
||||
findbar_label=بحث
|
||||
|
||||
# Document outline messages
|
||||
no_outline=لا يوجد ملخص
|
||||
|
||||
# Thumbnails panel item (tooltip and alt text for images)
|
||||
# LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_title=الصفحة {{page}}
|
||||
# LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_canvas=صورة مصغرة من الصفحة {{page}}
|
||||
|
||||
# Context menu
|
||||
page_rotate_cw.label=تدوير مع عقارب الساعة
|
||||
page_rotate_ccw.label=تدوير عكس عقارب الساعة
|
||||
|
||||
# Find panel button title and messages
|
||||
find=بحث
|
||||
find_terms_not_found=(لا يوجد)
|
||||
|
||||
# Error panel labels
|
||||
error_more_info=مزيد من المعلومات
|
||||
error_less_info=معلومات أقل
|
||||
error_close=إغلاق
|
||||
# LOCALIZATION NOTE (error_build): "{{build}}" will be replaced by the PDF.JS
|
||||
# build ID.
|
||||
error_build=بناء PDF.JS: {{build}}
|
||||
# LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an
|
||||
# english string describing the error.
|
||||
error_message=رسالة: {{message}}
|
||||
# LOCALIZATION NOTE (error_stack): "{{stack}}" will be replaced with a stack
|
||||
# trace.
|
||||
error_stack=المكدس: {{stack}}
|
||||
# LOCALIZATION NOTE (error_file): "{{file}}" will be replaced with a filename
|
||||
error_file=الملف: {{file}}
|
||||
# LOCALIZATION NOTE (error_line): "{{line}}" will be replaced with a line number
|
||||
error_line=السطر: {{line}}
|
||||
rendering_error=حدث خطأ اثناء رسم الصفحة.
|
||||
|
||||
# Predefined zoom values
|
||||
page_scale_width=عرض الصفحة
|
||||
page_scale_fit=تناسب الصفحة
|
||||
page_scale_auto=تقريب تلقائي
|
||||
page_scale_actual=الحجم الحقيقي
|
||||
|
||||
# Loading indicator messages
|
||||
loading_error_indicator=خطأ
|
||||
loading_error=حدث خطأ أثناء تحميل وثيقه الـPDF
|
||||
|
||||
# LOCALIZATION NOTE (text_annotation_type): This is used as a tooltip.
|
||||
# "{{type}}" will be replaced with an annotation type from a list defined in
|
||||
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
|
||||
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
|
||||
text_annotation_type=[ملاحظة {{type}}]
|
||||
request_password=الـPDF محمي بكلمة مرور:
|
||||
|
||||
printing_not_supported=تحذير: الطباعة ليست مدعومة كليًا في هذا المتصفح.
|
||||
127
common/static/js/vendor/pdfjs/locale/ca/viewer.properties
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
# Copyright 2012 Mozilla Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Main toolbar buttons (tooltips and alt text for images)
|
||||
previous.title=Pàgina anterior
|
||||
previous_label=Anterior
|
||||
next.title=Pàgina següent
|
||||
next_label=Següent
|
||||
|
||||
# LOCALIZATION NOTE (page_label, page_of):
|
||||
# These strings are concatenated to form the "Page: X of Y" string.
|
||||
# Do not translate "{{pageCount}}", it will be substituted with a number
|
||||
# representing the total number of pages.
|
||||
page_label=Pàgina:
|
||||
page_of=de {{pageCount}}
|
||||
|
||||
zoom_out.title=Reduir
|
||||
zoom_out_label=Reduir
|
||||
zoom_in.title=Ampliar
|
||||
zoom_in_label=Ampliar
|
||||
zoom.title=Ampliació
|
||||
print.title=Imprimir
|
||||
print_label=Imprimir
|
||||
fullscreen.title=Pantalla completa
|
||||
fullscreen_label=Pantalla completa
|
||||
presentation_mode.title=Canviar a mode de Presentació
|
||||
presentation_mode_label=Mode de Presentació
|
||||
open_file.title=Obrir arxiu
|
||||
open_file_label=Obrir
|
||||
download.title=Descarregar
|
||||
download_label=Descarregar
|
||||
bookmark.title=Vista actual (copiï o obri en una finestra nova)
|
||||
bookmark_label=Vista actual
|
||||
|
||||
# Tooltips and alt text for side panel toolbar buttons
|
||||
# (the _label strings are alt text for the buttons, the .title strings are
|
||||
# tooltips)
|
||||
toggle_slider.title=Alternar lliscador
|
||||
toggle_slider_label=Alternar lliscador
|
||||
outline.title=Mostrar esquema del document
|
||||
outline_label=Esquema del document
|
||||
thumbs.title=Mostrar miniatures
|
||||
thumbs_label=Miniatures
|
||||
findbar.title=Cercar en el document
|
||||
findbar_label=Cercar
|
||||
|
||||
# Document outline messages
|
||||
no_outline=No hi ha cap esquema disponible
|
||||
|
||||
# Thumbnails panel item (tooltip and alt text for images)
|
||||
# LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_title=Pàgina {{page}}
|
||||
# LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_canvas=Miniatura de la pàgina {{page}}
|
||||
|
||||
# Find panel button title and messages
|
||||
find=Cercar
|
||||
find_terms_not_found=(No trobat)
|
||||
# Context menu
|
||||
first_page.label=Primera pàgina
|
||||
last_page.label=Darrera pàgina
|
||||
page_rotate_cw.label=Rotar sentit horari
|
||||
page_rotate_ccw.label=Rotar sentit anti-horari
|
||||
|
||||
# Find panel button title and messages
|
||||
find_label=Cerca:
|
||||
find_previous.title=Trobar ocurrència anterior
|
||||
find_previous_label=Previ
|
||||
find_next.title=Trobar ocurrència posterior
|
||||
find_next_label=Següent
|
||||
find_highlight=Contrastar tot
|
||||
find_match_case_label=Majúscules i minúscules
|
||||
find_wrapped_to_bottom=Part superior assolida, continu a la part inferior
|
||||
find_wrapped_to_top=Final de pàgina finalitzada, continu a la part superior
|
||||
find_not_found=Frase no trobada
|
||||
|
||||
# Error panel labels
|
||||
error_more_info=Més informació
|
||||
error_less_info=Menys informació
|
||||
error_close=Tancar
|
||||
# LOCALIZATION NOTE (error_build): "{{build}}" will be replaced by the PDF.JS
|
||||
# build ID.
|
||||
error_build=Compilació de PDF.JS: {{build}}
|
||||
# LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an
|
||||
# english string describing the error.
|
||||
error_message=Missatge: {{message}}
|
||||
# LOCALIZATION NOTE (error_stack): "{{stack}}" will be replaced with a stack
|
||||
# trace.
|
||||
error_stack=Pila: {{stack}}
|
||||
# LOCALIZATION NOTE (error_file): "{{file}}" will be replaced with a filename
|
||||
error_file=Arxiu: {{file}}
|
||||
# LOCALIZATION NOTE (error_line): "{{line}}" will be replaced with a line number
|
||||
error_line=Línia: {{line}}
|
||||
rendering_error=Ha ocurregut un error mentre es renderitzava la pàgina.
|
||||
|
||||
# Predefined zoom values
|
||||
page_scale_width=Ample de pàgina
|
||||
page_scale_fit=Ajustar a la pàgina
|
||||
page_scale_auto=Ampliació automàtica
|
||||
page_scale_actual=Tamany real
|
||||
|
||||
# Loading indicator messages
|
||||
loading_error_indicator=Error
|
||||
loading_error=Ha ocorregut un error mentres es carregava el PDF.
|
||||
invalid_file_error=Invàlid o fitxer PDF corrupte.
|
||||
|
||||
# LOCALIZATION NOTE (text_annotation_type): This is used as a tooltip.
|
||||
# "{{type}}" will be replaced with an annotation type from a list defined in
|
||||
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
|
||||
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
|
||||
text_annotation_type=[Anotació {{type}}]
|
||||
request_password=El PDF està protegit amb una contrasenya:
|
||||
|
||||
printing_not_supported=Avís: La impressió no és compatible totalment en aquest navegador.
|
||||
59
common/static/js/vendor/pdfjs/locale/cs/viewer.properties
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
# Copyright 2012 Mozilla Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
bookmark.title=Aktuální zobrazení(zkopírovat nebo otevřít v novém okně)
|
||||
previous.title=Předchozí stránka
|
||||
next.title=Další stránka
|
||||
print.title=Tisk
|
||||
download.title=Stáhnout
|
||||
zoom_out.title=Zmenšit
|
||||
zoom_in.title=Zvětšit
|
||||
error_more_info=Více informací
|
||||
error_less_info=Méně informací
|
||||
error_close=Zavřít
|
||||
error_build=PDF.JS Build: {{build}}
|
||||
error_message=Zpráva:{{message}}
|
||||
error_stack=Stack:{{stack}}
|
||||
error_file=Soubor:{{file}}
|
||||
error_line=Řádek:{{line}}
|
||||
page_scale_width=Šířka stránky
|
||||
page_scale_fit=Stránka
|
||||
page_scale_auto=Automatické přibližení
|
||||
page_scale_actual=Skutečná velikost
|
||||
toggle_slider.title=Přepnout posuvník
|
||||
thumbs.title=Zobrazit náhledy
|
||||
outline.title=Zobrazit osnovu dokumentu
|
||||
loading=Načítám... {{percent}}%
|
||||
loading_error_indicator=Chyba
|
||||
loading_error=Došlo k chybě při načítání PDF.
|
||||
rendering_error=Došlo k chybě při vykreslování stránky.
|
||||
page_label=Stránka:
|
||||
page_of=z{{pageCount}}
|
||||
no_outline=Žádné osnovy k dispozici
|
||||
open_file.title=Otevřít soubor
|
||||
text_annotation_type=[{{type}}Anotace]
|
||||
toggle_slider_label=Přepnout posuvník
|
||||
thumbs_label=Náhledy
|
||||
outline_label=Přehled dokumentu
|
||||
bookmark_label=Aktuální zobrazení
|
||||
previous_label=Předchozí
|
||||
next_label=Další
|
||||
print_label=Tisk
|
||||
download_label=Stáhnout
|
||||
zoom_out_label=Zmenšit
|
||||
zoom_in_label=Přiblížit
|
||||
zoom.title=Zvětšit
|
||||
thumb_page_title=Stránka{{page}}
|
||||
thumb_page_canvas=Náhled stránky {{page}}
|
||||
request_password=PDF je chráněn heslem:
|
||||
107
common/static/js/vendor/pdfjs/locale/da/viewer.properties
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
# Copyright 2012 Mozilla Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Værktøjslinje knapper (tooltups og billedtekster)
|
||||
previous.title=Forrige
|
||||
previous_label=Forrige
|
||||
next.title=Næste
|
||||
next_label=Næste
|
||||
|
||||
# Oversættelsesnote:
|
||||
# Disse tekststrenge bliver sammensat i formen "Side: X af Y"
|
||||
# Oversæt ikke "{{pageCount}}", det er en variabel og vil blive erstattet
|
||||
# med det egentlig antal sider i PDF filen
|
||||
page_label=Side:
|
||||
page_of=af {{pageCount}}
|
||||
|
||||
zoom_out.title=Zoom ud
|
||||
zoom_out_label=Zoom ud
|
||||
zoom_in.title=Zoom ind
|
||||
zoom_in_label=Zoom ind
|
||||
zoom.title=Zoom
|
||||
print_label=Udskriv
|
||||
print.title=Udskriv
|
||||
fullscreen.title=Fuldskærm
|
||||
fullscreen_label=Fuldskærm
|
||||
open_file.title=Åbn fil
|
||||
open_file_label=Åbn
|
||||
download.title=Hent
|
||||
download_label=Hent
|
||||
bookmark.title=Aktuel visning (kopier eller åbn i et nyt vindue)
|
||||
bookmark_label=Aktuel visning
|
||||
|
||||
# Tooltips of alternativ billedtekst til sidepanelet
|
||||
# (_label strengene er den alternative billedtekst, mens .title
|
||||
# strengene er tooltips
|
||||
toggle_slider.title=Skift slider
|
||||
toggle_slider_label=Skift slider
|
||||
outline.title=Vis dokumentoversigt
|
||||
outline_label=Dokumentoversigt
|
||||
thumbs.title=Vis thumbnails
|
||||
thumbs_label=Thumbnails
|
||||
findbar.title=Søg i dokumentet
|
||||
findbar_label=Søg
|
||||
|
||||
# Dokumentoversigtsbeskeder
|
||||
no_outline=Ingen dokumentoversigt tilgængelig
|
||||
|
||||
# Thumbnails panelet (tooltips og alt. billedtekst)
|
||||
# Oversættelsesnote: "{{page}}" vil blive erstattet af det
|
||||
# egentlige sidetal
|
||||
thumb_page_title=Side {{page}}
|
||||
# Oversættelsesnote: "{{page}}" vil blive erstattet af det
|
||||
# egentlige sidetal
|
||||
thumb_page_canvas=Thumbnail af side {{page}}
|
||||
|
||||
# Søgepanelet
|
||||
find=Søg
|
||||
find_terms_not_found=(Ikke fundet)
|
||||
|
||||
# Fejlpanel
|
||||
error_more_info=Mere information
|
||||
error_less_info=Mindre information
|
||||
error_close=Luk
|
||||
# Oversættelsesnote: "{{build}}" vil blive erstattet af PDF.JS build nummer
|
||||
#
|
||||
error_build=PDF.JS Build: {{build}}
|
||||
# Oversættelsesnote: "{{message}}" vil blive erstattet af en (engelsk) fejlbesked
|
||||
#
|
||||
error_message=Besked: {{message}}
|
||||
# Oversættelsesnote: "{{stack}}" vil blive erstattet af et stack trace
|
||||
#
|
||||
error_stack=Stak: {{stack}}
|
||||
# Oversættelsesnote: "{{file}}" vil blive erstattet af et filnavn
|
||||
error_file=Fil: {{file}}
|
||||
# Oversættelsesnote: "{{line}}" vil blive erstattet af et linjetal
|
||||
error_line=Linje: {{line}}
|
||||
rendering_error=Der skete en fejl under gengivelsen af PDF-filen
|
||||
|
||||
# Prædefinerede zoom værdier
|
||||
page_scale_width=Sidebredde
|
||||
page_scale_fit=Helside
|
||||
page_scale_auto=Automatisk zoom
|
||||
page_scale_actual=Faktisk størrelse
|
||||
|
||||
# Indlæsningsindikator (load ikon)
|
||||
loading_error_indicator=Fejl
|
||||
loading_error=Der skete en fejl under indlæsningen af PDF-filen
|
||||
|
||||
# Oversættelsesnote: Dette vil blive brugt som et tooltip
|
||||
# "{{type}}" vil blive ersattet af en kommentar type fra en liste
|
||||
# defineret i PDF specifikationen (32000-1:2008 Table 169 – Annotation types).
|
||||
# Nogle almindelige typer er f.eks.: "Check", "Text", "Comment" og "Note"
|
||||
text_annotation_type=[{{type}} Kommentar]
|
||||
request_password=PDF filen er beskyttet med et kodeord:
|
||||
|
||||
printing_not_supported=Advarsel: Denne browser er ikke fuldt understøttet ved udskrift
|
||||
123
common/static/js/vendor/pdfjs/locale/de/viewer.properties
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
# Copyright 2012 Mozilla Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Main toolbar buttons (tooltips and alt text for images)
|
||||
previous.title=Eine Seite zurück
|
||||
previous_label=Zurück
|
||||
next.title=Eine Seite vor
|
||||
next_label=Vor
|
||||
|
||||
# LOCALIZATION NOTE (page_label, page_of):
|
||||
# These strings are concatenated to form the "Page: X of Y" string.
|
||||
# Do not translate "{{pageCount}}", it will be substituted with a number
|
||||
# representing the total number of pages.
|
||||
page_label=Seite:
|
||||
page_of=von {{pageCount}}
|
||||
|
||||
zoom_out.title=Verkleinern
|
||||
zoom_out_label=Verkleinern
|
||||
zoom_in.title=Vergrößern
|
||||
zoom_in_label=Vergrößern
|
||||
zoom.title=Zoom
|
||||
print.title=Drucken
|
||||
print_label=Drucken
|
||||
presentation_mode.title=Zum Präsentationsmodus wechseln
|
||||
presentation_mode_label=Bildschirmpräsentation
|
||||
open_file.title=Datei öffnen
|
||||
open_file_label=Öffnen
|
||||
download.title=Herunterladen
|
||||
download_label=Herunterladen
|
||||
bookmark.title=Aktuelle Ansicht (Kopieren oder in einem neuen Fenster öffnen)
|
||||
bookmark_label=Aktuelle Ansicht
|
||||
|
||||
# Tooltips and alt text for side panel toolbar buttons
|
||||
# (the _label strings are alt text for the buttons, the .title strings are
|
||||
# tooltips)
|
||||
toggle_slider.title=Seitenleiste anzeigen
|
||||
toggle_slider_label=Seitenleiste
|
||||
outline.title=Zeige Inhaltsverzeichnis
|
||||
outline_label=Inhaltsverzeichnis
|
||||
thumbs.title=Zeige Vorschaubilder
|
||||
thumbs_label=Vorschaubilder
|
||||
findbar.title=Im Dokument suchen
|
||||
findbar_label=Suchen
|
||||
|
||||
# Document outline messages
|
||||
no_outline=Kein Inhaltsverzeichnis verfügbar
|
||||
|
||||
# Thumbnails panel item (tooltip and alt text for images)
|
||||
# LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_title=Seite {{page}}
|
||||
# LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_canvas=Vorschau von Seite {{page}}
|
||||
|
||||
# Context menu
|
||||
first_page.label=Erste Seite
|
||||
last_page.label=Letzte Seite
|
||||
page_rotate_cw.label=Im Uhrzeigersinn drehen
|
||||
page_rotate_ccw.label=Entgegen dem Uhrzeigersinn drehen
|
||||
|
||||
# Find panel button title and messages
|
||||
find_label=Suchen:
|
||||
find_previous.title=Das vorherige Auftreten des Ausdrucks suchen
|
||||
find_previous_label=Aufwärts
|
||||
find_next.title=Das nächste Auftreten des Ausdrucks suchen
|
||||
find_next_label=Abwärts
|
||||
find_highlight=Hervorheben
|
||||
find_match_case_label=Groß-/Kleinschreibung
|
||||
find_reached_top=Der Anfang des Dokuments wurde erreicht, Suche am Ende des Dokuments fortgesetzt
|
||||
find_reached_bottom=Das Ende des Dokuments wurde erreicht, Suche am Anfang des Dokuments fortgesetzt
|
||||
find_not_found=Ausdruck nicht gefunden
|
||||
|
||||
# Error panel labels
|
||||
error_more_info=Mehr Info
|
||||
error_less_info=Weniger Info
|
||||
error_close=Schließen
|
||||
# LOCALIZATION NOTE (error_build): "{{build}}" will be replaced by the PDF.JS
|
||||
# build ID.
|
||||
error_build=PDF.JS Build: {{build}}
|
||||
# LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an
|
||||
# english string describing the error.
|
||||
error_message=Nachricht: {{message}}
|
||||
# LOCALIZATION NOTE (error_stack): "{{stack}}" will be replaced with a stack
|
||||
# trace.
|
||||
error_stack=Stack: {{stack}}
|
||||
# LOCALIZATION NOTE (error_file): "{{file}}" will be replaced with a filename
|
||||
error_file=Datei: {{file}}
|
||||
# LOCALIZATION NOTE (error_line): "{{line}}" will be replaced with a line number
|
||||
error_line=Zeile: {{line}}
|
||||
rendering_error=Das PDF konnte nicht angezeigt werden.
|
||||
|
||||
# Predefined zoom values
|
||||
page_scale_width=Seitenbreite
|
||||
page_scale_fit=Ganze Seite
|
||||
page_scale_auto=Automatisch
|
||||
page_scale_actual=Originalgröße
|
||||
|
||||
# Loading indicator messages
|
||||
loading_error_indicator=Fehler
|
||||
loading_error=Das PDF konnte nicht geladen werden.
|
||||
invalid_file_error=Ungültige oder beschädigte PDF-Datei.
|
||||
|
||||
# LOCALIZATION NOTE (text_annotation_type): This is used as a tooltip.
|
||||
# "{{type}}" will be replaced with an annotation type from a list defined in
|
||||
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
|
||||
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
|
||||
text_annotation_type=[{{type}} Annotation]
|
||||
request_password=Das PDF ist passwortgeschützt:
|
||||
|
||||
printing_not_supported=Warnung: Drucken wird durch diesen Browser nicht vollständig unterstützt.
|
||||
web_fonts_disabled=Webfonts sind deaktiviert: Eingebundene PDF-Schriftarten können nicht verwendet werden.
|
||||
124
common/static/js/vendor/pdfjs/locale/en-US/viewer.properties
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
# Copyright 2012 Mozilla Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Main toolbar buttons (tooltips and alt text for images)
|
||||
previous.title=Previous Page
|
||||
previous_label=Previous
|
||||
next.title=Next Page
|
||||
next_label=Next
|
||||
|
||||
# LOCALIZATION NOTE (page_label, page_of):
|
||||
# These strings are concatenated to form the "Page: X of Y" string.
|
||||
# Do not translate "{{pageCount}}", it will be substituted with a number
|
||||
# representing the total number of pages.
|
||||
page_label=Page:
|
||||
page_of=of {{pageCount}}
|
||||
|
||||
zoom_out.title=Zoom Out
|
||||
zoom_out_label=Zoom Out
|
||||
zoom_in.title=Zoom In
|
||||
zoom_in_label=Zoom In
|
||||
zoom.title=Zoom
|
||||
print.title=Print
|
||||
print_label=Print
|
||||
presentation_mode.title=Switch to Presentation Mode
|
||||
presentation_mode_label=Presentation Mode
|
||||
open_file.title=Open File
|
||||
open_file_label=Open
|
||||
download.title=Download
|
||||
download_label=Download
|
||||
bookmark.title=Current view (copy or open in new window)
|
||||
bookmark_label=Current View
|
||||
|
||||
# Tooltips and alt text for side panel toolbar buttons
|
||||
# (the _label strings are alt text for the buttons, the .title strings are
|
||||
# tooltips)
|
||||
toggle_sidebar.title=Toggle Sidebar
|
||||
toggle_sidebar_label=Toggle Sidebar
|
||||
outline.title=Show Document Outline
|
||||
outline_label=Document Outline
|
||||
thumbs.title=Show Thumbnails
|
||||
thumbs_label=Thumbnails
|
||||
findbar.title=Find in Document
|
||||
findbar_label=Find
|
||||
|
||||
# Document outline messages
|
||||
no_outline=No Outline Available
|
||||
|
||||
# Thumbnails panel item (tooltip and alt text for images)
|
||||
# LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_title=Page {{page}}
|
||||
# LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_canvas=Thumbnail of Page {{page}}
|
||||
|
||||
# Context menu
|
||||
first_page.label=Go to First Page
|
||||
last_page.label=Go to Last Page
|
||||
page_rotate_cw.label=Rotate Clockwise
|
||||
page_rotate_ccw.label=Rotate Counterclockwise
|
||||
|
||||
# Find panel button title and messages
|
||||
find_label=Find:
|
||||
find_previous.title=Find the previous occurrence of the phrase
|
||||
find_previous_label=Previous
|
||||
find_next.title=Find the next occurrence of the phrase
|
||||
find_next_label=Next
|
||||
find_highlight=Highlight all
|
||||
find_match_case_label=Match case
|
||||
find_reached_top=Reached top of document, continued from bottom
|
||||
find_reached_bottom=Reached end of document, continued from top
|
||||
find_not_found=Phrase not found
|
||||
|
||||
# Error panel labels
|
||||
error_more_info=More Information
|
||||
error_less_info=Less Information
|
||||
error_close=Close
|
||||
# LOCALIZATION NOTE (error_version_info): "{{version}}" and "{{build}}" will be
|
||||
# replaced by the PDF.JS version and build ID.
|
||||
error_version_info=PDF.js v{{version}} (build: {{build}})
|
||||
# LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an
|
||||
# english string describing the error.
|
||||
error_message=Message: {{message}}
|
||||
# LOCALIZATION NOTE (error_stack): "{{stack}}" will be replaced with a stack
|
||||
# trace.
|
||||
error_stack=Stack: {{stack}}
|
||||
# LOCALIZATION NOTE (error_file): "{{file}}" will be replaced with a filename
|
||||
error_file=File: {{file}}
|
||||
# LOCALIZATION NOTE (error_line): "{{line}}" will be replaced with a line number
|
||||
error_line=Line: {{line}}
|
||||
rendering_error=An error occurred while rendering the page.
|
||||
|
||||
# Predefined zoom values
|
||||
page_scale_width=Page Width
|
||||
page_scale_fit=Page Fit
|
||||
page_scale_auto=Automatic Zoom
|
||||
page_scale_actual=Actual Size
|
||||
|
||||
# Loading indicator messages
|
||||
loading_error_indicator=Error
|
||||
loading_error=An error occurred while loading the PDF.
|
||||
invalid_file_error=Invalid or corrupted PDF file.
|
||||
missing_file_error=Missing PDF file.
|
||||
|
||||
# LOCALIZATION NOTE (text_annotation_type): This is used as a tooltip.
|
||||
# "{{type}}" will be replaced with an annotation type from a list defined in
|
||||
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
|
||||
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
|
||||
text_annotation_type=[{{type}} Annotation]
|
||||
request_password=PDF is protected by a password:
|
||||
|
||||
printing_not_supported=Warning: Printing is not fully supported by this browser.
|
||||
web_fonts_disabled=Web fonts are disabled: unable to use embedded PDF fonts.
|
||||
124
common/static/js/vendor/pdfjs/locale/es-MX/viewer.properties
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
# Copyright 2012 Mozilla Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Main toolbar buttons (tooltips and alt text for images)
|
||||
previous.title=Página anterior
|
||||
previous_label=Anterior
|
||||
next.title=Página siguiente
|
||||
next_label=Siguiente
|
||||
|
||||
# LOCALIZATION NOTE (page_label, page_of):
|
||||
# These strings are concatenated to form the "Page: X of Y" string.
|
||||
# Do not translate "{{pageCount}}", it will be substituted with a number
|
||||
# representing the total number of pages.
|
||||
page_label=Página:
|
||||
page_of=of {{pageCount}}
|
||||
|
||||
zoom_out.title=Reducir
|
||||
zoom_out_label=Reducir
|
||||
zoom_in.title=Aumentar
|
||||
zoom_in_label=Aumentar
|
||||
zoom.title=Tamaño
|
||||
print.title=Imprimir
|
||||
print_label=Imprimir
|
||||
presentation_mode.title=Cambiar al modo de presentación
|
||||
presentation_mode_label=Modo de presentación
|
||||
open_file.title=Abrir archivo
|
||||
open_file_label=Abrir
|
||||
download.title=Descargar
|
||||
download_label=Descargar
|
||||
bookmark.title=Vista actual (copiar o abrir en una nueva ventana)
|
||||
bookmark_label=Vista actual
|
||||
|
||||
# Tooltips and alt text for side panel toolbar buttons
|
||||
# (the _label strings are alt text for the buttons, the .title strings are
|
||||
# tooltips)
|
||||
toggle_sidebar.title=Activar barra lateral
|
||||
toggle_sidebar_label=Activar barra lateral
|
||||
outline.title=Mostrar el esquema del documento
|
||||
outline_label=Esquema del documento
|
||||
thumbs.title=Mostrar miniaturas
|
||||
thumbs_label=Miniaturas
|
||||
findbar.title=Buscar en el documento
|
||||
findbar_label=Buscar
|
||||
|
||||
# Document outline messages
|
||||
no_outline=No hay esquema disponible
|
||||
|
||||
# Thumbnails panel item (tooltip and alt text for images)
|
||||
# LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_title=Página {{page}}
|
||||
# LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_canvas=Miniatura o página {{page}}
|
||||
|
||||
# Context menu
|
||||
first_page.label=Ir a la primera página
|
||||
last_page.label=Ir a la última página
|
||||
page_rotate_cw.label=Girar hacia la derecha
|
||||
page_rotate_ccw.label=Girar hacia la izquierda
|
||||
|
||||
# Find panel button title and messages
|
||||
find_label=Buscar:
|
||||
find_previous.title=Ir a la anterior frase encontrada
|
||||
find_previous_label=Anterior
|
||||
find_next.title=Ir a la siguiente frase encontrada
|
||||
find_next_label=Siguiente
|
||||
find_highlight=Marcar todo
|
||||
find_match_case_label=Coincidir con mayúsculas y minúsculas
|
||||
find_reached_top=Inicio del documento, se continúa desde el final
|
||||
find_reached_bottom=Final del documento, se continúa desde el inicio
|
||||
find_not_found=No se encontró la frase
|
||||
|
||||
# Error panel labels
|
||||
error_more_info=Más información
|
||||
error_less_info=Menos información
|
||||
error_close=Cerrar
|
||||
# LOCALIZATION NOTE (error_version_info): "{{version}}" and "{{build}}" will be
|
||||
# replaced by the PDF.JS version and build ID.
|
||||
error_version_info=PDF.js v{{version}} (compilación: {{build}})
|
||||
# LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an
|
||||
# english string describing the error.
|
||||
error_message=Mensaje: {{message}}
|
||||
# LOCALIZATION NOTE (error_stack): "{{stack}}" will be replaced with a stack
|
||||
# trace.
|
||||
error_stack=Pila: {{stack}}
|
||||
# LOCALIZATION NOTE (error_file): "{{file}}" will be replaced with a filename
|
||||
error_file=Archivo: {{file}}
|
||||
# LOCALIZATION NOTE (error_line): "{{line}}" will be replaced with a line number
|
||||
error_line=Línea: {{line}}
|
||||
rendering_error=Ocurrió un error al interpretar la página.
|
||||
|
||||
# Predefined zoom values
|
||||
page_scale_width=Ancho de página
|
||||
page_scale_fit=Ajustar a la página
|
||||
page_scale_auto=Ampliación automática
|
||||
page_scale_actual=Tamaño real
|
||||
|
||||
# Loading indicator messages
|
||||
loading_error_indicator=Error
|
||||
loading_error=Ocurrió un error al cargar el PDF.
|
||||
invalid_file_error=Archivo PDF inválido o corrupto.
|
||||
missing_file_error=Archivo PDF faltante.
|
||||
|
||||
# LOCALIZATION NOTE (text_annotation_type): This is used as a tooltip.
|
||||
# "{{type}}" will be replaced with an annotation type from a list defined in
|
||||
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
|
||||
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
|
||||
text_annotation_type=[Anotación {{type}}]
|
||||
request_password=El archivo PDF está protegido por contraseña:
|
||||
|
||||
printing_not_supported=Advertencia: la impresión no está completamente soportada en este navegador.
|
||||
web_fonts_disabled=Las tipografías web están deshabilitadas: no es posible utilizar tipografías PDF incrustadas.
|
||||
107
common/static/js/vendor/pdfjs/locale/es/viewer.properties
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
# Copyright 2012 Mozilla Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Main toolbar buttons (tooltips and alt text for images)
|
||||
previous.title=Página anterior
|
||||
previous_label=Anterior
|
||||
next.title=Página siguiente
|
||||
next_label=Siguiente
|
||||
|
||||
# LOCALIZATION NOTE (page_label, page_of):
|
||||
# These strings are concatenated to form the "Page: X of Y" string.
|
||||
# Do not translate "{{pageCount}}", it will be substituted with a number
|
||||
# representing the total number of pages.
|
||||
page_label=Página:
|
||||
page_of=de {{pageCount}}
|
||||
|
||||
zoom_out.title=Reducir
|
||||
zoom_out_label=Reducir
|
||||
zoom_in.title=Ampliar
|
||||
zoom_in_label=Ampliar
|
||||
zoom.title=Ampliación
|
||||
print.title=Imprimir
|
||||
print_label=Imprimir
|
||||
fullscreen.title=Pantalla completa
|
||||
fullscreen_label=Pantalla completa
|
||||
open_file.title=Abrir archivo
|
||||
open_file_label=Abrir
|
||||
download.title=Descargar
|
||||
download_label=Descargar
|
||||
bookmark.title=Vista actual (copie o abra en una ventana nueva)
|
||||
bookmark_label=Vista actual
|
||||
|
||||
# Tooltips and alt text for side panel toolbar buttons
|
||||
# (the _label strings are alt text for the buttons, the .title strings are
|
||||
# tooltips)
|
||||
toggle_slider.title=Alternar deslizador
|
||||
toggle_slider_label=Alternar deslizador
|
||||
outline.title=Mostrar esquema del documento
|
||||
outline_label=Esquema del documento
|
||||
thumbs.title=Mostrar miniaturas
|
||||
thumbs_label=Miniaturas
|
||||
findbar.title=Buscar en el documento
|
||||
findbar_label=Buscar
|
||||
|
||||
# Document outline messages
|
||||
no_outline=No hay un esquema disponible
|
||||
|
||||
# Thumbnails panel item (tooltip and alt text for images)
|
||||
# LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_title=Página {{page}}
|
||||
# LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_canvas=Miniatura de la página {{page}}
|
||||
|
||||
# Find panel button title and messages
|
||||
find=Buscar
|
||||
find_terms_not_found=(No encontrado)
|
||||
|
||||
# Error panel labels
|
||||
error_more_info=Más información
|
||||
error_less_info=Menos información
|
||||
error_close=Cerrar
|
||||
# LOCALIZATION NOTE (error_build): "{{build}}" will be replaced by the PDF.JS
|
||||
# build ID.
|
||||
error_build=Compilación de PDF.JS: {{build}}
|
||||
# LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an
|
||||
# english string describing the error.
|
||||
error_message=Mensaje: {{message}}
|
||||
# LOCALIZATION NOTE (error_stack): "{{stack}}" will be replaced with a stack
|
||||
# trace.
|
||||
error_stack=Pila: {{stack}}
|
||||
# LOCALIZATION NOTE (error_file): "{{file}}" will be replaced with a filename
|
||||
error_file=Archivo: {{file}}
|
||||
# LOCALIZATION NOTE (error_line): "{{line}}" will be replaced with a line number
|
||||
error_line=Línea: {{line}}
|
||||
rendering_error=Ocurrió un error mientras se renderizaba la página.
|
||||
|
||||
# Predefined zoom values
|
||||
page_scale_width=Anchura de página
|
||||
page_scale_fit=Ajustar a la página
|
||||
page_scale_auto=Ampliación automática
|
||||
page_scale_actual=Tamaño real
|
||||
|
||||
# Loading indicator messages
|
||||
loading_error_indicator=Error
|
||||
loading_error=Ocurrió un error mientras se cargaba el PDF.
|
||||
|
||||
# LOCALIZATION NOTE (text_annotation_type): This is used as a tooltip.
|
||||
# "{{type}}" will be replaced with an annotation type from a list defined in
|
||||
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
|
||||
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
|
||||
text_annotation_type=[Anotación {{type}}]
|
||||
request_password=El PDF está protegido con una contraseña:
|
||||
|
||||
printing_not_supported=Aviso: La impresión no es compatible totalmente con este navegador.
|
||||
108
common/static/js/vendor/pdfjs/locale/fi/viewer.properties
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
# Copyright 2012 Mozilla Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Main toolbar buttons (tooltips and alt text for images)
|
||||
previous.title=Edellinen sivu
|
||||
previous_label=Edellinen
|
||||
next.title=Seuraava sivu
|
||||
next_label=Seuraava
|
||||
|
||||
# LOCALIZATION NOTE (page_label, page_of):
|
||||
# These strings are concatenated to form the "Page: X of Y" string.
|
||||
# Do not translate "{{pageCount}}", it will be substituted with a number
|
||||
# representing the total number of pages.
|
||||
page_label=Sivu:
|
||||
page_of=/ {{pageCount}}
|
||||
|
||||
zoom_out.title=Suurenna
|
||||
zoom_out_label=Suurenna
|
||||
zoom_in.title=Pienennä
|
||||
zoom_in_label=Pienennä
|
||||
zoom.title=Sivun suurennus
|
||||
print.title=Tulosta
|
||||
print_label=Tulosta
|
||||
fullscreen.title=Kokoruututila
|
||||
fullscreen_label=Kokoruututila
|
||||
open_file.title=Avaa tiedosto
|
||||
open_file_label=Avaa
|
||||
download.title=Lataa
|
||||
download_label=Lataa
|
||||
bookmark.title=Nykyinen näkymä (kopioi tai avaa uuteen ikkunaan)
|
||||
bookmark_label=Nykyinen näkymä
|
||||
|
||||
# Tooltips and alt text for side panel toolbar buttons
|
||||
# (the _label strings are alt text for the buttons, the .title strings are
|
||||
# tooltips)
|
||||
toggle_slider.title=Vaihda vieritysnäkymä
|
||||
toggle_slider_label=Vaihda vieritysnäkymä
|
||||
outline.title=Näytä asiakirjan jäsennys
|
||||
outline_label=Asiakirjan jäsennys
|
||||
thumbs.title=Näytä esikatselukuvat
|
||||
thumbs_label=Esikatselukuvat
|
||||
findbar.title=Etsi asiakirjasta
|
||||
findbar_label=Etsi
|
||||
|
||||
# Document outline messages
|
||||
no_outline=Jäsennystä ei ole tarjolla
|
||||
|
||||
# Thumbnails panel item (tooltip and alt text for images)
|
||||
# LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_title=Sivu {{page}}
|
||||
# LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
|
||||
# number.
|
||||
thumb_page_canvas=Sivun {{page}} esikatselukuva
|
||||
|
||||
# Find panel button title and messages
|
||||
find=Etsi
|
||||
find_terms_not_found=(Ei löytynyt)
|
||||
|
||||
# Error panel labels
|
||||
error_more_info=Enemmän tietoa
|
||||
error_less_info=Vähemmän tietoa
|
||||
error_close=Sulje
|
||||
# LOCALIZATION NOTE (error_build): "{{build}}" will be replaced by the PDF.JS
|
||||
# build ID.
|
||||
error_build=PDF.JS rakennus: {{build}}
|
||||
# LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an
|
||||
# english string describing the error.
|
||||
error_message=Viesti: {{message}}
|
||||
# LOCALIZATION NOTE (error_stack): "{{stack}}" will be replaced with a stack
|
||||
# trace.
|
||||
error_stack=Kutsupino: {{stack}}
|
||||
# LOCALIZATION NOTE (error_file): "{{file}}" will be replaced with a filename
|
||||
error_file=Tiedosto: {{file}}
|
||||
# LOCALIZATION NOTE (error_line): "{{line}}" will be replaced with a line number
|
||||
error_line=Rivi: {{line}}
|
||||
rendering_error=Virhe on tapahtunut sivua mallintaessa.
|
||||
|
||||
# Predefined zoom values
|
||||
page_scale_width=Sivun leveys
|
||||
page_scale_fit=Sivun sovitus
|
||||
page_scale_auto=Automaatinen sivun suurennus
|
||||
page_scale_actual=Todellinen koko
|
||||
|
||||
# Loading indicator messages
|
||||
loading_error_indicator=Virhe
|
||||
loading_error=Virhe on tapahtunut PDF:ää ladattaessa.
|
||||
|
||||
# LOCALIZATION NOTE (text_annotation_type): This is used as a tooltip.
|
||||
# "{{type}}" will be replaced with an annotation type from a list defined in
|
||||
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
|
||||
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
|
||||
text_annotation_type=[{{type}} Selite]
|
||||
request_password=PDF on salasanasuojattu:
|
||||
|
||||
printing_not_supported=Varoitus: Tämä selain ei täysin tue tulostusta.
|
||||
|
||||
71
common/static/js/vendor/pdfjs/locale/fr/viewer.properties
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
previous.title=Page précédente
|
||||
previous_label=Précédent
|
||||
next.title=Page suivante
|
||||
next_label=Suivant
|
||||
page_label=Page :
|
||||
page_of=sur {{pageCount}}
|
||||
zoom_out.title=Zoom arrière
|
||||
zoom_out_label=Zoom arrière
|
||||
zoom_in.title=Zoom avant
|
||||
zoom_in_label=Zoom avant
|
||||
zoom.title=Zoom
|
||||
print.title=Imprimer
|
||||
print_label=Imprimer
|
||||
presentation_mode.title=Basculer en mode présentation
|
||||
presentation_mode_label=Mode présentation
|
||||
open_file.title=Ouvrir le fichier
|
||||
open_file_label=Ouvrir
|
||||
download.title=Télécharger
|
||||
download_label=Télécharger
|
||||
bookmark.title=Affichage courant (copier ou ouvrir dans une nouvelle fenêtre)
|
||||
bookmark_label=Affichage actuel
|
||||
toggle_slider.title=Afficher/masquer le panneau latéral
|
||||
toggle_slider_label=Afficher/masquer le panneau latéral
|
||||
outline.title=Afficher les signets
|
||||
outline_label=Signets du document
|
||||
thumbs.title=Afficher les vignettes
|
||||
thumbs_label=Vignettes
|
||||
findbar.title=Rechercher dans le document
|
||||
findbar_label=Rechercher
|
||||
no_outline=Aucun signet disponible
|
||||
thumb_page_title=Page {{page}}
|
||||
thumb_page_canvas=Vignette de la page {{page}}
|
||||
first_page.label=Aller à la première page
|
||||
last_page.label=Aller à la dernière page
|
||||
page_rotate_cw.label=Rotation horaire
|
||||
page_rotate_ccw.label=Rotation anti-horaire
|
||||
|
||||
# Find panel button title and messages
|
||||
find_label=Rechercher :
|
||||
find_previous.title=Trouver l'occurrence précédente de la phrase
|
||||
find_previous_label=Précédent
|
||||
find_next.title=Trouver la prochaine occurrence de la phrase
|
||||
find_next_label=Suivant
|
||||
find_highlight=Tout surligner
|
||||
find_match_case_label=Respecter la casse
|
||||
find_wrapped_to_bottom=Bas de la page atteint, poursuite depuis la fin
|
||||
find_wrapped_to_top=Bas de la page atteint, poursuite au début
|
||||
find_not_found=Phrase introuvable
|
||||
|
||||
error_more_info=Plus d'informations
|
||||
error_less_info=Moins d'informations
|
||||
error_close=Fermer
|
||||
error_build=Version de PDF.JS : {{build}}
|
||||
error_message=Message : {{message}}
|
||||
error_stack=Pile : {{stack}}
|
||||
error_file=Fichier : {{file}}
|
||||
error_line=Ligne : {{line}}
|
||||
rendering_error=Une erreur s'est produite lors de l'affichage de la page.
|
||||
page_scale_width=Pleine largeur
|
||||
page_scale_fit=Page entière
|
||||
page_scale_auto=Zoom automatique
|
||||
page_scale_actual=Taille réelle
|
||||
loading_error_indicator=Erreur
|
||||
loading_error=Une erreur s'est produite lors du chargement du fichier PDF.
|
||||
text_annotation_type=[Annotation {{type}}]
|
||||
request_password=Le PDF est protégé par un mot de passe :
|
||||
printing_not_supported=Attention : l'impression n'est pas totalement prise en charge par ce navigateur.
|
||||