Merge branch 'master' into feature/abarrett/annotatable_xmodule
1
.gitignore
vendored
@@ -27,4 +27,5 @@ lms/lib/comment_client/python
|
|||||||
nosetests.xml
|
nosetests.xml
|
||||||
cover_html/
|
cover_html/
|
||||||
.idea/
|
.idea/
|
||||||
|
.redcar/
|
||||||
chromedriver.log
|
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.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from path import path
|
from path import path
|
||||||
from tempfile import mkdtemp
|
from tempdir import mkdtemp_clean
|
||||||
import json
|
import json
|
||||||
from fs.osfs import OSFS
|
from fs.osfs import OSFS
|
||||||
import copy
|
import copy
|
||||||
@@ -194,7 +194,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
|||||||
import_from_xml(ms, 'common/test/data/', ['full'])
|
import_from_xml(ms, 'common/test/data/', ['full'])
|
||||||
location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
|
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)
|
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')
|
self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ContentStoreTest(ModuleStoreTestCase):
|
class ContentStoreTest(ModuleStoreTestCase):
|
||||||
"""
|
"""
|
||||||
Tests for the CMS ContentStore application.
|
Tests for the CMS ContentStore application.
|
||||||
@@ -421,6 +422,64 @@ class ContentStoreTest(ModuleStoreTestCase):
|
|||||||
self.assertIn('markdown', problem.metadata, "markdown is missing from metadata")
|
self.assertIn('markdown', problem.metadata, "markdown is missing from metadata")
|
||||||
self.assertNotIn('markdown', problem.editable_metadata_fields, "Markdown slipped into the editable metadata fields")
|
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):
|
class TemplateTestCase(ModuleStoreTestCase):
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.test.client import Client
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from path import path
|
from path import path
|
||||||
from tempfile import mkdtemp
|
|
||||||
import json
|
import json
|
||||||
from fs.osfs import OSFS
|
from fs.osfs import OSFS
|
||||||
import copy
|
import copy
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import copy
|
import copy
|
||||||
from time import time
|
from uuid import uuid4
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
@@ -20,13 +20,12 @@ class ModuleStoreTestCase(TestCase):
|
|||||||
def _pre_setup(self):
|
def _pre_setup(self):
|
||||||
super(ModuleStoreTestCase, self)._pre_setup()
|
super(ModuleStoreTestCase, self)._pre_setup()
|
||||||
|
|
||||||
# Use the current seconds since epoch to differentiate
|
# Use a uuid to differentiate
|
||||||
# the mongo collections on jenkins.
|
# the mongo collections on jenkins.
|
||||||
sec_since_epoch = '%s' % int(time() * 100)
|
|
||||||
self.orig_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
|
self.orig_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
|
||||||
self.test_MODULESTORE = self.orig_MODULESTORE
|
self.test_MODULESTORE = self.orig_MODULESTORE
|
||||||
self.test_MODULESTORE['default']['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' % sec_since_epoch
|
self.test_MODULESTORE['direct']['OPTIONS']['collection'] = 'modulestore_%s' % uuid4().hex
|
||||||
settings.MODULESTORE = self.test_MODULESTORE
|
settings.MODULESTORE = self.test_MODULESTORE
|
||||||
|
|
||||||
# Flush and initialize the module store
|
# Flush and initialize the module store
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ Longer TODO:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
|
||||||
import os.path
|
import os.path
|
||||||
import os
|
import os
|
||||||
import lms.envs.common
|
import lms.envs.common
|
||||||
@@ -59,7 +58,8 @@ sys.path.append(COMMON_ROOT / 'lib')
|
|||||||
|
|
||||||
############################# WEB CONFIGURATION #############################
|
############################# WEB CONFIGURATION #############################
|
||||||
# This is where we stick our compiled template files.
|
# 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 = {}
|
||||||
MAKO_TEMPLATES['main'] = [
|
MAKO_TEMPLATES['main'] = [
|
||||||
PROJECT_ROOT / 'templates',
|
PROJECT_ROOT / 'templates',
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
|
|||||||
save_videosource: function(newsource) {
|
save_videosource: function(newsource) {
|
||||||
// newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string
|
// 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
|
// 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
|
// TODO remove all whitespace w/in string
|
||||||
else {
|
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();
|
return this.videosourceSample();
|
||||||
|
|||||||
@@ -498,6 +498,7 @@ input.courseware-unit-search-input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.new-section {
|
&.new-section {
|
||||||
|
|
||||||
header {
|
header {
|
||||||
height: auto;
|
height: auto;
|
||||||
@include clearfix();
|
@include clearfix();
|
||||||
@@ -506,6 +507,15 @@ input.courseware-unit-search-input {
|
|||||||
.expand-collapse-icon {
|
.expand-collapse-icon {
|
||||||
visibility: hidden;
|
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.contrib.auth.models import User
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
|
|
||||||
from courseware import courses
|
from courseware import courses
|
||||||
from student.models import get_user_by_username_or_email
|
from student.models import get_user_by_username_or_email
|
||||||
@@ -64,7 +65,23 @@ def is_commentable_cohorted(course_id, commentable_id):
|
|||||||
ans))
|
ans))
|
||||||
return 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):
|
def get_cohort(user, course_id):
|
||||||
"""
|
"""
|
||||||
Given a django User and a course_id, return the user's cohort in that
|
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,
|
group_type=CourseUserGroup.COHORT,
|
||||||
users__id=user.id)
|
users__id=user.id)
|
||||||
except CourseUserGroup.DoesNotExist:
|
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
|
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):
|
def get_course_cohorts(course_id):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ class TestCohorts(django.test.TestCase):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def config_course_cohorts(course, discussions,
|
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
|
Given a course with no discussion set up, add the discussions and set
|
||||||
the cohort config appropriately.
|
the cohort config appropriately.
|
||||||
@@ -59,6 +62,9 @@ class TestCohorts(django.test.TestCase):
|
|||||||
cohorted: bool.
|
cohorted: bool.
|
||||||
cohorted_discussions: optional list of topic names. If specified,
|
cohorted_discussions: optional list of topic names. If specified,
|
||||||
converts them to use the same ids as topic names.
|
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:
|
Returns:
|
||||||
Nothing -- modifies course in place.
|
Nothing -- modifies course in place.
|
||||||
@@ -76,6 +82,12 @@ class TestCohorts(django.test.TestCase):
|
|||||||
if cohorted_discussions is not None:
|
if cohorted_discussions is not None:
|
||||||
d["cohorted_discussions"] = [to_id(name)
|
d["cohorted_discussions"] = [to_id(name)
|
||||||
for name in cohorted_discussions]
|
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
|
course.metadata["cohort_config"] = d
|
||||||
|
|
||||||
|
|
||||||
@@ -89,12 +101,9 @@ class TestCohorts(django.test.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
def test_get_cohort(self):
|
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
|
Make sure get_cohort() does the right thing when the course is cohorted
|
||||||
# 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.
|
|
||||||
course = modulestore().get_course("edX/toy/2012_Fall")
|
course = modulestore().get_course("edX/toy/2012_Fall")
|
||||||
self.assertEqual(course.id, "edX/toy/2012_Fall")
|
self.assertEqual(course.id, "edX/toy/2012_Fall")
|
||||||
self.assertFalse(course.is_cohorted)
|
self.assertFalse(course.is_cohorted)
|
||||||
@@ -122,6 +131,54 @@ class TestCohorts(django.test.TestCase):
|
|||||||
self.assertEquals(get_cohort(other_user, course.id), None,
|
self.assertEquals(get_cohort(other_user, course.id), None,
|
||||||
"other_user shouldn't have a cohort")
|
"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):
|
def test_get_course_cohorts(self):
|
||||||
course1_id = 'a/b/c'
|
course1_id = 'a/b/c'
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from django.template.loaders.app_directories import Loader as AppDirectoriesLoad
|
|||||||
from mitxmako.template import Template
|
from mitxmako.template import Template
|
||||||
import mitxmako.middleware
|
import mitxmako.middleware
|
||||||
|
|
||||||
|
import tempdir
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ class MakoLoader(object):
|
|||||||
|
|
||||||
if module_directory is None:
|
if module_directory is None:
|
||||||
log.warning("For more caching of mako templates, set the MAKO_MODULE_DIR in settings!")
|
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
|
self.module_directory = module_directory
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from mako.lookup import TemplateLookup
|
from mako.lookup import TemplateLookup
|
||||||
import tempfile
|
import tempdir
|
||||||
from django.template import RequestContext
|
from django.template import RequestContext
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ class MakoMiddleware(object):
|
|||||||
module_directory = getattr(settings, 'MAKO_MODULE_DIR', None)
|
module_directory = getattr(settings, 'MAKO_MODULE_DIR', None)
|
||||||
|
|
||||||
if module_directory is None:
|
if module_directory is None:
|
||||||
module_directory = tempfile.mkdtemp()
|
module_directory = tempdir.mkdtemp_clean()
|
||||||
|
|
||||||
for location in template_locations:
|
for location in template_locations:
|
||||||
lookup[location] = TemplateLookup(directories=template_locations[location],
|
lookup[location] = TemplateLookup(directories=template_locations[location],
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
import cStringIO
|
import cStringIO
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
@@ -143,23 +144,18 @@ class PearsonTestCase(TestCase):
|
|||||||
'''
|
'''
|
||||||
Base class for tests running Pearson-related commands
|
Base class for tests running Pearson-related commands
|
||||||
'''
|
'''
|
||||||
import_dir = mkdtemp(prefix="import")
|
|
||||||
export_dir = mkdtemp(prefix="export")
|
|
||||||
|
|
||||||
def assertErrorContains(self, error_message, expected):
|
def assertErrorContains(self, error_message, expected):
|
||||||
self.assertTrue(error_message.find(expected) >= 0, 'error message "{}" did not contain "{}"'.format(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 tearDown(self):
|
||||||
def delete_temp_dir(dirname):
|
pass
|
||||||
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)
|
|
||||||
|
|
||||||
# and clean up the database:
|
# and clean up the database:
|
||||||
# TestCenterUser.objects.all().delete()
|
# TestCenterUser.objects.all().delete()
|
||||||
# TestCenterRegistration.objects.all().delete()
|
# TestCenterRegistration.objects.all().delete()
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class DragAndDrop(object):
|
|||||||
Returns: bool.
|
Returns: bool.
|
||||||
'''
|
'''
|
||||||
for draggable in self.excess_draggables:
|
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
|
return False # user answer has more draggables than correct answer
|
||||||
|
|
||||||
# Number of draggables in user_groups may be differ that in
|
# Number of draggables in user_groups may be differ that in
|
||||||
@@ -304,8 +304,13 @@ class DragAndDrop(object):
|
|||||||
|
|
||||||
user_answer = json.loads(user_answer)
|
user_answer = json.loads(user_answer)
|
||||||
|
|
||||||
# check if we have draggables that are not in correct answer:
|
# This dictionary will hold a key for each draggable the user placed on
|
||||||
self.excess_draggables = {}
|
# 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
|
# create identical data structures from user answer and correct answer
|
||||||
for i in xrange(0, len(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_groups[groupname].append(draggable_name)
|
||||||
self.user_positions[groupname]['user'].append(
|
self.user_positions[groupname]['user'].append(
|
||||||
draggable_dict[draggable_name])
|
draggable_dict[draggable_name])
|
||||||
self.excess_draggables[draggable_name] = True
|
# proved that this is not excess
|
||||||
else:
|
self.excess_draggables[draggable_name] = False
|
||||||
self.excess_draggables[draggable_name] = \
|
|
||||||
self.excess_draggables.get(draggable_name, False)
|
|
||||||
|
|
||||||
|
|
||||||
def grade(user_input, correct_answer):
|
def grade(user_input, correct_answer):
|
||||||
""" Creates DragAndDrop instance from user_input and correct_answer and
|
""" 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'}
|
correct_answer = {'1': 't1', 'name_with_icon': 't2'}
|
||||||
self.assertTrue(draganddrop.grade(user_input, correct_answer))
|
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):
|
def test_targets_false(self):
|
||||||
user_input = '{"draggables": [{"1": "t1"}, \
|
user_input = '{"draggables": [{"1": "t1"}, \
|
||||||
{"name_with_icon": "t2"}]}'
|
{"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
|
# used by conditional module
|
||||||
return self.attempts > 0
|
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):
|
def answer_available(self):
|
||||||
'''
|
'''
|
||||||
Is the user allowed to see an answer?
|
Is the user allowed to see an answer?
|
||||||
@@ -449,6 +454,9 @@ class CapaModule(XModule):
|
|||||||
return self.lcp.done
|
return self.lcp.done
|
||||||
elif self.show_answer == 'closed':
|
elif self.show_answer == 'closed':
|
||||||
return self.closed()
|
return self.closed()
|
||||||
|
elif self.show_answer == 'finished':
|
||||||
|
return self.closed() or self.is_correct()
|
||||||
|
|
||||||
elif self.show_answer == 'past_due':
|
elif self.show_answer == 'past_due':
|
||||||
return self.is_past_due()
|
return self.is_past_due()
|
||||||
elif self.show_answer == 'always':
|
elif self.show_answer == 'always':
|
||||||
|
|||||||
@@ -108,11 +108,13 @@ class CombinedOpenEndedModule(XModule):
|
|||||||
instance_state = {}
|
instance_state = {}
|
||||||
|
|
||||||
self.version = self.metadata.get('version', DEFAULT_VERSION)
|
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):
|
if not isinstance(self.version, basestring):
|
||||||
try:
|
try:
|
||||||
self.version = str(self.version)
|
self.version = str(self.version)
|
||||||
except:
|
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
|
self.version = DEFAULT_VERSION
|
||||||
|
|
||||||
versions = [i[0] for i in VERSION_TUPLES]
|
versions = [i[0] for i in VERSION_TUPLES]
|
||||||
@@ -122,7 +124,8 @@ class CombinedOpenEndedModule(XModule):
|
|||||||
try:
|
try:
|
||||||
version_index = versions.index(self.version)
|
version_index = versions.index(self.version)
|
||||||
except:
|
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
|
self.version = DEFAULT_VERSION
|
||||||
version_index = versions.index(self.version)
|
version_index = versions.index(self.version)
|
||||||
|
|
||||||
@@ -205,4 +208,4 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
|
|||||||
for child in ['task']:
|
for child in ['task']:
|
||||||
add_child(child)
|
add_child(child)
|
||||||
|
|
||||||
return elt
|
return elt
|
||||||
|
|||||||
@@ -352,6 +352,13 @@ class CourseDescriptor(SequenceDescriptor):
|
|||||||
"""
|
"""
|
||||||
return self.metadata.get('tabs')
|
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
|
@tabs.setter
|
||||||
def tabs(self, value):
|
def tabs(self, value):
|
||||||
self.metadata['tabs'] = value
|
self.metadata['tabs'] = value
|
||||||
@@ -371,6 +378,28 @@ class CourseDescriptor(SequenceDescriptor):
|
|||||||
|
|
||||||
return bool(config.get("cohorted"))
|
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
|
@property
|
||||||
def top_level_discussion_topic_ids(self):
|
def top_level_discussion_topic_ids(self):
|
||||||
"""
|
"""
|
||||||
@@ -707,7 +736,7 @@ class CourseDescriptor(SequenceDescriptor):
|
|||||||
def get_test_center_exam(self, exam_series_code):
|
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]
|
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
|
return exams[0] if len(exams) == 1 else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title(self):
|
def title(self):
|
||||||
return self.display_name
|
return self.display_name
|
||||||
|
|||||||
@@ -1,6 +1,61 @@
|
|||||||
class @Rubric
|
class @Rubric
|
||||||
constructor: () ->
|
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
|
# finds the scores for each rubric category
|
||||||
@get_score_list: () =>
|
@get_score_list: () =>
|
||||||
# find the number of categories:
|
# find the number of categories:
|
||||||
@@ -34,6 +89,7 @@ class @CombinedOpenEnded
|
|||||||
constructor: (element) ->
|
constructor: (element) ->
|
||||||
@element=element
|
@element=element
|
||||||
@reinitialize(element)
|
@reinitialize(element)
|
||||||
|
$(window).keydown @keydown_handler
|
||||||
|
|
||||||
reinitialize: (element) ->
|
reinitialize: (element) ->
|
||||||
@wrapper=$(element).find('section.xmodule_CombinedOpenEndedModule')
|
@wrapper=$(element).find('section.xmodule_CombinedOpenEndedModule')
|
||||||
@@ -45,6 +101,9 @@ class @CombinedOpenEnded
|
|||||||
@task_count = @el.data('task-count')
|
@task_count = @el.data('task-count')
|
||||||
@task_number = @el.data('task-number')
|
@task_number = @el.data('task-number')
|
||||||
@accept_file_upload = @el.data('accept-file-upload')
|
@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')
|
@allow_reset = @el.data('allow_reset')
|
||||||
@reset_button = @$('.reset-button')
|
@reset_button = @$('.reset-button')
|
||||||
@@ -89,6 +148,8 @@ class @CombinedOpenEnded
|
|||||||
@can_upload_files = false
|
@can_upload_files = false
|
||||||
@open_ended_child= @$('.open-ended-child')
|
@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
|
if @task_number>1
|
||||||
@prompt_hide()
|
@prompt_hide()
|
||||||
else if @task_number==1 and @child_state!='initial'
|
else if @task_number==1 and @child_state!='initial'
|
||||||
@@ -116,6 +177,9 @@ class @CombinedOpenEnded
|
|||||||
@submit_evaluation_button = $('.submit-evaluation-button')
|
@submit_evaluation_button = $('.submit-evaluation-button')
|
||||||
@submit_evaluation_button.click @message_post
|
@submit_evaluation_button.click @message_post
|
||||||
Collapsible.setCollapsibles(@results_container)
|
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) =>
|
show_results: (event) =>
|
||||||
status_item = $(event.target).parent()
|
status_item = $(event.target).parent()
|
||||||
@@ -153,7 +217,6 @@ class @CombinedOpenEnded
|
|||||||
@legend_container= $('.legend-container')
|
@legend_container= $('.legend-container')
|
||||||
|
|
||||||
message_post: (event)=>
|
message_post: (event)=>
|
||||||
Logger.log 'message_post', @answers
|
|
||||||
external_grader_message=$(event.target).parent().parent().parent()
|
external_grader_message=$(event.target).parent().parent().parent()
|
||||||
evaluation_scoring = $(event.target).parent()
|
evaluation_scoring = $(event.target).parent()
|
||||||
|
|
||||||
@@ -182,6 +245,7 @@ class @CombinedOpenEnded
|
|||||||
$('section.evaluation').slideToggle()
|
$('section.evaluation').slideToggle()
|
||||||
@message_wrapper.html(response.message_html)
|
@message_wrapper.html(response.message_html)
|
||||||
|
|
||||||
|
|
||||||
$.ajaxWithPrefix("#{@ajax_url}/save_post_assessment", settings)
|
$.ajaxWithPrefix("#{@ajax_url}/save_post_assessment", settings)
|
||||||
|
|
||||||
|
|
||||||
@@ -283,6 +347,7 @@ class @CombinedOpenEnded
|
|||||||
if response.success
|
if response.success
|
||||||
@rubric_wrapper.html(response.rubric_html)
|
@rubric_wrapper.html(response.rubric_html)
|
||||||
@rubric_wrapper.show()
|
@rubric_wrapper.show()
|
||||||
|
Rubric.initialize(@location)
|
||||||
@answer_area.html(response.student_response)
|
@answer_area.html(response.student_response)
|
||||||
@child_state = 'assessing'
|
@child_state = 'assessing'
|
||||||
@find_assessment_elements()
|
@find_assessment_elements()
|
||||||
@@ -293,7 +358,12 @@ class @CombinedOpenEnded
|
|||||||
$.ajaxWithPrefix("#{@ajax_url}/save_answer",settings)
|
$.ajaxWithPrefix("#{@ajax_url}/save_answer",settings)
|
||||||
|
|
||||||
else
|
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) =>
|
save_assessment: (event) =>
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -315,7 +385,7 @@ class @CombinedOpenEnded
|
|||||||
else
|
else
|
||||||
@errors_area.html(response.error)
|
@errors_area.html(response.error)
|
||||||
else
|
else
|
||||||
@errors_area.html('Problem state got out of sync. Try reloading the page.')
|
@errors_area.html(@out_of_sync_message)
|
||||||
|
|
||||||
save_hint: (event) =>
|
save_hint: (event) =>
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -330,7 +400,7 @@ class @CombinedOpenEnded
|
|||||||
else
|
else
|
||||||
@errors_area.html(response.error)
|
@errors_area.html(response.error)
|
||||||
else
|
else
|
||||||
@errors_area.html('Problem state got out of sync. Try reloading the page.')
|
@errors_area.html(@out_of_sync_message)
|
||||||
|
|
||||||
skip_post_assessment: =>
|
skip_post_assessment: =>
|
||||||
if @child_state == 'post_assessment'
|
if @child_state == 'post_assessment'
|
||||||
@@ -342,7 +412,7 @@ class @CombinedOpenEnded
|
|||||||
else
|
else
|
||||||
@errors_area.html(response.error)
|
@errors_area.html(response.error)
|
||||||
else
|
else
|
||||||
@errors_area.html('Problem state got out of sync. Try reloading the page.')
|
@errors_area.html(@out_of_sync_message)
|
||||||
|
|
||||||
reset: (event) =>
|
reset: (event) =>
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -362,7 +432,7 @@ class @CombinedOpenEnded
|
|||||||
else
|
else
|
||||||
@errors_area.html(response.error)
|
@errors_area.html(response.error)
|
||||||
else
|
else
|
||||||
@errors_area.html('Problem state got out of sync. Try reloading the page.')
|
@errors_area.html(@out_of_sync_message)
|
||||||
|
|
||||||
next_problem: =>
|
next_problem: =>
|
||||||
if @child_state == 'done'
|
if @child_state == 'done'
|
||||||
@@ -385,7 +455,7 @@ class @CombinedOpenEnded
|
|||||||
else
|
else
|
||||||
@errors_area.html(response.error)
|
@errors_area.html(response.error)
|
||||||
else
|
else
|
||||||
@errors_area.html('Problem state got out of sync. Try reloading the page.')
|
@errors_area.html(@out_of_sync_message)
|
||||||
|
|
||||||
gentle_alert: (msg) =>
|
gentle_alert: (msg) =>
|
||||||
if @el.find('.open-ended-alert').length
|
if @el.find('.open-ended-alert').length
|
||||||
@@ -404,7 +474,7 @@ class @CombinedOpenEnded
|
|||||||
$.postWithPrefix "#{@ajax_url}/check_for_score", (response) =>
|
$.postWithPrefix "#{@ajax_url}/check_for_score", (response) =>
|
||||||
if response.state == "done" or response.state=="post_assessment"
|
if response.state == "done" or response.state=="post_assessment"
|
||||||
delete window.queuePollerID
|
delete window.queuePollerID
|
||||||
location.reload()
|
@reload()
|
||||||
else
|
else
|
||||||
window.queuePollerID = window.setTimeout(@poll, 10000)
|
window.queuePollerID = window.setTimeout(@poll, 10000)
|
||||||
|
|
||||||
@@ -438,7 +508,9 @@ class @CombinedOpenEnded
|
|||||||
@prompt_container.toggleClass('open')
|
@prompt_container.toggleClass('open')
|
||||||
if @question_header.text() == "(Hide)"
|
if @question_header.text() == "(Hide)"
|
||||||
new_text = "(Show)"
|
new_text = "(Show)"
|
||||||
|
Logger.log 'oe_hide_question', {location: @location}
|
||||||
else
|
else
|
||||||
|
Logger.log 'oe_show_question', {location: @location}
|
||||||
new_text = "(Hide)"
|
new_text = "(Hide)"
|
||||||
@question_header.text(new_text)
|
@question_header.text(new_text)
|
||||||
|
|
||||||
@@ -454,4 +526,16 @@ class @CombinedOpenEnded
|
|||||||
@prompt_container.toggleClass('open')
|
@prompt_container.toggleClass('open')
|
||||||
@question_header.text("(Show)")
|
@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')
|
@prompt_container = $('.prompt-container')
|
||||||
@rubric_container = $('.rubric-container')
|
@rubric_container = $('.rubric-container')
|
||||||
@flag_student_container = $('.flag-student-container')
|
@flag_student_container = $('.flag-student-container')
|
||||||
|
@answer_unknown_container = $('.answer-unknown-container')
|
||||||
@calibration_panel = $('.calibration-panel')
|
@calibration_panel = $('.calibration-panel')
|
||||||
@grading_panel = $('.grading-panel')
|
@grading_panel = $('.grading-panel')
|
||||||
@content_panel = $('.content-panel')
|
@content_panel = $('.content-panel')
|
||||||
@@ -208,6 +209,10 @@ class @PeerGradingProblem
|
|||||||
@interstitial_page_button = $('.interstitial-page-button')
|
@interstitial_page_button = $('.interstitial-page-button')
|
||||||
@calibration_interstitial_page_button = $('.calibration-interstitial-page-button')
|
@calibration_interstitial_page_button = $('.calibration-interstitial-page-button')
|
||||||
@flag_student_checkbox = $('.flag-checkbox')
|
@flag_student_checkbox = $('.flag-checkbox')
|
||||||
|
@answer_unknown_checkbox = $('.answer-unknown-checkbox')
|
||||||
|
|
||||||
|
$(window).keydown @keydown_handler
|
||||||
|
|
||||||
@collapse_question()
|
@collapse_question()
|
||||||
|
|
||||||
Collapsible.setCollapsibles(@content_panel)
|
Collapsible.setCollapsibles(@content_panel)
|
||||||
@@ -249,9 +254,6 @@ class @PeerGradingProblem
|
|||||||
fetch_submission_essay: () =>
|
fetch_submission_essay: () =>
|
||||||
@backend.post('get_next_submission', {location: @location}, @render_submission)
|
@backend.post('get_next_submission', {location: @location}, @render_submission)
|
||||||
|
|
||||||
gentle_alert: (msg) =>
|
|
||||||
@grading_message.fadeIn()
|
|
||||||
@grading_message.html("<p>" + msg + "</p>")
|
|
||||||
|
|
||||||
construct_data: () ->
|
construct_data: () ->
|
||||||
data =
|
data =
|
||||||
@@ -262,6 +264,7 @@ class @PeerGradingProblem
|
|||||||
submission_key: @submission_key_input.val()
|
submission_key: @submission_key_input.val()
|
||||||
feedback: @feedback_area.val()
|
feedback: @feedback_area.val()
|
||||||
submission_flagged: @flag_student_checkbox.is(':checked')
|
submission_flagged: @flag_student_checkbox.is(':checked')
|
||||||
|
answer_unknown: @answer_unknown_checkbox.is(':checked')
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@@ -334,6 +337,14 @@ class @PeerGradingProblem
|
|||||||
@show_submit_button()
|
@show_submit_button()
|
||||||
@grade = Rubric.get_total_score()
|
@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()
|
@calibration_panel.find('.grading-text').hide()
|
||||||
@grading_panel.find('.grading-text').hide()
|
@grading_panel.find('.grading-text').hide()
|
||||||
@flag_student_container.hide()
|
@flag_student_container.hide()
|
||||||
|
@answer_unknown_container.hide()
|
||||||
|
|
||||||
@feedback_area.val("")
|
@feedback_area.val("")
|
||||||
|
|
||||||
@submit_button.unbind('click')
|
@submit_button.unbind('click')
|
||||||
@@ -388,6 +401,7 @@ class @PeerGradingProblem
|
|||||||
@calibration_panel.find('.grading-text').show()
|
@calibration_panel.find('.grading-text').show()
|
||||||
@grading_panel.find('.grading-text').show()
|
@grading_panel.find('.grading-text').show()
|
||||||
@flag_student_container.show()
|
@flag_student_container.show()
|
||||||
|
@answer_unknown_container.show()
|
||||||
@feedback_area.val("")
|
@feedback_area.val("")
|
||||||
|
|
||||||
@submit_button.unbind('click')
|
@submit_button.unbind('click')
|
||||||
@@ -420,6 +434,7 @@ class @PeerGradingProblem
|
|||||||
@submit_button.hide()
|
@submit_button.hide()
|
||||||
@action_button.hide()
|
@action_button.hide()
|
||||||
@calibration_feedback_panel.hide()
|
@calibration_feedback_panel.hide()
|
||||||
|
Rubric.initialize(@location)
|
||||||
|
|
||||||
|
|
||||||
render_calibration_feedback: (response) =>
|
render_calibration_feedback: (response) =>
|
||||||
@@ -466,11 +481,17 @@ class @PeerGradingProblem
|
|||||||
# And now hook up an event handler again
|
# And now hook up an event handler again
|
||||||
$("input[class='score-selection']").change @graded_callback
|
$("input[class='score-selection']").change @graded_callback
|
||||||
|
|
||||||
|
gentle_alert: (msg) =>
|
||||||
|
@grading_message.fadeIn()
|
||||||
|
@grading_message.html("<p>" + msg + "</p>")
|
||||||
|
|
||||||
collapse_question: () =>
|
collapse_question: () =>
|
||||||
@prompt_container.slideToggle()
|
@prompt_container.slideToggle()
|
||||||
@prompt_container.toggleClass('open')
|
@prompt_container.toggleClass('open')
|
||||||
if @question_header.text() == "(Hide)"
|
if @question_header.text() == "(Hide)"
|
||||||
|
Logger.log 'peer_grading_hide_question', {location: @location}
|
||||||
new_text = "(Show)"
|
new_text = "(Show)"
|
||||||
else
|
else
|
||||||
|
Logger.log 'peer_grading_show_question', {location: @location}
|
||||||
new_text = "(Hide)"
|
new_text = "(Hide)"
|
||||||
@question_header.text(new_text)
|
@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)
|
# cdodge: encapsulate a means to expose "editable" metadata fields (i.e. not internal system metadata)
|
||||||
@property
|
@property
|
||||||
def editable_metadata_fields(self):
|
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
|
return subset
|
||||||
|
|||||||
@@ -23,6 +23,15 @@ URL_RE = re.compile("""
|
|||||||
(@(?P<revision>[^/]+))?
|
(@(?P<revision>[^/]+))?
|
||||||
""", re.VERBOSE)
|
""", 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
|
# TODO (cpennington): We should decide whether we want to expand the
|
||||||
# list of valid characters in a location
|
# list of valid characters in a location
|
||||||
INVALID_CHARS = re.compile(r"[^\w.-]")
|
INVALID_CHARS = re.compile(r"[^\w.-]")
|
||||||
@@ -164,12 +173,16 @@ class Location(_LocationBase):
|
|||||||
if isinstance(location, basestring):
|
if isinstance(location, basestring):
|
||||||
match = URL_RE.match(location)
|
match = URL_RE.match(location)
|
||||||
if match is None:
|
if match is None:
|
||||||
log.debug('location is instance of %s but no URL match' % basestring)
|
# cdodge:
|
||||||
raise InvalidLocationError(location)
|
# check for a dropped slash near the i4x:// element of the location string. This can happen with some
|
||||||
else:
|
# redirects (e.g. edx.org -> www.edx.org which I think happens in Nginx)
|
||||||
groups = match.groupdict()
|
match = MISSING_SLASH_URL_RE.match(location)
|
||||||
check_dict(groups)
|
if match is None:
|
||||||
return _LocationBase.__new__(_cls, **groups)
|
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)):
|
elif isinstance(location, (list, tuple)):
|
||||||
if len(location) not in (5, 6):
|
if len(location) not in (5, 6):
|
||||||
log.debug('location has wrong length')
|
log.debug('location has wrong length')
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import pymongo
|
import pymongo
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
|
import copy
|
||||||
|
|
||||||
from bson.son import SON
|
from bson.son import SON
|
||||||
from fs.osfs import OSFS
|
from fs.osfs import OSFS
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
from path import path
|
from path import path
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from xmodule.errortracker import null_error_tracker, exc_info_to_str
|
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
|
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
|
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,
|
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
|
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
|
# 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
|
# define an attribute here as well, even though it's None
|
||||||
self.course_id = None
|
self.course_id = None
|
||||||
|
self.metadata_inheritance_tree = metadata_inheritance_tree
|
||||||
|
|
||||||
def load_item(self, location):
|
def load_item(self, location):
|
||||||
location = Location(location)
|
location = Location(location)
|
||||||
@@ -61,11 +66,13 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
|||||||
if json_data is None:
|
if json_data is None:
|
||||||
return self.modulestore.get_item(location)
|
return self.modulestore.get_item(location)
|
||||||
else:
|
else:
|
||||||
# TODO (vshnayder): metadata inheritance is somewhat broken because mongo, doesn't
|
# load the module and apply the inherited metadata
|
||||||
# always load an entire course. We're punting on this until after launch, and then
|
|
||||||
# will build a proper course policy framework.
|
|
||||||
try:
|
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:
|
except:
|
||||||
return ErrorDescriptor.from_json(
|
return ErrorDescriptor.from_json(
|
||||||
json_data,
|
json_data,
|
||||||
@@ -142,6 +149,82 @@ class MongoModuleStore(ModuleStoreBase):
|
|||||||
self.fs_root = path(fs_root)
|
self.fs_root = path(fs_root)
|
||||||
self.error_tracker = error_tracker
|
self.error_tracker = error_tracker
|
||||||
self.render_template = render_template
|
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):
|
def _clean_item_data(self, item):
|
||||||
"""
|
"""
|
||||||
@@ -196,6 +279,8 @@ class MongoModuleStore(ModuleStoreBase):
|
|||||||
|
|
||||||
resource_fs = OSFS(root)
|
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(
|
system = CachingDescriptorSystem(
|
||||||
self,
|
self,
|
||||||
data_cache,
|
data_cache,
|
||||||
@@ -203,6 +288,7 @@ class MongoModuleStore(ModuleStoreBase):
|
|||||||
resource_fs,
|
resource_fs,
|
||||||
self.error_tracker,
|
self.error_tracker,
|
||||||
self.render_template,
|
self.render_template,
|
||||||
|
metadata_inheritance_tree = self.get_cached_metadata_inheritance_tree(Location(item['location']), 60)
|
||||||
)
|
)
|
||||||
return system.load_item(item['location'])
|
return system.load_item(item['location'])
|
||||||
|
|
||||||
@@ -261,11 +347,11 @@ class MongoModuleStore(ModuleStoreBase):
|
|||||||
descendents of the queried modules for more efficient results later
|
descendents of the queried modules for more efficient results later
|
||||||
in the request. The depth is counted in the number of
|
in the request. The depth is counted in the number of
|
||||||
calls to get_children() to cache. None indicates to cache all descendents.
|
calls to get_children() to cache. None indicates to cache all descendents.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
location = Location.ensure_fully_specified(location)
|
location = Location.ensure_fully_specified(location)
|
||||||
item = self._find_one(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):
|
def get_instance(self, course_id, location, depth=0):
|
||||||
"""
|
"""
|
||||||
@@ -285,7 +371,8 @@ class MongoModuleStore(ModuleStoreBase):
|
|||||||
sort=[('revision', pymongo.ASCENDING)],
|
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):
|
def clone_item(self, source, location):
|
||||||
"""
|
"""
|
||||||
@@ -313,7 +400,7 @@ class MongoModuleStore(ModuleStoreBase):
|
|||||||
raise DuplicateItemError(location)
|
raise DuplicateItemError(location)
|
||||||
|
|
||||||
|
|
||||||
def get_course_for_item(self, location):
|
def get_course_for_item(self, location, depth=0):
|
||||||
'''
|
'''
|
||||||
VS[compat]
|
VS[compat]
|
||||||
cdodge: for a given Xmodule, return the course that it belongs to
|
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
|
# 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
|
# 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]
|
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
|
# make sure we found exactly one match on this above course search
|
||||||
found_cnt = len(courses)
|
found_cnt = len(courses)
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from lxml.html import rewrite_links
|
from lxml.html import rewrite_links
|
||||||
|
from xmodule.timeinfo import TimeInfo
|
||||||
|
|
||||||
from xmodule.capa_module import only_one, ComplexEncoder
|
from xmodule.capa_module import only_one, ComplexEncoder
|
||||||
from xmodule.editing_module import EditingDescriptor
|
from xmodule.editing_module import EditingDescriptor
|
||||||
from xmodule.html_checker import check_html
|
from xmodule.html_checker import check_html
|
||||||
@@ -14,16 +13,13 @@ from xmodule.xml_module import XmlDescriptor
|
|||||||
import self_assessment_module
|
import self_assessment_module
|
||||||
import open_ended_module
|
import open_ended_module
|
||||||
from combined_open_ended_rubric import CombinedOpenEndedRubric, GRADER_TYPE_IMAGE_DICT, HUMAN_GRADER_TYPE, LEGEND_LIST
|
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")
|
log = logging.getLogger("mitx.courseware")
|
||||||
|
|
||||||
# Set the default number of max attempts. Should be 1 for production
|
# Set the default number of max attempts. Should be 1 for production
|
||||||
# Set higher for debugging/testing
|
# Set higher for debugging/testing
|
||||||
# attempts specified in xml definition overrides this.
|
# attempts specified in xml definition overrides this.
|
||||||
MAX_ATTEMPTS = 10000
|
MAX_ATTEMPTS = 1
|
||||||
|
|
||||||
# Set maximum available number of points.
|
# Set maximum available number of points.
|
||||||
# Overriden by max_score specified in xml.
|
# Overriden by max_score specified in xml.
|
||||||
@@ -48,6 +44,10 @@ HUMAN_TASK_TYPE = {
|
|||||||
'openended' : "edX Assessment",
|
'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():
|
class CombinedOpenEndedV1Module():
|
||||||
"""
|
"""
|
||||||
This is a module that encapsulates all open ended grading (self assessment, peer assessment, etc).
|
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.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS))
|
||||||
self.is_scored = self.metadata.get('is_graded', IS_SCORED) in TRUE_DICT
|
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.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)
|
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)
|
grace_period_string = self.metadata.get('graceperiod', None)
|
||||||
if grace_period_string is not None and self.display_due_date:
|
try:
|
||||||
try:
|
self.timeinfo = TimeInfo(display_due_date_string, grace_period_string)
|
||||||
self.grace_period = parse_timedelta(grace_period_string)
|
except:
|
||||||
self.close_date = self.display_due_date + self.grace_period
|
log.error("Error parsing due date information in location {0}".format(location))
|
||||||
except:
|
raise
|
||||||
log.error("Error parsing the grace period {0} for location {1}".format(grace_period_string, location))
|
self.display_due_date = self.timeinfo.display_due_date
|
||||||
raise
|
|
||||||
else:
|
|
||||||
self.grace_period = None
|
|
||||||
self.close_date = self.display_due_date
|
|
||||||
|
|
||||||
# Used for progress / grading. Currently get credit just for
|
# Used for progress / grading. Currently get credit just for
|
||||||
# completion (doesn't matter if you self-assessed correct/incorrect).
|
# completion (doesn't matter if you self-assessed correct/incorrect).
|
||||||
@@ -185,8 +174,9 @@ class CombinedOpenEndedV1Module():
|
|||||||
'rubric': definition['rubric'],
|
'rubric': definition['rubric'],
|
||||||
'display_name': self.display_name,
|
'display_name': self.display_name,
|
||||||
'accept_file_upload': self.accept_file_upload,
|
'accept_file_upload': self.accept_file_upload,
|
||||||
'close_date' : self.close_date,
|
'close_date' : self.timeinfo.close_date,
|
||||||
's3_interface' : self.system.s3_interface,
|
's3_interface' : self.system.s3_interface,
|
||||||
|
'skip_basic_checks' : self.skip_basic_checks,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.task_xml = definition['task_xml']
|
self.task_xml = definition['task_xml']
|
||||||
@@ -340,6 +330,7 @@ class CombinedOpenEndedV1Module():
|
|||||||
'status': self.get_status(False),
|
'status': self.get_status(False),
|
||||||
'display_name': self.display_name,
|
'display_name': self.display_name,
|
||||||
'accept_file_upload': self.accept_file_upload,
|
'accept_file_upload': self.accept_file_upload,
|
||||||
|
'location': self.location,
|
||||||
'legend_list' : LEGEND_LIST,
|
'legend_list' : LEGEND_LIST,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,7 +647,10 @@ class CombinedOpenEndedV1Module():
|
|||||||
if self.attempts > self.max_attempts:
|
if self.attempts > self.max_attempts:
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'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.state = self.INITIAL
|
||||||
self.allow_reset = False
|
self.allow_reset = False
|
||||||
@@ -795,7 +789,8 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor):
|
|||||||
expected_children = ['task', 'rubric', 'prompt']
|
expected_children = ['task', 'rubric', 'prompt']
|
||||||
for child in expected_children:
|
for child in expected_children:
|
||||||
if len(xml_object.xpath(child)) == 0:
|
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):
|
def parse_task(k):
|
||||||
"""Assumes that xml_object has child k"""
|
"""Assumes that xml_object has child k"""
|
||||||
@@ -820,4 +815,4 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor):
|
|||||||
for child in ['task']:
|
for child in ['task']:
|
||||||
add_child(child)
|
add_child(child)
|
||||||
|
|
||||||
return elt
|
return elt
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from lxml import etree
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
GRADER_TYPE_IMAGE_DICT = {
|
GRADER_TYPE_IMAGE_DICT = {
|
||||||
'8B' : '/static/images/random_grading_icon.png',
|
|
||||||
'SA' : '/static/images/self_assessment_icon.png',
|
'SA' : '/static/images/self_assessment_icon.png',
|
||||||
'PE' : '/static/images/peer_grading_icon.png',
|
'PE' : '/static/images/peer_grading_icon.png',
|
||||||
'ML' : '/static/images/ml_grading_icon.png',
|
'ML' : '/static/images/ml_grading_icon.png',
|
||||||
@@ -13,7 +12,6 @@ GRADER_TYPE_IMAGE_DICT = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HUMAN_GRADER_TYPE = {
|
HUMAN_GRADER_TYPE = {
|
||||||
'8B' : 'Magic-8-Ball-Assessment',
|
|
||||||
'SA' : 'Self-Assessment',
|
'SA' : 'Self-Assessment',
|
||||||
'PE' : 'Peer-Assessment',
|
'PE' : 'Peer-Assessment',
|
||||||
'IN' : 'Instructor-Assessment',
|
'IN' : 'Instructor-Assessment',
|
||||||
@@ -71,8 +69,9 @@ class CombinedOpenEndedRubric(object):
|
|||||||
})
|
})
|
||||||
success = True
|
success = True
|
||||||
except:
|
except:
|
||||||
error_message = "[render_rubric] Could not parse the rubric with xml: {0}".format(rubric_xml)
|
#This is a staff_facing_error
|
||||||
log.error(error_message)
|
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)
|
raise RubricParsingError(error_message)
|
||||||
return {'success' : success, 'html' : html, 'rubric_scores' : rubric_scores}
|
return {'success' : success, 'html' : html, 'rubric_scores' : rubric_scores}
|
||||||
|
|
||||||
@@ -81,7 +80,8 @@ class CombinedOpenEndedRubric(object):
|
|||||||
success = rubric_dict['success']
|
success = rubric_dict['success']
|
||||||
rubric_feedback = rubric_dict['html']
|
rubric_feedback = rubric_dict['html']
|
||||||
if not success:
|
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)
|
log.error(error_message)
|
||||||
raise RubricParsingError(error_message)
|
raise RubricParsingError(error_message)
|
||||||
|
|
||||||
@@ -90,13 +90,15 @@ class CombinedOpenEndedRubric(object):
|
|||||||
for category in rubric_categories:
|
for category in rubric_categories:
|
||||||
total = total + len(category['options']) - 1
|
total = total + len(category['options']) - 1
|
||||||
if len(category['options']) > (max_score_allowed + 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)
|
len(category['options']), max_score_allowed)
|
||||||
log.error(error_message)
|
log.error(error_message)
|
||||||
raise RubricParsingError(error_message)
|
raise RubricParsingError(error_message)
|
||||||
|
|
||||||
if total != max_score:
|
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)
|
max_score, location, total)
|
||||||
log.error(error_msg)
|
log.error(error_msg)
|
||||||
raise RubricParsingError(error_msg)
|
raise RubricParsingError(error_msg)
|
||||||
@@ -118,7 +120,8 @@ class CombinedOpenEndedRubric(object):
|
|||||||
categories = []
|
categories = []
|
||||||
for category in element:
|
for category in element:
|
||||||
if category.tag != 'category':
|
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:
|
else:
|
||||||
categories.append(self.extract_category(category))
|
categories.append(self.extract_category(category))
|
||||||
return categories
|
return categories
|
||||||
@@ -144,12 +147,14 @@ class CombinedOpenEndedRubric(object):
|
|||||||
self.has_score = True
|
self.has_score = True
|
||||||
# if we are missing the score tag and we are expecting one
|
# if we are missing the score tag and we are expecting one
|
||||||
elif self.has_score:
|
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
|
# parse description
|
||||||
if descriptionxml.tag != '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
|
description = descriptionxml.text
|
||||||
|
|
||||||
@@ -159,7 +164,8 @@ class CombinedOpenEndedRubric(object):
|
|||||||
# parse options
|
# parse options
|
||||||
for option in optionsxml:
|
for option in optionsxml:
|
||||||
if option.tag != 'option':
|
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:
|
else:
|
||||||
pointstr = option.get("points")
|
pointstr = option.get("points")
|
||||||
if pointstr:
|
if pointstr:
|
||||||
@@ -168,7 +174,8 @@ class CombinedOpenEndedRubric(object):
|
|||||||
try:
|
try:
|
||||||
points = int(pointstr)
|
points = int(pointstr)
|
||||||
except ValueError:
|
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:
|
elif autonumbering:
|
||||||
# use the generated one if we're in the right mode
|
# use the generated one if we're in the right mode
|
||||||
points = cur_points
|
points = cur_points
|
||||||
@@ -200,7 +207,6 @@ class CombinedOpenEndedRubric(object):
|
|||||||
for grader_type in tuple[3]:
|
for grader_type in tuple[3]:
|
||||||
rubric_categories[i]['options'][j]['grader_types'].append(grader_type)
|
rubric_categories[i]['options'][j]['grader_types'].append(grader_type)
|
||||||
|
|
||||||
log.debug(rubric_categories)
|
|
||||||
html = self.system.render_template('open_ended_combined_rubric.html',
|
html = self.system.render_template('open_ended_combined_rubric.html',
|
||||||
{'categories': rubric_categories,
|
{'categories': rubric_categories,
|
||||||
'has_score': True,
|
'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
|
Validates a set of options. This can and should be extended to filter out other bad edge cases
|
||||||
'''
|
'''
|
||||||
if len(options) == 0:
|
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:
|
if len(options) == 1:
|
||||||
return
|
return
|
||||||
prev = options[0]['points']
|
prev = options[0]['points']
|
||||||
for option in options[1:]:
|
for option in options[1:]:
|
||||||
if prev == option['points']:
|
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:
|
else:
|
||||||
prev = option['points']
|
prev = option['points']
|
||||||
|
|
||||||
@@ -241,11 +249,14 @@ class CombinedOpenEndedRubric(object):
|
|||||||
"""
|
"""
|
||||||
success = False
|
success = False
|
||||||
if len(scores)==0:
|
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, ""
|
return success, ""
|
||||||
|
|
||||||
if len(scores) != len(score_types) or len(feedback_types) != len(scores):
|
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, ""
|
return success, ""
|
||||||
|
|
||||||
score_lists = []
|
score_lists = []
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
from xmodule.open_ended_grading_classes.grading_service_module import GradingService
|
from grading_service_module import GradingService
|
||||||
|
|
||||||
from xmodule.x_module import ModuleSystem
|
|
||||||
from mitxmako.shortcuts import render_to_string
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -11,8 +8,8 @@ class ControllerQueryService(GradingService):
|
|||||||
"""
|
"""
|
||||||
Interface to staff grading backend.
|
Interface to staff grading backend.
|
||||||
"""
|
"""
|
||||||
def __init__(self, config):
|
def __init__(self, config, system):
|
||||||
config['system'] = ModuleSystem(None, None, None, render_to_string, None)
|
config['system'] = system
|
||||||
super(ControllerQueryService, self).__init__(config)
|
super(ControllerQueryService, self).__init__(config)
|
||||||
self.url = config['url'] + config['grading_controller']
|
self.url = config['url'] + config['grading_controller']
|
||||||
self.login_url = self.url + '/login/'
|
self.login_url = self.url + '/login/'
|
||||||
@@ -77,3 +74,16 @@ class ControllerQueryService(GradingService):
|
|||||||
|
|
||||||
response = self.post(self.take_action_on_flags_url, params)
|
response = self.post(self.take_action_on_flags_url, params)
|
||||||
return response
|
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)
|
r = self._try_with_login(op)
|
||||||
except (RequestException, ConnectionError, HTTPError) as err:
|
except (RequestException, ConnectionError, HTTPError) as err:
|
||||||
# reraise as promised GradingServiceError, but preserve stacktrace.
|
# 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]
|
raise GradingServiceError, str(err), sys.exc_info()[2]
|
||||||
|
|
||||||
return r.text
|
return r.text
|
||||||
@@ -67,6 +69,8 @@ class GradingService(object):
|
|||||||
r = self._try_with_login(op)
|
r = self._try_with_login(op)
|
||||||
except (RequestException, ConnectionError, HTTPError) as err:
|
except (RequestException, ConnectionError, HTTPError) as err:
|
||||||
# reraise as promised GradingServiceError, but preserve stacktrace.
|
# 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]
|
raise GradingServiceError, str(err), sys.exc_info()[2]
|
||||||
|
|
||||||
return r.text
|
return r.text
|
||||||
@@ -119,11 +123,13 @@ class GradingService(object):
|
|||||||
return response_json
|
return response_json
|
||||||
# if we can't parse the rubric into HTML,
|
# if we can't parse the rubric into HTML,
|
||||||
except etree.XMLSyntaxError, RubricParsingError:
|
except etree.XMLSyntaxError, RubricParsingError:
|
||||||
|
#This is a dev_facing_error
|
||||||
log.exception("Cannot parse rubric string. Raw string: {0}"
|
log.exception("Cannot parse rubric string. Raw string: {0}"
|
||||||
.format(rubric))
|
.format(rubric))
|
||||||
return {'success': False,
|
return {'success': False,
|
||||||
'error': 'Error displaying submission'}
|
'error': 'Error displaying submission'}
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
#This is a dev_facing_error
|
||||||
log.exception("Error parsing response: {0}".format(response))
|
log.exception("Error parsing response: {0}".format(response))
|
||||||
return {'success': False,
|
return {'success': False,
|
||||||
'error': "Error displaying submission"}
|
'error': "Error displaying submission"}
|
||||||
|
|||||||
@@ -251,8 +251,9 @@ def upload_to_s3(file_to_upload, keyname, s3_interface):
|
|||||||
|
|
||||||
return True, public_url
|
return True, public_url
|
||||||
except:
|
except:
|
||||||
error_message = "Could not connect to S3."
|
#This is a dev_facing_error
|
||||||
log.exception(error_message)
|
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
|
return False, error_message
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,12 +59,14 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
|||||||
self.submission_id = None
|
self.submission_id = None
|
||||||
self.grader_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:
|
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:
|
if self.prompt is None:
|
||||||
raise ValueError("No prompt found in problem xml.")
|
raise ValueError(error_message.format('prompt'))
|
||||||
if self.rubric is None:
|
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)
|
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.send_to_grader(self.latest_answer(), system)
|
||||||
self.created = False
|
self.created = False
|
||||||
|
|
||||||
|
|
||||||
def _parse(self, oeparam, prompt, rubric, system):
|
def _parse(self, oeparam, prompt, rubric, system):
|
||||||
'''
|
'''
|
||||||
Parse OpenEndedResponse XML:
|
Parse OpenEndedResponse XML:
|
||||||
@@ -98,19 +101,21 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
|||||||
# __init__ adds it (easiest way to get problem location into
|
# __init__ adds it (easiest way to get problem location into
|
||||||
# response types)
|
# response types)
|
||||||
except TypeError, ValueError:
|
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.initial_display = find_with_default(oeparam, 'initial_display', '')
|
||||||
self.answer = find_with_default(oeparam, 'answer_display', 'No answer given.')
|
self.answer = find_with_default(oeparam, 'answer_display', 'No answer given.')
|
||||||
|
|
||||||
parsed_grader_payload.update({
|
parsed_grader_payload.update({
|
||||||
'location': system.location.url(),
|
'location': self.location_string,
|
||||||
'course_id': system.course_id,
|
'course_id': system.course_id,
|
||||||
'prompt': prompt_string,
|
'prompt': prompt_string,
|
||||||
'rubric': rubric_string,
|
'rubric': rubric_string,
|
||||||
'initial_display': self.initial_display,
|
'initial_display': self.initial_display,
|
||||||
'answer': self.answer,
|
'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)
|
updated_grader_payload = json.dumps(parsed_grader_payload)
|
||||||
|
|
||||||
@@ -133,24 +138,27 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
event_info = dict()
|
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['student_id'] = system.anonymous_student_id
|
||||||
event_info['survey_responses'] = get
|
event_info['survey_responses'] = get
|
||||||
|
|
||||||
survey_responses = event_info['survey_responses']
|
survey_responses = event_info['survey_responses']
|
||||||
for tag in ['feedback', 'submission_id', 'grader_id', 'score']:
|
for tag in ['feedback', 'submission_id', 'grader_id', 'score']:
|
||||||
if tag not in survey_responses:
|
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:
|
try:
|
||||||
submission_id = int(survey_responses['submission_id'])
|
submission_id = int(survey_responses['submission_id'])
|
||||||
grader_id = int(survey_responses['grader_id'])
|
grader_id = int(survey_responses['grader_id'])
|
||||||
feedback = str(survey_responses['feedback'].encode('ascii', 'ignore'))
|
feedback = str(survey_responses['feedback'].encode('ascii', 'ignore'))
|
||||||
score = int(survey_responses['score'])
|
score = int(survey_responses['score'])
|
||||||
except:
|
except:
|
||||||
|
#This is a dev_facing_error
|
||||||
error_message = ("Could not parse submission id, grader id, "
|
error_message = ("Could not parse submission id, grader id, "
|
||||||
"or feedback from message_post ajax call. Here is the message data: {0}".format(
|
"or feedback from message_post ajax call. Here is the message data: {0}".format(
|
||||||
survey_responses))
|
survey_responses))
|
||||||
log.exception(error_message)
|
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."}
|
return {'success': False, 'msg': "There was an error saving your feedback. Please contact course staff."}
|
||||||
|
|
||||||
qinterface = system.xqueue['interface']
|
qinterface = system.xqueue['interface']
|
||||||
@@ -187,6 +195,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
|||||||
|
|
||||||
self.state = self.DONE
|
self.state = self.DONE
|
||||||
|
|
||||||
|
#This is a student_facing_message
|
||||||
return {'success': success, 'msg': "Successfully submitted your feedback."}
|
return {'success': success, 'msg': "Successfully submitted your feedback."}
|
||||||
|
|
||||||
def send_to_grader(self, submission, system):
|
def send_to_grader(self, submission, system):
|
||||||
@@ -336,18 +345,22 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
|||||||
|
|
||||||
for tag in ['success', 'feedback', 'submission_id', 'grader_id']:
|
for tag in ['success', 'feedback', 'submission_id', 'grader_id']:
|
||||||
if tag not in response_items:
|
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']
|
feedback_items = response_items['feedback']
|
||||||
try:
|
try:
|
||||||
feedback = json.loads(feedback_items)
|
feedback = json.loads(feedback_items)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
log.exception("feedback_items have invalid json %r", feedback_items)
|
#This is a dev_facing_error
|
||||||
return format_feedback('errors', 'Could not parse feedback')
|
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 response_items['success']:
|
||||||
if len(feedback) == 0:
|
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:
|
for tag in do_not_render:
|
||||||
if tag in feedback:
|
if tag in feedback:
|
||||||
@@ -356,6 +369,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
|||||||
feedback_lst = sorted(feedback.items(), key=get_priority)
|
feedback_lst = sorted(feedback.items(), key=get_priority)
|
||||||
feedback_list_part1 = u"\n".join(format_feedback(k, v) for k, v in feedback_lst)
|
feedback_list_part1 = u"\n".join(format_feedback(k, v) for k, v in feedback_lst)
|
||||||
else:
|
else:
|
||||||
|
#This is a student_facing_error
|
||||||
feedback_list_part1 = format_feedback('errors', response_items['feedback'])
|
feedback_list_part1 = format_feedback('errors', response_items['feedback'])
|
||||||
|
|
||||||
feedback_list_part2 = (u"\n".join([format_feedback_hidden(feedback_type, value)
|
feedback_list_part2 = (u"\n".join([format_feedback_hidden(feedback_type, value)
|
||||||
@@ -431,14 +445,16 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
|||||||
try:
|
try:
|
||||||
score_result = json.loads(score_msg)
|
score_result = json.loads(score_msg)
|
||||||
except (TypeError, ValueError):
|
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))
|
" Received score_msg = {0}".format(score_msg))
|
||||||
log.error(error_message)
|
log.error(error_message)
|
||||||
fail['feedback'] = error_message
|
fail['feedback'] = error_message
|
||||||
return fail
|
return fail
|
||||||
|
|
||||||
if not isinstance(score_result, dict):
|
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))
|
" Received score_result = {0}".format(score_result))
|
||||||
log.error(error_message)
|
log.error(error_message)
|
||||||
fail['feedback'] = 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']:
|
for tag in ['score', 'feedback', 'grader_type', 'success', 'grader_id', 'submission_id']:
|
||||||
if tag not in score_result:
|
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))
|
.format(tag))
|
||||||
log.error(error_message)
|
log.error(error_message)
|
||||||
fail['feedback'] = error_message
|
fail['feedback'] = error_message
|
||||||
@@ -563,7 +580,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if dispatch not in handlers:
|
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()
|
before = self.get_progress()
|
||||||
d = handlers[dispatch](get, system)
|
d = handlers[dispatch](get, system)
|
||||||
@@ -604,15 +624,21 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
|||||||
success, get = self.append_image_to_student_answer(get)
|
success, get = self.append_image_to_student_answer(get)
|
||||||
error_message = ""
|
error_message = ""
|
||||||
if success:
|
if success:
|
||||||
get['student_answer'] = OpenEndedModule.sanitize_html(get['student_answer'])
|
success, allowed_to_submit, error_message = self.check_if_student_can_submit()
|
||||||
self.new_history_entry(get['student_answer'])
|
if allowed_to_submit:
|
||||||
self.send_to_grader(get['student_answer'], system)
|
get['student_answer'] = OpenEndedModule.sanitize_html(get['student_answer'])
|
||||||
self.change_state(self.ASSESSING)
|
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:
|
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."
|
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 {
|
return {
|
||||||
'success': True,
|
'success': success,
|
||||||
'error': error_message,
|
'error': error_message,
|
||||||
'student_response': get['student_answer']
|
'student_response': get['student_answer']
|
||||||
}
|
}
|
||||||
@@ -637,17 +663,21 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
|||||||
Output: Rendered HTML
|
Output: Rendered HTML
|
||||||
"""
|
"""
|
||||||
#set context variables and render template
|
#set context variables and render template
|
||||||
|
eta_string = None
|
||||||
if self.state != self.INITIAL:
|
if self.state != self.INITIAL:
|
||||||
latest = self.latest_answer()
|
latest = self.latest_answer()
|
||||||
previous_answer = latest if latest is not None else self.initial_display
|
previous_answer = latest if latest is not None else self.initial_display
|
||||||
post_assessment = self.latest_post_assessment(system)
|
post_assessment = self.latest_post_assessment(system)
|
||||||
score = self.latest_score()
|
score = self.latest_score()
|
||||||
correct = 'correct' if self.is_submission_correct(score) else 'incorrect'
|
correct = 'correct' if self.is_submission_correct(score) else 'incorrect'
|
||||||
|
if self.state == self.ASSESSING:
|
||||||
|
eta_string = self.get_eta()
|
||||||
else:
|
else:
|
||||||
post_assessment = ""
|
post_assessment = ""
|
||||||
correct = ""
|
correct = ""
|
||||||
previous_answer = self.initial_display
|
previous_answer = self.initial_display
|
||||||
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'prompt': self.prompt,
|
'prompt': self.prompt,
|
||||||
'previous_answer': previous_answer,
|
'previous_answer': previous_answer,
|
||||||
@@ -660,6 +690,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
|||||||
'child_type': 'openended',
|
'child_type': 'openended',
|
||||||
'correct': correct,
|
'correct': correct,
|
||||||
'accept_file_upload': self.accept_file_upload,
|
'accept_file_upload': self.accept_file_upload,
|
||||||
|
'eta_message' : eta_string,
|
||||||
}
|
}
|
||||||
html = system.render_template('open_ended.html', context)
|
html = system.render_template('open_ended.html', context)
|
||||||
return html
|
return html
|
||||||
@@ -689,7 +720,8 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
|
|||||||
"""
|
"""
|
||||||
for child in ['openendedparam']:
|
for child in ['openendedparam']:
|
||||||
if len(xml_object.xpath(child)) != 1:
|
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):
|
def parse(k):
|
||||||
"""Assumes that xml_object has child 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.xml_module import XmlDescriptor
|
||||||
from xmodule.modulestore import Location
|
from xmodule.modulestore import Location
|
||||||
from capa.util import *
|
from capa.util import *
|
||||||
|
from peer_grading_service import PeerGradingService
|
||||||
|
import controller_query_service
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -99,10 +101,21 @@ class OpenEndedChild(object):
|
|||||||
self.accept_file_upload = static_data['accept_file_upload']
|
self.accept_file_upload = static_data['accept_file_upload']
|
||||||
self.close_date = static_data['close_date']
|
self.close_date = static_data['close_date']
|
||||||
self.s3_interface = static_data['s3_interface']
|
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
|
# Used for progress / grading. Currently get credit just for
|
||||||
# completion (doesn't matter if you self-assessed correct/incorrect).
|
# completion (doesn't matter if you self-assessed correct/incorrect).
|
||||||
self._max_score = static_data['max_score']
|
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)
|
self.setup_response(system, location, definition, descriptor)
|
||||||
|
|
||||||
@@ -126,12 +139,14 @@ class OpenEndedChild(object):
|
|||||||
if self.closed():
|
if self.closed():
|
||||||
return True, {
|
return True, {
|
||||||
'success': False,
|
'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:
|
elif self.attempts > self.max_attempts:
|
||||||
return True, {
|
return True, {
|
||||||
'success': False,
|
'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:
|
else:
|
||||||
return False, {}
|
return False, {}
|
||||||
@@ -250,7 +265,8 @@ class OpenEndedChild(object):
|
|||||||
try:
|
try:
|
||||||
return Progress(self.get_score()['score'], self._max_score)
|
return Progress(self.get_score()['score'], self._max_score)
|
||||||
except Exception as err:
|
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
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -258,10 +274,12 @@ class OpenEndedChild(object):
|
|||||||
"""
|
"""
|
||||||
return dict out-of-sync error message, and also log.
|
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)
|
self.state, get, msg)
|
||||||
|
#This is a student_facing_error
|
||||||
return {'success': False,
|
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):
|
def get_html(self):
|
||||||
"""
|
"""
|
||||||
@@ -339,6 +357,10 @@ class OpenEndedChild(object):
|
|||||||
if get_data['can_upload_files'] in ['true', '1']:
|
if get_data['can_upload_files'] in ['true', '1']:
|
||||||
has_file_to_upload = True
|
has_file_to_upload = True
|
||||||
file = get_data['student_file'][0]
|
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)
|
uploaded_to_s3, image_ok, s3_public_url = self.upload_image_to_s3(file)
|
||||||
if uploaded_to_s3:
|
if uploaded_to_s3:
|
||||||
image_tag = self.generate_image_tag_from_url(s3_public_url, file.name)
|
image_tag = self.generate_image_tag_from_url(s3_public_url, file.name)
|
||||||
@@ -407,3 +429,55 @@ class OpenEndedChild(object):
|
|||||||
success = True
|
success = True
|
||||||
|
|
||||||
return success, string
|
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
|
self.system = system
|
||||||
|
|
||||||
def get_data_for_location(self, problem_location, student_id):
|
def get_data_for_location(self, problem_location, student_id):
|
||||||
response = self.get(self.get_data_for_location_url,
|
params = {'location': problem_location, 'student_id': student_id}
|
||||||
{'location': problem_location, 'student_id': student_id})
|
response = self.get(self.get_data_for_location_url, params)
|
||||||
return self.try_to_decode(response)
|
return self.try_to_decode(response)
|
||||||
|
|
||||||
def get_next_submission(self, problem_location, grader_id):
|
def get_next_submission(self, problem_location, grader_id):
|
||||||
@@ -106,7 +106,7 @@ class MockPeerGradingService(object):
|
|||||||
'max_score': 4})
|
'max_score': 4})
|
||||||
|
|
||||||
def save_grade(self, location, grader_id, submission_id,
|
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})
|
return json.dumps({'success': True})
|
||||||
|
|
||||||
def is_student_calibrated(self, problem_location, grader_id):
|
def is_student_calibrated(self, problem_location, grader_id):
|
||||||
@@ -122,7 +122,8 @@ class MockPeerGradingService(object):
|
|||||||
'max_score': 4})
|
'max_score': 4})
|
||||||
|
|
||||||
def save_calibration_essay(self, problem_location, grader_id,
|
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}
|
return {'success': True, 'actual_score': 2}
|
||||||
|
|
||||||
def get_problem_list(self, course_id, grader_id):
|
def get_problem_list(self, course_id, grader_id):
|
||||||
|
|||||||
@@ -90,7 +90,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if dispatch not in handlers:
|
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()
|
before = self.get_progress()
|
||||||
d = handlers[dispatch](get, system)
|
d = handlers[dispatch](get, system)
|
||||||
@@ -123,7 +126,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
|||||||
elif self.state in (self.POST_ASSESSMENT, self.DONE):
|
elif self.state in (self.POST_ASSESSMENT, self.DONE):
|
||||||
context['read_only'] = True
|
context['read_only'] = True
|
||||||
else:
|
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)
|
return system.render_template('self_assessment_rubric.html', context)
|
||||||
|
|
||||||
@@ -148,7 +152,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
|||||||
elif self.state == self.DONE:
|
elif self.state == self.DONE:
|
||||||
context['read_only'] = True
|
context['read_only'] = True
|
||||||
else:
|
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)
|
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.
|
# add new history element with answer and empty score and hint.
|
||||||
success, get = self.append_image_to_student_answer(get)
|
success, get = self.append_image_to_student_answer(get)
|
||||||
if success:
|
if success:
|
||||||
get['student_answer'] = SelfAssessmentModule.sanitize_html(get['student_answer'])
|
success, allowed_to_submit, error_message = self.check_if_student_can_submit()
|
||||||
self.new_history_entry(get['student_answer'])
|
if allowed_to_submit:
|
||||||
self.change_state(self.ASSESSING)
|
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:
|
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."
|
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 {
|
return {
|
||||||
@@ -214,7 +225,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
|||||||
for i in xrange(0,len(score_list)):
|
for i in xrange(0,len(score_list)):
|
||||||
score_list[i] = int(score_list[i])
|
score_list[i] = int(score_list[i])
|
||||||
except ValueError:
|
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
|
#Record score as assessment and rubric scores as post assessment
|
||||||
self.record_latest_score(score)
|
self.record_latest_score(score)
|
||||||
@@ -256,6 +270,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
|||||||
try:
|
try:
|
||||||
rubric_scores = json.loads(latest_post_assessment)
|
rubric_scores = json.loads(latest_post_assessment)
|
||||||
except:
|
except:
|
||||||
|
#This is a dev_facing_error
|
||||||
log.error("Cannot parse rubric scores in self assessment module from {0}".format(latest_post_assessment))
|
log.error("Cannot parse rubric scores in self assessment module from {0}".format(latest_post_assessment))
|
||||||
rubric_scores = []
|
rubric_scores = []
|
||||||
return [rubric_scores]
|
return [rubric_scores]
|
||||||
@@ -287,7 +302,8 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
|
|||||||
expected_children = []
|
expected_children = []
|
||||||
for child in expected_children:
|
for child in expected_children:
|
||||||
if len(xml_object.xpath(child)) != 1:
|
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):
|
def parse(k):
|
||||||
"""Assumes that xml_object has child k"""
|
"""Assumes that xml_object has child k"""
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import logging
|
|||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from pkg_resources import resource_string
|
from pkg_resources import resource_string
|
||||||
from .capa_module import ComplexEncoder
|
from .capa_module import ComplexEncoder
|
||||||
from .editing_module import EditingDescriptor
|
from .editing_module import EditingDescriptor
|
||||||
@@ -10,6 +11,8 @@ from .stringify import stringify_children
|
|||||||
from .x_module import XModule
|
from .x_module import XModule
|
||||||
from .xml_module import XmlDescriptor
|
from .xml_module import XmlDescriptor
|
||||||
from xmodule.modulestore import Location
|
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
|
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
|
MAX_SCORE = 1
|
||||||
IS_GRADED = True
|
IS_GRADED = True
|
||||||
|
|
||||||
|
EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please notify course staff."
|
||||||
|
|
||||||
|
|
||||||
class PeerGradingModule(XModule):
|
class PeerGradingModule(XModule):
|
||||||
_VERSION = 1
|
_VERSION = 1
|
||||||
@@ -50,6 +55,7 @@ class PeerGradingModule(XModule):
|
|||||||
self.system = system
|
self.system = system
|
||||||
self.peer_gs = PeerGradingService(self.system.open_ended_grading_interface, self.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)
|
self.use_for_single_location = self.metadata.get('use_for_single_location', USE_FOR_SINGLE_LOCATION)
|
||||||
if isinstance(self.use_for_single_location, basestring):
|
if isinstance(self.use_for_single_location, basestring):
|
||||||
self.use_for_single_location = (self.use_for_single_location in TRUE_DICT)
|
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):
|
if isinstance(self.is_graded, basestring):
|
||||||
self.is_graded = (self.is_graded in TRUE_DICT)
|
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)
|
self.link_to_location = self.metadata.get('link_to_location', USE_FOR_SINGLE_LOCATION)
|
||||||
if self.use_for_single_location == True:
|
if self.use_for_single_location == True:
|
||||||
#This will raise an exception if the location is invalid
|
try:
|
||||||
link_to_location_object = Location(self.link_to_location)
|
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
|
self.ajax_url = self.system.ajax_url
|
||||||
if not self.ajax_url.endswith("/"):
|
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
|
#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)
|
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):
|
def _err_response(self, msg):
|
||||||
"""
|
"""
|
||||||
Return a HttpResponse with a json dump with success=False, and the given error message.
|
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.
|
Needs to be implemented by inheritors. Renders the HTML that students see.
|
||||||
@return:
|
@return:
|
||||||
"""
|
"""
|
||||||
|
if self.closed():
|
||||||
|
return self.peer_grading_closed()
|
||||||
if not self.use_for_single_location:
|
if not self.use_for_single_location:
|
||||||
return self.peer_grading()
|
return self.peer_grading()
|
||||||
else:
|
else:
|
||||||
@@ -112,7 +147,10 @@ class PeerGradingModule(XModule):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if dispatch not in handlers:
|
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)
|
d = handlers[dispatch](get)
|
||||||
|
|
||||||
@@ -130,6 +168,7 @@ class PeerGradingModule(XModule):
|
|||||||
count_required = response['count_required']
|
count_required = response['count_required']
|
||||||
success = True
|
success = True
|
||||||
except GradingServiceError:
|
except GradingServiceError:
|
||||||
|
#This is a dev_facing_error
|
||||||
log.exception("Error getting location data from controller for location {0}, student {1}"
|
log.exception("Error getting location data from controller for location {0}, student {1}"
|
||||||
.format(location, student_id))
|
.format(location, student_id))
|
||||||
|
|
||||||
@@ -155,6 +194,7 @@ class PeerGradingModule(XModule):
|
|||||||
count_graded = response['count_graded']
|
count_graded = response['count_graded']
|
||||||
count_required = response['count_required']
|
count_required = response['count_required']
|
||||||
if count_required > 0 and count_graded >= 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
|
self.student_data_for_location = response
|
||||||
|
|
||||||
score_dict = {
|
score_dict = {
|
||||||
@@ -204,10 +244,12 @@ class PeerGradingModule(XModule):
|
|||||||
response = self.peer_gs.get_next_submission(location, grader_id)
|
response = self.peer_gs.get_next_submission(location, grader_id)
|
||||||
return response
|
return response
|
||||||
except GradingServiceError:
|
except GradingServiceError:
|
||||||
|
#This is a dev_facing_error
|
||||||
log.exception("Error getting next submission. server url: {0} location: {1}, grader_id: {2}"
|
log.exception("Error getting next submission. server url: {0} location: {1}, grader_id: {2}"
|
||||||
.format(self.peer_gs.url, location, grader_id))
|
.format(self.peer_gs.url, location, grader_id))
|
||||||
|
#This is a student_facing_error
|
||||||
return {'success': False,
|
return {'success': False,
|
||||||
'error': 'Could not connect to grading service'}
|
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR}
|
||||||
|
|
||||||
def save_grade(self, get):
|
def save_grade(self, get):
|
||||||
"""
|
"""
|
||||||
@@ -244,14 +286,16 @@ class PeerGradingModule(XModule):
|
|||||||
score, feedback, submission_key, rubric_scores, submission_flagged)
|
score, feedback, submission_key, rubric_scores, submission_flagged)
|
||||||
return response
|
return response
|
||||||
except GradingServiceError:
|
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}"""
|
submission_key: {3}, score: {4}"""
|
||||||
.format(self.peer_gs.url,
|
.format(self.peer_gs.url,
|
||||||
location, submission_id, submission_key, score)
|
location, submission_id, submission_key, score)
|
||||||
)
|
)
|
||||||
|
#This is a student_facing_error
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Could not connect to grading service'
|
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
def is_student_calibrated(self, get):
|
def is_student_calibrated(self, get):
|
||||||
@@ -284,11 +328,13 @@ class PeerGradingModule(XModule):
|
|||||||
response = self.peer_gs.is_student_calibrated(location, grader_id)
|
response = self.peer_gs.is_student_calibrated(location, grader_id)
|
||||||
return response
|
return response
|
||||||
except GradingServiceError:
|
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))
|
.format(self.peer_gs.url, grader_id, location))
|
||||||
|
#This is a student_facing_error
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Could not connect to grading service'
|
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
def show_calibration_essay(self, get):
|
def show_calibration_essay(self, get):
|
||||||
@@ -327,16 +373,20 @@ class PeerGradingModule(XModule):
|
|||||||
response = self.peer_gs.show_calibration_essay(location, grader_id)
|
response = self.peer_gs.show_calibration_essay(location, grader_id)
|
||||||
return response
|
return response
|
||||||
except GradingServiceError:
|
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))
|
.format(self.peer_gs.url, location))
|
||||||
|
#This is a student_facing_error
|
||||||
return {'success': False,
|
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,
|
# if we can't parse the rubric into HTML,
|
||||||
except etree.XMLSyntaxError:
|
except etree.XMLSyntaxError:
|
||||||
|
#This is a dev_facing_error
|
||||||
log.exception("Cannot parse rubric string. Raw string: {0}"
|
log.exception("Cannot parse rubric string. Raw string: {0}"
|
||||||
.format(rubric))
|
.format(rubric))
|
||||||
|
#This is a student_facing_error
|
||||||
return {'success': False,
|
return {'success': False,
|
||||||
'error': 'Error displaying submission'}
|
'error': 'Error displaying submission. Please notify course staff.'}
|
||||||
|
|
||||||
|
|
||||||
def save_calibration_essay(self, get):
|
def save_calibration_essay(self, get):
|
||||||
@@ -375,8 +425,20 @@ class PeerGradingModule(XModule):
|
|||||||
submission_key, score, feedback, rubric_scores)
|
submission_key, score, feedback, rubric_scores)
|
||||||
return response
|
return response
|
||||||
except GradingServiceError:
|
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))
|
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):
|
def peer_grading(self, get=None):
|
||||||
'''
|
'''
|
||||||
@@ -397,13 +459,49 @@ class PeerGradingModule(XModule):
|
|||||||
problem_list = problem_list_dict['problem_list']
|
problem_list = problem_list_dict['problem_list']
|
||||||
|
|
||||||
except GradingServiceError:
|
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
|
success = False
|
||||||
# catch error if if the json loads fails
|
# catch error if if the json loads fails
|
||||||
except ValueError:
|
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
|
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
|
ajax_url = self.ajax_url
|
||||||
html = self.system.render_template('peer_grading/peer_grading.html', {
|
html = self.system.render_template('peer_grading/peer_grading.html', {
|
||||||
'course_id': self.system.course_id,
|
'course_id': self.system.course_id,
|
||||||
@@ -425,6 +523,8 @@ class PeerGradingModule(XModule):
|
|||||||
if get == None or get.get('location') == None:
|
if get == None or get.get('location') == None:
|
||||||
if not self.use_for_single_location:
|
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 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}
|
return {'html': "", 'success': False}
|
||||||
problem_location = self.link_to_location
|
problem_location = self.link_to_location
|
||||||
|
|
||||||
@@ -489,7 +589,8 @@ class PeerGradingDescriptor(XmlDescriptor, EditingDescriptor):
|
|||||||
expected_children = []
|
expected_children = []
|
||||||
for child in expected_children:
|
for child in expected_children:
|
||||||
if len(xml_object.xpath(child)) == 0:
|
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):
|
def parse_task(k):
|
||||||
"""Assumes that xml_object has child k"""
|
"""Assumes that xml_object has child k"""
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ import xmodule
|
|||||||
from xmodule.x_module import ModuleSystem
|
from xmodule.x_module import ModuleSystem
|
||||||
from mock import Mock
|
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(
|
test_system = ModuleSystem(
|
||||||
ajax_url='courses/course_id/modx/a_location',
|
ajax_url='courses/course_id/modx/a_location',
|
||||||
track_function=Mock(),
|
track_function=Mock(),
|
||||||
@@ -31,7 +40,8 @@ test_system = ModuleSystem(
|
|||||||
debug=True,
|
debug=True,
|
||||||
xqueue={'interface': None, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 10},
|
xqueue={'interface': None, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 10},
|
||||||
node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
|
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,
|
force_save_button=None,
|
||||||
attempts=None,
|
attempts=None,
|
||||||
problem_state=None,
|
problem_state=None,
|
||||||
|
correct=False
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
All parameters are optional, and are added to the created problem if specified.
|
All parameters are optional, and are added to the created problem if specified.
|
||||||
@@ -58,6 +59,7 @@ class CapaFactory(object):
|
|||||||
module.
|
module.
|
||||||
|
|
||||||
attempts: also added to instance state. Will be converted to an int.
|
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, }
|
definition = {'data': CapaFactory.sample_problem_xml, }
|
||||||
location = Location(["i4x", "edX", "capa_test", "problem",
|
location = Location(["i4x", "edX", "capa_test", "problem",
|
||||||
@@ -81,10 +83,19 @@ class CapaFactory(object):
|
|||||||
instance_state_dict = {}
|
instance_state_dict = {}
|
||||||
if problem_state is not None:
|
if problem_state is not None:
|
||||||
instance_state_dict = problem_state
|
instance_state_dict = problem_state
|
||||||
|
|
||||||
if attempts is not None:
|
if attempts is not None:
|
||||||
# converting to int here because I keep putting "0" and "1" in the tests
|
# converting to int here because I keep putting "0" and "1" in the tests
|
||||||
# since everything else is a string.
|
# since everything else is a string.
|
||||||
instance_state_dict['attempts'] = int(attempts)
|
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:
|
if len(instance_state_dict) > 0:
|
||||||
instance_state = json.dumps(instance_state_dict)
|
instance_state = json.dumps(instance_state_dict)
|
||||||
else:
|
else:
|
||||||
@@ -94,13 +105,16 @@ class CapaFactory(object):
|
|||||||
definition, descriptor,
|
definition, descriptor,
|
||||||
instance_state, None, metadata=metadata)
|
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
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CapaModuleTest(unittest.TestCase):
|
class CapaModuleTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
day_delta = datetime.timedelta(days=1)
|
day_delta = datetime.timedelta(days=1)
|
||||||
@@ -120,6 +134,18 @@ class CapaModuleTest(unittest.TestCase):
|
|||||||
self.assertNotEqual(module.url_name, other_module.url_name,
|
self.assertNotEqual(module.url_name, other_module.url_name,
|
||||||
"Factory should be creating unique names for each problem")
|
"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):
|
def test_showanswer_default(self):
|
||||||
"""
|
"""
|
||||||
Make sure the show answer logic does the right thing.
|
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.
|
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',
|
used_all_attempts = CapaFactory.create(showanswer='past_due',
|
||||||
max_attempts="1",
|
max_attempts="1",
|
||||||
attempts="1",
|
attempts="1",
|
||||||
@@ -209,3 +235,50 @@ class CapaModuleTest(unittest.TestCase):
|
|||||||
due=self.yesterday_str,
|
due=self.yesterday_str,
|
||||||
graceperiod=self.two_day_delta_str)
|
graceperiod=self.two_day_delta_str)
|
||||||
self.assertFalse(still_in_grace.answer_available())
|
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,
|
'close_date': None,
|
||||||
's3_interface' : "",
|
's3_interface' : "",
|
||||||
'open_ended_grading_interface' : {},
|
'open_ended_grading_interface' : {},
|
||||||
|
'skip_basic_checks' : False,
|
||||||
}
|
}
|
||||||
definition = Mock()
|
definition = Mock()
|
||||||
descriptor = Mock()
|
descriptor = Mock()
|
||||||
@@ -167,6 +168,7 @@ class OpenEndedModuleTest(unittest.TestCase):
|
|||||||
'close_date': None,
|
'close_date': None,
|
||||||
's3_interface' : test_util_open_ended.S3_INTERFACE,
|
's3_interface' : test_util_open_ended.S3_INTERFACE,
|
||||||
'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
|
'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
|
||||||
|
'skip_basic_checks' : False,
|
||||||
}
|
}
|
||||||
|
|
||||||
oeparam = etree.XML('''
|
oeparam = etree.XML('''
|
||||||
@@ -301,6 +303,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
|
|||||||
'close_date' : "",
|
'close_date' : "",
|
||||||
's3_interface' : test_util_open_ended.S3_INTERFACE,
|
's3_interface' : test_util_open_ended.S3_INTERFACE,
|
||||||
'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
|
'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
|
||||||
|
'skip_basic_checks' : False,
|
||||||
}
|
}
|
||||||
|
|
||||||
oeparam = etree.XML('''
|
oeparam = etree.XML('''
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from fs.osfs import OSFS
|
|||||||
from nose.tools import assert_equals, assert_true
|
from nose.tools import assert_equals, assert_true
|
||||||
from path import path
|
from path import path
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from shutil import copytree
|
import shutil
|
||||||
|
|
||||||
from xmodule.modulestore.xml import XMLModuleStore
|
from xmodule.modulestore.xml import XMLModuleStore
|
||||||
|
|
||||||
@@ -46,11 +46,11 @@ class RoundTripTestCase(unittest.TestCase):
|
|||||||
Thus we make sure that export and import work properly.
|
Thus we make sure that export and import work properly.
|
||||||
'''
|
'''
|
||||||
def check_export_roundtrip(self, data_dir, course_dir):
|
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)
|
print "Copying test course to temp dir {0}".format(root_dir)
|
||||||
|
|
||||||
data_dir = path(data_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"
|
print "Starting import"
|
||||||
initial_import = XMLModuleStore(root_dir, course_dirs=[course_dir])
|
initial_import = XMLModuleStore(root_dir, course_dirs=[course_dir])
|
||||||
@@ -108,6 +108,8 @@ class RoundTripTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
|
self.temp_dir = mkdtemp()
|
||||||
|
self.addCleanup(shutil.rmtree, self.temp_dir)
|
||||||
|
|
||||||
def test_toy_roundtrip(self):
|
def test_toy_roundtrip(self):
|
||||||
self.check_export_roundtrip(DATA_DIR, "toy")
|
self.check_export_roundtrip(DATA_DIR, "toy")
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import json
|
import json
|
||||||
from mock import Mock
|
from mock import Mock, MagicMock
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from xmodule.open_ended_grading_classes.self_assessment_module import SelfAssessmentModule
|
from xmodule.open_ended_grading_classes.self_assessment_module import SelfAssessmentModule
|
||||||
from xmodule.modulestore import Location
|
from xmodule.modulestore import Location
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from nose.plugins.skip import SkipTest
|
|
||||||
|
|
||||||
from . import test_system
|
from . import test_system
|
||||||
|
|
||||||
@@ -51,6 +50,7 @@ class SelfAssessmentTest(unittest.TestCase):
|
|||||||
'close_date': None,
|
'close_date': None,
|
||||||
's3_interface' : test_util_open_ended.S3_INTERFACE,
|
's3_interface' : test_util_open_ended.S3_INTERFACE,
|
||||||
'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
|
'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
|
||||||
|
'skip_basic_checks' : False,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.module = SelfAssessmentModule(test_system, self.location,
|
self.module = SelfAssessmentModule(test_system, self.location,
|
||||||
@@ -63,13 +63,29 @@ class SelfAssessmentTest(unittest.TestCase):
|
|||||||
self.assertTrue("This is sample prompt text" in html)
|
self.assertTrue("This is sample prompt text" in html)
|
||||||
|
|
||||||
def test_self_assessment_flow(self):
|
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.assertEqual(self.module.get_score()['score'], 0)
|
||||||
|
|
||||||
self.module.save_answer({'student_answer': "I am an answer"}, test_system)
|
self.module.save_answer({'student_answer': "I am an answer"}, test_system)
|
||||||
self.assertEqual(self.module.state, self.module.ASSESSING)
|
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)
|
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
|
# if we now assess as right, skip the REQUEST_HINT state
|
||||||
self.module.save_answer({'student_answer': 'answer 4'}, test_system)
|
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)
|
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
|
return templates
|
||||||
|
|
||||||
|
|
||||||
class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
|
class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
|
||||||
"""
|
"""
|
||||||
An XModuleDescriptor is a specification for an element of a course. This
|
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):
|
def inherit_metadata(self, metadata):
|
||||||
"""
|
"""
|
||||||
Updates this module with metadata inherited from a containing module.
|
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
|
be inherited
|
||||||
"""
|
"""
|
||||||
# Set all inheritable metadata from kwargs that are
|
# 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:
|
for attr in self.inheritable_metadata:
|
||||||
if attr not in self.metadata and attr in metadata:
|
if attr not in self.metadata and attr in metadata:
|
||||||
self._inherited_metadata.add(attr)
|
self._inherited_metadata.add(attr)
|
||||||
|
|||||||
@@ -128,8 +128,7 @@ class XmlDescriptor(XModuleDescriptor):
|
|||||||
'graded': bool_map,
|
'graded': bool_map,
|
||||||
'hide_progress_tab': bool_map,
|
'hide_progress_tab': bool_map,
|
||||||
'allow_anonymous': bool_map,
|
'allow_anonymous': bool_map,
|
||||||
'allow_anonymous_to_peers': bool_map,
|
'allow_anonymous_to_peers': bool_map
|
||||||
'weight': int_map
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ if Backbone?
|
|||||||
url = DiscussionUtil.urlFor 'threads'
|
url = DiscussionUtil.urlFor 'threads'
|
||||||
when 'followed'
|
when 'followed'
|
||||||
url = DiscussionUtil.urlFor 'followed_threads', options.user_id
|
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_key'] = sort_options.sort_key || 'date'
|
||||||
data['sort_order'] = sort_options.sort_order || 'desc'
|
data['sort_order'] = sort_options.sort_order || 'desc'
|
||||||
DiscussionUtil.safeAjax
|
DiscussionUtil.safeAjax
|
||||||
|
|||||||
@@ -70,10 +70,21 @@ if Backbone?
|
|||||||
DiscussionUtil.loadRoles(response.roles)
|
DiscussionUtil.loadRoles(response.roles)
|
||||||
allow_anonymous = response.allow_anonymous
|
allow_anonymous = response.allow_anonymous
|
||||||
allow_anonymous_to_peers = response.allow_anonymous_to_peers
|
allow_anonymous_to_peers = response.allow_anonymous_to_peers
|
||||||
|
cohorts = response.cohorts
|
||||||
# $elem.html("Hide Discussion")
|
# $elem.html("Hide Discussion")
|
||||||
@discussion = new Discussion()
|
@discussion = new Discussion()
|
||||||
@discussion.reset(response.discussion_data, {silent: false})
|
@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
|
if @$('section.discussion').length
|
||||||
@$('section.discussion').replaceWith($discussion)
|
@$('section.discussion').replaceWith($discussion)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ if Backbone?
|
|||||||
"click .browse-topic-drop-search-input": "ignoreClick"
|
"click .browse-topic-drop-search-input": "ignoreClick"
|
||||||
"click .post-list .list-item a": "threadSelected"
|
"click .post-list .list-item a": "threadSelected"
|
||||||
"click .post-list .more-pages a": "loadMorePages"
|
"click .post-list .more-pages a": "loadMorePages"
|
||||||
|
"change .cohort-options": "chooseCohort"
|
||||||
'keyup .browse-topic-drop-search-input': DiscussionFilter.filterDrop
|
'keyup .browse-topic-drop-search-input': DiscussionFilter.filterDrop
|
||||||
|
|
||||||
initialize: ->
|
initialize: ->
|
||||||
@@ -128,10 +129,20 @@ if Backbone?
|
|||||||
switch @mode
|
switch @mode
|
||||||
when 'search'
|
when 'search'
|
||||||
options.search_text = @current_search
|
options.search_text = @current_search
|
||||||
|
if @group_id
|
||||||
|
options.group_id = @group_id
|
||||||
when 'followed'
|
when 'followed'
|
||||||
options.user_id = window.user.id
|
options.user_id = window.user.id
|
||||||
|
options.group_id = "all"
|
||||||
when 'commentables'
|
when 'commentables'
|
||||||
options.commentable_ids = @discussionIds
|
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})
|
@collection.retrieveAnotherPage(@mode, options, {sort_key: @sortBy})
|
||||||
|
|
||||||
renderThread: (thread) =>
|
renderThread: (thread) =>
|
||||||
@@ -263,13 +274,25 @@ if Backbone?
|
|||||||
if discussionId == "#all"
|
if discussionId == "#all"
|
||||||
@discussionIds = ""
|
@discussionIds = ""
|
||||||
@$(".post-search-field").val("")
|
@$(".post-search-field").val("")
|
||||||
|
@$('.cohort').show()
|
||||||
@retrieveAllThreads()
|
@retrieveAllThreads()
|
||||||
else if discussionId == "#following"
|
else if discussionId == "#following"
|
||||||
@retrieveFollowed(event)
|
@retrieveFollowed(event)
|
||||||
|
@$('.cohort').hide()
|
||||||
else
|
else
|
||||||
discussionIds = _.map item.find(".board-name[data-discussion_id]"), (board) -> $(board).data("discussion_id").id
|
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) ->
|
retrieveDiscussion: (discussion_id, callback=null) ->
|
||||||
url = DiscussionUtil.urlFor("retrieve_discussion", discussion_id)
|
url = DiscussionUtil.urlFor("retrieve_discussion", discussion_id)
|
||||||
DiscussionUtil.safeAjax
|
DiscussionUtil.safeAjax
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ if Backbone?
|
|||||||
@$delegateElement = @$local
|
@$delegateElement = @$local
|
||||||
|
|
||||||
render: ->
|
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')
|
if not @model.has('abbreviatedBody')
|
||||||
@abbreviateBody()
|
@abbreviateBody()
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ if Backbone?
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
title = @$(".new-post-title").val()
|
title = @$(".new-post-title").val()
|
||||||
body = @$(".new-post-body").find(".wmd-input").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
|
# TODO tags: commenting out til we know what to do with them
|
||||||
#tags = @$(".new-post-tags").val()
|
#tags = @$(".new-post-tags").val()
|
||||||
@@ -45,6 +46,7 @@ if Backbone?
|
|||||||
data:
|
data:
|
||||||
title: title
|
title: title
|
||||||
body: body
|
body: body
|
||||||
|
group_id: group
|
||||||
|
|
||||||
# TODO tags: commenting out til we know what to do with them
|
# TODO tags: commenting out til we know what to do with them
|
||||||
#tags: tags
|
#tags: tags
|
||||||
|
|||||||
@@ -14,8 +14,14 @@ if Backbone?
|
|||||||
@setSelectedTopic()
|
@setSelectedTopic()
|
||||||
|
|
||||||
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
|
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "new-post-body"
|
||||||
|
|
||||||
@$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
|
@$(".new-post-tags").tagsInput DiscussionUtil.tagsInputOptions()
|
||||||
|
|
||||||
|
if @$($(".topic_menu li a")[0]).attr('cohorted') != "True"
|
||||||
|
$('.choose-cohort').hide();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
events:
|
events:
|
||||||
"submit .new-post-form": "createPost"
|
"submit .new-post-form": "createPost"
|
||||||
"click .topic_dropdown_button": "toggleTopicDropdown"
|
"click .topic_dropdown_button": "toggleTopicDropdown"
|
||||||
@@ -65,6 +71,11 @@ if Backbone?
|
|||||||
@topicText = @getFullTopicName($target)
|
@topicText = @getFullTopicName($target)
|
||||||
@topicId = $target.data('discussion_id')
|
@topicId = $target.data('discussion_id')
|
||||||
@setSelectedTopic()
|
@setSelectedTopic()
|
||||||
|
if $target.attr('cohorted') == "True"
|
||||||
|
$('.choose-cohort').show();
|
||||||
|
else
|
||||||
|
$('.choose-cohort').hide();
|
||||||
|
|
||||||
|
|
||||||
setSelectedTopic: ->
|
setSelectedTopic: ->
|
||||||
@dropdownButton.html(@fitName(@topicText) + ' <span class="drop-arrow">▾</span>')
|
@dropdownButton.html(@fitName(@topicText) + ' <span class="drop-arrow">▾</span>')
|
||||||
@@ -116,6 +127,7 @@ if Backbone?
|
|||||||
title = @$(".new-post-title").val()
|
title = @$(".new-post-title").val()
|
||||||
body = @$(".new-post-body").find(".wmd-input").val()
|
body = @$(".new-post-body").find(".wmd-input").val()
|
||||||
tags = @$(".new-post-tags").val()
|
tags = @$(".new-post-tags").val()
|
||||||
|
group = @$(".new-post-group option:selected").attr("value")
|
||||||
|
|
||||||
anonymous = false || @$("input.discussion-anonymous").is(":checked")
|
anonymous = false || @$("input.discussion-anonymous").is(":checked")
|
||||||
anonymous_to_peers = false || @$("input.discussion-anonymous-to-peers").is(":checked")
|
anonymous_to_peers = false || @$("input.discussion-anonymous-to-peers").is(":checked")
|
||||||
@@ -137,6 +149,7 @@ if Backbone?
|
|||||||
anonymous: anonymous
|
anonymous: anonymous
|
||||||
anonymous_to_peers: anonymous_to_peers
|
anonymous_to_peers: anonymous_to_peers
|
||||||
auto_subscribe: follow
|
auto_subscribe: follow
|
||||||
|
group_id: group
|
||||||
error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors"))
|
error: DiscussionUtil.formErrorHandler(@$(".new-post-form-errors"))
|
||||||
success: (response, textStatus) =>
|
success: (response, textStatus) =>
|
||||||
# TODO: Move this out of the callback, this makes it feel sluggish
|
# 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>')
|
$(".remove", tr).html('<a href="#">remove</a>')
|
||||||
.click(function() {
|
.click(function() {
|
||||||
remove_user_from_cohort(item.username, current_cohort_id, tr);
|
remove_user_from_cohort(item.username, current_cohort_id, tr);
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
detail_users.append(tr);
|
detail_users.append(tr);
|
||||||
@@ -217,6 +218,7 @@ var CohortManager = (function ($) {
|
|||||||
show_cohorts_button.click(function() {
|
show_cohorts_button.click(function() {
|
||||||
state = state_summary;
|
state = state_summary;
|
||||||
render();
|
render();
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
add_cohort_input.change(function() {
|
add_cohort_input.change(function() {
|
||||||
@@ -231,12 +233,14 @@ var CohortManager = (function ($) {
|
|||||||
var add_url = url + '/add';
|
var add_url = url + '/add';
|
||||||
data = {'name': add_cohort_input.val()}
|
data = {'name': add_cohort_input.val()}
|
||||||
$.post(add_url, data).done(added_cohort);
|
$.post(add_url, data).done(added_cohort);
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
add_members_button.click(function() {
|
add_members_button.click(function() {
|
||||||
var add_url = detail_url + '/add';
|
var add_url = detail_url + '/add';
|
||||||
data = {'users': users_area.val()}
|
data = {'users': users_area.val()}
|
||||||
$.post(add_url, data).done(added_users);
|
$.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.
|
||||||